package com.ard.route.service.impl; import java.io.IOException; import java.util.ArrayList; import java.util.List; import com.ard.common.exception.ControlException; import com.ard.common.utils.DateUtils; import com.ard.common.utils.file.FileUtils; import com.ard.common.utils.uuid.IdUtils; import com.ard.data.noflyzone.domain.ArdNoFlyZone; import com.ard.data.noflyzone.domain.Vertex; import com.ard.data.noflyzone.service.IArdNoFlyZoneService; import com.ard.data.noflyzone.utils.NoFlyZoneUtils; import com.ard.framework.web.service.TokenService; import com.ard.oss.model.MinioUploadResult; import com.ard.oss.service.impl.OssServiceContext; import com.ard.route.domain.djuav.ActionGroupReq; import com.ard.route.domain.djuav.PointActionReq; import com.ard.route.domain.djuav.RoutePointReq; import com.ard.route.domain.djuav.UavRouteReq; import com.ard.route.domain.dto.ArdNoflyDto; import com.ard.route.domain.route.ArdRouteInfo; import com.ard.route.domain.route.ArdRouteMission; import com.ard.route.domain.vo.KmzResultVo; import com.ard.route.mapper.ArdRouteInfoMapper; import com.ard.route.mapper.ArdRouteMissionMapper; import com.ard.route.service.IArdRouteInfoService; import com.ard.route.service.UavRouteService; import com.ard.work.domain.cache.UavTelemetryDataCache; import com.ard.work.domain.cache.UavWaylineCache; import com.ard.work.domain.dto.state.PointData; import com.ard.work.domain.dto.telemetry.TelemetryData; import com.ard.work.domain.dto.wayline.*; import com.ard.work.service.ITyService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import static com.ard.common.utils.SecurityUtils.getUsername; import static com.ard.route.enums.kml.DroneEnumValueEnums.M350_RTK; import static com.ard.route.util.InitializationUtils.divided_23; import static com.ard.route.util.ToolUtils.*; import static com.ard.route.util.UavRouteReqUtils.fileConversionEntity; /** * 航线(kmz)Service业务层处理 * * @author xia * @date 2025-10-23 */ @Service public class ArdRouteInfoServiceImpl implements IArdRouteInfoService { private static final Logger log = LoggerFactory.getLogger(TokenService.class); @Autowired private ArdRouteInfoMapper ardRouteInfoMapper; @Autowired private OssServiceContext ossService; @Autowired private UavRouteService routeService; @Autowired private ArdRouteMissionMapper ardRouteMissionMapper; @Resource private IArdNoFlyZoneService ardNoFlyZoneService; @Resource private ITyService tyService; /** * 查询航线(kmz) * * @param id 航线(kmz)主键 * @return 航线(kmz) */ @Override public ArdRouteInfo selectArdRouteInfoById(String id) { ArdRouteInfo ardRouteInfo = ardRouteInfoMapper.selectArdRouteInfoById(id); try { if (ardRouteInfo != null) { if (ardRouteInfo.getRouteData() != null){ ardRouteInfo.setUavRouteReq((UavRouteReq) deserialize(ardRouteInfo.getRouteData())); } } } catch (IOException | ClassNotFoundException e) { throw new RuntimeException(e); } // 如果参考起飞点不为空,将每个航点高度进行加工。 if (ardRouteInfo.getUavRouteReq().getTakeOffRefPoint() != null){ double altitudeOffset = getDoublePoint(ardRouteInfo.getUavRouteReq().getTakeOffRefPoint(), "alt"); for (RoutePointReq routePointReq : ardRouteInfo.getUavRouteReq().getRoutePointList()) { // 调整每个航点高度 adjustRoutePointHeight(routePointReq, altitudeOffset); } }else { // 如果没有设置参考起飞点,将第一个航点设置为参考起飞点。 ardRouteInfo.getUavRouteReq().setTakeOffRefPoint( ardRouteInfo.getUavRouteReq().getRoutePointList().get(0).getLatitude().toString() +"," + ardRouteInfo.getUavRouteReq().getRoutePointList().get(0).getLongitude().toString()+","+ ardRouteInfo.getUavRouteReq().getRoutePointList().get(0).getHeight().toString()); } return ardRouteInfo; } private void adjustRoutePointHeight(RoutePointReq routePointReq, double altitudeOffset) { routePointReq.setHeight(routePointReq.getHeight() + altitudeOffset); } /** * 查询航线(kmz)列表 * * @param ardRouteInfo 航线(kmz) * @return 航线(kmz) */ @Override public List selectArdRouteInfoList(ArdRouteInfo ardRouteInfo) { if ("1".equals(ardRouteInfo.getOrderByTime())) { ardRouteInfo.setOrderByTime("DESC"); }else if ("0".equals(ardRouteInfo.getOrderByTime())) { ardRouteInfo.setOrderByTime("ASC"); } return ardRouteInfoMapper.selectArdRouteInfoList(ardRouteInfo); } /** * 新增航线(kmz) * * @param ardRouteInfo 航线(kmz) * @return 结果 */ @Override public String insertArdRouteInfo(ArdRouteInfo ardRouteInfo) { processRouteMission(ardRouteInfo); ardRouteInfo.setId(IdUtils.simpleUUID()); ardRouteInfo.setCreateTime(DateUtils.getNowDate()); ardRouteInfo.setUpdateTime(DateUtils.getNowDate()); // 修改时间,用于排序 if (ardRouteInfo.getGroupId() == null){ ardRouteInfo.setGroupId("0"); } ardRouteInfo.getUavRouteReq().setDroneType(subStringDroneType(ardRouteInfo.getAircraft())); KmzResultVo kmzResult = routeService.buildKmz(ardRouteInfo.getUavRouteReq(),ardRouteInfo.getName()); zoomProcessing(ardRouteInfo); ardRouteInfo.setPath(kmzResult.getPath()); ardRouteInfo.setReadOnly("0"); ardRouteInfoMapper.insertArdRouteInfo(dataProcessing(ardRouteInfo)); return kmzResult.getPath(); } // 航线任务数据加工校验 private void processRouteMission(ArdRouteInfo ardRouteInfo){ if ("3".equals(ardRouteInfo.getType())) { ArdRouteMission ardRouteMission = ardRouteMissionMapper.selectArdRouteMissionById(ardRouteInfo.getMissionId()); if ("0".equals(ardRouteMission.getRouteId())) { ardRouteMission.setRouteId(ardRouteInfo.getId()); ardRouteMissionMapper.updateArdRouteMission(ardRouteMission); }else { throw new RuntimeException("航线任务下已绑定航线,不可创建新航线信息!!!"); } } } /** * 修改航线(kmz) * * @param ardRouteInfo 航线(kmz) * @return 结果 */ @Override public int updateArdRouteInfo(ArdRouteInfo ardRouteInfo) { ardRouteInfo.setUpdateTime(DateUtils.getNowDate()); // 判断生成kmz文件实体类是否为空,为空就修改航信信息,不修改行点信息。 if (ardRouteInfo.getUavRouteReq() != null) { KmzResultVo kmzResult; // 获取源文件名字 ArdRouteInfo route = ardRouteInfoMapper.selectArdRouteInfoById(ardRouteInfo.getId()); // 先删除航线 delOssService(route); // 再生成航线 ardRouteInfo.getUavRouteReq().setDroneType(subStringDroneType(ardRouteInfo.getAircraft())); zoomProcessing(ardRouteInfo); kmzResult = routeService.buildKmz(ardRouteInfo.getUavRouteReq(), ardRouteInfo.getName()); if ("更新航线kmz文件失败".equals(kmzResult.getData())) { return 0; } ardRouteInfo.setPath(kmzResult.getPath()); } return ardRouteInfoMapper.updateArdRouteInfo(dataProcessing(ardRouteInfo)); } // 变倍加工方法 private void zoomProcessing(ArdRouteInfo ardRouteInfo) { if (M350_RTK.getValue().equals(ardRouteInfo.getUavRouteReq().getDroneType())) { // 变焦数值加工 for (RoutePointReq routePointReq : ardRouteInfo.getUavRouteReq().getRoutePointList()) { for (ActionGroupReq actionGroupReq : routePointReq.getActionGroupList()) { for (PointActionReq action : actionGroupReq.getActions()) { if (action.getZoom() != null){ action.setZoom(divided_23(action.getZoom())); } } } } } } // 新增信息加工 private ArdRouteInfo dataProcessing(ArdRouteInfo ardRouteInfo) { // 航线数据 byte[] routeData = null; try { if (ardRouteInfo.getUavRouteReq() != null){ routeData = serialize(ardRouteInfo.getUavRouteReq()); } } catch (IOException e) { throw new RuntimeException(e); } ardRouteInfo.setRouteData(routeData); ardRouteInfo.setPointCount(getPointCount(ardRouteInfo)); return ardRouteInfo; } // 获取行点总数 private Integer getPointCount(ArdRouteInfo ardRouteInfo) { if (ardRouteInfo.getUavRouteReq() != null){ if (ardRouteInfo.getUavRouteReq().getRoutePointList() != null) { return ardRouteInfo.getUavRouteReq().getRoutePointList().size(); } } return 0; } /** * 批量删除航线(kmz) * * @param ids 需要删除的航线(kmz)主键 * @return 结果 */ @Override public int deleteArdRouteInfoByIds(String[] ids) { // 删除航线(kmz)文件 for (String id : ids) { ArdRouteInfo information = ardRouteInfoMapper.selectArdRouteInfoById(id); delOssService(information); } return ardRouteInfoMapper.deleteArdRouteInfoByIds(ids); } // 删除minio文件 private void delOssService(ArdRouteInfo information){ if (information.getPath() != null){ ossService.deleteObject("kmz", subStringMinioUrlName(information.getPath())); } } /** * 删除航线(kmz)信息 * * @param id 航线(kmz)主键 * @return 结果 */ @Override public int deleteArdRouteInfoById(String id) { // 删除航线(kmz)文件 ArdRouteInfo information = ardRouteInfoMapper.selectArdRouteInfoById(id); delOssService(information); return ardRouteInfoMapper.deleteArdRouteInfoById(id); } /** * 导入航线(kmz)文件 * * @param file 文件 * @return 结果 */ @Override public MinioUploadResult uploadArdRouteInfo(MultipartFile file) { try { // 增加航线实体类信息 UavRouteReq uavRouteReq = fileConversionEntity(file); ArdRouteInfo ardRouteInfo = new ArdRouteInfo(); if (uavRouteReq != null){ ardRouteInfo.setUavRouteReq(uavRouteReq); ardRouteInfo.setType(templateTypeData(uavRouteReq)); ardRouteInfo.setAircraft(droneTypeData(uavRouteReq)); } String url = ossService.upload("kmz",file); ardRouteInfo.setName(file.getOriginalFilename()); ardRouteInfo.setPath(url); ardRouteInfo.setGroupId("0"); // 默认分组 ardRouteInfo.setReadOnly("1"); // 默认只读 insertRouteInfomation(ardRouteInfo); return MinioUploadResult.builder() .url(url) .newFileName(FileUtils.getName(url)) .originalFilename(file.getOriginalFilename()) .build(); }catch (Exception e){ log.error("上传航线文件失败", e); return null; } } /** * 判断一组数组,是否包含禁飞区 */ private void arrayWithinNoFlyZone(List routePointList) { if (routePointList == null || routePointList.isEmpty()) { return; } // 检查第一个航点 ArdNoflyDto firstDto = new ArdNoflyDto(); firstDto.setTarget(new Vertex( routePointList.get(0).getLongitude(), routePointList.get(0).getLatitude())); verifyNoFlyZone(firstDto); // 检查后续航点及与前一个航点的线段 for (int i = 1; i < routePointList.size(); i++) { ArdNoflyDto dto = new ArdNoflyDto(); dto.setCurrent(new Vertex( routePointList.get(i - 1).getLongitude(), routePointList.get(i - 1).getLatitude())); dto.setTarget(new Vertex( routePointList.get(i).getLongitude(), routePointList.get(i).getLatitude())); verifyNoFlyZone(dto); } } /** * 根据名称查询航线信息 * * @param name 文件名称 * @return 结果 */ @Override public ArdRouteInfo selectArdRouteInfoByName(String name) { return ardRouteInfoMapper.selectArdRouteInfoByName(name); } /** * 修改航线分组ID * * @param id ID * @param groupId 分组ID * @return 结果 */ @Override public int updateRouteInfomationGroupId(String id, String groupId) { return ardRouteInfoMapper.updateRouteInforGroupId(id, groupId); } /** * 判断行点是否在禁飞区 * @return 结果 */ @Override public Boolean selectRouteInfomationNoFly(ArdNoflyDto ardNoflyDto) { return verifyNoFlyZone(ardNoflyDto); } /** * 上传航线到边缘服务器 * ardNoflyDto * @return 结果 */ @Override public Boolean uploadRouteToSixHe(String id,String boxSn) { // 1.获取航线信息 // 1.1通过航线数据,转换。 ArdRouteInfo route = ardRouteInfoMapper.selectArdRouteInfoById(id); try { route.setUavRouteReq((UavRouteReq) deserialize(route.getRouteData())); } catch (IOException | ClassNotFoundException e) { throw new RuntimeException(e); } // 获取第一个点 double firstPointLng = route.getUavRouteReq().getRoutePointList().get(0).getLongitude(); double firstPointLat = route.getUavRouteReq().getRoutePointList().get(0).getLatitude(); // 获取最后一个点 double lastPointLng = route.getUavRouteReq().getRoutePointList().get(route.getUavRouteReq().getRoutePointList().size()-1).getLongitude(); double lastPointLat = route.getUavRouteReq().getRoutePointList().get(route.getUavRouteReq().getRoutePointList().size()-1).getLatitude(); // 无人机遥测数据,用于校验真实起飞点和第一个点是否在禁飞区 TelemetryData telemetryData = UavTelemetryDataCache.get(boxSn); double telemetryDataLng = 0; double telemetryDataLat = 0; if (telemetryData != null){ telemetryDataLng = telemetryData.getLng(); telemetryDataLat = telemetryData.getLat(); } ArdNoflyDto dto1 = new ArdNoflyDto(); dto1.setCurrent(new Vertex(telemetryDataLng,telemetryDataLat)); dto1.setTarget(new Vertex(firstPointLng,firstPointLat)); verifyNoFlyZone(dto1); ArdNoflyDto dto2 = new ArdNoflyDto(); dto2.setCurrent(new Vertex(lastPointLng,lastPointLat)); dto2.setTarget(new Vertex(telemetryDataLng,telemetryDataLat)); verifyNoFlyZone(dto2); // 判断禁飞区 arrayWithinNoFlyZone(route.getUavRouteReq().getRoutePointList()); // 1.2通过minio获取航线信息 // 2.将航线信息转换成对应实体类 WayLineUploadDTO wayLineUploadDTO = buildWayLineUploadDTO(route); wayLineUploadDTO.setBoxSn(boxSn); // 3.缓存航线信息 WaylineInfo WaylineInfo = new WaylineInfo(); WaylineInfo.setBoxSn(boxSn); WaylineInfo.setWaylineId(route.getId()); UavWaylineCache.put(WaylineInfo); // 4.调用接口 tyService.wayLineUpload(wayLineUploadDTO); return true; } /** * 开始航线 * * @param boxSn 云盒SN编号 * @return 结果 */ @Override public Boolean routeBegins(String id,String boxSn) { ArdRouteInfo route = ardRouteInfoMapper.selectArdRouteInfoById(id); // 判断是否循环航线 if (null != route && "0".equals(route.getIsLoop())){ deleteArdRouteInfoById(id); updateMissionStatus(route); } WayLineBeginDTO wayLineBeginDTO = new WayLineBeginDTO(boxSn); tyService.wayLineBegin(wayLineBeginDTO); return true; } // 执行航线的时候修改航线任务表执行状态 private void updateMissionStatus(ArdRouteInfo route){ if ("3".equals(route.getType())) { ArdRouteMission ardRouteMission = ardRouteMissionMapper.selectArdRouteMissionById(route.getMissionId()); if ("0".equals(ardRouteMission.getExecuteStatus())) { ardRouteMission.setExecuteStatus("1"); ardRouteMission.setExecuteBy(getUsername()); ardRouteMission.setExecuteTime(DateUtils.getNowDate()); ardRouteMission.setUpdateTime(DateUtils.getNowDate()); ardRouteMissionMapper.updateArdRouteMission(ardRouteMission); } } } /** * 暂停航线 * * @param boxSn 云盒SN编号 * @return 结果 */ @Override public Boolean routeSuspension(String boxSn) { WayLinePauseDTO wayLinePauseDTO = new WayLinePauseDTO(boxSn); tyService.wayLinePause(wayLinePauseDTO); return true; } /** * 恢复航线 * * @param boxSn 云盒SN编号 * @return 结果 */ @Override public Boolean routeResumption(String boxSn) { WayLineResumeDTO wayLineResumeDTO = new WayLineResumeDTO(boxSn); tyService.wayLineResume(wayLineResumeDTO); return true; } /** * 结束航线 * * @param boxSn 云盒SN编号 * @return 结果 */ @Override public Boolean routeEnd(String boxSn) { WayLineEndDTO wayLineEndDTO = new WayLineEndDTO(boxSn); tyService.wayLineEnd(wayLineEndDTO); return true; } /** * ArdRouteInfo实体转换,WayLineUploadDTO */ private WayLineUploadDTO buildWayLineUploadDTO(ArdRouteInfo ardRouteInfo){ WayLineUploadDTO wayLineUploadDTO = new WayLineUploadDTO(); String finishAction = ardRouteInfo.getUavRouteReq().getFinishAction(); //1 Double autoFlightSpeed = ardRouteInfo.getUavRouteReq().getAutoFlightSpeed(); //15 Double globalTransitionalSpeed = ardRouteInfo.getUavRouteReq().getGlobalTransitionalSpeed(); //8 String exitOnRcLostAction = ardRouteInfo.getUavRouteReq().getExitOnRcLostAction(); //0 PlanLineData planLineData = new PlanLineData(); if (finishAction != null){ planLineData.setLoseAction(0); } if (autoFlightSpeed != null){ planLineData.setAutoSpeed(8); } planLineData.setMaxSpeed(15); // if (globalTransitionalSpeed != null){ // planLineData.setMaxSpeed(15); // } if (exitOnRcLostAction != null){ planLineData.setFinishedAction(1); } planLineData.setHomeHeight(50); planLineData.setInterests(new ArrayList<>()); planLineData.setIsSaveEnergyMode(0); planLineData.setPoints(buildPointDataList(ardRouteInfo.getUavRouteReq())); wayLineUploadDTO.setWayLineData(planLineData); return wayLineUploadDTO; } private List buildPointDataList(UavRouteReq uavRouteReq){ List pointDataList = new ArrayList<>(); for (RoutePointReq routePointReq : uavRouteReq.getRoutePointList()) { PointData pointData = new PointData(); Integer routePointIndex = routePointReq.getRoutePointIndex(); if (routePointIndex != null){ pointData.setGroupIndex(routePointIndex); } Double height = routePointReq.getHeight(); if (height != null){ pointData.setHeight(height.floatValue()); } Double longitude = routePointReq.getLongitude(); if (longitude != null){ pointData.setLng(longitude.floatValue()); } Double latitude = routePointReq.getLatitude(); if (latitude != null){ pointData.setLat(latitude.floatValue()); } Double speed = routePointReq.getSpeed(); if (speed != null){ pointData.setSpeed(speed.floatValue()); } pointData.setDampingDistance(0); pointData.setFlightPathMode(1); pointData.setHeading(0); pointData.setHeadingMode(0); pointData.setInterestIndex(-1); pointData.setSafeGohomeFlag(0); pointData.setActions(buildPointActionList(routePointReq.getActionGroupList())); pointDataList.add(pointData); } return pointDataList; } private List buildPointActionList(List actionGroupList){ List pointActionList = new ArrayList<>(); for (ActionGroupReq actionGroupReq : actionGroupList) { for (PointActionReq action : actionGroupReq.getActions()) { PointAction pointAction = typeProcessing(action); pointActionList.add(pointAction); } } return pointActionList; } private PointAction typeProcessing(PointActionReq pointActionReq){ PointAction pointAction = new PointAction(); if (pointActionReq.getZoom() != null){ pointAction.setType(1); pointAction.setParam(pointActionReq.getZoom().floatValue()); pointAction.setSpeakInfo(""); pointAction.setWaitTime(2); } if (pointActionReq.getTakePhotoType() != null){ pointAction.setType(2); pointAction.setParam(1); pointAction.setSpeakInfo(""); pointAction.setWaitTime(2); } if (pointActionReq.getStartRecord() != null){ pointAction.setType(3); pointAction.setParam(1); pointAction.setSpeakInfo(""); pointAction.setWaitTime(2); } if (pointActionReq.getStopRecord() != null){ pointAction.setType(4); pointAction.setParam(1); pointAction.setSpeakInfo(""); pointAction.setWaitTime(2); } if (pointActionReq.getAircraftHeading() != null){ pointAction.setType(5); pointAction.setParam(pointActionReq.getAircraftHeading().floatValue()); pointAction.setSpeakInfo(""); pointAction.setWaitTime(2); } if (pointActionReq.getGimbalPitchRotateAngle() != null){ pointAction.setType(6); pointAction.setParam(pointActionReq.getGimbalPitchRotateAngle().floatValue()); pointAction.setSpeakInfo(""); pointAction.setWaitTime(2); } if (pointActionReq.getGimbalYawRotateAngle() != null){ pointAction.setType(7); pointAction.setParam(pointActionReq.getGimbalYawRotateAngle().floatValue()); pointAction.setSpeakInfo(""); pointAction.setWaitTime(2); } if (pointActionReq.getHoverTime() != null){ pointAction.setType(9); pointAction.setParam(pointActionReq.getHoverTime().floatValue()); pointAction.setSpeakInfo(""); pointAction.setWaitTime(2); } return pointAction; } /** * 数据新增航线信息 */ private void insertRouteInfomation(ArdRouteInfo ardRouteInfo){ ardRouteInfo.setId(IdUtils.simpleUUID()); ardRouteInfo.setCreateTime(DateUtils.getNowDate()); ardRouteInfo.setUpdateTime(DateUtils.getNowDate()); ardRouteInfoMapper.insertArdRouteInfo(dataProcessing(ardRouteInfo)); } /** * 判断行点是否在禁飞区 * 原点 current 第一个节点 * 目标点 target 第二个节点 */ private Boolean verifyNoFlyZone(ArdNoflyDto ardNoflyDto){ // ③ 查询禁飞区 List zones = ardNoFlyZoneService.selectArdNoFlyZoneList(new ArdNoFlyZone()); if (zones.isEmpty()) { return true; } for (ArdNoFlyZone zone : zones) { List polygon = zone.getVertices(); if (polygon == null || polygon.size() < 3) continue; // 检查目标点是否在禁飞区 if (NoFlyZoneUtils.isPointInPolygon(ardNoflyDto.getTarget(), polygon)) { throw new ControlException("目标点在禁飞区【" + zone.getName() + "】内,无法执行操作!"); } // 检查路径是否穿越禁飞区 if (ardNoflyDto.getCurrent() != null && NoFlyZoneUtils.isLineIntersectPolygon(ardNoflyDto.getCurrent(), ardNoflyDto.getTarget(), polygon)) { throw new ControlException("路径穿越禁飞区【" + zone.getName() + "】,无法执行操作!"); } } return true; } }