From bb508127e9454e8b39ac7d431765b6de0ac74c35 Mon Sep 17 00:00:00 2001
From: liusuyi <1951119284@qq.com>
Date: Sat, 06 Sep 2025 15:39:02 +0800
Subject: [PATCH] 优化
---
ard-ui/src/main.js | 6
ard-work/src/main/java/com/ard/tianyu/domain/dto/wayline/ArdWaylineDTO.java | 77 +
ard-work/src/main/java/com/ard/tianyu/domain/entity/UavWayline.java | 80 +
ard-work/src/main/java/com/ard/tianyu/util/GeoTools.java | 98 ++
ard-work/src/main/java/com/ard/tianyu/service/IKmzFileService.java | 13
ard-ui/package.json | 10
pom.xml | 8
ard-work/src/main/java/com/ard/tianyu/config/OssConfiguration.java | 92 +
ard-work/src/main/java/com/ard/tianyu/domain/dto/wayline/ArdGimbalDTO.java | 27
ard-work/src/main/java/com/ard/tianyu/domain/dto/wayline/WaylineFileDTO.java | 43
ard-work/src/main/resources/mapper/UavWaylineMapper.xml | 161 +++
ard-work/src/main/java/com/ard/tianyu/config/GeoserverConfig.java | 14
ard-work/src/main/java/com/ard/tianyu/config/WaylinePlaningConfig.java | 18
ard-work/src/main/java/com/ard/tianyu/domain/dto/wayline/ArdPositionDTO.java | 30
ard-work/src/main/java/com/ard/tianyu/service/impl/UavWaylineServiceImpl.java | 278 +++++
ard-work/src/main/java/com/ard/tianyu/controller/UavWayLineController.java | 2
ard-work/src/main/java/com/ard/tianyu/service/impl/KmzFileServiceImpl.java | 517 ++++++++++
ard-work/src/main/java/com/ard/tianyu/service/impl/GeoToolsServiceImpl.java | 158 +++
ard-work/src/main/java/com/ard/tianyu/controller/wayline/terrain/TerrainFollowingPlanner.java | 222 ++++
ard-work/src/main/java/com/ard/tianyu/domain/enums/WaylineEnum.java | 76 +
ard-work/src/main/java/com/ard/tianyu/util/Vertex.java | 29
ard-work/src/main/java/com/ard/tianyu/component/minio/config/MinioConfig.java | 44
ard-admin/src/main/resources/application.yml | 4
ard-work/src/main/java/com/ard/tianyu/domain/dto/wayline/ArdActionDTO.java | 53 +
ard-work/src/main/java/com/ard/tianyu/util/CameraUtil.java | 6
ard-work/src/main/java/com/ard/tianyu/controller/wayline/terrain/GeoUtils.java | 62 +
ard-work/pom.xml | 57 +
ard-work/src/main/java/com/ard/tianyu/component/websocket/handler/TyMessageHandler.java | 6
ard-work/src/main/java/com/ard/tianyu/service/IGeoToolsService.java | 16
ard-ui/vue.config.js | 82 +
ard-work/src/main/java/com/ard/tianyu/domain/dto/wayline/ArdUavPositionDTO.java | 65 +
ard-admin/src/main/resources/application-dev.yml | 30
ard-work/src/main/java/com/ard/tianyu/controller/wayline/UavWaylineController.java | 114 ++
ard-work/src/main/java/com/ard/tianyu/mapper/UavWaylineMapper.java | 71 +
ard-work/src/main/java/com/ard/tianyu/service/IUavWaylineService.java | 71 +
ard-work/src/main/java/com/ard/tianyu/component/minio/utils/MinioUtil.java | 210 ++++
36 files changed, 2,820 insertions(+), 30 deletions(-)
diff --git a/ard-admin/src/main/resources/application-dev.yml b/ard-admin/src/main/resources/application-dev.yml
index 4bff9d9..9889498 100644
--- a/ard-admin/src/main/resources/application-dev.yml
+++ b/ard-admin/src/main/resources/application-dev.yml
@@ -37,14 +37,36 @@
# 天宇服务器配置
tianyu:
# 天宇http服务器地址
- endpoint: 59.110.237.99:8060
+ endpoint: 192.168.1.97:8060
accesskeyid: wuwuwu
accesskeysecret: 9c9f139f63b343b0a9ef42e05ed29b0b
# 天宇tcp服务器地址
- tcpserver: 59.110.237.99
- tcpport: 8125
+ tcpserver: 192.168.1.97
+ tcpport: 8126
+# 地图资源配置
+geoserver:
+ deviceCoordinateType: WGS84
+ tifPath: H:\map\hlj.tif
+wayline:
+ #可理解为100米大小占图像的比例
+ targetRatio: 0.5
+ #最大航段距离
+ maxLength: 2000
+ #最小航段距离
+ minLength: 1
+ #随飞随动分段长度
+ PeerAtLength: 10
+ #航线KMZ临时存放路径
+ waylineSavePath: /home/ard/myworkspace/uav_server/mainserver/waylines
+
+# MinIO
+minio:
+ url: http://192.168.1.15:9001
+ accessKey: admin
+ secretKey: xzx12345
+ bucketName: kmz
#流媒体配置
mediamtx:
# 流媒体服务器地址
- host: 192.168.1.27
\ No newline at end of file
+ host: 192.168.1.15
\ No newline at end of file
diff --git a/ard-admin/src/main/resources/application.yml b/ard-admin/src/main/resources/application.yml
index 4347a12..2d96918 100644
--- a/ard-admin/src/main/resources/application.yml
+++ b/ard-admin/src/main/resources/application.yml
@@ -111,13 +111,13 @@
# redis 配置
redis:
# 地址
- host: 127.0.0.1
+ host: 192.168.1.15
# 端口,默认为6379
port: 6379
# 数据库索引
database: 0
# 密码
- password: xzx12345
+ password:
# 连接超时时间
timeout: 10s
lettuce:
diff --git a/ard-ui/package.json b/ard-ui/package.json
index 2a091d2..f1c7380 100644
--- a/ard-ui/package.json
+++ b/ard-ui/package.json
@@ -6,10 +6,16 @@
"license": "MIT",
"scripts": {
"dev": "vue-cli-service serve",
- "build:prod": "vue-cli-service build",
+ "build:prod": "vue-cli-service build --mode production",
"build:stage": "vue-cli-service build --mode staging",
+ "build:analyze": "vue-cli-service build --report",
+ "build:test": "vue-cli-service build --mode test",
"preview": "node build/index.js --preview",
- "lint": "eslint --ext .js,.vue src"
+ "lint": "eslint --ext .js,.vue src",
+ "lint:fix": "eslint --ext .js,.vue src --fix",
+ "clean": "rimraf dist",
+ "serve:prod": "npm run build:prod && npm run preview",
+ "serve:stage": "npm run build:stage && npm run preview"
},
"husky": {
"hooks": {
diff --git a/ard-ui/src/main.js b/ard-ui/src/main.js
index 13c6cf2..baa78af 100644
--- a/ard-ui/src/main.js
+++ b/ard-ui/src/main.js
@@ -37,6 +37,10 @@
import VueMeta from 'vue-meta'
// 字典数据组件
import DictData from '@/components/DictData'
+// 性能监控工具
+import performanceMonitor from '@/utils/performance'
+// 缓存管理器
+import cacheManager from '@/utils/cache'
// 全局方法挂载
Vue.prototype.getDicts = getDicts
@@ -48,6 +52,8 @@
Vue.prototype.selectDictLabels = selectDictLabels
Vue.prototype.download = download
Vue.prototype.handleTree = handleTree
+Vue.prototype.performanceMonitor = performanceMonitor
+Vue.prototype.cacheManager = cacheManager
// 全局组件挂载
Vue.component('DictTag', DictTag)
diff --git a/ard-ui/vue.config.js b/ard-ui/vue.config.js
index be17c26..c27e37d 100644
--- a/ard-ui/vue.config.js
+++ b/ard-ui/vue.config.js
@@ -1,14 +1,13 @@
'use strict'
const path = require('path')
+const CompressionPlugin = require('compression-webpack-plugin')
+const TerserPlugin = require('terser-webpack-plugin')
function resolve(dir) {
return path.join(__dirname, dir)
}
-const CompressionPlugin = require('compression-webpack-plugin')
-
const name = process.env.VUE_APP_TITLE || '无人机后台管理系统' // 网页标题
-
const port = process.env.port || process.env.npm_config_port || 80 // 端口
// vue.config.js 配置说明
@@ -27,12 +26,23 @@
lintOnSave: process.env.NODE_ENV === 'development',
// 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。
productionSourceMap: false,
+ // 转译依赖
transpileDependencies: ['quill'],
// webpack-dev-server 相关配置
devServer: {
host: '0.0.0.0',
port: port,
open: true,
+ // 添加热更新配置
+ hot: true,
+ // 添加客户端日志级别
+ client: {
+ logging: 'warn',
+ overlay: {
+ errors: true,
+ warnings: false,
+ },
+ },
proxy: {
// detail: https://cli.vuejs.org/config/#devserver-proxy
[process.env.VUE_APP_BASE_API]: {
@@ -40,34 +50,66 @@
changeOrigin: true,
pathRewrite: {
['^' + process.env.VUE_APP_BASE_API]: ''
- }
+ },
+ // 添加代理超时配置
+ timeout: 30000,
+ // 添加代理日志
+ logLevel: 'debug'
}
},
- disableHostCheck: true
+ // 移除已废弃的配置
+ // disableHostCheck: true,
+ // 使用新的配置
+ allowedHosts: 'all'
},
css: {
loaderOptions: {
sass: {
sassOptions: { outputStyle: "expanded" }
}
- }
+ },
+ // 添加CSS提取配置
+ extract: process.env.NODE_ENV === 'production'
},
configureWebpack: {
name: name,
resolve: {
alias: {
'@': resolve('src')
- }
+ },
+ // 添加文件扩展名解析
+ extensions: ['.js', '.vue', '.json']
+ },
+ // 添加性能提示配置
+ performance: {
+ hints: process.env.NODE_ENV === 'production' ? 'warning' : false,
+ maxEntrypointSize: 512000,
+ maxAssetSize: 512000
+ },
+ // 添加优化配置
+ optimization: {
+ minimizer: [
+ new TerserPlugin({
+ terserOptions: {
+ compress: {
+ drop_console: process.env.NODE_ENV === 'production',
+ drop_debugger: process.env.NODE_ENV === 'production'
+ }
+ }
+ })
+ ]
},
plugins: [
// http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件
new CompressionPlugin({
cache: false, // 不启用文件缓存
- test: /\.(js|css|html|jpe?g|png|gif|svg)?$/i, // 压缩文件格式
+ test: /\.(js|css|html|jpe?g|png|gif|svg|woff2?|eot|ttf|otf)?$/i, // 压缩文件格式
filename: '[path][base].gz[query]', // 压缩后的文件名
algorithm: 'gzip', // 使用gzip压缩
minRatio: 0.8, // 压缩比例,小于 80% 的文件不会被压缩
- deleteOriginalAssets: false // 压缩后删除原文件
+ deleteOriginalAssets: false, // 压缩后删除原文件
+ threshold: 10240, // 只有大于该值的资源会被处理
+ minSize: 0 // 只有大于该值的资源会被处理
})
],
},
@@ -91,6 +133,13 @@
symbolId: 'icon-[name]'
})
.end()
+
+ // 添加图片压缩配置
+ config.module
+ .rule('images')
+ .use('url-loader')
+ .loader('url-loader')
+ .tap(options => Object.assign(options, { limit: 10240 }))
config.when(process.env.NODE_ENV !== 'development', config => {
config
@@ -116,6 +165,11 @@
test: /[\\/]node_modules[\\/]_?element-ui(.*)/, // in order to adapt to cnpm
priority: 20 // the weight needs to be larger than libs and app or it will be packaged into libs or app
},
+ echarts: {
+ name: 'chunk-echarts', // split echarts into a single package
+ test: /[\\/]node_modules[\\/]_?echarts(.*)/,
+ priority: 15
+ },
commons: {
name: 'chunk-commons',
test: resolve('src/components'), // can customize your rules
@@ -127,5 +181,15 @@
})
config.optimization.runtimeChunk('single')
})
+
+ // 添加开发环境优化
+ config.when(process.env.NODE_ENV === 'development', config => {
+ config.devtool('cheap-module-eval-source-map')
+ })
+
+ // 添加生产环境优化
+ config.when(process.env.NODE_ENV === 'production', config => {
+ config.devtool('source-map')
+ })
}
}
diff --git a/ard-work/pom.xml b/ard-work/pom.xml
index 63e9ebb..5b14053 100644
--- a/ard-work/pom.xml
+++ b/ard-work/pom.xml
@@ -14,7 +14,10 @@
<description>
主业务入口
</description>
-
+ <properties>
+ <minio.version>8.3.7</minio.version>
+ <dom4j.version>2.1.3</dom4j.version>
+ </properties>
<dependencies>
<!-- 通用工具-->
@@ -69,6 +72,56 @@
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
+ <!--geo-->
+ <dependency>
+ <groupId>org.geotools</groupId>
+ <artifactId>gt-geotiff</artifactId>
+ <version>28.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.geotools</groupId>
+ <artifactId>gt-main</artifactId>
+ <version>28.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.geotools</groupId>
+ <artifactId>gt-referencing</artifactId>
+ <version>28.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.geotools</groupId>
+ <artifactId>gt-epsg-hsql</artifactId>
+ <version>28.2</version>
+ </dependency>
+ <!--minio-->
+ <dependency>
+ <groupId>io.minio</groupId>
+ <artifactId>minio</artifactId>
+ <version>${minio.version}</version>
+ </dependency>
+ <!--DOM4j-->
+ <dependency>
+ <groupId>org.dom4j</groupId>
+ <artifactId>dom4j</artifactId>
+ <version>${dom4j.version}</version>
+ </dependency>
+ <!--okhttp3-->
+ <dependency>
+ <groupId>com.squareup.okhttp3</groupId>
+ <artifactId>okhttp</artifactId>
+ </dependency>
</dependencies>
-
+ <repositories>
+ <repository>
+ <id>osgeo</id>
+ <name>OSGeo Release Repository</name>
+ <url>https://repo.osgeo.org/repository/release/</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ <releases>
+ <enabled>true</enabled>
+ </releases>
+ </repository>
+ </repositories>
</project>
\ No newline at end of file
diff --git a/ard-work/src/main/java/com/ard/tianyu/component/minio/config/MinioConfig.java b/ard-work/src/main/java/com/ard/tianyu/component/minio/config/MinioConfig.java
new file mode 100644
index 0000000..8eda37b
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/component/minio/config/MinioConfig.java
@@ -0,0 +1,44 @@
+package com.ard.tianyu.component.minio.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import io.minio.MinioClient;
+
+/**
+ * Minio 配置信息
+ *
+ * @author ruoyi
+ */
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "minio")
+public class MinioConfig {
+
+ /**
+ * 服务地址
+ */
+ private String url;
+
+ /**
+ * 用户名
+ */
+ private String accessKey;
+
+ /**
+ * 密码
+ */
+ private String secretKey;
+
+ /**
+ * 存储桶名称
+ */
+ private String bucketName;
+
+
+ @Bean
+ public MinioClient getMinioClient() {
+ return MinioClient.builder().endpoint(url).credentials(accessKey, secretKey).build();
+ }
+}
diff --git a/ard-work/src/main/java/com/ard/tianyu/component/minio/utils/MinioUtil.java b/ard-work/src/main/java/com/ard/tianyu/component/minio/utils/MinioUtil.java
new file mode 100644
index 0000000..a6c0ed6
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/component/minio/utils/MinioUtil.java
@@ -0,0 +1,210 @@
+package com.ard.tianyu.component.minio.utils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import com.ard.common.utils.ServletUtils;
+import com.ard.common.utils.spring.SpringUtils;
+import io.minio.*;
+import org.springframework.web.multipart.MultipartFile;
+import io.minio.http.Method;
+
+/**
+ * Minio 文件存储工具类
+ *
+ * @author ruoyi
+ */
+public class MinioUtil {
+ /**
+ * 判断桶是否存在
+ *
+ * @param bucketName 桶名称
+ * @return 如果桶存在返回 true,否则返回 false
+ * @throws IOException
+ */
+ public static boolean bucketExists(String bucketName) throws IOException {
+ MinioClient minioClient = SpringUtils.getBean(MinioClient.class);
+ try {
+ return minioClient.bucketExists(io.minio.BucketExistsArgs.builder().bucket(bucketName).build());
+ } catch (Exception e) {
+ throw new IOException("Error checking if bucket exists", e);
+ }
+ }
+
+ // 创建桶
+ public static void createBucket(String bucketName) throws IOException {
+ MinioClient minioClient = SpringUtils.getBean(MinioClient.class);
+ try {
+ minioClient.makeBucket(io.minio.MakeBucketArgs.builder().bucket(bucketName).build());
+ setBucketPolicy(bucketName);
+ } catch (Exception e) {
+ throw new IOException("Error creating bucket", e);
+ }
+ }
+
+
+ /**
+ * 设置桶策略
+ * 刘苏义
+ * 2023/9/20 10:01:39
+ */
+ public static boolean setBucketPolicy(String bucket) throws Exception {
+ MinioClient minioClient = SpringUtils.getBean(MinioClient.class);
+ try {
+ //设置策略
+ String sb = "{\"Version\":\"2012-10-17\"," +
+ "\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":" +
+ "{\"AWS\":[\"*\"]},\"Action\":[\"s3:ListBucket\",\"s3:ListBucketMultipartUploads\"," +
+ "\"s3:GetBucketLocation\"],\"Resource\":[\"arn:aws:s3:::" + bucket +
+ "\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:PutObject\",\"s3:AbortMultipartUpload\",\"s3:DeleteObject\",\"s3:GetObject\",\"s3:ListMultipartUploadParts\"],\"Resource\":[\"arn:aws:s3:::" +
+ bucket + "/*\"]}]}";
+ SetBucketPolicyArgs setBucketPolicyArgs = SetBucketPolicyArgs.builder()
+ .bucket(bucket)
+ .config(sb)
+ .build();
+ minioClient.setBucketPolicy(setBucketPolicyArgs);
+ return true;
+ } catch (Exception ex) {
+ throw new Exception("设置桶策略异常:" + ex.getMessage());
+ }
+ }
+
+
+ /**
+ * 上传文件
+ *
+ * @param bucketName 桶名称
+ * @param fileName
+ * @throws IOException
+ */
+ public static String uploadFile(String bucketName, String fileName, MultipartFile multipartFile) throws IOException {
+ String url = "";
+ MinioClient minioClient = SpringUtils.getBean(MinioClient.class);
+ try (InputStream inputStream = multipartFile.getInputStream()) {
+ minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(fileName).stream(inputStream,
+ multipartFile.getSize(), -1).contentType(multipartFile.getContentType()).build());
+ url = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().bucket(bucketName).object(fileName).method(Method.GET).build());
+ url = url.substring(0, url.indexOf('?'));
+ return ServletUtils.urlDecode(url);
+ } catch (Exception e) {
+ throw new IOException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 上传本地文件
+ *
+ * @param bucketName 桶名称
+ * @param fileName 存储到 Minio 的文件名
+ * @param localFilePath 本地文件绝对路径
+ * @return 访问 URL
+ * @throws IOException
+ */
+ public static String uploadLocalFile(String bucketName, String fileName, String localFilePath) throws IOException {
+ MinioClient minioClient = SpringUtils.getBean(MinioClient.class);
+ try (InputStream inputStream = new FileInputStream(localFilePath)) {
+ File file = new File(localFilePath);
+ minioClient.putObject(
+ PutObjectArgs.builder()
+ .bucket(bucketName)
+ .object(fileName)
+ .stream(inputStream, file.length(), -1)
+ .contentType("application/octet-stream") // 可根据需求自行修改
+ .build()
+ );
+ String url = minioClient.getPresignedObjectUrl(
+ GetPresignedObjectUrlArgs.builder()
+ .bucket(bucketName)
+ .object(fileName)
+ .method(Method.GET)
+ .build()
+ );
+ return ServletUtils.urlDecode(url.substring(0, url.indexOf('?')));
+ } catch (Exception e) {
+ throw new IOException(e.getMessage(), e);
+ }
+ }
+
+ public static String uploadInputStream(String bucketName, String fileName, InputStream inputStream, long size,
+ String contentType) throws IOException {
+ MinioClient minioClient = SpringUtils.getBean(MinioClient.class);
+ if (!bucketExists(bucketName)) {
+ createBucket(bucketName);
+ }
+ try {
+ minioClient.putObject(
+ PutObjectArgs.builder()
+ .bucket(bucketName)
+ .object(fileName)
+ .stream(inputStream, size, -1)
+ .contentType(contentType)
+ .build()
+ );
+
+ String url = minioClient.getPresignedObjectUrl(
+ GetPresignedObjectUrlArgs.builder()
+ .bucket(bucketName)
+ .object(fileName)
+ .method(Method.GET)
+ .build()
+ );
+ url = url.substring(0, url.indexOf('?'));
+ return ServletUtils.urlDecode(url);
+ } catch (Exception e) {
+ throw new IOException(e.getMessage(), e);
+ } finally {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException ignore) {
+ }
+ }
+ }
+ }
+
+ /**
+ * 判断文件是否存在
+ */
+ public static boolean exists(String bucketName, String fileName) throws IOException {
+ MinioClient minioClient = SpringUtils.getBean(MinioClient.class);
+ if (!bucketExists(bucketName)) {
+ createBucket(bucketName);
+ }
+ try {
+ minioClient.statObject(
+ io.minio.StatObjectArgs.builder()
+ .bucket(bucketName)
+ .object(fileName)
+ .build()
+ );
+ return true;
+ } catch (io.minio.errors.ErrorResponseException e) {
+ if ("NoSuchKey".equals(e.errorResponse().code()) || "NoSuchObject".equals(e.errorResponse().code())) {
+ return false;
+ }
+ throw new IOException(e.getMessage(), e);
+ } catch (Exception e) {
+ throw new IOException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 删除文件
+ */
+ public static void delete(String bucketName, String fileName) throws IOException {
+ MinioClient minioClient = SpringUtils.getBean(MinioClient.class);
+ try {
+ minioClient.removeObject(
+ io.minio.RemoveObjectArgs.builder()
+ .bucket(bucketName)
+ .object(fileName)
+ .build()
+ );
+ } catch (Exception e) {
+ throw new IOException(e.getMessage(), e);
+ }
+ }
+
+}
diff --git a/ard-work/src/main/java/com/ard/tianyu/component/websocket/handler/TyMessageHandler.java b/ard-work/src/main/java/com/ard/tianyu/component/websocket/handler/TyMessageHandler.java
index bfb4cfa..c9b158f 100644
--- a/ard-work/src/main/java/com/ard/tianyu/component/websocket/handler/TyMessageHandler.java
+++ b/ard-work/src/main/java/com/ard/tianyu/component/websocket/handler/TyMessageHandler.java
@@ -122,7 +122,7 @@
Boolean subscribe = uavStateMessage.getSubscribe();
if (subscribe) {
// 执行订阅操作
- uavStateManager.subscribe(boxSn, userId, 2);
+ uavStateManager.subscribe(boxSn, userId, 1);
} else {
// 执行退订操作
uavStateManager.unsubscribe(boxSn, userId);
@@ -153,7 +153,7 @@
Boolean subscribe = uavTelemetryMessage.getSubscribe();
if (subscribe) {
// 执行订阅操作
- uavTelemetryManager.subscribe(boxSn, userId, 2);
+ uavTelemetryManager.subscribe(boxSn, userId, 1);
} else {
// 执行退订操作
uavTelemetryManager.unsubscribe(boxSn, userId);
@@ -239,7 +239,7 @@
Boolean subscribe = uavControlMessage.getSubscribe();
if (subscribe) {
// 执行订阅操作
- uavAllManager.subscribe(boxSn, userId, 2);
+ uavAllManager.subscribe(boxSn, userId, 1);
} else {
// 执行退订操作
uavAllManager.unsubscribe(boxSn, userId);
diff --git a/ard-work/src/main/java/com/ard/tianyu/config/GeoserverConfig.java b/ard-work/src/main/java/com/ard/tianyu/config/GeoserverConfig.java
new file mode 100644
index 0000000..409a28e
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/config/GeoserverConfig.java
@@ -0,0 +1,14 @@
+package com.ard.tianyu.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "geoserver")
+public class GeoserverConfig {
+ private String deviceCoordinateType;
+ private String tifPath;//用于仿地飞行的地形文件
+
+}
diff --git a/ard-work/src/main/java/com/ard/tianyu/config/OssConfiguration.java b/ard-work/src/main/java/com/ard/tianyu/config/OssConfiguration.java
new file mode 100644
index 0000000..5a0678a
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/config/OssConfiguration.java
@@ -0,0 +1,92 @@
+package com.ard.tianyu.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/9
+ */
+@ConfigurationProperties(prefix = "oss")
+@Component
+public class OssConfiguration {
+
+ /**
+ * @see com.dji.ard.component.oss.model.enums.OssTypeEnum
+ */
+ public static String provider;
+
+ /**
+ * Whether to use the object storage service.
+ */
+ public static boolean enable;
+
+ /**
+ * The protocol needs to be included at the beginning of the address.
+ */
+ public static String endpoint;
+
+ public static String accessKey;
+
+ public static String secretKey;
+
+ public static String region;
+
+ public static Long expire;
+
+ public static String roleSessionName;
+
+ public static String roleArn;
+
+ public static String bucket;
+
+ public static String objectDirPrefix;
+
+ public void setProvider(String provider) {
+ OssConfiguration.provider = provider;
+ }
+
+ public void setEnable(boolean enable) {
+ OssConfiguration.enable = enable;
+ }
+
+ public void setEndpoint(String endpoint) {
+ OssConfiguration.endpoint = endpoint;
+ }
+
+ public void setAccessKey(String accessKey) {
+ OssConfiguration.accessKey = accessKey;
+ }
+
+ public void setSecretKey(String secretKey) {
+ OssConfiguration.secretKey = secretKey;
+ }
+
+ public void setRegion(String region) {
+ OssConfiguration.region = region;
+ }
+
+ public void setExpire(Long expire) {
+ OssConfiguration.expire = expire;
+ }
+
+ public void setRoleSessionName(String roleSessionName) {
+ OssConfiguration.roleSessionName = roleSessionName;
+ }
+
+ public void setRoleArn(String roleArn) {
+ OssConfiguration.roleArn = roleArn;
+ }
+
+ public void setBucket(String bucket) {
+ OssConfiguration.bucket = bucket;
+ }
+
+ public void setObjectDirPrefix(String objectDirPrefix) {
+ OssConfiguration.objectDirPrefix = objectDirPrefix;
+ }
+}
+
+
+
diff --git a/ard-work/src/main/java/com/ard/tianyu/config/WaylinePlaningConfig.java b/ard-work/src/main/java/com/ard/tianyu/config/WaylinePlaningConfig.java
new file mode 100644
index 0000000..fa897e1
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/config/WaylinePlaningConfig.java
@@ -0,0 +1,18 @@
+package com.ard.tianyu.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "wayline")
+public class WaylinePlaningConfig {
+
+ private Float targetRatio;
+ private Integer maxLength;
+ private Integer minLength;
+ private Integer PeerAtLength;
+ private String waylineSavePath;
+
+}
\ No newline at end of file
diff --git a/ard-work/src/main/java/com/ard/tianyu/controller/UavWayLineController.java b/ard-work/src/main/java/com/ard/tianyu/controller/UavWayLineController.java
index e4200d9..b2d19eb 100644
--- a/ard-work/src/main/java/com/ard/tianyu/controller/UavWayLineController.java
+++ b/ard-work/src/main/java/com/ard/tianyu/controller/UavWayLineController.java
@@ -19,7 +19,7 @@
* @author liusuyi
* @date 2024年10月21日13:52
**/
-@Api(tags = "无人机航线飞行接口")
+@Api(tags = "无人机航线操作接口")
@RequestMapping("/uav/wayline")
@RestController
public class UavWayLineController {
diff --git a/ard-work/src/main/java/com/ard/tianyu/controller/wayline/UavWaylineController.java b/ard-work/src/main/java/com/ard/tianyu/controller/wayline/UavWaylineController.java
new file mode 100644
index 0000000..6dddc4f
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/controller/wayline/UavWaylineController.java
@@ -0,0 +1,114 @@
+package com.ard.tianyu.controller.wayline;
+
+import com.ard.tianyu.domain.dto.wayline.ArdWaylineDTO;
+import com.ard.tianyu.domain.entity.UavWayline;
+import com.ard.tianyu.service.IUavWaylineService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import java.util.List;
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import com.ard.common.annotation.Log;
+import com.ard.common.core.controller.BaseController;
+import com.ard.common.core.domain.AjaxResult;
+import com.ard.common.enums.BusinessType;
+import com.ard.common.utils.poi.ExcelUtil;
+import com.ard.common.core.page.TableDataInfo;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 航线Controller
+ *
+ * @author ruoyi
+ * @date 2025-06-27
+ */
+@Api(tags = "无人机航线管理接口")
+@RestController
+@RequestMapping("/uav/manage/wayline")
+public class UavWaylineController extends BaseController
+{
+ @Resource
+ private IUavWaylineService uavWaylineService;
+
+ /**
+ * 查询航线列表
+ */
+ @ApiOperation("查询航线列表")
+ @PreAuthorize("@ss.hasPermi('uav:wayline:list')")
+ @GetMapping("/list")
+ public TableDataInfo list(UavWayline uavWayline)
+ {
+ startPage();
+ List<UavWayline> list = uavWaylineService.selectUavWaylineList(uavWayline);
+ return getDataTable(list);
+ }
+
+ /**
+ * 导出航线列表
+ */
+ @PreAuthorize("@ss.hasPermi('uav:wayline:export')")
+ @Log(title = "航线", businessType = BusinessType.EXPORT)
+ @PostMapping("/export")
+ public void export(HttpServletResponse response, UavWayline uavWayline)
+ {
+ List<UavWayline> list = uavWaylineService.selectUavWaylineList(uavWayline);
+ ExcelUtil<UavWayline> util = new ExcelUtil<UavWayline>(UavWayline.class);
+ util.exportExcel(response, list, "航线数据");
+ }
+
+ /**
+ * 获取航线详细信息
+ */
+ @ApiOperation("获取航线详细信息")
+ @PreAuthorize("@ss.hasPermi('uav:wayline:query')")
+ @GetMapping(value = "/{id}")
+ public AjaxResult getInfo(@PathVariable("id") String id)
+ {
+ return success(uavWaylineService.selectUavWaylineById(id));
+ }
+
+ /**
+ * 新增航线
+ */
+ @ApiOperation("新增航线")
+ @PreAuthorize("@ss.hasPermi('uav:wayline:add')")
+ @Log(title = "航线", businessType = BusinessType.INSERT)
+ @PostMapping
+ public AjaxResult add(@RequestBody ArdWaylineDTO ardWaylineDTO)
+ {
+ if (!uavWaylineService.checkWaylineNameUnique(ardWaylineDTO))
+ {
+ return error("新增航线'" + ardWaylineDTO.getWaylineName() + "'失败,航线已存在");
+ }
+ return toAjax(uavWaylineService.insertUavWayline(ardWaylineDTO));
+ }
+
+ /**
+ * 修改航线
+ */
+ @ApiOperation("修改航线")
+ @PreAuthorize("@ss.hasPermi('uav:wayline:edit')")
+ @Log(title = "航线", businessType = BusinessType.UPDATE)
+ @PutMapping
+ public AjaxResult edit(@RequestBody ArdWaylineDTO ardWaylineDTO)
+ {
+ if (!uavWaylineService.checkWaylineNameUnique(ardWaylineDTO))
+ {
+ return error("修改航线'" + ardWaylineDTO.getWaylineName() + "'失败,航线已存在");
+ }
+ return toAjax(uavWaylineService.updateUavWayline(ardWaylineDTO));
+ }
+
+ /**
+ * 删除航线
+ */
+ @ApiOperation("删除航线")
+ @PreAuthorize("@ss.hasPermi('uav:wayline:remove')")
+ @Log(title = "航线", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{ids}")
+ public AjaxResult remove(@PathVariable String[] ids)
+ {
+ return toAjax(uavWaylineService.deleteUavWaylineByIds(ids));
+ }
+}
diff --git a/ard-work/src/main/java/com/ard/tianyu/controller/wayline/terrain/GeoUtils.java b/ard-work/src/main/java/com/ard/tianyu/controller/wayline/terrain/GeoUtils.java
new file mode 100644
index 0000000..8c03c43
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/controller/wayline/terrain/GeoUtils.java
@@ -0,0 +1,62 @@
+package com.ard.tianyu.controller.wayline.terrain;
+
+import com.ard.tianyu.config.GeoserverConfig;
+import lombok.extern.slf4j.Slf4j;
+import org.geotools.coverage.grid.GridCoverage2D;
+import org.geotools.coverage.processing.Operations;
+import org.geotools.gce.geotiff.GeoTiffReader;
+import org.geotools.geometry.DirectPosition2D;
+import org.geotools.referencing.CRS;
+import org.opengis.geometry.DirectPosition;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.springframework.stereotype.Component;
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import java.io.File;
+import java.io.IOException;
+
+@Slf4j
+@Component
+public class GeoUtils {
+
+ @Resource
+ private GeoserverConfig geoserverConfig;
+ private GridCoverage2D coverage;
+
+ /**
+ * 加载地形文件
+ *
+ * @author lsy
+ * @date 2025/6/18
+ */
+ @PostConstruct
+ public void initTif() {
+ try {
+ File file = new File(geoserverConfig.getTifPath());
+ GeoTiffReader reader = new GeoTiffReader(file);
+ GridCoverage2D rawCoverage = reader.read(null);
+ CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:4326");
+ coverage = (GridCoverage2D) Operations.DEFAULT.resample(rawCoverage, targetCRS);
+ } catch (Exception e) {
+ log.error("加载tif地形文件异常:{}", e.getMessage());
+ }
+ }
+
+ /**
+ * 获取地形高度
+ *
+ * @param lon 经度
+ * @param lat 纬度
+ * @return 海拔高度
+ * @author lsy
+ * @date 2025/6/18
+ */
+
+ public double getHeight(double lon, double lat) throws IOException {
+ DirectPosition pos = new DirectPosition2D(null, lat, lon);
+ double[] result = coverage.evaluate(pos, new double[1]);
+ System.out.println("Elevation at (" + lon + ", " + lat + "): " + result[0]);
+ return result[0];
+ }
+
+}
\ No newline at end of file
diff --git a/ard-work/src/main/java/com/ard/tianyu/controller/wayline/terrain/TerrainFollowingPlanner.java b/ard-work/src/main/java/com/ard/tianyu/controller/wayline/terrain/TerrainFollowingPlanner.java
new file mode 100644
index 0000000..2703a0f
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/controller/wayline/terrain/TerrainFollowingPlanner.java
@@ -0,0 +1,222 @@
+package com.ard.tianyu.controller.wayline.terrain;
+
+import com.ard.tianyu.domain.dto.wayline.ArdActionDTO;
+import com.ard.tianyu.domain.dto.wayline.ArdPositionDTO;
+import com.ard.tianyu.util.GeoTools;
+import com.ard.tianyu.util.Vertex;
+import com.ard.tianyu.domain.dto.wayline.ArdUavPositionDTO;
+import com.ard.tianyu.domain.dto.wayline.ArdWaylineDTO;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.stereotype.Component;
+import javax.annotation.Resource;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 仿地飞行规划器
+ *
+ * @author lsy
+ * @date 2025/6/18
+ */
+@Component
+public class TerrainFollowingPlanner {
+ @Resource
+ private GeoUtils geoUtils;
+
+ private final double intervalMeter = 100.0; // 插值距离
+ private final double smoothThreshold = 3.0; // 平滑阈值
+ private static final ObjectMapper mapper = new ObjectMapper();
+
+ // 算法主函数
+ public List<ArdUavPositionDTO> interpolateWithTerrain(ArdWaylineDTO ardWaylineDTO) throws Exception {
+ // 0. 验证源航线数据
+ validateWaylineData(ardWaylineDTO);
+
+ // 1. 先进行纯几何插值(不考虑高度)
+ List<ArdUavPositionDTO> interpolatedPoints = pureInterpolation(ardWaylineDTO.getUavPositions());
+
+ // 2. 然后统一处理地形高度
+ return adjustAltitudeForTerrain(interpolatedPoints, ardWaylineDTO.getGlobalFlyHeight());
+ }
+
+ // 第一阶段:纯几何插值
+ private List<ArdUavPositionDTO> pureInterpolation(List<ArdUavPositionDTO> originalPoints) {
+ List<ArdUavPositionDTO> result = new ArrayList<>();
+
+ for (int i = 0; i < originalPoints.size() - 1; i++) {
+ ArdUavPositionDTO start = originalPoints.get(i);
+ ArdUavPositionDTO end = originalPoints.get(i + 1);
+
+ // 添加原始起点
+ result.add(deepCopy(start, ArdUavPositionDTO.class));
+
+ // 插值中间点
+ result.addAll(interpolateBetween(start, end));
+ }
+
+ // 添加最后一个点
+ result.add(deepCopy(originalPoints.get(originalPoints.size() - 1), ArdUavPositionDTO.class));
+
+ return result;
+ }
+
+ // 第二阶段:地形高度调整
+ private List<ArdUavPositionDTO> adjustAltitudeForTerrain(List<ArdUavPositionDTO> points, double flyHeight) throws IOException {
+
+ double homeTerrainHeight = 0; // 记录起飞点地形高度作为基准海拔
+ double lastFlightAltitude = flyHeight; // 记录上一个飞行高度
+
+ for (int i = 0; i < points.size(); i++) {
+ ArdUavPositionDTO point = points.get(i);
+ double currentTerrainHeight = geoUtils.getHeight(
+ point.getUavPosition().getLongitude(),
+ point.getUavPosition().getLatitude()
+ );
+ if (i == 0) {
+ homeTerrainHeight = currentTerrainHeight;
+ }
+ // 计算相对于起飞点的地形高度差
+ double terrainHeightDiff = currentTerrainHeight - homeTerrainHeight;
+
+ // 理论目标高度 = 设定高度 + 地形高度变化量
+ double targetAltitude = flyHeight + terrainHeightDiff;
+
+ // 平滑处理:如果高度变化不大,沿用上一个飞行高度
+ if (i > 0 && Math.abs(targetAltitude - lastFlightAltitude) < smoothThreshold) {
+ targetAltitude = lastFlightAltitude;
+ }
+
+ // 设置高度
+ point.getUavPosition().setAltitude(targetAltitude);
+ updateActionAltitude(point, currentTerrainHeight);
+
+ lastFlightAltitude = targetAltitude; // 更新最后飞行高度
+ }
+
+ renumberWaypoints(points);
+ return points;
+ }
+
+ // 辅助方法:两点间插值
+ private List<ArdUavPositionDTO> interpolateBetween(ArdUavPositionDTO start, ArdUavPositionDTO end) {
+ List<ArdUavPositionDTO> interpolated = new ArrayList<>();
+ // 计算两点间的距离
+ Vertex startV=new Vertex( start.getUavPosition().getLatitude(), start.getUavPosition().getLongitude());
+ Vertex endV=new Vertex( end.getUavPosition().getLatitude(), end.getUavPosition().getLongitude());
+ double distance = GeoTools.DistanceSphere(startV,endV);
+ // 如果距离小于插值间隔,则直接返回
+ if (distance <= intervalMeter) return interpolated;
+ // 计算插值步数
+ int steps = (int) (distance / intervalMeter);
+ for (int i = 1; i < steps; i++) {
+ double ratio = i * intervalMeter / distance;
+ double lat = interpolate(
+ start.getUavPosition().getLatitude(),
+ end.getUavPosition().getLatitude(),
+ ratio
+ );
+ double lon = interpolate(
+ start.getUavPosition().getLongitude(),
+ end.getUavPosition().getLongitude(),
+ ratio
+ );
+
+ ArdUavPositionDTO newPoint = deepCopy(start, ArdUavPositionDTO.class);
+ newPoint.setType(1);
+ newPoint.getUavPosition().setLatitude(lat);
+ newPoint.getUavPosition().setLongitude(lon);
+ interpolated.add(newPoint);
+ }
+
+ return interpolated;
+ }
+
+ /**
+ * 更新航点动作中的地面高度
+ *
+ * @param point 航点对象
+ * @param terrainHeight 当前地形高度
+ */
+ private void updateActionAltitude(ArdUavPositionDTO point, double terrainHeight) {
+ if (point.getActionGroup() == null || point.getActionGroup().isEmpty()) {
+ return;
+ }
+
+ for (ArdActionDTO action : point.getActionGroup()) {
+ // 如果已有地面位置,则更新高度
+ if (action.getGroundPosition() != null) {
+ action.getGroundPosition().setLongitude(point.getUavPosition().getLongitude());
+ action.getGroundPosition().setLatitude(point.getUavPosition().getLatitude());
+ action.getGroundPosition().setAltitude(terrainHeight);
+ }
+ // 如果没有地面位置,则创建新的地面位置
+ else {
+ action.setGroundPosition(ArdPositionDTO.builder()
+ .longitude(point.getUavPosition().getLongitude())
+ .latitude(point.getUavPosition().getLatitude())
+ .altitude(terrainHeight)
+ .build());
+ }
+ }
+ }
+
+ /**
+ * 重新编号航点和动作
+ *
+ * @param points 航点列表
+ */
+ private void renumberWaypoints(List<ArdUavPositionDTO> points) {
+ // 航点重新编号
+ for (int i = 0; i < points.size(); i++) {
+ ArdUavPositionDTO point = points.get(i);
+ point.setIndex(i + 1); // 通常从1开始编号
+
+ // 动作重新编号
+ if (point.getActionGroup() != null && !point.getActionGroup().isEmpty()) {
+ int actionIndex = 1;
+ for (ArdActionDTO action : point.getActionGroup()) {
+ action.setActionIndex(actionIndex++);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * 校验航线数据有效性
+ *
+ * @param ardWaylineDTO 航线数据
+ * @throws IllegalArgumentException 如果数据无效
+ */
+ private void validateWaylineData(ArdWaylineDTO ardWaylineDTO) {
+ if (ardWaylineDTO == null) {
+ throw new IllegalArgumentException("航线数据不能为null");
+ }
+ if (ardWaylineDTO.getUavPositions() == null || ardWaylineDTO.getUavPositions().size() < 2) {
+ throw new IllegalArgumentException("航点数量必须大于等于2");
+ }
+ if (ardWaylineDTO.getGlobalFlyHeight() <= 0) {
+ throw new IllegalArgumentException("全局飞行高度必须大于0");
+ }
+ }
+
+ // 通用深拷贝
+ public static <T> T deepCopy(T original, Class<T> clazz) {
+ if (original == null) {
+ return null;
+ }
+ try {
+ byte[] bytes = mapper.writeValueAsBytes(original);
+ return mapper.readValue(bytes, clazz);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ // 线性插值
+ public double interpolate(double start, double end, double ratio) {
+ return start + (end - start) * ratio;
+ }
+}
\ No newline at end of file
diff --git a/ard-work/src/main/java/com/ard/tianyu/domain/dto/wayline/ArdActionDTO.java b/ard-work/src/main/java/com/ard/tianyu/domain/dto/wayline/ArdActionDTO.java
new file mode 100644
index 0000000..50bee04
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/domain/dto/wayline/ArdActionDTO.java
@@ -0,0 +1,53 @@
+package com.ard.tianyu.domain.dto.wayline;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
+public class ArdActionDTO {
+
+ @JsonProperty("actionIndex")
+ public Integer actionIndex;
+
+ @JsonProperty("actionType")
+ public Integer actionType;
+
+ @JsonProperty("actionParameter")
+ public Integer actionParameter;
+
+ @JsonProperty("waitTime")
+ public Integer waitTime;
+
+ @JsonProperty("gimbalMode")
+ @Builder.Default
+ public Integer gimbalMode = 0;
+
+ @JsonProperty("actionGimbal")
+ public ArdGimbalDTO actionGimbal;
+
+ @JsonProperty("zoomFactor")
+ public Integer zoomFactor;
+
+ @Builder.Default
+ @JsonProperty("fieldAngle")
+ public Float fieldAngle = 66.6F;
+
+ @JsonProperty("groundPosition")
+ public ArdPositionDTO groundPosition;
+
+ //地面视场范围
+ @JsonProperty("groundViewRange")
+ private List<ArdPositionDTO> groundViewRange;
+
+}
diff --git a/ard-work/src/main/java/com/ard/tianyu/domain/dto/wayline/ArdGimbalDTO.java b/ard-work/src/main/java/com/ard/tianyu/domain/dto/wayline/ArdGimbalDTO.java
new file mode 100644
index 0000000..4c06dfe
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/domain/dto/wayline/ArdGimbalDTO.java
@@ -0,0 +1,27 @@
+package com.ard.tianyu.domain.dto.wayline;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
+public class ArdGimbalDTO {
+
+ @JsonProperty("pitch")
+ public Double pitch;
+
+ @JsonProperty("yaw")
+ public Double yaw;
+
+ @JsonProperty("roll")
+ public Double roll;
+
+}
diff --git a/ard-work/src/main/java/com/ard/tianyu/domain/dto/wayline/ArdPositionDTO.java b/ard-work/src/main/java/com/ard/tianyu/domain/dto/wayline/ArdPositionDTO.java
new file mode 100644
index 0000000..23b4fb4
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/domain/dto/wayline/ArdPositionDTO.java
@@ -0,0 +1,30 @@
+package com.ard.tianyu.domain.dto.wayline;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class ArdPositionDTO {
+
+ /**
+ * 经度
+ */
+ private Double longitude;
+
+ /**
+ * 纬度
+ */
+ private Double latitude;
+
+ /**
+ * 高度
+ */
+ @Builder.Default
+ private Double altitude = 0.0;
+
+}
diff --git a/ard-work/src/main/java/com/ard/tianyu/domain/dto/wayline/ArdUavPositionDTO.java b/ard-work/src/main/java/com/ard/tianyu/domain/dto/wayline/ArdUavPositionDTO.java
new file mode 100644
index 0000000..696fc08
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/domain/dto/wayline/ArdUavPositionDTO.java
@@ -0,0 +1,65 @@
+package com.ard.tianyu.domain.dto.wayline;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class ArdUavPositionDTO {
+ /**
+ * 索引
+ */
+ private Integer Index;
+
+ /**
+ * 位置类型
+ * 0-用户输入
+ * 1-算法生成
+ */
+ @Builder.Default
+ private Integer Type = 0;
+
+ /**
+ * 飞行路径模式
+ * 1-直线飞行(过点且到点停)
+ * 2-协调转弯(提前转弯不过点)
+ * 3-曲线飞行(到点停)
+ * 4-曲线飞行(到点不停)
+ */
+ @JsonProperty("flightPathMode")
+ @Builder.Default
+ private Integer flightPathMode = 1;
+
+ /**
+ * 偏航模式
+ */
+ @JsonProperty("headingMode")
+ @Builder.Default
+ private Integer headingMode = 0;
+
+ /**
+ * 点飞行速度
+ */
+ @JsonProperty("pointFlySpeed")
+ private Float pointFlySpeed;
+
+ /**
+ * 无人机位置
+ */
+ @JsonProperty("uavPosition")
+ private ArdPositionDTO uavPosition;
+
+ /**
+ * 动作组
+ */
+ @JsonProperty("actionGroup")
+ private List<ArdActionDTO> actionGroup;
+
+}
diff --git a/ard-work/src/main/java/com/ard/tianyu/domain/dto/wayline/ArdWaylineDTO.java b/ard-work/src/main/java/com/ard/tianyu/domain/dto/wayline/ArdWaylineDTO.java
new file mode 100644
index 0000000..7ac2492
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/domain/dto/wayline/ArdWaylineDTO.java
@@ -0,0 +1,77 @@
+package com.ard.tianyu.domain.dto.wayline;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import java.util.List;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ArdWaylineDTO {
+
+ /**
+ * 航线id
+ */
+ public String waylineId;
+
+ /**
+ * 航线名称
+ */
+ public String waylineName;
+
+ /**
+ * 航线类型
+ */
+ public Integer waylineType;
+
+ /**
+ * 无人机型号
+ */
+ private String uavModel;
+
+ /**
+ * 负载型号
+ */
+ private String payloadModel;
+
+ /**
+ * 航线飞行速度
+ */
+ public Float globalFlySpeed;
+
+ /**
+ * 航线飞行高度
+ */
+ public Float globalFlyHeight;
+
+ /**
+ * 航线长度
+ */
+ public Double waylineLength;
+
+ /**
+ * 用户航点数量
+ */
+ public Integer waylineUserPointCount;
+
+ /**
+ * 航点总数量
+ */
+ public Integer waylineAllPointCount;
+
+ /**
+ * 航线飞行时间
+ */
+ public Double waylineBaseFlyTime;
+
+ /**
+ * 航线航点集合
+ */
+ public List<ArdUavPositionDTO> uavPositions;
+
+}
diff --git a/ard-work/src/main/java/com/ard/tianyu/domain/dto/wayline/WaylineFileDTO.java b/ard-work/src/main/java/com/ard/tianyu/domain/dto/wayline/WaylineFileDTO.java
new file mode 100644
index 0000000..ed4a9de
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/domain/dto/wayline/WaylineFileDTO.java
@@ -0,0 +1,43 @@
+package com.ard.tianyu.domain.dto.wayline;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2021/12/22
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class WaylineFileDTO {
+
+ private String name;
+
+ @JsonProperty("id")
+ private String waylineId;
+
+ private String droneModelKey;
+
+ private String sign;
+
+ private List<String> payloadModelKeys;
+
+ private Boolean favorited;
+
+ private List<Integer> templateTypes;
+
+ private String objectKey;
+
+ @JsonProperty("user_name")
+ private String username;
+
+ private Long updateTime;
+}
diff --git a/ard-work/src/main/java/com/ard/tianyu/domain/entity/UavWayline.java b/ard-work/src/main/java/com/ard/tianyu/domain/entity/UavWayline.java
new file mode 100644
index 0000000..e67ca07
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/domain/entity/UavWayline.java
@@ -0,0 +1,80 @@
+package com.ard.tianyu.domain.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ard.common.annotation.Excel;
+import com.ard.common.core.domain.BaseEntity;
+
+/**
+ * 航线对象 uav_wayline
+ *
+ * @author ruoyi
+ * @date 2025-06-27
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class UavWayline extends BaseEntity
+{
+ private static final long serialVersionUID = 1L;
+
+ /** 主键 */
+ private String id;
+
+ /** 航线名称 */
+ @Excel(name = "航线名称")
+ private String name;
+
+ /** 航线类型 */
+ @Excel(name = "航线类型")
+ private Integer type;
+
+ /** 无人机型号 */
+ @Excel(name = "无人机型号")
+ private String uavModel;
+
+ /** 负载型号 */
+ @Excel(name = "负载型号")
+ private String payloadModel;
+
+ /** 全局速度 */
+ @Excel(name = "全局速度")
+ private Float globalSpeed;
+
+ /** 全局高度 */
+ @Excel(name = "全局高度")
+ private Float globalHeight;
+
+ /** 用户航点数量 */
+ @Excel(name = "用户航点数量")
+ private Integer userWaypointCount;
+
+ /** 规划航点数量 */
+ @Excel(name = "规划航点数量")
+ private Integer planWaypointCount;
+
+ /** 原始航线数据 */
+ @Excel(name = "航线数据")
+ private byte[] srcWaylineData;
+
+ /** 规划航线数据 */
+ @Excel(name = "航线数据")
+ private byte[] planWaylineData;
+
+ /** kmz地址 */
+ @Excel(name = "kmz地址")
+ private String kmzUrl;
+
+ /** 用户ID */
+ @Excel(name = "用户ID")
+ private Long userId;
+
+ /** 部门ID */
+ @Excel(name = "部门ID")
+ private Long deptId;
+}
diff --git a/ard-work/src/main/java/com/ard/tianyu/domain/enums/WaylineEnum.java b/ard-work/src/main/java/com/ard/tianyu/domain/enums/WaylineEnum.java
new file mode 100644
index 0000000..c3e84b3
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/domain/enums/WaylineEnum.java
@@ -0,0 +1,76 @@
+package com.ard.tianyu.domain.enums;
+
+public enum WaylineEnum {
+
+
+ //航向
+ coordinateTurn(1,"coordinateTurn"),
+
+ toPointAndStopWithDiscontinuityCurvature(2,"toPointAndStopWithDiscontinuityCurvature"),
+
+ toPointAndStopWithContinuityCurvature(3,"toPointAndStopWithContinuityCurvature"),
+
+ toPointAndPassWithContinuityCurvature(4,"toPointAndPassWithContinuityCurvature"),
+
+ UNKNOWN(-1, "unknown");
+
+ private int val;
+
+ private String desc;
+
+ WaylineEnum(int val, String desc) {
+ this.val = val;
+ this.desc = desc;
+ }
+
+ public int getVal() {
+ return val;
+ }
+
+ public static String getDesc(int val) {
+
+
+ if (coordinateTurn.val == val) {
+ return coordinateTurn.desc;
+ }
+
+ if (toPointAndStopWithDiscontinuityCurvature.val == val) {
+ return toPointAndStopWithDiscontinuityCurvature.desc;
+ }
+
+ if (toPointAndStopWithContinuityCurvature.val == val) {
+ return toPointAndStopWithContinuityCurvature.desc;
+ }
+
+ if (toPointAndPassWithContinuityCurvature.val == val) {
+ return toPointAndPassWithContinuityCurvature.desc;
+ }
+
+
+
+ return UNKNOWN.desc;
+ }
+
+ public static int getVal(String desc) {
+
+ if (coordinateTurn.desc.equals(desc)) {
+ return coordinateTurn.val;
+ }
+
+ if (toPointAndStopWithDiscontinuityCurvature.desc.equals(desc)) {
+ return toPointAndStopWithDiscontinuityCurvature.val;
+ }
+
+ if (toPointAndStopWithContinuityCurvature.desc.equals(desc)) {
+ return toPointAndStopWithContinuityCurvature.val;
+ }
+
+ if (toPointAndPassWithContinuityCurvature.desc.equals(desc)) {
+ return toPointAndPassWithContinuityCurvature.val;
+ }
+
+
+ return UNKNOWN.val;
+ }
+
+}
diff --git a/ard-work/src/main/java/com/ard/tianyu/mapper/UavWaylineMapper.java b/ard-work/src/main/java/com/ard/tianyu/mapper/UavWaylineMapper.java
new file mode 100644
index 0000000..9f89dd3
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/mapper/UavWaylineMapper.java
@@ -0,0 +1,71 @@
+package com.ard.tianyu.mapper;
+
+import com.ard.common.core.domain.entity.SysUser;
+import com.ard.tianyu.domain.entity.UavWayline;
+
+import java.util.List;
+
+/**
+ * 航线Mapper接口
+ *
+ * @author ruoyi
+ * @date 2025-06-27
+ */
+public interface UavWaylineMapper
+{
+ /**
+ * 查询航线
+ *
+ * @param id 航线主键
+ * @return 航线
+ */
+ public UavWayline selectUavWaylineById(String id);
+
+ /**
+ * 查询航线列表
+ *
+ * @param uavWayline 航线
+ * @return 航线集合
+ */
+ public List<UavWayline> selectUavWaylineList(UavWayline uavWayline);
+
+ /**
+ * 新增航线
+ *
+ * @param uavWayline 航线
+ * @return 结果
+ */
+ public int insertUavWayline(UavWayline uavWayline);
+
+ /**
+ * 修改航线
+ *
+ * @param uavWayline 航线
+ * @return 结果
+ */
+ public int updateUavWayline(UavWayline uavWayline);
+
+ /**
+ * 删除航线
+ *
+ * @param id 航线主键
+ * @return 结果
+ */
+ public int deleteUavWaylineById(String id);
+
+ /**
+ * 批量删除航线
+ *
+ * @param ids 需要删除的数据主键集合
+ * @return 结果
+ */
+ public int deleteUavWaylineByIds(String[] ids);
+
+ /**
+ * 校验航线是否唯一
+ *
+ * @param name 航线名称
+ * @return 结果
+ */
+ public UavWayline checkUavWaylineNameUnique(String name);
+}
diff --git a/ard-work/src/main/java/com/ard/tianyu/service/IGeoToolsService.java b/ard-work/src/main/java/com/ard/tianyu/service/IGeoToolsService.java
new file mode 100644
index 0000000..b008e41
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/service/IGeoToolsService.java
@@ -0,0 +1,16 @@
+package com.ard.tianyu.service;
+
+import com.ard.tianyu.domain.dto.wayline.ArdPositionDTO;
+import com.ard.tianyu.util.Vertex;
+import java.util.List;
+
+public interface IGeoToolsService {
+
+
+ Vertex gcj02ToTargetCoordinate(Vertex gcjVertex);
+
+ Vertex targetCoordinateToGcj02(Vertex targetVertex);
+
+ List<ArdPositionDTO> calConvexHull(List<ArdPositionDTO> polygonPoints);
+
+}
diff --git a/ard-work/src/main/java/com/ard/tianyu/service/IKmzFileService.java b/ard-work/src/main/java/com/ard/tianyu/service/IKmzFileService.java
new file mode 100644
index 0000000..7b77a7a
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/service/IKmzFileService.java
@@ -0,0 +1,13 @@
+package com.ard.tianyu.service;
+
+import com.ard.tianyu.domain.dto.wayline.ArdWaylineDTO;
+
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2021/12/22
+ */
+public interface IKmzFileService {
+ void outputKmz(ArdWaylineDTO ardWaylineDTO);
+}
diff --git a/ard-work/src/main/java/com/ard/tianyu/service/IUavWaylineService.java b/ard-work/src/main/java/com/ard/tianyu/service/IUavWaylineService.java
new file mode 100644
index 0000000..49c0df5
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/service/IUavWaylineService.java
@@ -0,0 +1,71 @@
+package com.ard.tianyu.service;
+
+import java.util.List;
+
+import com.ard.tianyu.domain.dto.wayline.ArdWaylineDTO;
+import com.ard.tianyu.domain.entity.UavWayline;
+
+/**
+ * 航线Service接口
+ *
+ * @author ruoyi
+ * @date 2025-06-27
+ */
+public interface IUavWaylineService
+{
+ /**
+ * 查询航线
+ *
+ * @param id 航线主键
+ * @return 航线
+ */
+ public ArdWaylineDTO selectUavWaylineById(String id);
+
+ /**
+ * 查询航线列表
+ *
+ * @param uavWayline 航线
+ * @return 航线集合
+ */
+ public List<UavWayline> selectUavWaylineList(UavWayline uavWayline);
+
+ /**
+ * 新增航线
+ *
+ * @param ardWaylineDTO 航线
+ * @return 结果
+ */
+ public int insertUavWayline(ArdWaylineDTO ardWaylineDTO);
+
+ /**
+ * 修改航线
+ *
+ * @param ardWaylineDTO 航线
+ * @return 结果
+ */
+ public int updateUavWayline(ArdWaylineDTO ardWaylineDTO);
+
+ /**
+ * 批量删除航线
+ *
+ * @param ids 需要删除的航线主键集合
+ * @return 结果
+ */
+ public int deleteUavWaylineByIds(String[] ids);
+
+ /**
+ * 删除航线信息
+ *
+ * @param id 航线主键
+ * @return 结果
+ */
+ public int deleteUavWaylineById(String id);
+
+ /**
+ * 校验航线是否唯一
+ *
+ * @param ardWaylineDTO 航线信息
+ * @return 结果
+ */
+ public boolean checkWaylineNameUnique(ArdWaylineDTO ardWaylineDTO);
+}
diff --git a/ard-work/src/main/java/com/ard/tianyu/service/impl/GeoToolsServiceImpl.java b/ard-work/src/main/java/com/ard/tianyu/service/impl/GeoToolsServiceImpl.java
new file mode 100644
index 0000000..bc2445c
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/service/impl/GeoToolsServiceImpl.java
@@ -0,0 +1,158 @@
+package com.ard.tianyu.service.impl;
+
+
+import com.ard.tianyu.config.GeoserverConfig;
+import com.ard.tianyu.domain.dto.wayline.ArdPositionDTO;
+import com.ard.tianyu.service.IGeoToolsService;
+import com.ard.tianyu.util.GeoTools;
+import com.ard.tianyu.util.Vertex;
+import lombok.extern.slf4j.Slf4j;
+import org.geotools.data.DefaultTransaction;
+import org.geotools.data.Transaction;
+import org.geotools.data.collection.ListFeatureCollection;
+import org.geotools.data.simple.SimpleFeatureCollection;
+import org.geotools.data.simple.SimpleFeatureSource;
+import org.geotools.data.simple.SimpleFeatureStore;
+import org.geotools.feature.simple.SimpleFeatureBuilder;
+import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
+import org.geotools.geometry.jts.JTSFactoryFinder;
+import org.geotools.referencing.crs.DefaultGeographicCRS;
+import org.locationtech.jts.geom.*;
+import org.locationtech.jts.operation.buffer.BufferParameters;
+import org.opengis.feature.simple.SimpleFeature;
+import org.opengis.feature.simple.SimpleFeatureType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+import org.springframework.web.multipart.MultipartFile;
+import java.io.*;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+@Service
+@Slf4j
+public class GeoToolsServiceImpl implements IGeoToolsService {
+
+ @Autowired
+ private GeoserverConfig geoserverConfig;
+
+ private static Double distance = 1500 * 180 / Math.PI / 6371000;
+ private static GeometryFactory geoFactory = new GeometryFactory();
+
+
+ //坐标类型转换
+ public Vertex gcj02ToTargetCoordinate(Vertex gcjVertex){
+ Vertex newVertex = new Vertex(0,0);
+ if(geoserverConfig.getDeviceCoordinateType().equals("WGS84")){
+ newVertex = GeoTools.GCJ02toWGS84(new Vertex(gcjVertex.x, gcjVertex.y));
+ }else {
+ newVertex = new Vertex(gcjVertex.x, gcjVertex.y);
+ }
+ return newVertex;
+ }
+
+ public Vertex targetCoordinateToGcj02(Vertex targetVertex){
+ Vertex newVertex = new Vertex(0,0);
+ if(geoserverConfig.getDeviceCoordinateType().equals("WGS84")){
+ newVertex = GeoTools.WGS84toGCJ02(new Vertex(targetVertex.x, targetVertex.y));
+ }else {
+ newVertex = new Vertex(targetVertex.x, targetVertex.y);
+ }
+ return newVertex;
+ }
+
+ public List<ArdPositionDTO> calConvexHull(List<ArdPositionDTO> polygonPoints){
+ //缓冲距离
+ List<ArdPositionDTO> convexHullPoints = new ArrayList<>();
+ try{
+ //用户定义区域
+ Integer pCount = polygonPoints.size();
+ //首位最后一个点
+ Coordinate[] uPoints = new Coordinate[pCount+1];
+ int i = 0;
+ for(i=0;i<pCount;i++){
+ uPoints[i] = new Coordinate(polygonPoints.get(i).getLongitude(),polygonPoints.get(i).getLatitude());
+ }
+ //将第一个点重新加入到最后,形成闭环
+ uPoints[i] = new Coordinate(polygonPoints.get(0).getLongitude(),polygonPoints.get(0).getLatitude());
+ //多边形点的个数必须大于等于3
+ if(uPoints.length >= 3){
+ //生成集合要素
+ Geometry oneGeometry = geoFactory.createPolygon(uPoints);
+ //做凸包
+ Geometry geoConvexHull = oneGeometry.convexHull();
+ Coordinate[] points = geoConvexHull.getCoordinates();
+ for (Coordinate coordinate : points) {
+ convexHullPoints.add(ArdPositionDTO.builder().longitude(coordinate.getX())
+ .latitude(coordinate.getY()).build());
+ }
+ }
+ }catch (Exception e){
+ e.printStackTrace();
+ }
+ return convexHullPoints;
+ }
+
+
+ private double meterToDegree(double threshold){
+ return threshold * 180 / Math.PI / 6371000;
+ }
+
+ public void deleteFile(File filePath) {
+ //判断路径是否存在
+ if(filePath.exists()) {
+ //判断是否是目录
+ if (filePath.isDirectory()) {
+ //将目录逐层拆分成数组元素
+ File[] files = filePath.listFiles();
+ for (File f : files) {
+ //递归调用,由最底层往上逐层删除
+ deleteFile(f);
+ }
+ }
+ //如果是文件,直接删除
+ filePath.delete();
+ }
+ }
+
+ public void zipShapeFile(String shpPath){
+ try{
+ File shpFile = new File(shpPath);
+ String shpRoot = shpFile.getParentFile().getPath(),
+ _shpName = shpFile.getName(),
+ shpName = _shpName.substring(0, _shpName.lastIndexOf("."));
+
+ String zipPath = shpRoot+File.separator+shpName+".zip";
+ File zipFile = new File(zipPath);
+ InputStream input = null;
+ ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFile));
+ // zip的名称为
+ zipOut.setComment(shpName);
+ String[] shpFiles = new String[]{
+ shpRoot+File.separator+shpName+".dbf",
+ shpRoot+File.separator+shpName+".prj",
+ shpRoot+File.separator+shpName+".shp",
+ shpRoot+File.separator+shpName+".shx",
+ };
+ for(int i=0;i<shpFiles.length;i++){
+ File _file = new File(shpFiles[i]);
+ input = new FileInputStream(_file);
+ zipOut.putNextEntry(new ZipEntry(_file.getName()));
+ int temp = 0;
+ while ((temp = input.read()) != -1) {
+ zipOut.write(temp);
+ }
+ input.close();
+ }
+ zipOut.close();
+ }catch (Exception e){
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/ard-work/src/main/java/com/ard/tianyu/service/impl/KmzFileServiceImpl.java b/ard-work/src/main/java/com/ard/tianyu/service/impl/KmzFileServiceImpl.java
new file mode 100644
index 0000000..9f60018
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/service/impl/KmzFileServiceImpl.java
@@ -0,0 +1,517 @@
+package com.ard.tianyu.service.impl;
+
+import com.ard.tianyu.component.minio.config.MinioConfig;
+import com.ard.tianyu.component.minio.utils.MinioUtil;
+import com.ard.tianyu.config.WaylinePlaningConfig;
+import com.ard.tianyu.domain.dto.wayline.*;
+import com.ard.tianyu.domain.entity.UavWayline;
+import com.ard.tianyu.domain.enums.WaylineEnum;
+import com.ard.tianyu.mapper.UavWaylineMapper;
+import com.ard.tianyu.service.IGeoToolsService;
+import com.ard.tianyu.service.IKmzFileService;
+import com.ard.tianyu.util.Vertex;
+import lombok.extern.slf4j.Slf4j;
+import org.dom4j.*;
+import org.dom4j.io.OutputFormat;
+import org.dom4j.io.XMLWriter;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import javax.annotation.Resource;
+import java.io.*;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * @author liusuyi
+ * @version 3.0
+ * @date 2025/06/27
+ */
+@Service
+@Slf4j
+@Transactional
+public class KmzFileServiceImpl implements IKmzFileService {
+
+ @Resource
+ private MinioConfig minioConfig;
+
+ @Resource
+ private IGeoToolsService geoToolsService;
+
+ @Resource
+ private WaylinePlaningConfig waylinePlaningConfig;
+
+ @Resource
+ private UavWaylineMapper uavWaylineMapper;
+
+ private Document initWayline(Boolean isTemplate) {
+ //创建Document对象
+ Document document = DocumentHelper.createDocument();
+ document.setXMLEncoding("UTF-8");
+
+ // 定义命名空间
+ String KML_NAMESPACE = "http://www.opengis.net/kml/2.2";
+ String WPML_NAMESPACE = "http://www.dji.com/wpmz/1.0.6";
+
+ //创建kml节点
+ QName kmlQName = QName.get("kml", Namespace.get("", KML_NAMESPACE));
+ Element kml = document.addElement(kmlQName);
+ //添加命名空间
+ kml.addNamespace("wpml", WPML_NAMESPACE);
+ //生成Document节点
+ Element wpml = kml.addElement("Document", KML_NAMESPACE);
+ //是否为模板
+ if (isTemplate) {
+ // Step 1: Implement File Creation Information
+ //wpml.addElement("wpml:author").addText(user);
+ wpml.addElement("wpml:createTime").addText(
+ String.valueOf(LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli()));
+ wpml.addElement("wpml:updateTime").addText(
+ String.valueOf(LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli()));
+ }
+ //返回
+ return document;
+ }
+
+ //产生kmz总体过程
+ private void gntTemplateKml(ArdWaylineDTO ardWaylineDTO, String savePath) {
+ //初始化文件头
+ Document reader = this.initWayline(true);
+ //Document元素
+ Element document = reader.getRootElement().element("Document");
+ //添加missionConfig信息
+ this.addMissionConfig(document, ardWaylineDTO);
+ //添加folder信息
+ Element folder = this.addFolder(document, ardWaylineDTO);
+ //2设置航点参数
+ List<ArdUavPositionDTO> uavPositions = ardWaylineDTO.getUavPositions();
+ //循环添加placemark
+ Integer actionGroupId = 0;
+ for (int i = 0; i < uavPositions.size(); i++) {
+ ArdUavPositionDTO onePosition = uavPositions.get(i);
+ this.addPlaceMark(folder, i, actionGroupId, onePosition);
+ if (onePosition.getActionGroup().size() > 0) {
+ actionGroupId += 1;
+ }
+ }
+ //打印输出结果
+ //log.info(reader.asXML());
+ //输出
+ String tmpKml = savePath + File.separator + "wpmz" + File.separator + "template.kml";
+ this.outputXml(tmpKml, reader);
+ }
+
+ public void gntWaylineWpml(ArdWaylineDTO ardWaylineDTO, String savePath) {
+ //初始化文件头
+ Document reader = this.initWayline(false);
+ //Document元素
+ Element document = reader.getRootElement().element("Document");
+ //添加missionConfig信息
+ this.addMissionConfig(document, ardWaylineDTO);
+ //添加folder信息
+ Element folder = this.addFolderWayline(document, ardWaylineDTO);
+ //2设置航点参数
+ //循环添加placemark
+ Integer actionGroupId = 0;
+ for (int i = 0; i < ardWaylineDTO.getUavPositions().size(); i++) {
+ ArdUavPositionDTO onePosition = ardWaylineDTO.getUavPositions().get(i);
+ this.addPlaceMarkWayline(folder, i, actionGroupId, onePosition, ardWaylineDTO);
+ if (onePosition.getActionGroup().size() > 0) {
+ actionGroupId += 1;
+ }
+ }
+ //打印输出结果
+ //log.info(reader.asXML());
+ //输出
+ String waylineWpml = savePath + File.separator + "wpmz" + File.separator + "waylines.wpml";
+ this.outputXml(waylineWpml, reader);
+ }
+
+ //missionConfig信息
+ private void addMissionConfig(Element document, ArdWaylineDTO ardWaylineDTO) {
+ //创建wpml:missionConfig元素
+ Element missionConfig = document.addElement("wpml:missionConfig");
+ //创建子元素
+ missionConfig.addElement("wpml:flyToWaylineMode").addText("safely");
+ missionConfig.addElement("wpml:finishAction").addText("goHome");
+ missionConfig.addElement("wpml:exitOnRCLost").addText("goContinue");
+ missionConfig.addElement("wpml:executeRCLostAction").addText("goBack");
+ missionConfig.addElement("wpml:takeOffSecurityHeight").addText("30");
+ //与第一个点重合
+ ArdPositionDTO firstPoint = ardWaylineDTO.getUavPositions().get(0).getUavPosition();
+ String refPoint = String.format("%s,%s,%s", firstPoint.getLatitude(), firstPoint.getLongitude()
+ , firstPoint.getAltitude());
+ missionConfig.addElement("wpml:takeOffRefPoint").addText(refPoint);
+ missionConfig.addElement("wpml:globalTransitionalSpeed").addText(String.valueOf(ardWaylineDTO.getGlobalFlySpeed()));
+ missionConfig.addElement("wpml:globalRTHHeight").addText(String.valueOf(ardWaylineDTO.getGlobalFlyHeight()));
+ //添加droneInfo
+ Element droneInfo = missionConfig.addElement("wpml:droneInfo");
+ droneInfo.addElement("wpml:droneEnumValue").addText("89");
+ droneInfo.addElement("wpml:droneSubEnumValue").addText("1");
+ //添加payloadInfo
+ Element payloadInfo = missionConfig.addElement("wpml:payloadInfo");
+ payloadInfo.addElement("wpml:payloadEnumValue").addText("43");
+ payloadInfo.addElement("wpml:payloadSubEnumValue").addText("0");
+ payloadInfo.addElement("wpml:payloadPositionIndex").addText("0");
+ }
+
+ //folder信息
+ private Element addFolder(Element document, ArdWaylineDTO ardWaylineDTO) {
+ //全局信息
+ Element folder = document.addElement("Folder");
+ folder.addElement("wpml:templateType").addText("waypoint");
+ folder.addElement("wpml:templateId").addText("0");
+ //坐标系统
+ Element waylineCoordinateSysParam = folder.addElement("wpml:waylineCoordinateSysParam");
+ waylineCoordinateSysParam.addElement("wpml:coordinateMode").addText("WGS84");
+ waylineCoordinateSysParam.addElement("wpml:heightMode").addText("relativeToStartPoint");
+ //
+ folder.addElement("wpml:autoFlightSpeed").addText(String.valueOf(ardWaylineDTO.getGlobalFlySpeed()));
+ folder.addElement("wpml:globalWaypointTurnMode").addText("toPointAndStopWithDiscontinuityCurvature");
+ folder.addElement("wpml:globalUseStraightLine").addText("1");
+ //全局航向角模式
+ Element globalWaypointHeadingParam = folder.addElement("wpml:globalWaypointHeadingParam");
+ globalWaypointHeadingParam.addElement("wpml:waypointHeadingMode").addText("followWayline");
+ globalWaypointHeadingParam.addElement("wpml:waypointHeadingYawPathMode").addText("followBadArc");
+ //
+ folder.addElement("wpml:gimbalPitchMode").addText("manual");//manual , usePointSetting
+ folder.addElement("wpml:globalHeight").addText(String.valueOf(ardWaylineDTO.getGlobalFlyHeight()));
+ //
+ return folder;
+ }
+
+ private Element addFolderWayline(Element document, ArdWaylineDTO ardWaylineDTO) {
+ //全局信息
+ Element folder = document.addElement("Folder");
+ folder.addElement("wpml:templateId").addText("0");
+ folder.addElement("wpml:executeHeightMode").addText("relativeToStartPoint");
+ folder.addElement("wpml:waylineId").addText("0");
+ folder.addElement("wpml:autoFlightSpeed").addText(String.valueOf(ardWaylineDTO.getGlobalFlySpeed()));
+ return folder;
+ }
+
+ //placemark信息
+ private void addPlaceMark(Element folder, Integer index, Integer actionGroupId, ArdUavPositionDTO onePostion) {
+ //添加placemark
+ Element placemark = folder.addElement("Placemark");
+ //转换坐标
+ //航点坐标位置转换
+ Double tLng = 0.0;
+ Double tLat = 0.0;
+ //转换坐标,将目标坐标转化为高德坐标
+ if (onePostion.getUavPosition().getLongitude() != null &&
+ onePostion.getUavPosition().getLatitude() != null) {
+ Vertex newVertex = geoToolsService.gcj02ToTargetCoordinate(
+ new Vertex(onePostion.getUavPosition().getLongitude(),
+ onePostion.getUavPosition().getLatitude()));
+ tLng = newVertex.x;
+ tLat = newVertex.y;
+ }
+ Element point = placemark.addElement("Point");
+ point.addElement("coordinates").addText(tLng + "," + tLat);
+ //添加一般元素
+ placemark.addElement("wpml:index").addText(String.valueOf(index));
+ placemark.addElement("wpml:ellipsoidHeight").addText(String.valueOf(onePostion.getUavPosition().getAltitude()));
+ placemark.addElement("wpml:height").addText(String.valueOf(onePostion.getUavPosition().getAltitude()));
+ //全局信息
+ placemark.addElement("wpml:useGlobalHeight").addText("1");
+ placemark.addElement("wpml:useGlobalSpeed").addText("1");
+ placemark.addElement("wpml:useGlobalHeadingParam").addText("1");
+ placemark.addElement("wpml:useGlobalTurnParam").addText("1");
+ placemark.addElement("wpml:useStraightLine").addText("1");
+ //添加actionGroup
+ if (onePostion.getActionGroup().size() > 0) {
+ Element actionGroup = placemark.addElement("wpml:actionGroup");
+ //添加动作组基础信息
+ actionGroup.addElement("wpml:actionGroupId").addText(String.valueOf(actionGroupId));
+ actionGroup.addElement("wpml:actionGroupStartIndex").addText(String.valueOf(index));
+ actionGroup.addElement("wpml:actionGroupEndIndex").addText(String.valueOf(index));
+ actionGroup.addElement("wpml:actionGroupMode").addText("sequence");
+ Element actionTrigger = actionGroup.addElement("wpml:actionTrigger");
+ actionTrigger.addElement("wpml:actionTriggerType").addText("reachPoint");
+ //定义action
+ Integer actionId = 0;
+ for (int j = 0; j < onePostion.getActionGroup().size(); j++) {
+ //逐个添加action
+ Integer actionCount = this.addAction(actionGroup, actionId, onePostion.getActionGroup().get(j));
+ actionId += actionCount;
+ }
+ }
+ }
+
+ //placemark信息
+ private void addPlaceMarkWayline(Element folder, Integer index, Integer actionGroupId,
+ ArdUavPositionDTO onePostion, ArdWaylineDTO ardWaylineDTO) {
+ //添加placemark
+ Element placemark = folder.addElement("Placemark");
+ //转换坐标
+ //航点坐标位置转换
+ Double tLng = 0.0;
+ Double tLat = 0.0;
+ //转换坐标,将目标坐标转化为高德坐标
+ if (onePostion.getUavPosition().getLongitude() != null &&
+ onePostion.getUavPosition().getLatitude() != null) {
+ Vertex newVertex = geoToolsService.gcj02ToTargetCoordinate(
+ new Vertex(onePostion.getUavPosition().getLongitude(),
+ onePostion.getUavPosition().getLatitude()));
+ tLng = newVertex.x;
+ tLat = newVertex.y;
+ }
+ Element point = placemark.addElement("Point");
+ point.addElement("coordinates").addText(tLng + "," + tLat);
+ //添加一般元素
+ placemark.addElement("wpml:index").addText(String.valueOf(index));
+ placemark.addElement("wpml:executeHeight").addText(String.valueOf(onePostion.getUavPosition().getAltitude()));
+ placemark.addElement("wpml:waypointSpeed").addText(String.valueOf(ardWaylineDTO.getGlobalFlySpeed()));
+ Element waypointHeadingParam = placemark.addElement("wpml:waypointHeadingParam");
+ waypointHeadingParam.addElement("wpml:waypointHeadingMode").addText("followWayline");
+ Element waypointTurnParam = placemark.addElement("wpml:waypointTurnParam");
+ waypointTurnParam.addElement("wpml:waypointTurnMode").addText(WaylineEnum.getDesc(onePostion.getFlightPathMode()));
+ waypointTurnParam.addElement("wpml:waypointTurnDampingDist").addText("0");
+ //添加actionGroup
+ if (onePostion.getActionGroup().size() > 0) {
+ Element actionGroup = placemark.addElement("wpml:actionGroup");
+ //添加动作组基础信息
+ actionGroup.addElement("wpml:actionGroupId").addText(String.valueOf(actionGroupId));
+ actionGroup.addElement("wpml:actionGroupStartIndex").addText(String.valueOf(index));
+ actionGroup.addElement("wpml:actionGroupEndIndex").addText(String.valueOf(index));
+ actionGroup.addElement("wpml:actionGroupMode").addText("sequence");
+ Element actionTrigger = actionGroup.addElement("wpml:actionTrigger");
+ actionTrigger.addElement("wpml:actionTriggerType").addText("reachPoint");
+ //定义action
+ Integer actionId = 0;
+ for (int j = 0; j < onePostion.getActionGroup().size(); j++) {
+ //逐个添加action
+ Integer actionCount = this.addAction(actionGroup, actionId, onePostion.getActionGroup().get(j));
+ actionId += actionCount;
+ }
+ }
+ }
+ //action
+ private Integer addAction(Element actionGroup, Integer index, ArdActionDTO actionDTO) {
+ Integer actionCount = 2;
+ //变焦动作
+ Element actionGimbal = actionGroup.addElement("wpml:action");
+ actionGimbal.addElement("wpml:actionId").addText(String.valueOf(index));
+ actionGimbal.addElement("wpml:actionActuatorFunc").addText("gimbalRotate");
+ Element param = actionGimbal.addElement("wpml:actionActuatorFuncParam");
+ param.addElement("wpml:payloadPositionIndex").addText(String.valueOf(0));
+ param.addElement("wpml:gimbalHeadingYawBase").addText("north");
+ param.addElement("wpml:gimbalRotateMode").addText("absoluteAngle");
+ param.addElement("wpml:gimbalPitchRotateEnable").addText(String.valueOf(1));
+ param.addElement("wpml:gimbalPitchRotateAngle").addText(String.valueOf(actionDTO.getActionGimbal().getPitch()));
+ param.addElement("wpml:gimbalRollRotateEnable").addText(String.valueOf(0));
+ param.addElement("wpml:gimbalRollRotateAngle").addText(String.valueOf(0));
+ param.addElement("wpml:gimbalYawRotateEnable").addText(String.valueOf(1));
+ param.addElement("wpml:gimbalYawRotateAngle").addText(String.valueOf(actionDTO.getActionGimbal().getYaw()));
+ param.addElement("wpml:gimbalRotateTimeEnable").addText(String.valueOf(0));
+ param.addElement("wpml:gimbalRotateTime").addText(String.valueOf(0));
+ //动作类型
+ switch (actionDTO.actionType) {
+ case 0://拍照
+ Element action0 = actionGroup.addElement("wpml:action");
+ action0.addElement("wpml:actionId").addText(String.valueOf(index + 1));
+ action0.addElement("wpml:actionActuatorFunc").addText("takePhoto");
+ Element param0 = action0.addElement("wpml:actionActuatorFuncParam");
+ param0.addElement("wpml:fileSuffix").addText("point");
+ param0.addElement("wpml:payloadPositionIndex").addText(String.valueOf(0));
+ break;
+ case 1://录像,拆解为3个动作
+ //添加开始录像动作
+ Element action11 = actionGroup.addElement("wpml:action");
+ action11.addElement("wpml:actionId").addText(String.valueOf(index + 1));
+ action11.addElement("wpml:actionActuatorFunc").addText("startRecord");
+ Element param11 = action11.addElement("wpml:actionActuatorFuncParam");
+ param11.addElement("wpml:fileSuffix").addText("point");
+ param11.addElement("wpml:payloadPositionIndex").addText(String.valueOf(0));
+ //添加新的悬停动作
+ Element action12 = actionGroup.addElement("wpml:action");
+ action12.addElement("wpml:actionId").addText(String.valueOf(index + 2));
+ action12.addElement("wpml:actionActuatorFunc").addText("hover");
+ Element param12 = action12.addElement("wpml:actionActuatorFuncParam");
+ param12.addElement("wpml:hoverTime").addText(String.valueOf(actionDTO.getActionParameter()));
+ //添加结束动作
+ Element action13 = actionGroup.addElement("wpml:action");
+ action13.addElement("wpml:actionId").addText(String.valueOf(index + 3));
+ action13.addElement("wpml:actionActuatorFunc").addText("stopRecord");
+ Element param13 = action13.addElement("wpml:actionActuatorFuncParam");
+ param13.addElement("wpml:payloadPositionIndex").addText(String.valueOf(0));
+ actionCount = 5;
+ break;
+ case 2://悬停 ,协议是3,前端发送为2
+ Element action2 = actionGroup.addElement("wpml:action");
+ action2.addElement("wpml:actionId").addText(String.valueOf(index + 1));
+ action2.addElement("wpml:actionActuatorFunc").addText("hover");
+ Element param2 = action2.addElement("wpml:actionActuatorFuncParam");
+ param2.addElement("wpml:hoverTime").addText(String.valueOf(actionDTO.getWaitTime()));
+ break;
+ default:
+ actionCount = 0;
+ log.info("未知的动作类型{}!", actionDTO.actionType);
+ break;
+ }
+ return actionCount;
+ }
+
+ //输出模板kml
+ private void outputXml(String vpath, Document doc) {
+ try {
+ OutputFormat format = OutputFormat.createPrettyPrint();
+ format.setEncoding("UTF-8"); //FileWriter改为FileOutputStream解决乱码问题
+ XMLWriter xmlWriter = new XMLWriter(new FileOutputStream(vpath), format);
+ xmlWriter.write(doc);
+ xmlWriter.close();
+ } catch (Exception e) {
+ log.error("输出航线文件失败:{}", vpath);
+ e.printStackTrace();
+ }
+ }
+
+ //创建路径
+ private String createSavePath(String waylineName) {
+ //创建航线名称路径
+ String pathStr = waylinePlaningConfig.getWaylineSavePath() + File.separator + waylineName;
+ File savePath = new File(pathStr);
+ if (!savePath.exists()) {
+ savePath.mkdirs();
+ } else {
+ deleteFile(savePath);
+ savePath.mkdirs();
+ }
+ //创建wpmz
+ File wpmz = new File(pathStr + File.separator + "wpmz");
+ wpmz.mkdir();
+ //创建res
+ File res = new File(pathStr + File.separator + "wpmz" + File.separator + "res");
+ res.mkdir();
+ return pathStr;
+ }
+
+ private void deleteFile(File filePath) {
+ //判断路径是否存在
+ if (filePath.exists()) {
+ //判断是否是目录
+ if (filePath.isDirectory()) {
+ //将目录逐层拆分成数组元素
+ File[] files = filePath.listFiles();
+ for (File f : files) {
+ //递归调用,由最底层往上逐层删除
+ deleteFile(f);
+ }
+ }
+ //如果是文件,直接删除
+ filePath.delete();
+ }
+ }
+
+ //输出MKZ过程
+ @Override
+ @Async
+ public void outputKmz(ArdWaylineDTO ardWaylineDTO) {
+ try {
+ //创建目录
+ String savePath = this.createSavePath(ardWaylineDTO.getWaylineName());
+ //输出模板kml
+ this.gntTemplateKml(ardWaylineDTO, savePath);
+ //输出航线wpml
+ this.gntWaylineWpml(ardWaylineDTO, savePath);
+ //压缩输出成kmz
+ String fileName = ardWaylineDTO.getWaylineName() + ".kmz";
+ String kmzPath = waylinePlaningConfig.getWaylineSavePath() + File.separator + fileName;
+ //输出kmz文件,是否成功
+ Boolean isSuccess = this.toKmz(kmzPath, savePath + File.separator + "wpmz",
+ true);
+ if (isSuccess) {
+ //上传kmz文件到对象数据库
+ InputStream inputStream = new FileInputStream(kmzPath);
+ //构建记录
+ String objectKey = fileName;
+ //若已经存在,则先删除,再添加
+ if (MinioUtil.exists(minioConfig.getBucketName(), objectKey))
+ MinioUtil.delete(minioConfig.getBucketName(), objectKey);
+ String kmzUrl = MinioUtil.uploadInputStream(minioConfig.getBucketName(), objectKey, inputStream,
+ inputStream.available(), "application/octet-stream");
+ // 更新航线kmz地址
+ UavWayline uavWayline = UavWayline.builder()
+ .id(ardWaylineDTO.waylineId)
+ .kmzUrl(kmzUrl)
+ .build();
+ uavWaylineMapper.updateUavWayline(uavWayline);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ log.info("生成kmz文件失败!");
+ }
+ }
+
+ private Boolean toKmz(String zipFileName, String sourceFileName, boolean KeepDirStructure) {
+ Boolean result = true;
+ long start = System.currentTimeMillis();//开始
+ ZipOutputStream zos = null;
+ try {
+ FileOutputStream fileOutputStream = new FileOutputStream(zipFileName);
+ zos = new ZipOutputStream(fileOutputStream);
+ File sourceFile = new File(sourceFileName);
+ compress(sourceFile, zos, sourceFile.getName(), KeepDirStructure);
+ long end = System.currentTimeMillis();//结束
+ System.out.println("压缩完成,耗时:" + (end - start) + " 毫秒");
+ } catch (Exception e) {
+ result = false;
+ e.printStackTrace();
+ } finally {
+ if (zos != null) {
+ try {
+ zos.close();
+ } catch (IOException e) {
+ e.getStackTrace();
+ }
+ }
+ }
+ return result;
+ }
+
+ private void compress(File sourceFile, ZipOutputStream zos, String name,
+ boolean KeepDirStructure) throws Exception {
+ byte[] buf = new byte[2 * 1024];
+ if (sourceFile.isFile()) {
+ // 向zip输出流中添加一个zip实体,构造器中name为zip实体的文件的名字
+ zos.putNextEntry(new ZipEntry(name));
+ // copy文件到zip输出流中
+ int len;
+ FileInputStream in = new FileInputStream(sourceFile);
+ while ((len = in.read(buf)) != -1) {
+ zos.write(buf, 0, len);
+ }
+ // Complete the entry
+ zos.closeEntry();
+ in.close();
+ } else {
+ File[] listFiles = sourceFile.listFiles();
+ if (listFiles == null || listFiles.length == 0) {
+ // 需要保留原来的文件结构时,需要对空文件夹进行处理
+ if (KeepDirStructure) {
+ // 空文件夹的处理
+ zos.putNextEntry(new ZipEntry(name + "/"));
+ // 没有文件,不需要文件的copy
+ zos.closeEntry();
+ }
+ } else {
+ for (File file : listFiles) {
+ // 判断是否需要保留原来的文件结构
+ if (KeepDirStructure) {
+ // 注意:file.getName()前面需要带上父文件夹的名字加一斜杠,
+ // 不然最后压缩包中就不能保留原来的文件结构,即:所有文件都跑到压缩包根目录下了
+ compress(file, zos, name + "/" + file.getName(), KeepDirStructure);
+ } else {
+ compress(file, zos, file.getName(), KeepDirStructure);
+ }
+
+ }
+ }
+ }
+ }
+
+}
diff --git a/ard-work/src/main/java/com/ard/tianyu/service/impl/UavWaylineServiceImpl.java b/ard-work/src/main/java/com/ard/tianyu/service/impl/UavWaylineServiceImpl.java
new file mode 100644
index 0000000..eaa88b8
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/service/impl/UavWaylineServiceImpl.java
@@ -0,0 +1,278 @@
+package com.ard.tianyu.service.impl;
+
+import com.ard.common.constant.UserConstants;
+import com.ard.common.core.domain.entity.SysUser;
+import com.ard.common.utils.DateUtils;
+import com.ard.common.utils.StringUtils;
+import com.ard.common.utils.uuid.IdUtils;
+import com.ard.tianyu.controller.wayline.terrain.TerrainFollowingPlanner;
+import com.ard.tianyu.domain.dto.wayline.ArdUavPositionDTO;
+import com.ard.tianyu.domain.dto.wayline.ArdWaylineDTO;
+import com.ard.tianyu.domain.entity.UavWayline;
+import com.ard.tianyu.mapper.UavWaylineMapper;
+import com.ard.tianyu.service.IUavWaylineService;
+import com.ard.tianyu.service.IKmzFileService;
+import com.ard.tianyu.util.GeoTools;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * 航线Service业务层处理
+ *
+ * @author ruoyi
+ * @date 2025-06-27
+ */
+@Slf4j
+@Service
+public class UavWaylineServiceImpl implements IUavWaylineService {
+ ObjectMapper mapper = new ObjectMapper();
+
+ @Resource
+ private UavWaylineMapper uavWaylineMapper;
+ @Resource
+ private IKmzFileService waylineFileService;
+ @Resource
+ private TerrainFollowingPlanner terrainFollowingPlanner;
+
+ /**
+ * 查询航线
+ *
+ * @param id 航线主键
+ * @return 航线
+ */
+ @Override
+ public ArdWaylineDTO selectUavWaylineById(String id) {
+ UavWayline uavWayline = uavWaylineMapper.selectUavWaylineById(id);
+ return EntityConverterTowaylineDTO(uavWayline);
+ }
+
+ /**
+ * 查询航线列表
+ *
+ * @param uavWayline 航线
+ * @return 航线
+ */
+ @Override
+ public List<UavWayline> selectUavWaylineList(UavWayline uavWayline) {
+ return uavWaylineMapper.selectUavWaylineList(uavWayline);
+ }
+
+ /**
+ * 新增航线
+ *
+ * @param ardWaylineDTO 航线
+ * @return 结果
+ */
+ @Override
+ public int insertUavWayline(ArdWaylineDTO ardWaylineDTO) {
+ UavWayline uavWayline = waylineDTOConverterToEntity(ardWaylineDTO);
+ uavWayline.setCreateTime(DateUtils.getNowDate());
+ int row = uavWaylineMapper.insertUavWayline(uavWayline);
+ if (row > 0) {
+ waylineFileService.outputKmz(ardWaylineDTO);
+ }
+ return row;
+ }
+
+ /**
+ * 修改航线
+ *
+ * @param ardWaylineDTO 航线
+ * @return 结果
+ */
+ @Override
+ public int updateUavWayline(ArdWaylineDTO ardWaylineDTO) {
+ UavWayline uavWayline = waylineDTOConverterToEntity(ardWaylineDTO);
+ uavWayline.setUpdateTime(DateUtils.getNowDate());
+ int row = uavWaylineMapper.updateUavWayline(uavWayline);
+ if (row > 0) {
+ waylineFileService.outputKmz(ardWaylineDTO);
+ }
+ return row;
+ }
+
+ /**
+ * 批量删除航线
+ *
+ * @param ids 需要删除的航线主键
+ * @return 结果
+ */
+ @Override
+ public int deleteUavWaylineByIds(String[] ids) {
+ return uavWaylineMapper.deleteUavWaylineByIds(ids);
+ }
+
+ /**
+ * 删除航线信息
+ *
+ * @param id 航线主键
+ * @return 结果
+ */
+ @Override
+ public int deleteUavWaylineById(String id) {
+ return uavWaylineMapper.deleteUavWaylineById(id);
+ }
+
+ /**
+ * 校验航线是否唯一
+ *
+ * @param ardWaylineDTO 航线信息
+ * @return 结果
+ */
+ @Override
+ public boolean checkWaylineNameUnique(ArdWaylineDTO ardWaylineDTO)
+ {
+ String waylineId = ardWaylineDTO.getWaylineId();
+ UavWayline info = uavWaylineMapper.checkUavWaylineNameUnique(ardWaylineDTO.getWaylineName());
+ if (StringUtils.isNotNull(info) && !info.getId().equals(waylineId))
+ {
+ return UserConstants.NOT_UNIQUE;
+ }
+ return UserConstants.UNIQUE;
+ }
+
+ // DTO转换为实体类
+ private UavWayline waylineDTOConverterToEntity(ArdWaylineDTO ardWaylineDTO) {
+ UavWayline uavWayline = UavWayline.builder()
+ .id(Optional.ofNullable(ardWaylineDTO.getWaylineId()).orElse(IdUtils.simpleUUID()))
+ .name(ardWaylineDTO.getWaylineName())
+ .type(ardWaylineDTO.getWaylineType())
+ .uavModel(ardWaylineDTO.getUavModel())
+ .payloadModel(ardWaylineDTO.getPayloadModel())
+ .userWaypointCount(ardWaylineDTO.waylineUserPointCount)
+ .globalSpeed(ardWaylineDTO.getGlobalFlySpeed())
+ .globalHeight(ardWaylineDTO.getGlobalFlyHeight())
+ .build();
+ generateWaylineBytes(ardWaylineDTO, uavWayline);
+ return uavWayline;
+ }
+
+ // 实体类转换DTO
+ private ArdWaylineDTO EntityConverterTowaylineDTO(UavWayline uavWayline) {
+ try {
+ ArdWaylineDTO ardWaylineDTO = ArdWaylineDTO.builder()
+ .waylineId(uavWayline.getId())
+ .waylineName(uavWayline.getName())
+ .waylineType(uavWayline.getType())
+ .uavModel(uavWayline.getUavModel())
+ .payloadModel(uavWayline.getPayloadModel())
+ .waylineUserPointCount(uavWayline.getUserWaypointCount())
+ .waylineAllPointCount(uavWayline.getPlanWaypointCount())
+ .globalFlySpeed(uavWayline.getGlobalSpeed())
+ .globalFlyHeight(uavWayline.getGlobalHeight())
+ .build();
+ byte[] planWaylineBytes = uavWayline.getPlanWaylineData();
+ ardWaylineDTO = mapper.readValue(planWaylineBytes, ArdWaylineDTO.class);
+
+ //计算长度
+ double length = calculateWaylineLength(ardWaylineDTO);
+ ardWaylineDTO.setWaylineLength(length);
+ //计算飞行时间
+ double flyTime = calculateFlyTime(ardWaylineDTO);
+ ardWaylineDTO.setWaylineBaseFlyTime(flyTime);
+ return ardWaylineDTO;
+ } catch (IOException e) {
+ throw new RuntimeException("恢复航线对象错误!" + e.getMessage());
+ }
+ }
+
+ // 生成航线字节数组
+ private void generateWaylineBytes(ArdWaylineDTO ardWaylineDTO, UavWayline uavWayline) {
+ try {
+ ardWaylineDTO.setWaylineId(uavWayline.getId());
+ //二进制航线编码,保存原始航线数据
+ byte[] waylineBytes = mapper.writeValueAsBytes(ardWaylineDTO);
+ uavWayline.setSrcWaylineData(waylineBytes);
+ if (ardWaylineDTO.getWaylineType() == 1) {
+ log.info("面向逐点的航线规划!");
+ byte[] newWaylineBytes = mapper.writeValueAsBytes(ardWaylineDTO);
+ uavWayline.setPlanWaylineData(newWaylineBytes);
+ uavWayline.setPlanWaypointCount(ardWaylineDTO.getWaylineUserPointCount());
+ } else if (ardWaylineDTO.getWaylineType() == 3) {
+ log.info("面向仿地的航线规划!");
+ //仿地飞行算法
+ ArdWaylineDTO newWayline = terrainFollowingWayline(ardWaylineDTO);
+ byte[] newWaylineBytes = mapper.writeValueAsBytes(newWayline);
+ uavWayline.setPlanWaylineData(newWaylineBytes);
+ uavWayline.setPlanWaypointCount(newWayline.getWaylineUserPointCount());
+ } else {
+ log.error("航线设置错误:{}", ardWaylineDTO.getWaylineType());
+ throw new RuntimeException("航线类型错误" + ardWaylineDTO.getWaylineType());
+ }
+ } catch (Exception e) {
+ log.error("航线规划失败:{}", e.getMessage());
+ throw new RuntimeException("航线规划失败!" + e.getMessage());
+ }
+ }
+
+ //调用仿地航线算法
+ private ArdWaylineDTO terrainFollowingWayline(ArdWaylineDTO ardWaylineDTO) throws Exception {
+
+ ArdWaylineDTO newWaylineDTO = deepCopyWayline(ardWaylineDTO);
+ //使用仿地飞行规划器
+ List<ArdUavPositionDTO> ardUavPositionDTOS = terrainFollowingPlanner.interpolateWithTerrain(ardWaylineDTO);
+ newWaylineDTO.setWaylineAllPointCount(ardUavPositionDTOS.size());//总航点数量
+ long userPointCount = ardUavPositionDTOS.stream()
+ .filter(p -> p.getType() == 0)
+ .count();
+ newWaylineDTO.setWaylineUserPointCount((int) userPointCount);//用户航点数量
+ newWaylineDTO.setUavPositions(ardUavPositionDTOS);
+ // 规划成功,返回结果
+ return newWaylineDTO;
+
+ }
+
+ private ArdWaylineDTO deepCopyWayline(ArdWaylineDTO ardWaylineDTO) {
+
+ ArdWaylineDTO newWaylineDTO = null;
+ try {
+ byte[] waylineBytes = mapper.writeValueAsBytes(ardWaylineDTO);
+
+ newWaylineDTO = mapper.readValue(waylineBytes, ArdWaylineDTO.class);
+
+ } catch (JsonProcessingException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return newWaylineDTO;
+ }
+
+ //计算航线长度
+ public double calculateWaylineLength( ArdWaylineDTO ardWaylineDTO) {
+ List<ArdUavPositionDTO> uavPositions = ardWaylineDTO.getUavPositions();
+ if (uavPositions == null || uavPositions.size() < 2) {
+ return 0.0;
+ }
+
+ double totalLength = 0.0;
+ for (int i = 1; i < uavPositions.size(); i++) {
+ ArdUavPositionDTO prev = uavPositions.get(i - 1);
+ ArdUavPositionDTO curr = uavPositions.get(i);
+ totalLength += GeoTools.calculateDistance(
+ prev.getUavPosition().getLatitude(), prev.getUavPosition().getLongitude(),
+ curr.getUavPosition().getLatitude(), curr.getUavPosition().getLongitude()
+ );
+ }
+ return totalLength;
+ }
+ //计算飞行时间
+ public double calculateFlyTime(ArdWaylineDTO ardWaylineDTO) {
+ double totalLength = calculateWaylineLength(ardWaylineDTO); // 单位:米
+ Float globalSpeed = ardWaylineDTO.getGlobalFlySpeed(); // 单位:米/秒
+
+ if (globalSpeed == null || globalSpeed <= 0) {
+ throw new IllegalArgumentException("全局速度无效,必须大于0");
+ }
+ return totalLength / globalSpeed; // 返回时间,单位:秒
+ }
+
+
+}
diff --git a/ard-work/src/main/java/com/ard/tianyu/util/CameraUtil.java b/ard-work/src/main/java/com/ard/tianyu/util/CameraUtil.java
index f30ce8e..32362ce 100644
--- a/ard-work/src/main/java/com/ard/tianyu/util/CameraUtil.java
+++ b/ard-work/src/main/java/com/ard/tianyu/util/CameraUtil.java
@@ -66,10 +66,4 @@
}
return zoomFactor;
}
-
-
- public static void main(String[] args) {
- Float fieldAngleFromZoom = getFieldAngleFromZoom(1, 3F);
- System.out.println(fieldAngleFromZoom);
- }
}
\ No newline at end of file
diff --git a/ard-work/src/main/java/com/ard/tianyu/util/GeoTools.java b/ard-work/src/main/java/com/ard/tianyu/util/GeoTools.java
new file mode 100644
index 0000000..a407484
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/util/GeoTools.java
@@ -0,0 +1,98 @@
+package com.ard.tianyu.util;
+
+public class GeoTools {
+ public static double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
+ final double R = 6371000; // 地球半径,单位:米
+
+ double radLat1 = Math.toRadians(lat1);
+ double radLat2 = Math.toRadians(lat2);
+ double deltaLat = radLat2 - radLat1;
+ double deltaLon = Math.toRadians(lon2 - lon1);
+
+ double a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) +
+ Math.cos(radLat1) * Math.cos(radLat2) *
+ Math.sin(deltaLon / 2) * Math.sin(deltaLon / 2);
+
+ double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+
+ return R * c;
+ }
+
+ // 经纬度坐标球面距离计算
+ public static double DistanceSphere(Vertex v1, Vertex v2) {
+ double radLat1 = Math.toRadians(v1.y);
+ double radLat2 = Math.toRadians(v2.y);
+ double a = Math.abs(radLat1 - radLat2);
+ double b = Math.abs(Math.toRadians(v1.x) - Math.toRadians(v2.x));
+ double h = Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1)
+ * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2);
+ double earthRadius = 6371000;
+ return 2 * earthRadius * Math.asin(Math.sqrt(h));
+ }
+ /*
+ * 高德坐标系转wgs84
+ */
+ public static Vertex GCJ02toWGS84(Vertex v_gcj) {
+ Vertex temp = WGS84toGCJ02(v_gcj);
+ double lng_wgs = v_gcj.x * 2 - temp.x;
+ double lat_wgs = v_gcj.y * 2 - temp.y;
+
+ return new Vertex(lng_wgs, lat_wgs);
+ }
+ /*
+ * wgs84转高德坐标系
+ */
+ public static Vertex WGS84toGCJ02(Vertex v_wgs) {
+ // 一些常数
+ double a = 6378245.0; // 长半轴
+ double ee = 0.00669342162296594323; // 扁率
+ double lng = v_wgs.x;
+ double lat = v_wgs.y;
+
+ //判断是否在国内
+ if (OutOfChina(v_wgs))
+ return v_wgs;
+ double dlat = transformlat(lng - 105.0, lat - 35.0);
+ double dlng = transformlng(lng - 105.0, lat - 35.0);
+ double radlat = lat / 180.0 * Math.PI;
+ double magic = Math.sin(radlat);
+ magic = 1 - ee * magic * magic;
+ double sqrtmagic = Math.sqrt(magic);
+ dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * Math.PI);
+ dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * Math.PI);
+
+ return new Vertex(lng + dlng, lat + dlat);
+ }
+ /*
+ * 判断点(经纬度)是否在国内,不在国内不做偏移
+ */
+ public static boolean OutOfChina(Vertex v) {
+ if (v.x < 72.004 || v.x > 137.8347)
+ return true;
+ if (v.y < 0.8293 || v.y > 55.8271)
+ return true;
+ return false;
+ }
+ public static double transformlat(double lng, double lat) {
+ double ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat +
+ 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));
+ ret += (20.0 * Math.sin(6.0 * lng * Math.PI) + 20.0 *
+ Math.sin(2.0 * lng * Math.PI)) * 2.0 / 3.0;
+ ret += (20.0 * Math.sin(lat * Math.PI) + 40.0 *
+ Math.sin(lat / 3.0 * Math.PI)) * 2.0 / 3.0;
+ ret += (160.0 * Math.sin(lat / 12.0 * Math.PI) + 320 *
+ Math.sin(lat * Math.PI / 30.0)) * 2.0 / 3.0;
+ return ret;
+ }
+ public static double transformlng(double lng, double lat) {
+ double ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng +
+ 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng));
+ ret += (20.0 * Math.sin(6.0 * lng * Math.PI) + 20.0 *
+ Math.sin(2.0 * lng * Math.PI)) * 2.0 / 3.0;
+ ret += (20.0 * Math.sin(lng * Math.PI) + 40.0 *
+ Math.sin(lng / 3.0 * Math.PI)) * 2.0 / 3.0;
+ ret += (150.0 * Math.sin(lng / 12.0 * Math.PI) + 300.0 *
+ Math.sin(lng / 30.0 * Math.PI)) * 2.0 / 3.0;
+ return ret;
+ }
+}
\ No newline at end of file
diff --git a/ard-work/src/main/java/com/ard/tianyu/util/Vertex.java b/ard-work/src/main/java/com/ard/tianyu/util/Vertex.java
new file mode 100644
index 0000000..6af8ec2
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/util/Vertex.java
@@ -0,0 +1,29 @@
+package com.ard.tianyu.util;
+
+public class Vertex {
+ public double x;
+ public double y;
+
+ public Vertex() {
+
+ }
+
+ public Vertex(double x, double y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ // 判断这个点是不是和另外一个点重合(允许极小的误差,算作同一个点
+ public boolean IsSame(Vertex anotherV) {
+ double almostZero = 0.00000001;
+ if ((Math.abs(x - anotherV.x) < almostZero) && (Math.abs(y - anotherV.y) < almostZero))
+ return true;
+ else
+ return false;
+ }
+
+ // 直线距离
+ public double Distance(Vertex anotherV) {
+ return Math.sqrt(Math.pow(x - anotherV.x, 2) + Math.pow(y - anotherV.y, 2));
+ }
+}
\ No newline at end of file
diff --git a/ard-work/src/main/resources/mapper/UavWaylineMapper.xml b/ard-work/src/main/resources/mapper/UavWaylineMapper.xml
new file mode 100644
index 0000000..2fab33d
--- /dev/null
+++ b/ard-work/src/main/resources/mapper/UavWaylineMapper.xml
@@ -0,0 +1,161 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ard.tianyu.mapper.UavWaylineMapper">
+
+ <resultMap type="UavWayline" id="UavWaylineResult">
+ <result property="id" column="id" />
+ <result property="name" column="name" />
+ <result property="type" column="type" />
+ <result property="uavModel" column="uav_model" />
+ <result property="payloadModel" column="payload_model" />
+ <result property="globalSpeed" column="global_speed" />
+ <result property="globalHeight" column="global_height" />
+ <result property="userWaypointCount" column="user_waypoint_count" />
+ <result property="planWaypointCount" column="plan_waypoint_count" />
+ <result property="srcWaylineData" column="src_wayline_data" />
+ <result property="planWaylineData" column="plan_wayline_data" />
+ <result property="kmzUrl" column="kmz_url" />
+ <result property="userId" column="user_id" />
+ <result property="deptId" column="dept_id" />
+ <result property="createBy" column="create_by" />
+ <result property="createTime" column="create_time" />
+ <result property="updateBy" column="update_by" />
+ <result property="updateTime" column="update_time" />
+ <result property="remark" column="remark" />
+ </resultMap>
+
+ <sql id="selectUavWaylineVo">
+ select id,
+ name,
+ type,
+ uav_model,
+ payload_model,
+ global_speed,
+ global_height,
+ user_waypoint_count,
+ plan_waypoint_count,
+ src_wayline_data,
+ plan_wayline_data,
+ kmz_url,
+ user_id,
+ dept_id,
+ create_by,
+ create_time,
+ update_by,
+ update_time,
+ remark
+ from uav_wayline
+ </sql>
+
+ <select id="selectUavWaylineList" parameterType="UavWayline" resultMap="UavWaylineResult">
+ <include refid="selectUavWaylineVo"/>
+ <where>
+ <if test="name != null and name != ''"> and name like concat('%', #{name}, '%')</if>
+ <if test="type != null and type != ''"> and type = #{type}</if>
+ <if test="uavModel != null and uavModel != ''"> and uav_model = #{uavModel}</if>
+ <if test="payloadModel != null and payloadModel != ''"> and payload_model = #{payloadModel}</if>
+ <if test="globalSpeed != null "> and global_speed = #{globalSpeed}</if>
+ <if test="globalHeight != null "> and global_height = #{globalHeight}</if>
+ <if test="userWaypointCount != null "> and user_waypoint_count = #{userWaypointCount}</if>
+ <if test="planWaypointCount != null "> and plan_waypoint_count = #{planWaypointCount}</if>
+ <if test="srcWaylineData != null"> and src_wayline_data =#{srcWaylineData}</if>
+ <if test="planWaylineData != null"> and plan_wayline_data = #{planWaylineData}</if>
+ <if test="kmzUrl != null and kmzUrl != ''"> and kmz_url = #{kmzUrl}</if>
+ <if test="userId != null "> and user_id = #{userId}</if>
+ <if test="deptId != null "> and dept_id = #{deptId}</if>
+ </where>
+ </select>
+
+ <select id="selectUavWaylineById" parameterType="String" resultMap="UavWaylineResult">
+ <include refid="selectUavWaylineVo"/>
+ where id = #{id}
+ </select>
+
+ <insert id="insertUavWayline" parameterType="UavWayline">
+ insert into uav_wayline
+ <trim prefix="(" suffix=")" suffixOverrides=",">
+ <if test="id != null">id,</if>
+ <if test="name != null">name,</if>
+ <if test="type != null">type,</if>
+ <if test="uavModel != null">uav_model,</if>
+ <if test="payloadModel != null">payload_model,</if>
+ <if test="globalSpeed != null">global_speed,</if>
+ <if test="globalHeight != null">global_height,</if>
+ <if test="userWaypointCount != null">user_waypoint_count,</if>
+ <if test="planWaypointCount != null">plan_waypoint_count,</if>
+ <if test="srcWaylineData != null">src_wayline_data,</if>
+ <if test="planWaylineData != null">plan_wayline_data,</if>
+ <if test="kmzUrl != null">kmz_url,</if>
+ <if test="userId != null">user_id,</if>
+ <if test="deptId != null">dept_id,</if>
+ <if test="createBy != null">create_by,</if>
+ <if test="createTime != null">create_time,</if>
+ <if test="updateBy != null">update_by,</if>
+ <if test="updateTime != null">update_time,</if>
+ <if test="remark != null">remark,</if>
+ </trim>
+ <trim prefix="values (" suffix=")" suffixOverrides=",">
+ <if test="id != null">#{id},</if>
+ <if test="name != null">#{name},</if>
+ <if test="type != null">#{type},</if>
+ <if test="uavModel != null">#{uavModel},</if>
+ <if test="payloadModel != null">#{payloadModel},</if>
+ <if test="globalSpeed != null">#{globalSpeed},</if>
+ <if test="globalHeight != null">#{globalHeight},</if>
+ <if test="userWaypointCount != null">#{userWaypointCount},</if>
+ <if test="planWaypointCount != null">#{planWaypointCount},</if>
+ <if test="srcWaylineData != null">#{srcWaylineData},</if>
+ <if test="planWaylineData != null">#{planWaylineData},</if>
+ <if test="kmzUrl != null">#{kmzUrl},</if>
+ <if test="userId != null">#{userId},</if>
+ <if test="deptId != null">#{deptId},</if>
+ <if test="createBy != null">#{createBy},</if>
+ <if test="createTime != null">#{createTime},</if>
+ <if test="updateBy != null">#{updateBy},</if>
+ <if test="updateTime != null">#{updateTime},</if>
+ <if test="remark != null">#{remark},</if>
+ </trim>
+ </insert>
+
+ <update id="updateUavWayline" parameterType="UavWayline">
+ update uav_wayline
+ <trim prefix="SET" suffixOverrides=",">
+ <if test="name != null">name = #{name},</if>
+ <if test="type != null">type = #{type},</if>
+ <if test="uavModel != null">uav_model = #{uavModel},</if>
+ <if test="payloadModel != null">payload_model = #{payloadModel},</if>
+ <if test="globalSpeed != null">global_speed = #{globalSpeed},</if>
+ <if test="globalHeight != null">global_height = #{globalHeight},</if>
+ <if test="userWaypointCount != null "> user_waypoint_count = #{userWaypointCount}</if>
+ <if test="planWaypointCount != null "> plan_waypoint_count = #{planWaypointCount}</if>
+ <if test="srcWaylineData != null "> src_wayline_data =#{srcWaylineData}</if>
+ <if test="planWaylineData != null "> plan_wayline_data = #{planWaylineData}</if>
+ <if test="kmzUrl != null">kmz_url = #{kmzUrl},</if>
+ <if test="userId != null">user_id = #{userId},</if>
+ <if test="deptId != null">dept_id = #{deptId},</if>
+ <if test="createBy != null">create_by = #{createBy},</if>
+ <if test="createTime != null">create_time = #{createTime},</if>
+ <if test="updateBy != null">update_by = #{updateBy},</if>
+ <if test="updateTime != null">update_time = #{updateTime},</if>
+ <if test="remark != null">remark = #{remark},</if>
+ </trim>
+ where id = #{id}
+ </update>
+
+ <delete id="deleteUavWaylineById" parameterType="String">
+ delete from uav_wayline where id = #{id}
+ </delete>
+
+ <delete id="deleteUavWaylineByIds" parameterType="String">
+ delete from uav_wayline where id in
+ <foreach item="id" collection="array" open="(" separator="," close=")">
+ #{id}
+ </foreach>
+ </delete>
+
+ <select id="checkUavWaylineNameUnique" parameterType="String" resultMap="UavWaylineResult">
+ select id, name from uav_wayline where name = #{name} limit 1
+ </select>
+</mapper>
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 3cf3f23..81e6962 100644
--- a/pom.xml
+++ b/pom.xml
@@ -31,6 +31,7 @@
<velocity.version>2.3</velocity.version>
<jwt.version>0.9.1</jwt.version>
<knife4j.version>3.0.3</knife4j.version>
+ <okhttp.version>4.8.1</okhttp.version>
</properties>
<!-- 依赖声明 -->
@@ -182,7 +183,12 @@
<artifactId>ard-common</artifactId>
<version>${ard.version}</version>
</dependency>
-
+ <!-- okhttp3-->
+ <dependency>
+ <groupId>com.squareup.okhttp3</groupId>
+ <artifactId>okhttp</artifactId>
+ <version>${okhttp.version}</version>
+ </dependency>
</dependencies>
</dependencyManagement>
--
Gitblit v1.9.3