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

Java中使用JTS实现WKT字符串读取转换线、查找LineString的list中距离最近的线、LineString做缓冲区扩展并计算点在缓冲区内的方位角

场景

Java中使用JTS对空间几何计算(读取WKT、距离、点在面内、长度、面积、相交等):

Java中使用JTS对空间几何计算(读取WKT、距离、点在面内、长度、面积、相交等)_jts-core_霸道流氓气质的博客-CSDN博客

Java+GeoTools实现WKT数据根据EPSG编码进行坐标系转换:

Java+GeoTools实现WKT数据根据EPSG编码进行坐标系转换_霸道流氓气质的博客-CSDN博客

基于gis的业务场景中,需要在地图中录入区域数据的wkt数据,然后根据某个坐标点判断是属于哪个区域,

以及距离所属区域中最近的端点的方位角,比如坐标点位于某区域东南方向100米。

注:

博客:
霸道流氓气质_C#,架构之路,SpringBoot-CSDN博客

实现

1、参考上面引入jts的依赖。

首先数据库中存储的所有线的WKT数据为

其中region_name为线的名称,region_wkt为线的wkt字符串。

首先从数据库中读取所有的wkt字符串数据,并转换为map类型数据方便处理以及赋值线的名称到linestring的userData字段。

        List<LineString> regionList = new ArrayList<>();Map<String, List<LineString>> regionMap = new HashMap<>();//读取录入的区域位置信息RegionManagement param = RegionManagement.builder().deleteFlag(false).build();List<RegionManagement> regionManagements = regionManagementMapper.selectList(param);for (RegionManagement regionManagement : regionManagements) {LineString lineString = readWKT(regionManagement.getRegionWKT());RegionDTO regionDTO = JSON.parseObject(JSON.toJSONString(regionManagement), RegionDTO.class);regionDTO.setUpdateTime(regionManagement.getUpdateTime().toString());lineString.setUserData(regionDTO);regionList.add(lineString);}//将区域list流处理为map,方便快速查找Map<String, List<RegionManagement>> collect = regionManagements.stream().collect(Collectors.groupingBy(RegionManagement::getRegionName));for (String name : collect.keySet()) {List<LineString> tmp = new ArrayList<>();collect.get(name).forEach(item -> tmp.add(readWKT(item.getRegionWKT())));regionMap.put(name, tmp);}

这里的RegionManagement用来读取数据库中存储的wkt字符串等数据,实现为

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class RegionManagement {private Long id;private String regionName;private String regionWKT;// 0 false ; 1 trueprivate boolean deleteFlag;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private Date updateTime;}

调用读取wkt字符串并转换为jts的LineString对象的方法readWKT实现为

    //读取wkt数据为LineStringpublic LineString readWKT(String regionWKT){GeometryFactory fact = new GeometryFactory();WKTReader reader = new WKTReader(fact);LineString geometry1 = null;try {geometry1 = (LineString) reader.read(regionWKT);} catch (ParseException e) {e.printStackTrace();}return geometry1;}

中间获取所需要的数据的RegionDTO的实现为

import lombok.Data;@Data
public class RegionDTO {private Long id;private String regionName;private String updateTime;
}

2、将要判断方位的坐标值声明为Point2D对象

        //目标点位Point2D.Double carPoint = new Point2D.Double(36582834.745, 4259820.7951);

3、获取距离目标点位最近的线

        //获取离目标点位最近的线LineString lineString = findNearestLine(carPoint, 10D, regionList);

这里调用的findNearestLine方法的实现

    //查找最近的线,jts工具做线的缓冲区,扩展宽度为10public  LineString findNearestLine(java.awt.geom.Point2D.Double point, Double FuzzyLookupRange, List<LineString> lineStringList) {Point a = createPoint(point.getX(), point.getY());return lineStringList.parallelStream().filter((lineString) -> lineString.buffer(FuzzyLookupRange).contains(a)).min((o1, o2) -> {Double ax = o1.distance(a);Double axx = o2.distance(a);return ax.compareTo(axx);}).orElse(null);}

这里调用了createPoint用来创建point对象

    //根据坐标x y创建点对象public static Point createPoint(Double x, Double y) {GeometryFactory a = JTSFactoryFinder.getGeometryFactory();return a.createPoint(new Coordinate(x, y));}

然后使用lineString.buffer方法对线做缓冲区,扩展宽度为10,即将线向外扩充成类似区域的概念,判断点是否在扩充后

的区域内,如果有多个区域,则取距离最小的一个。

LineString.buffer方法的使用可参考:

Geometry (JTS Topology Suite 1.13 API) - Javadoc Extreme)

Computes a buffer area around this geometry having the given width. The buffer of a Geometry is the Minkowski sum or difference of the geometry

with a disc of radius abs(distance).

Mathematically-exact buffer area boundaries can contain circular arcs.

To represent these arcs using linear geometry they must be approximated with line segments.

The buffer geometry is constructed using 8 segments per quadrant to approximate the circular arcs. The end cap style is CAP_ROUND.

The buffer operation always returns a polygonal result. The negative or zero-distance buffer of lines and points is always an empty Polygon.

 This is also the result for the buffers of degenerate (zero-area) polygons.

直译:

计算具有给定宽度的几何体周围的缓冲区。几何体的缓冲区是具有半径为abs(距离)的圆盘的几何体的Minkowski和或差。

数学上精确的缓冲区边界可以包含圆弧。要使用线性几何图形表示这些圆弧,必须使用线段对其进行近似。

缓冲区几何结构使用每个象限8个线段来近似圆弧。端盖样式为cap_ROUND。

缓冲区操作总是返回多边形结果。直线和点的负或零距离缓冲区始终为空多边形。

这也是退化(零面积)多边形缓冲区的结果。

然后获取距离最近的线的名称并输出

        //获取离目标点位最近的线LineString lineString = findNearestLine(carPoint, 10D, regionList);String regionName = "区域位置为空";if (lineString != null) {RegionDTO userData = (RegionDTO) lineString.getUserData();regionName = userData.getRegionName();}System.out.println(regionName);

4、获取坐标点相对于该线的方位角

        String azimuth;if (!regionName.equals("区域位置为空")) {List<LineString> lineStringList = regionMap.get(regionName);LineString closeLine;if (lineStringList.size() > 1) {closeLine = findNearestLine(carPoint, 10D, lineStringList);} else {closeLine = lineStringList.get(0);}//获取线的两个端点Point startPoint = closeLine.getStartPoint();Point endPoint = closeLine.getEndPoint();//获取点位到两个端点的距离double startDistance = startPoint.distance(createPoint(carPoint.getX(), carPoint.getY()));double endDistance = endPoint.distance(createPoint(carPoint.getX(), carPoint.getY()));//获取较近的点作为参考点判断方位距离if (startDistance <= endDistance) {//获取方位角azimuth = regionName + DirectionUtil.getAzimuth(startPoint.getX(), startPoint.getY(), carPoint.getX(), carPoint.getY()) + "方向路口" + BigDecimal.valueOf(startDistance).intValue() + "米";} else {azimuth = regionName + DirectionUtil.getAzimuth(endPoint.getX(), endPoint.getY(), carPoint.getX(), carPoint.getY()) + "方向路口" + BigDecimal.valueOf(endDistance).intValue() + "米";}} else {azimuth = "[" + carPoint.getX() + "," + carPoint.getY() + "]";}System.out.println(azimuth);

其中获取方位角的工具类DirectionUtil.getAzimuth实现

import org.locationtech.jts.geom.LineSegment;public class DirectionUtil {/*** 笛卡尔坐标系*/enum DirectionEnum {DUE_EAST("正东", "==0 || ==360"),DUE_NORTHEAST("东北", "==45"),DUE_NORTH("正北", "==90"),NORTH_NORTHWEST("西北", "90<theta<135"),DUE_WEST("正西", "==180"),WEST_SOUTHWEST("西南", "180<theta<225"),DUE_SOUTH("正南", "==270"),DUE_SOUTHEAST("东南", "==315");private String direction;private String describe;DirectionEnum(String direction, String describe) {this.direction = direction;this.describe = describe;}public String getDirection() {return direction;}public void setDirection(String direction) {this.direction = direction;}public String getDescribe() {return describe;}public void setDescribe(String describe) {this.describe = describe;}}/*** 获取方位角** @param x1 观测点x* @param y1 观测点y* @param x2 目标点x* @param y2 目标点y* @return 返回距离观测点的方位角*/public static String getAzimuth(double x1, double y1, double x2, double y2) {LineSegment lineSegment = new LineSegment(x1, y1, x2, y2);double angle1 = lineSegment.angle();double angle = Math.toDegrees(lineSegment.angle());if (angle < 0) {angle = angle + 360;}if ((0 < angle && angle < 12.5) || (347.5 < angle && angle < 360)) {return DirectionEnum.DUE_EAST.getDirection();} else if (12.5 < angle && angle < 77.5) {return DirectionEnum.DUE_NORTHEAST.getDirection();} else if (77.5 < angle && angle < 102.5) {return DirectionEnum.DUE_NORTH.getDirection();} else if (102.5 < angle && angle < 167.5) {return DirectionEnum.NORTH_NORTHWEST.getDirection();} else if (167.5 < angle && angle < 192.5) {return DirectionEnum.DUE_WEST.getDirection();} else if (192.5 < angle && angle < 257.5) {return DirectionEnum.WEST_SOUTHWEST.getDirection();} else if (257.5 < angle && angle < 282.5) {return DirectionEnum.DUE_SOUTH.getDirection();} else if (282.5 < angle && angle < 347.5) {return DirectionEnum.WEST_SOUTHWEST.getDirection();} else {return "ERROR";}}
}

逻辑就是对比目标点到线的两个端点的距离,取较近的进行判断,然后做方位角判断。

运行效果测试

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

相关文章:

  • 【异步VS多线程】异步VS多线程区别
  • 【nosql】redis之高可用(主从复制、哨兵、集群)搭建
  • js如何实现数组去重的常用方法
  • XREAL Air 2 Pro发布,加入电致变色技术,拓展AR眼镜使用场景
  • Go基础11-理解Go语言的包导入
  • 【MySQL数据库原理】在MySQL Workbench界面运行SQL代码——学生管理系统
  • 高分三号1米分辨率飞机检测识别数据集
  • Unity 之Material 类型和 MeshRenderer 组件中的 Materials 之间有一些重要的区别
  • 【LeetCode-简单题】977. 有序数组的平方
  • 【笔试强训选择题】Day39.习题(错题)解析
  • Prometheus-Alertmanager 警报管理器-部署和设置
  • 恒运资本:小盘股的优点?投资小盘股要注意哪些方面?
  • LeetCode:2. 两数之和
  • OpenCV(二十四):可分离滤波
  • 【JS进阶】防抖与节流
  • 【css】linear-gradient()的用法
  • java: 读取snakeyaml-1.26.jar各种jar包时出错; error in opening zip file
  • 医疗知识图谱 neo4j
  • 【LeetCode-简单题】367. 有效的完全平方数
  • vben-admin中渲染table表格时怎么处理不同的数据结构
  • 从零开始在树莓派上搭建WordPress博客网站并实现公网访问
  • Go基础18-理解方法的本质以选择正确的receiver类型
  • Go基础12-理解Go语言表达式的求值顺序
  • OJ练习第165题——修车的最少时间
  • 纯前端实现 导入 与 导出 Excel
  • 关于一次两段式提交和数据库恢复数据我的一些想法
  • 阿里巴巴springcloud的gateway网关如何用继承接口WebExceptionHandler定义一个json格式的404错误页面实例
  • 『力扣每日一题07』字符串最后一个单词的长度
  • 成都睿趣科技:抖音开店初期要注意什么
  • QT 5.13保姆级安装教程