项目中需要分析postGis库保存的空间数据,规划线路图。参考了很多博文,概要整理如下。
JTS简介
Java Topology Suite (JTS) 。音译为java拓扑套件。可见,咱们讨论的东西属于拓扑学范畴了,很高端。其实,说得通俗点,就是研究两个几何对象的空间关系。
比如二维平面中,一个正方形,分为:外部、内部、边线。3种情况。那么两个正方形的关系,就是3*3=9,即出现9种组合情形。这就是大名鼎鼎的九交模型。
我用的版本是1.13(com.vividsolutions.jts)。新版本的包是(org.locationtech.jts)。
API下部分包:
com.vividsolutions.jts.geom包:几何图形
com.vividsolutions.jts.linearref包:线性处理
com.vividsolutions.jts.noding包:计算交点
com.vividsolutions.jts.operation包:几何图形操作
com.vividsolutions.jts.planargraph包:平面图
com.vividsolutions.jts.polygnize包:多边形化
com.vividsolutions.jts.precision包:精度
com.vividsolutions.jts.util包:工具
JTS点、线之间关系计算
1.计算两条线段相交的交点
GeometryFactory gf = new GeometryFactory(); WKTReader reader = new WKTReader(gf); Geometry line1 = reader.read("LINESTRING(0 0, 10 10)"); Geometry line2 = reader.read("LINESTRING(0 10, 9 0)"); System.out.println("两线段相交于点:" + line1.intersection(line2));
2.计算线外一点到线段上最短距离以及投影点
GeometryFactory gf = new GeometryFactory(); WKTReader reader = new WKTReader(gf); Geometry line2 = reader.read("LINESTRING(0 0, 10 0, 10 10, 20 10)"); Coordinate c = new Coordinate(5, 5); PointPairDistance ppd = new PointPairDistance(); DistanceToPoint.computeDistance(line2, c, ppd); System.out.println(ppd.getDistance() + ":" + ppd.getCoordinate(0));
3. 已知点在线段上投影点,截取子线段
GeometryFactory gf = new GeometryFactory(); WKTReader reader = new WKTReader(gf); Geometry line2 = reader.read("MULTILINESTRING((113.197 28.6344,113.482 28.6563))"); Geometry pointJiaoDian = reader.read("POINT(113.211 28.764)"); LocationIndexedLine lil = new LocationIndexedLine(line2); LinearLocation start = lil.indexOf(pointJiaoDian.getCoordinate()); LinearLocation end = lil.getEndIndex(); // 按自己的业务需要设定终点 Geometry result = lil.extractLine(start, end); System.out.println(result.toText()); // 子线
4. 一些基本操作
翻转线段上的点:geometry.reverse();
合并两条线段:geom1.union(geom2);
点与点的距离:coordinate1.distance(coordinate2);
将WKT(well know text)转为几何图形:geometry = wktReader.read("MULTILINESTRING((113.197 28.6344,113.482 28.6563))");
整合springboot+postGis+JTS
1. maven依赖如下
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.2</version> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.1.4</version> </dependency> <dependency> <groupId>com.vividsolutions</groupId> <artifactId>jts</artifactId> <version>1.13</version> </dependency>
如果你想脱离postgis数据库的函数,算两个经纬度坐标点之间的实际距离,可以用下面这个包:
<dependency> <groupId>org.gavaghan</groupId> <artifactId>geodesy</artifactId> <version>1.1.3</version> </dependency>
代码算法:
import com.vividsolutions.jts.geom.Coordinate; import org.gavaghan.geodesy.Ellipsoid; import org.gavaghan.geodesy.GeodeticCalculator; import org.gavaghan.geodesy.GlobalCoordinates; public static double getDistance(Coordinate p1, Coordinate p2) { GlobalCoordinates source = new GlobalCoordinates(p1.y, p1.x); GlobalCoordinates target = new GlobalCoordinates(p2.y, p2.x); return new GeodeticCalculator().calculateGeodeticCurve(Ellipsoid.WGS84, source, target).getEllipsoidalDistance(); }
2. yml配置如下
spring: application: name: demo datasource: driver-class-name: org.postgresql.Driver url: jdbc:postgresql://localhost:5432/postgis username: aa password: mybatis-plus: type-handlers-package: com.cs.dgt.hxd.handler # 自定义typehandler时,需要配置这个 mapper-locations: classpath*:mappers//*Mapper.xml
postgreSQL的默认端口是5432。不用mysql是由于postgreSQL对于自定义数据类型的优秀扩展性,所以postgis是基于postgreSQL来做的。
由于用到mybatis, 自定义typehandler,将postgis库中Geometry类型映射到java类型:
@MappedTypes(Geometry.class) public class GeometryTypeHandler extends BaseTypeHandler<Geometry> { public void setNonNullParameter(PreparedStatement preparedStatement, int i, Geometry geometry, JdbcType jdbcType) throws SQLException { PGobject pGobject=new PGobject(); pGobject.setValue(geometry.toText()); pGobject.setType("geometry"); preparedStatement.setObject(i,pGobject); } public Geometry getNullableResult(ResultSet resultSet, String columnName) throws SQLException { String geom = resultSet.getString(columnName); if(geom==null){ return null; }else{ WKTReader wktReader=new WKTReader(); try { return wktReader.read(geom); } catch (ParseException e) { e.printStackTrace(); return null; } } } public Geometry getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException { String geom = resultSet.getString(columnIndex); if(geom==null){ return null; }else{ WKTReader wktReader=new WKTReader(); try { return wktReader.read(geom); } catch (ParseException e) { e.printStackTrace(); return null; } } } public Geometry getNullableResult(CallableStatement callableStatement, int i) throws SQLException { String geom = callableStatement.getString(i); if(geom==null){ return null; }else{ WKTReader wktReader=new WKTReader(); try { return wktReader.read(geom); } catch (ParseException e) { e.printStackTrace(); return null; } } } }
pojo类中属性上,使用注解,将数据库类型映射为java 类型:
@TableField(typeHandler = GeometryTypeHandler.class) private Geometry geom;
最后
分享我在已知由多条线段组成的有序清单下,拼接成一条最短规划线路的迭代算法。我觉得如果要做自动寻路,也不过是在未知有序清单时,根据道路权重等因素处理交叉路口,最后选取最短路程的那一条或者权重最合理的那一条罢了。
/ * 迭代计算路径数据(将多段路径拼接。兼容相交时剪切最短路径、或者不相交时计算投影点最短路径) * @param inputList 多条子线段的有序数据输入 * @param currIndex 当前处理到节点位置 * @param resultGeometry 数据输出 */ private Geometry calculateShortestPath(List<Hxd> inputList, int currIndex, Geometry resultGeometry) { assert !CollectionUtils.isEmpty(inputList); if (inputList.size() == 1) { // 只有一条线,直接返回 return inputList.get(0).getGeom(); } if (currIndex >= inputList.size()) { return resultGeometry; } Hxd currHxd = inputList.get(currIndex); // 获取上一线段的终点,作为当前线段的起点 Coordinate startPoint = null; if (resultGeometry != null) { Coordinate[] coordinates = resultGeometry.getCoordinates(); startPoint = coordinates[coordinates.length - 1]; } // 获取下一条线段的起点,作为当前线段的终点 Coordinate endPoint; if (currIndex == inputList.size() -1) { // 当前为最后一条线段,则在线段的起点、终点中选距离远的一个点为终点 assert startPoint != null; endPoint = calculateEndPointFarthest(startPoint, currHxd.getGeom()); } else { // 计算 线与线 最近的点 endPoint = calculateNearestPoint(currHxd.getGeom(), inputList.get(currIndex + 1).getGeom()); } if (resultGeometry == null) { // 第一次,则在线段的起点、终点中选距离远的一个点作为起点 startPoint = calculateEndPointFarthest(endPoint, currHxd.getGeom()); } // 添加截取的子线(线外一点也是可以截取子线的) LocationIndexedLine lil = new LocationIndexedLine(currHxd.getGeom()); LinearLocation start = lil.indexOf(startPoint); LinearLocation end = lil.indexOf(endPoint); resultGeometry = resultGeometry == null ? lil.extractLine(start, end) : resultGeometry.union(lil.extractLine(start, end)); return calculateShortestPath(inputList, ++currIndex, resultGeometry); } / * 计算点 到 线两端的距离,返回距离较远的端点 * @param endPoint 点 * @param geometry 线 * @return 返回距离较远的端点 */ private Coordinate calculateEndPointFarthest(Coordinate endPoint, Geometry geometry) { Coordinate[] cs = geometry.getCoordinates(); Coordinate start = cs[0]; Coordinate end = cs[cs.length - 1]; return endPoint.distance(start) > endPoint.distance(end) ? start : end; } / * 线1与线2的交点,或线1上距离线2最近的点 * @param line1 线1 * @param line2 线2 * @return 线1与线2的交点,或线1上距离线2最近的点 */ private Coordinate calculateNearestPoint(Geometry line1, Geometry line2) { // 如果两条线段有交点,则取交点。 Geometry geo = line1.intersection(line2); if (!geo.isEmpty()) { return geo.getCoordinate(); } // 如果没有交点,则取线段上距离最近的一个点 Coordinate[] cs = line1.getCoordinates(); Coordinate nearest = null; Double distance = null; PointPairDistance ppd = new PointPairDistance(); for (Coordinate coordinate : cs) { DistanceToPoint.computeDistance(line2, coordinate, ppd); if (distance == null || distance > ppd.getDistance()) { nearest = coordinate; distance = ppd.getDistance(); } } // log.info("line1={}, line2={}不相交,计算line1上距离line2最近点为:{}", line1.toText(), line2.toText(), nearest); return nearest; }
今天的文章
JTS使用笔记分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/bian-cheng-ji-chu/88101.html