java jts 针对shp含洞多边形进行三角剖分切分成可行区域

前言

java jts 提供了Delaunay三角剖分的相关方法,但是该方法不考虑含洞的多边形的。虽然 jts 的 ConformingDelaunayTriangulationBuilder 类可以通过提供线段约束的方式防止切割到洞内,但是仅支持最多99条线段,虽然可以通过重写破除99条线段的约束,但是线段多了性能会变得很差。且在网上没找到相关的解决方案。

本文主要是基于 jts 实现对含洞多边形进行三角剖分,并且可以不切割到洞内的方法。

三角剖分是生成寻路用的 navmesh 的步骤之一

原生工具切割效果展示

首先是初始shp文件,就是一系列的多边形障碍物,三角剖分时不能切割到障碍物内部。

将上面的shp取反后就是一个含洞的多边形,洞内就是上面的多边形障碍物。

切分后的效果如下图所示

放大局部后如下图所示,可以看出切割的时候是不考虑多边形障碍物的,会切割到多边形内部。

实现代码

pom.xml依赖

 <dependency><groupId>org.geotools</groupId><artifactId>gt-geotiff</artifactId><version>21.1</version>
</dependency>
<dependency><groupId>org.geotools</groupId><artifactId>gt-shapefile</artifactId><version>22.1</version>
</dependency>
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.19</version>
</dependency>

其他工具

QGIS: 用于打开 shp 文件可视化看效果

代码

代码目前测试暂未发现还有什么问题,但不确保一点问题都没。

public class ShpUtils {public static final GeometryFactory GEOMETRY_FACTORY = JTSFactoryFinder.getGeometryFactory();public static void main(String[] args) throws Exception {List<Polygon> resultTriangles = genTriangle("D:\\shp\\test.shp", false, Lists.newArrayList());//将剖分的结果写入shp文件,可以用于看剖分效果writeMultiPolygonShp(GEOMETRY_FACTORY.createMultiPolygon(resultTriangles.toArray(new Polygon[resultTriangles.size()])), "D:\\shp\\test_triangle.shp");}/*** 三角剖分** @param shpPath     shp文件地址* @param isNegation  是否取反后再剖分* @param coordinates 外边框(地图的边界范围),可以为空* @throws Exception*/public static List<Polygon> genTriangle(String shpPath, boolean isNegation, List<Coordinate> coordinates) throws Exception {SimpleFeatureSource featureSource = getFeatureSourceByPath(shpPath);List<MultiPolygon> multiPolygons = getPolygons(featureSource);Geometry triangles;if (!isNegation) {if (CollectionUtil.isEmpty(coordinates)) {//如果没传入外边框,则不设置外边框coordinates = Lists.newArrayList();//取shp外接矩形的外边框的话剖分结果应该是和取反后再三角剖分的效果是一样的,取反默认就是shp文件外接矩形对应的外边框
//            ReferencedEnvelope bounds = featureSource.getBounds();
//            List<Coordinate> coordinates = boundToCoordinate(bounds);}triangles = genTriangle(coordinates, (List) multiPolygons);} else {//取反后三角剖分Geometry negationShp = negationShp(featureSource, coordinates);triangles = genTriangle(Lists.newArrayList(), Lists.newArrayList(negationShp));}//最终剖分得到的三角形List<Polygon> resultTriangles = Lists.newArrayList();List<Polygon> polygons = multiPolygonToPolygon(multiPolygons);for (int i = 0; i < triangles.getNumGeometries(); i++) {//剖分后的三角形Polygon triangle = (Polygon) triangles.getGeometryN(i);List<Polygon> disposePolygons = disposeTriangle(triangle, polygons, 0);resultTriangles.addAll(disposePolygons);//注释掉上面两行使用下面这行即为原生的剖分效果
//            resultTriangles.add(triangle);}return resultTriangles;}/*** 对剖分的三角形存在下面两中情况的进行处理* 1.剖分的三角形可能在障碍物内部* 2.剖分的三角形可能和障碍物有交集,但没被完全包含** @param triangle* @param polygons* @param start* @return*/private static List<Polygon> disposeTriangle(Polygon triangle, List<Polygon> polygons, int start) {List<Polygon> resultTriangles = Lists.newArrayList();for (int i = start; i < polygons.size(); i++) {Polygon polygon = polygons.get(i);if (polygon.contains(triangle)) {//当剖分的三角形完全在障碍物多边形内部,则忽略掉该三角形,在障碍物内部的三角形是不可能为可行区域的return resultTriangles;} else if (polygon.intersects(triangle)) {//当剖分的三角形和障碍物多边形有交集,则相交部分必为不可行区域//取出当前三角形相对于障碍物多边形没有交集的部分,没有交集的部分可能为可行区域,需要对没有交集的部分进行处理Geometry difference = triangle.difference(polygon);if (difference instanceof MultiPolygon) {//障碍物多边形可能将三角形截断成多个多边形,需要对截断后的多个多边形分别进行处理for (int j = 0; j < difference.getNumGeometries(); j++) {//只需要对之后的障碍物多边形校验是否相交resultTriangles.addAll(disposeTriangle((Polygon) difference.getGeometryN(j), polygons, i + 1));}} else if (difference instanceof Polygon) {//如果没有交集的部分依然只是一个多边形,则检测该多边形与后续障碍物多边形是否有交集triangle = (Polygon) difference;} else if (difference instanceof GeometryCollection) {//同 MultiPolygonfor (int k = 0; k < difference.getNumGeometries(); k++) {Geometry geometryN = difference.getGeometryN(k);if (geometryN instanceof Polygon) {resultTriangles.addAll(disposeTriangle((Polygon) geometryN, polygons, i + 1));} else {//TODO 有可能得到的结果是非多边形,目前测试可能得到一条线段,线段应该不需要处理,不确定是否有其他情况System.out.println(MessageFormat.format("不可解析图形#1,{0}", geometryN));}}} else {//TODO 目前测试可能得到一条线段,不予处理System.out.println(MessageFormat.format("不可解析图形#2,{0}", difference));}}}//确保返回的几何图形都为三角形if (triangle.getCoordinates().length == 4) {resultTriangles.add(triangle);} else {//对于非三角形,还需要进行一次原生工具的三角剖分,剖分成多个三角形Geometry geometry = genTriangle(Lists.newArrayList(), Lists.newArrayList(triangle));for (int i = 0; i < geometry.getNumGeometries(); i++) {resultTriangles.add((Polygon) geometry.getGeometryN(i));}}//下方代码是判断是否为凸多边形,返回的结果集集合图形不一定是三角形,但不会是凹多边形// navmesh寻路只要不是凹多边形就可以使用
//        if (isConvex(triangle)) {
//            resultTriangles.add(triangle);
//        } else {
//            //如果是被障碍物多边形截断后的形状,有可能是凹多边形,对于凹多边形,还需要进行一次原生工具的三角剖分
//            Geometry geometry = genTriangle(Lists.newArrayList(), Lists.newArrayList(triangle));
//            for (int i = 0; i < geometry.getNumGeometries(); i++) {
//                resultTriangles.add((Polygon) geometry.getGeometryN(i));
//            }
//        }return resultTriangles;}/*** 多边形是否为凸多边形* 本方法代码是chatgpt生成的,目前测试好像可用** @param polygon 多边形* @return*/public static boolean isConvex(Polygon polygon) {Coordinate[] coords = polygon.getExteriorRing().getCoordinates();if (coords.length < 4) {//之所以是四个是因为第一个点和第四个点是一个样的,所以四个点则为三角形return false; // 少于4个顶点的多边形不可能是凸多边形}boolean direction = getTurnDirection(coords[0], coords[1], coords[2]);for (int i = 1; i < coords.length - 2; i++) {boolean currentDirection = getTurnDirection(coords[i], coords[i + 1], coords[i + 2]);if (currentDirection != direction) {return false;}}return true;}/*** chatgpt 生成代码** @param a* @param b* @param c* @return*/private static boolean getTurnDirection(Coordinate a, Coordinate b, Coordinate c) {double crossProduct = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);return crossProduct > 0; // 返回true表示逆时针转,false表示顺时针转}/*** 将 MultiPolygon 转成 Polygon 集合** @param multiPolygons* @return*/public static List<Polygon> multiPolygonToPolygon(List<MultiPolygon> multiPolygons) {List<Polygon> polygonList = Lists.newArrayList();for (MultiPolygon polygon : multiPolygons) {for (int i = 0; i < polygon.getNumGeometries(); i++) {polygonList.add((Polygon) polygon.getGeometryN(i));}}return polygonList;}/*** 使用jts 原生工具进行三角剖分** @param bounds   外边框的四个坐标点(地图边界范围),可以取所有多边形的最大外边框,也可以自己设置* @param polygons 所有障碍物多边形* @return*/public static Geometry genTriangle(List<Coordinate> bounds, List<Geometry> polygons) {//获取外边框及所有障碍物多边形点位的集合Set<Coordinate> coordinates = Sets.newHashSet();for (Geometry polygon : polygons) {coordinates.addAll(Arrays.asList(polygon.getCoordinates()));}coordinates.addAll(bounds);//对所有点调用 jts 原生工具进行三角剖分DelaunayTriangulationBuilder builder = new DelaunayTriangulationBuilder();builder.setSites(coordinates);return builder.getTriangles(GEOMETRY_FACTORY);}/*** 获取外边框的四个坐标点** @param bounds* @return*/public static List<Coordinate> boundToCoordinate(ReferencedEnvelope bounds) {List<Coordinate> coordinates = Lists.newArrayList();coordinates.add(new Coordinate(bounds.getMinX(), bounds.getMinY()));coordinates.add(new Coordinate(bounds.getMinX(), bounds.getMaxY()));coordinates.add(new Coordinate(bounds.getMaxX(), bounds.getMaxY()));coordinates.add(new Coordinate(bounds.getMaxX(), bounds.getMinY()));return coordinates;}/*** 加载shp文件** @param shpPath shp文件地址* @return* @throws Exception*/public static SimpleFeatureSource getFeatureSourceByPath(String shpPath) throws Exception {ShapefileDataStore store = new ShapefileDataStore(new URL("file:/" + shpPath));String typeName = store.getTypeNames()[0];return store.getFeatureSource(typeName);}/*** 获取shp文件内的所有多边形** @param featureSource* @return* @throws IOException*/private static List<MultiPolygon> getPolygons(SimpleFeatureSource featureSource) throws IOException {List<MultiPolygon> polygons = Lists.newArrayList();SimpleFeatureIterator features = featureSource.getFeatures().features();while (features.hasNext()) {SimpleFeature feature = features.next();Geometry geometry = (Geometry) feature.getDefaultGeometry();if (geometry instanceof MultiPolygon) {MultiPolygon multiPolygon = (MultiPolygon) geometry;polygons.add(multiPolygon);}}return polygons;}/*** 取反* 比如如果一个shp 含有多个多边形障碍物,可以用本方法生成一个多边形含有多个洞,洞表示原来的障碍物多边形** @param featureSource* @param coordinates   外边框(地图边界范围)* @return 取反后的几何图形* @throws IOException*/public static Geometry negationShp(SimpleFeatureSource featureSource, List<Coordinate> coordinates) throws IOException {if (CollectionUtil.isEmpty(coordinates)) {//如果没有自定义外边框,则取shp文件的外接矩形作为外边框ReferencedEnvelope bounds = featureSource.getBounds();coordinates = boundToCoordinate(bounds);}//考虑是否判断最后一个点和第一个点一样时不添加新点coordinates.add(coordinates.get(0));Geometry geometryNegation = GEOMETRY_FACTORY.createPolygon(coordinates.toArray(new Coordinate[0]));SimpleFeatureIterator features = featureSource.getFeatures().features();while (features.hasNext()) {SimpleFeature feature = features.next();Geometry geometry = (Geometry) feature.getDefaultGeometry();geometryNegation = geometryNegation.difference(geometry);}return geometryNegation;}/*** 将几何图形写入shp文件** @param geometry  几何图形(可以是一个集合GeometryCollection)* @param writePath 写入的路径位置* @throws Exception*/public static void writeMultiPolygonShp(Geometry geometry, String writePath) throws Exception {ShapefileDataStore ds = new ShapefileDataStore(new URL("file:/" + writePath));//设置编码ds.setCharset(Charset.forName("UTF-8"));//定义图形信息和属性信息SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder();tb.setCRS(DefaultGeographicCRS.WGS84);tb.setName("shapefile");tb.add("the_geom", MultiPolygon.class);tb.add("num", Integer.class);ds.createSchema(tb.buildFeatureType());//设置WriterFeatureWriter<SimpleFeatureType, SimpleFeature> writer = ds.getFeatureWriter(ds.getTypeNames()[0], Transaction.AUTO_COMMIT);//写入文件信息for (int i = 0; i < geometry.getNumGeometries(); i++) {SimpleFeature feature = writer.next();Geometry triangle = geometry.getGeometryN(i);feature.setAttribute("the_geom", triangle);feature.setAttribute("num", i);}writer.write();writer.close();ds.dispose();}
}

效果

上面代码切割的效果如下图所示

上面代码有个取反后再进行三角剖分的代码,效果如下

与原生工具对比如下,不会切割到障碍物内部了。

扩展

求共边

上面生成的三角形数量还是比较多的, navmesh 不要求一定要是三角形,可以是凸多边形,不可以是凹多边形,所以可以基于上面的三角形集合根据共边合并成凸多边形,从而减少寻路的三角形数量。从而提升寻路性能。

耳切法

网上是有说耳切法进行三角剖分是可以切割有洞的多边形的,有兴趣的可以自己试试

寻路

GitHub - jzyong/GameAI4j: Game AI for java.NavMesh、A*、BehaviorTree、FSM

上面地址提供了如何基于 navmesh 寻路的相关代码

核心类

PolygonMeshWindow 多边形寻路的可视化 ui 界面启动类, loadMap 方法会加载 navmesh 文件初始化PolygonNavMesh
PolygonNavMesh
多边形寻路的类,将多边形转为改结构即可用 findPath 进行寻路

TriangleNavMeshWindow 三角形寻路的可视化 ui 界面启动类
TriangleNavMesh 用于三角形寻路的类

三角形寻路没用过,可以考虑直接用多边形寻路。

navmesh文件结构介绍

只需要将本文生成的三角形转换成上面库可以解析的navmesh文件,即可用上面的库进行寻路

navmesh的结构如下图所示

主要就是介绍下 pathTriangles 和 pathVertices

pathVertices s是所有多边形的点集数组,如下图,表示的是多边形所有顶点。

pathTriangles 多边形的所有标识,存的值表示的是 pathVertices 中的数组下标对应的点。具有以下特性

1.每三个点表示一个三角形,比如第1-3个值 0,1,2 表示  pathVertices[0]、pathVertices[1]、pathVertices[2] 组成一个三角形,第4-6个指 0、2、3 又是一个三角形。
2.当连续的三角形有两个下标是一样的,表示是共边三角形,可以组成一个凸多边形。所以生成navmesh的时候需要注意不然让连续的三角形有两个下标一样的却不能合成凸多边形。比如前面 0、1、2 和0、2、3有相同的点0、2,所以就可以组成一个0、1、2、3的四边形,以此类推可以合并成五边形、六边形。

另一种思路

上面的方法是将 shp 转换的三角形或者多边形转成 navmesh 文件,但是这样得按照navmesh的格式规则,其实可以自己去看  P olygonNavMesh 类的代码,直接将三角形和多边形转成 PolygonNavMesh 需要的数据结构。这个也不会很难,无非就修改一些 navmesh的解析逻辑

recast

java 版的 recastnavigation ,提供类似unity 3d 的寻路,可视化的三角剖分、体素化等相关功能。我也没去研究过,里面可能会有三角剖分、求共边的相关工具。链接地址如下

GitHub - recast4j/recast4j: Java Port of Recast & Detour navigation mesh toolset

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

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

相关文章

Java——————接口(interface) <详解>

1.1 接口的概念 在现实生活中&#xff0c;接口的例子比比皆是&#xff0c;比如&#xff1a;笔记本电脑上的USB接口&#xff0c;电源插座等。 电脑的USB口上&#xff0c;可以插&#xff1a;U盘、鼠标、键盘...所有符合USB协议的设备 电源插座插孔上&#xff0c;可以插&#xff…

CSS Text(文本)

CSS Text(文本) CSS中的text属性用于控制文本的显示方式&#xff0c;包括字体、大小、颜色、对齐方式等。本文将详细介绍CSS中文本属性的各种用法和技巧&#xff0c;帮助您更好地设计和布局网页文本。 1. 字体属性 1.1 font-family font-family属性用于设置文本的字体。可以…

OS Copilot初体验的感受与心得

本文介绍体验操作系统智能助手OS Copilot后&#xff0c;个人的一些收获、体验等。 最近&#xff0c;抽空体验了阿里云的操作系统智能助手OS Copilot&#xff0c;在这里记录一下心得与收获。总体观之&#xff0c;从个人角度来说&#xff0c;感觉这个OS Copilot确实抓住了不少开发…

TypeScript极速梳理

TypeScript简介 TypeScript是对JavaScript的超集它对JS进行了扩展&#xff0c;向JS中引入了类型的概念&#xff0c;并添加了许多新的特性TS代码需要通过编译器编译为JS&#xff0c;然后再交由JS解析器执行TS完全兼容JS,换言之&#xff0c;任何的JS代码都可以直接当成JS使用相较…

Vue Router【实用教程】(2024最新版)vue3 路由管理

Vue Router 是 Vue 官方的客户端路由解决方案&#xff0c;在单页应用 (SPA) 中&#xff0c;用户在应用中浏览不同页面时&#xff0c;URL 会随之更新&#xff0c;但页面不需要从服务器重新加载。 核心思想&#xff1a; 通过配置路由来告诉 Vue Router 为每个 URL 路径显示哪些…

EasyMedia转码rtsp视频流flv格式,hls格式,H5页面播放flv流视频

EasyMedia转码rtsp视频流flv格式&#xff0c;hls格式 H5页面播放flv流视频 文章最后有源码地址 解决海康视频播放视频流&#xff0c;先转码后自定义页面播放flv视频流 先看效果&#xff0c;1&#xff0c;EasyMedia自带的页面&#xff0c;这个页面二次开发改动页面比较麻烦 …

牛客14666(优先屏障) + 牛客14847(Masha与老鼠)

文章目录 写在前面14666-优先屏障思路编程 14847-Masha与老鼠思路编程 写在前面 昨天刷的这两道题写了很久&#xff0c;特别是Masha与老鼠这道题&#xff0c;写了都快3个小时&#xff0c;主要还是理解代码逻辑有点难&#xff0c;不过写完之后感觉收获挺大的&#xff0c;给我以…

张高兴的 MicroPython 入门指南:(三)使用串口通信

目录 什么是串口使用方法使用板载串口相互通信 硬件需求电路代码使用板载的 USB 串口参考 什么是串口 串口是串行接口的简称&#xff0c;这是一个非常大的概念&#xff0c;在嵌入式中串口通常指 UART(Universal Asynchronous Receiver/Transmitter&#xff0c;通用异步收发器)。…

gitlab查询分支API显示不全,只有20个问题

背景 gitlab查询分支API需要查询所有分支&#xff0c;且分支数量大于20&#xff0c;但目前调用接口返回的branch最多就显示了20个 解决方案 根据GitLab的文档&#xff0c;查询分支API默认最多返回20个分支。如果要一次性显示80个分支&#xff0c;可以使用分页参数来获取所有…

Vue的单元测试和端到端测试:确保组件可靠性与应用完整性

引言 在软件开发过程中,测试是保证代码质量和应用稳定性的关键环节。Vue.js 作为流行的前端框架,提供了一套完善的测试工具和生态系统,支持开发者进行单元测试和端到端测试。本文将深入探讨如何为Vue组件编写单元测试,并讨论如何使用Cypress等工具进行端到端测试。 单元测…

node.js实现静态资源加载的方法——两种方法http及express

一.使用内置http实现静态资源加载 原生node要实现静态资源加载就需要借助其内置模块“fs”中的方法去读取静态资源&#xff0c;然后用response中的方法响应给用户 1.需要用到的模块 const http require(http); const fs require(fs); const path require(path);2.加载静态…

初识c++(string和模拟实现string)

一、标准库中的string类 string类的文档介绍&#xff1a;cplusplus.com/reference/string/string/?kwstring 1、auto和范围for auto&#xff1a; 在早期C/C中auto的含义是&#xff1a;使用auto修饰的变量&#xff0c;是具有自动存储器的局部变量&#xff0c;后来这个 不重…

Cannot perform upm operation: connect ETIMEDOUT 34.36.199.114:443 [NotFound]

版本&#xff1a;Unity 2018 Windows 问题&#xff1a;打开 Package Manager&#xff0c;加载报错 尝试解决&#xff1a; 删除项目文件里的Packages下的mainfest.json文件&#xff0c;然后重新打开项目&#xff08;X&#xff09;重新登录 Unity 账号&#xff08;X&#xff09…

Kotlin 协程 — 基础

Kotlin 协程 — 基础 协程已经存在一段时间了&#xff0c;关于它的各种文章也很多。但我发现想要了解它还比较费时&#xff0c;所以我花了一段时间才真正理解了协程的基础知识以及它的工作原理。因此&#xff0c;我想分享一些我理解到的内容。 什么是协程&#xff1f; 协程代表…

vue项目实战速查记录

1.图片下载到本地 2.本地静态文件访问 3.元素大小相同,相互覆盖 1.图片下载到本地 实现原理:创建a标签,利用a标签下载属性. download(){const link document.createElement(a);link.href "图片地址";link.setAttribute(download, name);document.body.ap…

读论文《Hi-Net: Hybrid-fusion Network for Multi-modalMR Image Synthesis》

论文题目&#xff1a;Hi-Net:用于多模态磁共振图像合成的混合融合网络 论文地址&#xff1a;arxiv 项目地址&#xff1a;github 原项目可能在训练的时候汇报version的错&#xff0c;这是因为生成器和辨别器的优化有些逻辑错误&#xff0c;会改的话多加一个生成操作可以解决&…

React 学习——条件渲染、遍历循环、事件绑定

React特点&#xff1a; 声明式的设计高效&#xff0c;采用虚拟DOM来实现DOM的渲染&#xff0c;最大限度减少DOM的操作灵活&#xff0c;跟其他库灵活搭配使用JSX&#xff0c;俗称JS里面写HTML&#xff0c;JavaScript语法的扩展组件化&#xff0c;模块化&#xff0c;代码容易复用…

git 过滤LFS文件下载

git config --global filter.lfs.smudge "git-lfs smudge --skip -- %f" git config --global filter.lfs.process "git-lfs filter-process --skip" 恢复下载 git config --global filter.lfs.smudge "git-lfs smudge -- %f" git config --g…

pdf的下载,后端返回工作流,前端进行转换

前端将后端返回的工作流进行转换 项目中接触到了pdf的下载和预览的功能&#xff0c;记录一下~ 这里pdf的下载和预览的接口&#xff0c;后端返回的数据结构与其他的接口返回的数据结构有点不同&#xff0c;是直接返回的工作流&#xff0c;在控制台接口的响应预览内容大致是这样…

南京移动老年手机课堂助力社区老人智享生活

为帮助社区老年群体解决使用智能手机过程中遇到的问题&#xff0c;让老年人充分享受数字化、智能化带来的便利&#xff0c;同时积极营造敬老、爱老、助老的社会氛围&#xff0c;江苏移动南京分公司在鼓楼区龙蟠里社区新时代文明实践站开展了“学以‘智’用&#xff0c;‘智’享…