当前位置: 首页 > news >正文

【Cesium 开发实战教程】第四篇:动态数据可视化:实时 GPS 轨迹与时间轴控制

一、开篇衔接

大家好!前三篇我们掌握了 Cesium 的基础环境搭建、核心对象操控和 Entity 的静态可视化(点、线、面、模型)。但实际业务中,三维场景往往需要展示 “动态变化的数据”—— 比如外卖小哥的实时位置更新、船舶的历史航行轨迹回放、台风路径的时间演进等。

这些 “随时间变化” 的数据可视化,是 Cesium 区别于静态地图的核心能力之一。本篇将深入讲解 Cesium 的时间属性系统,通过SampledPositionProperty(动态位置属性)和TimeInterval(时间区间)实现三大实战场景,并结合时间轴控件实现 “播放 / 暂停 / 调速” 等交互,让你的三维场景真正 “动起来”。所有代码基于前序项目扩展,可直接运行。

二、动态数据核心概念(先懂原理再动手)

在 Cesium 中实现动态数据可视化,需要理解两个核心概念:“时间属性” 和 “动态位置”

1. 静态数据 vs 动态数据

  • 静态数据:位置、样式不随时间变化(如固定的建筑模型、行政区域面),对应前三篇讲的普通 Entity。
  • 动态数据:位置或样式随时间变化(如移动的车辆、变化的设备状态),需要给 Entity 的属性(位置、颜色等)绑定 “时间维度”。

2. 关键类解析

Cesium 通过以下类实现动态数据的时间关联:

类名作用核心用法
SampledPositionProperty存储随时间变化的位置数据(如 GPS 轨迹点)调用addSample(time, position)添加 “时间 - 位置” 样本
TimeInterval定义属性生效的时间区间(如 “8:00-12:00 设备为绿色”)设置start(开始时间)和end(结束时间)
TimeIntervalCollection管理多个 TimeInterval(如 “8:00-12:00 绿色,12:00 后红色”)调用addInterval(interval)添加区间
Clock场景时间控制器,管理当前时间、播放速度、时间范围通过viewer.clock获取实例,控制播放 / 暂停

3. 核心逻辑

动态数据可视化的本质是:“让 Entity 的属性(位置、颜色等)随viewer.clock.currentTime自动变化”

  • currentTime改变时,Cesium 会自动计算当前时间对应的属性值(如位置、颜色)并更新渲染。
  • 时间轴控件(Timeline)和动画控件(Animation)是Clock的 UI 表现,用于手动控制currentTime

三、实战场景(从基础到进阶)

以下场景均在CesiumViewer.vue中实现,需先引入新增的 Cesium 类:

// 在script setup顶部添加
import {SampledPositionProperty,TimeInterval,TimeIntervalCollection,JulianDate, // Cesium时间类(替代JS的Date)VelocityOrientationProperty, // 根据速度自动计算方向ColorMaterialProperty, // 随时间变化的颜色材质Cartesian3,// ...其他已引入的类
} from 'cesium'

1. 场景 1:历史轨迹回放(基础)

实现 “外卖小哥 30 分钟内的配送轨迹” 回放,包含轨迹线、实时位置点和方向指示,支持时间轴控制播放。

1.1 准备轨迹数据

首先需要一组 “时间 - 位置” 数据(模拟 GPS 记录),格式如下:

// 生成模拟轨迹数据(北京→朝阳区→海淀区的配送路线)
const generateTrackData = () => {const trackData = [];// 起始时间:2023-10-01 08:00:00const startTime = JulianDate.fromDate(new Date(2023, 9, 1, 8, 0, 0));// 生成30个点(每1分钟一个点)for (let i = 0; i < 30; i++) {// 时间:每次增加1分钟const time = JulianDate.addMinutes(startTime, i, new JulianDate());// 位置:模拟从北京西站到中关村的路线(经纬度渐变)const lon = 116.3214 + i * 0.015; // 经度逐渐增加const lat = 39.9042 - i * 0.008; // 纬度逐渐减少const height = 50; // 高度50米trackData.push({time,position: Cartesian3.fromDegrees(lon, lat, height)});}return {trackData,startTime,endTime: JulianDate.addMinutes(startTime, 29, new JulianDate()) // 结束时间:8:29:00};
};
1.2 创建动态轨迹 Entity

使用SampledPositionProperty存储轨迹点,实现位置随时间自动变化:

// 创建历史轨迹回放Entity
const createHistoricalTrack = () => {const { trackData, startTime, endTime } = generateTrackData();// 1. 创建动态位置属性(存储时间-位置样本)const positionProperty = new SampledPositionProperty();// 插值方式:线性插值(使移动更平滑)positionProperty.setInterpolationOptions({interpolationDegree: 1,interpolationAlgorithm: LinearApproximation});// 添加所有轨迹点到动态位置属性trackData.forEach(item => {positionProperty.addSample(item.time, item.position);});// 2. 创建轨迹线(显示完整历史路径)const trackLineEntity = viewer.entities.add({name: "配送轨迹线",polyline: {// 轨迹线的位置是动态位置属性的所有样本点positions: positionProperty,width: 3,material: Color.BLUE,clampToGround: false}});// 3. 创建动态点(随时间移动,表示当前位置)const movingPointEntity = viewer.entities.add({name: "外卖小哥",position: positionProperty, // 绑定动态位置属性// 方向:自动根据移动方向计算(无需手动设置heading)orientation: new VelocityOrientationProperty(positionProperty),// 模型:用箭头表示方向(替代点,更直观)billboard: {image: "/images/delivery-icon.png", // 外卖图标(public目录下)width: 40,height: 40,// 图标始终朝向相机(避免侧翻)alignedAxis: new Cartesian3(0, 0, 1)}});// 4. 配置时钟和时间轴(控制播放范围和速度)viewer.clock.startTime = startTime.clone(); // 时钟开始时间viewer.clock.stopTime = endTime.clone();   // 时钟结束时间viewer.clock.currentTime = startTime.clone(); // 当前时间从开始时间起viewer.clock.clockRange = ClockRange.LOOP_STOP; // 播放完后停止在结束时间viewer.clock.multiplier = 10; // 播放速度(10倍速,30分钟轨迹3分钟播完)// 5. 配置时间轴(显示时间范围)viewer.timeline.zoomTo(startTime, endTime); // 时间轴显示完整轨迹时间范围return { trackLineEntity, movingPointEntity };
};// 在onMounted中调用
onMounted(() => {// ...之前的初始化代码createHistoricalTrack(); // 新增历史轨迹回放
});
1.3 效果说明
  • 运行后,时间轴会显示 8:00-8:30 的时间范围;
  • 点击动画控件(左下角播放按钮),外卖图标会沿轨迹线移动;
  • 可拖动时间轴滑块手动定位到任意时间点;
  • 可通过动画控件调整播放速度(默认 10 倍速)。

2. 场景 2:实时轨迹更新(进阶)

实现 “实时接收 GPS 数据并更新轨迹”(模拟真实业务中的 WebSocket 推送),包含新轨迹点的动态添加和轨迹线的实时延长。

2.1 核心逻辑
  • 初始化一个空的SampledPositionProperty
  • setInterval模拟每秒接收一次新的 GPS 数据;
  • 每次接收数据后,调用addSample添加到动态位置属性;
  • 动态更新时钟的结束时间,确保时间轴能显示最新数据。
2.2 代码实现
// 创建实时轨迹Entity
const createRealtimeTrack = () => {// 1. 初始化动态位置属性const positionProperty = new SampledPositionProperty();positionProperty.setInterpolationOptions({interpolationDegree: 1,interpolationAlgorithm: LinearApproximation});// 2. 创建轨迹线和移动点(初始为空)const trackLineEntity = viewer.entities.add({name: "实时船舶轨迹",polyline: {positions: positionProperty,width: 4,material: Color.GREEN,clampToGround: false}});const movingPointEntity = viewer.entities.add({name: "货运船舶",position: positionProperty,orientation: new VelocityOrientationProperty(positionProperty),model: {uri: "/models/ship.glb", // 船舶模型(public目录下)scale: 50,minimumPixelSize: 64}});// 3. 模拟实时接收GPS数据(每1秒一次)let currentTime = JulianDate.fromDate(new Date()); // 起始时间:当前时间let lon = 121.5074; // 初始经度(上海附近海域)let lat = 31.2304;  // 初始纬度const updateInterval = setInterval(() => {// 生成新的时间(当前时间+1秒)currentTime = JulianDate.addSeconds(currentTime, 1, new JulianDate());// 生成新的位置(模拟船舶向东南方向移动)lon += 0.001; // 经度增加(向东)lat -= 0.0005; // 纬度减少(向南)const newPosition = Cartesian3.fromDegrees(lon, lat, 0);// 添加新点到动态位置属性positionProperty.addSample(currentTime, newPosition);// 更新时钟结束时间(确保时间轴能显示到最新数据)viewer.clock.stopTime = currentTime.clone();viewer.timeline.updateFromClock();// 自动将时间轴滚动到最新时间viewer.clock.currentTime = currentTime.clone();// 模拟:30秒后停止更新const elapsedSeconds = JulianDate.secondsDifference(currentTime,viewer.clock.startTime);if (elapsedSeconds > 30) {clearInterval(updateInterval);console.log("实时数据更新已停止");}}, 1000); // 每1秒更新一次// 4. 初始化时钟viewer.clock.startTime = currentTime.clone();viewer.clock.currentTime = currentTime.clone();viewer.clock.clockRange = ClockRange.CLAMPED; // 不循环,停在最新时间viewer.clock.multiplier = 1; // 实时速度return { trackLineEntity, movingPointEntity };
};// 在onMounted中调用(注释掉之前的createHistoricalTrack,避免冲突)
onMounted(() => {// ...之前的初始化代码// createHistoricalTrack();createRealtimeTrack(); // 新增实时轨迹更新
});
2.3 关键技巧
  • 实时场景中,SampledPositionProperty会不断累积数据,若轨迹过长(如几小时),建议定期清理过期数据(保留最近 1 小时),避免性能下降;
  • 真实项目中,setInterval应替换为 WebSocket/HTTP 长轮询,接收后端推送的 GPS 数据;
  • 可通过positionProperty.getSampleCount()获取当前轨迹点数量,超过阈值时调用positionProperty.removeSamplesBefore(time)删除旧数据。

3. 场景 3:动态样式(属性随时间变化)

实现 “设备状态随时间变化”(如正常→异常→修复),通过TimeIntervalCollection控制 Entity 的颜色、大小等样式在不同时间区间的表现。

3.1 代码实现
// 创建动态样式Entity(设备状态变化)
const createDynamicStyleEntity = () => {// 1. 定义时间区间(3个状态阶段)const startTime = JulianDate.fromDate(new Date(2023, 9, 1, 9, 0, 0)); // 9:00:00const errorTime = JulianDate.fromDate(new Date(2023, 9, 1, 9, 10, 0)); // 9:10:00(异常开始)const fixTime = JulianDate.fromDate(new Date(2023, 9, 1, 9, 20, 0)); // 9:20:00(修复开始)const endTime = JulianDate.fromDate(new Date(2023, 9, 1, 9, 30, 0)); // 9:30:00(结束)// 2. 创建颜色的时间区间集合(不同时间显示不同颜色)const colorIntervals = new TimeIntervalCollection();// 区间1:9:00-9:10(正常状态,绿色)colorIntervals.addInterval(new TimeInterval({start: startTime,end: errorTime,data: Color.GREEN}));// 区间2:9:10-9:20(异常状态,红色+闪烁)colorIntervals.addInterval(new TimeInterval({start: errorTime,end: fixTime,data: Color.RED}));// 区间3:9:20-9:30(修复后,黄色)colorIntervals.addInterval(new TimeInterval({start: fixTime,end: endTime,data: Color.YELLOW}));// 3. 创建大小的时间区间集合(异常时变大)const sizeIntervals = new TimeIntervalCollection();sizeIntervals.addInterval(new TimeInterval({start: startTime,end: errorTime,data: 10 // 正常大小10px}));sizeIntervals.addInterval(new TimeInterval({start: errorTime,end: fixTime,data: 15 // 异常时15px}));sizeIntervals.addInterval(new TimeInterval({start: fixTime,end: endTime,data: 12 // 修复后12px}));// 4. 创建Entity(固定位置,样式随时间变化)const statusEntity = viewer.entities.add({name: "设备状态监控",position: Cartesian3.fromDegrees(113.2644, 23.1291, 100), // 广州位置point: {color: new ColorMaterialProperty({intervals: colorIntervals // 绑定颜色的时间区间}),pixelSize: new ConstantPropertyFromIntervals({intervals: sizeIntervals // 绑定大小的时间区间}),outlineColor: Color.WHITE,outlineWidth: 2},// 标签:显示当前状态label: {text: new ConstantPropertyFromIntervals({intervals: new TimeIntervalCollection([new TimeInterval({ start: startTime, end: errorTime, data: "正常" }),new TimeInterval({ start: errorTime, end: fixTime, data: "异常" }),new TimeInterval({ start: fixTime, end: endTime, data: "已修复" })])}),font: "16px sans-serif",pixelOffset: new Cartesian2(0, 20), // 标签在点下方20pxfillColor: Color.WHITE}});// 5. 配置时钟和时间轴viewer.clock.startTime = startTime.clone();viewer.clock.stopTime = endTime.clone();viewer.clock.currentTime = startTime.clone();viewer.clock.multiplier = 30; // 30倍速(30分钟的状态变化1分钟播完)viewer.timeline.zoomTo(startTime, endTime);return statusEntity;
};// 在onMounted中调用(注释掉其他轨迹,单独测试)
onMounted(() => {// ...之前的初始化代码// createHistoricalTrack();// createRealtimeTrack();createDynamicStyleEntity(); // 新增动态样式
});
3.2 扩展说明
  • 除了颜色和大小,几乎所有 Entity 属性(线宽、模型缩放、标签文本等)都可以通过TimeIntervalCollection绑定时间区间;
  • 异常状态的 “闪烁” 效果可结合场景 2 的动态点逻辑,在errorTime区间内用setInterval修改透明度;
  • 实际业务中,时间区间的startend可从后端接口获取(如设备报警记录的开始 / 结束时间)。

4. 场景 4:时间轴与动画控件自定义

Cesium 默认的时间轴(Timeline)和动画控件(Animation)样式较简单,实际项目中常需要自定义样式或添加控制按钮(如 “回到起点”“加速播放”)。

4.1 添加控制按钮
<!-- 在template中添加控制按钮 -->
<div class="time-controls"><button @click="resetTime">回到起点</button><button @click="speedUp">加速</button><button @click="slowDown">减速</button><button @click="togglePlay">{{ isPlaying ? '暂停' : '播放' }}</button>
</div><script setup>
// 控制按钮逻辑
const isPlaying = ref(false); // 播放状态// 回到起点
const resetTime = () => {viewer.clock.currentTime = viewer.clock.startTime.clone();
};// 加速播放(最大100倍速)
const speedUp = () => {const newSpeed = viewer.clock.multiplier * 2;viewer.clock.multiplier = Math.min(newSpeed, 100);console.log(`当前速度:${viewer.clock.multiplier}x`);
};// 减速播放(最小0.1倍速)
const slowDown = () => {const newSpeed = viewer.clock.multiplier / 2;viewer.clock.multiplier = Math.max(newSpeed, 0.1);console.log(`当前速度:${viewer.clock.multiplier}x`);
};// 播放/暂停切换
const togglePlay = () => {viewer.clock.shouldAnimate = !viewer.clock.shouldAnimate;isPlaying.value = viewer.clock.shouldAnimate;
};// 监听时钟状态变化(同步按钮文本)
viewer.clock.onTick.addEventListener(() => {isPlaying.value = viewer.clock.shouldAnimate;
});
</script><style scoped>
/* 控制按钮样式(固定在右上角) */
.time-controls {position: absolute;top: 20px;right: 20px;z-index: 100;display: flex;gap: 10px;
}.time-controls button {padding: 6px 12px;background: rgba(255, 255, 255, 0.8);border: 1px solid #ccc;border-radius: 4px;cursor: pointer;font-size: 14px;
}.time-controls button:hover {background: white;
}
</style>
4.2 隐藏 / 替换默认控件

如果默认控件不符合 UI 需求,可隐藏并自定义:

// 在初始化Viewer时隐藏默认控件
viewer = new Viewer(cesiumContainer.value, {timeline: false, // 隐藏默认时间轴animation: false, // 隐藏默认动画控件// ...其他配置
});

四、常见问题与优化方案

动态数据可视化容易遇到 “性能卡顿”“时间不同步” 等问题,以下是解决方案:

1. 问题 1:轨迹点过多导致卡顿

  • 原因SampledPositionProperty存储大量样本点(如 1 小时的 GPS 数据,每秒 1 个点即 3600 个),渲染时计算量过大。
  • 优化方案
    • 降采样:每隔 n 个点保留 1 个(如 10 秒 1 个点),减少样本数量;
    • 分块加载:只加载当前时间窗口附近的点(如当前时间 ±5 分钟),滚动时动态加载;
    • 简化轨迹线:用Cesium.PolylinePipeline.reduce简化轨迹线的顶点数量。
// 降采样示例(保留每5个点中的1个)
trackData.forEach((item, index) => {if (index % 5 === 0) { // 只添加索引为5的倍数的点positionProperty.addSample(item.time, item.position);}
});

2. 问题 2:实时更新时时间轴不跟随最新时间

  • 原因viewer.timeline未及时更新结束时间或未滚动到最新位置。
  • 解决方案:每次添加新点后,更新时钟结束时间并滚动时间轴:
// 实时更新时间轴
viewer.clock.stopTime = currentTime.clone();
viewer.timeline.updateFromClock(); // 更新时间轴范围
viewer.timeline.scrollTo(currentTime); // 滚动到最新时间

3. 问题 3:动态样式在时间区间切换时闪烁

  • 原因:时间区间的startend未无缝衔接(如前一个区间 end=9:10:00,后一个 start=9:10:01,中间有 1 秒空白)。
  • 解决方案:确保时间区间无缝衔接(end 等于下一个 start):
// 错误示例(有间隙)
interval1.end = 9:10:00;
interval2.start = 9:10:01;// 正确示例(无缝衔接)
interval1.end = 9:10:00;
interval2.start = 9:10:00; // 与上一个end相同

五、总结与下一篇预告

本篇我们掌握了 Cesium 动态数据可视化的核心能力:

  1. 理解SampledPositionPropertyTimeInterval的工作原理;
  2. 实现历史轨迹回放(时间轴控制 + 方向自动计算);
  3. 实现实时轨迹更新(模拟 WebSocket 推送 + 动态加点);
  4. 实现动态样式变化(不同时间区间显示不同状态);
  5. 自定义时间控制按钮,解决性能与同步问题。

下一篇预告:《Cesium 空间分析实战:缓冲区、可视域与高度测量》—— 三维场景不仅能展示数据,还能进行空间分析(如 “某基站的信号覆盖范围”“某区域的可视区域”)。下一篇将讲解 Cesium 的Analysis模块,实战缓冲区分析、可视域分析和距离 / 高度测量工具,让你的应用从 “展示” 升级为 “决策支持”。

如果本篇的动态轨迹实现对你的业务有帮助,欢迎点赞 + 收藏!实时更新时遇到的性能问题,可在评论区留言讨论~

  


文章转载自:

http://VBwDkKBI.jprrh.cn
http://4uvbh55p.jprrh.cn
http://fwVhzMba.jprrh.cn
http://nLsAYyDB.jprrh.cn
http://R4SWGn28.jprrh.cn
http://JZs2HWDE.jprrh.cn
http://1jkkVDrQ.jprrh.cn
http://dvIjMFjX.jprrh.cn
http://xhpUhA38.jprrh.cn
http://jPAcjSMU.jprrh.cn
http://Seyd2DEb.jprrh.cn
http://fAOAo4VV.jprrh.cn
http://iJVdmvVK.jprrh.cn
http://OlwsAUmS.jprrh.cn
http://T7wXnJgz.jprrh.cn
http://Z1YQWQSx.jprrh.cn
http://wMzitBKi.jprrh.cn
http://kdGzKyKg.jprrh.cn
http://90npiXeX.jprrh.cn
http://aMPGrJt7.jprrh.cn
http://LvbgVWvM.jprrh.cn
http://Q1FQEWK0.jprrh.cn
http://6HDX0ZID.jprrh.cn
http://6nQyiyCy.jprrh.cn
http://91Mc2Ad0.jprrh.cn
http://RgSxq4lH.jprrh.cn
http://QF8RX5O6.jprrh.cn
http://43ZNO8VF.jprrh.cn
http://r4uKMZIV.jprrh.cn
http://qVUFCGqS.jprrh.cn
http://www.dtcms.com/a/386097.html

相关文章:

  • 大数据毕业设计选题推荐-基于大数据的快手平台用户活跃度分析系统-Spark-Hadoop-Bigdata
  • HTML打包EXE工具中的WebView2内核更新指南
  • 固定资产管理软件是什么?哪家好?对比分析10款产品
  • gdb-dashboard使用
  • 【脑电分析系列】第13篇:脑电源定位:从头皮到大脑深处,EEG源定位的原理、算法与可视化
  • 【51单片机】【protues仿真】基于51单片机SHT11温湿度系统
  • 【Vue3 ✨】Vue3 入门之旅 · 第二篇:安装与配置开发环境
  • 【30】C# WinForm入门到精通 ——字体控件FontDialog 【属性、方法、事件、实例、源码】
  • 使用Nginx+uWSGI部署Django项目
  • 芯伯乐低噪声轨到轨运放芯片XAD8605/8606/8608系列,11MHz带宽高精度信号调理
  • FPGA硬件设计6 ZYNQ外围-HDMI、PCIE、SFP、SATA、FMC
  • FPGA硬件设计5 ZYNQ外围-USB、SD、EMMC、FLASH、JTAG
  • 知识图谱中:基于神经网络的知识推理解析~
  • 深度学习面试题:请介绍梯度优化的各种算法
  • python资源释放问题
  • ATR网格---ATR计算原理研究运用
  • 用Postman实现自动化接口测试
  • Hyper Rust HTTP 库入门教程
  • 软考系统架构设计师之软件架构评估法-ATAM
  • 贪心算法应用:图着色问题(顶点着色)
  • 基于51单片机的电子琴弹奏及播放系统
  • 守护每一滴水的清澈与安全
  • Python入门教程之成员运算符
  • 简易BIOS设置模拟界面设计
  • Git教程:常用命令 和 核心原理
  • Tomcat Session 管理与分布式方案
  • 声纹识别技术深度剖析:从原理到实践的全面探索
  • 第6章串数组:特殊矩阵的压缩存储
  • 多账号矩阵管理再也不复杂
  • 电商接口之电子面单API接口对接以及调用:以快递鸟为例