aix
2024-07-19 6cf5d9645686477280ebb30a11ab916eb1f33b73
新建面状航线
5 files added
500 ■■■■■ changed files
src/main/java/com/dji/sample/wayline/plane/BoundingRectangle.java 69 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/plane/MapLatLng.java 28 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/plane/PlaneCourseUtils.java 342 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/plane/controller/PlaneCreateWayLineController.java 30 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/plane/param/CreateWaylineParam.java 31 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/plane/BoundingRectangle.java
New file
@@ -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);
    }
}
src/main/java/com/dji/sample/wayline/plane/MapLatLng.java
New file
@@ -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 +
                '}';
    }
}
src/main/java/com/dji/sample/wayline/plane/PlaneCourseUtils.java
New file
@@ -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;
    }
}
src/main/java/com/dji/sample/wayline/plane/controller/PlaneCreateWayLineController.java
New file
@@ -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));
    }
}
src/main/java/com/dji/sample/wayline/plane/param/CreateWaylineParam.java
New file
@@ -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;//航线角度
}