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 true
    private boolean deleteFlag;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date updateTime;

}

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

    //读取wkt数据为LineString
    public 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工具做线的缓冲区,扩展宽度为10
    public  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";
        }
    }
}

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

运行效果测试

相关推荐
Miketutu17 分钟前
Spring MVC消息转换器
java·spring
乔冠宇18 分钟前
Java手写简单Merkle树
java·区块链·merkle树
小王子102431 分钟前
设计模式Python版 组合模式
python·设计模式·组合模式
LUCIAZZZ1 小时前
简单的SQL语句的快速复习
java·数据库·sql
komo莫莫da2 小时前
寒假刷题Day19
java·开发语言
Mason Lin2 小时前
2025年1月22日(网络编程 udp)
网络·python·udp
清弦墨客2 小时前
【蓝桥杯】43697.机器人塔
python·蓝桥杯·程序算法
S-X-S2 小时前
算法总结-数组/字符串
java·数据结构·算法
linwq82 小时前
设计模式学习(二)
java·学习·设计模式
桦说编程3 小时前
CompletableFuture 超时功能有大坑!使用不当直接生产事故!
java·性能优化·函数式编程·并发编程