[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