Optimize route algorithm beforehand

This commit is contained in:
longguancheng 2026-01-29 18:11:09 +08:00
parent 71023e0153
commit a41e10d128
2 changed files with 133 additions and 86 deletions

View File

@ -14,7 +14,10 @@ import com.zhangy.skyeye.jm.entity.JmJobPayload;
import com.zhangy.skyeye.jm.entity.JmJobPoint; import com.zhangy.skyeye.jm.entity.JmJobPoint;
import com.zhangy.skyeye.jm.entity.JmJobUav; import com.zhangy.skyeye.jm.entity.JmJobUav;
import com.zhangy.skyeye.jm.service.JmAirlinePlanService; import com.zhangy.skyeye.jm.service.JmAirlinePlanService;
import com.zhangy.skyeye.py.dto.*; import com.zhangy.skyeye.py.dto.PyAirlineParamDTO;
import com.zhangy.skyeye.py.dto.PyAirlinePayloadDTO;
import com.zhangy.skyeye.py.dto.PyAirlineTargetDTO;
import com.zhangy.skyeye.py.dto.PyAirlineUavDTO;
import com.zhangy.skyeye.py.service.IPyAirlineService; import com.zhangy.skyeye.py.service.IPyAirlineService;
import com.zhangy.skyeye.sar.consts.SarImageModeEnum; import com.zhangy.skyeye.sar.consts.SarImageModeEnum;
import com.zhangy.skyeye.sar.dto.SarFlightPlanDTO; import com.zhangy.skyeye.sar.dto.SarFlightPlanDTO;
@ -23,7 +26,10 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.*; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -46,7 +52,6 @@ public class JmAirlinePlanServiceImpl implements JmAirlinePlanService {
JSON.toJSONString(uavList), JSON.toJSONString(pointList)); JSON.toJSONString(uavList), JSON.toJSONString(pointList));
checkParam(jobMode, imageMode, targetType); checkParam(jobMode, imageMode, targetType);
// 非航线模式需要调算法生成航线需要从缓存取sar坐标 // 非航线模式需要调算法生成航线需要从缓存取sar坐标
Map<Long, List<JmAirline>> airlineGroup = null;
// 1聚束 // 1聚束
if (imageMode == SarImageModeEnum.JS) { if (imageMode == SarImageModeEnum.JS) {
return planJs(uavList.get(0), pointList); return planJs(uavList.get(0), pointList);
@ -176,7 +181,6 @@ public class JmAirlinePlanServiceImpl implements JmAirlinePlanService {
.map(p -> new double[]{p.getLongitude(), p.getLatitude()}) .map(p -> new double[]{p.getLongitude(), p.getLatitude()})
.toArray(double[][]::new); .toArray(double[][]::new);
log.info("提取到 {} 个目标点坐标", coords.length); log.info("提取到 {} 个目标点坐标", coords.length);
List<PyAirlineTargetDTO> pyTargets = new ArrayList<>(); List<PyAirlineTargetDTO> pyTargets = new ArrayList<>();
List<PyAirlineUavDTO> pyUavList = new ArrayList<>(); List<PyAirlineUavDTO> pyUavList = new ArrayList<>();
for (int i = 0; i < uavList.size(); i++) { for (int i = 0; i < uavList.size(); i++) {
@ -221,6 +225,7 @@ public class JmAirlinePlanServiceImpl implements JmAirlinePlanService {
/** /**
* 从无人机载荷列表中查找 SAR 类型载荷并设置 IP * 从无人机载荷列表中查找 SAR 类型载荷并设置 IP
*
* @param uav 无人机对象 * @param uav 无人机对象
* @return SAR 载荷 * @return SAR 载荷
* @throws ServiceException 如果没有找到 SAR 载荷 * @throws ServiceException 如果没有找到 SAR 载荷

View File

@ -1,6 +1,7 @@
package com.zhangy.skyeye.py.service.impl; package com.zhangy.skyeye.py.service.impl;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson2.JSON;
import com.zhangy.skyeye.common.extend.exception.ServiceException; import com.zhangy.skyeye.common.extend.exception.ServiceException;
import com.zhangy.skyeye.common.extend.util.HttpUtil; import com.zhangy.skyeye.common.extend.util.HttpUtil;
import com.zhangy.skyeye.common.extend.util.JsonUtil; import com.zhangy.skyeye.common.extend.util.JsonUtil;
@ -13,16 +14,16 @@ import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.alibaba.fastjson2.*;
import java.io.IOException; import java.io.IOException;
import java.net.ConnectException; import java.net.ConnectException;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
/** /**
* 航线规划 * 航线规划
@ -76,91 +77,132 @@ public class PyAirlineServiceImpl implements IPyAirlineService {
@Override @Override
public Map<Long, List<JmAirline>> getAirline(PyAirlineParamDTO param) { public Map<Long, List<JmAirline>> getAirline(PyAirlineParamDTO param) {
log.info("请求航线规划算法服务参数:{}", JSON.toJSONString(param)); log.info("请求航线规划算法 | param: {}", JSON.toJSONString(param));
// HttpResponse<String> post; // 1. 调用Python算法服务
// try { HttpResponse<String> httpResponse = callPythonAirlineService(param);
// post = HttpUtil.post(javaUrl, null, param); // 2. 解析响应
// } catch (IOException e) { PyAirlineResponse response = parseResponse(httpResponse);
// throw new RuntimeException(e); // 3. 检查HTTP状态码
// } int statusCode = httpResponse.statusCode();
// PyAirlineResponse pyAirlineResponse; if (statusCode >= 400) {
// pyAirlineResponse = JsonUtil.parse(post.body(), PyAirlineResponse.class); String errMsg = String.format("算法服务返回错误[%d]: %s", statusCode, response.getMessage());
// log.info("请求航线规划算法服务响应:{}",JSON.toJSONString(pyAirlineResponse)); log.warn("调用航线规划算法失败 | httpStatus={}, msg={}, param={}",
HttpResponse<String> httpResponse; statusCode, response.getMessage(), JSON.toJSONString(param));
try { throw ServiceException.noLog(errMsg);
httpResponse = HttpUtil.post(url, null, param);
} catch (ConnectException ex) {
throw ServiceException.noLog("航线规划服务未启动!");
} catch (IOException ex) {
throw new ServiceException("Post请求错误url=[" + url + "] " + ex.getMessage(), ex);
}
PyAirlineResponse response;
response = JsonUtil.parse(httpResponse.body(), PyAirlineResponse.class);
log.info("请求航线规划算法服务响应:{}", JSON.toJSONString(response));
int responseCode = httpResponse.statusCode();
if (responseCode >= 400) {
log.warn("调用航线规划算法错误[{}]{},参数:{}", responseCode, response.getMessage(), JsonUtil.toString(param));
throw ServiceException.noLog("调用航线规划算法错误[" + responseCode + "]" + response.getMessage());
} }
log.info("算法服务响应 | {}", JSON.toJSONString(response));
// 4. 获取核心数据
Map<Long, PyAirlineDTO[]> uavMap = response.getData(); Map<Long, PyAirlineDTO[]> uavMap = response.getData();
log.info("航线规划算法结果:{}", JSON.toJSONString(uavMap)); // 5. 空结果的特殊处理
if (checkEmpty(uavMap)) { if (isEmpty(uavMap)) {
if ("success".endsWith(response.getMessage())) { handleEmptyResult(statusCode, response.getMessage(), param);
}
log.info("算法返回航线数据 | uavCount={}, rawData={}",
uavMap.size(), JSON.toJSONString(uavMap));
// 6. 转换为业务对象
return convertToJmAirlineMap(uavMap);
}
/**
* 调用Python航线规划服务
*/
private HttpResponse<String> callPythonAirlineService(PyAirlineParamDTO param) {
try {
return HttpUtil.post(url, null, param);
} catch (ConnectException e) {
throw ServiceException.noLog("航线规划服务未启动");
} catch (IOException e) {
String err = String.format("Post请求失败 url=[%s] %s", url, e.getMessage());
throw new ServiceException(err, e);
}
}
/**
* 解析HTTP响应体为对象
*/
private PyAirlineResponse parseResponse(HttpResponse<String> httpResponse) {
try {
return JsonUtil.parse(httpResponse.body(), PyAirlineResponse.class);
} catch (Exception e) {
log.error("算法响应JSON解析失败 | body={}", httpResponse.body(), e);
throw new ServiceException("算法返回数据格式错误", e);
}
}
/**
* 处理算法返回空数据的情况兼容历史特殊逻辑
*/
private void handleEmptyResult(int statusCode, String message, PyAirlineParamDTO param) {
String paramJson = JSON.toJSONString(param);
if ("success".equals(message)) {
log.warn("算法返回空结果但message=success可能区域过大 | param={}", paramJson);
throw ServiceException.noLog("无法生成航线可能飞行区域超过25平方公里"); throw ServiceException.noLog("无法生成航线可能飞行区域超过25平方公里");
} }
log.warn("调用航线规划算法未返回数据[{}]{},参数:{}", responseCode, response.getMessage(), JsonUtil.toString(param)); String errMsg = String.format("算法未返回有效数据[%d]: %s", statusCode, message);
throw ServiceException.noLog("调用航线规划算法未返回数据[" + responseCode + "]" + response.getMessage()); log.warn("算法返回空数据 | httpStatus={}, msg={}, param={}", statusCode, message, paramJson);
throw ServiceException.noLog(errMsg);
} }
Map<Long, List<JmAirline>> map = new HashMap<>(); /**
* 将算法返回的DTO数组转换为业务对象Map
* 注意算法可能出现多架无人机只对应一条航线的情况其他无人机返回空数组
*/
private Map<Long, List<JmAirline>> convertToJmAirlineMap(Map<Long, PyAirlineDTO[]> uavMap) {
Map<Long, List<JmAirline>> result = new HashMap<>();
for (Map.Entry<Long, PyAirlineDTO[]> entry : uavMap.entrySet()) { for (Map.Entry<Long, PyAirlineDTO[]> entry : uavMap.entrySet()) {
long uavId = entry.getKey(); Long uavId = entry.getKey();
PyAirlineDTO[] arr = entry.getValue(); PyAirlineDTO[] airlines = entry.getValue();
// 当多架无人机对应一条航线则只有一架无人机有航线数据其余为空数组 // 跳过空数组的无人机常见于多机共享一条航线的情况
if (arr == null || arr.length == 0) { if (airlines == null || airlines.length == 0) {
continue; continue;
} }
map.put(uavId, Stream.of(arr) List<JmAirline> jmAirlines = Arrays.stream(airlines)
.map(airlineDTO -> { .map(this::convertPyAirlineToJmAirline)
// 算法返回高度为相对高度需要加起飞点高度为海拔高度 .collect(Collectors.toList());
JmAirline airline = BeanUtil.copyProperties(airlineDTO, JmAirline.class); result.put(uavId, jmAirlines);
double[] flightStart = airlineDTO.getFlightStart();
airline.setFlightStartLon(flightStart[0]);
airline.setFlightStartLat(flightStart[1]);
airline.setFlightStartHeight(flightStart[2]); // 相对起飞点高度
double[] flightEnd = airlineDTO.getFlightEnd();
airline.setFlightEndLon(flightEnd[0]);
airline.setFlightEndLat(flightEnd[1]);
airline.setFlightEndHeight(flightEnd[2]); // 相对起飞点高度
double[] groundStart = airlineDTO.getGroundStart();
airline.setGroundStartLon(groundStart[0]);
airline.setGroundStartLat(groundStart[1]);
airline.setGroundStartHeight(groundStart[2]); // 相对起飞点高度
double[] targetCentroid = airlineDTO.getTargetCentroid();
airline.setTargetCentroidLon(targetCentroid[0]);
airline.setTargetCentroidLat(targetCentroid[1]);
airline.setTargetCentroidHeight(targetCentroid[2]); // 相对起飞点高度
airline.setTargetWidth(airlineDTO.getTargetWidth());
airline.setTargetLength(airlineDTO.getTargetLength());
airline.setTargetHeading(airlineDTO.getTargetHeading());
double[] start = airlineDTO.getStart();
airline.setStartLon(start[0]);
airline.setStartLat(start[1]);
airline.setStartHeight(start[2]); // 相对起飞点高度
double[] end = airlineDTO.getEnd();
airline.setEndLon(end[0]);
airline.setEndLat(end[1]);
airline.setEndHeight(end[2]); // 相对起飞点高度
return airline;
})
.collect(Collectors.toList()));
} }
return map; return result;
}
/**
* 单条航线DTO 业务对象转换
* 核心点算法返回的是相对高度需要结合起飞点高度理解但本方法不做高度修正仅字段映射
*/
private JmAirline convertPyAirlineToJmAirline(PyAirlineDTO dto) {
JmAirline airline = BeanUtil.copyProperties(dto, JmAirline.class);
// 起飞点起点
setCoordinate(airline::setFlightStartLon, airline::setFlightStartLat, airline::setFlightStartHeight,
dto.getFlightStart());
// 结束点
setCoordinate(airline::setFlightEndLon, airline::setFlightEndLat, airline::setFlightEndHeight,
dto.getFlightEnd());
// 离地起点起飞段
setCoordinate(airline::setGroundStartLon, airline::setGroundStartLat, airline::setGroundStartHeight,
dto.getGroundStart());
// 目标区域中心
setCoordinate(airline::setTargetCentroidLon, airline::setTargetCentroidLat, airline::setTargetCentroidHeight,
dto.getTargetCentroid());
// 目标区域尺寸 & 朝向
airline.setTargetWidth(dto.getTargetWidth());
airline.setTargetLength(dto.getTargetLength());
airline.setTargetHeading(dto.getTargetHeading());
// 航线规划起点/终点通常是路径规划后的首末点
setCoordinate(airline::setStartLon, airline::setStartLat, airline::setStartHeight, dto.getStart());
setCoordinate(airline::setEndLon, airline::setEndLat, airline::setEndHeight, dto.getEnd());
return airline;
}
private void setCoordinate(Consumer<Double> lonSetter, Consumer<Double> latSetter, Consumer<Double> heightSetter,
double[] coord) {
if (coord != null && coord.length == 3) {
lonSetter.accept(coord[0]);
latSetter.accept(coord[1]);
heightSetter.accept(coord[2]); // 算法返回的是相对高度
}
}
private boolean isEmpty(Map<Long, PyAirlineDTO[]> map) {
return map == null || map.isEmpty() ||
map.values().stream().allMatch(arr -> arr == null || arr.length == 0);
} }
} }