diff --git a/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/jm/service/impl/JmAirlinePlanServiceImpl.java b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/jm/service/impl/JmAirlinePlanServiceImpl.java index 51fb19f..6b897f1 100644 --- a/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/jm/service/impl/JmAirlinePlanServiceImpl.java +++ b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/jm/service/impl/JmAirlinePlanServiceImpl.java @@ -14,7 +14,10 @@ import com.zhangy.skyeye.jm.entity.JmJobPayload; import com.zhangy.skyeye.jm.entity.JmJobPoint; import com.zhangy.skyeye.jm.entity.JmJobUav; 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.sar.consts.SarImageModeEnum; 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.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; /** @@ -46,7 +52,6 @@ public class JmAirlinePlanServiceImpl implements JmAirlinePlanService { JSON.toJSONString(uavList), JSON.toJSONString(pointList)); checkParam(jobMode, imageMode, targetType); // 非航线模式需要调算法生成航线,需要从缓存取sar坐标 - Map> airlineGroup = null; // 1)聚束 if (imageMode == SarImageModeEnum.JS) { return planJs(uavList.get(0), pointList); @@ -111,8 +116,8 @@ public class JmAirlinePlanServiceImpl implements JmAirlinePlanService { /** * 聚束模式 * - * @param uav 无人机 - * @param home 起飞点 + * @param uav 无人机 + * @param home 起飞点 * @return 航线 */ public JmAirline planJs(JmJobUav uav, JmJobPoint home, JmJobPoint target) { @@ -176,7 +181,6 @@ public class JmAirlinePlanServiceImpl implements JmAirlinePlanService { .map(p -> new double[]{p.getLongitude(), p.getLatitude()}) .toArray(double[][]::new); log.info("提取到 {} 个目标点坐标", coords.length); - List pyTargets = new ArrayList<>(); List pyUavList = new ArrayList<>(); for (int i = 0; i < uavList.size(); i++) { @@ -221,6 +225,7 @@ public class JmAirlinePlanServiceImpl implements JmAirlinePlanService { /** * 从无人机载荷列表中查找 SAR 类型载荷,并设置 IP + * * @param uav 无人机对象 * @return SAR 载荷 * @throws ServiceException 如果没有找到 SAR 载荷 diff --git a/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/py/service/impl/PyAirlineServiceImpl.java b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/py/service/impl/PyAirlineServiceImpl.java index 331eafe..2348211 100644 --- a/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/py/service/impl/PyAirlineServiceImpl.java +++ b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/py/service/impl/PyAirlineServiceImpl.java @@ -1,6 +1,7 @@ package com.zhangy.skyeye.py.service.impl; 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.util.HttpUtil; import com.zhangy.skyeye.common.extend.util.JsonUtil; @@ -13,16 +14,16 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import com.alibaba.fastjson2.*; import java.io.IOException; import java.net.ConnectException; import java.net.http.HttpResponse; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import java.util.stream.Collectors; -import java.util.stream.Stream; /** * 航线规划 @@ -76,91 +77,132 @@ public class PyAirlineServiceImpl implements IPyAirlineService { @Override public Map> getAirline(PyAirlineParamDTO param) { - log.info("请求航线规划算法服务参数:{}", JSON.toJSONString(param)); -// HttpResponse post; -// try { -// post = HttpUtil.post(javaUrl, null, param); -// } catch (IOException e) { -// throw new RuntimeException(e); -// } -// PyAirlineResponse pyAirlineResponse; -// pyAirlineResponse = JsonUtil.parse(post.body(), PyAirlineResponse.class); -// log.info("请求航线规划算法服务响应:{}",JSON.toJSONString(pyAirlineResponse)); - HttpResponse httpResponse; - try { - 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("请求航线规划算法 | param: {}", JSON.toJSONString(param)); + // 1. 调用Python算法服务 + HttpResponse httpResponse = callPythonAirlineService(param); + // 2. 解析响应 + PyAirlineResponse response = parseResponse(httpResponse); + // 3. 检查HTTP状态码 + int statusCode = httpResponse.statusCode(); + if (statusCode >= 400) { + String errMsg = String.format("算法服务返回错误[%d]: %s", statusCode, response.getMessage()); + log.warn("调用航线规划算法失败 | httpStatus={}, msg={}, param={}", + statusCode, response.getMessage(), JSON.toJSONString(param)); + throw ServiceException.noLog(errMsg); } + log.info("算法服务响应 | {}", JSON.toJSONString(response)); + // 4. 获取核心数据 Map uavMap = response.getData(); - log.info("航线规划算法结果:{}", JSON.toJSONString(uavMap)); - if (checkEmpty(uavMap)) { - if ("success".endsWith(response.getMessage())) { - throw ServiceException.noLog("无法生成航线,可能飞行区域超过25平方公里"); - } - log.warn("调用航线规划算法未返回数据[{}]:{},参数:{}", responseCode, response.getMessage(), JsonUtil.toString(param)); - throw ServiceException.noLog("调用航线规划算法未返回数据[" + responseCode + "]:" + response.getMessage()); + // 5. 空结果的特殊处理 + if (isEmpty(uavMap)) { + handleEmptyResult(statusCode, response.getMessage(), param); } + log.info("算法返回航线数据 | uavCount={}, rawData={}", + uavMap.size(), JSON.toJSONString(uavMap)); + // 6. 转换为业务对象 + return convertToJmAirlineMap(uavMap); + } - Map> map = new HashMap<>(); + /** + * 调用Python航线规划服务 + */ + private HttpResponse 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 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平方公里"); + } + String errMsg = String.format("算法未返回有效数据[%d]: %s", statusCode, message); + log.warn("算法返回空数据 | httpStatus={}, msg={}, param={}", statusCode, message, paramJson); + throw ServiceException.noLog(errMsg); + } + + /** + * 将算法返回的DTO数组转换为业务对象Map + * 注意:算法可能出现多架无人机只对应一条航线的情况(其他无人机返回空数组) + */ + private Map> convertToJmAirlineMap(Map uavMap) { + Map> result = new HashMap<>(); for (Map.Entry entry : uavMap.entrySet()) { - long uavId = entry.getKey(); - PyAirlineDTO[] arr = entry.getValue(); - // 当多架无人机对应一条航线,则只有一架无人机有航线数据,其余为空数组 - if (arr == null || arr.length == 0) { + Long uavId = entry.getKey(); + PyAirlineDTO[] airlines = entry.getValue(); + // 跳过空数组的无人机(常见于多机共享一条航线的情况) + if (airlines == null || airlines.length == 0) { continue; } - map.put(uavId, Stream.of(arr) - .map(airlineDTO -> { - // 算法返回高度为相对高度,需要加起飞点高度为海拔高度 - JmAirline airline = BeanUtil.copyProperties(airlineDTO, JmAirline.class); - 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())); + List jmAirlines = Arrays.stream(airlines) + .map(this::convertPyAirlineToJmAirline) + .collect(Collectors.toList()); + result.put(uavId, jmAirlines); } - 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 lonSetter, Consumer latSetter, Consumer 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 map) { + return map == null || map.isEmpty() || + map.values().stream().allMatch(arr -> arr == null || arr.length == 0); } }