package org.sxkj.odm.service.impl; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; import org.apache.logging.log4j.util.Strings; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Point; import org.springblade.core.log.exception.ServiceException; import org.springblade.core.oss.model.BladeFile; import org.springblade.core.redis.cache.BladeRedis; import org.springblade.core.secure.utils.AuthUtil; import org.springblade.core.tool.api.R; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.FileSystemResource; import org.springframework.mock.web.MockMultipartFile; import org.springframework.stereotype.Service; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; import org.sxkj.common.constant.CacheConstant; import org.sxkj.common.constant.CommonConstant; import org.sxkj.common.enums.AttachResultTypeEnum; import org.sxkj.common.enums.WaylineTypeEnum; import org.sxkj.odm.enums.OdmTypeEnum; import org.sxkj.odm.enums.WebOdmProjectIdEnum; import org.sxkj.common.func.Streams; import org.sxkj.common.model.ResponseResult; import org.sxkj.common.model.TimeRange; import org.sxkj.common.redis.RedisOpsUtils; import org.sxkj.common.utils.*; import org.sxkj.odm.build.DevicePermissionBuilder; import org.sxkj.odm.config.MinioDataConstant; import org.sxkj.odm.config.OdmProperties; import org.sxkj.odm.config.PyToolsServiceProperties; import org.sxkj.odm.constant.OdmConstant; import org.sxkj.odm.constant.RedisKeyConstant; import org.sxkj.odm.constant.TifConstant; import org.sxkj.odm.entity.*; import org.sxkj.odm.enums.WebOdmStatusEnum; import org.sxkj.odm.mapper.OdmTaskInfoMapper; import org.sxkj.odm.service.*; import org.sxkj.odm.utils.GeoJsonToWktConverterUtils; import org.sxkj.odm.utils.ParseTifUtils; import org.sxkj.odm.utils.TreeUtils; import org.sxkj.odm.utils.VideoConverterUtils; import org.sxkj.odm.vo.*; import org.sxkj.resource.vo.AttachTypeStatisticsVO; import org.sxkj.tools.feign.IObjConvertClient; import org.sxkj.tools.feign.ITifConvertClient; import org.sxkj.resource.entity.Attach; import org.sxkj.resource.feign.IAttachClient; import org.sxkj.system.cache.SysCache; import org.sxkj.system.feign.ISysClient; import org.sxkj.system.vo.TreeVo; import org.sxkj.tools.model.TifConvertResult; import javax.net.ssl.HttpsURLConnection; import java.io.*; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.text.MessageFormat; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static org.sxkj.odm.utils.FileUtils.deletedFile; /** * odm 任务信息服务实现层 * * @author zhongrj * @date 2024-09-11 */ @Slf4j @Service public class OdmTaskInfoServiceImpl extends ServiceImpl implements IOdmTaskInfoService { @Autowired private WebOdmSendRequest sendRequest; @Autowired private PyTifSendRequest pyTifSendRequest; @Autowired private OdmTaskThreadService odmTaskThreadService; @Autowired private MinioDataConstant minioDataConstant; @Autowired private IAttachClient attachClient; @Autowired private AsyncService asyncService; @Autowired private ISysClient sysClient; @Autowired private IObjConvertClient objConvertClient; @Autowired private ITifConvertClient tifConvertClient; @Value("${odm.down.temp.basePath}") private String odmDownTempBasePath; @Value("${odm.down.temp.preFix}") private String odmDownPreFixPath; @Value("${odm.down.temp.zipBasePath}") private String odmDownTempZipBasePath; @Autowired private BladeRedis bladeRedis; @Autowired private OdmProperties odmProperties; @Autowired private PyToolsServiceProperties pyToolsServiceProperties; @Autowired private DevicePermissionBuilder permissionBuilder; /** * 自定义分页列表查询 * * @param page * @param odmTaskInfo * @return */ @Override public IPage selectOdmTaskInfoPage(IPage page, OdmTaskInfoVO odmTaskInfo) { if (!AuthUtil.isAdministrator() && !Strings.isBlank(AuthUtil.getDeptId())) { // odmTaskInfo.setDeptId(Long.parseLong(AuthUtil.getDeptId())); List deptIdList = SysCache.getDeptChildIds(Long.valueOf(AuthUtil.getDeptId())); odmTaskInfo.setDeptIdList(deptIdList); odmTaskInfo.setPermissionCondition(permissionBuilder.buildDataPermissionCondition(AuthUtil.getUserId(), "wj")); } // 查询并返回 return page.setRecords(baseMapper.selectOdmTaskInfoPage(page, odmTaskInfo)); } /** * 创建 odm 任务--定时创建 * * @return */ @Override public Boolean createWebOdmTask() { boolean flag = false; int num = 0; // 先查询在执行的任务数,超过1个就先不创建了 Integer processTaskNum = baseMapper.getProcessTaskNum(); if (processTaskNum >= OdmConstant.MAX_PROCESS_TASK_NUM) { log.info("在运行的任务超过{}个,延时创建任务",OdmConstant.MAX_PROCESS_TASK_NUM); return false; } // 查询所有未在 webodm 创建任务的任务信息 List list = baseMapper.getNotByWebOdmCreateTaskList(); if (list.size() == 0) { return false; } // 遍历 for (OdmTaskInfoVO odmTaskInfo : list) { flag = createWebOdmTask(odmTaskInfo.getWaylineJobId(), odmTaskInfo.getGsd()); if (flag) { num++; } } if (num == list.size()) { return true; } return false; } /** * odm 任务创建 * * @param jobId 航线任务id * @param ortRes 正射影像的分辨率,单位为 cm/pixel * @return */ @Override public Boolean createWebOdmTask(String jobId, Double ortRes) { // 查询当前航线任务的类型(过滤掉不参与图片合并的类型),当前只要 2:航测 4:正射举证 5:倾斜摄影 6:视频切斜摄影 Integer waylineType = baseMapper.getWaylineType(jobId, OdmConstant.ODM_TASK_OPTION_TYPE); // 如果不存在 if (null == waylineType) { return false; } // 查询航线任务对应的图片url List picList = baseMapper.getWaylinePicUrl(jobId, waylineType); Integer projectId = WebOdmProjectIdEnum.ORT.getProjectId(); // 创建odm 任务,实景三维和白膜三维共用一个工程id:4 List tiltTaskTypes = OdmConstant.TILT_TASK_TYPES; List altTaskTypes = OdmConstant.ALB_TASK_TYPES; if (tiltTaskTypes.contains(waylineType) || altTaskTypes.contains(waylineType)) { projectId = WebOdmProjectIdEnum.TD.getProjectId(); } Boolean x = createOdmTask(jobId, ortRes, waylineType, picList, projectId); if (x != null) return x; // 完成任务创建 return false; } /** * 创建 odm 任务 * * @param jobId 航线任务id * @param ortRes 正射影像的分辨率,单位为 cm/pixel * @param waylineType 航线任务类型 * @param picList 图片地址集合 * @return */ @Nullable private Boolean createOdmTask(String jobId, Double ortRes, Integer waylineType, List picList, Integer projectId) { log.info("当前航线任务:{},对应的图片数量:{}", jobId, picList.size()); if (picList.size() <= OdmConstant.MIN_PIC_NUM) { log.error("当前航线任务:{},对应的图片数量:{},少于{}张,无法创建任务", jobId, picList.size(),OdmConstant.MIN_PIC_NUM); throw new ServiceException("当前航线任务:" + jobId + ",对应的图片数量:" + picList.size() + ",少于" + OdmConstant.MIN_PIC_NUM + "张,无法创建任务"); } // 设置 odm 任务创建参数 Map map = setCreteOdmTaskParam(waylineType, ortRes); // url 格式化 String createTaskUrl = MessageFormat.format(OdmConstant.CREATE_TASK_API, projectId); // 校验该次任务已经创建 if (isCreateWebOdmTask(jobId, waylineType)) { log.error("webodm任务已经创建过了!jobId:{}", jobId); return false; } // 创建任务 JSONObject jsonObject = sendRequest.sendPostJsonByMap(createTaskUrl, true, map); // 保存关联信息到表 if (null == jsonObject) { log.error("创建任务失败!jobId:{}", jobId); return false; } // 上传对应的图片到 odm // 设置多线程进行图片上传,返回上传成功的文件数量 int count = uploadWaylinePic(picList, jsonObject.getString("id"), projectId); log.info("当前航线任务:{},已成功上传odm图片数量:{}", jobId, count); if (count < OdmConstant.MIN_PIC_NUM) { log.error("已上传odm图片数量为:{},不符合要求,至少需要上传{}张图片!", count,OdmConstant.MIN_PIC_NUM); return false; } // 图片上传完成提交任务 String taskId = jsonObject.getString("id"); // url 格式化,odm 任务id 从创建任务返回的id 取 String commitTaskUrl = MessageFormat.format(OdmConstant.COMMIT_TASK_API, projectId, taskId); jsonObject.put("images_count", count); jsonObject.put("partial", false); JSONObject commitTaskJson = sendRequest.sendPostJsonByJson(commitTaskUrl, true, jsonObject.toString()); if (null != commitTaskJson) { // 获取odm 任务信息 OdmTaskInfo odmTaskInfo = getOdmTaskInfo(jobId); // 更新 odm task 任务信息 boolean update = updateOdmTaskInfo(count, commitTaskJson, odmTaskInfo); return update && true; } return null; } /** * 定时补漏odm任务的的生成(由其他原因到时没有创建到任务的) * * @return */ @Override public Boolean leakRepairOdmTask() { int num = 0; // 先查询在执行的任务数,超过1个就先不创建了 Integer processTaskNum = baseMapper.getProcessTaskNum(); if (processTaskNum >= OdmConstant.MAX_PROCESS_TASK_NUM) { log.info("在运行的任务超过{}个,延时创建任务",OdmConstant.MAX_PROCESS_TASK_NUM); return false; } // 查询没有生成的odm任务航线任务,不包含已经删除的航线任务 List jobIds = baseMapper.getNoCreateTaskJob(OdmConstant.ODM_TASK_OPTION_TYPE); if (jobIds.size() == 0) { return false; } // 遍历生成任务 for (String jobId : jobIds) { boolean flag = saveOdmTaskInfo(jobId); if (flag) { num++; } } return num == jobIds.size(); } /** * 保存 odm 任务信息 * * @param jobId * @return */ @Override public Boolean saveOdmTaskInfo(String jobId) { // 查询当前航线任务的类型(过滤掉不参与图片合并的类型),当前只要 2:航测(正射举证) 4:图斑正射举证 5:三维倾斜摄影 6:视频三维白膜 Integer waylineType = baseMapper.getWaylineType(jobId, OdmConstant.ODM_TASK_OPTION_TYPE); // 如果不存在 if (null == waylineType) { return false; } // 视频白膜 if (waylineType == WaylineTypeEnum.ALB_COL.getType().intValue()) { // 判断是否有再处理视频的任务,如果有则不处理,没有才处理,防止cpu,内存使用过多 String value = bladeRedis.get(RedisKeyConstant.ODM_VIDEO_TO_PIC_KEY); if (!Strings.isBlank(value)) { log.info("已存在处理的视频,待处理完成后执行,退出当前执行"); return false; } } // 判断是否创建,没有则直接创建 odm 任务 if (isCreateTask(jobId)) { return false; } // 查询航线任务对应的图片url List picList = baseMapper.getWaylinePicUrl(jobId, waylineType); log.info("当前航线任务:{},对应的图片数量:{}", jobId, picList.size()); if (waylineType != WaylineTypeEnum.ALB_COL.getType().intValue() && picList.size() <= OdmConstant.MIN_PIC_NUM) { log.error("当前航线任务:{},对应的图片数量:{},少于{}张,无法创建任务", jobId, picList.size(),OdmConstant.MIN_PIC_NUM); return false; } // 创建 OdmTaskInfo odmTaskInfo = saveOdmTaskInfo(jobId, null, waylineType); if (null != odmTaskInfo) { return true; } return false; } /** * 校验任务是否已经创建 * * @param jobId * @return */ private boolean isCreateTask(String jobId) { // 根据jobId 查询任务信息 OdmTaskInfo odmTaskInfo = getOdmTaskInfo(jobId); if (null == odmTaskInfo) { return false; } return true; } /** * 校验webodm任务是否已经创建 * * @param jobId * @return */ private boolean isCreateWebOdmTask(String jobId, Integer waylineType) { // 根据jobId 查询任务信息 OdmTaskInfo odmTaskInfo = getOdmTaskInfo(jobId); if (null == odmTaskInfo) { // 没有就先创建 saveOdmTaskInfo(jobId, null, waylineType); return false; } // 如果 taskId 已存在,则无需再创建webodm任务 if (Strings.isBlank(odmTaskInfo.getTaskId())) { return false; } return true; } /** * 获取odm 任务信息 * * @param jobId * @return */ private OdmTaskInfo getOdmTaskInfo(String jobId) { // 根据jobId 查询任务信息 QueryWrapper wrapper = new QueryWrapper<>(); wrapper.eq("wayline_job_id", jobId); // 返回 return baseMapper.selectOne(wrapper); } /** * 保存odm 任务信息 * * @param jobId * @param taskId * @param waylineType 航线类型 * @return */ public OdmTaskInfo saveOdmTaskInfo(String jobId, String taskId, Integer waylineType) { // 如果是视频的,需要单独处理 if (waylineType == WaylineTypeEnum.ALB_COL.getType().intValue()) { videoToOdm(jobId); } else { OdmTaskInfo odmTaskInfo = new OdmTaskInfo(); // 根据jobId 查询对应的设备编号 String dockSn = baseMapper.getDeviceSnByJobId(jobId); odmTaskInfo.setDeviceSn(dockSn); OdmTaskInfo jobBaseInfo = baseMapper.selectJobBaseInfo(jobId); odmTaskInfo.setAreaCode(jobBaseInfo.getAreaCode()); odmTaskInfo.setCreateUser(jobBaseInfo.getCreateUser()); odmTaskInfo.setUpdateUser(jobBaseInfo.getUpdateUser()); odmTaskInfo.setCreateDept(jobBaseInfo.getCreateDept()); odmTaskInfo.setCreateTime(new Date()); odmTaskInfo.setUpdateTime(new Date()); odmTaskInfo.setProjectId(WebOdmProjectIdEnum.ORT.getProjectId()); odmTaskInfo.setTaskId(taskId); odmTaskInfo.setWaylineJobId(jobId); if (null != waylineType && OdmConstant.TILT_TASK_TYPES.contains(waylineType)) { // 设置为倾斜摄影 odmTaskInfo.setType(OdmTypeEnum.TILT.getType()); odmTaskInfo.setProjectId(WebOdmProjectIdEnum.TD.getProjectId()); } boolean save = this.save(odmTaskInfo); if (save) { return odmTaskInfo; } } return null; } /** * 更新 odm 任务信息 * * @param count * @param jsonObject * @return */ private boolean updateOdmTaskInfo(int count, JSONObject jsonObject, OdmTaskInfo odmTaskInfo) { odmTaskInfo.setTaskId(jsonObject.getString("id")); // 更新任务信息 odmTaskInfo.setImageCount(count); odmTaskInfo.setUploadProgress(jsonObject.getDouble("upload_progress")); odmTaskInfo.setResizeProgress(jsonObject.getDouble("resize_progress")); odmTaskInfo.setRunningProgress(jsonObject.getDouble("running_progress")); odmTaskInfo.setProcessingTime(jsonObject.getLong("processing_time")); odmTaskInfo.setStatus(jsonObject.getInteger("status")); // 更新并返回 return updateById(odmTaskInfo); } /** * 设置多线程进行图片上传到 odm 对应的任务 * * @param picList 图片url集合 * @param taskId odm 任务id * @param projectId odm 项目id * @return */ private int uploadWaylinePic(List picList, String taskId, Integer projectId) { int count = 0; // 创建Future列表来保存每个任务的结果 List> futures = new ArrayList<>(); for (String url : picList) { // 提交任务 futures.add(odmTaskThreadService.submit(() -> { // 这里执行你的异步任务 return sendUploadWaylinePic(url, taskId, projectId); })); } // 收集并打印结果 for (Future future : futures) { try { Boolean result = future.get(); if (result) { count++; } } catch (Exception e) { log.error("异常信息-----》", e); } } return count; } /** * 发送图片上传请求 * * @param url * @param taskId * @return */ private Boolean sendUploadWaylinePic(String url, String taskId, Integer projectId) { // 获取图片进行上传到 odm,通过读取本地地址的方式获取图片流 url = minioDataConstant.getServerBasePath() + minioDataConstant.getBucket() + url; File file = new File(url); // 检查文件是否存在 if (!file.exists()) { log.error("文件 {} 不存在!", url); return false; } FileSystemResource fileResource = new FileSystemResource(file); // url 格式化,odm project_id 固定为3,odm 任务id 从创建任务返回的id 取 String commitTaskUrl = MessageFormat.format(OdmConstant.UPLOAD_PIC_BY_TASK_API, projectId, taskId); MultiValueMap multiValueMap = new LinkedMultiValueMap<>(); multiValueMap.add("file", fileResource); // 发送上传请求 JSONObject jsonObject = sendRequest.sendPostFormData(commitTaskUrl, true, multiValueMap); if (null == jsonObject) { return false; } Boolean success = jsonObject.getBoolean("success"); if (success) { return true; } return false; } /** * 设置拼图任务参数和选项数据 * * @param waylineType 航线任务类型 * @param ortRes 正射影像的分辨率,单位为 cm/pixel(默认是5) * @return */ @NotNull private Map setCreteOdmTaskParam(Integer waylineType, Double ortRes) { // 设置任务创建参数 Map map = new HashMap<>(5); map.put("auto_processing_node", true); map.put("partial", true); map.put("processing_node", OdmConstant.PROCESS_NODE_NUM); // 图片压缩分辨率(-1 时代表不修改图片尺寸) if (waylineType == WaylineTypeEnum.TILT_WAY.getType().intValue() || waylineType == WaylineTypeEnum.GEO_WAY.getType().intValue()){ // 斜面和几何做图片压缩调整,其他情况暂不做压缩 map.put("resize_to", OdmConstant.RESIZE_VALUE); } else { map.put("resize_to", OdmConstant.NO_RESIZE_VALUE); } // 获取选项参数信息 List> options = OdmConstant.ODM_TASK_OPTION.get(waylineType); if (null != ortRes) { if (ortRes < 2) { ortRes = 2.0; } Double finalOrtRes = ortRes; Map ortResMap = new HashMap<>() {{ put("name", "orthophoto-resolution"); put("value", finalOrtRes); }}; options.add(ortResMap); } map.put("options", options); // 返回 return map; } /** * 获取正射影像信息 * * @param jobId 航线任务id * @return */ @Override public String getOrthoimageUrl(String jobId) { // 根据jobId 查询导出对应的正射影像信息 OdmTaskInfo odmTaskInfo = getOdmTaskInfo(jobId); if (null == odmTaskInfo) { return null; } // 格式化url String url = MessageFormat.format(OdmConstant.ORTHOPHOTO_EXPORT_API, odmTaskInfo.getProjectId(), odmTaskInfo.getTaskId()); // 配置请求参数 MultiValueMap multiValueMap = new LinkedMultiValueMap<>(); multiValueMap.add("format", "gtiff"); multiValueMap.add("epsg", "4326"); //调用接口获取正射影像信息 JSONObject jsonObject = sendRequest.sendPostFormData(url, true, multiValueMap); // 返回 if (null == jsonObject) { return null; } // 拼接 url String tmpUrl = odmProperties.getHttpsPreFixUrl() + OdmConstant.GET_PHONE_URL; // 先适配 uuid String downloadUrl = MessageFormat.format(tmpUrl, jsonObject.getString("celery_task_id")); // 拼接 downloadUrl = downloadUrl + "?filename=" + jsonObject.getString("filename"); // 返回 return downloadUrl; } /** * 定时更新odm 任务状态 * * @return */ @Override public Integer updateOdmTaskInfo() { int count = 0; // 查询 running_progress 小于 1 的任务,isUpdate 为 0 的 List odmTaskInfoList = baseMapper.selectOdmTaskListLess1(); // 判断是否有 if (odmTaskInfoList.size() == 0) { return count; } // 创建Future列表来保存每个任务的结果 List> futures = new ArrayList<>(); for (OdmTaskInfoVO odmTaskInfo : odmTaskInfoList) { // 提交任务 futures.add(odmTaskThreadService.submit(() -> { // 这里执行你的异步任务,更新任务状态 return sendGetTaskInfoReqestAndUpdate(odmTaskInfo); })); } // 收集并打印结果 for (Future future : futures) { try { Boolean result = future.get(); if (result) { count++; } } catch (Exception e) { log.error("异常信息-----》", e); } } return count; } /** * 更新任务状态 * * @param odmTaskInfo * @return */ private Boolean sendGetTaskInfoReqestAndUpdate(OdmTaskInfoVO odmTaskInfo) { log.info("开始更新任务状态,任务id:{}", odmTaskInfo.getTaskId()); boolean flag = false; // 格式化url String url = MessageFormat.format(OdmConstant.GET_TASK_INFO_API, odmTaskInfo.getProjectId(), odmTaskInfo.getTaskId()); //调用接口从 webodm 获取任务信息 JSONObject jsonObject = sendRequest.sendGetRequest(url, true); if (null == jsonObject) { return flag; } // 获取 状态值:running_progress等并更新 odmTaskInfo.setUploadProgress(jsonObject.getDouble("upload_progress")); odmTaskInfo.setResizeProgress(jsonObject.getDouble("resize_progress")); odmTaskInfo.setRunningProgress(jsonObject.getDouble("running_progress")); odmTaskInfo.setProcessingTime(jsonObject.getLong("processing_time")); // 更新状态 Integer status = jsonObject.getInteger("status"); odmTaskInfo.setStatus(status); // 设置更新同步状态 if (status == WebOdmStatusEnum.FAIL.getStatus().intValue() || status == WebOdmStatusEnum.COMPLETE.getStatus().intValue()) { // 设置为已更新完成 odmTaskInfo.setIsUpdate(1); } if (status == WebOdmStatusEnum.FAIL.getStatus().intValue()) { // 设置错误消息 odmTaskInfo.setLastError(jsonObject.getString("last_error")); } // 设置路径 if (status == WebOdmStatusEnum.COMPLETE.getStatus().intValue()) { log.info("odm 拼图任务已完成,任务id:{},航线任务ID:{}", odmTaskInfo.getTaskId(), odmTaskInfo.getWaylineJobId()); // 获取转换 api url // 设置地表模型tif 路径 String demTifPath = MessageFormat.format(OdmConstant.DEM_TIF_PATH, odmTaskInfo.getProjectId(), odmTaskInfo.getTaskId()); // 获取运行环境信息 String activeProfile = SpringContextUtil.getActiveProfile(); // 设置地形文件(处理耗时较长)时配置 redis,防止重复调用 String value = bladeRedis.get(RedisKeyConstant.ODM_DEM_KEY); if (!Strings.isBlank(value)) { return false; } // 设置缓存 bladeRedis.setEx(RedisKeyConstant.ODM_DEM_KEY, "1", RedisKeyConstant.ODM_DEM_TIME); // 设置 dem 地表模型tif 路径 odmTaskInfo.setDemTifPath(demTifPath); // 设置 正射范围4点空间面数据 String geomStr = getGeom(odmTaskInfo.getProjectId(), odmTaskInfo.getTaskId(), activeProfile); if (!Strings.isBlank(geomStr)) { odmTaskInfo.setGeom(geomStr); } // 格式化正射影像路径 String orthoimagePath = MessageFormat.format(OdmConstant.ORTHO_IMAGE_PATH, odmTaskInfo.getProjectId(), odmTaskInfo.getTaskId()); // 格式化正射影像服务api String orthoimageApi = MessageFormat.format(OdmConstant.ORTHO_IMAGE_API, odmTaskInfo.getProjectId(), odmTaskInfo.getTaskId()) + OdmConstant.ORTHO_IMAGE_API_SUX; // 设置正射影像路径 odmTaskInfo.setOrthoimagePath(orthoimagePath); // 设置正射影像图斑范围画面路径 odmTaskInfo.setOrtTbPath(tifParse(odmTaskInfo.getDkbh(), true, orthoimagePath)); // 设置正射影像api odmTaskInfo.setOrthoimageApi(orthoimageApi); // 航测文件倾斜摄影才有 3d-tiles 文件-修改为手动生成,自动生成的无法调参 // if (odmTaskInfo.getType() == 1) { // String tiles3dPath = MessageFormat.format(OdmConstant.TILES_3D_PATH, odmTaskInfo.getProjectId(), odmTaskInfo.getTaskId()); // odmTaskInfo.setTilesPath(tiles3dPath); // } // if (odmTaskInfo.getType() == 2) { // 调用 api 对 点云laz 生成 3d-tiles,设置对应的路径,这里去生成白膜-暂时关闭白膜处理,修改为单独处理,这里处理耗时过长会导致其他状态更新异常 // setPointCloudTo3dTilesPath(odmTaskInfo, activeProfile); // } } if (Strings.isBlank(odmTaskInfo.getGeom())) { // 更新 flag = updateById(odmTaskInfo); } else { // 手动更新 int update = baseMapper.updateOdmTaskGeomInfoById(odmTaskInfo); flag = update > 0 ? true : false; } if (flag) { if (odmTaskInfo.getStatus()==WebOdmStatusEnum.COMPLETE.getStatus().intValue() && odmTaskInfo.getType()==OdmTypeEnum.ORT.getType().intValue()) { // 更新正射影像tif 到 附件表 updateOrthoimageAttach(odmTaskInfo); } // 更新三维白膜 到 附件表 if (!Strings.isBlank(odmTaskInfo.getVoxGridTilesPath()) && odmTaskInfo.getType()==OdmTypeEnum.ALB.getType().intValue()) { update3dTilesAttach(odmTaskInfo, odmTaskInfo.getVoxGridTilesPath()); } // 更新切斜摄影 到 附件表 if (!Strings.isBlank(odmTaskInfo.getTilesPath()) && odmTaskInfo.getType()==OdmTypeEnum.TILT.getType().intValue()) { update3dTilesAttach(odmTaskInfo, odmTaskInfo.getTilesPath()); } } // 删除缓存 bladeRedis.del(RedisKeyConstant.ODM_DEM_KEY); // 返回 return flag; } /** * 更新白膜/倾斜摄影 到 附件表 * * @param odmTaskInfo */ public boolean update3dTilesAttach(OdmTaskInfoVO odmTaskInfo, String filePath) { log.info("开始更新三维白膜/倾斜数据到附件表,odm任务id:{},航线任务id:{},文件路径:{}", odmTaskInfo.getTaskId(), odmTaskInfo.getWaylineJobId(), filePath); // 通过路径获取文件 String path = odmTaskInfo.getBasePath() + filePath; File file = new File(path); // 检查文件是否存在 if (!file.exists()) { log.error("三维白膜/倾斜数据文件 {} 不存在!", path); return false; } Attach attach = new Attach(); // 计算设置文件大小 attach.setAttachSize(file.length()); // 设置文件路径,链接等 attach.setDomainUrl(odmProperties.getDomainUrl()); attach.setLink(odmProperties.getDomainUrl() + filePath); attach.setName(path); attach.setOriginalName(path); attach.setExtension(".json"); // 设置航线对应的机构 attach.setTenantId("000000"); // 设置航线任务jobId if (!Strings.isBlank(odmTaskInfo.getWaylineJobId())) { attach.setPatrolTaskId(odmTaskInfo.getWaylineJobId()); } // 设置类型-切斜摄影 if (odmTaskInfo.getType() == OdmTypeEnum.TILT.getType().intValue()) { attach.setResultType(null); } // 设置类型-三维白膜 if (odmTaskInfo.getType() == OdmTypeEnum.ALB.getType().intValue()) { attach.setResultType(null); } // 设置区域编号 attach.setAreaCode(odmTaskInfo.getAreaCode()); attach.setCreateUser(odmTaskInfo.getCreateUser()); attach.setUpdateUser(odmTaskInfo.getUpdateUser()); attach.setCreateDept(odmTaskInfo.getCreateDept()); attach.setCreateTime(new Date()); attach.setUpdateTime(new Date()); if (!Strings.isBlank(odmTaskInfo.getCompletedTime())) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); } // 设置设备编号 if (!Strings.isBlank(odmTaskInfo.getDeviceSn())) { attach.setDeviceSn(odmTaskInfo.getDeviceSn()); } if (!Strings.isBlank(odmTaskInfo.getWorkspaceId())) { attach.setWorkspaceId(odmTaskInfo.getWorkspaceId()); } log.info("发起远程调用保存三维白膜/倾斜附件信息,tif 路径:{}", path); Boolean isSaveAttach = attachClient.saveAttachInfo(attach); // 返回 return isSaveAttach; } /** * 调用 api 对 obj 生成 3d-tiles,设置对应的路径 * * @param odmTaskInfo * @param activeProfile */ private void setVoxGridTilesPath(OdmTaskInfoVO odmTaskInfo, String activeProfile) { log.info("开始处理倾斜数据"); String objPath = odmProperties.getWebodmDataBasePath() + MessageFormat.format(OdmConstant.OBJ_PATH, odmTaskInfo.getProjectId(), odmTaskInfo.getTaskId()); // 中心点文件路径 String posFilePath = odmProperties.getWebodmDataBasePath() + MessageFormat.format(OdmConstant.CENTER_FILE_PATH, odmTaskInfo.getProjectId(), odmTaskInfo.getTaskId()); // obj 转体素化网格 3d-tiles String objConvVoxGrid3dTilesUrl = pyToolsServiceProperties.getPyToolsService() + TifConstant.OBJ_VOX_GRID_3D_TILES_API; // 设置请求参数 Map map = new HashMap<>(2); map.put("obj_path", objPath); map.put("pos_file_path", posFilePath); // 远程调用接口获取结果 String result = pyTifSendRequest.sendPostJsonByMap(objConvVoxGrid3dTilesUrl, map); log.info("当前任务编号:{},处理obj后的 3d-tiles 路径是:{}", odmTaskInfo.getTaskId(), result); if (!Strings.isBlank(result)) { String replaceAll = result.replaceAll("^\"|\"$", ""); String regex = odmProperties.getWebodmDataBasePath(); String[] split = replaceAll.split(regex); // 设置体素网格 3d-tiles 文件路径 odmTaskInfo.setVoxGridTilesPath(split[1] + "/tileset.json"); } } /** * 调用 api 对 点云生成 3d-tiles,设置对应的路径 * * @param odmTaskInfo * @param activeProfile */ private void setPointCloudTo3dTilesPath(OdmTaskInfoVO odmTaskInfo, String activeProfile) { log.info("开始处理点云数据,通过点云提取建筑然后生成白膜数据..."); // obj 转体素化网格 3d-tiles String pointCloudPath = odmProperties.getWebodmDataBasePath() + MessageFormat.format(OdmConstant.POINT_CLOUD_PATH, odmTaskInfo.getProjectId(), odmTaskInfo.getTaskId()); // 拼接 url String pointCloudTo3dTilesUrl = pyToolsServiceProperties.getPyToolsService() + TifConstant.POINT_CLOUD_TO_3D_TILES_API; Map map = new HashMap<>(2); map.put("piontcloud_path", pointCloudPath); map.put("wayline_job_id", odmTaskInfo.getWaylineJobId()); // 远程调用接口获取结果 String result = pyTifSendRequest.sendPostJsonByMap(pointCloudTo3dTilesUrl, map); log.info("当前任务编号:{},处理点云后的 3d-tiles 路径是:{}", odmTaskInfo.getTaskId(), result); if (!Strings.isBlank(result)) { String replaceAll = result.replaceAll("^\"|\"$", ""); String regex = odmProperties.getWebodmDataBasePath(); String[] split = replaceAll.split(regex); // 设置体素网格 3d-tiles 文件路径 odmTaskInfo.setVoxGridTilesPath(split[1] + "/tileset.json"); } } /** * 调用 api 对 obj 生成 3d-tiles,设置对应的路径 * * @param odmTaskInfo * @param activeProfile */ private void setObjTo3dTilesPath(OdmTaskInfoVO odmTaskInfo, String activeProfile) { log.info("开始处理obj数据,通过obj生成实景三维倾斜数据..."); // 设置 obj 路径、中心点文件路径 String objPath = odmProperties.getWebodmDataBasePath() + MessageFormat.format(OdmConstant.OBJ_PATH, odmTaskInfo.getProjectId(), odmTaskInfo.getTaskId()); // 中心点文件路径 String posFilePath = odmProperties.getWebodmDataBasePath() + MessageFormat.format(OdmConstant.CENTER_FILE_PATH, odmTaskInfo.getProjectId(), odmTaskInfo.getTaskId()); // 设置请求参数 Map map = new HashMap<>(2); map.put("obj_path", objPath); map.put("pos_file_path", posFilePath); // 远程调用接口获取结果 String result = objConvertClient.obj2TilesModel(map); log.info("当前任务编号:{},处理obj后的 3d-tiles 路径是:{}", odmTaskInfo.getTaskId(), result); if (!Strings.isBlank(result)) { String replaceAll = result.replaceAll("^\"|\"$", ""); String regex = odmProperties.getWebodmDataBasePath(); String[] split = replaceAll.split(regex); // 设置体素网格 3d-tiles 文件路径 odmTaskInfo.setTilesPath(split[1] + "/tileset.json"); } } /** * 设置 正射范围4点空间面数据 * * @param projectId 工程id * @param taskId 任务id * @param activeProfile * @return */ private String getGeom(Integer projectId, String taskId, String activeProfile) { // 读取文件获取EPSG:4326的面数据 try { String modelInfoFilePath = odmProperties.getWebodmDataBasePath() + MessageFormat.format(OdmConstant.MODEL_INFO_FILE_PATH, projectId, taskId); String wktData = GeoJsonToWktConverterUtils.jsonFileToWktData(modelInfoFilePath); log.debug("得到的 wkt 面数据为:{}", wktData); if (Strings.isBlank(wktData)){ return null; } return "'" + wktData + "'"; } catch (Exception e) { e.printStackTrace(); log.error("地表模型和面数据解析异常"); throw new ServiceException("地表模型和面数据解析异常"); } } /** * 设置 dem 地表模型tif 路径 * * @param odmTaskInfo * @param demTifPath * @param activeProfile */ private void setDemTifPath(OdmTaskInfoVO odmTaskInfo, String demTifPath, String activeProfile) { // 设置 tif 路径 demTifPath = odmProperties.getWebodmDataBasePath() + demTifPath; // tif 转换 geotiff 4326 String parseTifUrl = pyToolsServiceProperties.getPyToolsService() + TifConstant.PARSE_TIF_CONV_API; Map map = new HashMap<>(1); map.put("dsm_path", demTifPath); // 远程调用接口获取结果 String result = pyTifSendRequest.sendPostJsonByMap(parseTifUrl, map); if (!Strings.isBlank(result)) { String replaceAll = result.replaceAll("^\"|\"$", ""); String regex = odmProperties.getWebodmDataBasePath(); String[] split = replaceAll.split(regex); // 设置 dem 数字地表模型tif 路径 odmTaskInfo.setDemTifPath(split[1]); } } /** * 更新正射影像tif 到 附件表 * * @param odmTaskInfo */ private Boolean updateOrthoimageAttach(OdmTaskInfoVO odmTaskInfo) { if (odmTaskInfo.getType() == 0) { log.info("开始更新正射影像tif 到 附件表,航线任务id:{}...", odmTaskInfo.getWaylineJobId()); // 保存tif 数据到 附件表 // 通过路径获取文件 String path = odmTaskInfo.getBasePath() + odmTaskInfo.getOrthoimagePath(); File file = new File(path); // 检查文件是否存在 if (!file.exists()) { log.error("文件 {} 不存在!", path); return false; } // 同时压缩图片 Attach attach = new Attach(); // 计算设置文件大小 attach.setAttachSize(file.length()); // 设置文件路径,链接等 attach.setDomainUrl(odmProperties.getDomainUrl()); attach.setLink(odmProperties.getDomainUrl() + odmTaskInfo.getOrthoimagePath()); attach.setName(path); attach.setNickName(odmTaskInfo.getWaylineJobName()); attach.setOriginalName(path); attach.setExtension(".tif"); // 设置航线对应的机构 attach.setAreaCode(odmTaskInfo.getAreaCode()); attach.setCreateUser(odmTaskInfo.getCreateUser()); attach.setUpdateUser(odmTaskInfo.getUpdateUser()); attach.setCreateDept(odmTaskInfo.getCreateDept()); attach.setCreateTime(new Date()); attach.setUpdateTime(new Date()); attach.setTenantId("000000"); // 设置航线任务jobId if (!Strings.isBlank(odmTaskInfo.getWaylineJobId())) { attach.setPatrolTaskId(odmTaskInfo.getWaylineJobId()); } // 设置类型 attach.setResultType(null); // 设置设备编号 if (!Strings.isBlank(odmTaskInfo.getDeviceSn())) { attach.setDeviceSn(odmTaskInfo.getDeviceSn()); } if (!Strings.isBlank(odmTaskInfo.getWorkspaceId())) { attach.setWorkspaceId(odmTaskInfo.getWorkspaceId()); } if (!Strings.isBlank(odmTaskInfo.getCompletedTime())) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); } // 图片压缩 log.info("开始压缩tif图片,路径:{}", path); Map map = new HashMap<>(1); map.put("ort_path", path); TifConvertResult tifConvertResult = tifConvertClient.tifConvJpeg(map); log.info("tif 图片压缩结果:{}", tifConvertResult); // 无需再上传 if (null != tifConvertResult && !Strings.isBlank(tifConvertResult.getBasePath())) { log.info("tif 图片压缩成功,路径:{}", tifConvertResult.getBasePath()); Boolean isSaveAttach = attachClient.saveAttachInfo(attach); if (isSaveAttach) { log.info("tif 图片保存成功,发起远程调用保存tif附件信息"); return true; } } } return false; } public MultipartFile convertFileToMultipartFile(File file) { try { String fileName = file.getName(); String contentType = Files.probeContentType(file.toPath()); byte[] content = Files.readAllBytes(file.toPath()); return new MockMultipartFile("file", fileName, contentType, content); } catch (IOException e) { e.printStackTrace(); } return null; } /** * 获取正射影像服务信息 * * @param dkbh 图斑编号 * @param workspaceId 项目id * @return */ @Override public R getOrthoimageInfo(String dkbh, String workspaceId, String jobId) { Double runningProgress = 1.0; // 根据图斑编号 查询导出对应的正射影像信息 OdmTaskInfoVO odmTaskInfo = baseMapper.getTbOrthoimageInfo(dkbh, runningProgress, jobId,OdmConstant.OTR_TASK_TYPES); if (null == odmTaskInfo) { return R.data(null); } // 设置 webODM访问token odmTaskInfo.setOdmToken("JWT " + sendRequest.getOdmToken()); // 设置图斑中心点坐标,需要访问webodm接口获取,格式化url String url = MessageFormat.format(OdmConstant.GET_RTHOPHOTO_METADATA_API, odmTaskInfo.getProjectId(), odmTaskInfo.getTaskId()); //调用接口获取正射影像数据信息 JSONObject jsonObject = sendRequest.sendGetRequest(url, true); // 返回 if (null == jsonObject) { return R.data(odmTaskInfo); } // 设置中心点 JSONArray center = jsonObject.getJSONArray("center"); odmTaskInfo.setOrthoimageLng(center.get(0).toString()); odmTaskInfo.setOrthoimageLng(center.get(1).toString()); // 返回 return R.data(odmTaskInfo); } /** * 获取正射影像服务信息 * * @param lotInfoId 图斑id * @param workspaceId 项目id * @return */ @Override public R getOrthoimageInfos(String lotInfoId, String workspaceId, String jobId) { Double runningProgress = 1.0; List odmTaskInfoList = baseMapper.listTbOrthoimageInfoAsc(lotInfoId, runningProgress, jobId,OdmConstant.OTR_TASK_TYPES); if (CollectionUtils.isEmpty(odmTaskInfoList)) { return R.data(Collections.emptyList()); } // --- 优化:将循环外不变的变量提前获取 --- String odmToken = "JWT " + sendRequest.getOdmToken(); String activeProfile = SpringContextUtil.getActiveProfile(); // 获取一次环境配置 String nowDateStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")); // 获取一次当前日期字符串 // 遍历列表,为每个对象填充额外信息 for (OdmTaskInfoVO taskInfo : odmTaskInfoList) { // 1. 设置 Token taskInfo.setOdmToken(odmToken); // 2. 设置中心点坐标 String metaUrl = MessageFormat.format(OdmConstant.GET_RTHOPHOTO_METADATA_API, taskInfo.getProjectId(), taskInfo.getTaskId()); JSONObject jsonObject = sendRequest.sendGetRequest(metaUrl, true); if (jsonObject != null) { JSONArray center = jsonObject.getJSONArray("center"); if (center != null && center.size() >= 2) { taskInfo.setOrthoimageLng(center.get(0).toString()); taskInfo.setOrthoimageLat(center.get(1).toString()); } // 获取边界点 String bounds = jsonObject.getJSONObject("bounds").getJSONArray("value").toJSONString(); log.info("边界点:{}", bounds); taskInfo.setBounds(bounds); } // 3. 为当前 taskInfo 生成并设置下载 URL String downloadUrl = generateDownloadUrlForTask(taskInfo, activeProfile, nowDateStr); taskInfo.setDownloadUrl(downloadUrl); } return R.data(odmTaskInfoList); } /** * 为单个任务信息对象生成下载链接。 * 该方法整合了原有的 getOdmTaskDownUrl 和 getMasTifUrl 的逻辑。 * * @param taskInfo 任务信息对象 * @param activeProfile 当前运行环境 * @param nowDateStr 当前日期字符串 (yyyyMMdd) * @return 生成的下载链接,如果失败则返回 null */ private String generateDownloadUrlForTask(OdmTaskInfoVO taskInfo, String activeProfile, String nowDateStr) { // 优先判断是否有裁剪好的图斑面(ortTbPath) if (!Strings.isBlank(taskInfo.getOrtTbPath())) { return odmProperties.getDomainUrl() + taskInfo.getOrtTbPath(); } // 如果没有 ortTbPath,则执行文件复制并生成新的 URL try { // 确定输出目录 String outDir = odmDownTempBasePath + "/" + nowDateStr; File dir = new File(outDir); if (!dir.exists()) { dir.mkdirs(); } // 定义源文件和目标文件路径 String inputPath = taskInfo.getBasePath() + taskInfo.getOrthoimagePath(); String outPath = outDir + "/" + taskInfo.getDkbh() + ".tif"; File newFile = new File(outPath); // --- 文件复制逻辑 --- // 使用 try-with-resources 语句,可以自动关闭流,更安全 try (FileInputStream fileInputStream = new FileInputStream(inputPath); FileOutputStream os = new FileOutputStream(newFile)) { IOUtils.copy(fileInputStream, os); } // 成功复制后,返回可访问的下载地址 return odmDownPreFixPath + "/" + nowDateStr + "/" + taskInfo.getDkbh() + ".tif"; } catch (IOException e) { // 使用日志记录错误,而不是打印到控制台 log.error("为地块 {} 生成下载TIF文件失败: {}", taskInfo.getDkbh(), e.getMessage()); return null; // 发生异常时返回 null } } /** * 自定义删除 * * @param idList * @return */ @Override public Boolean removeOdmTaskByIds(List idList) { List list = new ArrayList<>(); int num1 = 0; int num2 = 0; // 遍历删除 for (Long id : idList) { // 查询对应的任务信息 OdmTaskInfo taskInfo = getById(id); list.add(taskInfo); // 删除 boolean b = removeById(id); if (b) { num1++; } } // 删除webodm任务 num2 = removeWebodmTask(list, num2); return num1 == num2; } /** * 删除webodm任务 * * @param list * @param num2 * @return */ private int removeWebodmTask(List list, int num2) { // 删除webodm上对应的任务 for (OdmTaskInfo odmTaskInfo : list) { // 格式化url String url = MessageFormat.format(OdmConstant.REMOVE_TASK_INFO_API, odmTaskInfo.getProjectId(), odmTaskInfo.getTaskId()); //调用接口删除信息 JSONObject jsonObject = sendRequest.sendPostFormData(url, true, null); // 返回 if (null != jsonObject) { Boolean success = jsonObject.getBoolean("success"); if (success) { num2++; // 异步删除对应的文件 asyncService.deleteFileByTaskId(odmTaskInfo.getTaskId()); } } } return num2; } /** * 删除webodm任务 * * @param list * @return */ private boolean updateAndRemoveWebodmTask(List list) { int num = 0; // 删除webodm上对应的任务 for (OdmTaskInfo odmTaskInfo : list) { // 格式化url String url = MessageFormat.format(OdmConstant.REMOVE_TASK_INFO_API, odmTaskInfo.getProjectId(), odmTaskInfo.getTaskId()); //调用接口删除信息 JSONObject jsonObject = sendRequest.sendPostFormData(url, true, null); // 返回 if (null != jsonObject) { Boolean success = jsonObject.getBoolean("success"); if (success) { // 异步删除对应的文件 boolean result = asyncService.deleteFileByTaskId(odmTaskInfo.getTaskId()); if (result) { odmTaskInfo.setFileDelFlag(1); // 更新删除状态 updateById(odmTaskInfo); num++; } } } } // 返回结果 return list.size() == num; } /** * 定时任务----异常 odm 任务重试 * * @return */ @Override public Boolean runErrorWebOdmTask() { int flag = 0; // 查询异常任务,异常次数3以下的,超过3次可以手动触发,不会自动进行重试 ,外加已取消的任务进行重启,暂定每次只处理一个,每次只查询一个 List list = baseMapper.getErrorWebOdmTaskList(); if (list.size() == 0) { return true; } // 开始处理 // 格式化重启任务url for (OdmTaskInfo odmTaskInfo : list) { String url = MessageFormat.format(OdmConstant.RESTART_TASK_API, odmTaskInfo.getProjectId(), odmTaskInfo.getTaskId()); //调用接口重启任务 JSONObject jsonObject = sendRequest.sendPostFormData(url, true, null); // 返回 if (null != jsonObject) { Boolean success = jsonObject.getBoolean("success"); if (success) { flag++; int num = odmTaskInfo.getErrorNum(); num++; // 更新odmTaskInfo 信息 if (odmTaskInfo.getStatus() == WebOdmStatusEnum.FAIL.getStatus().intValue()) { odmTaskInfo.setErrorNum(num); odmTaskInfo.setLastError(""); } odmTaskInfo.setRunningProgress(0.00); odmTaskInfo.setProcessingTime(0L); // 重置更新状态 odmTaskInfo.setIsUpdate(0); // 更新 baseMapper.updateById(odmTaskInfo); } } } // 返回 return flag == list.size(); } /** * 更新正射影像tif 信息到 附件表 * * @return */ @Override public R updateOrthoimageToAttach() { int count = 0; // 查询已完成的任务中没有将tif 信息同步到 附件表的信息 List list = baseMapper.getNoSaveAttachOdmTaskInfoList(); // 遍历将tif 信息同步到附件表 for (OdmTaskInfoVO odmTaskInfo : list) { Boolean flag = updateOrthoimageAttach(odmTaskInfo); if (flag) { count++; } } return R.status(count == list.size()); } /** * odm 任务重启 * * @param taskId 航线任务id * @return */ @Override public Boolean restartOdmTask(String taskId) { boolean flag = false; // 查询对应odm 信息 QueryWrapper wrapper = new QueryWrapper<>(); wrapper.eq("task_id", taskId); OdmTaskInfo odmTaskInfo = getOne(wrapper); // 先重置 resetOdmTask(taskId, odmTaskInfo.getProjectId()); // 获取重启任务url String url = MessageFormat.format(OdmConstant.RESTART_TASK_API, odmTaskInfo.getProjectId(), taskId); //调用接口重启任务 JSONObject jsonObject = sendRequest.sendPostFormData(url, true, null); // 返回 if (null != jsonObject) { Boolean success = jsonObject.getBoolean("success"); if (success) { // 更新odmTaskInfo 信息 if (odmTaskInfo.getStatus() == WebOdmStatusEnum.FAIL.getStatus().intValue()) { odmTaskInfo.setLastError(""); } odmTaskInfo.setRunningProgress(0.00); odmTaskInfo.setProcessingTime(0L); odmTaskInfo.setStatus(0); // 重置更新状态 odmTaskInfo.setIsUpdate(0); // 更新 int update = baseMapper.updateById(odmTaskInfo); if (update > 0) { flag = true; } } } return flag; } /** * 重置信息 * * @param taskId * @param projectId */ private boolean resetOdmTask(String taskId, Integer projectId) { // 查询任务信息 QueryWrapper wrapper = new QueryWrapper<>(); wrapper.eq("task_id", taskId); OdmTaskInfo odmTaskInfo = getOne(wrapper); // 查询当前航线任务的类型(过滤掉不参与图片合并的类型),当前只要 2:航测 4:正射举证 5:倾斜摄影 6:视频切斜摄影 Integer waylineType = baseMapper.getWaylineType(odmTaskInfo.getWaylineJobId(), OdmConstant.ODM_TASK_OPTION_TYPE); // 如果不存在 if (null == waylineType) { return false; } String url = MessageFormat.format(OdmConstant.PATCH_TASK_INFO_API, projectId, taskId); Map map = new HashMap<>(2); // 获取选项参数信息 List> options = OdmConstant.ODM_TASK_RESET_OPTIONS.get(waylineType); map.put("options", options); JSONObject jsonObject = sendRequest.sendPatchJsonByMap(url, true, map); if (null != jsonObject) { return true; } return false; } /** * 获取odm 任务下载url * * @param dkbh * @param workspaceId * @return */ @Override public String getOdmTaskDownUrl(String dkbh, String workspaceId, String jobId) { Double runningProgress = 1.0; // 根据图斑编号 查询导出对应的正射影像信息 OdmTaskInfoVO odmTaskInfo = baseMapper.getTbOrthoimageInfo(dkbh, runningProgress, jobId,OdmConstant.OTR_TASK_TYPES); if (null == odmTaskInfo) { return null; } // 判断是否有画好的图斑面,如果没有则正常取原tif图 if (!Strings.isBlank(odmTaskInfo.getOrtTbPath())) { // 拼接 url return odmProperties.getDomainUrl() + odmTaskInfo.getOrtTbPath(); } // 获取原tif重命名图 return getMasTifUrl(dkbh, odmTaskInfo); } /** * 获取原tif重命名图 * * @param dkbh * @param odmTaskInfo * @return */ @NotNull private String getMasTifUrl(String dkbh, OdmTaskInfoVO odmTaskInfo) { // 下载tif 图片,重新赋名 LocalDateTime currentTime = LocalDateTime.now(); // 格式化时间,生成当前时间 String nowDateStr = currentTime.format(DateTimeFormatter.ofPattern("yyyyMMdd")); String outPath = odmDownTempBasePath + "/" + nowDateStr; // 获取路径 String inputPath = odmTaskInfo.getBasePath() + odmTaskInfo.getOrthoimagePath(); // String inputPath = "E:\\work\\temp\\odm\\odm_orthophoto.tif"; //通过上传路径得到上传的文件夹 File file = new File(outPath); //若目标文件夹不存在,则创建 if (!file.exists()) { file.mkdirs(); } //定义重新生成后的目标文件名 outPath = outPath + "/" + dkbh + ".tif"; File newFile = new File(outPath); // 根据目标文件的新建其输出流对象 FileOutputStream os = null; try { FileInputStream fileInputStream = new FileInputStream(inputPath); os = new FileOutputStream(newFile); //完成输入流到输出流的复制 IOUtils.copy(fileInputStream, os); //关闭流(先开后关) os.close(); fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } // 返回下载地址 return odmDownPreFixPath + "/" + nowDateStr + "/" + dkbh + ".tif"; } /** * 定时删除odm临时文件任务 * * @return */ @Override public boolean deletedOdmTempFile() { // 删除下载临时目录 String path = odmDownTempBasePath; deletedFile(new File(path)); // 删除下载临时zip目录 // String zipPath = odmDownTempZipBasePath; // deletedFile(new File(zipPath)); return true; } /** * 定时删除odm拼图任务中失效的文件 * * @return */ @Override public boolean deletedOdmTaskInVFile() { // 清理已删除的 odm 任务产生的文件(任务删除后没有删除掉对于的文件) List typeList = new ArrayList<>(); typeList.add(WebOdmProjectIdEnum.ORT.getProjectId()); typeList.add(WebOdmProjectIdEnum.TD.getProjectId()); for (Integer type : typeList) { // odm 任务查询 url String url = MessageFormat.format(OdmConstant.QUERY_TASK_API, type); //调用接口重启任务 JSONArray jsonArray = sendRequest.sendGetRequestResArr(url, true); if (jsonArray.size() == 0) { log.error("没有查询到任务信息"); return false; } List nowList = new ArrayList<>(); // 转换成taskId集合 for (int i = 0; i < jsonArray.size(); i++) { nowList.add(jsonArray.getJSONObject(i).getString("id")); } // 读取对应文件目录列表 String filePath = MessageFormat.format(odmProperties.getWebodmDataBasePath() + OdmConstant.TASK_FILE_PATH, type); File file = new File(filePath); File[] files = file.listFiles(); if (null != files) { for (File f : files) { // 如果当前文件名(任务id)不存在已有的任务则删除 if (!nowList.contains(f.getName())) { deletedFile(f); } } } } return true; } /** * 定时删除odm拼图任务中重复的任务(同一个图斑多个任务,保留最新的) * * @return */ @Override public boolean deletedRepOdmTask() { // 查询同一个图斑多出的任务进行删除 List list = baseMapper.selectReqOdmList(); boolean a = removeOdmTaskByList(list); // 删除掉odm_task_info 表中没有的任务 boolean b = removeSuperfluousTask(); // 删除并返回 return a && b; } /** * 删除掉 odm_task_info 表中没有的任务 * * @return */ private boolean removeSuperfluousTask() { List typeList = new ArrayList<>(); typeList.add(WebOdmProjectIdEnum.ORT.getProjectId()); typeList.add(WebOdmProjectIdEnum.TD.getProjectId()); for (Integer type : typeList) { // 查询出所有odm_task_info 任务 List list = list(); List taskIds = list.stream().map(OdmTaskInfo::getTaskId) .collect(Collectors.toList()); // 查询出webodm 中所有的任务 // odm 任务查询 url String url = MessageFormat.format(OdmConstant.QUERY_TASK_API, type); //调用接口重启任务 JSONArray jsonArray = sendRequest.sendGetRequestResArr(url, true); if (jsonArray.size() == 0) { log.error("没有查询到任务信息"); return false; } List nowList = new ArrayList<>(); // 转换成taskId集合 for (int i = 0; i < jsonArray.size(); i++) { nowList.add(jsonArray.getJSONObject(i).getString("id")); } // 获取 nowList 中但不在 taskIds 中的元素 List difference = nowList.stream() .filter(element -> !taskIds.contains(element)) .collect(Collectors.toList()); // 删除webodm 任务中多出的任务 for (String taskId : difference) { //调用接口删除信息 JSONObject jsonObject = sendRequest.sendPostFormData( MessageFormat.format(OdmConstant.REMOVE_TASK_INFO_API, type, taskId), true, null ); // 返回 if (null != jsonObject) { Boolean success = jsonObject.getBoolean("success"); if (success) { // 异步删除对应的文件 asyncService.deleteFileByTaskId(taskId); } } } } return true; } /** * 定时删除odm拼图任务(航线任务已删除的) * * @return */ @Override public boolean delRepOdmTaskByNoWayJob() { // 查询出航线任务已经删除的任务,进行删除对于odm 任务 List list = baseMapper.selectRemWayJobList(); // 删除并返回 return removeOdmTaskByList(list); } /** * 删除odm任务 * * @param list * @return */ private boolean removeOdmTaskByList(List list) { // 删除 int num1 = 0; int num2 = 0; // 遍历删除 for (OdmTaskInfo odmTaskInfo : list) { odmTaskInfo.setFileDelFlag(1); // 数据不删除,修改数据状态 boolean b = updateById(odmTaskInfo); if (b) { num1++; } } // 删除webodm任务 num2 = removeWebodmTask(list, num2); // 返回 return num1 == num2; } /** * tif 图片解析画图斑面 * * @param dkbh 地块编号 * @param flag 是否已完成拼图任务 * @param orthoimagePath 正射文件路径 * @return */ @Override public String tifParse(String dkbh, Boolean flag, String orthoimagePath) { log.info("tif 图片解析画图斑面,地块编号:{},是否完成拼图任务:{},正射文件路径:{}", dkbh, flag, orthoimagePath); if (null == flag) { flag = false; } if (Strings.isBlank(dkbh)) { log.error("地块编号不能为空"); return null; } // 根据图斑编号 查询导出对应的正射影像信息 OdmTaskInfoVO odmTaskInfo = baseMapper.getTbOrthoimageInfo(dkbh, null, "",OdmConstant.OTR_TASK_TYPES); if (Strings.isBlank(orthoimagePath)) { orthoimagePath = odmTaskInfo.getOrthoimagePath(); } // 解析 if (null == odmTaskInfo) { log.error("没有查到对应的任务"); return null; } if (!flag && odmTaskInfo.getRunningProgress() < 1.0) { log.error("拼图任务尚未完成"); return null; } if (Strings.isBlank(odmTaskInfo.getGeom())) { log.error("当前地块无空间面数据"); return null; } // 将面数据转点集合 List coordinates = polygonStringToCoordinates(odmTaskInfo.getGeom()); // 下载tif 图片,重新赋名 // 格式化正射影像图斑画面路径 String outPath = odmProperties.getWebodmDataBasePath() + MessageFormat.format(OdmConstant.ORTHO_TB_IMAGE_PATH, odmTaskInfo.getProjectId(), odmTaskInfo.getTaskId()); // 获取路径 String inputPath = odmTaskInfo.getBasePath() + orthoimagePath; // String inputPath = "C:\\Users\\Administrator\\Desktop\\其他\\无人机\\102.tif"; //通过上传路径得到上传的文件夹 File file = new File(outPath); //若目标文件夹不存在,则创建 if (!file.exists()) { file.mkdirs(); } //定义重新生成后的目标文件名 outPath = outPath + "/" + dkbh + ".tif"; log.info("tif 文件路径:{}", outPath); log.debug("tif 对应面数据:{}", coordinates); // outPath = "C:\\Users\\Administrator\\Desktop\\其他\\无人机\\out.tif"; // 将所有点转换成地理坐标 List pointList = ParseTifUtils.getTifPosByMoreLngLat(inputPath, new ArrayList<>(coordinates)); log.debug("tif 地理坐标数据:{}", pointList); // 将所有点的地理坐标转换成像素坐标 List pixelByPointList = getPixelByPointList(inputPath, pointList); if (pixelByPointList.size() == 0) { log.error("点位转换失败"); return null; } // 画线 boolean drawFlag = ParseTifUtils.drawPolygonByPoints(inputPath, outPath, pixelByPointList, "tif"); // 失败返回空 if (!drawFlag) { log.error("画面失败"); return null; } // 返回数据路径 return MessageFormat.format(OdmConstant.ORTHO_TB_IMAGE_PATH, odmTaskInfo.getProjectId(), odmTaskInfo.getTaskId()) + "/" + dkbh + ".tif"; } /** * 将所有点的地理坐标转换成像素坐标 * * @param inputPath tif 图片路径 * @param pointList 地理坐标集合 * @return */ @Override public List getPixelByPointList(String inputPath, List pointList) { // 转 myPoint List myPoints = new ArrayList<>(); for (Point point : pointList) { myPoints.add(new MyPoint(point.getX(), point.getY())); } String url = pyToolsServiceProperties.getPyToolsService() + TifConstant.PARSE_TIF_API; Map map = new HashMap<>(2); map.put("path", inputPath); map.put("source_points", myPoints); log.info("转换地理坐标数据:{}", map); // 远程调用接口获取结果 String result = pyTifSendRequest.sendPostJsonByMap(url, map); JSONArray jsonArray = JSON.parseArray(result); // 清除原有数据进行重新赋值返回 myPoints.clear(); if (null != jsonArray) { for (int i = 0; i < jsonArray.size(); i++) { MyPoint myPoint = new MyPoint(); JSONObject jsonObject = jsonArray.getJSONObject(i); myPoint.setX(jsonObject.getDouble("x")); myPoint.setY(jsonObject.getDouble("y")); myPoints.add(myPoint); } } return myPoints; } /** * polygon 数据转点集合数据 * * @param polygonString * @return */ public List polygonStringToCoordinates(String polygonString) { List points = new ArrayList<>(); String[] coordinates = polygonString.replace("POLYGON((", "").replace("))", "").split(","); // 遍历转换 for (String coordinate : coordinates) { String[] point = coordinate.trim().split(" "); double x = Double.parseDouble(point[0]); double y = Double.parseDouble(point[1]); points.add(new Coordinate(x, y)); } return points; } /** * 处理没有 tif 画面的 * * @return */ @Override public boolean handleNotParseTif() { // 查询没有处理tif拼图斑面的任务 List odmTaskInfoVOList = baseMapper.getNotParseTifOdmList(OdmConstant.OTR_TASK_TYPES); int size = odmTaskInfoVOList.size(); //处理 int num = 0; if (size > 0) { for (OdmTaskInfoVO odmTaskInfoVO : odmTaskInfoVOList) { // 更新 String ortTbPath = tifParse(odmTaskInfoVO.getDkbh(), true, odmTaskInfoVO.getOrthoimagePath()); odmTaskInfoVO.setOrtTbPath(ortTbPath); boolean update = updateById(odmTaskInfoVO); if (update) { // 更新正射影像tif 到 附件表 updateOrthoimageAttach(odmTaskInfoVO); num++; } } } if (size == num) { return true; } return false; } /** * 定时删除odm拼图任务(保留一个月内的数据) 按天来,动态传参 * * @param day * @return */ @Override public boolean delRepOdmTaskByBefOneMon(Integer day) { // 查询一个月之外并且没有删除的任务 List list = baseMapper.getOneMonLatOdmTask(day); // 删除并返回 return updateAndRemoveWebodmTask(list); } /** * 历史odm tif 数据处理 * * @return */ @Override public Boolean hisOdmTaskTifDataHandler() { int number = 0; // 1. 查询所有历史 odm 任务列表 List list = baseMapper.getHistoryOdmTaskByTifComp(OdmConstant.OTR_TASK_TYPES); // 2. 遍历列表处理历史odm 图片 for (OdmTaskInfoVO odmTaskInfo : list) { // 通过路径获取文件 String path = odmTaskInfo.getBasePath() + odmTaskInfo.getOrthoimagePath(); File file = new File(path); // 检查文件是否存在 if (!file.exists()) { log.error("文件 {} 不存在!", path); continue; } String smallTargetName = "_small.jpeg"; File smallOutFile = new File(file.getParent(), file.getName().replace(".tif", smallTargetName)); // 检查文件是否存在 if (smallOutFile.exists()) { log.error("文件已存在!", path); continue; } // 图片压缩 log.info("开始压缩tif图片,路径:{}", path); Map map = new HashMap<>(1); map.put("ort_path", path); TifConvertResult tifConvertResult = tifConvertClient.tifConvJpeg(map); log.info("tif 图片压缩结果:{}", tifConvertResult); } return number == list.size(); } /** * 视频处理成3d 白膜测试 * * @param jobId * @return */ @Override public boolean videoToOdm(String jobId) { // 1. 获取视频地址 List mediaObjectKeyList = baseMapper.getMediaFileInfoByWaylineJobId(jobId); List picAllList = new ArrayList<>(); if (mediaObjectKeyList.size() > 0) { // 判断是否有再处理视频的任务,如果有则不处理,没有才处理,防止cpu,内存使用过多 String value = bladeRedis.get(RedisKeyConstant.ODM_VIDEO_TO_PIC_KEY); if (!Strings.isBlank(value)) { log.info("已存在处理的视频,待处理完成后执行,退出当前执行"); return false; } // 设置redis,有效时长 60 分钟 bladeRedis.setEx(RedisKeyConstant.ODM_VIDEO_TO_PIC_KEY, "1", RedisKeyConstant.ODM_VIDEO_TO_PIC_TIME); // 设置缓存 for (String objectKey : mediaObjectKeyList) { // 找到第一个/的位置 int firstSlashIndex = objectKey.indexOf('/'); if (firstSlashIndex != -1) { // 截取第一个/之后的部分 objectKey = "/" + objectKey.substring(firstSlashIndex + 1); } String videoPath = ""; // 获取运行环境信息 String activeProfile = SpringContextUtil.getActiveProfile(); // 拼接 url if (activeProfile.equals("test") || activeProfile.equals("prod")) { videoPath = minioDataConstant.getServerBasePath() + minioDataConstant.getBucket() + objectKey; } // 2. 读取视频,视频切图 try { boolean mp4ToJpgFlag = VideoConverterUtils.mp4ToJpgRestriction(videoPath, OdmConstant.FRAME_INTERVAL); boolean mp4ToCreateStrFlag = VideoConverterUtils.mp4ToCreateStr(videoPath); if (mp4ToJpgFlag && mp4ToCreateStrFlag) { // 3. 切图图片经纬度、高度赋值 String url = pyToolsServiceProperties.getPyToolsService() + TifConstant.SET_PIC_LOCATION_API; String inputPath = VideoConverterUtils.getOutputPathByInputPath(videoPath); String srtPath = inputPath + "out.srt"; String picPath = inputPath + "*.jpg"; Map map = new HashMap<>(2); map.put("pic_path", picPath); map.put("srt_path", srtPath); // 远程调用接口获取结果 String result = pyTifSendRequest.sendPostJsonByMap(url, map); boolean parseBoolean = Boolean.parseBoolean(result); // 4. 生成 webodm 任务 if (parseBoolean) { // 获取图片集合 List list = getPicListByPath(inputPath, objectKey); // 合并数据集 picAllList.addAll(list); } } } catch (IOException e) { // 清除缓存 bladeRedis.del(RedisKeyConstant.ODM_VIDEO_TO_PIC_KEY); e.printStackTrace(); } catch (InterruptedException e) { // 清除缓存 bladeRedis.del(RedisKeyConstant.ODM_VIDEO_TO_PIC_KEY); e.printStackTrace(); } } if (picAllList.size() > 0) { // 先创建任务 OdmTaskInfo odmTaskInfo = new OdmTaskInfo(); odmTaskInfo.setWaylineJobId(jobId); boolean b = saveOdmTaskInfo(odmTaskInfo); if (b) { createOdmTask(jobId, null, WaylineTypeEnum.ALB_COL.getType().intValue(), picAllList, odmTaskInfo.getProjectId()); } // 清除缓存 bladeRedis.del(RedisKeyConstant.ODM_VIDEO_TO_PIC_KEY); } } // 下面步骤需要依赖定时任务触发执行 // 5. 通过定时任务定时获取已完成的视频任务 // 6. 视频拼图任务完成后,处理地表模型数据,地表模型 tif 写入数据库 // 7. 将 obj 文件处理成白膜 // 8. 将生成的白膜转换成 3dtiles,并生成路径保存到数据库 return true; } @Override public List getTifDataByCoord(double longitude, double latitude) { return baseMapper.getTifDataByCoord(longitude, latitude); } /** * 根据经纬度获取10公里内tif数据 * * @param longitude 经度 * @param latitude 纬度 * @return */ @Override public List getAllTifDataByCoord(double longitude, double latitude) { return baseMapper.getAllTifDataByCoord(longitude, latitude); } public boolean saveOdmTaskInfo(OdmTaskInfo odmTaskInfo) { OdmTaskInfo odmTask = getOdmTaskInfo(odmTaskInfo.getWaylineJobId()); if (null == odmTask) { OdmTaskInfo jobBaseInfo = baseMapper.selectJobBaseInfo(odmTaskInfo.getWaylineJobId()); odmTaskInfo.setType(OdmTypeEnum.ALB.getType()); odmTaskInfo.setProjectId(WebOdmProjectIdEnum.TD.getProjectId()); odmTaskInfo.setAreaCode(jobBaseInfo.getAreaCode()); odmTaskInfo.setCreateUser(jobBaseInfo.getCreateUser()); odmTaskInfo.setUpdateUser(jobBaseInfo.getUpdateUser()); odmTaskInfo.setCreateDept(jobBaseInfo.getCreateDept()); odmTaskInfo.setCreateTime(new Date()); odmTaskInfo.setUpdateTime(new Date()); boolean save = this.save(odmTaskInfo); return save; } return false; } /** * 获取图片路径集合 * * @param inputDir 图片完整路径目录 * @param object_key 视频文件的 object_key * @return */ private List getPicListByPath(String inputDir, String object_key) throws IOException { List list = new ArrayList<>(); int lastSlashIndex = object_key.lastIndexOf('.'); if (lastSlashIndex != -1) { // 取 object_key 目录,取文件名作为目录 String pathBeforeLastSlash = object_key.substring(0, lastSlashIndex); Path startPath = Paths.get(inputDir); Files.walk(startPath) .filter(Files::isRegularFile) .filter(p -> p.toString().toLowerCase().endsWith(".jpg")) .forEach(p -> list.add(pathBeforeLastSlash + File.separator + p.getFileName().toString())); } return list; } /** * 查询地图位置上3d-tiles数据 * * @param dto * @return */ @Override public TreeVo getVoxGridTilesList(OdmTaskInfoQueryParam dto) { if (null==dto.getType()){ // 默认展示查询白膜 dto.setType(AttachResultTypeEnum.ALB.getType()); } if (Objects.nonNull(dto.getDateEnum())) { TimeRange timeRange = TimeRangeUtils.getTimeRange(dto.getDateEnum()); dto.setStartDate(timeRange.getStartTime()); dto.setEndDate(timeRange.getEndTime()); } // 设置区域code String areaCode = HeaderUtils.getHeader(CommonConstant.AREA_CODE).orElse(null); dto.setAreaCode(!StringUtils.isEmpty(dto.getAreaCode()) ? dto.getAreaCode() : areaCode); //所有区域数据 Object regionResultz = RedisOpsUtils.get(CacheConstant.REGION_INFO + dto.getAreaCode()); if (Objects.isNull(regionResultz)) { R regionResult = sysClient.getRegionTreeByPrentCode(dto.getAreaCode()); RedisOpsUtils.setWithExpire(CacheConstant.REGION_INFO + dto.getAreaCode(), regionResult.getData(), TimeUnit.HOURS.toSeconds(1)); regionResultz = regionResult.getData(); } // 设置区域树数据 TreeVo data = regionResultz == null ? null : (TreeVo) regionResultz; // 设置格式化后的区域编号 dto.setAreaCode(HeaderUtils.getAreaCode(dto.getAreaCode())); //根据条件查询所有区域对应的odm统计数据 // List odmTaskStatisticsBOList = baseMapper.getOdmTaskStatisticsByAreaCode(dto); //获取3d-tiles明细数据 // List odmTaskTilesList = baseMapper.getOdmTaskTilesList(dto); // 计算面积 // List attachTypeStatisticsVO = attachClient.calculateTheThreeDimensionalArea("", "", "", String.valueOf(dto.getType()), dto.getAreaCode()); // 路径处理 // tilesPathHandler(odmTaskTilesList); // 分组处理 // Map> odmTaskTilesMap = Streams.groupBy(attachTypeStatisticsVO, AttachTypeStatisticsVO::getAreaCode); //县下面的所有数量 key=区域code,value =数量 // Map map = Streams.toMap(attachTypeStatisticsVO, AttachTypeStatisticsVO::getAreaCode, AttachTypeStatisticsVO::getArea); //县区域下的所有3D-TILES数量 //根据条件查询所有区域对应的odm统计数据 List odmTaskStatisticsBOList = baseMapper.getOdmTaskStatisticsByAreaCode(dto); //获取3d-tiles明细数据 List odmTaskTilesList = baseMapper.getOdmTaskTilesList(dto); // 路径处理 // tilesPathHandler(odmTaskTilesList); List attachTypeStatisticsVO = attachClient.calculateTheThreeDimensionalArea("", "", "", String.valueOf(dto.getType()), dto.getAreaCode()); // 分组处理 Map> odmTaskTilesMap = Streams.groupBy(odmTaskTilesList, OdmTaskTilesVo::getAreaCode); //县下面的所有数量 key=区域code,value =数量 // Map map = Streams.toMap(odmTaskStatisticsBOList, OdmTaskStatisticsBO::getDictKey, OdmTaskStatisticsBO::getNum); Map map = Streams.toMap(attachTypeStatisticsVO, AttachTypeStatisticsVO::getAreaCode, AttachTypeStatisticsVO::getArea); //县区域下的所有3D-TILES数量 TreeVo treeVo = TreeUtils.buildTreeVo(data, map, odmTaskTilesMap); // 返回 return treeVo; } /** * tiles 路径处理 * * @param odmTaskTilesList */ private void tilesPathHandler(List odmTaskTilesList) { if (odmTaskTilesList.size() > 0) { // 获取运行环境信息 String activeProfile = SpringContextUtil.getActiveProfile(); // 先拼接数据 for (OdmTaskTilesVo odmTaskTilesVo : odmTaskTilesList) { if (!Strings.isBlank(odmTaskTilesVo.getTilesPath())) { // 拼接 url odmTaskTilesVo.setTilesPath(odmProperties.getDomainUrl() + odmTaskTilesVo.getTilesPath()); } } } } /** * 查询地图位置上3d-tiles数据-单机巢 * * @param dto * @return */ @Override public List getVoxGridTilesListBySinglePlayer(OdmTaskInfoQueryParam dto) { List odmTaskTilesList = new ArrayList<>(); if (Strings.isBlank(dto.getDeviceSn())) { return odmTaskTilesList; } if(null==dto.getType()){ // 没传默认给3 白膜 dto.setType(AttachResultTypeEnum.ALB.getType()); } odmTaskTilesList = baseMapper.getOdmTaskTilesList(dto); // 路径处理 // tilesPathHandler(odmTaskTilesList); return odmTaskTilesList; } /** * 历史 odm 新增相关字段数据补充 * * @return */ @Override public int historyOdmTaskDataHandler() { // 获取运行环境信息 String activeProfile = SpringContextUtil.getActiveProfile(); // 查询没有设备编号的数据,进行补充 // List list = baseMapper.getNoDeviceOdmTaskList(); List list = baseMapper.getNoGeomOdmTaskList(); for (OdmTaskInfoVO odmTaskInfo : list) { odmTaskInfo.setGeom(getGeom(odmTaskInfo.getProjectId(), odmTaskInfo.getTaskId(), activeProfile)); // 更新 baseMapper.updateOdmTaskGeomInfoById(odmTaskInfo); } return 0; } /** * 测试远程调用资源模块 * * @return */ @Override public Boolean testToResource(MultipartFile file) { Attach attach = new Attach(); attach.setName("/software/data/odm-data/docker/volumes/webodm_appmedia/_data/project/3/task/a34ad90f-bb45-4c93-b022-597c3a86d60c/assets/odm_orthophoto/odm_orthophoto.tif"); Boolean aBoolean = attachClient.saveAttachInfo(attach); try { BladeFile test = attachClient.putFile(file, "test"); } catch (IOException e) { e.printStackTrace(); } return aBoolean; } /** * 更新三维白膜信息到 附件表 * * @return */ @Override public R updateVoxGridTilesToAttach() { int count = 0; // 查询已完成的任务中没有将三维白膜 信息同步到 附件表的信息 List list = baseMapper.getNoSaveTilesAttachOdmTaskInfoList(); // 遍历将三维白膜 信息同步到附件表 for (OdmTaskInfoVO odmTaskInfo : list) { Boolean flag = update3dTilesAttach(odmTaskInfo, odmTaskInfo.getVoxGridTilesPath()); if (flag) { count++; } } return R.status(count == list.size()); } /** * 3d白膜部分生成失败补充 * * @return */ @Override public void tilesSup() { OdmTaskInfoVO odmTaskInfoVO = new OdmTaskInfoVO(); odmTaskInfoVO.setType(OdmTypeEnum.ALB.getType()); handleTiles(odmTaskInfoVO); } /** * 实景三维生成失败补充 * @return */ @Override public void liveTilesSup() { OdmTaskInfoVO odmTaskInfoVO = new OdmTaskInfoVO(); odmTaskInfoVO.setType(OdmTypeEnum.TILT.getType()); handleLiveTiles(odmTaskInfoVO); } /** * 3d白膜部分生成失败补充 * * @param odmTaskInfo * @return */ @Override public void tilesSupByParam(OdmTaskInfoVO odmTaskInfo) { handleTiles(odmTaskInfo); } /** * 补充处理白膜 * * @param odmTaskInfoVO */ public void handleTiles(OdmTaskInfoVO odmTaskInfoVO) { // 设置地形文件(处理耗时较长)时配置 redis,防止重复调用 String value = bladeRedis.get(RedisKeyConstant.ODM_TIELS_KEY); if (!Strings.isBlank(value)) { return; } // 查询状态已经完成已处理过的,然后没有生成出白膜的任务 List list = baseMapper.getNotCreateTilesTask(odmTaskInfoVO); // 获取运行环境信息 String activeProfile = SpringContextUtil.getActiveProfile(); // 遍历处理 for (OdmTaskInfoVO odmTaskInfo : list) { log.info("开始补充处理三维白膜数据,任务id:{}", odmTaskInfo.getTaskId()); // 设置缓存 bladeRedis.setEx(RedisKeyConstant.ODM_TIELS_KEY, "1", RedisKeyConstant.DEFAULT_TIME); setPointCloudTo3dTilesPath(odmTaskInfo, activeProfile); log.info("补充处理三维白膜数据结束,任务id:{},处理后白膜路径:{}", odmTaskInfo.getTaskId(), odmTaskInfo.getVoxGridTilesPath()); if (!Strings.isBlank(odmTaskInfo.getVoxGridTilesPath())) { boolean flag = false; if (Strings.isBlank(odmTaskInfo.getGeom())) { // 更新 flag = updateById(odmTaskInfo); } else { // 手动更新 int update = baseMapper.updateOdmTaskGeomInfoById(odmTaskInfo); flag = update > 0 ? true : false; } if (flag) { log.info("开始更新三维白膜数据到附件表,任务id:{}", odmTaskInfo.getTaskId()); update3dTilesAttach(odmTaskInfo, odmTaskInfo.getVoxGridTilesPath()); } } bladeRedis.del(RedisKeyConstant.ODM_TIELS_KEY); } } /** * 补充处理实景三维 * @param odmTaskInfoVO */ public void handleLiveTiles(OdmTaskInfoVO odmTaskInfoVO) { // 设置地形文件(处理耗时较长)时配置 redis,防止重复调用 String value = bladeRedis.get(RedisKeyConstant.ODM_LIVE_TIELS_KEY); if (!Strings.isBlank(value)) { return; } // 查询状态已经完成已处理过的,然后没有生成出白膜的任务 List list = baseMapper.getNotCreateTilesTask(odmTaskInfoVO); // 获取运行环境信息 String activeProfile = SpringContextUtil.getActiveProfile(); // 遍历处理 for (OdmTaskInfoVO odmTaskInfo : list) { log.info("开始补充处理实景倾斜三维数据,任务id:{}", odmTaskInfo.getTaskId()); // 设置缓存 bladeRedis.setEx(RedisKeyConstant.ODM_LIVE_TIELS_KEY, "1", RedisKeyConstant.DEFAULT_TIME); // 远程调用服务设置三维倾斜路径 setObjTo3dTilesPath(odmTaskInfo, activeProfile); log.info("补充处理实景倾斜三维数据结束,任务id:{},处理后实景倾斜三维路径:{}", odmTaskInfo.getTaskId(), odmTaskInfo.getVoxGridTilesPath()); if (!Strings.isBlank(odmTaskInfo.getTilesPath())) { boolean flag = false; if (Strings.isBlank(odmTaskInfo.getGeom())) { // 更新 flag = updateById(odmTaskInfo); } else { // 手动更新 int update = baseMapper.updateOdmTaskGeomInfoById(odmTaskInfo); flag = update > 0 ? true : false; } if (flag) { log.info("开始更新实景倾斜三维数据到附件表,任务id:{}", odmTaskInfo.getTaskId()); update3dTilesAttach(odmTaskInfo, odmTaskInfo.getTilesPath()); } } bladeRedis.del(RedisKeyConstant.ODM_LIVE_TIELS_KEY); } } @Override public TifParseResponse tifParseNew(TifParseRequest request) { String url = pyToolsServiceProperties.getPyToolsService() + TifConstant.ANALYZE_TIF_ELEVATION; Map map = convertTifParseRequestToMap(request); // 远程调用接口获取结果 log.info("开始调用 tif 解析接口,参数:{}", map); String result = pyTifSendRequest.sendPostJsonByMap(url, map); log.info("tif 解析接口返回结果:{}", result); TifParseResponse tifParseResponse = JSON.parseObject(result, TifParseResponse.class); return tifParseResponse; } // 将 TifParseRequest 对象转换为 Map private Map convertTifParseRequestToMap(TifParseRequest request) { Map map = new HashMap<>(); map.put("tif_paths", request.getTif_paths()); map.put("start_lat", request.getStart_lat()); map.put("start_lon", request.getStart_lon()); map.put("end_lat", request.getEnd_lat()); map.put("end_lon", request.getEnd_lon()); map.put("interval", request.getInterval()); return map; } @Override public List waylineFlightPathPlanning(List param) { String url = pyToolsServiceProperties.getPyToolsService() + TifConstant.WAYLINE_PLAN_API; List> map = convertPointParamsToFlightPathList(param); // 远程调用接口获取结果 log.info("开始调用网格航线接口,参数:{}", map); String result = pyTifSendRequest.planFlightPath(url, map); log.info("网格航线接口返回结果:{}", result); // 处理返回的JSON字符串,去除外层的引号并解析转义字符 if (result != null && !result.isEmpty()) { // 去除首尾的引号 if (result.startsWith("\"") && result.endsWith("\"")) { result = result.substring(1, result.length() - 1); } // 处理转义字符 result = result.replace("\\\"", "\""); } List pointParams = new ArrayList<>(); if (result == null || result.isEmpty()) { return pointParams; } try { // 解析JSON数组 JSONArray jsonArray = JSONArray.parseArray(result); for (int i = 0; i < jsonArray.size(); i++) { JSONObject jsonObject = jsonArray.getJSONObject(i); // 创建PointParam对象 PointParam pointParam = new PointParam(); pointParam.setLongitude(jsonObject.getDouble("longitude")); pointParam.setLatitude(jsonObject.getDouble("latitude")); pointParam.setExecuteHeight(jsonObject.getDouble("height")); pointParam.setIsTarget(jsonObject.getInteger("isTarget")); pointParam.setOriginalIndex(jsonObject.getInteger("originalIndex")); // 设置默认值 pointParam.setWaypointSpeed(10.0); // 默认速度 pointParam.setClickTiles(1); // 默认在模型上 pointParam.setHoverTime(0.0); // 默认悬停时间 pointParams.add(pointParam); } } catch (Exception e) { log.error("解析JSON字符串失败:{}", e.getMessage()); e.printStackTrace(); } return pointParams; } @Override public void generateGridTask() { log.info("开始生成网格任务"); String url = pyToolsServiceProperties.getPyToolsService() + TifConstant.GRID_TASK_API; // 远程调用接口获取结果 pyTifSendRequest.sendGetByMap(url); log.info("生成网格任务结束"); } /** * 保存禁飞区面数据 * * @param height 高度 * @param geoData geo 数据 * @return */ @Override public Map saveMultipolygonInfo(Double height, String geoData) { try { String url = pyToolsServiceProperties.getPyToolsService() + TifConstant.SAVE_MULTIPOLYGON_API; // area1 = "[[115.884897,28.61798],[115.881343,28.613184],[115.887823,28.612999],[115.884897,28.61798],[115.884897,28.61798]]"; // area2 = "MULTIPOLYGON(((113.0 23.0,113.0 24.0,114.0 24.0,114.0 23.0,113.0 23.0)))"; Map map = new HashMap<>(); map.put("height", String.valueOf(height)); map.put("multipolygon_wkt", geoData); // 远程调用接口获取结果 String responseJson = pyTifSendRequest.sendPostJsonByMap(url, map); String idsIfSuccess = getIdsIfSuccess(responseJson); log.info("保存禁飞区面数据成功,返回的IDs为:{}", idsIfSuccess); if (idsIfSuccess == null || idsIfSuccess.isEmpty()) { log.error("保存禁飞区面数据失败"); throw new RuntimeException("保存禁飞区面数据失败"); } Map resultMap = new HashMap<>(); resultMap.put("ids", idsIfSuccess); return resultMap; } catch (Exception e) { log.error("保存禁飞区面数据失败: {}", e); throw new RuntimeException(e); } } /** * 判断操作是否成功,如果成功则返回IDs,否则返回空字符串 * * @param responseJson 响应的JSON字符串 * @return 成功时返回IDs,失败时返回空字符串 */ public String getIdsIfSuccess(String responseJson) { try { JSONObject jsonObject = JSON.parseObject(responseJson); String status = jsonObject.getString("status"); if ("success".equals(status)) { JSONArray insertedGids = jsonObject.getJSONArray("inserted_gids"); if (insertedGids != null && !insertedGids.isEmpty()) { // 将数组转换为逗号分隔的字符串 StringBuilder idsBuilder = new StringBuilder(); for (int i = 0; i < insertedGids.size(); i++) { if (i > 0) { idsBuilder.append(","); } idsBuilder.append(insertedGids.get(i)); } return idsBuilder.toString(); } } } catch (Exception e) { log.error("解析JSON响应时出错: {}", e.getMessage()); } return ""; } /** * 将PointParam列表按相邻点分组,例如(1,2)、(2,3)、(3,4)等 * * @param request PointParam列表 * @return 分组后的列表,每组包含两个相邻的点 */ private List> groupPointParamsInPairs(List request) { List> groupedPoints = new ArrayList<>(); if (request == null || request.size() < 2) { return groupedPoints; } for (int i = 0; i < request.size() - 1; i++) { List pair = new ArrayList<>(); pair.add(request.get(i)); pair.add(request.get(i + 1)); groupedPoints.add(pair); } return groupedPoints; } /** * 将PointParam列表转换为用于飞行路径规划的Map列表 * * @param request PointParam列表 * @return 转换后的Map列表 */ private List> convertPointParamsToFlightPathList(List request) { List> flightPathList = new ArrayList<>(); // 将相邻点分组 List> groupedPoints = groupPointParamsInPairs(request); // 转换每组点为Map格式 for (List pair : groupedPoints) { if (pair.size() == 2) { PointParam startPoint = pair.get(0); PointParam endPoint = pair.get(1); Map segmentMap = new HashMap<>(); segmentMap.put("start_latitude", startPoint.getLatitude()); segmentMap.put("start_longitude", startPoint.getLongitude()); segmentMap.put("start_height", startPoint.getExecuteHeight()); segmentMap.put("start_index", startPoint.getOriginalIndex()); segmentMap.put("end_latitude", endPoint.getLatitude()); segmentMap.put("end_longitude", endPoint.getLongitude()); segmentMap.put("end_height", endPoint.getExecuteHeight()); segmentMap.put("end_index", endPoint.getOriginalIndex()); flightPathList.add(segmentMap); } } return flightPathList; } /** * 获取历史 obj 网格数据 * * @return */ @Override public int getHisObjGridData() { // 查询已经生成的obj 网格白马数据 List list = baseMapper.getHistoryOdmTaskListByTifComp(); String basePath = "D:/obj_grid/"; for (OdmTaskInfoVO odmTaskInfo : list) { // 拼接obj 网格路径 String objPath = "https://wrj.shuixiongit.com/aisky-webodm-vol"; String objBasePath = "/webodm_appmedia/_data/project/{0}/task/{1}/assets/odm_texturing/odm_textured_model_geo_conv_vox_grid.obj"; String posiBasePath = "/webodm_appmedia/_data/project/{0}/task/{1}/assets/odm_georeferencing/odm_georeferencing_model_geo.txt"; // 写入 obj 网格 String objDownUrl = objPath + MessageFormat.format(objBasePath, odmTaskInfo.getProjectId(), odmTaskInfo.getTaskId()); log.info("开始下载 obj 网格数据,任务id:{},下载路径:{}", odmTaskInfo.getTaskId(), objDownUrl); downloadFile(objDownUrl, basePath + odmTaskInfo.getTaskId() + "/" + "odm_grid.obj"); // 拼接坐标文件路径 String posiDownUrl = objPath + MessageFormat.format(posiBasePath, odmTaskInfo.getProjectId(), odmTaskInfo.getTaskId()); // 下载坐标文件 log.info("开始下载 obj 坐标文件,任务id:{},下载路径:{}", odmTaskInfo.getTaskId(), objDownUrl); String posiSavePath = basePath + odmTaskInfo.getTaskId() + "/" + "position.txt"; downloadFile(posiDownUrl, posiSavePath); // 调用接口转换坐标 setTilesPostion(posiSavePath); } return 0; } /** * 下载文件并保存到指定路径 */ private boolean downloadFile(String fileUrl, String saveFilePath) { HttpsURLConnection connection = null; InputStream in = null; FileOutputStream out = null; try { // 确保目标文件的父目录存在 File saveFile = new File(saveFilePath); if (saveFile.exists()) { return true; } File parentDir = saveFile.getParentFile(); if (parentDir != null && !parentDir.exists()) { // 递归创建所有不存在的父目录 boolean dirsCreated = parentDir.mkdirs(); if (!dirsCreated) { System.out.println("无法创建目录: " + parentDir.getAbsolutePath()); return false; } } URL url = new URL(fileUrl); connection = (HttpsURLConnection) url.openConnection(); connection.setRequestMethod("GET"); connection.setConnectTimeout(5000); connection.setReadTimeout(10000); // 检查响应码 if (connection.getResponseCode() != HttpsURLConnection.HTTP_OK) { System.out.println("下载失败,响应码: " + connection.getResponseCode()); return false; } // 读取输入流并写入文件 in = connection.getInputStream(); out = new FileOutputStream(saveFilePath); byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } return true; } catch (Exception e) { e.printStackTrace(); return false; } finally { // 关闭资源 try { if (out != null) out.close(); if (in != null) in.close(); if (connection != null) connection.disconnect(); } catch (IOException e) { e.printStackTrace(); } } } /** * 调用 api 转换白膜中心点坐标 * * @param filePath */ private void setTilesPostion(String filePath) { log.info("开始处理倾斜数据"); // obj 转体素化网格 3d-tiles String objPositionConvUrl = pyToolsServiceProperties.getPyToolsService() + TifConstant.OBJ_POSITION_CONV_API; Map map = new HashMap<>(2); map.put("file_path", filePath); // 远程调用接口获取结果 String result = pyTifSendRequest.sendPostJsonByMap(objPositionConvUrl, map); log.info("结果:{}", result); } /** * 处理历史已飞的点云提取建筑生成白膜 * * @return */ @Override public Boolean handleHistoryPointCloudTo3dtiles() { // 判断是否有再处理任务,如果有则不处理,没有才处理,防止cpu,内存使用过多 String value = bladeRedis.get(RedisKeyConstant.ODM_POINT_CLOUD_KEY); if (!Strings.isBlank(value)) { log.info("已存在处理点云"); return false; } // 设置redis,有效时长 60*2 分钟 bladeRedis.setEx(RedisKeyConstant.ODM_POINT_CLOUD_KEY, "1", RedisKeyConstant.ODM_POINT_CLOUD_TIME); // 查询已经生成的obj 网格白膜数据 List list = baseMapper.getHistoryOdmTaskListByTifComp(); // 获取运行环境信息 String activeProfile = SpringContextUtil.getActiveProfile(); // 每次取一个处理 OdmTaskInfoVO odmTaskInfo = list.get(0); // 调用接口处理 setPointCloudTo3dTilesPath(odmTaskInfo, activeProfile); // 通过路径获取文件 String path = odmProperties.getWebodmDataBasePath() + odmTaskInfo.getVoxGridTilesPath(); File file = new File(path); // 检查文件是否存在 if (!file.exists()) { log.error("三维白膜/倾斜数据文件 {} 不存在!", path); return false; } // 处理完成后更新数据 Attach attach = new Attach(); // 此处查询的id 为 附件表的id attach.setId(odmTaskInfo.getId()); // 计算设置文件大小 attach.setAttachSize(file.length()); // 设置文件路径,链接等 attach.setDomainUrl(odmProperties.getDomainUrl()); attach.setLink(odmProperties.getDomainUrl() + odmTaskInfo.getVoxGridTilesPath()); attach.setName(path); attach.setOriginalName(path); // 设置类型-三维白膜 // 测试用,临时用,用后需要手动数据库修改 attach.setResultType(null); log.info("发起远程调用保存三维白膜/倾斜附件信息,tif 路径:{}", path); Boolean isSaveAttach = attachClient.saveAttachInfo(attach); // 结束后删除redis bladeRedis.del(RedisKeyConstant.ODM_POINT_CLOUD_KEY); return isSaveAttach; } /** * 获取历史 点云数据 * * @return */ @Override public int getHisPointCloudData() { // 查询已经生成的obj 网格白马数据 List list = baseMapper.getHistoryOdmTaskListByTifComp(); String basePath = "E:\\temp\\webodm\\pointcloud\\"; for (OdmTaskInfoVO odmTaskInfo : list) { // 拼接点云路径 String pointCloudPath = "https://wrj.shuixiongit.com/aisky-webodm-vol"; String pointCloudBasePath = "/webodm_appmedia/_data/project/{0}/task/{1}/assets/odm_georeferencing/odm_georeferenced_model.laz"; // 写入 obj 网格 String pointCloudDownUrl = pointCloudPath + MessageFormat.format(pointCloudBasePath, odmTaskInfo.getProjectId(), odmTaskInfo.getTaskId()); log.info("开始下载点云laz数据,任务id:{},下载路径:{}", odmTaskInfo.getTaskId(), pointCloudDownUrl); downloadFile(pointCloudDownUrl, basePath + odmTaskInfo.getTaskId() + "/" + "odm_georeferencing_model.laz"); } return 0; } @Override public Boolean deleteGrid(String ids) { // 检查参数是否为空 if (StringUtils.isEmpty(ids)) { log.warn("传入的ids参数为空,无法执行删除操作"); return false; } try { String url = getTifServiceBaseUrl() + TifConstant.DELETE_DELETE_GRID_API; String[] split = ids.split(","); Map map = new HashMap<>(); map.put("gid_array", split); // 远程调用接口获取结果 String responseJson = pyTifSendRequest.sendPostJsonByMap(url, map); // 解析响应并安全获取状态值 if (StringUtils.isEmpty(responseJson)) { log.warn("从远程接口接收到空响应"); return false; } JSONObject jsonObject = JSON.parseObject(responseJson); Boolean status = jsonObject != null ? jsonObject.getBoolean("status") : null; return status != null ? status : false; } catch (Exception e) { log.error("删除网格数据时发生异常: ", e); return false; } } /** * 将机场地形高度初始化参数缓存到Redis队列中 * * @param centerLon 经度 * @param centerLat 纬度 * @param dockSn 机场序列号 * @return 缓存结果 1-成功 0-失败 */ @Override public Integer cacheAirportTerrainHeightParams(String centerLon, String centerLat, String dockSn) { try { // 检查机场参数是否已缓存 if (bladeRedis.exists(RedisKeyConstant.AIRPORT + dockSn)) { return 0; } // 设置机场参数缓存有效期 100 分钟 bladeRedis.setEx(RedisKeyConstant.AIRPORT + dockSn, dockSn, 100 * 60L); // 构造缓存key String cacheKey = RedisKeyConstant.AIRPORT_TERRAIN_HEIGHT; // 构造缓存数据 Map cacheData = new HashMap<>(); cacheData.put("center_lon", centerLon); cacheData.put("center_lat", centerLat); cacheData.put("dock_sn", dockSn); // 将数据存储到Redis列表中 bladeRedis.lPush(cacheKey, JSON.toJSONString(cacheData)); log.info("机场地形高度参数已缓存到Redis队列,key: {}", cacheKey); return 1; } catch (Exception e) { log.error("缓存机场地形高度参数到Redis时发生异常: ", e); } return 0; } /** * 从Redis队列中获取并移除第一个机场地形高度参数 * * @return 机场地形高度参数 */ @Override public Map getAirportTerrainHeightParams() { try { // 获取队列key String cacheKey = RedisKeyConstant.AIRPORT_TERRAIN_HEIGHT; // 从队列左侧获取并移除第一个元素 Object dataObj = bladeRedis.lPop(cacheKey); if (dataObj != null) { log.info("成功获取并移除机场地形高度参数,key: {}", JSON.toJSON(dataObj)); // 如果是字符串,则尝试解析为JSON String dataStr = JSON.toJSONString(dataObj); JSONArray array = JSON.parseArray(dataStr); String string = array.get(0).toString(); // 移除可能的引号和转义字符 Map data = JSON.parseObject(string, Map.class); log.info("成功获取并移除机场地形高度参数,key: {}", cacheKey); return data; } else { log.info("机场地形高度参数队列为空,key: {}", cacheKey); } } catch (Exception e) { log.error("获取并移除机场地形高度参数时发生异常: ", e); } return new HashMap<>(); } /** * 初始化机场高程 * * @param centerLon * @param centerLat * @param dockSn * @return */ @Override public Boolean initAirportTerrainHeight(String centerLon, String centerLat, String dockSn) { try { String url = getTifServiceBaseUrl() + TifConstant.INIT_AIRPORT_TERRAIN_API; String areaCodePrefix = getDistrictCodeByLocation(Double.parseDouble(centerLon), Double.parseDouble(centerLat)); log.info("初始化机场高程,区域前缀:{}", areaCodePrefix); String demInfoPath = baseMapper.getDemInfo(areaCodePrefix); if (demInfoPath == null) { log.warn("未找到匹配的DEM信息,请检查DEM信息表"); return false; } Map map = new HashMap<>(); map.put("center_lon", centerLon); map.put("center_lat", centerLat); map.put("tif_paths", demInfoPath); map.put("dock_sn", dockSn); // 远程调用接口获取结果 String responseJson = pyTifSendRequest.sendPostJsonByMap(url, map); // 解析响应并安全获取状态值 if (StringUtils.isEmpty(responseJson)) { log.warn("从远程接口接收到空响应"); return false; } JSONObject jsonObject = JSON.parseObject(responseJson); Boolean status = jsonObject != null ? jsonObject.getBoolean("status") : null; return status != null ? status : false; } catch (Exception e) { log.error("初始化机场高程发生异常: ", e); return false; } } /** * 获取航点最大高度 * * @param mapLatLng * @return */ @Override public Integer getWaylineMaxTerrainHeight(List mapLatLng) { try { String url = getTifServiceBaseUrl() + TifConstant.GET_AIRLINE_MAX_HEIGHT_API; Map map = new HashMap<>(); map.put("mapLatLng", mapLatLng); // 远程调用接口获取结果 String responseJson = pyTifSendRequest.sendPostJsonByMap(url, map); // 解析响应并安全获取状态值 if (StringUtils.isEmpty(responseJson)) { log.warn("从远程接口接收到空响应"); return 0; } JSONObject jsonObject = JSON.parseObject(responseJson); Boolean status = jsonObject != null ? jsonObject.getBoolean("status") : null; return status != null ? jsonObject.getInteger("max_height") : 0; } catch (Exception e) { log.error("获取航点最大高度发生异常: ", e); return 0; } } /** * 删除机场地形数据 * * @param dockSn * @return */ @Override public Integer deleteAirportTerrainHeight(String dockSn) { try { String url = getTifServiceBaseUrl() + TifConstant.DELETE_AIRPORT_TERRAIN_API; Map map = new HashMap<>(); map.put("dock_sn", dockSn); // 远程调用接口获取结果 String responseJson = pyTifSendRequest.sendPostJsonByMap(url, map); // 解析响应并安全获取状态值 if (StringUtils.isEmpty(responseJson)) { log.warn("从远程接口接收到空响应"); return 0; } JSONObject jsonObject = JSON.parseObject(responseJson); Boolean status = jsonObject != null ? jsonObject.getBoolean("status") : null; return status != null ? jsonObject.getInteger("total_count") : 0; } catch (Exception e) { log.error("删除机场地形数据发生异常: ", e); return 0; } } /** * 根据环境获取对应的TIF服务基础URL * * @return */ private String getTifServiceBaseUrl() { String baseUrl = pyToolsServiceProperties.getPyToolsService(); return baseUrl; } /** * 根据经纬度获取行政区划信息 * * @param longitude 经度 * @param latitude 纬度 * @return 行政区划信息 */ public Map getDistrictInfoByLocation(double longitude, double latitude) { // 使用高德地图工具类获取行政区划信息 String location = longitude + "," + latitude; JSONObject result = AmapUtils.searchByLatLng(location); Map districtInfo = new HashMap<>(); if (result != null && "1".equals(result.getString("status"))) { JSONObject regeocode = result.getJSONObject("regeocode"); if (regeocode != null) { JSONObject addressComponent = regeocode.getJSONObject("addressComponent"); if (addressComponent != null) { districtInfo.put("province", addressComponent.getString("province")); districtInfo.put("city", addressComponent.getString("city")); districtInfo.put("district", addressComponent.getString("district")); districtInfo.put("adcode", addressComponent.getString("adcode")); districtInfo.put("township", addressComponent.getString("township")); districtInfo.put("formatted_address", regeocode.getString("formatted_address")); } } } return districtInfo; } /** * 根据经纬度获取行政区划编码 * * @param longitude 经度 * @param latitude 纬度 * @return 行政区划编码 */ public String getDistrictCodeByLocation(double longitude, double latitude) { Map districtInfo = getDistrictInfoByLocation(longitude, latitude); String adcode = districtInfo.get("adcode"); if (adcode != null && adcode.length() >= 3) { return adcode.substring(0, 2); } return adcode; } }