当前位置: 首页 > news >正文

Java项目实战记录:雷达数据渲染

目录

  • Java项目实战记录:雷达数据渲染
    • 业务背景
    • 代码逻辑
      • 数据结构
      • 颜色渲染
      • MapContent加载数据并输出截图
    • 完整代码
      • GenerateMapImage地图渲染工具
      • 测试代码
    • 渲染效果

Java项目实战记录:雷达数据渲染

业务背景

我之前已经成功使用Java语言解析了C++处理的雷达数据文件,并提取了其中的经纬度点数据。这些文件中的点数据量相当庞大,每个雷达产品文件通常包含超过20万个数据点,有时甚至更多。

目前面临着如何在前端展示这20万个点数据的挑战。有两种可行的解决方案:一种是直接将所有点数据传输到前端进行渲染;另一种则是在后端将点数据渲染成PNG图片,然后再将图片传输到前端展示。经过考虑,决定采用第二种方案,即在后端进行数据的渲染处理。这样不仅可以有效减轻前端的负担,提高数据处理和展示的效率,而且还可以通过优化渲染过程来提高整体的展示质量。

代码逻辑

这里,选用了GeoTools这个强大的开源Java库来执行雷达数据的渲染任务。GeoTools是专为地理空间数据处理设计的工具集,它提供了丰富的API来支持地理数据的读取、查询、分析和展示。利用GeoTools,能够高效地将雷达扫描得到的点数据转换为图像,并以PNG格式保存。

数据结构

雷达数据结构如下所示:

import lombok.Data;/*** 雷达点数据信息*/
@Data
public class LonLatData {private float lon; // 经度private float lat;  // 纬度private double val;     // 回拨强度 (等于value/100)
}

image-20240316095554008

颜色渲染

根据回拨强度需要渲染不同颜色,定义一个Style样式,具体分类如下所示:

   /* * 根据数值创建基于值的点样式。** @return 样式对象,根据不同的值范围应用不同的颜色。*/public static Style createValueBasedPointStyle() {// 创建一个黑色描边,宽度为1。Stroke stroke = styleFactory.createStroke(filterFactory.literal(Color.BLACK), filterFactory.literal(1));FeatureTypeStyle fts = styleFactory.createFeatureTypeStyle();// 定义值的范围及其对应的颜色(十六进制表示)。Object[][] valueRanges = {{-5, Double.NEGATIVE_INFINITY, "#00acb8"},{0, -5, "#c1c0ac"},{10, 0, "#7a71fe"},{15, 10, "#1e26e3"},{20, 15, "#a6fcbb"},{25, 20, "#00ea15"},{30, 25, "#10932d"},{35, 30, "#fcf477"},{40, 35, "#c9c815"},{45, 40, "#8c8c12"},{50, 45, "#feadc0"},{55, 50, "#ff6370"},{60, 55, "#ee0244"},{65, 60, "#d48eff"},{Double.POSITIVE_INFINITY, 65, "#ab23ff"}};// 遍历每个值范围,为每个范围创建一个规则并添加到FeatureTypeStyle。for (Object[] range : valueRanges) {fts.rules().add(createRuleForRange(Double.parseDouble(range[1].toString()), Double.parseDouble(range[0].toString()), range[2].toString()));}// 创建样式并添加FeatureTypeStyle。Style style = styleFactory.createStyle();style.featureTypeStyles().add(fts);return style;}

image-20240316101134737

MapContent加载数据并输出截图

首先创建了一个MapContent对象,用它作为整个地图的内容载体。接着,创建了一个包含所有雷达数据点的图层,并为这些点指定了样式,这样做旨在通过视觉上的区分,提高数据的可读性。然后,将这个图层添加到MapContent中,最后使用GeoTools的功能,将这些内容渲染成一张清晰展示雷达数据的PNG图片。

    /*** 根据坐标和值创建地图图像。** @param coordinates 坐标对象列表,每个对象包含坐标点和与之相关联的值。* @param outputPath  图像输出路径,指定生成的地图图像保存的位置。* @throws Exception 抛出异常,处理文件操作或渲染过程中可能出现的错误。*/public static void createMapImageVal(ArrayList<CoordinatePojo> coordinates, String outputPath) throws Exception {// 步骤1: 定义SimpleFeatureTypeSimpleFeatureTypeBuilder typeBuilder = new SimpleFeatureTypeBuilder();typeBuilder.setName("Point");typeBuilder.setCRS(DefaultGeographicCRS.WGS84); // 坐标参考系统typeBuilder.add("location", Point.class); // 添加位置字段typeBuilder.add("value", Double.class); // 添加值字段SimpleFeatureType TYPE = typeBuilder.buildFeatureType(); // 构建特征类型// 步骤2: 创建SimpleFeatureCollectionDefaultFeatureCollection featureCollection = new DefaultFeatureCollection("internal", TYPE);GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(TYPE);// 为每个坐标点创建特征并添加到集合中for (CoordinatePojo data : coordinates) {Point point = geometryFactory.createPoint(data.getCoordinate()); // 根据坐标创建点// 根据点的值创建SimpleFeatureSimpleFeature feature = SimpleFeatureBuilder.build(TYPE, new Object[]{point, data.getValue()}, null);featureCollection.add(feature); // 将特征添加到集合}// 创建基于值的点样式Style style = createValueBasedPointStyle(); // 假设这是一个自定义方法创建基于值的样式// 步骤3: 定义样式// 这里我们使用了自定义的创建样式方法,而不是SLD.createPointStyle// 步骤4: 渲染图像MapContent mapContent = new MapContent();mapContent.setTitle("Sample Map"); // 设置地图标题Layer layer = new FeatureLayer(featureCollection, style); // 创建图层mapContent.addLayer(layer); // 将图层添加到地图内容GTRenderer renderer = new StreamingRenderer();renderer.setMapContent(mapContent); // 设置地图内容Rectangle imageBounds = null;ReferencedEnvelope mapBounds = null;try {mapBounds = mapContent.getMaxBounds(); // 获取最大边界double heightToWidth = mapBounds.getSpan(1) / mapBounds.getSpan(0); // 计算高宽比imageBounds = new Rectangle(0, 0, 800, (int) Math.round(800 * heightToWidth)); // 设置图像边界} catch (Exception e) {// 处理可能的异常}BufferedImage image = new BufferedImage(imageBounds.width, imageBounds.height, BufferedImage.TYPE_INT_ARGB);Graphics2D gr = image.createGraphics();gr.setPaint(Color.WHITE);gr.fill(imageBounds); // 填充背景色renderer.paint(gr, imageBounds, mapBounds); // 渲染图像File file = new File(outputPath); // 创建文件ImageIO.write(image, "png", file); // 写入图像文件gr.dispose(); // 释放资源mapContent.dispose(); // 释放地图内容}

完整代码

GenerateMapImage地图渲染工具

import com.zykj.radar_server.datahandle.RadarDataParser;
import com.zykj.radar_server.datahandle.pojo.LonLatData;
import com.zykj.radar_server.entity.pojo.CoordinatePojo;
import com.zykj.radar_server.entity.pojo.RadarDataPojo;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.feature.DefaultFeatureCollection;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.FeatureLayer;
import org.geotools.map.Layer;
import org.geotools.map.MapContent;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.renderer.GTRenderer;
import org.geotools.renderer.lite.StreamingRenderer;
import org.geotools.styling.SLD;
import org.geotools.styling.Stroke;
import org.geotools.styling.Style;import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.List;import org.geotools.styling.*;
import org.opengis.filter.FilterFactory2;public class GenerateMapImage {/*** 根据坐标和值创建地图图像。** @param coordinates 坐标对象列表,每个对象包含坐标点和与之相关联的值。* @param outputPath  图像输出路径,指定生成的地图图像保存的位置。* @throws Exception 抛出异常,处理文件操作或渲染过程中可能出现的错误。*/public static void createMapImageVal(ArrayList<CoordinatePojo> coordinates, String outputPath) throws Exception {// 步骤1: 定义SimpleFeatureTypeSimpleFeatureTypeBuilder typeBuilder = new SimpleFeatureTypeBuilder();typeBuilder.setName("Point");typeBuilder.setCRS(DefaultGeographicCRS.WGS84); // 坐标参考系统typeBuilder.add("location", Point.class); // 添加位置字段typeBuilder.add("value", Double.class); // 添加值字段SimpleFeatureType TYPE = typeBuilder.buildFeatureType(); // 构建特征类型// 步骤2: 创建SimpleFeatureCollectionDefaultFeatureCollection featureCollection = new DefaultFeatureCollection("internal", TYPE);GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(TYPE);// 为每个坐标点创建特征并添加到集合中for (CoordinatePojo data : coordinates) {Point point = geometryFactory.createPoint(data.getCoordinate()); // 根据坐标创建点// 根据点的值创建SimpleFeatureSimpleFeature feature = SimpleFeatureBuilder.build(TYPE, new Object[]{point, data.getValue()}, null);featureCollection.add(feature); // 将特征添加到集合}// 创建基于值的点样式Style style = createValueBasedPointStyle(); // 假设这是一个自定义方法创建基于值的样式// 步骤3: 定义样式// 这里我们使用了自定义的创建样式方法,而不是SLD.createPointStyle// 步骤4: 渲染图像MapContent mapContent = new MapContent();mapContent.setTitle("Sample Map"); // 设置地图标题Layer layer = new FeatureLayer(featureCollection, style); // 创建图层mapContent.addLayer(layer); // 将图层添加到地图内容GTRenderer renderer = new StreamingRenderer();renderer.setMapContent(mapContent); // 设置地图内容Rectangle imageBounds = null;ReferencedEnvelope mapBounds = null;try {mapBounds = mapContent.getMaxBounds(); // 获取最大边界double heightToWidth = mapBounds.getSpan(1) / mapBounds.getSpan(0); // 计算高宽比imageBounds = new Rectangle(0, 0, 800, (int) Math.round(800 * heightToWidth)); // 设置图像边界} catch (Exception e) {// 处理可能的异常}BufferedImage image = new BufferedImage(imageBounds.width, imageBounds.height, BufferedImage.TYPE_INT_ARGB);Graphics2D gr = image.createGraphics();gr.setPaint(Color.WHITE);gr.fill(imageBounds); // 填充背景色renderer.paint(gr, imageBounds, mapBounds); // 渲染图像File file = new File(outputPath); // 创建文件ImageIO.write(image, "png", file); // 写入图像文件gr.dispose(); // 释放资源mapContent.dispose(); // 释放地图内容}// StyleFactory 用于创建地图样式元素,例如图层的边框、填充等。private static final StyleFactory styleFactory = CommonFactoryFinder.getStyleFactory();// FilterFactory2 用于创建过滤条件,例如,在样式规则中选择符合特定条件的要素。private static final FilterFactory2 filterFactory = CommonFactoryFinder.getFilterFactory2();/*** 根据数值创建基于值的点样式。** @return 样式对象,根据不同的值范围应用不同的颜色。*/public static Style createValueBasedPointStyle() {// 创建一个黑色描边,宽度为1。Stroke stroke = styleFactory.createStroke(filterFactory.literal(Color.BLACK), filterFactory.literal(1));FeatureTypeStyle fts = styleFactory.createFeatureTypeStyle();// 定义值的范围及其对应的颜色(十六进制表示)。Object[][] valueRanges = {{-5, Double.NEGATIVE_INFINITY, "#00acb8"},{0, -5, "#c1c0ac"},{10, 0, "#7a71fe"},{15, 10, "#1e26e3"},{20, 15, "#a6fcbb"},{25, 20, "#00ea15"},{30, 25, "#10932d"},{35, 30, "#fcf477"},{40, 35, "#c9c815"},{45, 40, "#8c8c12"},{50, 45, "#feadc0"},{55, 50, "#ff6370"},{60, 55, "#ee0244"},{65, 60, "#d48eff"},{Double.POSITIVE_INFINITY, 65, "#ab23ff"}};// 遍历每个值范围,为每个范围创建一个规则并添加到FeatureTypeStyle。for (Object[] range : valueRanges) {fts.rules().add(createRuleForRange(Double.parseDouble(range[1].toString()), Double.parseDouble(range[0].toString()), range[2].toString()));}// 创建样式并添加FeatureTypeStyle。Style style = styleFactory.createStyle();style.featureTypeStyles().add(fts);return style;}/*** 根据给定的值范围和颜色创建一个规则。** @param min 范围的最小值。* @param max 范围的最大值。* @param hexColor 颜色的十六进制表示。* @return 样式规则,包含一个基于值范围的符号化表示。*/public static Rule createRuleForRange(double min, Object max, String hexColor) {// 根据十六进制颜色创建填充,并设置不透明度为1.0。Fill fill = styleFactory.createFill(filterFactory.literal(hexToColor(hexColor)), filterFactory.literal(1.0));// 获取默认的圆形标记,并设置填充和描边。Mark mark = styleFactory.getCircleMark();mark.setFill(fill);mark.setStroke(null); // 不使用描边。// 创建图形对象,清除默认的图形符号,添加自定义的标记。Graphic graphic = styleFactory.createDefaultGraphic();graphic.graphicalSymbols().clear();graphic.graphicalSymbols().add(mark);graphic.setSize(filterFactory.literal(4)); // 设置图形大小。// 创建点符号器,应用上面的图形。PointSymbolizer symbolizer = styleFactory.createPointSymbolizer(graphic, null);// 创建规则,设置过滤条件为值在指定范围内。Rule rule = styleFactory.createRule();rule.symbolizers().add(symbolizer);rule.setFilter(filterFactory.between(filterFactory.property("value"),filterFactory.literal(min),filterFactory.literal(max)));return rule;}/*** 将十六进制颜色字符串转换为Color对象。** @param hex 十六进制颜色字符串(例如,"#ff0000"表示红色)。* @return 转换后的Color对象。*/public static Color hexToColor(String hex) {// 将十六进制字符串转换为整数,并创建颜色对象。// 这里没有添加前缀"FF",因为Color类的构造函数默认处理不透明颜色。return new Color(Integer.parseInt(hex.substring(1), 16));}}

测试代码

测试数据自行准备,如何解析雷达数据请看我上一篇博客。

package com.zykj.radar_server.data;import com.zykj.radar_server.datahandle.GenerateMapImage;
import com.zykj.radar_server.datahandle.RadarDataParser;
import com.zykj.radar_server.datahandle.pojo.LonLatData;
import com.zykj.radar_server.entity.pojo.CoordinatePojo;
import com.zykj.radar_server.entity.pojo.RadarDataPojo;import org.junit.jupiter.api.Test;
import org.locationtech.jts.geom.Coordinate;
import org.springframework.boot.test.context.SpringBootTest;import java.util.ArrayList;
import java.util.List;@SpringBootTest
public class GenerateMapImageTest {@Testpublic void test() throws Exception {String filePath = "D:\\雷达测试数据\\数据\\20190305_110801_ZHBJ_Z_VOL_2.50.dat";RadarDataPojo radarDataPojo = RadarDataParser.handlePolarProductForPojo(filePath);ArrayList<LonLatData> lonLatDataList = radarDataPojo.getLonLatDataList();System.out.println(lonLatDataList.size());ArrayList<CoordinatePojo> coordinates = new ArrayList<>();for (int i = 0; i < lonLatDataList.size(); i++) {CoordinatePojo coordinatePojo = new CoordinatePojo();coordinatePojo.setCoordinate(new Coordinate(lonLatDataList.get(i).getLon(), lonLatDataList.get(i).getLat()));double val = lonLatDataList.get(i).getVal();coordinatePojo.setValue(val);coordinates.add(coordinatePojo);}// 指定输出图片路径String outputPath = "D:\\temp\\layers\\jiangsu_map_val4.png";GenerateMapImage.createMapImageVal(coordinates, outputPath);System.out.println("地图图片已生成: " + outputPath);}}

渲染效果

雷达数据渲染效果如下所示,颜色越深回波强度越大:

image-20240316095823124

渲染点的大小可自行调节。

image-20240316094446348

http://www.lryc.cn/news/320249.html

相关文章:

  • 进程的概念 | PCB | Linux下的task_struct | 父子进程和子进程
  • 【GPT-SOVITS-03】SOVITS 模块-生成模型解析
  • 2024HVV行动-进军蓝中研判(log4j2、fastjson、Struts2、Shiro)
  • 亮点抢先看!4月16-17日,百度Create大会开设“AI公开课”,大咖带你打造赚钱工具
  • 【笔记本清灰/实用经验】荣耀Magicbook14-2020款-R5-4500U-清灰实战
  • 如何写好Stable Diffusion的prompt
  • 计算机毕业设计 | SpringBoot+vue 移动端社区物业管理系统(附源码+论文)
  • 玩转C语言——数组初探
  • Nginx指令配置大全
  • 富格林:安全出金关注可信操作
  • DELETE、TRUNCATE 和 DROP 在MySQL中的区别及使用示例
  • 程序员应该如何选择职业赛道?
  • 深入浅出Hive性能优化策略
  • 利用卷积神经网络进行人脸识别
  • 固态硬盘有坏道怎么恢复数据 固态硬盘坏道怎么修复
  • adobe animate 时间轴找不到编辑多个帧按钮
  • 5 亿欧元巨额奖励!法国国防部启动量子初创公司项目
  • Linux:系统初始化,内核优化,性能优化(2)
  • JS08-DOM节点
  • 2024/3/14打卡棋子(14届蓝桥杯)——差分
  • A Survey on Multimodal Large Language Models
  • Java面向对象编程(高级)一
  • 1056:点和正方形的关系
  • 【iOS】ARC学习
  • 数据分析 | Matplotlib
  • mac npm install 很慢或报错
  • 100天精通Python(实用脚本篇)——第118天:基于selenium和ddddocr库实现反反爬策略之验证码识别
  • 51单片机与ARM单片机的区别
  • Android 10.0 mtk平台系统添加公共so库的配置方法
  • simulink平面五杆机构运动学仿真