liusuyi
2025-09-06 bb508127e9454e8b39ac7d431765b6de0ac74c35
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
package com.ard.tianyu.controller.wayline.terrain;
 
import com.ard.tianyu.domain.dto.wayline.ArdActionDTO;
import com.ard.tianyu.domain.dto.wayline.ArdPositionDTO;
import com.ard.tianyu.util.GeoTools;
import com.ard.tianyu.util.Vertex;
import com.ard.tianyu.domain.dto.wayline.ArdUavPositionDTO;
import com.ard.tianyu.domain.dto.wayline.ArdWaylineDTO;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
 
/**
 * 仿地飞行规划器
 *
 * @author lsy
 * @date 2025/6/18
 */
@Component
public class TerrainFollowingPlanner {
    @Resource
    private GeoUtils geoUtils;
 
    private final double intervalMeter = 100.0; // 插值距离
    private final double smoothThreshold = 3.0; // 平滑阈值
    private static final ObjectMapper mapper = new ObjectMapper();
 
    // 算法主函数
    public List<ArdUavPositionDTO> interpolateWithTerrain(ArdWaylineDTO ardWaylineDTO) throws Exception {
        // 0. 验证源航线数据
        validateWaylineData(ardWaylineDTO);
 
        // 1. 先进行纯几何插值(不考虑高度)
        List<ArdUavPositionDTO> interpolatedPoints = pureInterpolation(ardWaylineDTO.getUavPositions());
 
        // 2. 然后统一处理地形高度
        return adjustAltitudeForTerrain(interpolatedPoints, ardWaylineDTO.getGlobalFlyHeight());
    }
 
    // 第一阶段:纯几何插值
    private List<ArdUavPositionDTO> pureInterpolation(List<ArdUavPositionDTO> originalPoints) {
        List<ArdUavPositionDTO> result = new ArrayList<>();
 
        for (int i = 0; i < originalPoints.size() - 1; i++) {
            ArdUavPositionDTO start = originalPoints.get(i);
            ArdUavPositionDTO end = originalPoints.get(i + 1);
 
            // 添加原始起点
            result.add(deepCopy(start, ArdUavPositionDTO.class));
 
            // 插值中间点
            result.addAll(interpolateBetween(start, end));
        }
 
        // 添加最后一个点
        result.add(deepCopy(originalPoints.get(originalPoints.size() - 1), ArdUavPositionDTO.class));
 
        return result;
    }
 
    // 第二阶段:地形高度调整
    private List<ArdUavPositionDTO> adjustAltitudeForTerrain(List<ArdUavPositionDTO> points, double flyHeight) throws IOException {
 
        double homeTerrainHeight = 0; // 记录起飞点地形高度作为基准海拔
        double lastFlightAltitude = flyHeight; // 记录上一个飞行高度
 
        for (int i = 0; i < points.size(); i++) {
            ArdUavPositionDTO point = points.get(i);
            double currentTerrainHeight = geoUtils.getHeight(
                    point.getUavPosition().getLongitude(),
                    point.getUavPosition().getLatitude()
            );
            if (i == 0) {
                homeTerrainHeight = currentTerrainHeight;
            }
            // 计算相对于起飞点的地形高度差
            double terrainHeightDiff = currentTerrainHeight - homeTerrainHeight;
 
            // 理论目标高度 = 设定高度 + 地形高度变化量
            double targetAltitude = flyHeight + terrainHeightDiff;
 
            // 平滑处理:如果高度变化不大,沿用上一个飞行高度
            if (i > 0 && Math.abs(targetAltitude - lastFlightAltitude) < smoothThreshold) {
                targetAltitude = lastFlightAltitude;
            }
 
            // 设置高度
            point.getUavPosition().setAltitude(targetAltitude);
            updateActionAltitude(point, currentTerrainHeight);
 
            lastFlightAltitude = targetAltitude; // 更新最后飞行高度
        }
 
        renumberWaypoints(points);
        return points;
    }
 
    // 辅助方法:两点间插值
    private List<ArdUavPositionDTO> interpolateBetween(ArdUavPositionDTO start, ArdUavPositionDTO end) {
        List<ArdUavPositionDTO> interpolated = new ArrayList<>();
        // 计算两点间的距离
        Vertex startV=new Vertex( start.getUavPosition().getLatitude(), start.getUavPosition().getLongitude());
        Vertex endV=new Vertex( end.getUavPosition().getLatitude(), end.getUavPosition().getLongitude());
        double distance = GeoTools.DistanceSphere(startV,endV);
        // 如果距离小于插值间隔,则直接返回
        if (distance <= intervalMeter) return interpolated;
        // 计算插值步数
        int steps = (int) (distance / intervalMeter);
        for (int i = 1; i < steps; i++) {
            double ratio = i * intervalMeter / distance;
            double lat = interpolate(
                    start.getUavPosition().getLatitude(),
                    end.getUavPosition().getLatitude(),
                    ratio
            );
            double lon = interpolate(
                    start.getUavPosition().getLongitude(),
                    end.getUavPosition().getLongitude(),
                    ratio
            );
 
            ArdUavPositionDTO newPoint = deepCopy(start, ArdUavPositionDTO.class);
            newPoint.setType(1);
            newPoint.getUavPosition().setLatitude(lat);
            newPoint.getUavPosition().setLongitude(lon);
            interpolated.add(newPoint);
        }
 
        return interpolated;
    }
 
    /**
     * 更新航点动作中的地面高度
     *
     * @param point         航点对象
     * @param terrainHeight 当前地形高度
     */
    private void updateActionAltitude(ArdUavPositionDTO point, double terrainHeight) {
        if (point.getActionGroup() == null || point.getActionGroup().isEmpty()) {
            return;
        }
 
        for (ArdActionDTO action : point.getActionGroup()) {
            // 如果已有地面位置,则更新高度
            if (action.getGroundPosition() != null) {
                action.getGroundPosition().setLongitude(point.getUavPosition().getLongitude());
                action.getGroundPosition().setLatitude(point.getUavPosition().getLatitude());
                action.getGroundPosition().setAltitude(terrainHeight);
            }
            // 如果没有地面位置,则创建新的地面位置
            else {
                action.setGroundPosition(ArdPositionDTO.builder()
                        .longitude(point.getUavPosition().getLongitude())
                        .latitude(point.getUavPosition().getLatitude())
                        .altitude(terrainHeight)
                        .build());
            }
        }
    }
 
    /**
     * 重新编号航点和动作
     *
     * @param points 航点列表
     */
    private void renumberWaypoints(List<ArdUavPositionDTO> points) {
        // 航点重新编号
        for (int i = 0; i < points.size(); i++) {
            ArdUavPositionDTO point = points.get(i);
            point.setIndex(i + 1); // 通常从1开始编号
 
            // 动作重新编号
            if (point.getActionGroup() != null && !point.getActionGroup().isEmpty()) {
                int actionIndex = 1;
                for (ArdActionDTO action : point.getActionGroup()) {
                    action.setActionIndex(actionIndex++);
                }
            }
        }
    }
 
 
    /**
     * 校验航线数据有效性
     *
     * @param ardWaylineDTO 航线数据
     * @throws IllegalArgumentException 如果数据无效
     */
    private void validateWaylineData(ArdWaylineDTO ardWaylineDTO) {
        if (ardWaylineDTO == null) {
            throw new IllegalArgumentException("航线数据不能为null");
        }
        if (ardWaylineDTO.getUavPositions() == null || ardWaylineDTO.getUavPositions().size() < 2) {
            throw new IllegalArgumentException("航点数量必须大于等于2");
        }
        if (ardWaylineDTO.getGlobalFlyHeight() <= 0) {
            throw new IllegalArgumentException("全局飞行高度必须大于0");
        }
    }
 
    // 通用深拷贝
    public static <T> T deepCopy(T original, Class<T> clazz) {
        if (original == null) {
            return null;
        }
        try {
            byte[] bytes = mapper.writeValueAsBytes(original);
            return mapper.readValue(bytes, clazz);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
 
    // 线性插值
    public double interpolate(double start, double end, double ratio) {
        return start + (end - start) * ratio;
    }
}