Cesium的时间与时钟系统 是实现时空数据可视化、历史轨迹回放、实时数据同步 的核心基石,其设计理念是将时间表示、逻辑控制、UI交互、属性绑定四层解耦,通过模块化协作实现复杂的时间驱动场景。以下从核心概念、关键API、实战场景、开发实例、注意事项五个维度进行深度解析:
一、核心概念:四层时间系统架构
Cesium的时间系统由四个核心模块构成,各模块职责明确且协同工作:
| 模块 | 核心类 | 核心职责 | 底层原理 |
|---|---|---|---|
| 时间表示层 | Cesium.JulianDate |
高精度时间存储与转换 | 采用儒略日 (Julian Day)天文时间标准,以天为单位存储,精度可达微秒级,解决JavaScript Date(毫秒精度)的天文计算缺陷 |
| 逻辑控制层 | Cesium.Clock |
时间推进逻辑、播放模式、速度控制 | 通过clockStep控制时间步进方式,clockRange控制循环模式,multiplier控制播放速度,是整个时间系统的"大脑" |
| UI交互层 | Cesium.Timeline |
可视化时间范围、提供拖拽/缩放/播放交互 | 作为Clock的UI映射,用户操作Timeline会直接修改Clock参数,同时同步显示当前时间与时间范围 |
| 属性绑定层 | SampledProperty/TimeIntervalCollectionProperty |
让实体/图层属性随时间动态变化 | 将属性值与时间戳绑定,Clock时间变化时自动插值或切换属性值,实现时空数据的动态渲染 |
核心关系链
用户操作Timeline
修改Clock参数
触发Clock时间更新
驱动时间属性(SampledProperty)更新
场景实时渲染动态变化
二、重要API详解
1. 时间表示层:JulianDate核心API
JulianDate是Cesium时间系统的基础,所有时间操作都基于该类,核心API分为转换、计算、比较三类:
(1)时间转换API
| 方法 | 作用 | 示例 |
|---|---|---|
Cesium.JulianDate.fromDate(jsDate) |
JavaScript Date → JulianDate |
const julian = Cesium.JulianDate.fromDate(new Date('2024-01-01T00:00:00Z')) |
Cesium.JulianDate.toDate(julianDate) |
JulianDate → JavaScript Date |
const jsDate = Cesium.JulianDate.toDate(julian) |
Cesium.JulianDate.fromIso8601(isoString) |
ISO8601字符串 → JulianDate |
const julian = Cesium.JulianDate.fromIso8601('2024-01-01T00:00:00Z') |
Cesium.JulianDate.toIso8601(julianDate) |
JulianDate → ISO8601字符串 |
const isoStr = Cesium.JulianDate.toIso8601(julian) |
(2)时间计算API
| 方法 | 作用 | 示例 |
|---|---|---|
Cesium.JulianDate.addHours(julian, hours, result) |
增加指定小时数 | const nextHour = Cesium.JulianDate.addHours(julian, 1, new Cesium.JulianDate()) |
Cesium.JulianDate.addDays(julian, days, result) |
增加指定天数 | const nextDay = Cesium.JulianDate.addDays(julian, 1, new Cesium.JulianDate()) |
Cesium.JulianDate.secondsDifference(end, start) |
计算两个时间的秒差 | const diff = Cesium.JulianDate.secondsDifference(endJulian, startJulian) |
Cesium.JulianDate.fromMillisecondsSinceEpoch(ms) |
时间戳(毫秒)→ JulianDate |
const julian = Cesium.JulianDate.fromMillisecondsSinceEpoch(Date.now()) |
(3)时间比较API
| 方法 | 作用 | 示例 |
|---|---|---|
Cesium.JulianDate.greaterThan(a, b) |
判断a是否晚于b | if (Cesium.JulianDate.greaterThan(current, stopTime)) { ... } |
Cesium.JulianDate.lessThan(a, b) |
判断a是否早于b | if (Cesium.JulianDate.lessThan(current, startTime)) { ... } |
Cesium.JulianDate.equals(a, b) |
判断两个时间是否相等 | if (Cesium.JulianDate.equals(current, targetTime)) { ... } |
2. 逻辑控制层:Clock核心API
Clock是时间系统的控制中枢,核心属性与方法决定了时间的推进规则:
(1)核心属性
| 属性 | 类型 | 作用 | 可选值/示例 |
|---|---|---|---|
startTime |
JulianDate |
时间范围起始点 | Cesium.JulianDate.fromDate(new Date('2024-01-01T00:00:00Z')) |
stopTime |
JulianDate |
时间范围结束点 | Cesium.JulianDate.fromDate(new Date('2024-01-01T23:59:59Z')) |
currentTime |
JulianDate |
当前时间 | 可手动设置或由Clock自动更新 |
shouldAnimate |
Boolean |
是否自动播放 | true(播放)/ false(暂停) |
clockRange |
ClockRange |
播放到边界后的行为 | * Cesium.ClockRange.LOOP_STOP:循环到结束点后重新开始 * Cesium.ClockRange.LOOP_START:循环到开始点后重新结束 * Cesium.ClockRange.CLAMPED:到边界后停止 |
clockStep |
ClockStep |
时间步进方式 | * Cesium.ClockStep.SYSTEM_CLOCK:与系统实时时间同步 * Cesium.ClockStep.SYSTEM_CLOCK_MULTIPLIER:倍速播放(基于系统时间) * Cesium.ClockStep.TICK_DEPENDENT:固定步长推进(每帧推进指定时间) |
multiplier |
Number |
播放速度倍数 | 1(正常速度)、60(60倍速)、0.1(0.1倍速慢放) |
tickRate |
Number |
固定步长(仅TICK_DEPENDENT模式生效) |
3600(每帧推进1小时) |
(2)核心方法
| 方法 | 作用 | 示例 |
|---|---|---|
clock.advanceClock(seconds) |
手动推进指定秒数 | viewer.clock.advanceClock(3600)(推进1小时) |
clock.togglePause() |
切换暂停/播放状态 | viewer.clock.togglePause() |
clock.onTick.addEventListener(callback) |
监听每帧时间更新 | viewer.clock.onTick.addEventListener(clock => { ... }) |
3. UI交互层:Timeline核心API
Timeline是用户与时间系统交互的入口,核心API用于控制UI显示与监听交互:
| 方法 | 作用 | 示例 |
|---|---|---|
timeline.zoomTo(startTime, stopTime) |
缩放时间轴到指定范围 | viewer.timeline.zoomTo(viewer.clock.startTime, viewer.clock.stopTime) |
timeline.setCurrentTime(time) |
设置时间轴当前时间 | viewer.timeline.setCurrentTime(Cesium.JulianDate.now()) |
timeline.addEventListener(eventName, callback) |
监听时间轴交互事件 | * timeChanged:当前时间变化时触发 * rangeChanged:时间范围变化时触发 |
timeline.zoomToClockRange() |
缩放到Clock的startTime-stopTime范围 |
viewer.timeline.zoomToClockRange() |
4. 属性绑定层:时间属性核心API
时间属性是实现"属性随时间变化"的关键,分为连续变化 与离散变化两类:
(1)连续变化:SampledProperty
适用于轨迹、速度、高度等需要平滑过渡的场景,核心API:
javascript
// 1. 创建采样属性(指定属性类型:Cartesian3表示位置)
const positionProperty = new Cesium.SampledProperty(Cesium.Cartesian3);
// 2. 添加时间-值采样点
const time1 = Cesium.JulianDate.fromDate(new Date('2024-01-01T00:00:00Z'));
const position1 = Cesium.Cartesian3.fromDegrees(110, 34, 1000);
positionProperty.addSample(time1, position1);
const time2 = Cesium.JulianDate.fromDate(new Date('2024-01-01T01:00:00Z'));
const position2 = Cesium.Cartesian3.fromDegrees(110.1, 34.1, 1000);
positionProperty.addSample(time2, position2);
// 3. 可选:设置插值方式(默认线性插值)
positionProperty.interpolationType = Cesium.InterpolationType.LAGRANGE; // 拉格朗日插值更平滑
positionProperty.numberOfInterpolationPoints = 5; // 插值点数
(2)离散变化:TimeIntervalCollectionProperty
适用于颜色、材质、显隐状态等需要跳变的场景,核心API:
javascript
// 1. 创建时间区间属性(指定属性类型:Color表示颜色)
const colorProperty = new Cesium.TimeIntervalCollectionProperty();
// 2. 添加时间区间-值对
colorProperty.intervals.addInterval(new Cesium.TimeInterval({
start: Cesium.JulianDate.fromDate(new Date('2024-01-01T00:00:00Z')),
stop: Cesium.JulianDate.fromDate(new Date('2024-01-01T12:00:00Z')),
data: Cesium.Color.RED, // 该时间段内的颜色
isExclusive: true // 区间互斥,避免重叠冲突
}));
colorProperty.intervals.addInterval(new Cesium.TimeInterval({
start: Cesium.JulianDate.fromDate(new Date('2024-01-01T12:00:00Z')),
stop: Cesium.JulianDate.fromDate(new Date('2024-01-01T23:59:59Z')),
data: Cesium.Color.BLUE
}));
三、实际使用场景与开发实例
场景1:历史轨迹回放(连续时间属性)
需求:模拟无人机从A点到B点的24小时飞行轨迹,支持倍速回放与暂停。
javascript
const viewer = new Cesium.Viewer('cesiumContainer', {
animation: true,
timeline: true
});
// 1. 创建位置采样属性
const positionProperty = new Cesium.SampledProperty(Cesium.Cartesian3);
const startDate = new Date('2024-01-01T00:00:00Z');
// 2. 生成24个时间点的位置数据(每小时移动0.1经度/纬度)
for (let i = 0; i <= 24; i++) {
const time = Cesium.JulianDate.addHours(
Cesium.JulianDate.fromDate(startDate),
i,
new Cesium.JulianDate()
);
const position = Cesium.Cartesian3.fromDegrees(110 + i*0.1, 34 + i*0.1, 1000 + i*100);
positionProperty.addSample(time, position);
}
// 3. 创建无人机实体
viewer.entities.add({
// 明确设置 availability,确保实体在时间范围内可见
availability: new Cesium.TimeIntervalCollection([
new Cesium.TimeInterval({ start: startJulian, stop: stopJulian })
]),
position: positionProperty,
point: {
pixelSize: 15,
color: Cesium.Color.RED,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 2
},
// 绘制飞行轨迹线
path: {
resolution: 1,
material: new Cesium.PolylineGlowMaterialProperty({
glowPower: 0.2,
color: Cesium.Color.BLUE
}),
width: 8,
leadTime: 3600, // 显示未来1小时的轨迹
trailTime: 7200 // 显示过去2小时的轨迹
}
});
// 4. 配置Clock与Timeline
viewer.clock.startTime = Cesium.JulianDate.fromDate(startDate);
viewer.clock.stopTime = Cesium.JulianDate.addHours(Cesium.JulianDate.fromDate(startDate), 24, new Cesium.JulianDate());
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP; // 循环回放
viewer.clock.clockStep = Cesium.ClockStep.SYSTEM_CLOCK_MULTIPLIER; // 倍速播放
viewer.clock.multiplier = 3600; // 1小时/秒的速度(24小时轨迹仅需24秒播放完成)
viewer.clock.shouldAnimate = true; // 自动开始播放
// 5. 时间轴缩放到轨迹时间范围
viewer.timeline.zoomTo(viewer.clock.startTime, viewer.clock.stopTime);
场景2:实时数据同步(与系统时间同步)
需求:实时显示当前UTC时间的卫星位置,每秒钟更新一次。
javascript
const viewer = new Cesium.Viewer('cesiumContainer');
// 1. 创建卫星实体
const satellite = viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(110, 34, 10000000),
point: {
pixelSize: 20,
color: Cesium.Color.YELLOW
}
});
// 2. 配置Clock与系统时间同步
viewer.clock.clockStep = Cesium.ClockStep.SYSTEM_CLOCK; // 与系统实时时间同步
viewer.clock.multiplier = 1; // 正常速度
// 3. 监听每帧时间更新,实时获取卫星位置
viewer.clock.onTick.addEventListener(clock => {
const currentTime = Cesium.JulianDate.toDate(clock.currentTime);
// 模拟从API获取当前时间的卫星位置(实际开发中替换为真实API请求)
fetch(`/api/satellite-position?time=${currentTime.toISOString()}`)
.then(res => res.json())
.then(data => {
satellite.position = Cesium.Cartesian3.fromDegrees(data.lng, data.lat, data.alt);
});
});
场景3:时间触发事件(特定时间点弹窗)
需求:当时间到达2024-01-01T12:00:00时,弹出提示框。
javascript
const viewer = new Cesium.Viewer('cesiumContainer', {
animation: true,
timeline: true
});
// 1. 配置时间范围
const startTime = Cesium.JulianDate.fromDate(new Date('2024-01-01T00:00:00Z'));
const stopTime = Cesium.JulianDate.fromDate(new Date('2024-01-01T23:59:59Z'));
const targetTime = Cesium.JulianDate.fromDate(new Date('2024-01-01T12:00:00Z'));
viewer.clock.startTime = startTime;
viewer.clock.stopTime = stopTime;
viewer.clock.shouldAnimate = true;
viewer.timeline.zoomTo(startTime, stopTime);
// 2. 监听时间更新,判断是否到达目标时间
let hasTriggered = false;
viewer.clock.onTick.addEventListener(clock => {
const currentTime = clock.currentTime;
// 判断当前时间是否到达目标时间(允许1秒误差)
if (Cesium.JulianDate.secondsDifference(currentTime, targetTime) >= 0 &&
Cesium.JulianDate.secondsDifference(currentTime, targetTime) < 1 &&
!hasTriggered) {
alert('时间到达2024-01-01T12:00:00,触发事件!');
hasTriggered = true; // 避免重复触发
}
});
场景4:多数据源独立时间控制
需求:加载两个不同时间范围的数据源,支持独立控制每个图层的时间范围。
javascript
const viewer = new Cesium.Viewer('cesiumContainer', {
animation: true,
timeline: true
});
// 1. 创建第一个数据源(时间范围:2024-01-01)
const dataSource1 = new Cesium.CustomDataSource('2024-01-01图层');
dataSource1.clock = new Cesium.Clock({
startTime: Cesium.JulianDate.fromDate(new Date('2024-01-01T00:00:00Z')),
stopTime: Cesium.JulianDate.fromDate(new Date('2024-01-01T23:59:59Z'))
});
// 添加实体到dataSource1
dataSource1.entities.add({
position: Cesium.Cartesian3.fromDegrees(110, 34),
point: {
pixelSize: 10,
color: Cesium.Color.RED
}
});
// 2. 创建第二个数据源(时间范围:2024-01-02)
const dataSource2 = new Cesium.CustomDataSource('2024-01-02图层');
dataSource2.clock = new Cesium.Clock({
startTime: Cesium.JulianDate.fromDate(new Date('2024-01-02T00:00:00Z')),
stopTime: Cesium.JulianDate.fromDate(new Date('2024-01-02T23:59:59Z'))
});
// 添加实体到dataSource2
dataSource2.entities.add({
position: Cesium.Cartesian3.fromDegrees(111, 35),
point: {
pixelSize: 10,
color: Cesium.Color.BLUE
}
});
// 3. 添加到Viewer
viewer.dataSources.add(dataSource1);
viewer.dataSources.add(dataSource2);
// 4. 配置主Clock时间范围为两个数据源的并集
viewer.clock.startTime = Cesium.JulianDate.fromDate(new Date('2024-01-01T00:00:00Z'));
viewer.clock.stopTime = Cesium.JulianDate.fromDate(new Date('2024-01-02T23:59:59Z'));
viewer.timeline.zoomTo(viewer.clock.startTime, viewer.clock.stopTime);
四、开发注意事项
1. 时间格式转换陷阱
-
UTC与本地时间混淆 :Cesium所有时间默认使用UTC时间 ,JavaScript
Date默认显示本地时间,转换时需注意:javascript// 错误:new Date()是本地时间,Cesium会转成UTC,导致时间偏移 const wrongJulian = Cesium.JulianDate.fromDate(new Date('2024-01-01 12:00:00')); // 正确:使用ISO 8601 UTC格式字符串 const correctJulian = Cesium.JulianDate.fromDate(new Date('2024-01-01T12:00:00Z')); -
毫秒精度丢失 :JavaScript
Date的精度为毫秒,CesiumJulianDate精度为微秒,避免频繁转换导致精度丢失。
2. Clock参数配置错误
- startTime >= stopTime :Clock无法播放,Timeline无交互效果,需确保
startTime < stopTime。 - clockStep与multiplier不匹配 :
SYSTEM_CLOCK模式下multiplier无效,TICK_DEPENDENT模式下需设置tickRate而非multiplier。 - clockRange模式误用 :
LOOP_START模式会反向播放(从stopTime到startTime),需根据需求选择正确模式。
3. 时间属性绑定错误
- 属性类型不匹配 :
SampledProperty的泛型参数必须与实体属性类型一致(如position对应Cesium.Cartesian3,color对应Cesium.Color)。 - 时间区间重叠 :
TimeIntervalCollectionProperty的区间重叠会导致属性值闪烁,需设置isExclusive: true或确保区间首尾衔接。 - 采样点不足 :
SampledProperty的采样点过少会导致轨迹不平滑,过多会影响性能,需根据场景选择合适的采样密度。
4. 性能优化要点
- 减少采样点数量 :对于长时间范围的轨迹,可采用动态采样(如仅保留关键帧,中间用插值填充)。
- 使用CZML加载时间数据 :CZML支持二进制采样数据,加载效率远高于手动创建
SampledProperty。 - 避免在onTick事件中执行重逻辑 :
onTick每帧触发,逻辑过重会导致帧率下降,可采用节流 或批量处理。
5. 内存泄漏与清理
- 清理时间属性事件监听 :销毁实体时需移除
SampledProperty的事件监听(若手动添加过)。 - 清理Clock事件监听 :销毁Cesium实例时,需调用
viewer.clock.onTick.removeEventListener(callback)。 - 释放时间属性资源 :
SampledProperty与TimeIntervalCollectionProperty无需手动dispose(),但实体销毁时需解除其引用。
五、高级技巧
1. 自定义时间推进逻辑
通过ClockStep.TICK_DEPENDENT模式与onTick事件,实现非线性时间推进(如加速→匀速→减速):
javascript
viewer.clock.clockStep = Cesium.ClockStep.TICK_DEPENDENT;
viewer.clock.tickRate = 3600; // 初始步长1小时/帧
let speed = 1;
viewer.clock.onTick.addEventListener(clock => {
// 前10秒加速,中间匀速,后10秒减速
const elapsed = Cesium.JulianDate.secondsDifference(clock.currentTime, viewer.clock.startTime);
if (elapsed < 10) {
speed += 0.1;
} else if (elapsed > viewer.clock.stopTime - 10) {
speed -= 0.1;
}
viewer.clock.tickRate = 3600 * speed; // 动态调整步长
});
2. 时间轴自定义样式
通过CSS覆盖Cesium默认样式,实现个性化时间轴:
css
/* 调整时间轴高度 */
.cesium-timeline { height: 60px !important; }
/* 自定义时间轴背景色 */
.cesium-timeline-ticLabelContainer { background-color: #1a1a2e; }
/* 自定义当前时间指示器颜色 */
.cesium-timeline-bar { background-color: #ff2e63 !important; }
/* 自定义时间标签颜色 */
.cesium-timeline-ticLabel { color: #eaeaea !important; }
总结
Cesium的时间与时钟系统是时空数据可视化的核心,其优势在于高精度时间表示、灵活的播放控制、强大的属性绑定能力。开发中需重点掌握:
JulianDate的时间转换与计算Clock的播放模式与速度控制SampledProperty与TimeIntervalCollectionProperty的属性绑定- Timeline的交互监听与UI定制
通过合理配置Clock参数、正确使用时间属性,可实现从简单轨迹回放到复杂实时数据同步的各类时空场景,同时需注意时间格式转换、参数配置、性能优化等细节,避免常见陷阱。