|
|
/**
|
|
|
* 面状航线航线文件生成
|
|
|
*
|
|
|
* 目标:构造方法传入template.json、wayline.json参数,
|
|
|
*
|
|
|
* 结果:返回kmz航线文件的Blob(二进制数据的对象)
|
|
|
*
|
|
|
*/
|
|
|
|
|
|
import { XMLBuilder } from 'fast-xml-parser';
|
|
|
import JSZip from "jszip";
|
|
|
import { saveAs } from "file-saver";
|
|
|
|
|
|
import { calculateGsd,calculateSpacing,calculateInterval,uavModel } from './calculateAirLine'
|
|
|
|
|
|
export default class Mapping2d {
|
|
|
|
|
|
/**
|
|
|
* 基础信息
|
|
|
*/
|
|
|
private templateKmlConfig;
|
|
|
private waylineWpmlConfig;
|
|
|
private gsd;
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 构造方法
|
|
|
*/
|
|
|
constructor(templateKmlConfig:Object,waylineWpmlConfig:Object){
|
|
|
this.templateKmlConfig = templateKmlConfig;
|
|
|
this.waylineWpmlConfig = waylineWpmlConfig;
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 计算获取结果
|
|
|
*/
|
|
|
generateAirLine(){
|
|
|
|
|
|
// 测区
|
|
|
let polygon = this.handlerCalculateParams();
|
|
|
|
|
|
// 根据航线文件中高度,计算gsd
|
|
|
this.gsd = calculateGsd(this.templateKmlConfig.Folder.Placemark.height,uavModel['m4td']);
|
|
|
|
|
|
// 间距
|
|
|
let spacing = calculateSpacing(this.gsd,this.templateKmlConfig.Folder.Placemark.overlap.orthoCameraOverlapW/100,uavModel['m4td']);
|
|
|
|
|
|
// 初始化角度
|
|
|
let angle = this.templateKmlConfig.Folder.Placemark.direction ? this.templateKmlConfig.Folder.Placemark.direction : 0;
|
|
|
|
|
|
// 获取航点
|
|
|
let airPoints = this.generateScanLines(polygon,spacing/10000,angle);
|
|
|
|
|
|
// 获取文件
|
|
|
let blob = this.handlerWaylineWpmlConfig(airPoints);
|
|
|
|
|
|
return blob;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 根据航线文件高度计算gsd
|
|
|
*
|
|
|
* 根据gsd 和 航线文件中重叠率 计算航线间距
|
|
|
*
|
|
|
*/
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 处理测区范围
|
|
|
*/
|
|
|
handlerCalculateParams(){
|
|
|
|
|
|
let coordinateArray =this.templateKmlConfig.Folder.Placemark.Polygon.outerBoundaryIs.LinearRing.coordinates.split('\n');
|
|
|
|
|
|
|
|
|
let geomtryCoorinate = [];
|
|
|
|
|
|
coordinateArray.forEach((item, index) => {
|
|
|
|
|
|
let trimStr = item.trim();
|
|
|
let arr = trimStr.split(',');
|
|
|
|
|
|
for (let i = 0; i < arr.length; i++) {
|
|
|
arr[i] = parseFloat(arr[i]);
|
|
|
}
|
|
|
geomtryCoorinate.push(arr);
|
|
|
});
|
|
|
|
|
|
geomtryCoorinate.push(geomtryCoorinate[0]);
|
|
|
|
|
|
let geometry = {
|
|
|
type: 'Polygon',
|
|
|
coordinates: [geomtryCoorinate],
|
|
|
};
|
|
|
|
|
|
console.log("geometry123",geometry)
|
|
|
|
|
|
|
|
|
return geometry;
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 生成航线
|
|
|
*/
|
|
|
generateScanLines(polygon, spacing, angle = 0) {
|
|
|
|
|
|
if (!turf.booleanValid(polygon)) throw new Error('无效的多边形');
|
|
|
|
|
|
spacing = Math.abs(spacing);
|
|
|
|
|
|
// 获取多边形的旋转边界框
|
|
|
const bbox = turf.bbox(polygon);
|
|
|
const center = turf.center(polygon).geometry.coordinates;
|
|
|
|
|
|
// 计算需要生成的线数量(加缓冲确保完全覆盖)
|
|
|
const diagLength = turf.distance(
|
|
|
turf.point([bbox[0], bbox[1]]),
|
|
|
turf.point([bbox[2], bbox[3]]),
|
|
|
);
|
|
|
const lineCount = Math.ceil(diagLength / spacing) + 2;
|
|
|
|
|
|
// 生成基础水平线
|
|
|
const lines = [];
|
|
|
|
|
|
for (let i = -1; i <= lineCount; i++) {
|
|
|
const y = bbox[1] + i * spacing * 0.11 - spacing * 0.11;
|
|
|
lines.push(
|
|
|
turf.lineString([
|
|
|
[bbox[0] - spacing, y],
|
|
|
[bbox[2] + spacing, y],
|
|
|
]),
|
|
|
);
|
|
|
}
|
|
|
|
|
|
// 旋转线条到指定角度
|
|
|
const rotatedLines = lines.map((line) =>
|
|
|
turf.transformRotate(line, angle - 90, { pivot: center }),
|
|
|
);
|
|
|
|
|
|
// 裁剪线条到多边形内并处理结果
|
|
|
const coverageLines = [];
|
|
|
|
|
|
rotatedLines.forEach((line) => {
|
|
|
try {
|
|
|
const intersection = turf.lineIntersect(line, polygon);
|
|
|
|
|
|
if (intersection.features.length >= 2) {
|
|
|
// 按与线起点距离排序交点
|
|
|
const sortedPoints = intersection.features
|
|
|
.map((f) => f.geometry.coordinates)
|
|
|
.sort(
|
|
|
(a, b) =>
|
|
|
turf.distance(line.geometry.coordinates[0], turf.point(a)) -
|
|
|
turf.distance(line.geometry.coordinates[0], turf.point(b)),
|
|
|
);
|
|
|
|
|
|
// 创建连接最远两个交点的线段
|
|
|
coverageLines.push(
|
|
|
turf.lineString([sortedPoints[0], sortedPoints[sortedPoints.length - 1]]),
|
|
|
);
|
|
|
}
|
|
|
} catch (e) {
|
|
|
console.warn('处理线段时出错:', e);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
let connectedLine = this.connectLinesManual(turf.featureCollection(coverageLines));
|
|
|
|
|
|
// 将航点提取并添加到airPoints
|
|
|
let airPoints = [];
|
|
|
|
|
|
connectedLine.geometry.coordinates?.forEach((item, index) => {
|
|
|
let point = {
|
|
|
id: index,
|
|
|
lng: item[0],
|
|
|
lat: item[1],
|
|
|
alt: item[2],
|
|
|
aircraftHorizontalAngle: 0,
|
|
|
cameraHorizontalAngle: 0,
|
|
|
cameraVerticalAngle: 0,
|
|
|
focalLength: 1,
|
|
|
};
|
|
|
|
|
|
airPoints?.push(point);
|
|
|
});
|
|
|
|
|
|
// 倒数第2个点
|
|
|
let penultimatePoint = JSON.parse(JSON.stringify(airPoints[airPoints.length - 1]));
|
|
|
penultimatePoint.id = penultimatePoint.id + 1;
|
|
|
airPoints?.push(penultimatePoint);
|
|
|
|
|
|
// 添加测区中心点 (最后1个点)
|
|
|
// let turfPolygon = turf.polygon([polygonGeoJson.value]);
|
|
|
|
|
|
let polygonCenter = turf.centroid(polygon);
|
|
|
|
|
|
let lastPoint = {
|
|
|
id: airPoints[airPoints.length - 1].id + 1,
|
|
|
lng: polygonCenter.geometry.coordinates[0],
|
|
|
lat: polygonCenter.geometry.coordinates[1],
|
|
|
alt: airPoints[airPoints.length - 1].alt,
|
|
|
aircraftHorizontalAngle: 0,
|
|
|
cameraHorizontalAngle: 0,
|
|
|
cameraVerticalAngle: 0,
|
|
|
focalLength: 1,
|
|
|
};
|
|
|
airPoints?.push(lastPoint);
|
|
|
|
|
|
// 添加最后一个点到线数据中
|
|
|
connectedLine.geometry.coordinates.push([lastPoint.lng, lastPoint.lat, lastPoint.alt]);
|
|
|
|
|
|
// return connectedLine;
|
|
|
return airPoints;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 处理航点高度
|
|
|
*/
|
|
|
connectLinesManual(lines){
|
|
|
|
|
|
// 确保输入是FeatureCollection
|
|
|
if (lines.type !== 'FeatureCollection') {
|
|
|
lines = turf.featureCollection([lines]);
|
|
|
}
|
|
|
|
|
|
// 收集所有坐标点
|
|
|
let allCoords = [];
|
|
|
|
|
|
lines.features.forEach((line, index) => {
|
|
|
// 相对起飞点高度
|
|
|
let relativeHeight = 120;
|
|
|
|
|
|
line.geometry.coordinates?.forEach((item, idx) => {
|
|
|
line.geometry.coordinates[idx].push(relativeHeight);
|
|
|
});
|
|
|
|
|
|
if (line.geometry.type === 'LineString' && index % 2 == 0) {
|
|
|
allCoords = allCoords.concat(line.geometry.coordinates.reverse());
|
|
|
} else {
|
|
|
allCoords = allCoords.concat(line.geometry.coordinates);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 移除连续重复的点
|
|
|
const cleanedCoords = [];
|
|
|
|
|
|
if (allCoords.length > 0) {
|
|
|
cleanedCoords.push(allCoords[0]);
|
|
|
for (let i = 1; i < allCoords.length; i++) {
|
|
|
const prev = cleanedCoords[cleanedCoords.length - 1];
|
|
|
if (prev[0] !== allCoords[i][0] || prev[1] !== allCoords[i][1]) {
|
|
|
cleanedCoords.push(allCoords[i]);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return turf.lineString(cleanedCoords);
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 处理wayline.json数据
|
|
|
*/
|
|
|
async handlerWaylineWpmlConfig(airPoints:array){
|
|
|
|
|
|
this.waylineWpmlConfig.Folder.Placemark = [];
|
|
|
|
|
|
|
|
|
airPoints.forEach((item,index)=>{
|
|
|
|
|
|
let point = null;
|
|
|
|
|
|
// 根据高度模式 设置高度
|
|
|
let height = 0;
|
|
|
|
|
|
// 计算间隔时间
|
|
|
let spaceTime = (calculateInterval(this.gsd,this.templateKmlConfig.Folder.Placemark.overlap.orthoCameraOverlapH/100,uavModel['m4td']) / this.templateKmlConfig.Folder.autoFlightSpeed) * 2
|
|
|
|
|
|
|
|
|
// 起飞点高度
|
|
|
let heightArr = this.templateKmlConfig.missionConfig.takeOffRefPoint.split(",");
|
|
|
|
|
|
height = heightArr[2]
|
|
|
|
|
|
|
|
|
// 处理第1个航点
|
|
|
if(index == 0){
|
|
|
point = {
|
|
|
"Point": {
|
|
|
"coordinates":"\n"+item.lng+","+item.lat+"\n"
|
|
|
},
|
|
|
"index": item.id,
|
|
|
"executeHeight": this.templateKmlConfig.Folder.Placemark.height,
|
|
|
"waypointSpeed": this.templateKmlConfig.Folder.autoFlightSpeed,
|
|
|
"waypointHeadingParam": {
|
|
|
"waypointHeadingMode": "followWayline",
|
|
|
"waypointHeadingAngle": -96.9990577342362,
|
|
|
"waypointPoiPoint": "0.000000,0.000000,0.000000",
|
|
|
"waypointHeadingAngleEnable": 1,
|
|
|
"waypointHeadingPathMode": "followBadArc",
|
|
|
"waypointHeadingPoiIndex": 0
|
|
|
},
|
|
|
"waypointTurnParam": {
|
|
|
"waypointTurnMode": "toPointAndStopWithDiscontinuityCurvature",
|
|
|
"waypointTurnDampingDist": 0
|
|
|
},
|
|
|
"useStraightLine": 1,
|
|
|
"actionGroup": [
|
|
|
{
|
|
|
"actionGroupId": 0,
|
|
|
"actionGroupStartIndex": item.id,
|
|
|
"actionGroupEndIndex": airPoints.length - 2,
|
|
|
"actionGroupMode": "sequence",
|
|
|
"actionTrigger": {
|
|
|
"actionTriggerType": "betweenAdjacentPoints"
|
|
|
},
|
|
|
"action": [
|
|
|
{
|
|
|
"actionId": 0,
|
|
|
"actionActuatorFunc": "gimbalAngleLock"
|
|
|
},
|
|
|
{
|
|
|
"actionId": 1,
|
|
|
"actionActuatorFunc": "gimbalRotate",
|
|
|
"actionActuatorFuncParam": {
|
|
|
"gimbalHeadingYawBase": "aircraft",
|
|
|
"gimbalRotateMode": "absoluteAngle",
|
|
|
"gimbalPitchRotateEnable": 1,
|
|
|
"gimbalPitchRotateAngle": -90,
|
|
|
"gimbalRollRotateEnable": 0,
|
|
|
"gimbalRollRotateAngle": 0,
|
|
|
"gimbalYawRotateEnable": 0,
|
|
|
"gimbalYawRotateAngle": 0,
|
|
|
"gimbalRotateTimeEnable": 0,
|
|
|
"gimbalRotateTime": 10,
|
|
|
"payloadPositionIndex": 0
|
|
|
}
|
|
|
},
|
|
|
{
|
|
|
"actionId": 2,
|
|
|
"actionActuatorFunc": "startTimeLapse",
|
|
|
"actionActuatorFuncParam": {
|
|
|
"payloadPositionIndex": 0,
|
|
|
"useGlobalPayloadLensIndex": 0,
|
|
|
"payloadLensIndex": "visable",
|
|
|
"minShootInterval":spaceTime
|
|
|
}
|
|
|
}
|
|
|
]
|
|
|
},
|
|
|
{
|
|
|
"actionGroupId": 1,
|
|
|
"actionGroupStartIndex": item.id,
|
|
|
"actionGroupEndIndex": airPoints.length - 2,
|
|
|
"actionGroupMode": "sequence",
|
|
|
"actionTrigger": {
|
|
|
"actionTriggerType": "multipleTiming",
|
|
|
"actionTriggerParam": 2
|
|
|
},
|
|
|
"action": {
|
|
|
"actionId": 0,
|
|
|
"actionActuatorFunc": "gimbalRotate",
|
|
|
"actionActuatorFuncParam": {
|
|
|
"gimbalHeadingYawBase": "aircraft",
|
|
|
"gimbalRotateMode": "absoluteAngle",
|
|
|
"gimbalPitchRotateEnable": 1,
|
|
|
"gimbalPitchRotateAngle": -90,
|
|
|
"gimbalRollRotateEnable": 0,
|
|
|
"gimbalRollRotateAngle": 0,
|
|
|
"gimbalYawRotateEnable": 0,
|
|
|
"gimbalYawRotateAngle": 0,
|
|
|
"gimbalRotateTimeEnable": 0,
|
|
|
"gimbalRotateTime": 10,
|
|
|
"payloadPositionIndex": 0
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
],
|
|
|
"waypointGimbalHeadingParam": {
|
|
|
"waypointGimbalPitchAngle": 0,
|
|
|
"waypointGimbalYawAngle": 0
|
|
|
},
|
|
|
"isRisky": 0,
|
|
|
"waypointWorkType": 0
|
|
|
}
|
|
|
}else if(index == airPoints.length -1){ // 处理最后1个航点
|
|
|
point = {
|
|
|
"Point": {
|
|
|
"coordinates":"\n"+item.lng+","+item.lat +"\n"
|
|
|
},
|
|
|
"index": item.id,
|
|
|
"executeHeight": this.templateKmlConfig.Folder.Placemark.height,
|
|
|
"waypointSpeed": this.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,
|
|
|
"actionGroup": {
|
|
|
"actionGroupId": 6,
|
|
|
"actionGroupStartIndex": item.id,
|
|
|
"actionGroupEndIndex": item.id,
|
|
|
"actionGroupMode": "sequence",
|
|
|
"actionTrigger": {
|
|
|
"actionTriggerType": "reachPoint"
|
|
|
},
|
|
|
"action": [
|
|
|
{
|
|
|
"actionId": 0,
|
|
|
"actionActuatorFunc": "stopTimeLapse",
|
|
|
"actionActuatorFuncParam": {
|
|
|
"payloadPositionIndex": 0,
|
|
|
"payloadLensIndex": "visable"
|
|
|
}
|
|
|
},
|
|
|
{
|
|
|
"actionId": 1,
|
|
|
"actionActuatorFunc": "gimbalAngleUnlock"
|
|
|
}
|
|
|
]
|
|
|
},
|
|
|
"waypointGimbalHeadingParam": {
|
|
|
"waypointGimbalPitchAngle": 0,
|
|
|
"waypointGimbalYawAngle": 0
|
|
|
},
|
|
|
"isRisky": 0,
|
|
|
"waypointWorkType": 0
|
|
|
}
|
|
|
}else if(index == airPoints.length -2){ // 处理倒数第2个航点
|
|
|
|
|
|
point = {
|
|
|
"Point": {
|
|
|
"coordinates":"\n"+ (item.lng+0.00001)+","+(item.lat+0.00001)+"\n"
|
|
|
},
|
|
|
"index": item.id,
|
|
|
"executeHeight": this.templateKmlConfig.Folder.Placemark.height,
|
|
|
"waypointSpeed": this.templateKmlConfig.Folder.autoFlightSpeed,
|
|
|
"waypointHeadingParam": {
|
|
|
"waypointHeadingMode": "followWayline",
|
|
|
"waypointHeadingAngle": -120.875268753987,
|
|
|
"waypointPoiPoint": "0.000000,0.000000,0.000000",
|
|
|
"waypointHeadingAngleEnable": 1,
|
|
|
"waypointHeadingPathMode": "followBadArc",
|
|
|
"waypointHeadingPoiIndex": 0
|
|
|
},
|
|
|
"waypointTurnParam": {
|
|
|
"waypointTurnMode": "toPointAndStopWithDiscontinuityCurvature",
|
|
|
"waypointTurnDampingDist": 0
|
|
|
},
|
|
|
"useStraightLine": 1,
|
|
|
"actionGroup": [
|
|
|
{
|
|
|
"actionGroupId": 3,
|
|
|
"actionGroupStartIndex": item.id,
|
|
|
"actionGroupEndIndex": item.id,
|
|
|
"actionGroupMode": "sequence",
|
|
|
"actionTrigger": {
|
|
|
"actionTriggerType": "reachPoint"
|
|
|
},
|
|
|
"action": [
|
|
|
{
|
|
|
"actionId": 0,
|
|
|
"actionActuatorFunc": "gimbalRotate",
|
|
|
"actionActuatorFuncParam": {
|
|
|
"gimbalHeadingYawBase": "aircraft",
|
|
|
"gimbalRotateMode": "absoluteAngle",
|
|
|
"gimbalPitchRotateEnable": 1,
|
|
|
"gimbalPitchRotateAngle": -45,
|
|
|
"gimbalRollRotateEnable": 0,
|
|
|
"gimbalRollRotateAngle": 0,
|
|
|
"gimbalYawRotateEnable": 1,
|
|
|
"gimbalYawRotateAngle": 0,
|
|
|
"gimbalRotateTimeEnable": 0,
|
|
|
"gimbalRotateTime": 10,
|
|
|
"payloadPositionIndex": 0
|
|
|
}
|
|
|
},
|
|
|
{
|
|
|
"actionId": 1,
|
|
|
"actionActuatorFunc": "hover",
|
|
|
"actionActuatorFuncParam": {
|
|
|
"hoverTime": 0.5
|
|
|
}
|
|
|
}
|
|
|
]
|
|
|
},
|
|
|
{
|
|
|
"actionGroupId": 4,
|
|
|
"actionGroupStartIndex": item.id,
|
|
|
"actionGroupEndIndex": item.id+1,
|
|
|
"actionGroupMode": "sequence",
|
|
|
"actionTrigger": {
|
|
|
"actionTriggerType": "multipleTiming",
|
|
|
"actionTriggerParam": 2
|
|
|
},
|
|
|
"action": {
|
|
|
"actionId": 0,
|
|
|
"actionActuatorFunc": "gimbalRotate",
|
|
|
"actionActuatorFuncParam": {
|
|
|
"gimbalHeadingYawBase": "aircraft",
|
|
|
"gimbalRotateMode": "absoluteAngle",
|
|
|
"gimbalPitchRotateEnable": 1,
|
|
|
"gimbalPitchRotateAngle": -45,
|
|
|
"gimbalRollRotateEnable": 0,
|
|
|
"gimbalRollRotateAngle": 0,
|
|
|
"gimbalYawRotateEnable": 0,
|
|
|
"gimbalYawRotateAngle": 0,
|
|
|
"gimbalRotateTimeEnable": 0,
|
|
|
"gimbalRotateTime": 10,
|
|
|
"payloadPositionIndex": 0
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
{
|
|
|
"actionGroupId": 5,
|
|
|
"actionGroupStartIndex": item.id,
|
|
|
"actionGroupEndIndex": item.id+1,
|
|
|
"actionGroupMode": "sequence",
|
|
|
"actionTrigger": {
|
|
|
"actionTriggerType": "betweenAdjacentPoints"
|
|
|
},
|
|
|
"action": [
|
|
|
{
|
|
|
"actionId": 0,
|
|
|
"actionActuatorFunc": "gimbalAngleLock"
|
|
|
},
|
|
|
{
|
|
|
"actionId": 1,
|
|
|
"actionActuatorFunc": "gimbalRotate",
|
|
|
"actionActuatorFuncParam": {
|
|
|
"gimbalHeadingYawBase": "aircraft",
|
|
|
"gimbalRotateMode": "absoluteAngle",
|
|
|
"gimbalPitchRotateEnable": 1,
|
|
|
"gimbalPitchRotateAngle": -45,
|
|
|
"gimbalRollRotateEnable": 0,
|
|
|
"gimbalRollRotateAngle": 0,
|
|
|
"gimbalYawRotateEnable": 0,
|
|
|
"gimbalYawRotateAngle": 0,
|
|
|
"gimbalRotateTimeEnable": 0,
|
|
|
"gimbalRotateTime": 10,
|
|
|
"payloadPositionIndex": 0
|
|
|
}
|
|
|
},
|
|
|
{
|
|
|
"actionId": 2,
|
|
|
"actionActuatorFunc": "startTimeLapse",
|
|
|
"actionActuatorFuncParam": {
|
|
|
"payloadPositionIndex": 0,
|
|
|
"useGlobalPayloadLensIndex": 0,
|
|
|
"payloadLensIndex": "visable",
|
|
|
"minShootInterval": spaceTime
|
|
|
}
|
|
|
}
|
|
|
]
|
|
|
}
|
|
|
],
|
|
|
"waypointGimbalHeadingParam": {
|
|
|
"waypointGimbalPitchAngle": 0,
|
|
|
"waypointGimbalYawAngle": 0
|
|
|
},
|
|
|
"isRisky": 0,
|
|
|
"waypointWorkType": 0
|
|
|
};
|
|
|
let point2 = {
|
|
|
"Point": {
|
|
|
"coordinates":(item.lng+0.00001)+","+(item.lat+0.00001)
|
|
|
},
|
|
|
"index": item.id,
|
|
|
"executeHeight": this.templateKmlConfig.Folder.Placemark.height,
|
|
|
"waypointSpeed": this.templateKmlConfig.Folder.autoFlightSpeed,
|
|
|
"waypointHeadingParam": {
|
|
|
"waypointHeadingMode": "followWayline",
|
|
|
"waypointHeadingAngle": -44.6751949389683,
|
|
|
"waypointPoiPoint": "0.000000,0.000000,0.000000",
|
|
|
"waypointHeadingAngleEnable": 1,
|
|
|
"waypointHeadingPathMode": "followBadArc",
|
|
|
"waypointHeadingPoiIndex": 0
|
|
|
},
|
|
|
"waypointTurnParam": {
|
|
|
"waypointTurnMode": "toPointAndStopWithDiscontinuityCurvature",
|
|
|
"waypointTurnDampingDist": 0
|
|
|
},
|
|
|
"useStraightLine": 1,
|
|
|
"actionGroup": [
|
|
|
{
|
|
|
"actionGroupId": 3,
|
|
|
"actionGroupStartIndex": item.id,
|
|
|
"actionGroupEndIndex": item.id,
|
|
|
"actionGroupMode": "sequence",
|
|
|
"actionTrigger": {
|
|
|
"actionTriggerType": "reachPoint"
|
|
|
},
|
|
|
"action": [
|
|
|
{
|
|
|
"actionId": 0,
|
|
|
"actionActuatorFunc": "gimbalRotate",
|
|
|
"actionActuatorFuncParam": {
|
|
|
"gimbalHeadingYawBase": "aircraft",
|
|
|
"gimbalRotateMode": "absoluteAngle",
|
|
|
"gimbalPitchRotateEnable": 1,
|
|
|
"gimbalPitchRotateAngle": -45,
|
|
|
"gimbalRollRotateEnable": 0,
|
|
|
"gimbalRollRotateAngle": 0,
|
|
|
"gimbalYawRotateEnable": 1,
|
|
|
"gimbalYawRotateAngle": 0,
|
|
|
"gimbalRotateTimeEnable": 0,
|
|
|
"gimbalRotateTime": 10,
|
|
|
"payloadPositionIndex": 0
|
|
|
}
|
|
|
},
|
|
|
{
|
|
|
"actionId": 1,
|
|
|
"actionActuatorFunc": "hover",
|
|
|
"actionActuatorFuncParam": {
|
|
|
"hoverTime": 0.5
|
|
|
}
|
|
|
}
|
|
|
]
|
|
|
},
|
|
|
{
|
|
|
"actionGroupId": 4,
|
|
|
"actionGroupStartIndex": item.id,
|
|
|
"actionGroupEndIndex": item.id+1,
|
|
|
"actionGroupMode": "sequence",
|
|
|
"actionTrigger": {
|
|
|
"actionTriggerType": "multipleTiming",
|
|
|
"actionTriggerParam": 2
|
|
|
},
|
|
|
"action": {
|
|
|
"actionId": 0,
|
|
|
"actionActuatorFunc": "gimbalRotate",
|
|
|
"actionActuatorFuncParam": {
|
|
|
"gimbalHeadingYawBase": "aircraft",
|
|
|
"gimbalRotateMode": "absoluteAngle",
|
|
|
"gimbalPitchRotateEnable": 1,
|
|
|
"gimbalPitchRotateAngle": -45,
|
|
|
"gimbalRollRotateEnable": 0,
|
|
|
"gimbalRollRotateAngle": 0,
|
|
|
"gimbalYawRotateEnable": 0,
|
|
|
"gimbalYawRotateAngle": 0,
|
|
|
"gimbalRotateTimeEnable": 0,
|
|
|
"gimbalRotateTime": 10,
|
|
|
"payloadPositionIndex": 0
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
{
|
|
|
"actionGroupId":5,
|
|
|
"actionGroupStartIndex": item.id,
|
|
|
"actionGroupEndIndex": item.id+1,
|
|
|
"actionGroupMode": "sequence",
|
|
|
"actionTrigger": {
|
|
|
"actionTriggerType": "betweenAdjacentPoints"
|
|
|
},
|
|
|
"action": [
|
|
|
{
|
|
|
"actionId": 0,
|
|
|
"actionActuatorFunc": "gimbalAngleLock"
|
|
|
},
|
|
|
{
|
|
|
"actionId": 1,
|
|
|
"actionActuatorFunc": "gimbalRotate",
|
|
|
"actionActuatorFuncParam": {
|
|
|
"gimbalHeadingYawBase": "aircraft",
|
|
|
"gimbalRotateMode": "absoluteAngle",
|
|
|
"gimbalPitchRotateEnable": 1,
|
|
|
"gimbalPitchRotateAngle": -45,
|
|
|
"gimbalRollRotateEnable": 0,
|
|
|
"gimbalRollRotateAngle": 0,
|
|
|
"gimbalYawRotateEnable": 0,
|
|
|
"gimbalYawRotateAngle": 0,
|
|
|
"gimbalRotateTimeEnable": 0,
|
|
|
"gimbalRotateTime": 10,
|
|
|
"payloadPositionIndex": 0
|
|
|
}
|
|
|
},
|
|
|
{
|
|
|
"actionId": 2,
|
|
|
"actionActuatorFunc": "startTimeLapse",
|
|
|
"actionActuatorFuncParam": {
|
|
|
"payloadPositionIndex": 0,
|
|
|
"useGlobalPayloadLensIndex": 0,
|
|
|
"payloadLensIndex": "visable",
|
|
|
"minShootInterval": spaceTime
|
|
|
}
|
|
|
}
|
|
|
]
|
|
|
},
|
|
|
|
|
|
],
|
|
|
"waypointGimbalHeadingParam": {
|
|
|
"waypointGimbalPitchAngle": 0,
|
|
|
"waypointGimbalYawAngle": 0
|
|
|
},
|
|
|
"isRisky": 0,
|
|
|
"waypointWorkType": 0
|
|
|
}
|
|
|
}else if(index == airPoints.length -3){
|
|
|
point = {
|
|
|
"Point": {
|
|
|
"coordinates":"\n"+item.lng+","+item.lat+"\n"
|
|
|
},
|
|
|
"index": item.id,
|
|
|
"executeHeight": this.templateKmlConfig.Folder.Placemark.height,
|
|
|
"waypointSpeed": this.templateKmlConfig.Folder.autoFlightSpeed,
|
|
|
"waypointHeadingParam": {
|
|
|
"waypointHeadingMode": "followWayline",
|
|
|
"waypointHeadingAngle": -90.9999957673264,
|
|
|
"waypointPoiPoint": "0.000000,0.000000,0.000000",
|
|
|
"waypointHeadingAngleEnable": 1,
|
|
|
"waypointHeadingPathMode": "followBadArc",
|
|
|
"waypointHeadingPoiIndex": 0
|
|
|
},
|
|
|
"waypointTurnParam": {
|
|
|
"waypointTurnMode": "toPointAndStopWithDiscontinuityCurvature",
|
|
|
"waypointTurnDampingDist": 0
|
|
|
},
|
|
|
"useStraightLine": 1,
|
|
|
"actionGroup": {
|
|
|
"actionGroupId": 2,
|
|
|
"actionGroupStartIndex": item.id,
|
|
|
"actionGroupEndIndex": item.id,
|
|
|
"actionGroupMode": "sequence",
|
|
|
"actionTrigger": {
|
|
|
"actionTriggerType": "reachPoint"
|
|
|
},
|
|
|
"action": [
|
|
|
{
|
|
|
"actionId": 0,
|
|
|
"actionActuatorFunc": "stopTimeLapse",
|
|
|
"actionActuatorFuncParam": {
|
|
|
"payloadPositionIndex": 0,
|
|
|
"payloadLensIndex": "visable"
|
|
|
}
|
|
|
},
|
|
|
{
|
|
|
"actionId": 1,
|
|
|
"actionActuatorFunc": "gimbalAngleUnlock"
|
|
|
}
|
|
|
]
|
|
|
},
|
|
|
"waypointGimbalHeadingParam": {
|
|
|
"waypointGimbalPitchAngle": 0,
|
|
|
"waypointGimbalYawAngle": 0
|
|
|
},
|
|
|
"isRisky": 0,
|
|
|
"waypointWorkType": 0
|
|
|
}
|
|
|
}else{ // 处理中间航点
|
|
|
point = {
|
|
|
"Point": {
|
|
|
"coordinates":"\n"+item.lng+","+item.lat+"\n"
|
|
|
},
|
|
|
"index": item.id,
|
|
|
"executeHeight": this.templateKmlConfig.Folder.Placemark.height,
|
|
|
"waypointSpeed": this.templateKmlConfig.Folder.autoFlightSpeed,
|
|
|
"waypointHeadingParam": {
|
|
|
"waypointHeadingMode": "followWayline",
|
|
|
"waypointHeadingAngle": -96.9993404112147,
|
|
|
"waypointPoiPoint": "0.000000,0.000000,0.000000",
|
|
|
"waypointHeadingAngleEnable": 1,
|
|
|
"waypointHeadingPathMode": "followBadArc",
|
|
|
"waypointHeadingPoiIndex": 0
|
|
|
},
|
|
|
"waypointTurnParam": {
|
|
|
"waypointTurnMode": "coordinateTurn",
|
|
|
"waypointTurnDampingDist": 10
|
|
|
},
|
|
|
"useStraightLine": 1,
|
|
|
"waypointGimbalHeadingParam": {
|
|
|
"waypointGimbalPitchAngle": 0,
|
|
|
"waypointGimbalYawAngle": 0
|
|
|
},
|
|
|
"isRisky": 0,
|
|
|
"waypointWorkType": 0
|
|
|
}
|
|
|
}
|
|
|
|
|
|
this.waylineWpmlConfig.Folder.Placemark.push(point);
|
|
|
})
|
|
|
|
|
|
return await this.generateKmzFile();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 生成kmz航线文件
|
|
|
*/
|
|
|
async generateKmzFile(){
|
|
|
|
|
|
// 将template中的 missionConfig 赋值 给wayline
|
|
|
this.waylineWpmlConfig.missionConfig = JSON.parse(JSON.stringify(this.templateKmlConfig.missionConfig))
|
|
|
|
|
|
const builder = new XMLBuilder({
|
|
|
format: true, // 启用换行和缩进
|
|
|
indentBy: " ", // 缩进字符(默认2空格,可自定义为 \t 等)
|
|
|
suppressEmptyNode: true, // 可选:是否忽略空节点
|
|
|
});
|
|
|
|
|
|
// 带wpml前缀的 template json数据
|
|
|
let templateJson = {kml:{Document:this.templateKmlConfig}}
|
|
|
let templateWpmlJson = this.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">`)
|
|
|
|
|
|
|
|
|
let waylineJosn = {kml:{Document:this.waylineWpmlConfig}}
|
|
|
let waylienWpmlJson = this.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">`)
|
|
|
|
|
|
// 创建压缩文件
|
|
|
return await this.handlerCreateFile(templateXml,waylineXml);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 处理标签前缀
|
|
|
*/
|
|
|
handlerPrefixWpml(obj){
|
|
|
|
|
|
for (const key in obj) {
|
|
|
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) { // 如果是对象
|
|
|
|
|
|
this.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])){ // 如果是数组
|
|
|
|
|
|
this.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;
|
|
|
}
|
|
|
}
|
|
|
return obj;
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 文件压缩
|
|
|
*/
|
|
|
async handlerCreateFile(templateXml,waylineXml){
|
|
|
|
|
|
const blob = await this.convertXmlToKmz(templateXml,waylineXml);
|
|
|
return blob;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* xml文件转换为 kml、wpml文件
|
|
|
*/
|
|
|
async convertXmlToKmz(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;
|
|
|
}
|
|
|
|
|
|
} |