点线面的智慧:转转JTS技术如何塑造上门履约地理布局

news2024/11/15 13:43:44

1 引言

image.png
如上图所示,在转转上门履约的场景中,上门服务的覆盖区域是在地图上画电子围栏来划定的。这就涉及到一些几何图形的操作和空间关系判断,其中最核心问题就是要解决如何判断位置是否在上门覆盖范围内。下面介绍下 JTS,以及如何通过 JTS 的空间之力来解决这些问题。

2 JTS 介绍

JTS,全称 Java Topology Suite,是一个用于创建和操作向量几何的 Java 库。提供了对几何模型的抽象,以及各种空间操作和空间关系判断,非常强大。

2.1 引入 jar 包

JTS 有多个模块,这里只使用了核心的模块。

  • jts-core:提供几何模型的抽象、空间操作、空间关系判断算法等
  • jts-io-common:提供各种格式描述几何模型的输入输出包,如对 WKT、WKB 等格式
<dependency>
  <groupId>org.locationtech.jts</groupId>
  <artifactId>jts-core</artifactId>
  <version>1.19.0</version>
</dependency>

<dependency>
    <groupId>org.locationtech.jts.io</groupId>
    <artifactId>jts-io-common</artifactId>
    <version>1.19.0</version>
</dependency>

2.2 基本的几何模型

JTS 提供了常见的几何模型抽象,并且各具特点。

模型定义常见应用
点(Point)空间中的单个位置,由一对 x,y 坐标表示兴趣点、事件位置等
多点(MultiPoint)由多个独立的点组成的几何对象表示多个相关但分散的位置,如连锁店分布,多个不同人位置
线(LineString)由一系列点组成的一维几何对象,有起点和终点,中间可以有任意数量的点表示道路、河流等线性特征
多线(MultiLineString)由多个不相连的 LineString 组成的几何对象表示复杂的道路网络、等高线等
多边形(Polygon)由一系列首尾相连的线段围成的平面区域(可以有内部空洞)表示行政区划、建筑物轮廓等
多多边形(MultiPolygon)由多个独立的 Polygon 组成的几何对象,可以表示不相连的多个区域表示群岛、复杂的行政区划
几何集合(GeometryCollection)可以包含任意类型几何对象的集合,最灵活的几何类型,可以混合包含点、线、面等表示复杂的空间场景,如包含多种类型要素的地图

在 JTS 中的各几何模型对象关系如下所示
image.png

在实际应用场景中,最常使用的模型如下

  • 点(Point):表示位置信息,如用户地址位置、工程师位置等
  • 多边形(Polygon)、多多边形(MultiPolygon):用来表示上门履约的覆盖区域

2.3 几何模型的描述格式

WKT(Well-Know Text)格式是一种文本格式,用于描述二维和三维几何对象的空间特征。
WKT 的基本语法格式如下:

几何模型类型 (模型数据)

示例如下所示

点:POINT (282 455)
线:LINESTRING (260 250, 485 248, 520 380)
多边形:POLYGON ((320 390, 370 330, 470 360, 460 430, 375 432, 320 390))

JTS 支持对该格式的读写操作,主要是两个对象WKTReaderWKTWriter,代码示例如下

// 读取wkt描述的几何对象
WKTReader wktReader = new WKTReader();
Geometry point = wktReader.read("POINT (282 455)");
Geometry line = wktReader.read("LINESTRING (260 250, 485 248, 520 380)");
Geometry polygon = wktReader.read("POLYGON ((320 390, 370 330, 470 360, 460 430, 375 432, 320 390))");

// 输出几何对象的wkt描述
WKTWriter wktWriter = new WKTWriter();
System.out.println(wktWriter.write(point));
System.out.println(wktWriter.write(line));
System.out.println(wktWriter.write(polygon));

2.4 空间关系

JTS 中的空间关系是基于 DE-9IM(Dimensionally Extended Nine-Intersection Model)模型定义的,这里列举常见的空间关系

空间关系定义
相等 (Equals)两个几何对象在拓扑上相等
相离 (Disjoint)两个几何对象没有任何共同点
相交 (Intersects)两个几何对象有至少一个共同点
内含 (Within)几何对象 A 完全位于几何对象 B 内部
包含 (Contains)几何对象 A 完全包含几何对象 B

以该图形为例,两个多边形的关系判断的代码示例
image.png

WKTReader wktReader = new WKTReader();
Geometry geometryA = wktReader.read("POLYGON ((320 390, 370 330, 470 360, 460 430, 375 432, 320 390))");
Geometry geometryB = wktReader.read("POLYGON ((500 420, 430 360, 530 260, 500 420))");

System.out.println("Equal: " + geometryA.equals(geometryB));
System.out.println("Disjoint: " + geometryA.disjoint(geometryB));
System.out.println("Intersects: " + geometryA.intersects(geometryB));
System.out.println("Within: " + geometryA.within(geometryB));
System.out.println("Contains: " + geometryA.contains(geometryB));

在实际场景中,判断上门位置是否在上门区域内,转换成空间关系的判断就是点是否在多边形内。解决该问题的实例代码如下

WKTReader wktReader = new WKTReader();
Geometry geometryA = wktReader.read("POLYGON ((320 390, 370 330, 470 360, 460 430, 375 432, 320 390))");
Geometry geometryB = wktReader.read("POLYGON ((500 420, 430 360, 530 260, 500 420))");
Geometry point = wktReader.read("POINT (390 380)");

System.out.println("point in geometryA: " + geometryA.contains(point));
System.out.println("point in geometryB: " + geometryB.contains(point));

2.5 空间操作

JTS 提供了丰富的空间操作功能,用于处理和分析几何对象。这里列举常见的几种

空间操作定义
相交 (Intersection)计算两个几何对象的共同部分
并集 (Union)合并两个或多个几何对象
差集 (Difference)从一个几何对象中减去另一个几何对象

以该图为例,操作示例代码如下
image.png

WKTReader wktReader = new WKTReader();
Geometry geometryA = wktReader.read("POLYGON ((320 390, 370 330, 470 360, 460 430, 375 432, 320 390))");
Geometry geometryB = wktReader.read("POLYGON ((500 420, 430 360, 530 260, 500 420))");

System.out.println("Intersection: " + wktWriter.write(geometryA.intersection(geometryB)));
System.out.println("Union: " + wktWriter.write(geometryA.union(geometryB)));
System.out.println("Difference: " + wktWriter.write(geometryA.difference(geometryB)));

下面是 Union 合并后的效果
image.png

3 快速判断是否支持上门

在上门履约实际场景中,需要快速的识别用户所在位置、地址位置是否在上门服务的覆盖区域内。转换成空间关系的判断上,也就是点是否在多边形内(PIP,Point-In-Polygon)问题了。

在上述的 JTS 介绍中,已经得知 JTS 提供了 contains 的关系判断能力。但是这只是解决了单个问题,假设全国共有 N 个多边形,那么就需要遍历 N 个多边形来判断,复杂度是 O(N),并且还需要全部多边形加载到内存中。可想而知,直接使用的话会存在性能问题。为此,我们需要一个快速解决 PIP 问题的方案。

3.1 最小外接矩形(MBR)

最小外接矩形 MBR (Minimum Bounding Retangle),是能够完全包含一个几何对象的最小矩形。
如下图所示,这个规则的矩形就是该多边形的 MBR 表示。
最小外接矩形

表示 MBR 非常简单,只需要知道他的左下角和右上角,那么就可以知道这个 MBR 图形了。如下图所示最小外接矩形表示

知道了这个最小外接矩形有什么用?
可以断定:如果点不在这个 MBR 内了,那么肯定不在这个多边形内。所以把点和 MBR 进行比较,就能够快速排除不可能有关系的多边形对象。

那么如何快速的判断点是否在 MBR 中?比较坐标值的大小就可以了。示例代码如下

mbr.getLngMin() <= point.getLng()
&& mbr.getLngMax() >= point.getLng()
&& mbr.getLatMin() <= point.getLat()
&& mbr.getLatMax() >= point.getLat()

综上,MBR 用简单的矩形来近似表示复杂的几何形状,将复杂的空间关系简化为矩形之间的关系。 通过 MBR 这一层的初步筛选,就能够快速排除不可能有关系的多边形对象。

在 JTS 中,Envelope 对象来表示 MBR。代码示例如下

WKTReader wktReader = new WKTReader();
Geometry geometryA = wktReader.read("POLYGON ((320 390, 370 330, 470 360, 460 430, 375 432, 320 390))");

Envelope envelope = geometryA.getEnvelopeInternal();
System.out.println(envelope.getMaxX());
System.out.println(envelope.getMaxY());
System.out.println(envelope.getMinX());
System.out.println(envelope.getMinY());

3.2 空间索引

上述构建 MBR 可以理解为简单索引的一种,实际上有复杂的空间索引。常见空间索引有

  • R 树(R-tree):平衡树,适用于多维空间数据(类似一维的 B+树)
  • 四叉树(Quad-tree):将二维空间递归地分为四个象限
  • 网格(Grid):将空间划分为规则的网格单元

空间索引的基本原理基本类似,采用分割原理,逐级划分地理空间。举个不那么恰当的例子,一个自上而下、逐级划分地理空间的索引定位过程如下

北方 还是 南方 ? 南方
广东 还是 广西 ? 广东
深圳 还是 广州 ? 深圳
福田 还是 南山 ? 福田

JTS 提供了四叉树和 R 树的实现

  • Quadtree(四叉树)
  • STRtree(基于 R 树的变体)

以这个图形为例,使用 JTS 构建 R 树空间索引

示例代码如下

WKTReader wktReader = new WKTReader();
Geometry geometryA = wktReader.read("POLYGON ((320 390, 370 330, 470 360, 460 430, 375 432, 320 390))");
Geometry geometryB = wktReader.read("POLYGON ((500 420, 430 360, 530 260, 500 420))");

STRtree rtree = new STRtree();
// 向R树种添加MBR,和自己的数据
rtree.insert(geometryA.getEnvelopeInternal(), "Polygon-A");
rtree.insert(geometryB.getEnvelopeInternal(), "Polygon-B");
rtree.build();

// 点只在Polygon-A中
System.out.println(rtree.query(wktReader.read("POINT (337 391)").getEnvelopeInternal()));
// 点只在Polygon-B中
System.out.println(rtree.query(wktReader.read("POINT (496 390)").getEnvelopeInternal()));
// 点在Polygon-A和Polygon-B的交集中
System.out.println(rtree.query(wktReader.read("POINT (452 367)").getEnvelopeInternal()));

3.3 整体方案流程

综上所述,快速定位点(Point)在哪些多边形中的具体流程如下

  1. 先通过 STRtree 构建空间索引
  2. 利用空间索引快速筛选可能包含点的多边形
  3. 对筛选后的多边形进行精确的空间关系判断

多边形是随时都有可能可以调整,如果一个多边形发生了调整就需要重构整颗索引树。但是在实践中,为了降低构建索引树的频次,通过定时任务去间隔 10 分钟在内存中构建一次。并且为了减少索引树占用的内存大小,向索引树中添加 MBR 关联的是多边形的 Id,初筛后再根据 id 从缓存中取具体的多边形数据进行精确的空间关系判断,实现一个类似懒加载的过程。

具体流程如下图所示
空间索引整体流程

4 几何图形的修复处理

在实际运营过程中,画的图形各种形状,会出现不少异常的情况,如点重叠、边之间细微的间隙、自交等问题。实际操作中还提拱了图形合并的能力,合并出来的图像也有可能也是不符合规范的。为此,需要对这些异常的图像进行修复。

常见的修复手段有两种

  • Buffer 操作:在几何对象周围的创建缓冲区,一般用来修复自相交问题、精度导致的小间隙等
  • Snap 操作:一个几何对象的顶点捕捉到另一个几何对象的顶点或边缘,一般用来修复小的拓扑错误

这两种操作也不是万能,也是需要自己根据实际情况进行不断地调整。

下面来看一个修复自交的例子,一个自交的图形如下所示
空间修复案例

修复代码示例如下


WKTReader wktReader = new WKTReader();
Geometry geometryA = wktReader.read("POLYGON ((340 490, 370 330, 730 350, 700 270, 340 490))");

WKTWriter wktWriter = new WKTWriter();
wktWriter.setPrecisionModel(new PrecisionModel(0));
System.out.println(wktWriter.write(geometryA.buffer(0)));

修复之后如下图所示
几何图形修复示例2

5 总结

Java Topology Suite (JTS) 作为一个功能强大的空间数据处理库,为开发者提供了丰富的工具来处理复杂的空间问题。它在许多地理信息系统得到了广泛的应用。这里只是对其的一个简单应用,后续还待更深入的挖掘。

6 参考

  • Java Topology Suite (JTS)
  • GIS原理在线教程

关于作者

揭荣,转转上门履约业务研发工程师

转转研发中心及业界小伙伴们的技术学习交流平台,定期分享一线的实战经验及业界前沿的技术话题。
关注公众号「转转技术」(综合性)、「大转转FE」(专注于FE)、「转转QA」(专注于QA),更多干货实践,欢迎交流分享~

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

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

相关文章

微服务实战系列之玩转Docker(八)

前言 “Compose”一词的原意是创作、组成、构成&#xff08;一个整体&#xff09;&#xff0c;那么“Docker Compose”一词可以理解为一组docker。今天博主着重介绍一下这个“容器导演” —— 一个docker自带的容器管理工具。 Docker Compose入门 1. 定义 Docker Compose is …

这八本大模型书籍看完你就是LLM大师,非常详细收藏我这一篇就够了

如果你想深入了解大模型领域&#xff0c;无论是为了学术研究还是实际应用&#xff0c;选择合适的书籍是非常重要的。以下是精选的八本大模型相关书籍&#xff0c;涵盖了从基础理论到高级实践的内容&#xff0c;可以帮助你构建全面的知识体系。 《大模型应用开发极简入门》 作者…

PyTorch图像分割的基本形式

【图书推荐】《PyTorch深度学习与计算机视觉实践》-CSDN博客 图像分割是计算机视觉领域中的一个重要任务&#xff0c;旨在将图像划分为多个不同的区域或对象。简单来说&#xff0c;图像分割就是将图像中的像素或区域按照某种规则或标准进行分类&#xff0c;使得同一区域内的像…

如何更改软件ip地址:方法详解

在数字化时代&#xff0c;网络连接已成为我们日常生活与工作中不可或缺的一部分。无论是日常娱乐、办公协作&#xff0c;还是商业运营&#xff0c;软件应用都需要通过IP地址与互联网建立联系。然而&#xff0c;在某些特定情况下&#xff0c;我们可能需要更改软件的IP地址&#…

【Vulnhub系列】Vulnhub Lampiao-1 靶场渗透(原创)

【Vulnhub系列靶场】Vulnhub Lampiao-1靶场渗透 原文转载已经过授权 原文链接&#xff1a;Lusen的小窝 - 学无止尽&#xff0c;不进则退 (lusensec.github.io) 一、主机发现 二、端口扫描 三、web框架 四、web渗透 1、信息收集 2、目录扫描 获得版本信息7.56 3、获取shell …

AI时代:成为AI产品经理的方法

一、非技术背景的人员如何转型成为AI产品经理 ​ 产品经理是一个非常重要的岗位&#xff0c;但是高校并没有培养产品经理特别是 AI 产品经理的专业课程&#xff0c;这方面的书籍也非常稀缺。非技术背景的人员转型成为 AI 产品经理是未来一段时间内的一种趋势&#xff0c;这里主…

Nginx 反向代理https域名接口的注意事项

网络环境条件及实际调用需求如下图所示&#xff1a; 如图&#xff0c;要点如下&#xff1a; 应用实际请求的协议是 http&#xff0c;而通过 nginx 反向代理的最终接口是 https应用实际请求的域名是 mynginx.com&#xff0c;而通过 nginx 反向代理的最终接口域名是 api.weixin.…

【虚拟化】KVM概念和架构

目录 一、什么是KVM&#xff1f; 二、KVM的功能 2.1 主要的功能 2.2 其它功能 三、KVM核心组件及作用 四、KVM与VMware的优势 五、KVM架构 六、qemu介绍 七、创建虚拟机流程 一、什么是KVM&#xff1f; Kernel-based Virtual Machine的简称&#xff0c;KVM 是基于虚拟…

CentOS配置NTP服务

更改配置文件 [rootController ~]# vim /etc/chrony.conf 重启服务并设置为开机自启动 [rootController ~]# systemctl restart chronyd.service [rootController ~]# systemctl enable chronyd.service 在另一台CentOS测试 更改配置文件 [rootCompute ~]# vim /etc/chron…

开放式耳机推荐性价比排行榜!公认口碑最好的型号推荐

随着生活的提高&#xff0c;耳机在近几年来一直受到很多用户的喜欢&#xff0c;也逐渐成为大家生活中的必需品&#xff0c;我使用过的耳机也有好几十款了&#xff0c;大部分都因为是入耳式耳机佩戴久了无法忍受酸痛感&#xff0c;有些不入耳的耳机戴久了也会有一种无法形容的不…

【Java】重生之String类再爱我一次---练习题(012)

目录 ♦️练习一&#xff1a;用户登录 ♦️练习二&#xff1a;遍历字符串 ♦️练习三&#xff1a;统计字符次数数 ♦️练习四&#xff1a;拼接字符串 ♦️练习五&#xff1a;反转字符串 ♦️练习六&#xff1a;金额转换 ♦️练习七&#xff1a;手机号屏蔽 ♦️练习一&am…

【全国大学生电子设计竞赛】2024年H题

&#x1f970;&#x1f970;全国大学生电子设计大赛学习资料专栏已开启&#xff0c;限时免费&#xff0c;速速收藏~

【一图学技术】5.OSI模型和TCP/IP模型关系图解及应用场景

OSI模型和TCP/IP模型关系图解 OSI模型和TCP/IP模型都是网络通信的参考模型&#xff0c;用于描述网络协议的层次结构和功能。下面是它们的定义和区别&#xff1a; OSI模型&#xff08;Open Systems Interconnection Model&#xff09; OSI模型是一个理论上的七层模型&#xff…

1套农场,20小时,10万张!重建大师6.3矿山重建实测案例

数字矿山是国家战略资源安全保障体系的重要组成部分&#xff0c;是资源可持续发展的重要基石&#xff0c;是化解高危行业风险的根本途径。 国内某矿山&#xff08;图源网络&#xff09; 在矿山的开发与建设过程中&#xff0c;需要定期采集并动态更新矿区的三维空间数据&#x…

嵌入式Linux开发板如何挂载u盘?

第一步&#xff1a;插入U盘。 第二步&#xff1a;查看U盘名称。 dmesg | tail 可以看到此处我的U盘名称为sda&#xff0c;第一个分区为sda1&#xff0c;路径为/dev/sda。 第三步&#xff1a;创建一个文件夹&#xff0c;将u盘挂载到此文件夹下&#xff0c;查看U盘下的文件 sud…

代码随想录训练营 Day16打卡 二叉树 part04 513. 找树左下角的值 112. 路径总和 106. 从中序与后序遍历序列构造二叉树

代码随想录训练营 Day16打卡 二叉树 part04 一、 力扣513. 找树左下角的值 给定一个二叉树&#xff0c;判断它是否是 平衡二叉树 给定一个二叉树的 根节点 root&#xff0c;请找出该二叉树的 最底层 最左边 节点的值。 假设二叉树中至少有一个节点。 示例 &#xff1a; 输入: …

探索算法系列 - 二分查找算法

目录 二分查找&#xff08;原题链接&#xff09; 在排序数组中查找元素的第一个和最后一个位置&#xff08;原题链接&#xff09; 搜索插入位置&#xff08;原题链接&#xff09; x 的平方根&#xff08;原题链接&#xff09; 山脉数组的峰顶索引&#xff08;原题链接&…

数据结构(面试)

线索二叉树 原理&#xff1a;利用树节点的n1个左右空指针指向其遍历序列的前驱和后继&#xff08;线索&#xff09; 哈夫曼树 哈夫曼树定义&#xff1a;在含有n个带权叶节点的二叉树中&#xff0c;其中带权路径&#xff08;WPL&#xff09;最小的二叉树称为哈夫曼树&#x…

协程的八种创建方式

协程简介 在深入了解创建方式之前&#xff0c;我们先简要回顾一下协程是什么。协程是轻量级的线程。它们在协作式多任务处理中运行&#xff0c;允许在不阻塞线程的情况下挂起和恢复。这使得协程非常适合进行异步编程和高性能的并发任务。&#x1f310; Kotlin中创建协程的方式…