/** * 面状航线航线文件生成 * * 目标:构造方法传入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("",``) 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("",``) // 创建压缩文件 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; } }