From 3756cf0e3841910b22998bc0edf8d209e8539556 Mon Sep 17 00:00:00 2001
From: liusuyi <1951119284@qq.com>
Date: Thu, 13 Nov 2025 11:02:53 +0800
Subject: [PATCH] 增加禁飞区工具类

---
 /dev/null                                                                        |  171 ---------------------
 ard-system/pom.xml                                                               |    7 
 ard-system/src/main/java/com/ard/data/noflyzone/domain/ArdNoFlyZone.java         |   10 
 ard-work/src/main/java/com/ard/work/aspect/UavControlAspect.java                 |   66 +++++--
 ard-system/src/main/java/com/ard/data/noflyzone/utils/NoFlyZoneUtils.java        |  197 ++++++++++++++++--------
 ard-route/src/main/java/com/ard/route/service/impl/ArdRouteInforServiceImpl.java |    8 
 6 files changed, 193 insertions(+), 266 deletions(-)

diff --git a/ard-route/src/main/java/com/ard/route/service/impl/ArdRouteInforServiceImpl.java b/ard-route/src/main/java/com/ard/route/service/impl/ArdRouteInforServiceImpl.java
index ea33a8b..da04076 100644
--- a/ard-route/src/main/java/com/ard/route/service/impl/ArdRouteInforServiceImpl.java
+++ b/ard-route/src/main/java/com/ard/route/service/impl/ArdRouteInforServiceImpl.java
@@ -217,10 +217,10 @@
         List<ArdNoFlyZone> ardNoFlyZones = ardNoFlyZoneService.selectArdNoFlyZoneList(new ArdNoFlyZone());
         if (!ardNoFlyZones.isEmpty()) {
             Vertex vertex = new Vertex(longitude, latitude);
-            boolean inNoFlyZones = NoFlyZoneUtils.isInNoFlyZones(vertex, ardNoFlyZones);
-            if (inNoFlyZones) {
-                throw new ControlException("目标点在禁飞区内,无法执行操作!");
-            }
+//            boolean inNoFlyZones = NoFlyZoneUtils.isPointInPolygon(vertex, ardNoFlyZones);
+//            if (inNoFlyZones) {
+//                throw new ControlException("目标点在禁飞区内,无法执行操作!");
+//            }
         }
         return true;
     }
diff --git a/ard-system/pom.xml b/ard-system/pom.xml
index 798c287..76dd7ba 100644
--- a/ard-system/pom.xml
+++ b/ard-system/pom.xml
@@ -22,6 +22,13 @@
             <groupId>com.ard</groupId>
             <artifactId>ard-common</artifactId>
         </dependency>
+
+        <dependency>
+            <groupId>org.locationtech.jts</groupId>
+            <artifactId>jts-core</artifactId>
+            <version>1.19.0</version>
+        </dependency>
+
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
diff --git a/ard-system/src/main/java/com/ard/data/noflyzone/domain/ArdNoFlyZone.java b/ard-system/src/main/java/com/ard/data/noflyzone/domain/ArdNoFlyZone.java
index 0b746f5..0ed974c 100644
--- a/ard-system/src/main/java/com/ard/data/noflyzone/domain/ArdNoFlyZone.java
+++ b/ard-system/src/main/java/com/ard/data/noflyzone/domain/ArdNoFlyZone.java
@@ -1,11 +1,11 @@
 package com.ard.data.noflyzone.domain;
 
-import com.ard.data.noflyzone.utils.GeoUtils;
+import com.ard.common.annotation.Excel;
+import com.ard.common.core.domain.BaseEntity;
+import com.ard.data.noflyzone.utils.NoFlyZoneUtils;
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;
-import com.ard.common.annotation.Excel;
-import com.ard.common.core.domain.BaseEntity;
 
 import java.util.List;
 
@@ -41,14 +41,14 @@
     // 懒加载 / 动态计算
     public ArdNoFlyBox getArdNoFlyBox() {
         if (ardNoFlyBox == null && geojson != null) {
-            ardNoFlyBox = GeoUtils.getArdNoFlyBox(geojson);
+            ardNoFlyBox = NoFlyZoneUtils.getArdNoFlyBox(geojson);
         }
         return ardNoFlyBox;
     }
 
     public List<Vertex> getVertices() {
         if (vertices == null && geojson != null) {
-            vertices = GeoUtils.toVertexes(geojson);
+            vertices = NoFlyZoneUtils.toVertexes(geojson);
         }
         return vertices;
     }
diff --git a/ard-system/src/main/java/com/ard/data/noflyzone/utils/GeoUtils.java b/ard-system/src/main/java/com/ard/data/noflyzone/utils/GeoUtils.java
deleted file mode 100644
index 0c8e045..0000000
--- a/ard-system/src/main/java/com/ard/data/noflyzone/utils/GeoUtils.java
+++ /dev/null
@@ -1,171 +0,0 @@
-package com.ard.data.noflyzone.utils;
-
-import com.ard.data.noflyzone.domain.ArdNoFlyBox;
-import com.ard.data.noflyzone.domain.FeatureCollection;
-import com.ard.data.noflyzone.domain.Vertex;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * 几何工具类
- */
-public class GeoUtils {
-    /**
-     * 根据 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<List<List<Double>>> rings = (List<List<List<Double>>>) coords;
-                for (List<List<Double>> ring : rings) {
-                    for (List<Double> 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<List<List<List<Double>>>> polygons = (List<List<List<List<Double>>>>) coords;
-                for (List<List<List<Double>>> polygon : polygons) {
-                    for (List<List<Double>> ring : polygon) {
-                        for (List<Double> 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);
-    }
-    /**
-     * 判断点是否在多边形内(包括边界)
-     * 射线法:从该点向右发射水平射线,计算交点个数
-     *
-     * @param vertex 点对象
-     * @param vertexes 多边形顶点列表(按顺序闭合)
-     * @return true 表示点在多边形内或在边上
-     */
-    public static boolean isVertexInPoly(Vertex vertex, List<Vertex> vertexes) {
-        if (vertex == null || vertexes == null || vertexes.size() < 3) {
-            return false; // 无效输入
-        }
-
-        int count = 0;
-        int n = vertexes.size();
-
-        for (int i = 0; i < n; i++) {
-            Vertex v1 = vertexes.get(i);
-            Vertex v2 = vertexes.get((i + 1) % n);
-
-            // 1. 点就是多边形的顶点
-            if (v1.IsSame(vertex)) {
-                return true;
-            }
-
-            // 2. 点在线段上
-            double minX = Math.min(v1.x, v2.x);
-            double maxX = Math.max(v1.x, v2.x);
-            double minY = Math.min(v1.y, v2.y);
-            double maxY = Math.max(v1.y, v2.y);
-
-            // 水平线段
-            if (minY == maxY) {
-                if (vertex.y == minY && vertex.x >= minX && vertex.x <= maxX) {
-                    return true;
-                } else {
-                    continue; // 不计入交点
-                }
-            }
-
-            // 点在线段纵向范围外
-            if (vertex.y < minY || vertex.y > maxY) {
-                continue;
-            }
-
-            // 计算射线交点的 X 坐标
-            double xIntersect = v1.x + (vertex.y - v1.y) * (v2.x - v1.x) / (v2.y - v1.y);
-
-            // 交点在点左侧,不计入
-            if (xIntersect < vertex.x) {
-                continue;
-            }
-
-            // 交点就在点上,认为在边界
-            if (xIntersect == vertex.x) {
-                return true;
-            }
-
-            // 避免射线穿过线段下端点重复计数
-            if (vertex.y == minY) {
-                continue;
-            }
-
-            // 其它情况,交点计数
-            count++;
-        }
-
-        // 奇数交点 → 点在多边形内
-        return count % 2 != 0;
-    }
-
-    /**
-     * 将 GeoJSON Polygon 的外环坐标转换成 Vertex 列表
-     */
-    public static List<Vertex> toVertexes(FeatureCollection featureCollection) {
-        List<Vertex> 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<List<List<Double>>> coords = feature.getGeometry().getCoordinates();
-            if (coords != null && !coords.isEmpty()) {
-                List<List<Double>> outerRing = coords.get(0);
-                for (List<Double> point : outerRing) {
-                    if (point.size() >= 2) {
-                        vertexes.add(new Vertex(point.get(0), point.get(1)));
-                    }
-                }
-            }
-        }
-        return vertexes;
-    }
-
-}
diff --git a/ard-system/src/main/java/com/ard/data/noflyzone/utils/NoFlyZoneUtils.java b/ard-system/src/main/java/com/ard/data/noflyzone/utils/NoFlyZoneUtils.java
index 350b3d4..ea09a5a 100644
--- a/ard-system/src/main/java/com/ard/data/noflyzone/utils/NoFlyZoneUtils.java
+++ b/ard-system/src/main/java/com/ard/data/noflyzone/utils/NoFlyZoneUtils.java
@@ -2,104 +2,169 @@
 
 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 {
 
     /**
-     * 判断点是否在禁飞区集合中
-     *
-     * @param point   目标点
-     * @param zones   禁飞区集合
-     * @return true: 点在任意禁飞区内
+     * 判断点是否在多边形内(射线法)
      */
-    public static boolean isInNoFlyZones(Vertex point, List<ArdNoFlyZone> zones) {
-        if (point == null || zones == null || zones.isEmpty()) {
-            return false;
+    public static boolean isPointInPolygon(Vertex point, List<Vertex> 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;
+    }
 
-        for (ArdNoFlyZone zone : zones) {
-            // 1️⃣ 先快速判断外接矩形
-            if (zone.getArdNoFlyBox() != null &&
-                !isInBox(point, zone.getArdNoFlyBox())) {
-                continue; // 点不在外接矩形内,跳过
-            }
-
-            // 2️⃣ 多边形判断
-            List<Vertex> vertices = zone.getVertices();
-            if (vertices != null && !vertices.isEmpty()) {
-                if (isPointInPolygon(point, vertices)) {
-                    return true;
-                }
+    /**
+     * 判断线段是否与多边形相交
+     */
+    public static boolean isLineIntersectPolygon(Vertex p1, Vertex p2, List<Vertex> 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 isInBox(Vertex point, ArdNoFlyBox box) {
-        return point.x >= box.getMinx() && point.x <= box.getMaxx() &&
-               point.y >= box.getMiny() && point.y <= box.getMaxy();
-    }
+    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);
 
-    /**
-     * 射线法判断点是否在多边形内
-     */
-    private static boolean isPointInPolygon(Vertex point, List<Vertex> vertices) {
-        if (point == null || vertices == null || vertices.size() < 3) {
-            return false;
+        if (((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) &&
+                ((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0))) {
+            return true;
         }
 
-        int count = 0;
-        int n = vertices.size();
-        double epsilon = 1e-9;
+        // 处理共线情况
+        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;
 
-        for (int i = 0; i < n; i++) {
-            Vertex v1 = vertices.get(i);
-            Vertex v2 = vertices.get((i + 1) % n);
+        return false;
+    }
 
-            // 点就是顶点
-            if (v1.IsSame(point)) {
-                return true;
-            }
+    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);
+    }
 
-            double minX = Math.min(v1.x, v2.x);
-            double maxX = Math.max(v1.x, v2.x);
-            double minY = Math.min(v1.y, v2.y);
-            double maxY = Math.max(v1.y, v2.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);
+    }
 
-            // 水平线段
-            if (Math.abs(minY - maxY) < epsilon) {
-                if (Math.abs(point.y - minY) < epsilon && point.x >= minX && point.x <= maxX) {
-                    return true;
-                } else {
-                    continue;
-                }
-            }
 
-            // 点不在纵向范围
-            if (point.y < minY || point.y > maxY) {
+    /**
+     * 根据 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;
             }
 
-            double xIntersect = v1.x + (point.y - v1.y) * (v2.x - v1.x) / (v2.y - v1.y);
+            String type = geometry.getType();
+            Object coords = geometry.getCoordinates();
 
-            if (Math.abs(xIntersect - point.x) < epsilon) {
-                return true; // 点在边界
+            if ("Polygon".equalsIgnoreCase(type)) {
+                // Polygon: [[[lon, lat], [lon, lat], ...]]
+                List<List<List<Double>>> rings = (List<List<List<Double>>>) coords;
+                for (List<List<Double>> ring : rings) {
+                    for (List<Double> 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);
+                    }
+                }
             }
-            if (xIntersect > point.x) {
-                count++;
-            }
-
-            if (Math.abs(point.y - minY) < epsilon) {
-                count--;
+            else if ("MultiPolygon".equalsIgnoreCase(type)) {
+                // MultiPolygon: [[[[lon, lat], ...]], ...]
+                List<List<List<List<Double>>>> polygons = (List<List<List<List<Double>>>>) coords;
+                for (List<List<List<Double>>> polygon : polygons) {
+                    for (List<List<Double>> ring : polygon) {
+                        for (List<Double> 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 count % 2 != 0;
+        return new ArdNoFlyBox(minx, miny, maxx, maxy);
     }
+
+    /**
+     * 将 GeoJSON Polygon 的外环坐标转换成 Vertex 列表
+     */
+    public static List<Vertex> toVertexes(FeatureCollection featureCollection) {
+        List<Vertex> 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<List<List<Double>>> coords = feature.getGeometry().getCoordinates();
+            if (coords != null && !coords.isEmpty()) {
+                List<List<Double>> outerRing = coords.get(0);
+                for (List<Double> point : outerRing) {
+                    if (point.size() >= 2) {
+                        vertexes.add(new Vertex(point.get(0), point.get(1)));
+                    }
+                }
+            }
+        }
+        return vertexes;
+    }
+
+
 }
diff --git a/ard-work/src/main/java/com/ard/work/aspect/UavControlAspect.java b/ard-work/src/main/java/com/ard/work/aspect/UavControlAspect.java
index 0450d6a..5f1f2fb 100644
--- a/ard-work/src/main/java/com/ard/work/aspect/UavControlAspect.java
+++ b/ard-work/src/main/java/com/ard/work/aspect/UavControlAspect.java
@@ -23,7 +23,6 @@
 import org.aspectj.lang.annotation.Aspect;
 import org.aspectj.lang.annotation.Before;
 import org.springframework.stereotype.Component;
-
 import javax.annotation.Resource;
 import java.util.List;
 
@@ -144,34 +143,61 @@
             return;
         }
 
-        Double longitude = null;
-        Double latitude = null;
+        // ① 提取 boxSn(无人机编号)和目标点坐标
+        String boxSn = null;
+        Double targetLon = null, targetLat = null;
 
         for (Object arg : args) {
-            if (arg == null) {
-                continue;
+            if (arg == null) continue;
+
+            if (boxSn == null) {
+                String sn = ReflectUtils.getFieldValue(arg, "boxSn");
+                if (sn != null) boxSn = sn;
             }
-            Double lon = ReflectUtils.getFieldValue(arg, "longitude");
-            Double lat = ReflectUtils.getFieldValue(arg, "latitude");
-            if (lon != null && lat != null) {
-                longitude = lon;
-                latitude = lat;
-                break;
+
+            if (targetLon == null || targetLat == null) {
+                Double lon = ReflectUtils.getFieldValue(arg, "longitude");
+                Double lat = ReflectUtils.getFieldValue(arg, "latitude");
+                if (lon != null && lat != null) {
+                    targetLon = lon;
+                    targetLat = lat;
+                }
             }
         }
 
-        if (longitude == null || latitude == null) {
-            throw new ControlException("方法参数中未找到有效经纬度字段,无法判断禁飞区!");
+        if (targetLon == null || targetLat == null) {
+            throw new ControlException("方法参数中未找到有效目标经纬度字段,无法判断禁飞区!");
         }
 
-        List<ArdNoFlyZone> ardNoFlyZones = ardNoFlyZoneService.selectArdNoFlyZoneList(new ArdNoFlyZone());
-        if (!ardNoFlyZones.isEmpty()) {
-            Vertex vertex = new Vertex(longitude, latitude);
-            boolean inNoFlyZones = NoFlyZoneUtils.isInNoFlyZones(vertex, ardNoFlyZones);
-            if (inNoFlyZones) {
-                throw new ControlException("目标点在禁飞区内,无法执行操作!");
+        // ② 获取无人机当前位置(从缓存)
+        Vertex current = null;
+        if (boxSn != null) {
+            TelemetryData telemetry = UavTelemetryDataCache.get(boxSn);
+            if (telemetry != null) {
+                current = new Vertex(telemetry.getLng(), telemetry.getLat());
+                //current = new Vertex(125.03, 46.58);
+            }
+        }
+
+        Vertex target = new Vertex(targetLon, targetLat);
+
+        // ③ 查询禁飞区
+        List<ArdNoFlyZone> zones = ardNoFlyZoneService.selectArdNoFlyZoneList(new ArdNoFlyZone());
+        if (zones.isEmpty()) return;
+
+        for (ArdNoFlyZone zone : zones) {
+            List<Vertex> polygon = zone.getVertices();
+            if (polygon == null || polygon.size() < 3) continue;
+
+            // 检查目标点是否在禁飞区
+            if (NoFlyZoneUtils.isPointInPolygon(target, polygon)) {
+                throw new ControlException("目标点在禁飞区【" + zone.getName() + "】内,无法执行操作!");
+            }
+
+            // 检查路径是否穿越禁飞区
+            if (current != null && NoFlyZoneUtils.isLineIntersectPolygon(current, target, polygon)) {
+                throw new ControlException("路径穿越禁飞区【" + zone.getName() + "】,无法执行操作!");
             }
         }
     }
-
 }

--
Gitblit v1.9.3