• 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字段。

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

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

    1. import com.fasterxml.jackson.annotation.JsonFormat;
    2. import lombok.AllArgsConstructor;
    3. import lombok.Builder;
    4. import lombok.Data;
    5. import lombok.NoArgsConstructor;
    6. import java.util.Date;
    7. @Data
    8. @NoArgsConstructor
    9. @AllArgsConstructor
    10. @Builder
    11. public class RegionManagement {
    12.     private Long id;
    13.     private String regionName;
    14.     private String regionWKT;
    15.     // 0 false ; 1 true
    16.     private boolean deleteFlag;
    17.     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    18.     private Date updateTime;
    19. }

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

    1.     //读取wkt数据为LineString
    2.     public LineString readWKT(String regionWKT){
    3.         GeometryFactory fact = new GeometryFactory();
    4.         WKTReader reader = new WKTReader(fact);
    5.         LineString geometry1 = null;
    6.         try {
    7.             geometry1 = (LineString) reader.read(regionWKT);
    8.         } catch (ParseException e) {
    9.             e.printStackTrace();
    10.         }
    11.         return geometry1;
    12.     }

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

    1. import lombok.Data;
    2. @Data
    3. public class RegionDTO {
    4.     private Long id;
    5.     private String regionName;
    6.     private String updateTime;
    7. }

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

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

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

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

    这里调用的findNearestLine方法的实现

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

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

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

    然后使用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。

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

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

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

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

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

    1.         String azimuth;
    2.         if (!regionName.equals("区域位置为空")) {
    3.             List<LineString> lineStringList = regionMap.get(regionName);
    4.             LineString closeLine;
    5.             if (lineStringList.size() > 1) {
    6.                 closeLine = findNearestLine(carPoint, 10D, lineStringList);
    7.             } else {
    8.                 closeLine = lineStringList.get(0);
    9.             }
    10.             //获取线的两个端点
    11.             Point startPoint = closeLine.getStartPoint();
    12.             Point endPoint = closeLine.getEndPoint();
    13.             //获取点位到两个端点的距离
    14.             double startDistance = startPoint.distance(createPoint(carPoint.getX(), carPoint.getY()));
    15.             double endDistance = endPoint.distance(createPoint(carPoint.getX(), carPoint.getY()));
    16.             //获取较近的点作为参考点判断方位距离
    17.             if (startDistance <= endDistance) {
    18.                 //获取方位角
    19.                 azimuth = regionName + DirectionUtil.getAzimuth(startPoint.getX(), startPoint.getY(), carPoint.getX(), carPoint.getY()) + "方向路口" + BigDecimal.valueOf(startDistance).intValue() + "米";
    20.             } else {
    21.                 azimuth = regionName + DirectionUtil.getAzimuth(endPoint.getX(), endPoint.getY(), carPoint.getX(), carPoint.getY()) + "方向路口" + BigDecimal.valueOf(endDistance).intValue() + "米";
    22.             }
    23.         } else {
    24.             azimuth = "[" + carPoint.getX() + "," + carPoint.getY() + "]";
    25.         }
    26.         System.out.println(azimuth);

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

    1. import org.locationtech.jts.geom.LineSegment;
    2. public class DirectionUtil {
    3.     /**
    4.      * 笛卡尔坐标系
    5.      */
    6.     enum DirectionEnum {
    7.         DUE_EAST("正东", "==0 || ==360"),
    8.         DUE_NORTHEAST("东北", "==45"),
    9.         DUE_NORTH("正北", "==90"),
    10.         NORTH_NORTHWEST("西北", "90),
    11.         DUE_WEST("正西", "==180"),
    12.         WEST_SOUTHWEST("西南", "180),
    13.         DUE_SOUTH("正南", "==270"),
    14.         DUE_SOUTHEAST("东南", "==315");
    15.         private String direction;
    16.         private String describe;
    17.         DirectionEnum(String direction, String describe) {
    18.             this.direction = direction;
    19.             this.describe = describe;
    20.         }
    21.         public String getDirection() {
    22.             return direction;
    23.         }
    24.         public void setDirection(String direction) {
    25.             this.direction = direction;
    26.         }
    27.         public String getDescribe() {
    28.             return describe;
    29.         }
    30.         public void setDescribe(String describe) {
    31.             this.describe = describe;
    32.         }
    33.     }
    34.     /**
    35.      * 获取方位角
    36.      *
    37.      * @param x1 观测点x
    38.      * @param y1 观测点y
    39.      * @param x2 目标点x
    40.      * @param y2 目标点y
    41.      * @return 返回距离观测点的方位角
    42.      */
    43.     public static String getAzimuth(double x1, double y1, double x2, double y2) {
    44.         LineSegment lineSegment = new LineSegment(x1, y1, x2, y2);
    45.         double angle1 = lineSegment.angle();
    46.         double angle = Math.toDegrees(lineSegment.angle());
    47.         if (angle < 0) {
    48.             angle = angle + 360;
    49.         }
    50.         if ((0 < angle && angle < 12.5) || (347.5 < angle && angle < 360)) {
    51.             return DirectionEnum.DUE_EAST.getDirection();
    52.         } else if (12.5 < angle && angle < 77.5) {
    53.             return DirectionEnum.DUE_NORTHEAST.getDirection();
    54.         } else if (77.5 < angle && angle < 102.5) {
    55.             return DirectionEnum.DUE_NORTH.getDirection();
    56.         } else if (102.5 < angle && angle < 167.5) {
    57.             return DirectionEnum.NORTH_NORTHWEST.getDirection();
    58.         } else if (167.5 < angle && angle < 192.5) {
    59.             return DirectionEnum.DUE_WEST.getDirection();
    60.         } else if (192.5 < angle && angle < 257.5) {
    61.             return DirectionEnum.WEST_SOUTHWEST.getDirection();
    62.         } else if (257.5 < angle && angle < 282.5) {
    63.             return DirectionEnum.DUE_SOUTH.getDirection();
    64.         } else if (282.5 < angle && angle < 347.5) {
    65.             return DirectionEnum.WEST_SOUTHWEST.getDirection();
    66.         } else {
    67.             return "ERROR";
    68.         }
    69.     }
    70. }

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

    运行效果测试

  • 相关阅读:
    Shell 脚本详解
    【Python&GIS】解决GIS属性表、矢量字段乱码,中文乱码
    八股文之并发编程
    代码越写越乱?那是因为你没用责任链
    学习笔记8--智能驾驶的功能安全设计之功能安全与ISO 26262标准
    《操作系统》期末客观题梳理
    Winform直接与Wpf交互
    perl use HTTP::Server::Simple 轻量级 http server
    深度解析四大主流软件架构模型:单体架构、分布式应用、微服务与Serverless的优缺点及场景应用
    vscode + conda+ ffmpeg + numpy 的安装方式
  • 原文地址:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/132755202