场景
Java中使用JTS对空间几何计算(读取WKT、距离、点在面内、长度、面积、相交等):
https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/126302894
Java+GeoTools实现WKT数据根据EPSG编码进行坐标系转换:
https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/130370754基于gis的业务场景中,需要在地图中录入区域数据的wkt数据,然后根据某个坐标点判断是属于哪个区域,
以及距离所属区域中最近的端点的方位角,比如坐标点位于某区域东南方向100米。
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
实现
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方法的使用可参考:
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"; } } }
逻辑就是对比目标点到线的两个端点的距离,取较近的进行判断,然后做方位角判断。
运行效果测试