package com.ard.data.noflyzone.utils; import com.ard.data.noflyzone.domain.ArdNoFlyBox; import com.ard.data.noflyzone.domain.ArdNoFlyZone; import com.ard.data.noflyzone.domain.FeatureCollection; import com.ard.data.noflyzone.domain.Vertex; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.LineString; import org.locationtech.jts.geom.Polygon; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class NoFlyZoneUtils { /** * 计算点到多边形的最近距离 */ public static double pointToPolygonDistance(Vertex point, List polygon) { // 如果在多边形内部,距离为 0 if (isPointInPolygon(point, polygon)) { return 0; } double minDistance = Double.MAX_VALUE; int n = polygon.size(); for (int i = 0; i < n; i++) { Vertex a = polygon.get(i); Vertex b = polygon.get((i + 1) % n); // 下一点,最后一条边连接第一个点 double dist = pointToSegmentDistance(point, a, b); minDistance = Math.min(minDistance, dist); } return minDistance; } /** * 计算点到线段的距离 */ public static double pointToSegmentDistance(Vertex p, Vertex a, Vertex b) { double x = p.x, y = p.y; double x1 = a.x, y1 = a.y; double x2 = b.x, y2 = b.y; double dx = x2 - x1; double dy = y2 - y1; if (dx == 0 && dy == 0) { // A 和 B 是同一个点 dx = x - x1; dy = y - y1; return Math.sqrt(dx * dx + dy * dy); } // 投影参数 t(0-1之间表示在线段内部) double t = ((x - x1) * dx + (y - y1) * dy) / (dx * dx + dy * dy); if (t < 0) { // 最近点是 A dx = x - x1; dy = y - y1; } else if (t > 1) { // 最近点是 B dx = x - x2; dy = y - y2; } else { // 最近点在线段中间 double projX = x1 + t * dx; double projY = y1 + t * dy; dx = x - projX; dy = y - projY; } return Math.sqrt(dx * dx + dy * dy); } /** * 判断点是否在多边形内(射线法) */ public static boolean isPointInPolygon(Vertex point, List polygon) { int n = polygon.size(); boolean inside = false; for (int i = 0, j = n - 1; i < n; j = i++) { Vertex vi = polygon.get(i); Vertex vj = polygon.get(j); boolean intersect = ((vi.y > point.y) != (vj.y > point.y)) && (point.x < (vj.x - vi.x) * (point.y - vi.y) / (vj.y - vi.y) + vi.x); if (intersect) inside = !inside; } return inside; } /** * 判断线段是否与多边形相交 */ public static boolean isLineIntersectPolygon(Vertex p1, Vertex p2, List polygon) { int n = polygon.size(); for (int i = 0; i < n; i++) { Vertex a = polygon.get(i); Vertex b = polygon.get((i + 1) % n); if (isSegmentsIntersect(p1, p2, a, b)) { return true; } } return false; } /** * 判断两条线段是否相交 */ private static boolean isSegmentsIntersect(Vertex p1, Vertex p2, Vertex p3, Vertex p4) { double d1 = cross(p3, p4, p1); double d2 = cross(p3, p4, p2); double d3 = cross(p1, p2, p3); double d4 = cross(p1, p2, p4); if (((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) && ((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0))) { return true; } // 处理共线情况 if (d1 == 0 && onSegment(p3, p4, p1)) return true; if (d2 == 0 && onSegment(p3, p4, p2)) return true; if (d3 == 0 && onSegment(p1, p2, p3)) return true; if (d4 == 0 && onSegment(p1, p2, p4)) return true; return false; } private static double cross(Vertex p1, Vertex p2, Vertex p3) { return (p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y); } private static boolean onSegment(Vertex p1, Vertex p2, Vertex q) { return q.x <= Math.max(p1.x, p2.x) && q.x >= Math.min(p1.x, p2.x) && q.y <= Math.max(p1.y, p2.y) && q.y >= Math.min(p1.y, p2.y); } /** * 判断点是否在矩形内 */ public static boolean isInsideBox(Vertex point, ArdNoFlyBox box) { if (point == null || box == null) return false; return point.x >= box.getMinx() && point.x <= box.getMaxx() && point.y >= box.getMiny() && point.y <= box.getMaxy(); } /** * 根据 GeoJSON(FeatureCollection)计算外接矩形 */ @SuppressWarnings("unchecked") public static ArdNoFlyBox getArdNoFlyBox(FeatureCollection featureCollection) { if (featureCollection == null || featureCollection.getFeatures() == null) { return null; } double minx = Double.MAX_VALUE; double miny = Double.MAX_VALUE; double maxx = Double.MIN_VALUE; double maxy = Double.MIN_VALUE; for (FeatureCollection.Feature feature : featureCollection.getFeatures()) { FeatureCollection.Geometry geometry = feature.getGeometry(); if (geometry == null || geometry.getCoordinates() == null) { continue; } String type = geometry.getType(); Object coords = geometry.getCoordinates(); if ("Polygon".equalsIgnoreCase(type)) { // Polygon: [[[lon, lat], [lon, lat], ...]] List>> rings = (List>>) coords; for (List> ring : rings) { for (List point : ring) { if (point.size() < 2) { continue; } double x = point.get(0); double y = point.get(1); minx = Math.min(minx, x); miny = Math.min(miny, y); maxx = Math.max(maxx, x); maxy = Math.max(maxy, y); } } } else if ("MultiPolygon".equalsIgnoreCase(type)) { // MultiPolygon: [[[[lon, lat], ...]], ...] List>>> polygons = (List>>>) coords; for (List>> polygon : polygons) { for (List> ring : polygon) { for (List point : ring) { if (point.size() < 2) { continue; } double x = point.get(0); double y = point.get(1); minx = Math.min(minx, x); miny = Math.min(miny, y); maxx = Math.max(maxx, x); maxy = Math.max(maxy, y); } } } } } return new ArdNoFlyBox(minx, miny, maxx, maxy); } /** * 将 GeoJSON Polygon 的外环坐标转换成 Vertex 列表 */ public static List toVertexes(FeatureCollection featureCollection) { List vertexes = new ArrayList<>(); if (featureCollection == null || featureCollection.getFeatures() == null || featureCollection.getFeatures().isEmpty()) { return vertexes; } FeatureCollection.Feature feature = featureCollection.getFeatures().get(0); if (feature.getGeometry() != null) { List>> coords = feature.getGeometry().getCoordinates(); if (coords != null && !coords.isEmpty()) { List> outerRing = coords.get(0); for (List point : outerRing) { if (point.size() >= 2) { vertexes.add(new Vertex(point.get(0), point.get(1))); } } } } return vertexes; } }