|
|
|
@ -2,18 +2,16 @@
|
|
|
|
|
<div class="container">
|
|
|
|
|
<div class="title">
|
|
|
|
|
<LeftOutlined @click="backPage" />
|
|
|
|
|
<div>航点航线</div>
|
|
|
|
|
<SaveOutlined @click="saveAirLine"/>
|
|
|
|
|
<div style="flex:1;">
|
|
|
|
|
<a-input v-model:value="props.airLineForm.airLineName" size="middle" placeholder="航线名称" />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<a-button type="primary" size="middle" @click="areaOptionsShow = !areaOptionsShow">
|
|
|
|
|
航线设置
|
|
|
|
|
<ControlOutlined />
|
|
|
|
|
</a-button>
|
|
|
|
|
|
|
|
|
|
<div style="flex:1;">
|
|
|
|
|
<a-input v-model:value="props.airLineForm.airLineName" size="middle" placeholder="航线名称" />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<SaveOutlined @click="saveAirLine" style="font-size:20px;" />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 航线信息 -->
|
|
|
|
@ -165,7 +163,7 @@
|
|
|
|
|
<div class="item">
|
|
|
|
|
<div class="label">全局航线飞行速度</div>
|
|
|
|
|
<div class="content">
|
|
|
|
|
<a-input style="width:100px;" placeholder="" v-model:value="missionConfig.globalTransitionalSpeed"></a-input>
|
|
|
|
|
<a-input style="width:100px;" placeholder="" v-model:value="props.templateKmlConfig.Folder.autoFlightSpeed"></a-input>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="unit"> m/s</div>
|
|
|
|
|
</div>
|
|
|
|
@ -343,12 +341,14 @@ import {ref,defineEmits,defineProps} from 'vue'
|
|
|
|
|
import { SaveOutlined,LeftOutlined,AppstoreOutlined,DownOutlined,ControlOutlined,InfoCircleOutlined} from '@ant-design/icons-vue';
|
|
|
|
|
|
|
|
|
|
// 航点动作汇总
|
|
|
|
|
import { airPointActions } from '../waylineConfig/action'
|
|
|
|
|
import { airPointActions } from '../waylineConfig/actionConfig'
|
|
|
|
|
import { XMLParser, XMLBuilder } from 'fast-xml-parser';
|
|
|
|
|
|
|
|
|
|
import {uploadXmlFile,addAirLine} from '@/api/sys/workplan';
|
|
|
|
|
import {uploadXmlFile,addAirLine,editAirLine} from '@/api/sys/workplan';
|
|
|
|
|
import { Modal, message } from 'ant-design-vue';
|
|
|
|
|
import {missionConfigOptions,folderConfigOptions} from '../waylineConfig/index.ts';
|
|
|
|
|
import {missionConfigOptions,folderConfigOptions} from '../waylineConfig/waylineParamsOptions.ts';
|
|
|
|
|
import JSZip from "jszip";
|
|
|
|
|
import { saveAs } from "file-saver";
|
|
|
|
|
|
|
|
|
|
const emits = defineEmits(["setTakeOffPoint","checkPoint","exitDraw","addAction"])
|
|
|
|
|
|
|
|
|
@ -520,93 +520,11 @@ const pointInfo = ref({
|
|
|
|
|
|
|
|
|
|
// 处理航点数据
|
|
|
|
|
const handlerPointInfo = ()=>{
|
|
|
|
|
|
|
|
|
|
if(props.airPoints?.length<=0){
|
|
|
|
|
message.warning("请添加航点!");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
props.airPoints?.forEach((item,index)=>{
|
|
|
|
|
|
|
|
|
|
console.log("item123",item);
|
|
|
|
|
|
|
|
|
|
let point = {
|
|
|
|
|
"Point": {
|
|
|
|
|
"coordinates": item.lng+","+item.lat
|
|
|
|
|
},
|
|
|
|
|
"index": index,
|
|
|
|
|
"executeHeight": item.alt,
|
|
|
|
|
"waypointSpeed": 10,
|
|
|
|
|
"waypointHeadingParam": {
|
|
|
|
|
"waypointHeadingMode": "followWayline", // 飞行器偏航角模式 :
|
|
|
|
|
"waypointHeadingAngle": item.aircraftHorizontalAngle, // 飞行器偏航角 [-180, 180] 当且仅当“wpml:waypointHeadingMode”为“smoothTransition”时必需
|
|
|
|
|
"waypointPoiPoint": "0.000000,0.000000,0.000000", // 兴趣点 仅当wpml:waypointHeadingMode为towardPOI时必需
|
|
|
|
|
"waypointHeadingAngleEnable": 0,
|
|
|
|
|
"waypointHeadingPathMode": "followBadArc", // 飞行器偏航角转动方向
|
|
|
|
|
"waypointHeadingPoiIndex": 0
|
|
|
|
|
},
|
|
|
|
|
"waypointTurnParam": {
|
|
|
|
|
"waypointTurnMode": "toPointAndStopWithDiscontinuityCurvature",
|
|
|
|
|
"waypointTurnDampingDist": 0
|
|
|
|
|
},
|
|
|
|
|
"useStraightLine": 1,
|
|
|
|
|
"actionGroup": {
|
|
|
|
|
"actionGroupId": 0,
|
|
|
|
|
"actionGroupStartIndex": 0,
|
|
|
|
|
"actionGroupEndIndex": 0,
|
|
|
|
|
"actionGroupMode": "sequence",
|
|
|
|
|
"actionTrigger": {
|
|
|
|
|
"actionTriggerType": "reachPoint"
|
|
|
|
|
},
|
|
|
|
|
"action": [
|
|
|
|
|
{
|
|
|
|
|
"actionId": 0,
|
|
|
|
|
"actionActuatorFunc": "rotateYaw",
|
|
|
|
|
"actionActuatorFuncParam": {
|
|
|
|
|
"aircraftHeading": 0,
|
|
|
|
|
"aircraftPathMode": "counterClockwise"
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"actionId": 1,
|
|
|
|
|
"actionActuatorFunc": "gimbalRotate",
|
|
|
|
|
"actionActuatorFuncParam": {
|
|
|
|
|
"gimbalHeadingYawBase": "north",
|
|
|
|
|
"gimbalRotateMode": "absoluteAngle",
|
|
|
|
|
"gimbalPitchRotateEnable": 1,
|
|
|
|
|
"gimbalPitchRotateAngle": 0,
|
|
|
|
|
"gimbalRollRotateEnable": 0,
|
|
|
|
|
"gimbalRollRotateAngle": 0,
|
|
|
|
|
"gimbalYawRotateEnable": 0,
|
|
|
|
|
"gimbalYawRotateAngle": 0,
|
|
|
|
|
"gimbalRotateTimeEnable": 0,
|
|
|
|
|
"gimbalRotateTime": 0,
|
|
|
|
|
"payloadPositionIndex": 0
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"actionId": 2,
|
|
|
|
|
"actionActuatorFunc": "zoom",
|
|
|
|
|
"actionActuatorFuncParam": {
|
|
|
|
|
"focalLength": 24,
|
|
|
|
|
"isUseFocalFactor": 0,
|
|
|
|
|
"payloadPositionIndex": 0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
"waypointGimbalHeadingParam": {
|
|
|
|
|
"waypointGimbalPitchAngle": item.cameraVerticalAngle, // 云台 垂直俯仰角参数 [-90, -30]
|
|
|
|
|
"waypointGimbalYawAngle": 0 // 云台 水平偏航角度
|
|
|
|
|
},
|
|
|
|
|
"isRisky": 0,
|
|
|
|
|
"waypointWorkType": 0
|
|
|
|
|
}
|
|
|
|
|
folder.value.Placemark.push(point);
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// waylines.json 数据
|
|
|
|
@ -626,52 +544,194 @@ const waylinesJson = ref(
|
|
|
|
|
// 保存航线
|
|
|
|
|
const saveAirLine = ()=>{
|
|
|
|
|
|
|
|
|
|
// 处理template.json
|
|
|
|
|
handlerTemplateKml();
|
|
|
|
|
|
|
|
|
|
// 处理航点
|
|
|
|
|
handlerWaylineWpml();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let handlerResult = handlerPointInfo();
|
|
|
|
|
|
|
|
|
|
if(handlerResult){
|
|
|
|
|
const builder = new XMLBuilder();
|
|
|
|
|
|
|
|
|
|
let lineData = {...waylinesJson.value}
|
|
|
|
|
// 将template中的 missionConfig 赋值 给wayline
|
|
|
|
|
props.waylineWpmlConfig.missionConfig = JSON.parse(JSON.stringify(props.templateKmlConfig.missionConfig))
|
|
|
|
|
|
|
|
|
|
const builder = new XMLBuilder({
|
|
|
|
|
format: true, // 启用换行和缩进
|
|
|
|
|
indentBy: " ", // 缩进字符(默认2空格,可自定义为 \t 等)
|
|
|
|
|
suppressEmptyNode: true, // 可选:是否忽略空节点
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 带wpml前缀的 template json数据
|
|
|
|
|
let templateJson = {kml:{Document:props.templateKmlConfig}}
|
|
|
|
|
let templateWpmlJson = handlerPrefixWpml(templateJson);
|
|
|
|
|
let templateXmlStr = builder.build(templateWpmlJson);
|
|
|
|
|
let templateXmlStrTemp = templateXmlStr.replace(/<\/?\d+>/g, "")
|
|
|
|
|
let templateXml = templateXmlStrTemp.replace("<kml>",`<?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://www.opengis.net/kml/2.2" xmlns:wpml="http://www.dji.com/wpmz/1.0.6">`)
|
|
|
|
|
|
|
|
|
|
// 带wpml前缀的 wayline json数据
|
|
|
|
|
// delete props.waylineWpmlConfig.missionConfig.takeOffRefPoint
|
|
|
|
|
// delete props.waylineWpmlConfig.missionConfig.takeOffRefPointAGLHeight
|
|
|
|
|
// delete props.waylineWpmlConfig.missionConfig.autoRerouteInfo
|
|
|
|
|
|
|
|
|
|
let waylineJosn = {kml:{Document:props.waylineWpmlConfig}}
|
|
|
|
|
let waylienWpmlJson = handlerPrefixWpml(waylineJosn);
|
|
|
|
|
let waylineXmlStr = builder.build(waylienWpmlJson);
|
|
|
|
|
let waylineXmlStrTemp = waylineXmlStr.replace(/<\/?\d+>/g, "")
|
|
|
|
|
let waylineXml = waylineXmlStrTemp.replace("<kml>",`<?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://www.opengis.net/kml/2.2" xmlns:wpml="http://www.dji.com/wpmz/1.0.6">`)
|
|
|
|
|
|
|
|
|
|
// 创建压缩文件
|
|
|
|
|
handlerCreateFile(templateXml,waylineXml);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let obj = handlerPrefixWpml(lineData);
|
|
|
|
|
// 处理生成template.json
|
|
|
|
|
const handlerTemplateKml = () => {
|
|
|
|
|
|
|
|
|
|
let xmlString = builder.build(obj);
|
|
|
|
|
// 处理时间和日期
|
|
|
|
|
handlerCreateOrUpdateTime();
|
|
|
|
|
|
|
|
|
|
let xmlString2 = xmlString.replace(/<\/?\d+>/g, "")
|
|
|
|
|
// 计算数据处理
|
|
|
|
|
// handlerAirLineHeight();
|
|
|
|
|
|
|
|
|
|
let xmlString3 = xmlString2.replace("<kml>",`<?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://www.opengis.net/kml/2.2" xmlns:wpml="http://www.dji.com/wpmz/1.0.6">`)
|
|
|
|
|
|
|
|
|
|
console.log("xmlString3",xmlString3);
|
|
|
|
|
return null;
|
|
|
|
|
handlerCreateFile(xmlString3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
// 处理创建时间 编辑时间
|
|
|
|
|
const handlerCreateOrUpdateTime = () => {
|
|
|
|
|
|
|
|
|
|
let date = new Date();
|
|
|
|
|
|
|
|
|
|
if(props.editModel == 'add'){
|
|
|
|
|
props.templateKmlConfig.createTime = date.getTime();
|
|
|
|
|
props.templateKmlConfig.updateTime = date.getTime();
|
|
|
|
|
}else if(props.editModel == 'edit'){
|
|
|
|
|
props.templateKmlConfig.updateTime = date.getTime();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理wmpl:前缀
|
|
|
|
|
|
|
|
|
|
// 处理生成wayline.json
|
|
|
|
|
const handlerWaylineWpml = () => {
|
|
|
|
|
|
|
|
|
|
// 处理基础配置
|
|
|
|
|
props.waylineWpmlConfig.missionConfig = JSON.parse(JSON.stringify(props.templateKmlConfig.missionConfig));
|
|
|
|
|
|
|
|
|
|
// 处理统计数据
|
|
|
|
|
handlerStatistics();
|
|
|
|
|
|
|
|
|
|
// 处理航点
|
|
|
|
|
handelrAirPoint();
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handlerStatistics = () => {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理航点 和 绑定动作信息
|
|
|
|
|
const handelrAirPoint = () => {
|
|
|
|
|
props.airPoints?.forEach((item,index)=>{
|
|
|
|
|
let point = {
|
|
|
|
|
"Point": {
|
|
|
|
|
"coordinates": item.lng+","+item.lat
|
|
|
|
|
},
|
|
|
|
|
"index": index,
|
|
|
|
|
"executeHeight": 172.948304450636,
|
|
|
|
|
"waypointSpeed": props.templateKmlConfig.Folder.autoFlightSpeed,
|
|
|
|
|
"waypointHeadingParam": {
|
|
|
|
|
"waypointHeadingMode": "followWayline",
|
|
|
|
|
"waypointHeadingAngle": 0,
|
|
|
|
|
"waypointPoiPoint": "0.000000,0.000000,0.000000",
|
|
|
|
|
"waypointHeadingAngleEnable": 0,
|
|
|
|
|
"waypointHeadingPathMode": "followBadArc",
|
|
|
|
|
"waypointHeadingPoiIndex": 0
|
|
|
|
|
},
|
|
|
|
|
"waypointTurnParam": {
|
|
|
|
|
"waypointTurnMode": "toPointAndStopWithDiscontinuityCurvature",
|
|
|
|
|
"waypointTurnDampingDist": 0
|
|
|
|
|
},
|
|
|
|
|
"useStraightLine": 1,
|
|
|
|
|
"waypointGimbalHeadingParam": {
|
|
|
|
|
"waypointGimbalPitchAngle": 0,
|
|
|
|
|
"waypointGimbalYawAngle": 0
|
|
|
|
|
},
|
|
|
|
|
"isRisky": 0,
|
|
|
|
|
"waypointWorkType": 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(item.actions.length>0){
|
|
|
|
|
point.actionGroup = {
|
|
|
|
|
"actionGroupId": 0,
|
|
|
|
|
"actionGroupStartIndex": index,
|
|
|
|
|
"actionGroupEndIndex": index,
|
|
|
|
|
"actionGroupMode": "sequence",
|
|
|
|
|
"actionTrigger": {
|
|
|
|
|
"actionTriggerType": "reachPoint"
|
|
|
|
|
},
|
|
|
|
|
"action": []
|
|
|
|
|
};
|
|
|
|
|
item.actions.forEach((action,idx)=>{
|
|
|
|
|
let actor = action.config;
|
|
|
|
|
actor.actionId = idx
|
|
|
|
|
point.actionGroup.action.push(actor);
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
props.waylineWpmlConfig.Folder.Placemark.push(point);
|
|
|
|
|
props.templateKmlConfig.Folder.Placemark.push(point);
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 处理wpml:前缀
|
|
|
|
|
const handlerPrefixWpml = (obj) => {
|
|
|
|
|
for (const key in obj) {
|
|
|
|
|
|
|
|
|
|
// 如果是对象
|
|
|
|
|
if (typeof obj[key] === 'object' && obj[key] !== null) {
|
|
|
|
|
for (const key in obj) {
|
|
|
|
|
|
|
|
|
|
handlerPrefixWpml(obj[key]); // 递归处理嵌套对象
|
|
|
|
|
|
|
|
|
|
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) { // 如果是对象
|
|
|
|
|
|
|
|
|
|
const newAttrs = {};
|
|
|
|
|
for (const attrName in obj[key]) {
|
|
|
|
|
|
|
|
|
|
// 检查属性名是否首字母小写
|
|
|
|
|
if (/^[a-z]/.test(attrName)) {
|
|
|
|
|
newAttrs[`wmpl:${attrName}`] = obj[key][attrName];
|
|
|
|
|
} else {
|
|
|
|
|
newAttrs[attrName] = obj[key][attrName];
|
|
|
|
|
handlerPrefixWpml(obj[key]); // 递归处理嵌套对象
|
|
|
|
|
|
|
|
|
|
const newAttrs = {};
|
|
|
|
|
|
|
|
|
|
for (const attrName in obj[key] ) {
|
|
|
|
|
|
|
|
|
|
// 检查属性名是否首字母小写
|
|
|
|
|
if (/^[a-z]/.test(attrName) && attrName != 'outerBoundaryIs' && attrName != 'coordinates') {
|
|
|
|
|
newAttrs[`wpml:${attrName}`] = obj[key][attrName];
|
|
|
|
|
} else {
|
|
|
|
|
newAttrs[attrName] = obj[key][attrName];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
obj[key] = newAttrs;
|
|
|
|
|
|
|
|
|
|
}else if(typeof obj[key] === 'object' && obj[key] !== null && Array.isArray(obj[key])){ // 如果是数组
|
|
|
|
|
|
|
|
|
|
handlerPrefixWpml(obj[key]); // 递归处理嵌套对象
|
|
|
|
|
|
|
|
|
|
const newAttrs = [];
|
|
|
|
|
|
|
|
|
|
for (const attrName in obj[key]) {
|
|
|
|
|
// 检查属性名是否首字母小写
|
|
|
|
|
if (/^[a-z]/.test(attrName)) {
|
|
|
|
|
newAttrs[`wpml:${attrName}`] = obj[key][attrName];
|
|
|
|
|
} else {
|
|
|
|
|
newAttrs[attrName] = obj[key][attrName];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
obj[key] = newAttrs;
|
|
|
|
|
}
|
|
|
|
|
obj[key] = newAttrs;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return obj;
|
|
|
|
|
return obj;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -695,15 +755,35 @@ const submitForm = ref({
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 生成xml文件,创建航线
|
|
|
|
|
const handlerCreateFile =async (content)=>{
|
|
|
|
|
const blob = new Blob([content], { type: 'wmpl/plain' });
|
|
|
|
|
const handlerCreateFile =async (templateXml,waylineXml)=>{
|
|
|
|
|
|
|
|
|
|
console.log("waylineXml",templateXml,waylineXml);
|
|
|
|
|
|
|
|
|
|
const blob =await convertXmlToKmz(templateXml,waylineXml);
|
|
|
|
|
|
|
|
|
|
// 创建FormData对象用于上传
|
|
|
|
|
const formData = new FormData();
|
|
|
|
|
formData.append('xmlFile', blob);
|
|
|
|
|
let res = await uploadXmlFile(formData);
|
|
|
|
|
|
|
|
|
|
formData.append('xmlFile', blob,"航点航线.kmz");
|
|
|
|
|
|
|
|
|
|
let res = await uploadXmlFile(props.airLineForm.folder,formData);
|
|
|
|
|
|
|
|
|
|
if(res){
|
|
|
|
|
submitForm.value.wpml = res.path;
|
|
|
|
|
let addAirLineRes =await addAirLine(submitForm.value);
|
|
|
|
|
props.airLineForm.wpml = res.path;
|
|
|
|
|
// props.airLineForm.lineData = {type:2}
|
|
|
|
|
|
|
|
|
|
let addAirLineRes = null;
|
|
|
|
|
|
|
|
|
|
if(props.editModel == 'add'){
|
|
|
|
|
|
|
|
|
|
addAirLineRes =await addAirLine(props.airLineForm);
|
|
|
|
|
|
|
|
|
|
}else if(props.editModel == 'edit'){
|
|
|
|
|
|
|
|
|
|
addAirLineRes =await editAirLine(props.airLineForm);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(addAirLineRes){
|
|
|
|
|
message.success("操作成功!");
|
|
|
|
|
backPage();
|
|
|
|
@ -715,6 +795,28 @@ const handlerCreateFile =async (content)=>{
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// xml文本转换为wpml和kml文件
|
|
|
|
|
const convertXmlToKmz =async (templateXml,waylineXml)=>{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const zip = new JSZip();
|
|
|
|
|
|
|
|
|
|
// 1. 创建 "wmpz" 文件夹
|
|
|
|
|
const wmpzFolder = zip.folder("wpmz");
|
|
|
|
|
|
|
|
|
|
// 2. 向文件夹中添加文件
|
|
|
|
|
wmpzFolder.file("waylines.wpml", waylineXml);
|
|
|
|
|
wmpzFolder.file("template.kml", templateXml);
|
|
|
|
|
|
|
|
|
|
// 3. 生成 KMZ (ZIP) 文件
|
|
|
|
|
const kmzBlob = await zip.generateAsync({ type: "blob" });
|
|
|
|
|
|
|
|
|
|
// 下载航线文件
|
|
|
|
|
// saveAs(kmzBlob, "output.kmz");
|
|
|
|
|
|
|
|
|
|
return kmzBlob;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 退出绘制
|
|
|
|
|
const backPage = ()=>{
|
|
|
|
@ -727,7 +829,6 @@ const addAirPointAction = (action) => {
|
|
|
|
|
|
|
|
|
|
// 处理动作添加逻辑
|
|
|
|
|
emits("addAction",action);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if(action.value == ""){
|
|
|
|
|
|
|
|
|
|