[add] 新增无人机模块后端项目

[refactor] 重构后端项目
This commit is contained in:
lcj
2025-05-21 11:30:59 +08:00
parent 4c238435d8
commit dc1de34116
1699 changed files with 54361 additions and 325 deletions

View File

@ -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);
}
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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());
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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"; // 默认回退到本地地址
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View 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>

View File

@ -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>