手机网站建设请示做pc端网站必知
手机网站建设请示,做pc端网站必知,网络培训,建设银行人力资源系统网站怎么进Cesium实战#xff1a;5分钟搞定动态轨迹绘制与回放#xff08;附完整代码#xff09;
最近在做一个智慧物流的看板项目#xff0c;客户要求在三维地球上实时展示运输车辆的移动轨迹#xff0c;并且能随时回放历史行程。一开始觉得用Cesium实现这个功能会挺复杂#xff0…Cesium实战5分钟搞定动态轨迹绘制与回放附完整代码最近在做一个智慧物流的看板项目客户要求在三维地球上实时展示运输车辆的移动轨迹并且能随时回放历史行程。一开始觉得用Cesium实现这个功能会挺复杂毕竟涉及到时间轴、动态实体和路径可视化。但实际折腾下来发现只要抓住几个核心的API从数据准备到最终渲染最快5分钟就能跑通一个基础版本。这篇文章我就把自己趟过的路、踩过的坑以及最终沉淀下来的那套“组合拳”代码毫无保留地分享给你。无论你是刚接触Cesium的前端同学还是想快速实现类似功能的GIS开发者这篇实战指南都能让你直接上手。1. 环境准备与数据格式的“第一道坎”在开始写代码之前有两个前置工作必须搞定一是搭建一个能跑Cesium的基础环境二是把你的轨迹数据“翻译”成Cesium能听懂的语言。很多人卡在第一步其实没那么复杂。1.1 快速搭建Cesium开发环境如果你已经有一个现成的Vue或React项目引入Cesium最快的方式是通过CDN。但对于追求稳定性和离线开发的场景我强烈推荐使用cesium这个npm包。这里给出一个最简的Vite Vue 3配置方案React项目也大同小异。首先安装核心依赖npm install cesium vite-plugin-cesium -D然后在你的vite.config.js中配置插件import { defineConfig } from vite import vue from vitejs/plugin-vue import cesium from vite-plugin-cesium export default defineConfig({ plugins: [vue(), cesium()] })这个插件会自动帮你处理Cesium静态资源的拷贝和路径配置省去大量手动操作的麻烦。接下来在项目的入口组件比如App.vue中就可以直接初始化Viewer了import { onMounted } from vue import * as Cesium from cesium import cesium/Build/Cesium/Widgets/widgets.css onMounted(() { const viewer new Cesium.Viewer(cesiumContainer, { terrainProvider: Cesium.createWorldTerrain(), // 使用真实地形 animation: false, // 先关闭底部动画控件 timeline: false, // 先关闭时间轴控件 baseLayerPicker: false, // 简化界面 }) // 将viewer实例挂载到全局方便其他地方调用 window.viewer viewer })注意在生产环境中请避免使用window.viewer这种全局变量方式建议使用状态管理工具如Pinia、Redux或Provide/Inject来传递实例。这里仅为演示简洁。1.2 理解Cesium的“时空”数据格式这是最关键的一步。你的原始轨迹数据可能来自数据库、GPS设备或后端API格式五花八门。但Cesium处理动态对象的核心是SampledPositionProperty它要求数据是“时间-位置”的采样点序列。假设你从后端拿到的是这样一段JSON[ {lng: 116.397, lat: 39.907, alt: 50, timestamp: 2023-10-27T08:00:00Z}, {lng: 116.408, lat: 39.915, alt: 52, timestamp: 2023-10-27T08:00:30Z}, {lng: 116.423, lat: 39.922, alt: 48, timestamp: 2023-10-27T08:01:00Z} ]你需要写一个转换函数将其转化为Cesium需要的格式function convertToCesiumSamples(trackData) { const positionProperty new Cesium.SampledPositionProperty() trackData.forEach(point { // 1. 将时间字符串转换为Cesium.JulianDate const time Cesium.JulianDate.fromIso8601(point.timestamp) // 2. 将经纬度高程转换为Cesium.Cartesian3笛卡尔空间坐标 const position Cesium.Cartesian3.fromDegrees(point.lng, point.lat, point.alt) // 3. 添加采样点 positionProperty.addSample(time, position) }) return positionProperty }这里有个常见的坑时间格式。Cesium.JulianDate支持从JavaScript的Date对象或ISO 8601字符串创建。确保你的时间戳是UTC时间或者明确指定时区否则回放时可能出现时间错乱。2. 绘制静态轨迹线让路径先“躺”在地上动态回放之前我们先让整条轨迹线在地球上显示出来。这步能帮你快速验证数据转换是否正确以及视觉效果是否满意。2.1 使用Entity API绘制贴地线Cesium的EntityAPI是声明式的用起来很直观。绘制一条静态轨迹线你只需要关注几个关键属性function drawStaticTrackLine(positionsArray) { const viewer window.viewer const trackLineEntity viewer.entities.add({ name: 静态轨迹线, polyline: { positions: positionsArray, // 这是Cartesian3数组 width: 8, material: new Cesium.PolylineOutlineMaterialProperty({ color: Cesium.Color.ROYALBLUE, outlineWidth: 2, outlineColor: Cesium.Color.WHITE }), clampToGround: true // 关键让线贴在地形表面 } }) viewer.zoomTo(trackLineEntity) // 自动飞到轨迹范围 return trackLineEntity }如何获得positionsArray利用之前转换函数中的位置数据或者直接从你的原始数据生成// 假设你有原始的经纬度数组 [lng1, lat1, lng2, lat2, ...] const lonLatArray [116.397, 39.907, 116.408, 39.915, 116.423, 39.922] const positions Cesium.Cartesian3.fromDegreesArray(lonLatArray)2.2 解决“飘在空中”与性能问题当你设置clampToGround: true后线会自动贴地但在复杂山区或高楼区域可能会出现线段“穿模”或抖动。这时可以考虑使用Cesium.GroundPrimitive替代Entity它能提供更精确的地形贴合但API稍复杂。另一个问题是性能。当轨迹点非常密集比如上万点时一次性渲染整条线可能会卡顿。我的经验是数据抽稀在保证精度的前提下对轨迹点进行道格拉斯-普克算法抽稀。分段加载对于超长轨迹可以按时间或距离分段滚动加载。使用简化的材质PolylineOutlineMaterialProperty比PolylineGlowMaterialProperty性能开销小。下表对比了两种绘制方式的特性特性Entity.polylineGroundPrimitive易用性极高声明式API较低需手动创建几何贴地精度一般有近似计算极高与地形精确贴合性能点数量少时优秀大数据量时更优支持材质丰富颜色、发光、箭头等相对有限适用场景大多数常规轨迹可视化对贴地精度要求极高的场景如山地徒步轨迹对于95%的“动态轨迹回放”需求Entity.polyline完全够用而且代码更简洁。3. 实现动态轨迹回放让模型“活”起来静态线画好了接下来就是重头戏让一个模型比如汽车、飞机图标沿着这条线移动并且走过的路径能高亮显示。这需要联动Cesium的时钟(Clock)和时间轴(Timeline)系统。3.1 配置时钟与时间轴Cesium的viewer.clock是整个动态场景的“心脏”。你需要根据轨迹的起止时间来设置它function setupClockForTrack(startTime, endTime) { const viewer window.viewer // 1. 设置时钟的时间范围 viewer.clock.startTime startTime.clone() viewer.clock.stopTime endTime.clone() viewer.clock.currentTime startTime.clone() // 2. 设置时间速率。1.0是实时2.0是2倍速-1.0是倒放 viewer.clock.multiplier 2.0 // 3. 控制动画的启停 viewer.clock.shouldAnimate true // 开始播放 // 4. 显示时间轴控件让用户能交互 viewer.timeline.zoomTo(startTime, endTime) viewer.timeline.updateFromClock() viewer.timeline.show true viewer.animation.show true // 显示播放控制控件 }提示shouldAnimate设为true后场景就会根据multiplier的速率自动推进时间。你可以通过绑定一个按钮的点击事件来切换shouldAnimate的值从而实现播放/暂停。3.2 创建动态移动的实体核心在于创建一个Entity并将其position属性绑定到我们之前创建的SampledPositionProperty上。这样Cesium就会在每一帧根据当前时钟时间自动计算实体应该所在的位置。function createMovingEntity(positionProperty, startTime, endTime) { const viewer window.viewer // 可选为移动实体设置一个可见时间区间 const availability new Cesium.TimeIntervalCollection([ new Cesium.TimeInterval({ start: startTime, stop: endTime }) ]) const movingEntity viewer.entities.add({ availability: availability, position: positionProperty, // 绑定动态位置属性 orientation: new Cesium.VelocityOrientationProperty(positionProperty), // 让模型头朝前进方向 model: { uri: /assets/models/vehicle.gltf, // 你的3D模型路径 scale: 2.0, minimumPixelSize: 64 // 确保模型缩小到一定程度后依然可见 }, // 或者使用2D图标性能更好 // billboard: { // image: /assets/icons/car.png, // scale: 0.5, // verticalOrigin: Cesium.VerticalOrigin.BOTTOM // 让图标底部对准地面 // }, path: { resolution: 1, // 路径显示的分辨率数字越小越平滑 material: new Cesium.PolylineGlowMaterialProperty({ glowPower: 0.2, color: Cesium.Color.LIME }), width: 6, leadTime: 0, // 路径显示在实体之前的时间长度秒 trailTime: 60 // 关键路径在实体之后保留的时间长度秒形成“尾迹”效果 } }) return movingEntity }trailTime属性是实现“走过路径高亮”的魔法钥匙。它指定了路径在实体经过后还会存在多久。设为60秒就意味着模型后面会拖着一条长达60秒“时间长度”的轨迹尾迹视觉效果上就是已经过的路径被高亮显示。3.3 处理模型朝向与贴地你可能发现模型虽然移动了但车头方向不对或者浮在空中。这就需要调整两个地方朝向Orientation上面代码中的VelocityOrientationProperty会自动计算实体运动的速度方向并让模型的Z轴指向该方向。对于汽车、飞机这类有前后之分的模型非常有用。如果你的模型本身方向不对可能还需要在model属性里设置nodeTransformations或直接旋转模型文件。贴地Height Reference为了让模型紧贴地面行驶尤其是地形起伏时需要在positionProperty上做文章。单纯使用fromDegrees给出的高程可能不够。一种更精确的做法是在添加采样点后使用Cesium.sampleTerrain函数查询每个点对应的真实地形高度然后用这个高度去创建位置。// 示例获取地形采样后的精确高度 async function enhancePositionWithTerrain(viewer, positionProperty) { const positions [] const times [] // ... 假设这里从positionProperty中提取出了原始的Cartesian3数组和时间数组 ... // 查询地形高度 const updatedPositions await Cesium.sampleTerrain(viewer.terrainProvider, 11, positions) // 创建新的SampledPositionProperty const newProperty new Cesium.SampledPositionProperty() for (let i 0; i times.length; i) { newProperty.addSample(times[i], updatedPositions[i]) } return newProperty }4. 交互控制与性能优化实战功能实现了但要做得体验好还得加上方便的交互和针对性的优化。4.1 添加快捷控制面板与其让用户去点Cesium默认的、功能复杂的时间轴不如我们自己做一个简单的控制面板!-- 在Cesium容器旁边添加 -- div classcontrol-panel button onclickclock.multiplier 0.50.5x/button button onclickclock.multiplier 11x/button button onclickclock.multiplier 22x/button button onclickclock.shouldAnimate !clock.shouldAnimate {{ clock.shouldAnimate ? 暂停 : 播放 }} /button button onclickjumpToStart()跳到开始/button input typerange min0 max100 oninputseekTrack(this.value) /div对应的JavaScript控制函数function jumpToStart() { const viewer window.viewer viewer.clock.currentTime viewer.clock.startTime.clone() } function seekTrack(percentage) { const viewer window.viewer const start Cesium.JulianDate.toDate(viewer.clock.startTime).getTime() const end Cesium.JulianDate.toDate(viewer.clock.stopTime).getTime() const targetTime start (end - start) * (percentage / 100) viewer.clock.currentTime Cesium.JulianDate.fromDate(new Date(targetTime)) }4.2 性能监控与常见问题排查当轨迹点很多或模型复杂时可能会遇到帧率下降。打开Cesium自带的性能面板可以快速定位viewer.scene.debugShowFramesPerSecond true几个常见的性能瓶颈和解决方案瓶颈1过多的Entity。每条轨迹线、每个模型都是一个Entity。对于大量静态轨迹线考虑使用PrimitiveAPI 或Cesium3DTileset合并渲染。瓶颈2高精度地形。如果轨迹范围不大可以关闭地形或使用低精度地形viewer.terrainProvider Cesium.createWorldTerrain({requestWaterMask: false, requestVertexNormals: false})瓶颈3复杂的模型。GLTF模型面数过高。使用工具如glTF-Pipeline进行压缩或使用简单的Billboard替代。4.3 处理轨迹数据中断与跳跃真实数据常有GPS信号丢失导致相邻两点时间间隔很长或距离很远。直接播放会让模型“瞬移”很突兀。我的处理方法是数据清洗在转换数据时过滤掉时间间隔超过阈值如10分钟或距离过大的点。插值补偿利用SampledPositionProperty的插值选项让移动更平滑。positionProperty.setInterpolationOptions({ interpolationDegree: 3, // 插值多项式次数 interpolationAlgorithm: Cesium.HermitePolynomialApproximation // 使用埃尔米特插值考虑速度更平滑 })5. 完整代码整合与扩展思路最后我们把所有碎片整合成一个完整的、可运行的函数。你可以把它复制到一个HTML文件里引入Cesium CDN后直接运行看看效果。!DOCTYPE html html langzh-CN head meta charsetUTF-8 titleCesium动态轨迹回放/title script srchttps://cesium.com/downloads/cesiumjs/releases/1.107/Build/Cesium/Cesium.js/script link hrefhttps://cesium.com/downloads/cesiumjs/releases/1.107/Build/Cesium/Widgets/widgets.css relstylesheet style #cesiumContainer { width: 100%; height: 100vh; } .control-panel { position: absolute; top: 10px; left: 10px; z-index: 1000; background: white; padding: 10px; border-radius: 5px; } /style /head body div idcesiumContainer/div div classcontrol-panel button idplayPause播放/暂停/button button idreset重置/button 速度: input typerange idspeed min1 max10 value2 /div script Cesium.Ion.defaultAccessToken 你的Ion Token; // 请替换为你的Token const viewer new Cesium.Viewer(cesiumContainer, { terrainProvider: Cesium.createWorldTerrain(), animation: false, timeline: false, baseLayerPicker: false }); // 1. 模拟轨迹数据 const mockTrackData []; const baseTime new Date(2023-10-27T08:00:00Z).getTime(); let lng 116.397, lat 39.907; for (let i 0; i 100; i) { mockTrackData.push({ lng: lng i * 0.001, lat: lat i * 0.0005, alt: 50 Math.random() * 10, timestamp: new Date(baseTime i * 1000).toISOString() // 每秒一个点 }); } // 2. 转换数据 function createTrackProperty(data) { const property new Cesium.SampledPositionProperty(); data.forEach(pt { const time Cesium.JulianDate.fromIso8601(pt.timestamp); const position Cesium.Cartesian3.fromDegrees(pt.lng, pt.lat, pt.alt); property.addSample(time, position); }); property.setInterpolationOptions({ interpolationDegree: 3, interpolationAlgorithm: Cesium.HermitePolynomialApproximation }); return property; } // 3. 绘制静态轨迹线 const positions mockTrackData.map(pt Cesium.Cartesian3.fromDegrees(pt.lng, pt.lat, pt.alt)); viewer.entities.add({ polyline: { positions: positions, width: 3, material: Cesium.Color.GRAY.withAlpha(0.5), clampToGround: true } }); // 4. 创建动态实体 const positionProperty createTrackProperty(mockTrackData); const startTime Cesium.JulianDate.fromIso8601(mockTrackData[0].timestamp); const stopTime Cesium.JulianDate.fromIso8601(mockTrackData[mockTrackData.length - 1].timestamp); const movingEntity viewer.entities.add({ position: positionProperty, orientation: new Cesium.VelocityOrientationProperty(positionProperty), model: { uri: Cesium.buildModuleUrl(Assets/Models/CesiumMilkTruck/CesiumMilkTruck.gltf), // Cesium自带的卡车模型 scale: 5.0 }, path: { resolution: 1, material: new Cesium.PolylineGlowMaterialProperty({ glowPower: 0.2, color: Cesium.Color.LIME }), width: 6, trailTime: 30 // 尾迹30秒 } }); // 5. 设置时钟 viewer.clock.startTime startTime.clone(); viewer.clock.stopTime stopTime.clone(); viewer.clock.currentTime startTime.clone(); viewer.clock.multiplier 2; viewer.clock.shouldAnimate true; viewer.timeline.zoomTo(startTime, stopTime); viewer.timeline.show true; viewer.animation.show true; // 6. 交互控制 document.getElementById(playPause).onclick () { viewer.clock.shouldAnimate !viewer.clock.shouldAnimate; }; document.getElementById(reset).onclick () { viewer.clock.currentTime startTime.clone(); }; document.getElementById(speed).oninput (e) { viewer.clock.multiplier parseInt(e.target.value); }; // 7. 视角飞到轨迹范围 viewer.zoomTo(movingEntity); /script /body /html把这个文件里的你的Ion Token替换成你在Cesium Ion官网注册的免费Token就能直接运行了。卡车模型会沿着灰色的轨迹线移动并拖出一条绿色的尾迹。扩展思路这个基础框架之上你可以玩出很多花样。比如用不同颜色表示速度红色代表快绿色代表慢在轨迹点上打标记点击可以弹出该时刻的详细信息如车速、状态甚至接入WebSocket实现真正的实时轨迹推送。核心逻辑你已经掌握了剩下的就是结合具体业务需求去调用Cesium丰富的API。