简介:面向无人机航线规划与三维可视化场景,这是一个基于 Cesium 三维地球引擎的可运行源码包,服务前端开发者和地理信息系统爱好者。项目复现大疆司空2的航线设计与动态渲染能力,围绕航点、航段和航线容器分层建模,重点解决航线箭头渲染:尝试过着色器与纹理贴图,最终改用离屏画布生成带透明通道的箭头位图,再通过图元与材质接口绑定到折线几何体,沿航段切线方向动态调节箭头大小与颜色。包体很轻量,共3个文件约8KB,包含可直接运行的 HTML 页面,以及环境配置与忽略规则文件,适合快速打开验证或嵌入现有项目。源码按模块组织,覆盖坐标转换、航迹解算、碰撞检测等核心逻辑,关键函数配有注释,还支持外部地理数据导入、网络通信指令下发和三维剖面联动。目前已有50人学习下载,对需要借鉴 Cesium 自定义图元与航线可视化的开发者,这是一份浓缩排错思路的可运行样例。
1. Ceisum航线规划:先弄明白这标题到底在说什么
先纠正一个拼写:标题里的 Ceisum 基本可以确定是 Cesium 的笔误,搜这个关键词的人,九成是在找三维地球里的无人机/飞行器航线规划 Demo。这类需求最常见的场景是:给出几个航点,在地球上用一条线把路径画出来,再让一架小飞机模型沿着这条线飞,同时相机跟着走。很多人以为这活儿的难点在“画线”,真正动手后才发现,核心问题是坐标转换、时间轴驱动和姿态计算——而这些恰恰是 Cesium 里最容易翻车的三块。
这篇笔记我按自己做航线规划 Demo 的顺序来写:先讲底座原理,再给最小可运行代码,然后调参数、避开常见坑,最后补上动态数据驱动的进阶玩法。如果你正要给某个巡检系统做航线展示,或者刚拿到一个“航线规划源码包”却看不懂它在跑什么,建议顺着章节一步步过一遍,代码可以直接抄。
2. Cesium 航线规划的底座:坐标、Entity 与 Property
航线规划在 Cesium 里的本质是:把一串 WGS84 经纬度坐标变成三维空间里的点序列,然后交给某个 Entity 去绘制或驱动。这三件事分别对应坐标体系、Entity 体系和 Property 体系。搞不懂这三层,后面所有代码都是懵的。
2.1 坐标系:为什么经纬度不能直接拿来画线
Cesium 的默认数据是 WGS84 椭球体,地理上我们用经度、纬度、高度(单位米)描述一个点。但这个描述方式不能直接用于三维渲染——图形引擎需要的是地心笛卡尔坐标(Cartesian3),也就是以地球中心为原点的 X/Y/Z 坐标。所有实体对象的 position、polyline 的 positions,接收的都是 Cartesian3 或它的数组。
// 错误示范:直接把经纬度塞进去 polyline: { positions: [[116.39, 39.9], [116.41, 39.91]] // 这样不会报错,但画出来是乱的 } // 正确做法:先转成 Cartesian3 const positions = Cesium.Cartesian3.fromDegreesArray([ 116.39, 39.9, 116.41, 39.91, 116.43, 39.92 ]);这就是新手最常见的第一个坑:经纬度和世界坐标混用。fromDegreesArray 是按 [经度, 纬度, 经度, 纬度...] 的扁平数组解析的,不是二维数组,也不是 [纬度, 经度] 的在地图服务里常见的顺序。我一般会在代码里写死顺序并用注释标清楚,避免后面数据接入时被带着走偏。
回到原理:这些 Cartesian3 坐标是相对地心的,单位是米。地球半径约 6371 公里,所以如果你把坐标直接拿来当作平面 XY 用,画出来的线会横穿地心,而不是贴在地表。这就是为什么有人看到航线“穿地”或者“飞到大洋彼岸”——不是地球模型错了,是坐标用错了。
2.2 Entity 体系:适合业务开发,但要知道它怎么更新
Cesium 有两套渲染 API:Primitive 和 Entity。Primitive 贴近底层,性能好,但代码繁琐,适合大批量数据;Entity 面向业务,数据驱动,写起来像操作普通对象,航线规划这种规模的数据完全够用。我自己的习惯是:航线绘制、飞行模拟、相机跟踪全用 Entity 实现,除非一次性要渲染上千条航线,才会去考虑 Primitive 的 polyline collection。
Entity 的另一个关键点是 Property。很多人第一次看到 CallbackProperty 不理解:它其实是一个“每次渲染时重新取值”的函数,这样你就能让实体动态变化。比如航线要随某个状态实时刷新,就可以用它。
// 动态刷新航线:每次渲染都取最新的坐标数组 const dynamicPositions = new Cesium.CallbackProperty(function () { return latestPositions; // 外部变量,更新后直接生效 }, false); viewer.entities.add({ polyline: { positions: dynamicPositions, width: 2 } });第二个参数 false 表示这个回调不是常量,每次渲染都要重新计算。如果误写成 true,Cesium 会认为结果恒定不变,后续更新就再也不生效了。这是很多人“数据变了但线不动”的原因。
2.3 让飞机按时飞起来:SampledPositionProperty 与时钟
画线只是静态结果,航线规划的重点是飞行过程。飞行意味着位置随时间变化,Cesium 为此提供了 SampledPositionProperty:它像一个采样表,把“时间点 → 坐标”成对存进去,渲染时按当前时钟插值出中间位置。配合 Cesium 自带的时钟系统,就能精确控制起飞、飞行、降落的过程。
const property = new Cesium.SampledPositionProperty(); // 每个航点绑定一个时刻,单位是 JulianDate property.addSample(Cesium.JulianDate.fromDate(new Date("2024-01-01T00:00:00Z")), Cesium.Cartesian3.fromDegrees(116.39, 39.9, 100)); property.addSample(Cesium.JulianDate.fromDate(new Date("2024-01-01T00:00:10Z")), Cesium.Cartesian3.fromDegrees(116.41, 39.91, 150)); // 把它赋给实体的 position,实体就会按时间走动 entity.position = property;SampledPositionProperty 的效果取决于两个因素:采样点的时间间距,以及插值方式(默认线性)。这两个参数直接影响飞行速度曲线,后面第 4 章我会单独说怎么调。
3. 跑通第一版:最小可运行的航线绘制与飞行 Demo
这章给一套能直接落地的代码结构。我习惯把这类 Demo 拆成三个文件:HTML 容器、JS 逻辑、JSON 数据。数据独立出来,方便后面接真实航点。
3.1 工程结构:三个文件跑起来
先说运行时依赖:你需要一个可用的 Cesium 库。开发环境用 npm 安装或 CDN 引入都行,我图省事常用 CDN,版本锁在一个大版本上,避免 API 漂移。这里不写死版本号,你按自己项目里已装好的来。
<!-- index.html --> <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <title>航线规划 Demo</title> <style> html, body, #cesiumContainer { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; } </style> <link href="https://cdn.jsdelivr.net/npm/cesium@latest/Build/Cesium/Widgets/widgets.css" rel="stylesheet" /> </head> <body> <div id="cesiumContainer"></div> <script src="https://cdn.jsdelivr.net/npm/cesium@latest/Build/Cesium/Cesium.js"></script> <script src="./flight.js"></script> </body> </html>这里要说明:Cesium 的窗口部件 CSS 和核心 JS 都必须正确引入,否则页面只有动画逻辑但地球渲染不出来。如果你的项目里有自己的构建流程,建议直接用 import 方式,CDN 方案适合快速验证。
数据文件 flight-data.json 存航点,格式我这样定义:
{ "name": "demo-route", "startTime": "2024-01-01T00:00:00Z", "points": [ { "lon": 116.39, "lat": 39.9, "height": 100 }, { "lon": 116.41, "lat": 39.91, "height": 150 }, { "lon": 116.43, "lat": 39.92, "height": 200 }, { "lon": 116.45, "lat": 39.90, "height": 120 } ] }字段含义:lon 是经度,lat 是纬度,height 是海拔高度(米)。startTime 是飞行起始时间,后面会用程序计算每个航点的到达时刻。数据组织成数组而不是扁平结构,是为了可读性——读取时再转成 Cesium 需要的扁平坐标数组。
3.2 绘制静态航线:三行代码的事
接下来写 flight.js。第一步加载数据并把航点转成 Cartesian3 数组,然后用 Entities 添加一条 polyline。
// flight.js const viewer = new Cesium.Viewer('cesiumContainer', { animation: true, // 显示动画控件 timeline: true, // 显示时间轴 baseLayerPicker: true, geocoder: false, sceneMode: Cesium.SceneMode.VIEW_2D // 可选:先二维后三维,默认三维 }); async function loadRoute() { const res = await fetch('./flight-data.json'); const data = await res.json(); const cartesianPositions = data.points.map(p => Cesium.Cartesian3.fromDegrees(p.lon, p.lat, p.height) ); viewer.entities.add({ name: 'route-line', polyline: { positions: cartesianPositions, width: 4, material: new Cesium.PolylineGlowMaterialProperty({ color: Cesium.Color.fromCssColorString('#00ff88'), glowPower: 0.2 }) } }); } loadRoute();这段逻辑看着简单,但有几个点容易踩:positions 必须是数组,而且每个元素都要是 Cartesian3,否则实体创建时不会报错,但渲染时可能整条线消失。width 的单位是像素,不是世界单位,所以大地球缩小时线会显得很粗,这在做标绘系统时要留意。用 PolylineGlowMaterialProperty 是为了让航线有发光效果,巡检演示时比纯颜色更能突出重点;生产环境如果线很多,建议换回纯颜色材质,减少渲染开销。
3.3 让飞机飞起来:SampledPositionProperty 与时钟设置
静态线画完,现在加飞行。思路是这样:把 flight-data.json 里的每个航点按顺序分配一个到达时间(比如每两个航点之间间隔 10 秒),然后把这些时间-坐标对塞进 SampledPositionProperty。
async function initFlight(data) { const start = Cesium.JulianDate.fromIso8601(data.startTime); const timeStep = 10; // 每个航段飞行秒数 const posProperty = new Cesium.SampledPositionProperty(); posProperty.setInterpolationOptions({ interpolationDegree: 1, interpolationAlgorithm: Cesium.LagrangePolynomialApproximation }); data.points.forEach((p, i) => { const time = Cesium.JulianDate.addSeconds(start, i * timeStep, new Cesium.JulianDate()); const position = Cesium.Cartesian3.fromDegrees(p.lon, p.lat, p.height); posProperty.addSample(time, position); }); // 设置 viewer 的时钟范围,让动画可以从头播到尾 const stop = Cesium.JulianDate.addSeconds(start, (data.points.length - 1) * timeStep, new Cesium.JulianDate()); viewer.clock.startTime = start.clone(); viewer.clock.stopTime = stop.clone(); viewer.clock.currentTime = start.clone(); viewer.clock.shouldAnimate = true; const flightEntity = viewer.entities.add({ position: posProperty, point: { pixelSize: 8, color: Cesium.Color.YELLOW }, // 如果想用模型替换点,取消下一行注释,并准备好 glb 文件 // model: { uri: '/models/CesiumMan.glb', scale: 1.0 } }); viewer.trackedEntity = flightEntity; } loadRoute().then(() => { // 重新读取一次数据传给飞行初始化 fetch('./flight-data.json') .then(r => r.json()) .then(initFlight); });这段代码里有三个容易出错的地方。一是 clock 必须显式设置 startTime 和 stopTime,否则默认时钟区间可能不覆盖你的采样时间,飞机会一直在初始位置不动。二是我把 viewer.clock.currentTime 设成 start,保证动画从起点开始,而不是从系统当前时间开始——这个细节很多人漏掉,结果飞机直接消失或瞬间跳到终点。三是 setInterpolationOptions 里面的 interpolationDegree: 1 表示线性插值,配合 LagrangePolynomialApproximation 是 Cesium 里最常见的组合;如果采样点过多,degree 设高了反而会造成震荡,后文会有具体说明。
3.4 相机跟踪:让视角跟着航线走
飞行实体创建完成后,把 viewer.trackedEntity 指向它,相机就会自动跟随。但这种跟随默认是“从外看目标”,如果你想做成第一视角(像坐在飞机上),需要改一下相机模式:
viewer.scene.preUpdate.addEventListener(() => { const pos = flightEntity.position.getValue(viewer.clock.currentTime); if (!pos) return; if (viewer.camera.positionCartographic.height < 50) { viewer.camera.lookAt(pos, new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-45), 0)); } });这段是临时方案:随着时钟推进,持续把相机对准飞机位置。注意我没用 viewer.trackedEntity 而是手动 lookAt,是因为某些版本里 trackedEntity 的默认跟随方式是第三人称的,切第一视角要么改 viewer.scene.screenSpaceCameraController 的 enableInputs,要么这样手动控制。生产系统里建议用 Camera.lookAtTransform 配合固定距离实现平滑跟随,但 Demo 阶段这样够直观。
4. 让航线规划真正可交付:速度插值、姿态与样式参数
能飞只是开始。真实的航线规划 Demo 要能回答三个问题:飞多久、怎么转向、姿态如何变化。这章把时间、插值、朝向和地形这几个必调参数说清。
4.1 速度与时间换算:航段间隔怎么设才自然
上一节的 timeStep 我写死成 10 秒。真实业务中,航点之间的地面距离不同,用固定时间间隔会导致速度忽快忽慢。正确做法:用航点间的大地距离除以期望速度,得到每段耗时。
function computeSegmentDuration(p1, p2, speedMps) { const carto1 = Cesium.Cartesian3.fromDegrees(p1.lon, p1.lat, p1.height); const carto2 = Cesium.Cartesian3.fromDegrees(p2.lon, p2.lat, p2.height); const distance = Cesium.Cartesian3.distance(carto1, carto2); return distance / speedMps; } // 用法 const speedMps = 15; // 15 米/秒 data.points.slice(0, -1).forEach((p, i) => { const t = computeSegmentDuration(p, data.points[i + 1], speedMps); // 累加到你自己的时间序列里 });这里我用的是空间直线距离,没考虑地球曲率和地形起伏。航点间距很小(几百米内)时误差可忽略;如果跨省飞行,建议改用 Cesium.EllipsoidGeodesic 计算测地线距离,否则算出来的耗时偏短。另外,速度单位务必统一——代码里是米/秒,如果需求文档写的是公里/小时,除以 3.6 再进来,这种低级换算错误我帮人排查时遇到不下三次。
4.2 插值方式:线性、样条与代码切换
第 3.3 节里我用了 degree: 1 线性插值。把它换成样条只需要改一个配置:
posProperty.setInterpolationOptions({ interpolationDegree: 3, interpolationAlgorithm: Cesium.HermitePolynomialApproximation });两种方案差异:
| 插值方式 | degree | 特性和适用场景 | 典型问题 |
|---|---|---|---|
| 线性插值 | 1 | 航段直来直去,速度恒定,适合航线简单、路径就是直线的情况 | 转角处拐得很硬,轨迹有折痕 |
| 三次样条/厄米特 | 3 | 轨迹平滑,转弯自然,适合模拟真实飞行 | 可能过冲,航线可能冲出航点范围 |
我自己的习惯:做航迹回放或航线预演用线性就够,因为要忠实还原设计航线;做“仿真飞行”或无人机表演一类需求才换三次样条。注意样条过冲不是 bug——数学上这是插值函数的固有行为,你需要检查插值后的轨迹是否穿山或离地太近。验证办法是把时间分成小步长、逐个求 position 值打印出来对比。
4.3 朝向与姿态:让机头自动对准前进方向
只有位置变化的飞机,在 Cesium 里看是“平移”的,机头不转向,很假。解决办法是用 VelocityOrientationProperty 自动从位置属性中计算速度方向,再转为朝向。
const flightEntity = viewer.entities.add({ position: posProperty, orientation: new Cesium.VelocityOrientationProperty(posProperty), model: { uri: '/models/CesiumMan.glb', scale: 0.5 } });这段代码把 orientation 与位置属性绑定,让模型始终顺着运动方向前进。这是最省事的方案,但要承认它的局限:它只根据速度向量算偏航和俯仰,不管飞机本身的滚转;而且当速度接近零时(比如航点处悬停),朝向会变得不稳定,因为单位向量方向不确定。如果你做的是四旋翼,这种转向其实是对的;如果做的是固定翼,需要自己写 HeadingPitchRoll 的平滑函数,而不是直接用 VelocityOrientationProperty。
4.4 地形贴合:为什么线会沉到山体里
Cesium 默认的地形是椭球面,不是真实地形起伏。你给的航点高度如果是相对地面的,画出来自然可能穿山。这时需要直接让线贴合地形:
const polyline = viewer.entities.add({ polyline: { positions: cartesianPositions, clampToGround: true } });clampToGround: true 可以让 polyline 自动贴合地形表面。但两个前提:一是 viewer 已经加载了地形数据,比如 createWorldTerrain,否则它贴的还是椭球面;二是这个属性对模型的 position 不起作用——模型位置始终是绝对三维坐标,不会自动吸到地形上。所以如果你的飞机模型要求贴着山坡飞,必须在业务层算好高度再传入,别指望 Cesium 帮你处理。
5. Cesium 航线规划避坑:5 个翻车场景与修复方案
这章写我实际调试中遇到频率最高的 5 个问题。每条按“现象 → 原因 → 解决”展开,有的不一定在文档里写明,属于经验型的坑。
5.1 航线整条跑到地球背面去了
现象:加载完数据后,视角切到目标区域却看不到航线,缩到全球视角才发现线画在了地球另一侧。 原因:绝大多数是经纬度顺序写反了。有人习惯 [lat, lon](像某些地图服务),而 Cesium 的 fromDegrees 严格是 (lon, lat, height)。另一个常见诱因是 JSON 数据里字段名是 lat/lon,但代码读取时写成了 p.lat, p.lon。 解决:在 fetch 数据后立即打印第一组坐标做断言。
if (data.points.length > 0) { const p = data.points[0]; console.log(`第一点: lon=${p.lon}, lat=${p.lat}, height=${p.height}`); if (Math.abs(p.lon) > 180 || Math.abs(p.lat) > 90) { console.error('经纬度字段有误,检查数据格式'); } }这个断言简单粗暴,能拦截大多数坐标顺序错误。我养成习惯:任何外部经纬度数据进来,先做范围校验再转 Cartesian3。
5.2 两个航点之间画出一条穿地心的直线
现象:航点明明在国内,画出来却是一条穿透地球内部的直线,两侧是陆地、中间是海洋。 原因:positions 数组中混入了非法值,比如 height 为 null 或字符串。fromDegrees 接收非法参数时不会抛异常,而是返回一个 NaN 坐标;Cesium 渲染时把所有 NaN 点连起来,就出现了看似“穿过地心”的线条。 解决:构造 Cartesian3 时统一用 Number 强制转换,并过滤掉无效值。
const validPoints = data.points .map(p => ({ lon: Number(p.lon), lat: Number(p.lat), height: Number(p.height) || 0 // 没有高度就按 0 处理 })) .filter(p => !isNaN(p.lon) && !isNaN(p.lat)); if (validPoints.length < 2) { console.error('有效航点少于 2 个,无法绘制航线'); return; }这段代码把“非法数据”拦截在进入 Cesium 之前。值得说明的是,Number(p.height) || 0 有一个副作用:如果高度字段是 0 也会被保留,因为 0 是 falsy,会变成 0,不影响结果;但如果你语义上要区分“未填”和“海拔 0”,还是要单独判断字段是否存在。
5.3 飞行实体一动不动,或时间轴不走
现象:飞机已经添加到 scene 里,但点位实体就是不动;点击时间轴播放按钮也没反应。 原因:clock.currentTime 不在位置采样的时间范围内。最常见的是忘了设置 clock.startTime / stopTime,Cesium 默认时间范围是当前系统时间附近,而你 addSample 的时间是 2024 年的固定值,根本不在范围里,于是当前位置查无数据,实体停在初始点或者干脆不渲染。 解决:显式设置时钟区间,并检查 shouldAnimate。
viewer.clock.startTime = start.clone(); viewer.clock.stopTime = stop.clone(); viewer.clock.currentTime = start.clone(); viewer.clock.shouldAnimate = true; viewer.clock.multiplier = 2; // 2 倍速播放调完代码后,在浏览器控制台手动执行 viewer.clock.currentTime.toString(),跟数据里的采样时间对比——如果时间范围对不上,飞行动画一定出问题。这条排查路径能省下大量“翻源码查为什么不动”的时间。
5.4 样条插值后航线异常过冲、飞出屏幕
现象:把插值从线性换成 degree: 3 的样条后,航线变得不贴合航点,甚至在转折处冲出很远。 原因:样条插值在数据点少(尤其只有 3-4 个航点)且航点间距不均匀时,会产生龙格现象。这不是代码 bug,是数学特性。初学 Cesium 的人遇到这个很容易怀疑插值实现有毛病,其实方向错了。 解决:航点少于 5 个时建议继续用线性;点数较多且间距均匀时再用样条。如果前后速度差异大,先对采样时间做均匀化处理。
// 统一时间步长:把总时长分成 N-1 等份,不考虑实际距离 function uniformTimeSamples(points, totalSeconds) { return points.map((p, i) => { const time = Cesium.JulianDate.addSeconds(start, totalSeconds * i / (points.length - 1), new Cesium.JulianDate()); return { time, position: Cesium.Cartesian3.fromDegrees(p.lon, p.lat, p.height) }; }); }这个函数会把时间等分,消除因采样时间间距不一致带来的样条抖动。代价是速度不再恒定——远距离航段显得快。到底是“速度恒定”还是“路径平滑”,你要先想清楚需求再选。
5.5 clampToGround 设置了线还是穿山
现象:代码里写了 clampToGround: true,航线还是插进山体里。 原因:clampToGround 只有在场景里有地形数据时才生效。如果 viewer 没加载地形,它贴合的“地面”就是椭球面,在真实山区自然会穿山。此外,这个属性对样式有要求:使用 PolylineGlowMaterialProperty 时,clampToGround 会失效,因为发光材质需要特殊渲染通道。 解决:先确认地形是否已到位,再检查材质类型。
const viewer = new Cesium.Viewer('cesiumContainer', { terrainProvider: Cesium.createWorldTerrain() }); // 绕开发光材质与 clamp 的兼容问题 polyline: { positions: validPositions, clampToGround: true, material: Cesium.Color.fromCssColorString('#00ff88'), // 不要用 Glow 材质 width: 3 }这条经验来自我自己趟过的泥:某次做山区输电线路巡检展示,线明明是 perching 状态,死活贴不了地,排查一圈发现是材质拖了后腿。所以涉及地形贴合时,样式越朴素越稳妥。
6. 进阶玩法:动态数据驱动与实时跟踪
走到这一步,你的 Demo 已经具备静态航线、飞行模拟、相机跟踪三个基本能力。现在说说怎么把它从“演示版”推向“能接真实业务”。核心就一句话:把航点数据从静态 JSON 换成外部输入。
6.1 把航点换成实时数据流
实际项目里,航点往往来自服务端——可能是无人机实时回传的 GPS 轨迹,也可能是后台上传的规划航线。动态更新航线不需要重新创建实体,只需要更新 positions 或 property 的数据源。
// 轮询接口,每次 fetch 更新全局变量 let livePositions = []; async function pollRoute() { const res = await fetch('/api/flight/current'); const data = await res.json(); livePositions = data.points.map(p => Cesium.Cartesian3.fromDegrees(p.lon, p.lat, p.height)); } // 定时更新,1 秒拉一次 setInterval(pollRoute, 1000); // 用 CallbackProperty 驱动航线 const liveLine = viewer.entities.add({ polyline: { positions: new Cesium.CallbackProperty(() => livePositions, false), width: 3, material: Cesium.Color.CYAN } });这段代码中,CallbackProperty 的 false 参数务必保留,否则 Cesium 会缓存第一次的结果,后面数据再变也画不出来。轮询间隔我写的是 1 秒,具体看你的后端数据频率:如果是无人机回传,通常 2-5 秒一个点是常态,没必要拉太频繁。
6.2 为已飞和未飞航段分段着色
飞行过程中,业务方通常希望看到一段分界:绿色表示已飞过,灰色表示尚未执行。实现思路很简单:在飞行实体更新的同时,根据当前时钟时间去截断 polyline 的 positions。
viewer.scene.preUpdate.addEventListener(() => { const currentTime = viewer.clock.currentTime; const upToIndex = flightProperty.getNumberOfSamples() - 1; let activeCount = 0; for (let i = 0; i < upToIndex; i++) { const sampleTime = flightProperty.getSampleTime(i); if (Cesium.JulianDate.lessThanOrEquals(sampleTime, currentTime)) { activeCount = i + 1; } } const activePositions = []; for (let i = 0; i < activeCount; i++) { activePositions.push(flightProperty.getValue(flightProperty.getSampleTime(i))); } flownLine.positions = activePositions; });这段逻辑里有个效率隐患:遍历采样点并重新构建数组,每次渲染都执行。当采样点数量超过几百时,计算量开始明显。更优做法是在每添加一个采样点时缓存当前已飞集合,而不是每次都重算。
6.3 收起线结尾的几句实在话
回看这套方案,真正决定它“能用还是玩具”的边界有几个:数据来源是否真实、时间轴是否与业务时钟同步、姿态是否适配机型。我做这类 Demo 的教训是:永远在产品侧把坐标和时间这两条写清楚,不要直接在渲染层猜——猜来的时间轴会让整个演示像一部按剧本演的戏,一旦接真实数据就崩。所以我现在的习惯是每接手一个新航线项目,第一件事就是在入口处加一段打印:打印起始时间、结束时间、航点数、首个航点坐标,跑通了再去看三维效果。这个习惯帮我省下无数排查时间,也推荐给你。希望帮到你。
本文还有配套的精品资源,点击获取