news 2026/5/1 10:15:49

用Cesium.js和CZML手搓一个无人机航线编辑器(附完整代码)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用Cesium.js和CZML手搓一个无人机航线编辑器(附完整代码)

用Cesium.js和CZML手搓一个无人机航线编辑器(附完整代码)

在无人机应用日益普及的今天,航线规划工具成为了开发者和操作人员不可或缺的助手。不同于商业软件的黑箱操作,自己动手构建一个轻量级航线编辑器不仅能满足特定需求,还能深入理解地理信息系统(GIS)与无人机飞控的结合原理。本文将带您从零开始,使用开源的Cesium.js三维地球库和CZML数据格式,打造一个功能完备的无人机航线编辑器。

这个工具将具备以下核心功能:

  • 交互式地图上的航线绘制与编辑
  • 航点高度、速度等参数的可视化调整
  • CZML格式的导入导出能力
  • 实时3D飞行预览

1. 环境准备与Cesium基础配置

1.1 项目初始化

首先创建一个标准的Web项目结构:

/drone-path-editor ├── index.html ├── scripts/ │ └── main.js ├── styles/ │ └── main.css └── assets/ └── czml/

安装Cesium.js的最简单方式是通过CDN引入,在index.html中添加:

<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>无人机航线编辑器</title> <link href="https://cesium.com/downloads/cesiumjs/releases/1.95/Build/Cesium/Widgets/widgets.css" rel="stylesheet"> <link href="styles/main.css" rel="stylesheet"> </head> <body> <div id="cesiumContainer"></div> <div id="controlPanel" class="control-panel"> <!-- 控制界面将在这里动态生成 --> </div> <script src="https://cesium.com/downloads/cesiumjs/releases/1.95/Build/Cesium/Cesium.js"></script> <script src="scripts/main.js" type="module"></script> </body> </html>

1.2 Cesium Viewer初始化

在main.js中配置基础地图视图:

Cesium.Ion.defaultAccessToken = '您的Cesium Ion访问令牌'; const viewer = new Cesium.Viewer('cesiumContainer', { terrainProvider: Cesium.createWorldTerrain(), timeline: true, animation: true, sceneModePicker: true, baseLayerPicker: false, imageryProvider: new Cesium.IonImageryProvider({ assetId: 3845 }), shouldAnimate: true }); // 禁用默认的地图操作冲突 viewer.scene.screenSpaceCameraController.enableCollisionDetection = false;

提示:获取Cesium Ion访问令牌需要注册免费账户,开发阶段可以使用测试令牌。

2. CZML数据结构解析与航线建模

2.1 理解CZML格式

CZML(发音为"Cee-Zee-M-L")是Cesium团队专门为动态场景设计的JSON格式。一个典型的航线CZML文档包含以下结构:

[ { "id": "document", "name": "无人机航线", "version": "1.0" }, { "id": "path1", "name": "测绘航线", "polyline": { "positions": { "cartographicDegrees": [ -75.0, 35.0, 100, -75.1, 35.1, 120, -75.2, 35.2, 100 ] }, "material": { "polylineGlow": { "color": {"rgba": [0, 255, 255, 255]} } }, "width": 8, "clampToGround": false } } ]

关键参数说明:

参数类型描述
positions.cartographicDegreesArray经度、纬度、高度(米)的三元组序列
materialObject定义线条的渲染样式
widthNumber线宽(像素)
clampToGroundBoolean是否贴地

2.2 动态生成CZML

我们需要创建一个函数,将用户交互生成的航点转换为CZML:

function generateCZML(waypoints) { const czml = [ { id: "document", name: "无人机航线", version: "1.0" } ]; const positions = []; waypoints.forEach(wp => { positions.push(wp.longitude, wp.latitude, wp.altitude); }); czml.push({ id: "flightPath", name: "飞行路径", polyline: { positions: { cartographicDegrees: positions }, material: { polylineGlow: { color: { rgba: [0, 255, 255, 200] }, glowPower: 0.2 } }, width: 6, clampToGround: false } }); return czml; }

3. 交互式航线编辑实现

3.1 航点绘制逻辑

实现鼠标点击添加航点的核心代码:

let waypoints = []; let activePoint = null; viewer.screenSpaceEventHandler.setInputAction((movement) => { const ray = viewer.camera.getPickRay(movement.position); const position = viewer.scene.globe.pick(ray, viewer.scene); if (position) { const cartographic = Cesium.Cartographic.fromCartesian(position); const longitude = Cesium.Math.toDegrees(cartographic.longitude); const latitude = Cesium.Math.toDegrees(cartographic.latitude); const altitude = cartographic.height; const newPoint = { longitude, latitude, altitude: altitude + 50 // 默认离地50米 }; waypoints.push(newPoint); updateFlightPath(); } }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

3.2 航点拖拽编辑

为航点添加可拖拽功能:

function createDraggablePoint(point) { const entity = viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees( point.longitude, point.latitude, point.altitude ), point: { pixelSize: 15, color: Cesium.Color.RED, outlineColor: Cesium.Color.WHITE, outlineWidth: 2, heightReference: Cesium.HeightReference.CLAMP_TO_GROUND } }); // 拖拽交互处理 viewer.screenSpaceEventHandler.setInputAction((movement) => { const pickedObject = viewer.scene.pick(movement.position); if (pickedObject && pickedObject.id === entity) { activePoint = entity; } }, Cesium.ScreenSpaceEventType.LEFT_DOWN); viewer.screenSpaceEventHandler.setInputAction((movement) => { if (activePoint) { const ray = viewer.camera.getPickRay(movement.endPosition); const position = viewer.scene.globe.pick(ray, viewer.scene); if (position) { activePoint.position = position; const cartographic = Cesium.Cartographic.fromCartesian(position); const index = waypoints.findIndex( wp => wp.longitude === point.longitude && wp.latitude === point.latitude ); if (index !== -1) { waypoints[index] = { longitude: Cesium.Math.toDegrees(cartographic.longitude), latitude: Cesium.Math.toDegrees(cartographic.latitude), altitude: cartographic.height }; updateFlightPath(); } } } }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); viewer.screenSpaceEventHandler.setInputAction(() => { activePoint = null; }, Cesium.ScreenSpaceEventType.LEFT_UP); return entity; }

4. 高级功能实现

4.1 禁飞区检测

实现简单的圆形禁飞区检测:

function isInNoFlyZone(point, noFlyZones) { return noFlyZones.some(zone => { const distance = Cesium.Cartesian3.distance( Cesium.Cartesian3.fromDegrees(point.longitude, point.latitude, 0), Cesium.Cartesian3.fromDegrees(zone.longitude, zone.latitude, 0) ); return distance < zone.radius; }); } // 示例禁飞区数据 const noFlyZones = [ { longitude: -75.2, latitude: 35.1, radius: 1000 }, { longitude: -75.3, latitude: 35.3, radius: 1500 } ]; // 在添加航点时检查 viewer.screenSpaceEventHandler.setInputAction((movement) => { // ... 原有代码 ... if (isInNoFlyZone(newPoint, noFlyZones)) { viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees( newPoint.longitude, newPoint.latitude, newPoint.altitude ), label: { text: "禁飞区!", font: '14pt sans-serif', style: Cesium.LabelStyle.FILL_AND_OUTLINE, outlineWidth: 2, verticalOrigin: Cesium.VerticalOrigin.BOTTOM, pixelOffset: new Cesium.Cartesian2(0, -20), fillColor: Cesium.Color.RED } }); return; } // ... 继续原有逻辑 ... }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

4.2 3D飞行预览

使用Cesium的相机飞行动画实现航线预览:

function previewFlightPath() { if (waypoints.length < 2) return; const positions = waypoints.map(wp => Cesium.Cartesian3.fromDegrees(wp.longitude, wp.latitude, wp.altitude) ); const property = new Cesium.SampledPositionProperty(); const startTime = Cesium.JulianDate.fromDate(new Date()); const stopTime = Cesium.JulianDate.addSeconds( startTime, waypoints.length * 5, // 假设每个航点间飞行5秒 new Cesium.JulianDate() ); viewer.clock.startTime = startTime.clone(); viewer.clock.stopTime = stopTime.clone(); viewer.clock.currentTime = startTime.clone(); viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP; viewer.clock.multiplier = 1; viewer.timeline.zoomTo(startTime, stopTime); // 创建采样点 waypoints.forEach((wp, index) => { const time = Cesium.JulianDate.addSeconds( startTime, index * 5, new Cesium.JulianDate() ); const position = Cesium.Cartesian3.fromDegrees( wp.longitude, wp.latitude, wp.altitude ); property.addSample(time, position); // 添加航点时间标记 viewer.entities.add({ position: position, point: { pixelSize: 10, color: Cesium.Color.YELLOW, outlineColor: Cesium.Color.BLACK, outlineWidth: 1 }, label: { text: `航点 ${index + 1}`, font: '12pt sans-serif', style: Cesium.LabelStyle.FILL_AND_OUTLINE, outlineWidth: 2, verticalOrigin: Cesium.VerticalOrigin.BOTTOM, pixelOffset: new Cesium.Cartesian2(0, -15) } }); }); // 设置相机跟随 viewer.trackedEntity = viewer.entities.add({ position: property, model: { uri: 'assets/models/drone.glb', minimumPixelSize: 64 }, path: { resolution: 1, material: new Cesium.PolylineGlowMaterialProperty({ glowPower: 0.2, color: Cesium.Color.CYAN }), width: 10 } }); }

5. 完整代码整合与优化

5.1 UI控制面板实现

在controlPanel div中添加交互控件:

function initControlPanel() { const panel = document.getElementById('controlPanel'); panel.innerHTML = ` <div class="control-group"> <h3>航线编辑</h3> <button id="clearPath">清除航线</button> <button id="exportCZML">导出CZML</button> <button id="importCZML">导入CZML</button> <button id="previewFlight">预览飞行</button> </div> <div class="control-group"> <h3>航点参数</h3> <div id="waypointParams" style="display:none"> <label>高度(米): <input type="number" id="wpAltitude" min="0"></label> <label>速度(m/s): <input type="number" id="wpSpeed" min="1" value="5"></label> </div> </div> `; document.getElementById('clearPath').addEventListener('click', () => { viewer.entities.removeAll(); waypoints = []; }); document.getElementById('exportCZML').addEventListener('click', () => { const czml = generateCZML(waypoints); const blob = new Blob([JSON.stringify(czml)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'flight_path.czml'; document.body.appendChild(a); a.click(); document.body.removeChild(a); }); // 其他事件监听... }

5.2 性能优化建议

当处理大量航点时,需要考虑以下优化措施:

  1. 简化实体渲染

    viewer.scene.globe.showGroundAtmosphere = false; viewer.scene.fog.enabled = false; viewer.scene.skyAtmosphere.show = false;
  2. 使用Web Worker处理复杂计算

    // 在worker.js中 self.onmessage = function(e) { const { waypoints } = e.data; // 执行耗时的路径计算 const result = complexCalculation(waypoints); postMessage(result); }; // 在主线程中 const worker = new Worker('scripts/worker.js'); worker.postMessage({ waypoints }); worker.onmessage = function(e) { // 处理计算结果 };
  3. 实现LOD(细节层次)控制

    viewer.scene.screenSpaceCameraController.minimumZoomDistance = 100; viewer.scene.screenSpaceCameraController.maximumZoomDistance = 5000000;

6. 实际应用案例

6.1 农业测绘场景

假设我们需要规划一个农田测绘航线,要求:

  • 飞行高度100米
  • 航向重叠率80%
  • 旁向重叠率60%

实现代码示例:

function generateFarmSurveyPath(area) { const { north, south, east, west } = area; const altitude = 100; const path = []; // 计算航线条数和间距 const latDistance = Cesium.Cartesian3.distance( Cesium.Cartesian3.fromDegrees(west, north, 0), Cesium.Cartesian3.fromDegrees(west, south, 0) ); const lineCount = Math.ceil(latDistance / (altitude * 0.4)); // 60%旁向重叠 const latStep = (north - south) / lineCount; // 生成航线 for (let i = 0; i <= lineCount; i++) { const lat = south + i * latStep; if (i % 2 === 0) { path.push({ longitude: west, latitude: lat, altitude }); path.push({ longitude: east, latitude: lat, altitude }); } else { path.push({ longitude: east, latitude: lat, altitude }); path.push({ longitude: west, latitude: lat, altitude }); } } return path; }

6.2 电力巡检场景

对于电力线巡检,需要沿线路保持固定距离:

function generatePowerLinePath(points, distance) { const path = []; for (let i = 0; i < points.length - 1; i++) { const start = Cesium.Cartesian3.fromDegrees( points[i].longitude, points[i].latitude, points[i].altitude ); const end = Cesium.Cartesian3.fromDegrees( points[i+1].longitude, points[i+1].latitude, points[i+1].altitude ); const direction = Cesium.Cartesian3.subtract(end, start, new Cesium.Cartesian3()); const length = Cesium.Cartesian3.magnitude(direction); Cesium.Cartesian3.normalize(direction, direction); const steps = Math.ceil(length / distance); const stepSize = length / steps; for (let j = 0; j <= steps; j++) { const position = Cesium.Cartesian3.add( start, Cesium.Cartesian3.multiplyByScalar(direction, j * stepSize, new Cesium.Cartesian3()), new Cesium.Cartesian3() ); const cartographic = Cesium.Cartographic.fromCartesian(position); path.push({ longitude: Cesium.Math.toDegrees(cartographic.longitude), latitude: Cesium.Math.toDegrees(cartographic.latitude), altitude: cartographic.height }); } } return path; }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/1 10:14:25

别再被@DS坑了!Spring Boot多数据源切换必须开启AOP的完整配置指南

深度解析Spring Boot多数据源切换中DS注解失效的根治方案 如果你正在使用Spring Boot配合MyBatis-Plus的DS注解实现多数据源动态切换&#xff0c;却频繁遭遇注解"失灵"的窘境——明明标注了DS("slave")却依然固执地连接主库&#xff0c;那么这篇文章将为你…

作者头像 李华
网站建设 2026/5/1 10:14:23

3步搞定电脑键鼠操控手机:QtScrcpy让你的安卓设备秒变游戏手柄

3步搞定电脑键鼠操控手机&#xff1a;QtScrcpy让你的安卓设备秒变游戏手柄 【免费下载链接】QtScrcpy Android real-time display control software 项目地址: https://gitcode.com/GitHub_Trending/qt/QtScrcpy 你是否曾想过用电脑键盘鼠标玩手机游戏&#xff1f;或者想…

作者头像 李华
网站建设 2026/5/1 10:10:23

E2B:为AI代码执行构建的安全沙盒基础设施

1. 项目概述&#xff1a;E2B&#xff0c;为AI代码执行构建的安全沙盒 如果你正在开发一个AI驱动的代码生成工具&#xff0c;或者想为你的LLM应用增加代码执行能力&#xff0c;那么“如何安全地运行AI生成的代码”这个问题&#xff0c;大概率已经让你头疼过。直接把用户或AI生成…

作者头像 李华
网站建设 2026/5/1 10:10:22

如何用Python轻松获取股票数据:MOOTDX完整指南

如何用Python轻松获取股票数据&#xff1a;MOOTDX完整指南 【免费下载链接】mootdx 通达信数据读取的一个简便使用封装 项目地址: https://gitcode.com/GitHub_Trending/mo/mootdx 还在为股票数据获取困难而烦恼吗&#xff1f;今天我要向你介绍一个能让你的量化投资效率…

作者头像 李华
网站建设 2026/5/1 10:09:43

3分钟解锁QQ音乐加密文件:终极音频解密工具完整指南

3分钟解锁QQ音乐加密文件&#xff1a;终极音频解密工具完整指南 【免费下载链接】qmc-decoder Fastest & best convert qmc 2 mp3 | flac tools 项目地址: https://gitcode.com/gh_mirrors/qm/qmc-decoder 你是否曾经在QQ音乐下载了心爱的歌曲&#xff0c;却发现只能…

作者头像 李华