[add] 新增无人机模块后端项目
[refactor] 重构后端项目
This commit is contained in:
		| @ -0,0 +1,350 @@ | ||||
| package com.ruoyi.wayline.controller; | ||||
|  | ||||
| import com.ruoyi.system.domain.FlightPaths; | ||||
| import com.ruoyi.system.domain.ManageDeviceDictionary; | ||||
| import com.ruoyi.system.service.IFlightPathsService; | ||||
| import com.ruoyi.system.service.IManageDeviceDictionaryService; | ||||
| import com.ruoyi.wayline.domain.AddWaypointVo; | ||||
| import com.ruoyi.wayline.domain.RenameVo; | ||||
| import com.ruoyi.wayline.domain.WaypointData; | ||||
| import com.ruoyi.wayline.utils.HttpResult; | ||||
| import com.ruoyi.wayline.utils.MD5Util; | ||||
| import com.ruoyi.wayline.waypoint.WayPointKmlProcessorModify; | ||||
| import com.ruoyi.wayline.waypoint.WayPointWpmlProcessorModify; | ||||
| import io.swagger.annotations.Api; | ||||
| import io.swagger.annotations.ApiOperation; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.core.env.Environment; | ||||
| import org.springframework.http.*; | ||||
| import org.springframework.web.bind.annotation.*; | ||||
| import org.springframework.web.multipart.MultipartFile; | ||||
|  | ||||
| import javax.annotation.Resource; | ||||
| import java.io.*; | ||||
| import java.net.InetAddress; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.nio.file.*; | ||||
| import java.security.MessageDigest; | ||||
| import java.security.NoSuchAlgorithmException; | ||||
| import java.util.*; | ||||
| import java.util.zip.*; | ||||
|  | ||||
| @Slf4j | ||||
| @RestController | ||||
| @Api(tags = "航线文件管理") | ||||
| public class WaypointController { | ||||
|  | ||||
|     @Resource | ||||
|     private IFlightPathsService flightPathsService; | ||||
|     @Resource | ||||
|     private IManageDeviceDictionaryService manageDeviceDictionaryService; | ||||
|     @Resource | ||||
|     private Environment environment; | ||||
|  | ||||
|     private static final String UPLOAD_DIR = System.getProperty("user.dir") + "/upload"; | ||||
|  | ||||
|     // 工具方法:使用 flag 和 fileName 组合计算 MD5 值 | ||||
|     private String calculateMD5(String flag, String fileName) { | ||||
|         String input = flag + fileName; | ||||
|         MessageDigest md = null; | ||||
|         try { | ||||
|             md = MessageDigest.getInstance("MD5"); | ||||
|         } catch (NoSuchAlgorithmException e) { | ||||
|             throw new RuntimeException(e); | ||||
|         } | ||||
|         byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8)); | ||||
|         StringBuilder sb = new StringBuilder(); | ||||
|         for (byte b : digest) sb.append(String.format("%02x", b)); | ||||
|         log.info("MD5: {}", sb.toString()); | ||||
|         return sb.toString(); | ||||
|     } | ||||
|  | ||||
|     // 工具方法:检查文件是否已存在并返回错误信息 | ||||
|     private ResponseEntity<HttpResult> checkFileExistence(String fileName, String flag) { | ||||
|         FlightPaths flightPaths = new FlightPaths(); | ||||
|         flightPaths.setFileName(fileName); | ||||
|         flightPaths.setFlag(flag); | ||||
|         List<FlightPaths> pathsList = flightPathsService.selectByCondition(flightPaths); | ||||
|         if (pathsList != null && !pathsList.isEmpty()) { | ||||
|             HttpResult httpResult = HttpResult.error(); | ||||
|             httpResult.setMessage("文件名重复,请重新命名"); | ||||
|             return ResponseEntity.ok(httpResult); | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     private boolean checkFileExistenceWithBool(String fileName, String flag) { | ||||
|         FlightPaths flightPaths = new FlightPaths(); | ||||
|         flightPaths.setFileName(fileName); | ||||
|         flightPaths.setFlag(flag); | ||||
|         List<FlightPaths> pathsList = flightPathsService.selectByCondition(flightPaths); | ||||
|         return pathsList != null && !pathsList.isEmpty(); | ||||
|     } | ||||
|  | ||||
|     @ApiOperation("导入航线文件") | ||||
|     @PostMapping("/dj/router/import") | ||||
|     public ResponseEntity<HttpResult> importWaypoint(@RequestParam("file") MultipartFile file, @RequestParam("flag") String flag) { | ||||
|         if (file.isEmpty()) { | ||||
|             HttpResult httpResult = HttpResult.error(); | ||||
|             httpResult.setMessage("文件为空"); | ||||
|             return ResponseEntity.ok(httpResult); | ||||
|         } | ||||
|  | ||||
|         try { | ||||
|             String fileName = file.getOriginalFilename(); | ||||
|             ResponseEntity<HttpResult> existenceCheck = checkFileExistence(fileName, flag); | ||||
|             if (existenceCheck != null) return existenceCheck; | ||||
|  | ||||
|             // 创建上传目录 | ||||
|             Path uploadPath = Paths.get(UPLOAD_DIR); | ||||
|             Files.createDirectories(uploadPath); | ||||
|  | ||||
|             byte[] fileBytes = file.getBytes(); | ||||
|             String md5 = calculateMD5(flag, fileName);  // 使用 flag 和 fileName 组合生成 MD5 | ||||
|             String md5FileNameWithExtension = md5 + ".kmz"; | ||||
|             Path serverPath = uploadPath.resolve(md5FileNameWithExtension); | ||||
|  | ||||
|             Files.write(serverPath, fileBytes); | ||||
|  | ||||
|             String downloadLink = buildDownloadLink(md5FileNameWithExtension); | ||||
|             saveFlightPath(fileName, flag, md5, downloadLink, true); | ||||
|  | ||||
|             HttpResult httpResult = HttpResult.ok(); | ||||
|             httpResult.setMessage("文件导入成功"); | ||||
|             return ResponseEntity.ok(httpResult); | ||||
|         } catch (Exception e) { | ||||
|             log.error("文件保存失败: {}", e.getMessage()); | ||||
|             HttpResult httpResult = HttpResult.error(); | ||||
|             httpResult.setMessage("保存失败"); | ||||
|             return ResponseEntity.ok(httpResult); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void saveFlightPath(String fileName, String flag, String md5, String fileUrl, boolean isImport) { | ||||
|         FlightPaths flightPath = new FlightPaths(); | ||||
|         flightPath.setFileName(fileName); | ||||
|         flightPath.setFlag(flag); | ||||
|         flightPath.setFileMd5(md5); | ||||
|         flightPath.setFileUrl(fileUrl); | ||||
|         flightPath.setIsImport(isImport ? "true" : "false"); | ||||
|         flightPath.setCreatedAt(new Date()); | ||||
|         flightPath.setUpdatedAt(new Date()); | ||||
|         flightPathsService.insertFlightPaths(flightPath); | ||||
|     } | ||||
|  | ||||
|     private String buildDownloadLink(String md5FileNameWithExtension) throws IOException { | ||||
|         String ip = environment.getProperty("server.host", InetAddress.getLocalHost().getHostAddress()); | ||||
|         int port = environment.getProperty("server.port", Integer.class, 8080); | ||||
|         return "http://" + ip + ":" + port + "/dj/router/download/" + md5FileNameWithExtension; | ||||
|     } | ||||
|  | ||||
|     @ApiOperation("新增航线文件") | ||||
|     @PostMapping("/dj/router/add") | ||||
|     public ResponseEntity<HttpResult> addWaypoint(@RequestBody AddWaypointVo addWaypointVo) { | ||||
|         ResponseEntity<HttpResult> existenceCheck = checkFileExistence(addWaypointVo.getFilename(), addWaypointVo.getFlag()); | ||||
|         if (existenceCheck != null) return existenceCheck; | ||||
|  | ||||
|         FlightPaths flightPaths = new FlightPaths(); | ||||
|         flightPaths.setFileName(addWaypointVo.getFilename()); | ||||
|         flightPaths.setFlag(addWaypointVo.getFlag()); | ||||
|         flightPaths.setIsImport("false"); | ||||
|         flightPaths.setGlobalPointHeight(addWaypointVo.getGlobalPointHeight()); | ||||
|         flightPaths.setCreatedAt(new Date()); | ||||
|         flightPaths.setUpdatedAt(new Date()); | ||||
|  | ||||
|         // 获取飞机类型 | ||||
|         String[] split = addWaypointVo.getRemark().split("-"); | ||||
|         ManageDeviceDictionary manageDeviceDictionary = manageDeviceDictionaryService | ||||
|                 .selectManageDeviceDictionaryByCondition(split[0], split[1], split[2]); | ||||
|         flightPaths.setDeviceType(manageDeviceDictionary.getDeviceName()); | ||||
|  | ||||
|         flightPathsService.insertFlightPaths(flightPaths); | ||||
|         return ResponseEntity.ok(HttpResult.ok()); | ||||
|     } | ||||
|  | ||||
|     @ApiOperation("根据参数追数据到加航线文件") | ||||
|     @PostMapping("/dj/router/waypoint") | ||||
|     public ResponseEntity<HttpResult> downloadWaypoint(@RequestBody WaypointData dataRequest) { | ||||
|         HttpResult httpResult = HttpResult.ok(); | ||||
|         Map<String, Object> map = new HashMap<>(); | ||||
|         String fileName = flightPathsService.selectFlightPathsById(Long.valueOf(dataRequest.getId())).getFileName(); | ||||
|         Path uploadPath = Paths.get(UPLOAD_DIR); | ||||
|  | ||||
|         try { | ||||
|             Files.createDirectories(uploadPath); | ||||
|             String md5FileName = calculateMD5(dataRequest.getFlag(), fileName);  // 使用 flag 和 fileName 组合生成 MD5 | ||||
|             String md5FileNameWithExtension = md5FileName + ".kmz"; | ||||
|             Path serverPath = uploadPath.resolve(md5FileNameWithExtension); | ||||
|  | ||||
|             // 日志:显示生成的文件路径 | ||||
|             log.info("下载航线文件路径: {}", serverPath); | ||||
|  | ||||
|             // 创建并压缩文件 | ||||
|             try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); | ||||
|                  ZipOutputStream zipOut = new ZipOutputStream(byteArrayOutputStream)) { | ||||
|  | ||||
|                 byte[] file1 = new WayPointKmlProcessorModify().processKml(dataRequest, new Random().nextInt(9000) + 1000); | ||||
|                 zipOut.putNextEntry(new ZipEntry("wpmz/template.kml")); | ||||
|                 zipOut.write(file1); | ||||
|                 zipOut.closeEntry(); | ||||
|  | ||||
|                 byte[] file2 = new WayPointWpmlProcessorModify().processWpml(dataRequest, new Random().nextInt(9000) + 1000); | ||||
|                 zipOut.putNextEntry(new ZipEntry("wpmz/waylines.wpml")); | ||||
|                 zipOut.write(file2); | ||||
|                 zipOut.closeEntry(); | ||||
|  | ||||
|                 zipOut.finish(); | ||||
|                 Files.write(serverPath, byteArrayOutputStream.toByteArray()); | ||||
|             } | ||||
|  | ||||
|             // 构建下载链接并更新数据库 | ||||
|             String downloadLink = buildDownloadLink(md5FileNameWithExtension); | ||||
|             map.put("downloadLink", downloadLink); | ||||
|             map.put("flag", true); | ||||
|             map.put("signature", md5FileName); | ||||
|             httpResult.setData(map); | ||||
|             httpResult.setMessage("生成航线文件并转存至服务器成功!"); | ||||
|  | ||||
|             // 更新数据库中的航线文件记录,保存生成的MD5值 | ||||
|             FlightPaths flightPaths = new FlightPaths(); | ||||
|             flightPaths.setId(Long.valueOf(dataRequest.getId())); | ||||
|             flightPaths.setFileName(fileName); | ||||
|             flightPaths.setFileUrl(downloadLink); | ||||
|             flightPaths.setFileMd5(md5FileName);  // 存储 MD5 值 | ||||
|             flightPaths.setPhotoNum(Long.valueOf(dataRequest.getPhotoNum())); | ||||
|             flightPaths.setEstimateTime(dataRequest.getEstimateTime()); | ||||
|             flightPaths.setType(dataRequest.getType()); | ||||
|             flightPaths.setWaylineLen(dataRequest.getWaylineLen()); | ||||
|             flightPaths.setPoints(dataRequest.getPoints()); | ||||
|             flightPaths.setUpdatedAt(new Date()); | ||||
|             flightPathsService.updateFlightPaths(flightPaths); | ||||
|  | ||||
|         } catch (Exception e) { | ||||
|             httpResult = HttpResult.error(); | ||||
|             httpResult.setMessage("生成航线文件并转存至服务器失败,原因:" + e.getMessage()); | ||||
|         } | ||||
|         return ResponseEntity.ok(httpResult); | ||||
|     } | ||||
|  | ||||
|     @ApiOperation("下载航线文件") | ||||
|     @GetMapping("/dj/router/download/{fileName}") | ||||
|     public ResponseEntity<byte[]> downloadFile(@PathVariable String fileName) { | ||||
|         Path filePath = Paths.get(UPLOAD_DIR, fileName); | ||||
|         if (!Files.exists(filePath)) { | ||||
|             return ResponseEntity.notFound().build(); | ||||
|         } | ||||
|         try { | ||||
|             byte[] fileContent = Files.readAllBytes(filePath); | ||||
|             HttpHeaders headers = new HttpHeaders(); | ||||
|             headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); | ||||
|             headers.setContentDisposition(ContentDisposition.builder("attachment").filename(fileName, StandardCharsets.UTF_8).build()); | ||||
|             return ResponseEntity.ok().headers(headers).body(fileContent); | ||||
|         } catch (IOException e) { | ||||
|             log.error("文件下载失败, 原因: {}", e.getMessage()); | ||||
|             return ResponseEntity.status(500).build(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @ApiOperation("复制航线文件") | ||||
|     @PostMapping("/dj/router/copy/{id}") | ||||
|     public ResponseEntity<HttpResult> copyWaypoint(@PathVariable Long id) { | ||||
|         FlightPaths originalFlightPaths = flightPathsService.selectFlightPathsById(id); | ||||
|         if (originalFlightPaths == null) return ResponseEntity.notFound().build(); | ||||
|  | ||||
|         String originalFileName = originalFlightPaths.getFileName(); | ||||
|         String originalMd5 = originalFlightPaths.getFileMd5(); | ||||
|         Path originalFilePath = Paths.get(UPLOAD_DIR, originalMd5 + ".kmz"); | ||||
|  | ||||
|         // 日志:显示待复制的原始文件路径 | ||||
|         log.info("复制航线文件路径: {}", originalFilePath); | ||||
|  | ||||
|         if (!Files.exists(originalFilePath)) { | ||||
|             HttpResult httpResult = HttpResult.error(); | ||||
|             httpResult.setMessage("请先生成航线文件再执行复制"); | ||||
|             return ResponseEntity.ok(httpResult); | ||||
|         } | ||||
|  | ||||
|         String newFileName = originalFileName + "(1)"; | ||||
|  | ||||
|         // 查询文件是否已存在 | ||||
|         if (checkFileExistenceWithBool(newFileName, originalFlightPaths.getFlag())) { | ||||
|             HttpResult httpResult = HttpResult.error(); | ||||
|             httpResult.setMessage("航线文件" + newFileName + "已存在"); | ||||
|             return ResponseEntity.ok(httpResult); | ||||
|         } | ||||
|  | ||||
|         String newMd5 = calculateMD5(originalFlightPaths.getFlag(), newFileName); | ||||
|         Path newFilePath = Paths.get(UPLOAD_DIR, newMd5 + ".kmz"); | ||||
|  | ||||
|         try { | ||||
|             Files.copy(originalFilePath, newFilePath); | ||||
|             FlightPaths newFlightPaths = new FlightPaths(); | ||||
|             newFlightPaths.setFileName(newFileName); | ||||
|             newFlightPaths.setFileUrl(buildDownloadLink(newMd5 + ".kmz")); | ||||
|             newFlightPaths.setFileMd5(newMd5); | ||||
|             newFlightPaths.setFlag(originalFlightPaths.getFlag()); | ||||
|             newFlightPaths.setIsImport(originalFlightPaths.getIsImport()); | ||||
|             newFlightPaths.setPoints(originalFlightPaths.getPoints()); | ||||
|             newFlightPaths.setDeviceType(originalFlightPaths.getDeviceType()); | ||||
|             newFlightPaths.setPhotoNum(originalFlightPaths.getPhotoNum()); | ||||
|             newFlightPaths.setEstimateTime(originalFlightPaths.getEstimateTime()); | ||||
|             newFlightPaths.setType(originalFlightPaths.getType()); | ||||
|             newFlightPaths.setWaylineLen(originalFlightPaths.getWaylineLen()); | ||||
|             newFlightPaths.setRemark(originalFlightPaths.getRemark()); | ||||
|  | ||||
|             newFlightPaths.setCreatedAt(new Date()); | ||||
|             newFlightPaths.setUpdatedAt(new Date()); | ||||
|  | ||||
|             flightPathsService.insertFlightPaths(newFlightPaths); | ||||
|  | ||||
|             HttpResult httpResult = HttpResult.ok(); | ||||
|             httpResult.setMessage("复制航线文件成功!"); | ||||
|             return ResponseEntity.ok(httpResult); | ||||
|         } catch (IOException e) { | ||||
|             HttpResult httpResult = HttpResult.error(); | ||||
|             httpResult.setMessage("复制航线文件失败, 原因:" + e.getMessage()); | ||||
|             return ResponseEntity.ok(httpResult); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @ApiOperation("重命名航线文件") | ||||
|     @PostMapping("/dj/router/rename") | ||||
|     public ResponseEntity<HttpResult> renameWaypoint(@RequestBody RenameVo renameVo) { | ||||
|         FlightPaths flightPaths = flightPathsService.selectFlightPathsById(renameVo.getId()); | ||||
|         if (flightPaths == null) return ResponseEntity.notFound().build(); | ||||
|  | ||||
|         // 如果文件名称和现在已有的名称相同则不进行任何处理 | ||||
|         if (flightPaths.getFileName().equals(renameVo.getNewFileName())) { | ||||
|             HttpResult httpResult = HttpResult.ok(); | ||||
|             return ResponseEntity.ok(httpResult); | ||||
|         } | ||||
|         if (checkFileExistenceWithBool(renameVo.getNewFileName(), flightPaths.getFlag())) { | ||||
|             HttpResult httpResult = HttpResult.error(); | ||||
|             httpResult.setMessage("航线文件" + renameVo.getNewFileName() + "已存在,请重新命名"); | ||||
|             return ResponseEntity.ok(httpResult); | ||||
|         } | ||||
|  | ||||
|         String newMd5 = calculateMD5(flightPaths.getFlag(), renameVo.getNewFileName()); | ||||
|         String originalMd5 = flightPaths.getFileMd5(); | ||||
|         Path originalFilePath = Paths.get(UPLOAD_DIR, originalMd5 + ".kmz"); | ||||
|         Path newFilePath = Paths.get(UPLOAD_DIR, newMd5 + ".kmz"); | ||||
|  | ||||
|         try { | ||||
|             Files.move(originalFilePath, newFilePath); | ||||
|             flightPaths.setFileName(renameVo.getNewFileName()); | ||||
|             flightPaths.setFileUrl(buildDownloadLink(newMd5 + ".kmz")); | ||||
|             flightPaths.setFileMd5(newMd5); | ||||
|             flightPaths.setGlobalPointHeight(flightPaths.getGlobalPointHeight()); | ||||
|             flightPaths.setUpdatedAt(new Date()); | ||||
|             flightPathsService.updateFlightPaths(flightPaths); | ||||
|  | ||||
|             HttpResult httpResult = HttpResult.ok(); | ||||
|             httpResult.setMessage("重命名航线文件成功!"); | ||||
|             return ResponseEntity.ok(httpResult); | ||||
|         } catch (IOException e) { | ||||
|             HttpResult httpResult = HttpResult.error(); | ||||
|             httpResult.setMessage("重命名航线文件失败, 原因: " + e.getMessage()); | ||||
|             return ResponseEntity.ok(httpResult); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,20 @@ | ||||
| package com.ruoyi.wayline.domain; | ||||
|  | ||||
| import io.swagger.annotations.ApiModel; | ||||
| import io.swagger.annotations.ApiModelProperty; | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.util.Map; | ||||
|  | ||||
| /** | ||||
|  * @auther 周志雄 | ||||
|  * @date 2024/8/30 16:36 | ||||
|  */ | ||||
| @Data | ||||
| @ApiModel("航点动作") | ||||
| public class Action { | ||||
|     @ApiModelProperty(value = "动作类型") | ||||
|     private String type; | ||||
|     @ApiModelProperty(value = "动作参数") | ||||
|     private Map<String, String> params; | ||||
| } | ||||
| @ -0,0 +1,13 @@ | ||||
| package com.ruoyi.wayline.domain; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| /** | ||||
|  * @auther 周志雄 | ||||
|  * @date 2024/9/11 14:24 | ||||
|  */ | ||||
| @Data | ||||
| public class ActionTrigger { | ||||
|     private String actionTriggerType; | ||||
|     private String actionTriggerParam; | ||||
| } | ||||
| @ -0,0 +1,22 @@ | ||||
| package com.ruoyi.wayline.domain; | ||||
|  | ||||
| import io.swagger.annotations.ApiModel; | ||||
| import io.swagger.annotations.ApiModelProperty; | ||||
| import lombok.Data; | ||||
|  | ||||
| /** | ||||
|  * @auther 周志雄 | ||||
|  * @date 2024/9/6 9:53 | ||||
|  */ | ||||
| @Data | ||||
| @ApiModel("航点文件创建参数") | ||||
| public class AddWaypointVo { | ||||
|     @ApiModelProperty(value = "文件名称") | ||||
|     private String filename; | ||||
|     @ApiModelProperty(value = "标识、例如 0-67-1") | ||||
|     private String remark; | ||||
|     @ApiModelProperty(value = "画航线的全局高度") | ||||
|     private Double globalPointHeight; | ||||
|     @ApiModelProperty(value = "标识") | ||||
|     private String flag; | ||||
| } | ||||
| @ -0,0 +1,18 @@ | ||||
| package com.ruoyi.wayline.domain; | ||||
|  | ||||
| import io.swagger.annotations.ApiModel; | ||||
| import io.swagger.annotations.ApiModelProperty; | ||||
| import lombok.Data; | ||||
|  | ||||
| /** | ||||
|  * @Author zhouzhixiong | ||||
|  * @Date 2023/12/2 16:39 | ||||
|  */ | ||||
| @Data | ||||
| @ApiModel("飞行器信息") | ||||
| public class DroneInfo { | ||||
|     @ApiModelProperty(value = "飞行器枚举值") | ||||
|     private int droneEnumValue; | ||||
|     @ApiModelProperty(value = "飞行器子枚举值") | ||||
|     private int droneSubEnumValue; | ||||
| } | ||||
| @ -0,0 +1,20 @@ | ||||
| package com.ruoyi.wayline.domain; | ||||
|  | ||||
| import io.swagger.annotations.ApiModel; | ||||
| import io.swagger.annotations.ApiModelProperty; | ||||
| import lombok.Data; | ||||
|  | ||||
| /** | ||||
|  * @Author zhouzhixiong | ||||
|  * @Date 2023/12/2 16:39 | ||||
|  */ | ||||
| @Data | ||||
| @ApiModel("负载信息") | ||||
| public class PayloadInfo { | ||||
|     @ApiModelProperty(value = "负载枚举值") | ||||
|     private int payloadEnumValue; | ||||
|     @ApiModelProperty(value = "负载子枚举值") | ||||
|     private int payloadSubEnumValue; | ||||
|     @ApiModelProperty(value = "负载位置索引") | ||||
|     private int payloadPositionIndex; | ||||
| } | ||||
| @ -0,0 +1,24 @@ | ||||
| package com.ruoyi.wayline.domain; | ||||
|  | ||||
| import io.swagger.annotations.ApiModel; | ||||
| import io.swagger.annotations.ApiModelProperty; | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * @Author zhouzhixiong | ||||
|  * @Date 2023/12/4 11:45 | ||||
|  */ | ||||
| @Data | ||||
| @ApiModel("航点信息") | ||||
| public class Placemark { | ||||
|     @ApiModelProperty(value = "经度") | ||||
|     private String longitude; | ||||
|     @ApiModelProperty(value = "纬度") | ||||
|     private String latitude; | ||||
|     @ApiModelProperty(value = "高度") | ||||
|     private String altitude; | ||||
|     @ApiModelProperty(value = "每个航点的动作列表") | ||||
|     private List<Action> actions; | ||||
| } | ||||
| @ -0,0 +1,13 @@ | ||||
| package com.ruoyi.wayline.domain; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| /** | ||||
|  * @auther 周志雄 | ||||
|  * @date 2024/9/6 11:26 | ||||
|  */ | ||||
| @Data | ||||
| public class RenameVo { | ||||
|     private Long id; | ||||
|     private String newFileName; | ||||
| } | ||||
| @ -0,0 +1,50 @@ | ||||
| package com.ruoyi.wayline.domain; | ||||
|  | ||||
| import io.swagger.annotations.ApiModel; | ||||
| import io.swagger.annotations.ApiModelProperty; | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * @auther 周志雄 | ||||
|  * @date 2024/7/12 14:30 | ||||
|  */ | ||||
| @Data | ||||
| @ApiModel("航点文件数据") | ||||
| public class WaypointData { | ||||
|     @ApiModelProperty(value = "航线ID") | ||||
|     private String id; | ||||
|     @ApiModelProperty(value = "起飞安全高度") | ||||
|     private int takeOffSecurityHeight; | ||||
|     @ApiModelProperty(value = "参考起飞点") | ||||
|     private String takeOffRefPoint; | ||||
|     @ApiModelProperty(value = "全局航线过渡速度") | ||||
|     private int globalTransitionalSpeed; | ||||
|     @ApiModelProperty(value = "飞行器信息") | ||||
|     private DroneInfo droneInfo; | ||||
|     @ApiModelProperty(value = "负载信息") | ||||
|     private PayloadInfo payloadInfo; | ||||
|     @ApiModelProperty(value = "全局航线飞行速度") | ||||
|     private int autoFlightSpeed; | ||||
|     @ApiModelProperty(value = "全局航线高度") | ||||
|     private int globalHeight; | ||||
|     @ApiModelProperty(value = "飞行器离被摄面高度") | ||||
|     private int globalShootHeight; | ||||
|     @ApiModelProperty(value = "图片格式列表") | ||||
|     private String imageFormat; | ||||
|     @ApiModelProperty(value = "预计执行时间") | ||||
|     private String estimateTime; | ||||
|     @ApiModelProperty(value = "航点信息列表") | ||||
|     private List<Placemark> placemarkList; | ||||
|     @ApiModelProperty(value = "航线类型 0为航点航线") | ||||
|     private Long type; | ||||
|     @ApiModelProperty(value = "航点照片数量") | ||||
|     private int photoNum; | ||||
|     @ApiModelProperty(value = "航线长度") | ||||
|     private String waylineLen; | ||||
|     @ApiModelProperty(value = "标识【前端要】") | ||||
|     private String flag; | ||||
|     @ApiModelProperty(value = "航点数据【前端要】") | ||||
|     private String points; | ||||
| } | ||||
| @ -0,0 +1,63 @@ | ||||
| package com.ruoyi.wayline.utils; | ||||
|  | ||||
| import cn.hutool.core.lang.Console; | ||||
| import cn.hutool.core.thread.ConcurrencyTester; | ||||
| import cn.hutool.core.thread.ThreadUtil; | ||||
| import cn.hutool.http.HttpRequest; | ||||
|  | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.nio.file.Path; | ||||
| import java.nio.file.Paths; | ||||
|  | ||||
| public class FileUtil { | ||||
|  | ||||
|     private static final String FILE_EXTENSION = ".kmz"; | ||||
|  | ||||
|     /** | ||||
|      * 获取文件名并拼接后缀 | ||||
|      * @param path 文件的路径 | ||||
|      * @return 完整的文件名 | ||||
|      */ | ||||
|     public static String getFileName(Path path) { | ||||
|         String fileName = path.getFileName().toString(); | ||||
|         if (!fileName.endsWith(FILE_EXTENSION)) { | ||||
|             fileName += FILE_EXTENSION; | ||||
|         } | ||||
|         return fileName; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取文件所在的目录路径 | ||||
|      * @param path 文件的完整路径 | ||||
|      * @return 目录路径 | ||||
|      */ | ||||
|     public static Path getDirectoryPath(Path path) { | ||||
|         return path.getParent(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 把 byte[] 中的内容写入指定路径的文件 | ||||
|      * @param path 文件路径 | ||||
|      * @param bytes 要写入的字节数据 | ||||
|      * @throws IOException | ||||
|      */ | ||||
|     public static void transferStreamToFile(Path path, byte[] bytes) throws IOException { | ||||
|         try (FileOutputStream fileOutputStream = new FileOutputStream(path.toFile())) { | ||||
|             fileOutputStream.write(bytes); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public static void main(String[] args) { | ||||
|         ConcurrencyTester tester = ThreadUtil.concurrencyTest(10000, () -> { | ||||
|             String body = HttpRequest.get("http://192.168.110.23:9099/ruoyi/system/books/list") | ||||
|                     .header("authorization", | ||||
|                             "eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjVkMjNlZWFjLTQ5MmItNDIyZS1hMzBhLWM1MzZjNDU5OGQyZiJ9._UQYWmYQGddpgjzhczoh_C5HpxMVUUKJRIPvVOQFsrfHIG8-EBMwW7VM3pS6yaQwjzOCE-upSX1JwTa86LdAbw") | ||||
|                     .execute().body(); | ||||
|             Console.log(body); | ||||
|         }); | ||||
|         // 获取总的执行时间,单位毫秒 | ||||
|         Console.log(tester.getInterval()); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,100 @@ | ||||
| package com.ruoyi.wayline.utils; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
|  | ||||
| /** | ||||
|  * @author ZZX | ||||
|  */ | ||||
| @Data | ||||
| public class HttpResult { | ||||
|     private Boolean success; | ||||
|     private Integer code; | ||||
|     private String message; | ||||
|     private Map<String, Object> data = new HashMap<>(); | ||||
|  | ||||
|     /** | ||||
|      * 成功,缺乏数据 | ||||
|      * | ||||
|      * @return | ||||
|      */ | ||||
|     public static HttpResult ok() { | ||||
|         HttpResult httpResult = new HttpResult(); | ||||
|         httpResult.setSuccess(HttpResultEnum.SUCCESS.getSuccess()); | ||||
|         httpResult.setCode(HttpResultEnum.SUCCESS.getCode()); | ||||
|         httpResult.setMessage(HttpResultEnum.SUCCESS.getMessage()); | ||||
|         return httpResult; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 失败,缺乏数据 | ||||
|      * | ||||
|      * @return | ||||
|      */ | ||||
|     public static HttpResult error() { | ||||
|         HttpResult httpResult = new HttpResult(); | ||||
|         httpResult.setSuccess(HttpResultEnum.FAIL.getSuccess()); | ||||
|         httpResult.setCode(HttpResultEnum.FAIL.getCode()); | ||||
|         httpResult.setMessage(HttpResultEnum.FAIL.getMessage()); | ||||
|         return httpResult; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 设置泛型,缺乏数据 | ||||
|      * | ||||
|      * @param httpResultEnum | ||||
|      * @return | ||||
|      */ | ||||
|     public static HttpResult setResult(HttpResultEnum httpResultEnum) { | ||||
|         HttpResult httpResult = new HttpResult(); | ||||
|         httpResult.setSuccess(httpResultEnum.getSuccess()); | ||||
|         httpResult.setCode(httpResultEnum.getCode()); | ||||
|         httpResult.setMessage(httpResultEnum.getMessage()); | ||||
|         return httpResult; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 设置成功标志位 | ||||
|      * | ||||
|      * @return | ||||
|      */ | ||||
|     public HttpResult success() { | ||||
|         this.setSuccess(HttpResultEnum.SUCCESS.getSuccess()); | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 设置失败标志位 | ||||
|      * | ||||
|      * @return | ||||
|      */ | ||||
|     public HttpResult fail() { | ||||
|         this.setSuccess(HttpResultEnum.FAIL.getSuccess()); | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 添加单个键值对数据 | ||||
|      * | ||||
|      * @param key | ||||
|      * @param value | ||||
|      * @return | ||||
|      */ | ||||
|     public HttpResult data(String key, Object value) { | ||||
|         this.data.put(key, value); | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 添加集合数据 | ||||
|      * | ||||
|      * @param map | ||||
|      * @return | ||||
|      */ | ||||
|     public HttpResult data(Map<String, Object> map) { | ||||
|         this.setData(map); | ||||
|         return this; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,44 @@ | ||||
| package com.ruoyi.wayline.utils; | ||||
|  | ||||
| /** | ||||
|  * @author ZZX | ||||
|  */ | ||||
|  | ||||
| public enum HttpResultEnum { | ||||
|     SUCCESS(true, 200, "成功"), | ||||
|     FAIL(false, 500, "失败"); | ||||
|  | ||||
|     private Boolean success; | ||||
|     private Integer code; | ||||
|     private String message; | ||||
|  | ||||
|     HttpResultEnum(Boolean success, Integer code, String message) { | ||||
|         this.success = success; | ||||
|         this.code = code; | ||||
|         this.message = message; | ||||
|     } | ||||
|  | ||||
|     public Boolean getSuccess() { | ||||
|         return success; | ||||
|     } | ||||
|  | ||||
|     public void setSuccess(Boolean success) { | ||||
|         this.success = success; | ||||
|     } | ||||
|  | ||||
|     public Integer getCode() { | ||||
|         return code; | ||||
|     } | ||||
|  | ||||
|     public void setCode(Integer code) { | ||||
|         this.code = code; | ||||
|     } | ||||
|  | ||||
|     public String getMessage() { | ||||
|         return message; | ||||
|     } | ||||
|  | ||||
|     public void setMessage(String message) { | ||||
|         this.message = message; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,34 @@ | ||||
| package com.ruoyi.wayline.utils; | ||||
|  | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
|  | ||||
| import java.io.BufferedReader; | ||||
| import java.io.InputStreamReader; | ||||
| import java.net.*; | ||||
| import java.util.Collections; | ||||
| import java.util.Enumeration; | ||||
|  | ||||
| /** | ||||
|  * @auther 周志雄 | ||||
|  * @date 2024/7/12 18:55 | ||||
|  */ | ||||
| @Slf4j | ||||
| public class IpUtil { | ||||
|     public static String getSystemIP() { | ||||
|         try { | ||||
|             Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces(); | ||||
|             for (NetworkInterface ni : Collections.list(networkInterfaces)) { | ||||
|                 Enumeration<InetAddress> inetAddresses = ni.getInetAddresses(); | ||||
|                 while (inetAddresses.hasMoreElements()) { | ||||
|                     InetAddress ia = inetAddresses.nextElement(); | ||||
|                     if (!ia.isLoopbackAddress() && ia.isSiteLocalAddress()) { | ||||
|                         return ia.getHostAddress(); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } catch (Exception e) { | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
|         return "127.0.0.1"; // 默认回退到本地地址 | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,42 @@ | ||||
| package com.ruoyi.wayline.utils; | ||||
|  | ||||
| import java.security.MessageDigest; | ||||
| import java.security.NoSuchAlgorithmException; | ||||
|  | ||||
| /** | ||||
|  * @Author zhouzhixiong | ||||
|  * @Date 2023/12/12 11:33 | ||||
|  */ | ||||
| public class MD5Util { | ||||
|     /** | ||||
|      * 将字符串转换为MD5哈希。 | ||||
|      * | ||||
|      * @param input 需要转换的字符串 | ||||
|      * @return MD5哈希字符串 | ||||
|      */ | ||||
|     public static String getMD5(String input) { | ||||
|         try { | ||||
|             // 创建MD5摘要算法实例 | ||||
|             MessageDigest md = MessageDigest.getInstance("MD5"); | ||||
|  | ||||
|             // 对字符串进行哈希处理 | ||||
|             byte[] messageDigest = md.digest(input.getBytes()); | ||||
|  | ||||
|             // 将哈希值转换为十六进制数 | ||||
|             StringBuilder hexString = new StringBuilder(); | ||||
|             for (byte b : messageDigest) { | ||||
|                 String hex = Integer.toHexString(0xff & b); | ||||
|                 if (hex.length() == 1) { | ||||
|                     hexString.append('0'); | ||||
|                 } | ||||
|                 hexString.append(hex); | ||||
|             } | ||||
|  | ||||
|             return hexString.toString(); | ||||
|         } | ||||
|         // 处理NoSuchAlgorithmException异常 | ||||
|         catch (NoSuchAlgorithmException e) { | ||||
|             throw new RuntimeException(e); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,297 @@ | ||||
| package com.ruoyi.wayline.waypoint; | ||||
|  | ||||
| /** | ||||
|  * @Author zhouzhixiong | ||||
|  * @Date 2023/12/2 17:04 | ||||
|  */ | ||||
|  | ||||
|  | ||||
| import com.ruoyi.wayline.domain.Action; | ||||
| import com.ruoyi.wayline.domain.Placemark; | ||||
| import com.ruoyi.wayline.domain.WaypointData; | ||||
| import org.w3c.dom.Document; | ||||
| import org.w3c.dom.Element; | ||||
| import org.w3c.dom.Node; | ||||
| import org.w3c.dom.NodeList; | ||||
| import org.xml.sax.SAXException; | ||||
|  | ||||
| import javax.xml.parsers.DocumentBuilder; | ||||
| import javax.xml.parsers.DocumentBuilderFactory; | ||||
| import javax.xml.parsers.ParserConfigurationException; | ||||
| import javax.xml.transform.OutputKeys; | ||||
| import javax.xml.transform.Transformer; | ||||
| import javax.xml.transform.TransformerException; | ||||
| import javax.xml.transform.TransformerFactory; | ||||
| import javax.xml.transform.dom.DOMSource; | ||||
| import javax.xml.transform.stream.StreamResult; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.io.StringWriter; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
|  | ||||
| public class WayPointKmlProcessorModify { | ||||
|  | ||||
|     /** | ||||
|      * 根据提供的数据请求处理并生成航点飞行的 KML 文件 | ||||
|      * | ||||
|      * @param dataRequest | ||||
|      * @return | ||||
|      */ | ||||
|     public byte[] processKml(WaypointData dataRequest, int templateId) { | ||||
|         try { | ||||
|             // 创建文件的编辑对象 | ||||
|             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); | ||||
|             DocumentBuilder builder = factory.newDocumentBuilder(); | ||||
|  | ||||
|             // 从资源中读取 KML 模板文件 | ||||
|             InputStream inputStream = getClass().getClassLoader().getResourceAsStream("waypoint/template.kml"); | ||||
|             Document document = builder.parse(inputStream); | ||||
|  | ||||
|             // 使用提供的数据请求更新文档 | ||||
|             updateDocumentWithRequestData(document, dataRequest, templateId); | ||||
|  | ||||
|             // 将更新后的文档转换为字节数组 | ||||
|             return convertDocumentToByteArray(document); | ||||
|         } catch (ParserConfigurationException | SAXException | IOException | TransformerException e) { | ||||
|             throw new RuntimeException(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据请求中的信息来更新文档内容 | ||||
|      * | ||||
|      * @param document | ||||
|      * @param dataRequest | ||||
|      */ | ||||
|     private void updateDocumentWithRequestData(Document document, WaypointData dataRequest, int templateId) { | ||||
|         // 更新安全起飞高度 | ||||
|         document.getElementsByTagName("wpml:takeOffSecurityHeight").item(0).setTextContent(String.valueOf(dataRequest.getTakeOffSecurityHeight())); | ||||
|  | ||||
|         // 更新参考起飞点 | ||||
|         document.getElementsByTagName("wpml:takeOffRefPoint").item(0).setTextContent(dataRequest.getTakeOffRefPoint()); | ||||
|  | ||||
|         // 更新飞向首航点速度 | ||||
|         document.getElementsByTagName("wpml:globalTransitionalSpeed").item(0).setTextContent(String.valueOf(dataRequest.getGlobalTransitionalSpeed())); | ||||
|  | ||||
|         // 更新无人机的信息和负载的信息 | ||||
|         updateDroneAndPayloadInfo(document, dataRequest); | ||||
|  | ||||
|         // 生成并设置 templateId | ||||
|         document.getElementsByTagName("wpml:templateId").item(0).setTextContent(String.valueOf(templateId)); | ||||
|  | ||||
|         // 更新全局航线速度 | ||||
|         document.getElementsByTagName("wpml:autoFlightSpeed").item(0).setTextContent(String.valueOf(dataRequest.getAutoFlightSpeed())); | ||||
|  | ||||
|         // 更新相对起飞点高度 | ||||
|         document.getElementsByTagName("wpml:globalHeight").item(0).setTextContent(String.valueOf(dataRequest.getGlobalHeight())); | ||||
|  | ||||
|         // 更新飞行器离被摄面高度(相对地面高) | ||||
|         document.getElementsByTagName("wpml:globalShootHeight").item(0).setTextContent(String.valueOf(dataRequest.getGlobalShootHeight())); | ||||
|  | ||||
|         // 更新图片格式列表 | ||||
|         document.getElementsByTagName("wpml:imageFormat").item(0).setTextContent(dataRequest.getImageFormat()); | ||||
|  | ||||
|         // 删除模板中现有的所有 <Placemark> 节点 | ||||
|         removeExistingPlacemarks(document); | ||||
|  | ||||
|         // TODO 根据提供的位置列表添加若干新的 <Placemark> 节点 | ||||
|         Element folderElement = (Element) document.getElementsByTagName("Folder").item(0); | ||||
|         for (int index = 0; index < dataRequest.getPlacemarkList().size(); index++) { | ||||
|             Placemark location = dataRequest.getPlacemarkList().get(index); | ||||
|             folderElement.appendChild(createPlacemarkElement(document, index, location, dataRequest)); | ||||
|         } | ||||
|  | ||||
|         // 更新文档中全部的负载位置索引标签 | ||||
|         updateAllPayloadPositionIndexes(document, dataRequest); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 更新所有 <wpml:payloadPositionIndex> 标签 | ||||
|      * | ||||
|      * @param document | ||||
|      * @param dataRequest | ||||
|      */ | ||||
|     private void updateAllPayloadPositionIndexes(Document document, WaypointData dataRequest) { | ||||
|         NodeList payloadPositionIndexes = document.getElementsByTagName("wpml:payloadPositionIndex"); | ||||
|         for (int i = 0; i < payloadPositionIndexes.getLength(); i++) { | ||||
|             payloadPositionIndexes.item(i).setTextContent(String.valueOf(dataRequest.getPayloadInfo().getPayloadPositionIndex())); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 创建一个新的 <Placemark> 元素 | ||||
|      * | ||||
|      * @param document | ||||
|      * @param index | ||||
|      * @param location | ||||
|      * @return | ||||
|      */ | ||||
|     private Element createPlacemarkElement(Document document, int index, Placemark location, WaypointData dataRequest) { | ||||
|         // 创建 <Placemark> 根节点 | ||||
|         Element placemark = document.createElement("Placemark"); | ||||
|  | ||||
|         // 设置 <Point> 元素及其坐标 | ||||
|         Element point = document.createElement("Point"); | ||||
|         Element coordinatesElement = document.createElement("coordinates"); | ||||
|         String coordinates = location.getLongitude() + "," + location.getLatitude(); | ||||
|         coordinatesElement.setTextContent(coordinates); | ||||
|         point.appendChild(coordinatesElement); | ||||
|         placemark.appendChild(point); | ||||
|  | ||||
|         // 设置 <wpml:index> 元素 | ||||
|         placemark.appendChild(createElementWithTextContent(document, "wpml:index", String.valueOf(index))); | ||||
|  | ||||
|         // 设置其他 <Placemark> 元素的属性 | ||||
|         placemark.appendChild(createElementWithTextContent(document, "wpml:ellipsoidHeight", String.valueOf(location.getAltitude()))); | ||||
|         placemark.appendChild(createElementWithTextContent(document, "wpml:height", String.valueOf(location.getAltitude()))); | ||||
|         placemark.appendChild(createElementWithTextContent(document, "wpml:useGlobalHeight", "0")); | ||||
|         placemark.appendChild(createElementWithTextContent(document, "wpml:useGlobalSpeed", "1")); | ||||
|         placemark.appendChild(createElementWithTextContent(document, "wpml:useGlobalHeadingParam", "1")); | ||||
|         placemark.appendChild(createElementWithTextContent(document, "wpml:useGlobalTurnParam", "1")); | ||||
|         placemark.appendChild(createElementWithTextContent(document, "wpml:useStraightLine", "1")); | ||||
|         placemark.appendChild(createElementWithTextContent(document, "wpml:gimbalPitchAngle", "0.0")); | ||||
|         placemark.appendChild(createElementWithTextContent(document, "wpml:waypointSpeed", String.valueOf(dataRequest.getAutoFlightSpeed()))); | ||||
|  | ||||
|         // 根据每个 Placemark 的动作列表创建动作组 | ||||
|         createActionGroup(document, location, placemark, index); | ||||
|  | ||||
|         return placemark; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * 更新无人机的信息和负载信息 | ||||
|      * | ||||
|      * @param document | ||||
|      * @param dataRequest | ||||
|      */ | ||||
|     public void updateDroneAndPayloadInfo(Document document, WaypointData dataRequest) { | ||||
|         // 更新飞行器的信息 | ||||
|         NodeList droneInfoNodes = document.getElementsByTagName("wpml:droneInfo").item(0).getChildNodes(); | ||||
|         for (int i = 0; i < droneInfoNodes.getLength(); i++) { | ||||
|             Node node = droneInfoNodes.item(i); | ||||
|             if (node.getNodeName().equals("wpml:droneEnumValue")) { | ||||
|                 node.setTextContent(String.valueOf(dataRequest.getDroneInfo().getDroneEnumValue())); | ||||
|             } else if (node.getNodeName().equals("wpml:droneSubEnumValue")) { | ||||
|                 node.setTextContent(String.valueOf(dataRequest.getDroneInfo().getDroneSubEnumValue())); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // 更新负载的信息 | ||||
|         NodeList payloadInfoNodes = document.getElementsByTagName("wpml:payloadInfo").item(0).getChildNodes(); | ||||
|         for (int i = 0; i < payloadInfoNodes.getLength(); i++) { | ||||
|             Node node = payloadInfoNodes.item(i); | ||||
|             if (node.getNodeName().equals("wpml:payloadEnumValue")) { | ||||
|                 node.setTextContent(String.valueOf(dataRequest.getPayloadInfo().getPayloadEnumValue())); | ||||
|             } else if (node.getNodeName().equals("wpml:payloadSubEnumValue")) { | ||||
|                 node.setTextContent(String.valueOf(dataRequest.getPayloadInfo().getPayloadSubEnumValue())); | ||||
|             } else if (node.getNodeName().equals("wpml:payloadPositionIndex")) { | ||||
|                 node.setTextContent(String.valueOf(dataRequest.getPayloadInfo().getPayloadPositionIndex())); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 从文档中移除所有现有的 <Placemark> 节点 | ||||
|      * | ||||
|      * @param document | ||||
|      */ | ||||
|     public void removeExistingPlacemarks(Document document) { | ||||
|         NodeList placemarks = document.getElementsByTagName("Placemark"); | ||||
|         int length = placemarks.getLength(); | ||||
|         for (int i = length - 1; i >= 0; i--) { | ||||
|             Node placemark = placemarks.item(i); | ||||
|             placemark.getParentNode().removeChild(placemark); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 创建带文本内容的新元素 | ||||
|      * | ||||
|      * @param document | ||||
|      * @param tagName | ||||
|      * @param textContent | ||||
|      * @return | ||||
|      */ | ||||
|     public Element createElementWithTextContent(Document document, String tagName, String textContent) { | ||||
|         Element element = document.createElement(tagName); | ||||
|         element.setTextContent(textContent); | ||||
|         return element; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 创建动作组 | ||||
|      * | ||||
|      * @param document | ||||
|      * @param placemark | ||||
|      */ | ||||
|     private void createActionGroup(Document document, Placemark placemark, Element placemarkElement, int index) { | ||||
|         // 创建 <wpml:actionGroup> 节点 | ||||
|         Element actionGroup = document.createElement("wpml:actionGroup"); | ||||
|         actionGroup.appendChild(createElementWithTextContent(document, "wpml:actionGroupId", "0")); | ||||
|         actionGroup.appendChild(createElementWithTextContent(document, "wpml:actionGroupStartIndex", String.valueOf(index))); | ||||
|         actionGroup.appendChild(createElementWithTextContent(document, "wpml:actionGroupEndIndex", String.valueOf(index))); | ||||
|         actionGroup.appendChild(createElementWithTextContent(document, "wpml:actionGroupMode", "sequence")); | ||||
|  | ||||
|         Element actionTrigger = document.createElement("wpml:actionTrigger"); | ||||
|         actionTrigger.appendChild(createElementWithTextContent(document, "wpml:actionTriggerType", "reachPoint")); | ||||
|         actionGroup.appendChild(actionTrigger); | ||||
|  | ||||
|         // 添加每个动作到动作组中 | ||||
|         List<Action> actions = placemark.getActions(); | ||||
|         if (actions == null || actions.isEmpty()) { | ||||
|             return; | ||||
|         } | ||||
|         for (int i = 0; i < actions.size(); i++) { | ||||
|             Action action = actions.get(i); | ||||
|             Element actionElement = createActionElement(document, i, action.getType(), action.getParams()); | ||||
|             actionGroup.appendChild(actionElement); | ||||
|         } | ||||
|  | ||||
|         // 将动作组添加到 Placemark | ||||
|         placemarkElement.appendChild(actionGroup); | ||||
|     } | ||||
|  | ||||
|     private Element createActionElement(Document document, int actionId, String actionActuatorFunc, Map<String, String> actionParams) { | ||||
|         Element actionElement = document.createElement("wpml:action"); | ||||
|  | ||||
|         // 设置动作ID | ||||
|         actionElement.appendChild(createElementWithTextContent(document, "wpml:actionId", String.valueOf(actionId))); | ||||
|  | ||||
|         // 设置动作类型 | ||||
|         actionElement.appendChild(createElementWithTextContent(document, "wpml:actionActuatorFunc", actionActuatorFunc)); | ||||
|  | ||||
|         // 创建并设置动作参数 | ||||
|         Element actionParamsElement = document.createElement("wpml:actionActuatorFuncParam"); | ||||
|         for (Map.Entry<String, String> param : actionParams.entrySet()) { | ||||
|             actionParamsElement.appendChild(createElementWithTextContent(document, "wpml:" + param.getKey(), param.getValue())); | ||||
|         } | ||||
|         actionElement.appendChild(actionParamsElement); | ||||
|  | ||||
|         return actionElement; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将文档转换为字节数组 | ||||
|      * | ||||
|      * @param document 要转换的文档 | ||||
|      * @return 转换后的字节数组 | ||||
|      * @throws TransformerException | ||||
|      */ | ||||
|     public byte[] convertDocumentToByteArray(Document document) throws TransformerException { | ||||
|         TransformerFactory transformerFactory = TransformerFactory.newInstance(); | ||||
|         Transformer transformer = transformerFactory.newTransformer(); | ||||
|         transformer.setOutputProperty(OutputKeys.INDENT, "yes"); | ||||
|         transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); | ||||
|         DOMSource source = new DOMSource(document); | ||||
|         StringWriter stringWriter = new StringWriter(); | ||||
|         StreamResult result = new StreamResult(stringWriter); | ||||
|         transformer.transform(source, result); | ||||
|         String xmlString = stringWriter.toString(); | ||||
|         // 使用正则表达式移除空白行 | ||||
|         xmlString = xmlString.replaceAll("(?m)^[ \t]*\r?\n", ""); | ||||
|         return xmlString.getBytes(StandardCharsets.UTF_8); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,210 @@ | ||||
| package com.ruoyi.wayline.waypoint; | ||||
|  | ||||
| import com.ruoyi.wayline.domain.Placemark; | ||||
| import com.ruoyi.wayline.domain.WaypointData; | ||||
| import com.ruoyi.wayline.domain.Action; | ||||
| import org.w3c.dom.Document; | ||||
| import org.w3c.dom.Element; | ||||
| import org.w3c.dom.Node; | ||||
| import org.w3c.dom.NodeList; | ||||
| import org.xml.sax.SAXException; | ||||
|  | ||||
| import javax.xml.parsers.DocumentBuilder; | ||||
| import javax.xml.parsers.DocumentBuilderFactory; | ||||
| import javax.xml.parsers.ParserConfigurationException; | ||||
| import javax.xml.transform.OutputKeys; | ||||
| import javax.xml.transform.Transformer; | ||||
| import javax.xml.transform.TransformerException; | ||||
| import javax.xml.transform.TransformerFactory; | ||||
| import javax.xml.transform.dom.DOMSource; | ||||
| import javax.xml.transform.stream.StreamResult; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.io.StringWriter; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
|  | ||||
| public class WayPointWpmlProcessorModify { | ||||
|  | ||||
|     public byte[] processWpml(WaypointData dataRequest, int templateId) { | ||||
|         try { | ||||
|             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); | ||||
|             DocumentBuilder builder = factory.newDocumentBuilder(); | ||||
|             InputStream inputStream = getClass().getClassLoader().getResourceAsStream("waypoint/waylines.wpml"); | ||||
|             Document document = builder.parse(inputStream); | ||||
|  | ||||
|             updateDocumentWithRequest(document, dataRequest, templateId); | ||||
|  | ||||
|             return convertDocumentToByteArray(document); | ||||
|         } catch (ParserConfigurationException | SAXException | IOException | TransformerException e) { | ||||
|             throw new RuntimeException(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void updateDocumentWithRequest(Document document, WaypointData dataRequest, int templateId) { | ||||
|         document.getElementsByTagName("wpml:takeOffSecurityHeight").item(0).setTextContent(String.valueOf(dataRequest.getTakeOffSecurityHeight())); | ||||
|         document.getElementsByTagName("wpml:globalTransitionalSpeed").item(0).setTextContent(String.valueOf(dataRequest.getGlobalTransitionalSpeed())); | ||||
|         updateDroneAndPayloadInfo(document, dataRequest); | ||||
|         document.getElementsByTagName("wpml:templateId").item(0).setTextContent(String.valueOf(templateId)); | ||||
|         document.getElementsByTagName("wpml:autoFlightSpeed").item(0).setTextContent(String.valueOf(dataRequest.getAutoFlightSpeed())); | ||||
|  | ||||
|         removeExistingPlacemarks(document); | ||||
|  | ||||
|         Element folderElement = (Element) document.getElementsByTagName("Folder").item(0); | ||||
|         List<Placemark> placemarkList = dataRequest.getPlacemarkList(); | ||||
|         for (int index = 0; index < placemarkList.size(); index++) { | ||||
|             Placemark location = placemarkList.get(index); | ||||
|             String coordinates = location.getLongitude() + "," + location.getLatitude(); | ||||
|             String height = String.valueOf(dataRequest.getGlobalHeight()); | ||||
|             folderElement.appendChild(createPlacemarkElement(document, coordinates, location, dataRequest, index, height)); | ||||
|         } | ||||
|  | ||||
|         updateAllPayloadPositionIndexes(document, dataRequest); | ||||
|     } | ||||
|  | ||||
|     private void updateAllPayloadPositionIndexes(Document document, WaypointData dataRequest) { | ||||
|         NodeList payloadPositionIndexes = document.getElementsByTagName("wpml:payloadPositionIndex"); | ||||
|         for (int i = 0; i < payloadPositionIndexes.getLength(); i++) { | ||||
|             payloadPositionIndexes.item(i).setTextContent(String.valueOf(dataRequest.getPayloadInfo().getPayloadPositionIndex())); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private Element createPlacemarkElement(Document document, String coordinates, Placemark placemarkData, WaypointData dataRequest, int index, String height) { | ||||
|         Element placemark = document.createElement("Placemark"); | ||||
|  | ||||
|         Element point = document.createElement("Point"); | ||||
|         Element coordinatesElement = document.createElement("coordinates"); | ||||
|         coordinatesElement.setTextContent(coordinates); | ||||
|         point.appendChild(coordinatesElement); | ||||
|         placemark.appendChild(point); | ||||
|  | ||||
|         placemark.appendChild(createElementWithTextContent(document, "wpml:index", String.valueOf(index))); | ||||
|         placemark.appendChild(createElementWithTextContent(document, "wpml:executeHeight", placemarkData.getAltitude())); | ||||
|         placemark.appendChild(createElementWithTextContent(document, "wpml:waypointSpeed", String.valueOf(dataRequest.getAutoFlightSpeed()))); | ||||
|  | ||||
|         addWaypointHeadingParam(placemark, document); | ||||
|         addWaypointTurnParam(placemark, document); | ||||
|         addWaypointGimbalHeadingParam(placemark, document); | ||||
|  | ||||
|         placemark.appendChild(createElementWithTextContent(document, "wpml:useStraightLine", "1")); | ||||
|  | ||||
|         createActionGroup(document, placemark, placemarkData, index); | ||||
|  | ||||
|         return placemark; | ||||
|     } | ||||
|  | ||||
|     private void createActionGroup(Document document, Element placemarkElement, Placemark placemarkData, int index) { | ||||
|         Element actionGroup = document.createElement("wpml:actionGroup"); | ||||
|  | ||||
|         actionGroup.appendChild(createElementWithTextContent(document, "wpml:actionGroupId", "0")); | ||||
|         actionGroup.appendChild(createElementWithTextContent(document, "wpml:actionGroupStartIndex", String.valueOf(index))); | ||||
|         actionGroup.appendChild(createElementWithTextContent(document, "wpml:actionGroupEndIndex", String.valueOf(index))); | ||||
|         actionGroup.appendChild(createElementWithTextContent(document, "wpml:actionGroupMode", "sequence")); | ||||
|  | ||||
|         Element actionTrigger = document.createElement("wpml:actionTrigger"); | ||||
|         actionTrigger.appendChild(createElementWithTextContent(document, "wpml:actionTriggerType", "reachPoint")); | ||||
|         actionGroup.appendChild(actionTrigger); | ||||
|  | ||||
|         // 动态添加动作 | ||||
|         List<Action> actions = placemarkData.getActions(); | ||||
|         if (actions == null || actions.isEmpty()) { | ||||
|             return; | ||||
|         } | ||||
|         for (int i = 0; i < actions.size(); i++) { | ||||
|             Action action = actions.get(i); | ||||
|             Element actionElement = createActionElement(document, i, action.getType(), action.getParams()); | ||||
|             actionGroup.appendChild(actionElement); | ||||
|         } | ||||
|  | ||||
|         placemarkElement.appendChild(actionGroup); | ||||
|     } | ||||
|  | ||||
|     private Element createActionElement(Document document, int actionId, String actionActuatorFunc, Map<String, String> actionParams) { | ||||
|         Element actionElement = document.createElement("wpml:action"); | ||||
|         actionElement.appendChild(createElementWithTextContent(document, "wpml:actionId", String.valueOf(actionId))); | ||||
|         actionElement.appendChild(createElementWithTextContent(document, "wpml:actionActuatorFunc", actionActuatorFunc)); | ||||
|  | ||||
|         Element actionParamsElement = document.createElement("wpml:actionActuatorFuncParam"); | ||||
|         for (Map.Entry<String, String> param : actionParams.entrySet()) { | ||||
|             actionParamsElement.appendChild(createElementWithTextContent(document, "wpml:" + param.getKey(), param.getValue())); | ||||
|         } | ||||
|         actionElement.appendChild(actionParamsElement); | ||||
|  | ||||
|         return actionElement; | ||||
|     } | ||||
|  | ||||
|     private void addWaypointHeadingParam(Element placemark, Document document) { | ||||
|         Element waypointHeadingParam = document.createElement("wpml:waypointHeadingParam"); | ||||
|         waypointHeadingParam.appendChild(createElementWithTextContent(document, "wpml:waypointHeadingMode", "followWayline")); | ||||
|         waypointHeadingParam.appendChild(createElementWithTextContent(document, "wpml:waypointHeadingAngle", "0")); | ||||
|         waypointHeadingParam.appendChild(createElementWithTextContent(document, "wpml:waypointHeadingPathMode", "followBadArc")); | ||||
|         placemark.appendChild(waypointHeadingParam); | ||||
|     } | ||||
|  | ||||
|     private void addWaypointTurnParam(Element placemark, Document document) { | ||||
|         Element waypointTurnParam = document.createElement("wpml:waypointTurnParam"); | ||||
|         waypointTurnParam.appendChild(createElementWithTextContent(document, "wpml:waypointTurnMode", "toPointAndStopWithDiscontinuityCurvature")); | ||||
|         waypointTurnParam.appendChild(createElementWithTextContent(document, "wpml:waypointTurnDampingDist", "0.0")); | ||||
|         placemark.appendChild(waypointTurnParam); | ||||
|     } | ||||
|  | ||||
|     private void addWaypointGimbalHeadingParam(Element placemark, Document document) { | ||||
|         Element waypointGimbalHeadingParam = document.createElement("wpml:waypointGimbalHeadingParam"); | ||||
|         waypointGimbalHeadingParam.appendChild(createElementWithTextContent(document, "wpml:waypointGimbalPitchAngle", "0.0")); | ||||
|         placemark.appendChild(waypointGimbalHeadingParam); | ||||
|     } | ||||
|  | ||||
|     public void updateDroneAndPayloadInfo(Document document, WaypointData dataRequest) { | ||||
|         NodeList droneInfoNodes = document.getElementsByTagName("wpml:droneInfo").item(0).getChildNodes(); | ||||
|         for (int i = 0; i < droneInfoNodes.getLength(); i++) { | ||||
|             Node node = droneInfoNodes.item(i); | ||||
|             if (node.getNodeName().equals("wpml:droneEnumValue")) { | ||||
|                 node.setTextContent(String.valueOf(dataRequest.getDroneInfo().getDroneEnumValue())); | ||||
|             } else if (node.getNodeName().equals("wpml:droneSubEnumValue")) { | ||||
|                 node.setTextContent(String.valueOf(dataRequest.getDroneInfo().getDroneSubEnumValue())); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         NodeList payloadInfoNodes = document.getElementsByTagName("wpml:payloadInfo").item(0).getChildNodes(); | ||||
|         for (int i = 0; i < payloadInfoNodes.getLength(); i++) { | ||||
|             Node node = payloadInfoNodes.item(i); | ||||
|             if (node.getNodeName().equals("wpml:payloadEnumValue")) { | ||||
|                 node.setTextContent(String.valueOf(dataRequest.getPayloadInfo().getPayloadEnumValue())); | ||||
|             } else if (node.getNodeName().equals("wpml:payloadSubEnumValue")) { | ||||
|                 node.setTextContent(String.valueOf(dataRequest.getPayloadInfo().getPayloadSubEnumValue())); | ||||
|             } else if (node.getNodeName().equals("wpml:payloadPositionIndex")) { | ||||
|                 node.setTextContent(String.valueOf(dataRequest.getPayloadInfo().getPayloadPositionIndex())); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public void removeExistingPlacemarks(Document document) { | ||||
|         NodeList placemarks = document.getElementsByTagName("Placemark"); | ||||
|         int length = placemarks.getLength(); | ||||
|         for (int i = length - 1; i >= 0; i--) { | ||||
|             Node placemark = placemarks.item(i); | ||||
|             placemark.getParentNode().removeChild(placemark); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public Element createElementWithTextContent(Document document, String tagName, String textContent) { | ||||
|         Element element = document.createElement(tagName); | ||||
|         element.setTextContent(textContent); | ||||
|         return element; | ||||
|     } | ||||
|  | ||||
|     public byte[] convertDocumentToByteArray(Document document) throws TransformerException { | ||||
|         TransformerFactory transformerFactory = TransformerFactory.newInstance(); | ||||
|         Transformer transformer = transformerFactory.newTransformer(); | ||||
|         transformer.setOutputProperty(OutputKeys.INDENT, "yes"); | ||||
|         transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); | ||||
|         DOMSource source = new DOMSource(document); | ||||
|         StringWriter stringWriter = new StringWriter(); | ||||
|         StreamResult result = new StreamResult(stringWriter); | ||||
|         transformer.transform(source, result); | ||||
|         String xmlString = stringWriter.toString(); | ||||
|         xmlString = xmlString.replaceAll("(?m)^[ \t]*\r?\n", ""); | ||||
|         return xmlString.getBytes(StandardCharsets.UTF_8); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										119
									
								
								drone/ruoyi-wayline/src/main/resources/waypoint/template.kml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								drone/ruoyi-wayline/src/main/resources/waypoint/template.kml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,119 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <kml xmlns="http://www.opengis.net/kml/2.2" xmlns:wpml="http://www.dji.com/wpmz/1.0.2"> | ||||
|     <Document> | ||||
|         <wpml:missionConfig> | ||||
|             <wpml:flyToWaylineMode>safely</wpml:flyToWaylineMode> | ||||
|             <wpml:finishAction>goHome</wpml:finishAction> | ||||
|             <wpml:exitOnRCLost>executeLostAction</wpml:exitOnRCLost> | ||||
|             <wpml:executeRCLostAction>goBack</wpml:executeRCLostAction> | ||||
|             <!-- 安全起飞高度 --> | ||||
|             <wpml:takeOffSecurityHeight>?</wpml:takeOffSecurityHeight> | ||||
|             <!-- 参考起飞点 --> | ||||
|             <wpml:takeOffRefPoint>?</wpml:takeOffRefPoint> | ||||
|             <!-- 全局航线过渡速度[飞向首航点速度] --> | ||||
|             <wpml:globalTransitionalSpeed>?</wpml:globalTransitionalSpeed> | ||||
|             <!-- 飞行器信息 --> | ||||
|             <wpml:droneInfo> | ||||
|                 <wpml:droneEnumValue>?</wpml:droneEnumValue> | ||||
|                 <wpml:droneSubEnumValue>?</wpml:droneSubEnumValue> | ||||
|             </wpml:droneInfo> | ||||
|             <!-- 负载信息 --> | ||||
|             <wpml:payloadInfo> | ||||
|                 <wpml:payloadEnumValue>?</wpml:payloadEnumValue> | ||||
|                 <wpml:payloadSubEnumValue>?</wpml:payloadSubEnumValue> | ||||
|                 <wpml:payloadPositionIndex>?</wpml:payloadPositionIndex> | ||||
|             </wpml:payloadInfo> | ||||
|         </wpml:missionConfig> | ||||
|  | ||||
|         <Folder> | ||||
|             <wpml:templateType>waypoint</wpml:templateType> | ||||
|             <!-- 模板ID --> | ||||
|             <wpml:templateId>?</wpml:templateId> | ||||
|             <!-- 全局航线飞行速度 --> | ||||
|             <wpml:autoFlightSpeed>?</wpml:autoFlightSpeed> | ||||
|             <wpml:globalWaypointTurnMode>toPointAndStopWithDiscontinuityCurvature</wpml:globalWaypointTurnMode> | ||||
|             <wpml:globalUseStraightLine>1</wpml:globalUseStraightLine> | ||||
|             <wpml:gimbalPitchMode>usePointSetting</wpml:gimbalPitchMode> | ||||
|             <!-- 全局航线高度(相对起飞点高度)--> | ||||
|             <wpml:globalHeight>?</wpml:globalHeight> | ||||
|             <wpml:waylineCoordinateSysParam> | ||||
|                 <wpml:coordinateMode>WGS84</wpml:coordinateMode> | ||||
|                 <wpml:heightMode>relativeToStartPoint</wpml:heightMode> | ||||
|                 <wpml:positioningType>GPS</wpml:positioningType> | ||||
|                 <!-- 飞行器离被摄面高度(相对地面高)--> | ||||
|                 <wpml:globalShootHeight>?</wpml:globalShootHeight> | ||||
|             </wpml:waylineCoordinateSysParam> | ||||
|             <wpml:globalWaypointHeadingParam> | ||||
|                 <wpml:waypointHeadingMode>followWayline</wpml:waypointHeadingMode> | ||||
|                 <wpml:waypointHeadingAngle>0</wpml:waypointHeadingAngle> | ||||
|                 <wpml:waypointHeadingPathMode>followBadArc</wpml:waypointHeadingPathMode> | ||||
|             </wpml:globalWaypointHeadingParam> | ||||
|             <Placemark> | ||||
|                 <Point> | ||||
|                     <!-- 经度,纬度 --> | ||||
|                     <coordinates>?</coordinates> | ||||
|                 </Point> | ||||
|                 <!-- 递增的 index --> | ||||
|                 <wpml:index>?</wpml:index> | ||||
|                 <!-- 全局航线高度(椭球高)选用相对起飞点高度则和下面一致 --> | ||||
|                 <wpml:ellipsoidHeight>?</wpml:ellipsoidHeight> | ||||
|                 <!-- 全局航线高度(EGM96海拔高)--> | ||||
|                 <wpml:height>?</wpml:height> | ||||
|                 <wpml:useGlobalHeight>1</wpml:useGlobalHeight> | ||||
|                 <wpml:useGlobalSpeed>1</wpml:useGlobalSpeed> | ||||
|                 <wpml:useGlobalHeadingParam>1</wpml:useGlobalHeadingParam> | ||||
|                 <wpml:useGlobalTurnParam>1</wpml:useGlobalTurnParam> | ||||
|                 <wpml:useStraightLine>1</wpml:useStraightLine> | ||||
|                 <wpml:gimbalPitchAngle>0.0</wpml:gimbalPitchAngle> | ||||
|                 <!-- 航点飞行速度 --> | ||||
|                 <wpml:waypointSpeed>?</wpml:waypointSpeed> | ||||
|                 <wpml:actionGroup> | ||||
|                     <wpml:actionGroupId>0</wpml:actionGroupId> | ||||
|                     <wpml:actionGroupStartIndex>0</wpml:actionGroupStartIndex> | ||||
|                     <wpml:actionGroupEndIndex>0</wpml:actionGroupEndIndex> | ||||
|                     <wpml:actionGroupMode>sequence</wpml:actionGroupMode> | ||||
|                     <wpml:actionTrigger> | ||||
|                         <wpml:actionTriggerType>reachPoint</wpml:actionTriggerType> | ||||
|                     </wpml:actionTrigger> | ||||
|                     <wpml:action> | ||||
|                         <wpml:actionId>0</wpml:actionId> | ||||
|                         <wpml:actionActuatorFunc>gimbalRotate</wpml:actionActuatorFunc> | ||||
|                         <wpml:actionActuatorFuncParam> | ||||
|                             <wpml:payloadPositionIndex>0</wpml:payloadPositionIndex> | ||||
|                             <wpml:gimbalPitchRotateEnable>1</wpml:gimbalPitchRotateEnable> | ||||
|                             <wpml:gimbalPitchRotateAngle>-90.0</wpml:gimbalPitchRotateAngle> | ||||
|                             <wpml:gimbalRollRotateEnable>0</wpml:gimbalRollRotateEnable> | ||||
|                             <wpml:gimbalRollRotateAngle>0.0</wpml:gimbalRollRotateAngle> | ||||
|                             <wpml:gimbalYawRotateEnable>0</wpml:gimbalYawRotateEnable> | ||||
|                             <wpml:gimbalYawRotateAngle>0.0</wpml:gimbalYawRotateAngle> | ||||
|                             <wpml:gimbalRotateTimeEnable>1</wpml:gimbalRotateTimeEnable> | ||||
|                             <wpml:gimbalRotateTime>0.0</wpml:gimbalRotateTime> | ||||
|                         </wpml:actionActuatorFuncParam> | ||||
|                     </wpml:action> | ||||
|                     <wpml:action> | ||||
|                         <wpml:actionId>1</wpml:actionId> | ||||
|                         <wpml:actionActuatorFunc>takePhoto</wpml:actionActuatorFunc> | ||||
|                         <wpml:actionActuatorFuncParam> | ||||
|                             <wpml:payloadPositionIndex>0</wpml:payloadPositionIndex> | ||||
|                             <!-- 图片格式列表 --> | ||||
|                             <wpml:payloadLensIndex>?</wpml:payloadLensIndex> | ||||
|                             <wpml:useGlobalPayloadLensIndex>1</wpml:useGlobalPayloadLensIndex> | ||||
|                         </wpml:actionActuatorFuncParam> | ||||
|                     </wpml:action> | ||||
|                 </wpml:actionGroup> | ||||
|             </Placemark> | ||||
|             <wpml:payloadParam> | ||||
|                 <wpml:payloadPositionIndex>0</wpml:payloadPositionIndex> | ||||
|                 <wpml:focusMode>firstPoint</wpml:focusMode> | ||||
|                 <wpml:meteringMode>average</wpml:meteringMode> | ||||
|                 <wpml:dewarpingEnable>0</wpml:dewarpingEnable> | ||||
|                 <wpml:returnMode>singleReturnStrongest</wpml:returnMode> | ||||
|                 <wpml:samplingRate>160000</wpml:samplingRate> | ||||
|                 <wpml:modelColoringEnable>0</wpml:modelColoringEnable> | ||||
|                 <!-- 图片格式列表 --> | ||||
|                 <wpml:imageFormat>?</wpml:imageFormat> | ||||
|             </wpml:payloadParam> | ||||
|             <Circle></Circle> | ||||
|         </Folder> | ||||
|     </Document> | ||||
| </kml> | ||||
| @ -0,0 +1,93 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <kml xmlns="http://www.opengis.net/kml/2.2" xmlns:wpml="http://www.dji.com/wpmz/1.0.2"> | ||||
|     <Document> | ||||
|         <wpml:missionConfig> | ||||
|             <wpml:flyToWaylineMode>safely</wpml:flyToWaylineMode> | ||||
|             <wpml:finishAction>goHome</wpml:finishAction> | ||||
|             <wpml:exitOnRCLost>executeLostAction</wpml:exitOnRCLost> | ||||
|             <wpml:executeRCLostAction>goBack</wpml:executeRCLostAction> | ||||
|             <!-- 安全起飞高度 --> | ||||
|             <wpml:takeOffSecurityHeight>?</wpml:takeOffSecurityHeight> | ||||
|             <!-- 全局航线过渡速度[飞向首航点速度] --> | ||||
|             <wpml:globalTransitionalSpeed>?</wpml:globalTransitionalSpeed> | ||||
|             <!-- 飞行器信息 --> | ||||
|             <wpml:droneInfo> | ||||
|                 <wpml:droneEnumValue>?</wpml:droneEnumValue> | ||||
|                 <wpml:droneSubEnumValue>?</wpml:droneSubEnumValue> | ||||
|             </wpml:droneInfo> | ||||
|             <!-- 负载信息 --> | ||||
|             <wpml:payloadInfo> | ||||
|                 <wpml:payloadEnumValue>?</wpml:payloadEnumValue> | ||||
|                 <wpml:payloadSubEnumValue>?</wpml:payloadSubEnumValue> | ||||
|                 <wpml:payloadPositionIndex>?</wpml:payloadPositionIndex> | ||||
|             </wpml:payloadInfo> | ||||
|         </wpml:missionConfig> | ||||
|         <Folder> | ||||
|             <!-- 模板ID --> | ||||
|             <wpml:templateId>?</wpml:templateId> | ||||
|             <wpml:waylineId>0</wpml:waylineId> | ||||
|             <!-- 全局航线飞行速度 --> | ||||
|             <wpml:autoFlightSpeed>?</wpml:autoFlightSpeed> | ||||
|             <wpml:executeHeightMode>relativeToStartPoint</wpml:executeHeightMode> | ||||
|             <Placemark> | ||||
|                 <Point> | ||||
|                     <!-- 经度,纬度 --> | ||||
|                     <coordinates>?</coordinates> | ||||
|                 </Point> | ||||
|                 <!-- 递增的 index --> | ||||
|                 <wpml:index>?</wpml:index> | ||||
|                 <!-- 航点执行高度 --> | ||||
|                 <wpml:executeHeight>?</wpml:executeHeight> | ||||
|                 <!-- 航点飞行速度,当前航点飞向下一个航点的速度 --> | ||||
|                 <wpml:waypointSpeed>?</wpml:waypointSpeed> | ||||
|                 <wpml:waypointHeadingParam> | ||||
|                     <wpml:waypointHeadingMode>followWayline</wpml:waypointHeadingMode> | ||||
|                     <wpml:waypointHeadingAngle>0</wpml:waypointHeadingAngle> | ||||
|                     <wpml:waypointHeadingPathMode>followBadArc</wpml:waypointHeadingPathMode> | ||||
|                 </wpml:waypointHeadingParam> | ||||
|                 <wpml:waypointTurnParam> | ||||
|                     <wpml:waypointTurnMode>toPointAndStopWithDiscontinuityCurvature</wpml:waypointTurnMode> | ||||
|                     <wpml:waypointTurnDampingDist>0.0</wpml:waypointTurnDampingDist> | ||||
|                 </wpml:waypointTurnParam> | ||||
|                 <wpml:waypointGimbalHeadingParam> | ||||
|                     <wpml:waypointGimbalPitchAngle>0.0</wpml:waypointGimbalPitchAngle> | ||||
|                 </wpml:waypointGimbalHeadingParam> | ||||
|                 <wpml:useStraightLine>1</wpml:useStraightLine> | ||||
|                 <wpml:actionGroup> | ||||
|                     <wpml:actionGroupId>0</wpml:actionGroupId> | ||||
|                     <wpml:actionGroupStartIndex>0</wpml:actionGroupStartIndex> | ||||
|                     <wpml:actionGroupEndIndex>0</wpml:actionGroupEndIndex> | ||||
|                     <wpml:actionGroupMode>sequence</wpml:actionGroupMode> | ||||
|                     <wpml:actionTrigger> | ||||
|                         <wpml:actionTriggerType>reachPoint</wpml:actionTriggerType> | ||||
|                     </wpml:actionTrigger> | ||||
|                     <wpml:action> | ||||
|                         <wpml:actionId>0</wpml:actionId> | ||||
|                         <wpml:actionActuatorFunc>gimbalRotate</wpml:actionActuatorFunc> | ||||
|                         <wpml:actionActuatorFuncParam> | ||||
|                             <wpml:payloadPositionIndex>0</wpml:payloadPositionIndex> | ||||
|                             <wpml:gimbalPitchRotateEnable>1</wpml:gimbalPitchRotateEnable> | ||||
|                             <wpml:gimbalPitchRotateAngle>-90.0</wpml:gimbalPitchRotateAngle> | ||||
|                             <wpml:gimbalRollRotateEnable>0</wpml:gimbalRollRotateEnable> | ||||
|                             <wpml:gimbalRollRotateAngle>0.0</wpml:gimbalRollRotateAngle> | ||||
|                             <wpml:gimbalYawRotateEnable>0</wpml:gimbalYawRotateEnable> | ||||
|                             <wpml:gimbalYawRotateAngle>0.0</wpml:gimbalYawRotateAngle> | ||||
|                             <wpml:gimbalRotateTimeEnable>0</wpml:gimbalRotateTimeEnable> | ||||
|                             <wpml:gimbalRotateTime>0.0</wpml:gimbalRotateTime> | ||||
|                         </wpml:actionActuatorFuncParam> | ||||
|                     </wpml:action> | ||||
|                     <wpml:action> | ||||
|                         <wpml:actionId>1</wpml:actionId> | ||||
|                         <wpml:actionActuatorFunc>takePhoto</wpml:actionActuatorFunc> | ||||
|                         <wpml:actionActuatorFuncParam> | ||||
|                             <wpml:payloadPositionIndex>0</wpml:payloadPositionIndex> | ||||
|                             <!-- 图片格式列表 --> | ||||
|                             <wpml:payloadLensIndex>?</wpml:payloadLensIndex> | ||||
|                             <wpml:useGlobalPayloadLensIndex>1</wpml:useGlobalPayloadLensIndex> | ||||
|                         </wpml:actionActuatorFuncParam> | ||||
|                     </wpml:action> | ||||
|                 </wpml:actionGroup> | ||||
|             </Placemark> | ||||
|         </Folder> | ||||
|     </Document> | ||||
| </kml> | ||||
		Reference in New Issue
	
	Block a user