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

news2025/2/26 23:04:10

场景

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";
        }
    }
}

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

运行效果测试

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/990587.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

分布式、锁、延时任务

1. redission 2.zk 2.1 指令 ls / / 下有哪些子节点 get /zookeeper 查看某个子节点内容 create /aa “test” delete /aa set /aa “test01” 2.2 创建节点 模式 默认创建永久 create -e 创建临时 create -e /zz “hello zz” create -s 创建 有序节点 create -s -e 临…

测试平台项目部署一(手动部署)

手动部署 一、项目框架图1、首先创建一个桥接网络:2、redis3、启动mariadb4、跨域配置5、JWT配置6、celery配置7、启动ck14_django 容器8、安装gunicorn9、数据库迁移10、创建用户11、添加工作进程12、验证异步执行任务、定时执行任务通过二、supervisor1、安装2、创建配置文件…

开源|HDR-ISP开源项目介绍

引言 拖更很久了&#xff0c;本着出品必精的原则&#xff0c;我们更新就来点干货。想起刚入行时&#xff0c;网上并没有很多以及系统的ISP的学习资料&#xff0c;都是边工作、边搜集资料然后边学习&#xff0c;一路坎坎坷坷走到今天算是刚入了ISP的大门。 为了解决新人入门的…

openGauss学习笔记-63 openGauss 数据库管理-资源池化架构

文章目录 openGauss学习笔记-63 openGauss 数据库管理-资源池化架构 openGauss学习笔记-63 openGauss 数据库管理-资源池化架构 本文档主要介绍资源池化架构下的一些最佳实践和使用注意事项&#xff0c;用于支撑对相关特性感兴趣的开发者可以快速部署、实践或进行定制化开发。…

【时空融合:改进MRA】

Multiresolution Analysis Pansharpening Based on Variation Factor for Multispectral and Panchromatic Images From Different Times &#xff08;基于变化因子的多光谱和全色图像多分辨率分析&#xff09; 大多数泛锐化方法是将同一区域上同时获取的原始低分辨率多光谱(M…

Redis的数据持久化方案

目录 前言 RDB方式 概述&#xff1a; 1.RDB手动 &#xff12;.RDB自动 RDB优缺点 AOF方式 概述 AOF写数据的三种策略 AOF相关配置 AOF重写 AOF重写方式 手动重写 bgrewriteaof 自动重写 总结 前言 Redis是一个内存型数据库&#xff0c;也就是说如果不将内存中的…

overleaf 参考文献引用,创建引用目录.bib文件,在文档中引用参考文献,生成参考文献列表

目录 1 创建一个Overleaf项目 2 导入或创建 .bib 文件 2.1 导入 .bib 文件&#xff1a; 参考文献的 .bib文件获取步骤 &#xff08;1&#xff09;打开谷歌学术 &#xff08;2&#xff09;输入文献题目 &#xff08;3&#xff09;点击引用&#xff0c;然后选择BibTex格式…

af-table-column插件的使用 element el-table-column宽度自适应

af-table-column 是一个用于 Vue.js 的表格列组件&#xff0c;用于在表格中定义列的样式和行为。下面是 af-table-column 的使用方法&#xff1a; 首先&#xff0c;确保已经安装了 af-table-column 包。可以使用以下命令进行安装&#xff1a; npm install af-table-column --…

MySQL——事务

一、事务的开始与结束 一个数据库事务由一条或多条sql语句构成&#xff0c;它们形成一个逻辑的工作单元。这些sql语句要么全部执行成功&#xff0c;要么全部执行失败。 1.1.事物的开始 1.对于DDL&#xff08;create&#xff0c;alter&#xff0c;drop&#xff09;和DCL&…

render函数使用和详解

背景 在平时编程时&#xff0c;大部分是通过template来创建html。但是在一些特殊的情况下&#xff0c;使用template方式时&#xff0c;就无法很好的满足需求&#xff0c;在这个时候就需要 通过JavaScript 的编程能力来进行操作。此时&#xff0c;就到了render函数展示拳脚去时…

【Python】迭代器__iter__、__next__

这里主要纠正迭代器的用法&#xff0c;因为一些教程传播错误示例让我很无语。 最大的错误就是&#xff0c;把__iter__和__next写在同个类里&#xff0c;每每看见都感到诧异。不是说这方法不行&#xff0c;主要是&#xff0c;一旦出现预期之外的运行结果往往很难查到原因(因为它…

Nomad 系列-Nomad+Traefik+Tailscale 集成实现零信任安全

系列文章 Nomad 系列文章Traefik 系列文章Tailscale 系列文章 概述 终于到了令人启动的环节了&#xff1a;NomadTraefikTailscale 集成实现零信任安全。 在这里&#xff1a; Nomad 负责容器调度&#xff1b;&#xff08;容器编排工具&#xff09;Traefik 负责入口流量&…

文件导入之Validation校验List对象数组

背景&#xff1a; 我们的接口是一个List对象&#xff0c;对象里面的数据基本都有一些基础数据校验的注解&#xff0c;我们怎么样才能校验这些基础规则呢&#xff1f; 我们在导入excel文件进行数据录入的时候&#xff0c;数据录入也有基础的校验规则&#xff0c;这个时候我们又…

Linux下C语言使用 netlink sockets与内核模块通信

netlink简介 Netlink套接字是用以实现用户进程与内核进程通信的一种特殊的进程间通信(IPC) ,也是网络应用程序与内核通信的最常用的接口。在Linux标准内核中&#xff0c;系统默认集成了很多netlink实例&#xff0c;比如日志上报、路由系统等&#xff0c;netlink消息是双向的&a…

解决 tesserocr报错 Failed to init API, possibly an invalid tessdata path : ./

问题描述 我们在初次使用tesserocr库的时候&#xff0c;可能会报以下错误&#xff1a; RuntimeError: Failed to init API, possibly an invalid tessdata path: ./ 这是因为我们在使用 conda 创建的环境中找不到"tessdata"这个文件夹。 解决办法 这时候把Tessera…

【CMake工具】工具CMake编译轻度使用(C/C++)

目录 CMake编译工具 一、CMake概述 二、CMake的使用 2.1 注释 2.1.1 注释行 2.1.2 注释块 2.2 源文件 2.1.1 共处一室 2.1.2 VIP包房 2.3 私人定制 2.2.1 定义变量 2.2.2 指定使用的C标准 2.2.3 指定输出的路径 2.4 搜索文件 2.3.1 方式1 2.3.2 方式2 2.5 包含…

CRM软件系统能否监控手机的使用

CRM可以监控手机吗&#xff1f;答案是不可以。CRM是一款帮助企业优化业务流程&#xff0c;提高销售效率的工具。例如Zoho CRM&#xff0c;最多也就是听一下销售的通话录音&#xff0c;却不可以监控手机&#xff0c;毕竟CRM不是一款监控软件。 CRM的主要作用有以下几点&#xf…

CASAIM与南京航空航天大学在自动化叶片曲面分析系统开展合作,推动航空航天发动机零部件自动化3D检测进程

近期&#xff0c;CASAIM与南京航空航天大学在自动化叶片曲面分析系统展开深入合作&#xff0c;充分发挥双方在航空航天和智能检测领域优势&#xff0c;共同推动航空航天发动机零部件自动化3D检测进程。 南京航空航天大学创建于1952年10月&#xff0c;是新中国自己创办的第一批…

聚观早报|华为Mate 60 Pro支持面容支付;特斯拉重回底特律车展

【聚观365】9月8日消息 华为Mate 60 Pro已支持面容支付 特斯拉将重回底特律车展 iPhone在美国有1.67亿用户 韩国半导体8月份出口85.6亿美元 比亚迪元PLUS冠军版将于9月15日上市 华为Mate 60 Pro已支持面容支付 毫无预热的华为Mate 60 Pro突然在华为商城首批开售&#xf…

Ansys Zemax | 手机镜头设计 - 第 3 部分:使用 STAR 模块和 ZOS-API 进行 STOP 分析

本文是 3 篇系列文章的一部分&#xff0c;该系列文章将讨论智能手机镜头模组设计的挑战&#xff0c;从概念、设计到制造和结构变形的分析。本文是三部分系列的第三部分。它涵盖了使用 Ansys Zemax OpticStudio Enterprise 版本提供的 STAR 技术对智能手机镜头进行自动的结构、热…