From 6cf5d9645686477280ebb30a11ab916eb1f33b73 Mon Sep 17 00:00:00 2001
From: aix <vip_xiaobin810@163.com>
Date: Fri, 19 Jul 2024 16:23:42 +0800
Subject: [PATCH] 新建面状航线
---
src/main/java/com/dji/sample/wayline/plane/PlaneCourseUtils.java | 342 ++++++++++++++++++++++++++++++++++++++
src/main/java/com/dji/sample/wayline/plane/param/CreateWaylineParam.java | 31 +++
src/main/java/com/dji/sample/wayline/plane/BoundingRectangle.java | 69 +++++++
src/main/java/com/dji/sample/wayline/plane/MapLatLng.java | 28 +++
src/main/java/com/dji/sample/wayline/plane/controller/PlaneCreateWayLineController.java | 30 +++
5 files changed, 500 insertions(+), 0 deletions(-)
diff --git a/src/main/java/com/dji/sample/wayline/plane/BoundingRectangle.java b/src/main/java/com/dji/sample/wayline/plane/BoundingRectangle.java
new file mode 100644
index 0000000..60fccf9
--- /dev/null
+++ b/src/main/java/com/dji/sample/wayline/plane/BoundingRectangle.java
@@ -0,0 +1,69 @@
+package com.dji.sample.wayline.plane;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @Author AIX
+ * @Date 2024/7/11 11:12
+ * @Version 1.0
+ */
+@Data
+public class BoundingRectangle {
+
+ public MapLatLng topLeft;
+ public MapLatLng topRight;
+ public MapLatLng bottomRight;
+ public MapLatLng bottomLeft;
+
+ // 构造器
+ // 构造函数,使用四个角点初始化外接矩形
+ public BoundingRectangle(MapLatLng topLeft, MapLatLng topRight, MapLatLng bottomRight, MapLatLng bottomLeft) {
+ this.topLeft = topLeft;
+ this.topRight = topRight;
+ this.bottomRight = bottomRight;
+ this.bottomLeft = bottomLeft;
+ }
+
+ /**
+ * 静态方法,根据多边形点列表计算外接矩形并返回BoundingRectangle实例
+ * @param polygon
+ * @return
+ */
+ public static BoundingRectangle fromPolygon(List<double[]> polygon) {
+ double maxLat = Double.NEGATIVE_INFINITY;
+ double minLat = Double.POSITIVE_INFINITY;
+ double maxLon = Double.NEGATIVE_INFINITY;
+ double minLon = Double.POSITIVE_INFINITY;
+
+ // 遍历多边形点以找到边界
+ for (double[] point : polygon) {
+ double lat = point[1]; // 假设每个点的数组是先经度[0]后纬度[1]
+ double lon = point[0];
+
+ if (lat > maxLat) {
+ maxLat = lat;
+ }
+ if (lat < minLat) {
+ minLat = lat;
+ }
+ if (lon > maxLon) {
+ maxLon = lon;
+ }
+ if (lon < minLon) {
+ minLon = lon;
+ }
+ }
+
+ // 创建外接矩形的四个角点
+ MapLatLng topLeft = new MapLatLng(maxLat, minLon);
+ MapLatLng topRight = new MapLatLng(maxLat, maxLon);
+ MapLatLng bottomRight = new MapLatLng(minLat, maxLon);
+ MapLatLng bottomLeft = new MapLatLng(minLat, minLon);
+
+ // 返回BoundingRectangle实例
+ return new BoundingRectangle(topLeft, topRight, bottomRight, bottomLeft);
+ }
+
+}
diff --git a/src/main/java/com/dji/sample/wayline/plane/MapLatLng.java b/src/main/java/com/dji/sample/wayline/plane/MapLatLng.java
new file mode 100644
index 0000000..bca3f6f
--- /dev/null
+++ b/src/main/java/com/dji/sample/wayline/plane/MapLatLng.java
@@ -0,0 +1,28 @@
+package com.dji.sample.wayline.plane;
+
+import lombok.Data;
+
+/**
+ * @Author AIX
+ * @Date 2024/7/9 21:40
+ * @Version 1.0
+ */
+@Data
+public class MapLatLng {
+
+ private double latitude;
+ private double longitude;
+
+ public MapLatLng(double lat, double lng) {
+ this.latitude = lat;
+ this.longitude = lng;
+ }
+
+ @Override
+ public String toString() {
+ return "{" +
+ "longitude:" + this.longitude +
+ ",\nlatitude:" + this.latitude +
+ '}';
+ }
+}
diff --git a/src/main/java/com/dji/sample/wayline/plane/PlaneCourseUtils.java b/src/main/java/com/dji/sample/wayline/plane/PlaneCourseUtils.java
new file mode 100644
index 0000000..bc780cb
--- /dev/null
+++ b/src/main/java/com/dji/sample/wayline/plane/PlaneCourseUtils.java
@@ -0,0 +1,342 @@
+package com.dji.sample.wayline.plane;
+
+import com.dji.sample.patches.utils.GeoToolsUtil;
+import com.dji.sample.wayline.plane.param.CreateWaylineParam;
+import org.geotools.geometry.jts.JTSFactoryFinder;
+import org.locationtech.jts.geom.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @Author AIX
+ * @Date 2024/7/9 21:33
+ * 面状航线
+ * @Version 1.0
+ */
+public class PlaneCourseUtils {
+
+ /**
+ * @param height 无人机高度,单位米(m)
+ * @param frame 画幅
+ * @param focal 焦距
+ * @param ratio 重叠率
+ */
+ private static double get(double height, double frame, double focal, double ratio) {
+ //todo 如果focal焦距为0的话,则使用默认值值24毫米
+ focal = focal == 0 ? 24 : focal;
+ //单位换成米
+ focal = focal / 1000;
+ frame = frame / 1000;
+ //设呈现的真实距离为x
+ double x;
+ //拍摄到的距离
+ x = frame * height / focal;
+ //设重叠距离
+ double d;
+ //重叠部分的距离
+ d = ratio * x;
+ //非重叠部分的距离 (单位米)
+ d = x - d;
+ return d;
+ }
+
+ /**
+ * 方位角算法:已知一点经纬度,方位角,距离,求另一点经纬度。
+ *
+ * @param latLng 经纬度
+ * @param distance 距离
+ * @param bearing 方位角
+ * @return
+ */
+ private static MapLatLng getBearingLatLng(MapLatLng latLng, int distance, double bearing) {
+
+ double R = 6371.393 * 1000;
+
+ double δ = distance / R;
+ double θ = toRadius(bearing);
+
+ double φ1 = toRadius(latLng.getLatitude());
+ double λ1 = toRadius(latLng.getLongitude());
+
+ double sinφ2 = Math.sin(φ1) * Math.cos(δ) + Math.cos(φ1) * Math.sin(δ) * Math.cos(θ);
+ double φ2 = Math.asin(sinφ2);
+
+ double y = Math.sin(θ) * Math.sin(δ) * Math.cos(φ1);
+ double x = Math.cos(δ) - Math.sin(φ1) * sinφ2;
+ double λ2 = λ1 + Math.atan2(y, x);
+
+ double lat = toDegree(φ2);
+ double lng = toDegree(λ2);
+
+ return new MapLatLng(lat, lng);
+ }
+
+ private static double toRadius(double value) {
+ return value * Math.PI / 180;
+ }
+
+ private static double toDegree(double value) {
+ return value * 180 / Math.PI;
+ }
+
+ /**
+ * 最北纬点的经纬度
+ *
+ * @param polygon 面集合
+ * @return
+ */
+ private static MapLatLng maxLatPoint(List<double[]> polygon) {
+ // 查找最北的点
+ double maxLat = Double.MIN_VALUE;
+ double[] maxLatPoint = null;
+
+ for (double[] point : polygon) {
+ if (point[1] > maxLat) {
+ maxLat = point[1];
+ maxLatPoint = point;
+ }
+ }
+ return new MapLatLng(maxLatPoint[1], maxLatPoint[0]);
+ }
+
+ /**
+ * 最南纬点的经纬度
+ *
+ * @param polygon 面集合
+ * @return
+ */
+ private static MapLatLng minLatPoint(List<double[]> polygon) {
+ // 查最南的点
+ double minLat = Double.MAX_VALUE;
+ double[] minLatPoint = null;
+
+ for (double[] point : polygon) {
+ if (point[1] < minLat) {
+ minLat = point[1];
+ minLatPoint = point;
+ }
+ }
+ return new MapLatLng(minLatPoint[1], minLatPoint[0]);
+ }
+
+ /**
+ * 根据左上角经纬度生成面点集合
+ *
+ * @param mapLatLng 从哪个点开始
+ * @param intValue 距离
+ * @param bearing 角度
+ * @param mapLatLngMin 面最南经纬度
+ */
+ private static List<MapLatLng> createPoint(MapLatLng mapLatLng, int intValue, double bearing, MapLatLng mapLatLngMin) {
+
+ List<MapLatLng> pointList = new ArrayList<>();
+ while (true) {
+ MapLatLng newLocation = getBearingLatLng(mapLatLng, intValue, bearing);
+ if (newLocation.getLatitude() <= mapLatLngMin.getLatitude())
+ break;
+
+ mapLatLng = newLocation;
+ pointList.add(newLocation);
+ System.out.println(newLocation + ",");
+ }
+ return pointList;
+ }
+
+ public static List<Coordinate> createWaylinePoints(CreateWaylineParam param) {
+ // 获取外接矩形
+ BoundingRectangle boundingRectangle = BoundingRectangle.fromPolygon(param.getPolygon());
+
+ // 航向重叠计算 待完成
+
+ // 旁向重叠计算
+ double distance2 = PlaneCourseUtils.get(param.getHeight(), param.getFrame(), param.getFocal(), 0.8);
+ System.out.println("旁向重叠计算距离:" + distance2 * 100 / 100);
+ int intValue = (int) distance2;
+
+ // 以最北纬度为基准点,在航线间隔距离下,
+ // 计算出 180 度方向(正北为0度)的一个点,该点则是第一条航线上的点,
+ // 然后再通过该点为基准点,一直反复计算出下一个点,直到下一个航点的位置小于最南纬度为止
+
+ //最南纬点经纬度
+ MapLatLng mapLatLngMin = PlaneCourseUtils.minLatPoint(param.getPolygon());
+
+ List<MapLatLng> topLeftPointList = PlaneCourseUtils.createPoint(boundingRectangle.topLeft, intValue, param.getBearing(), mapLatLngMin);
+ List<MapLatLng> topRightPointList = PlaneCourseUtils.createPoint(boundingRectangle.topRight, intValue, param.getBearing(), mapLatLngMin);
+
+ List<LineString> lineStrings = PlaneCourseUtils.createLine(topLeftPointList, topRightPointList);
+
+ List<Coordinate> points = PlaneCourseUtils.getPoints(param.getPolygon(), lineStrings);
+
+ return points;
+ }
+
+ //MULTIPOLYGON(((115.86528871951153 28.625287925196325,
+ // 115.86561708513025 28.625787612007546,
+ // 115.86815834948467 28.624602640426623,
+ // 115.86773004650362 28.62425999804172,
+ // 115.86528871951153 28.625287925196325)))
+ public static void main(String[] args) {
+
+ //外接矩形最北经纬度和最南经纬度
+ List<double[]> polygon = new ArrayList<>();
+ double[] a = {116.028037250229,25.8948290570725};
+ double[] a2 = {116.031565260299,25.8950672687002};
+ double[] a3 = {116.032112214864,25.8901339309971};
+ double[] a4 = {116.028726270654,25.8895106128711};
+ polygon.add(a);
+ polygon.add(a2);
+ polygon.add(a3);
+ polygon.add(a4);
+ BoundingRectangle boundingRectangle = BoundingRectangle.fromPolygon(polygon);
+ System.out.println("northLatLng:" + boundingRectangle);
+
+ //最北纬度点
+ MapLatLng mapLatLngMax = maxLatPoint(polygon);
+ MapLatLng mapLatLngMin = minLatPoint(polygon);
+ System.out.println("最北纬度点:" + mapLatLngMax);
+ System.out.println("最南纬度点:" + mapLatLngMin);
+
+ // 航向重叠计算
+ //已知短画幅=24mm,假设从无人机拿到的焦距是26mm,高度设置的是100米,重叠率设置80%,则无人机真正拍摄到的距离差为:
+ // m30t:5.0125 焦距:21-75 mm(等效焦距:113-405 mm)
+ // m3d:6.9999943 等效焦距:24 mm
+ double distance1 = get(100, 24, 26, 0.8);
+ System.out.println("航向重叠计算距离" + distance1);
+
+ // 旁向重叠计算
+ //旁向重叠的计算影响航线的的间隔,对于一块固定地表,间隔越宽,则飞行的航线就越少,
+ // 间隔越窄,则飞行的航线就越多,主要影响间隔的就是重叠率。
+ // 已知长画幅=35mm,假设从无人机拿到的焦距是26mm,高度设置的是100米,重叠率设置80%,则航线间距离为:
+ // 为什么是26mm 对焦距离 = 5 + 最小焦距 = 21
+ double distance2 = get(372.3, 100, 10*6.9999943, 0.8);
+ System.out.println("旁向重叠计算距离:" + distance2 * 100 / 100);
+ int intValue = (int) distance2;
+
+ // 创建一个起始的MapLatLng对象 以最北纬度为基准点
+// MapLatLng startLocation = new MapLatLng(boundingRectangle.bottomLeft.getLatitude(), boundingRectangle.bottomLeft.getLongitude()); // 经纬度
+//
+ // 定义要移动的距离(单位:米)和方位角(单位:度)
+ double bearing = 180; // 方向
+
+ // 调用getBearingLatLng方法计算新位置
+ System.out.println("第一条线路:" + mapLatLngMax);
+ System.out.println("------------------------------------------------");
+
+ // 以最北纬度为基准点,在航线间隔距离下,
+ // 计算出 180 度方向(正北为0度)的一个点,该点则是第一条航线上的点,
+ // 然后再通过该点为基准点,一直反复计算出下一个点,直到下一个航点的位置小于最南纬度为止
+ List<MapLatLng> topLeftPointList = createPoint(boundingRectangle.topLeft, intValue, bearing, mapLatLngMin);
+ List<MapLatLng> topRightPointList = createPoint(boundingRectangle.topRight, intValue, bearing, mapLatLngMin);
+
+ List<LineString> lineStrings = createLine(topLeftPointList, topRightPointList);
+
+ List<Coordinate> points = getPoints(polygon, lineStrings);
+ for (Coordinate point:points) {
+ System.out.println("" + point.x + ", " + point.y + ";");
+ }
+
+ }
+
+ /**
+ * 创建线的集合
+ *
+ * @param topLeftPointList 左上角点集合
+ * @param topRightPointList 右上角点集合
+ */
+ private static List<LineString> createLine(List<MapLatLng> topLeftPointList, List<MapLatLng> topRightPointList) {
+ // 创建GeometryFactory
+ GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();
+
+ //线的集合
+ List<LineString> lineStrings = new ArrayList<>();
+ // 定义两点来创建LineString
+ for (int i = 0; i < topLeftPointList.size(); i++) {
+ MapLatLng topLeft = topLeftPointList.get(i);
+ MapLatLng topRight = topRightPointList.get(i);
+ Coordinate start = new Coordinate(topLeft.getLongitude(), topRight.getLatitude(), 0);
+ Coordinate end = new Coordinate(topRight.getLongitude(), topRight.getLatitude(), 0);
+ LineString line = geometryFactory.createLineString(new Coordinate[]{start, end});
+ lineStrings.add(line);
+ System.out.println("line:" + line);
+ }
+ return lineStrings;
+ }
+
+ /**
+ * 获取面和线的交集点集合
+ *
+ * @param polygonNums
+ * @param lineStrings
+ */
+ private static List<Coordinate> getPoints(List<double[]> polygonNums, List<LineString> lineStrings) {
+
+ // 创建GeometryFactory
+ GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();
+
+ // 检查polygonNums是否为空或长度小于3(至少需要三个点才能形成一个闭合多边形)
+ if (polygonNums == null || polygonNums.size() < 3) {
+ System.out.println("多边形顶点数量不足或为空");
+ return null;
+ }
+
+ // 创建坐标数组并闭合多边形
+ Coordinate[] shell = new Coordinate[polygonNums.size() + 1];
+ for (int i = 0; i < polygonNums.size(); i++) {
+ double[] polygonD = polygonNums.get(i);
+ shell[i] = new Coordinate(polygonD[0], polygonD[1], 0);
+ }
+ // 闭合多边形
+ shell[polygonNums.size()] = new Coordinate(polygonNums.get(0)[0], polygonNums.get(0)[1], 0);
+
+ // 创建多边形
+ Polygon polygon = geometryFactory.createPolygon(shell);
+
+ System.out.println("polygon:" + polygon);
+
+ Coordinate centro = GeoToolsUtil.getCentro(polygon);
+
+ //返回集合
+ List<Coordinate> retPoint = new ArrayList<>();
+
+ // 循环处理线集合
+ int index = 0;
+ for (LineString line : lineStrings) {
+ index = index + 1;
+ // 计算交集
+ Geometry intersection = polygon.intersection(line);
+
+ // 处理交集结果
+ if (intersection instanceof Point) {
+ System.out.println("交点坐标: " + intersection.getCoordinate());
+ } else if (intersection instanceof MultiPoint) {
+ // 如果有多个交点
+ for (int i = 0; i < intersection.getNumGeometries(); i++) {
+ Point point = (Point) intersection.getGeometryN(i);
+ retPoint.add(point.getCoordinate());
+ }
+ } else if (intersection instanceof LineString) {
+ // 交集是一条线段,但这里我们可能不直接打印它作为点
+ System.out.println("交集是一个LineString。");
+ // 如果需要,可以打印线段的起点和终点
+ LineString lineString = (LineString) intersection;
+ Coordinate start = lineString.getStartPoint().getCoordinate();
+ Coordinate end = lineString.getEndPoint().getCoordinate();
+ if (!(index%2==0)) {
+ retPoint.add(start);
+ retPoint.add(end);
+ } else {
+ retPoint.add(end);
+ retPoint.add(start);
+ }
+
+ } else {
+ // 如果没有交点或结果不是点
+ System.out.println("没有交点或未知类型");
+ }
+ }
+ retPoint.add(centro);
+ return retPoint;
+ }
+
+}
diff --git a/src/main/java/com/dji/sample/wayline/plane/controller/PlaneCreateWayLineController.java b/src/main/java/com/dji/sample/wayline/plane/controller/PlaneCreateWayLineController.java
new file mode 100644
index 0000000..7dc82d8
--- /dev/null
+++ b/src/main/java/com/dji/sample/wayline/plane/controller/PlaneCreateWayLineController.java
@@ -0,0 +1,30 @@
+package com.dji.sample.wayline.plane.controller;
+
+import com.dji.sample.common.model.ResponseResult;
+import com.dji.sample.log.aspect.SysLogAnnotation;
+import com.dji.sample.wayline.plane.BoundingRectangle;
+import com.dji.sample.wayline.plane.MapLatLng;
+import com.dji.sample.wayline.plane.PlaneCourseUtils;
+import com.dji.sample.wayline.plane.param.CreateWaylineParam;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.LineString;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * @Author AIX
+ * @Date 2024/7/19 10:46
+ * @Version 1.0
+ */
+@RestController
+@RequestMapping("/wayline/plane/wayline")
+public class PlaneCreateWayLineController {
+
+ @PostMapping("/createPoints")
+ @SysLogAnnotation(operModul = "创建面状航线", operType = "创建面状航线", operDesc = "创建面状航线")
+ public ResponseResult createPoints(@RequestBody CreateWaylineParam param) {
+ return ResponseResult.success(PlaneCourseUtils.createWaylinePoints(param));
+ }
+
+}
diff --git a/src/main/java/com/dji/sample/wayline/plane/param/CreateWaylineParam.java b/src/main/java/com/dji/sample/wayline/plane/param/CreateWaylineParam.java
new file mode 100644
index 0000000..ff6d30f
--- /dev/null
+++ b/src/main/java/com/dji/sample/wayline/plane/param/CreateWaylineParam.java
@@ -0,0 +1,31 @@
+package com.dji.sample.wayline.plane.param;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+/**
+ * @Author AIX
+ * @Date 2024/7/19 11:01
+ * @Version 1.0
+ */
+@Data
+public class CreateWaylineParam {
+
+ @NotNull(message = "面不能为空")
+ private List<double[]> polygon;//面
+ @NotNull(message = "高度不能为空")
+ private double height;//高度
+ @NotNull(message = "画幅不能为空")
+ private double frame;//画幅
+ @NotNull(message = "焦距不能为空")
+ private double focal;//焦距
+ @NotNull(message = "航线重叠率不能为空")
+ private double courseRatio;//航向重叠率
+ @NotNull(message = "旁向重叠率不能为空")
+ private double sideRatio;//旁向重叠率
+ @NotNull(message = "航线角度不能为空")
+ private double bearing;//航线角度
+
+}
--
Gitblit v1.9.3