关于 cesiumjs-time-properties
面向 CesiumJS 开发者的技能,介绍时间系统、随时间变化的属性、插值、动画、材质和 CZML,适用于构建模拟与时序可视化。
主要功能
- 01JulianDate 与时钟控制
- 02时间区间与属性体系
- 03采样值与按需计算值
- 04位置定向与样条曲线
- 05时变材质与路径
- 06CZML 时序数据
适用场景
- 01沿采样位置为实体制作动画
- 02控制播放范围与速度
- 03按时间阶段设置路径分段样式
- 04从 CZML 加载动态时间数据
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ cesiumgs/ cesiumjs-skills --skill cesiumjs-time-properties安装后在 Claude Code 中输入/cesiumjs-time-properties确认已加载。
详细介绍
CesiumJS 时间、属性与动画
版本基线:CesiumJS v1.144
涵盖时间数据绑定层:Clock/JulianDate 时间系统、让实体属性随时间变化的 Property 层级、插值算法、样条曲线和材质属性。Property 属于此处(而非 Entities),因为 SampledProperty 和 CallbackProperty 若没有 Clock/JulianDate 就毫无意义。Material 类(Fabric)属于 cesiumjs-materials-shaders。
JulianDate -- 时间基元
将整天数和小数秒分开存储,以确保精度。内部始终使用 TAI。
import { JulianDate } from "cesium";
// Creation: fromIso8601 (most common), fromDate, now
const date = JulianDate.fromIso8601("2025-06-15T12:00:00Z");
const jd = JulianDate.fromDate(new Date("2025-06-15T12:00:00Z"));
const now = JulianDate.now();
// Conversion: toIso8601, toDate, toGregorianDate
const iso = JulianDate.toIso8601(date); // "2025-06-15T12:00:00Z"
const greg = JulianDate.toGregorianDate(date); // {year, month, day, hour, ...}
// Arithmetic -- all require a result parameter to avoid allocations
const r = new JulianDate();
JulianDate.addSeconds(date, 3600, r); // also: addMinutes, addHours, addDays
// Differences and comparisons
const stop = JulianDate.addHours(date, 24, new JulianDate());
JulianDate.secondsDifference(stop, date); // 86400
JulianDate.lessThan(date, stop); // true
JulianDate.compare(date, stop); // negative (date < stop)
Clock -- 模拟时间控制器
Viewer 会自动创建 Clock。配置它以控制播放速度和时间范围。
import { Viewer, JulianDate, ClockRange, ClockStep } from "cesium";
const viewer = new Viewer("cesiumContainer");
const start = JulianDate.fromIso8601("2025-06-15T00:00:00Z");
const stop = JulianDate.addHours(start, 24, new JulianDate());
viewer.clock.startTime = start.clone();
viewer.clock.stopTime = stop.clone();
viewer.clock.currentTime = start.clone();
viewer.clock.clockRange = ClockRange.LOOP_STOP; // loop at end
viewer.clock.multiplier = 60; // 60x real-time
viewer.clock.shouldAnimate = true;
viewer.timeline.zoomTo(start, stop);
// Per-frame callback: compute a [0,1] fraction for camera or property animation
viewer.clock.onTick.addEventListener((clock) => {
const elapsed = JulianDate.secondsDifference(clock.currentTime, clock.startTime);
const total = JulianDate.secondsDifference(clock.stopTime, clock.startTime);
const t = Math.max(0, Math.min(1, elapsed / total));
// Example: interpolate camera position linearly between two points
// const dest = Cartesian3.lerp(startPos, endPos, t, new Cartesian3());
// viewer.camera.setView({ destination: dest, orientation: { heading: 0, pitch: CesiumMath.toRadians(-30), roll: 0 } });
});
手动推进时钟 —— 调用 viewer.clock.tick(),在渲染循环之外将时钟推进一帧(适用于在截屏前设置区间中间的状态):
// Advance to midpoint before screenshot
viewer.clock.currentTime = JulianDate.addSeconds(start, 15, new JulianDate());
viewer.clock.tick(); // fires onTick listeners immediately
| ClockRange | 行为 |
|---|---|
UNBOUNDED | 向前和向后无限推进 |
CLAMPED | 在开始/停止时间处停止 |
LOOP_STOP | 从停止时间回绕到开始时间 |
| ClockStep | 行为 |
|---|---|
TICK_DEPENDENT | 每次 tick 按 multiplier 秒推进(取决于帧) |
SYSTEM_CLOCK_MULTIPLIER | 已流逝的实际时间 x multiplier(默认值) |
SYSTEM_CLOCK | 实时时间;忽略 multiplier |
TimeInterval 与 TimeIntervalCollection
import { TimeInterval, TimeIntervalCollection, JulianDate } from "cesium";
const interval = TimeInterval.fromIso8601({
iso8601: "2025-06-15T00:00:00Z/2025-06-16T00:00:00Z",
data: { phase: "daylight" }, // attach arbitrary data
});
TimeInterval.contains(interval, JulianDate.fromIso8601("2025-06-15T12:00:00Z")); // true
// Used by Entity.availability to cull entities outside the time window
const availability = new TimeIntervalCollection([
new TimeInterval({
start: JulianDate.fromIso8601("2025-06-15T00:00:00Z"),
stop: JulianDate.fromIso8601("2025-06-16T00:00:00Z"),
}),
]);
Property 系统 -- 随时间变化的值
每个实体属性都是一个 Property。CesiumJS 每帧都会调用 property.getValue(time)。
ConstantProperty
无论时间如何,都返回相同的值。CesiumJS 会自动包装原始值,因此很少需要显式使用。
import { ConstantProperty, Color } from "cesium";
const prop = new ConstantProperty(Color.RED);
prop.setValue(Color.BLUE); // fires definitionChanged
SampledProperty -- 插值时间序列
存储离散样本并进行插值。类型可以是 Number、Cartesian3、Color 或任何 Packable。
import { SampledProperty, JulianDate, LagrangePolynomialApproximation, ExtrapolationType } from "cesium";
const prop = new SampledProperty(Number);
const t0 = JulianDate.fromIso8601("2025-06-15T00:00:00Z");
prop.addSample(t0, 1.0);
prop.addSample(JulianDate.addSeconds(t0, 60, new JulianDate()), 2.5);
prop.addSample(JulianDate.addSeconds(t0, 120, new JulianDate()), 1.0);
prop.getValue(JulianDate.addSeconds(t0, 30, new JulianDate())); // ~1.75
// Default: LinearApproximation degree 1. Switch to smoother Lagrange:
prop.setInterpolationOptions({ interpolationDegree: 5, interpolationAlgorithm: LagrangePolynomialApproximation });
prop.forwardExtrapolationType = ExtrapolationType.HOLD; // hold last value outside range
SampledPositionProperty -- 插值位置
针对 Cartesian3 位置的专用属性。支持参考坐标系(默认是 ReferenceFrame.FIXED,也可以是 INERTIAL)。
import { SampledPositionProperty, JulianDate, Cartesian3, LagrangePolynomialApproximation, ExtrapolationType } from "cesium";
const position = new SampledPositionProperty();
const start = JulianDate.fromIso8601("2025-06-15T00:00:00Z");
for (let i = 0; i <= 360; i += 45) {
const rad = (i * Math.PI) / 180;
position.addSample(
JulianDate.addSeconds(start, i, new JulianDate()),
Cartesian3.fromDegrees(-112 + 0.045 * Math.cos(rad), 36 + 0.03 * Math.sin(rad), 2000 + Math.random() * 500),
);
}
position.setInterpolationOptions({ interpolationDegree: 5, interpolationAlgorithm: LagrangePolynomialApproximation });
position.forwardExtrapolationType = ExtrapolationType.HOLD;
对于截屏或评估场景,仅有采样路径往往不够。请在同一个移动实体上添加醒目的标记(point、billboard 或 model),以便在时钟的 currentTime 处识别当前样本。
import { Color } from "cesium";
const aircraft = viewer.entities.add({
position,
point: {
pixelSize: 14,
color: Color.CYAN,
outlineColor: Color.BLACK,
outlineWidth: 2,
disableDepthTestDistance: Number.POSITIVE_INFINITY,
},
path: {
width: 4,
leadTime: 180,
trailTime: 180,
material: Color.YELLOW,
},
});
| 算法 | 最适用场景 | 次数 |
|---|---|---|
LinearApproximation | 快速分段线性插值 | 1(固定) |
LagrangePolynomialApproximation | 根据稀疏样本生成平滑曲线 | 1--9 |
HermitePolynomialApproximation | 使用速度导数生成平滑曲线 | 1--9 |
CallbackProperty -- 按需计算
每帧都会计算一个函数。值会变化时,第二个参数(isConstant)必须为 false。
import { CallbackProperty, Color, JulianDate } from "cesium";
// Pulsing alpha via sine wave
const startTime = JulianDate.now();
const pulse = new CallbackProperty((time, result) => {
const s = JulianDate.secondsDifference(time, startTime);
return Color.RED.withAlpha(0.5 + 0.5 * Math.sin(s * 2), result ?? new Color());
}, false);
// Hue cycling -- full color wheel every `period` seconds using Color.fromHsl
// Color.fromHsl(hue 0-1, saturation 0-1, lightness 0-1, alpha 0-1, result?)
const period = 8; // seconds per full cycle
const hueCycle = new CallbackProperty((time, result) => {
const s = JulianDate.secondsDifference(time, viewer.clock.startTime);
const hue = (s % period) / period;
return Color.fromHsl(hue, 0.8, 0.5, 0.8, result ?? new Color());
}, false);
// Use as: polygon.material = new ColorMaterialProperty(hueCycle);
// Growing polygon -- mutate the array, property auto-updates
const pts = [/* initial Cartesian3[] */];
const dynamicPts = new CallbackProperty(() => pts, false);
CompositeProperty -- 按时间拼接属性
针对不同的时间范围委托给不同的子属性。每个区间的 data 都是一个 Property。
import { CompositeProperty, ConstantProperty, SampledProperty, TimeInterval, JulianDate } from "cesium";
const composite = new CompositeProperty();
composite.intervals.addInterval(TimeInterval.fromIso8601({
iso8601: "2025-06-15T00:00:00Z/2025-06-15T12:00:00Z", data: new ConstantProperty(1.0) }));
const sampled = new SampledProperty(Number);
sampled.addSample(JulianDate.fromIso8601("2025-06-15T12:00:00Z"), 1.0);
sampled.addSample(JulianDate.fromIso8601("2025-06-16T00:00:00Z"), 5.0);
composite.intervals.addInterval(TimeInterval.fromIso8601({
iso8601: "2025-06-15T12:00:00Z/2025-06-16T00:00:00Z", isStartIncluded: false, data: sampled }));
VelocityOrientationProperty -- 沿路径自动定向
根据位置属性的速度计算 Quaternion。对车辆和飞机至关重要。
import { VelocityOrientationProperty, SampledPositionProperty } from "cesium";
const position = new SampledPositionProperty();
// ... add samples ...
viewer.entities.add({
position, orientation: new VelocityOrientationProperty(position),
model: { uri: "aircraft.glb", minimumPixelSize: 64 },
});
为确保稳健的视觉识别,除非确定资源可以加载,否则不要只依赖外部模型 URI。如果提示词要求飞机、卫星或车辆本身可见,请同时添加 point 或 billboard 标记。
ReferenceProperty -- 跨实体绑定
通过 ID 字符串("entityId#propertyPath")将一个实体的属性链接到另一个实体。
import { ReferenceProperty } from "cesium";
viewer.entities.add({ id: "leader", position: Cartesian3.fromDegrees(-75, 40, 1000) });
viewer.entities.add({ id: "follower",
position: ReferenceProperty.fromString(viewer.entities, "leader#position"),
point: { pixelSize: 10 } });
加载 CZML 后,这种方式很有用:将 CZML 驱动的路径保留在数据源中,然后添加一个位置引用 CZML 实体的普通 Viewer 实体标记。这样无需重复样本,就能让当前对象清晰可见且易于检查。
材质属性
控制实体的表面外观。所有选项均接受原始值或 Property 实例,以实现随时间变化的行为。表面类型:ColorMaterialProperty、ImageMaterialProperty、GridMaterialProperty、StripeMaterialProperty、CheckerboardMaterialProperty。折线类型:PolylineArrowMaterialProperty、PolylineDashMaterialProperty、PolylineGlowMaterialProperty、PolylineOutlineMaterialProperty。
import { ColorMaterialProperty, SampledProperty, Color, JulianDate } from "cesium";
const solid = new ColorMaterialProperty(Color.RED);
// Time-varying color via SampledProperty
const colorProp = new SampledProperty(Color);
const t0 = JulianDate.fromIso8601("2025-06-15T00:00:00Z");
colorProp.addSample(t0, Color.BLUE);
colorProp.addSample(JulianDate.addHours(t0, 6, new JulianDate()), Color.RED);
const animated = new ColorMaterialProperty(colorProp);
分段路径材质(1.143+)
PathMode.WHOLE 保留原有行为:由当前模拟时间确定的材质为整段可见路径着色。使用
PathMode.PORTIONS,让路径过去和未来的各部分分别使用该部分对应时间的材质着色。
对于离散阶段,优先使用区间材质,因为 Cesium 只会在区间边界处分割:
import {
Color, ColorMaterialProperty, CompositeMaterialProperty,
PathMode, TimeInterval,
} from "cesium";
const phases = new CompositeMaterialProperty();
phases.intervals.addInterval(TimeInterval.fromIso8601({
iso8601: "2026-07-15T12:00:00Z/2026-07-15T12:02:00Z",
isStopIncluded: false,
data: new ColorMaterialProperty(Color.LIME),
}));
phases.intervals.addInterval(TimeInterval.fromIso8601({
iso8601: "2026-07-15T12:02:00Z/2026-07-15T12:04:00Z",
data: new ColorMaterialProperty(Color.ORANGE),
}));
viewer.entities.add({
position, // a time-dynamic PositionProperty
path: {
material: phases,
materialMode: PathMode.PORTIONS,
resolution: 30,
leadTime: 240,
trailTime: 240,
width: 6,
},
});
materialMode 是一个 Property,而不只是固定枚举。当同一路径需要在模拟过程中切换渲染策略时,请使用随时间变化的模式:
import {
PathGraphics, PathMode, TimeInterval, TimeIntervalCollectionProperty,
} from "cesium";
const mode = new TimeIntervalCollectionProperty();
mode.intervals.addInterval(TimeInterval.fromIso8601({
iso8601: "2026-07-15T12:00:00Z/2026-07-15T12:02:00Z",
isStopIncluded: false,
data: PathMode.PORTIONS,
}));
mode.intervals.addInterval(TimeInterval.fromIso8601({
iso8601: "2026-07-15T12:02:00Z/2026-07-15T12:04:00Z",
data: PathMode.WHOLE,
}));
const path = new PathGraphics({ material: phases, materialMode: mode });
viewer.entities.add({ position, path });
如需进行插值颜色过渡,请将 SampledProperty(Color) 传入 ColorMaterialProperty,并保留 materialMode: PathMode.PORTIONS。Cesium 大约每隔一个 resolution 步长创建分割点。请使用能够保留预期过渡效果的最大步长,并限制 leadTime/trailTime;长时间窗口内使用过小的步长会创建大量折线。
- 支持正的小数分辨率。在
PORTIONS模式下,非正分辨率会安全地回退为 60 秒。 - 对于恒定材质,请保留默认的
WHOLE模式;分段不会带来额外价值。 - CesiumJS 1.143 的类型定义在
PathGraphics.ConstructorOptions中包含materialMode,但实例成员中没有。对于 TypeScript,请像上面那样在构造函数/实体对象中设置;直接在运行时修改在 JavaScript 中有效,但需要添加范围较窄的本地类型扩展或类型断言。
样条曲线 -- 参数曲线插值
样条曲线使用无单位的参数时间(而非 JulianDate)实现平滑动画曲线。
import { HermiteSpline, CatmullRomSpline, Cartesian3 } from "cesium";
// Natural cubic (C2, auto-tangents)
const spline = HermiteSpline.createNaturalCubic({
times: [0, 1.5, 3, 4.5, 6],
points: [
new Cartesian3(1235398, -4810983, 4146266), new Cartesian3(1372574, -5345182, 4606657),
new Cartesian3(-757983, -5542796, 4514323), new Cartesian3(-2821260, -5248423, 4021290),
new Cartesian3(-2539788, -4724797, 3620093) ],
});
const point = spline.evaluate(2.0); // evaluate at parametric time t=2
// CatmullRom (C1, auto-tangents from control points)
const catmull = new CatmullRomSpline({ times: [0, 1, 2, 3], points: [p0, p1, p2, p3] });
| 样条曲线 | 用途 |
|---|---|
LinearSpline | 分段线性(C0),开销最低 |
HermiteSpline | 带切线的三次曲线(C1+);工厂方法:createNaturalCubic、createClampedCubic、createC1 |
CatmullRomSpline | 根据控制点自动计算切线(C1) |
QuaternionSpline | 通过 SLERP 插值旋转(C1) |
ConstantSpline | 所有时刻的值都相同 |
SteppedSpline | 保持当前值,直到下一个控制点 |
MorphWeightSpline | glTF 变形目标权重(C1) |
CZML 时间数据
CZML 用于传输随时间变化的数据。document 数据包设置时钟;实体数据包使用 epoch 和偏移数组,以紧凑地表示位置。位置格式:[secondsFromEpoch, lon, lat, alt, ...]。
import { Viewer, CzmlDataSource, Color, ReferenceProperty } from "cesium";
const czml = [
{ id: "document", version: "1.0", clock: {
interval: "2025-06-15T00:00:00Z/2025-06-15T06:00:00Z",
currentTime: "2025-06-15T00:00:00Z", multiplier: 60,
range: "LOOP_STOP", step: "SYSTEM_CLOCK_MULTIPLIER" } },
{ id: "aircraft", availability: "2025-06-15T00:00:00Z/2025-06-15T06:00:00Z",
position: { epoch: "2025-06-15T00:00:00Z",
cartographicDegrees: [0,-75,40,10000, 10800,-88,42,11000, 21600,-118,34,9000],
interpolationAlgorithm: "LAGRANGE", interpolationDegree: 5 },
point: { pixelSize: 12, color: { rgba: [0,255,255,255] },
outlineColor: { rgba: [0,0,0,255] }, outlineWidth: 2 },
path: { width: { number: 3 }, leadTime: { number: 10800 }, trailTime: { number: 10800 },
material: { solidColor: { color: { rgba: [255,255,0,255] } } } } },
];
const viewer = new Viewer("cesiumContainer", { shouldAnimate: true });
const ds = await CzmlDataSource.load(czml);
viewer.dataSources.add(ds);
// Optional robust marker: references the CZML entity position but lives in viewer.entities.
viewer.entities.add({
id: "aircraft-marker",
position: ReferenceProperty.fromString(ds.entities, "aircraft#position"),
point: {
pixelSize: 16,
color: Color.CYAN,
outlineColor: Color.BLACK,
outlineWidth: 2,
disableDepthTestDistance: Number.POSITIVE_INFINITY,
},
});
await viewer.zoomTo(ds);
CZML 可直接接受枚举名称。若要保留路径上的阶段颜色,请将
path.materialMode 设为 "PORTIONS",并将 path.material 设为带有区间标记的材质数据包数组。保留 "WHOLE" 或省略此字段,即可使用旧版的整条路径行为。
对于卫星轨道的 CZML 截图,请将轨道路径与卫星标记区分开:路径用于证明轨迹,而位于当前 CZML 位置的大型点或广告牌用于标明卫星主体。如果视图为全球视图,请在标记上使用 disableDepthTestDistance: Number.POSITIVE_INFINITY,使其在地球仪背景下仍保持可见。
EasingFunction -- 相机飞行曲线
用于 camera.flyTo 的计时常量(不用于 Property 插值)。常用值:LINEAR_NONE、CUBIC_IN_OUT、QUADRATIC_IN_OUT。完整集合还包括 QUARTIC、QUINTIC、SINUSOIDAL、EXPONENTIAL、CIRCULAR、ELASTIC、BACK、BOUNCE 变体(各自都带有 _IN、_OUT、_IN_OUT)。
import { EasingFunction, Cartesian3 } from "cesium";
viewer.camera.flyTo({
destination: Cartesian3.fromDegrees(-75, 40, 50000),
duration: 3.0,
easingFunction: EasingFunction.CUBIC_IN_OUT,
});
为时变实体设置取景
如果镜头没有对准时变实体,那么它就毫无用处。创建飞行轨迹或轨道后,务必显式设置场景取景——默认 Viewer 相机位于空间中,不会自动缩放到你的实体。截图时有以下三种方式,按推荐顺序排列:
viewer.zoomTo(entityOrDataSource)—— 最佳适配取景;在瓦片集/数据源就绪后返回一个Promise。适用于 CZML 数据源和一次性本地设置。对于单个移动实体,此方式会将实体当前采样位置的包围球置于画面中,通常会产生近地特写;若要显示长距离路线的完整弧线,建议使用选项 3。viewer.trackedEntity = entity—— 锁定相机,随实体移动。适合路径跨越大范围距离(跨国飞行、轨道),且希望实体始终位于每一帧画面中央的情况。viewer.camera.flyTo/setView配合显式的Cartesian3.fromDegrees或Rectangle.fromDegrees—— 当路径范围已知且默认缩放过宽或过紧时使用(例如,JFK->LAX 航班需要以美国大陆为取景范围,而不是被裁切的机场特写或全球视图)。
// Continental-US framing for a JFK -> LAX flight path
import { Rectangle } from "cesium";
viewer.camera.setView({
destination: Rectangle.fromDegrees(-130, 20, -60, 50), // west, south, east, north
});
根据路径范围选择取景方式:
| 路径范围 | 建议的取景方式 |
|---|---|
| 本地(城市,<50 km) | viewer.zoomTo(entity) 或使用 Cartesian3.fromDegrees(lon, lat, ~5000-50000) 的 setView |
| 区域/大陆(跨国飞行) | 使用 Rectangle.fromDegrees(...) 的 setView,覆盖两个端点并增加约 5° 的边距 |
| 轨道(LEO 卫星,约 90 min 轨道) | 使用 Cartesian3.fromDegrees(lon, lat, ~20-30 million m) 的 setView,让完整弧线沿可见半球弯曲 |
| 长距离且持续跟踪 | viewer.trackedEntity = entity |
对于应完整显示的路径弧线(前导线 + 尾迹),请充分缩小视图,确保 leadTime + trailTime 时长内的运动轨迹都能显示在视口中。如果评审者只能看到弧线的一部分,说明取景范围太紧。对于轨道,将 leadTime 和 trailTime 设置为至少覆盖半个轨道周期(例如,LEO 约为 2700 秒),让弧线明显环绕地球。
对于 JFK 至 LAX 等长距离航班,将时钟设为飞行中途,设置足够大的 leadTime 和 trailTime 以覆盖当前时间附近的路线,添加一个清晰可见的飞机标记,然后使用覆盖美国大陆的矩形。除非提示词要求近距离跟拍,否则不要对这类构图调用 zoomTo(aircraft)。
组合示例:动态飞行
结合 Clock、SampledPositionProperty、VelocityOrientationProperty 和 availability。务必在 path 上设置 leadTime 和 trailTime,以控制当前时间附近可见的尾迹范围,并在截图前显式设置实体取景。
import {
Viewer, JulianDate, ClockRange, SampledPositionProperty, VelocityOrientationProperty,
TimeIntervalCollection, TimeInterval, Cartesian3, LagrangePolynomialApproximation, Color,
} from "cesium";
const viewer = new Viewer("cesiumContainer", { shouldAnimate: true });
const start = JulianDate.fromIso8601("2025-06-15T16:00:00Z");
const stop = JulianDate.addSeconds(start, 360, new JulianDate());
viewer.clock.startTime = start.clone();
viewer.clock.stopTime = stop.clone();
viewer.clock.currentTime = start.clone();
viewer.clock.clockRange = ClockRange.LOOP_STOP;
viewer.clock.multiplier = 10;
viewer.timeline.zoomTo(start, stop);
const position = new SampledPositionProperty();
for (let i = 0; i <= 360; i += 45) {
const r = (i * Math.PI) / 180;
position.addSample(JulianDate.addSeconds(start, i, new JulianDate()),
Cartesian3.fromDegrees(-112 + 0.045 * Math.cos(r), 36 + 0.03 * Math.sin(r), 2000));
}
position.setInterpolationOptions({ interpolationDegree: 5, interpolationAlgorithm: LagrangePolynomialApproximation });
const aircraft = viewer.entities.add({
availability: new TimeIntervalCollection([new TimeInterval({ start, stop })]),
position,
orientation: new VelocityOrientationProperty(position),
model: { uri: "aircraft.glb", minimumPixelSize: 64 },
point: {
pixelSize: 14,
color: Color.CYAN,
outlineColor: Color.BLACK,
outlineWidth: 2,
disableDepthTestDistance: Number.POSITIVE_INFINITY,
},
path: {
resolution: 1,
width: 3,
leadTime: 180, // show 3 min of future path
trailTime: 180, // show 3 min of past path
material: Color.YELLOW,
},
});
// Advance to mid-interval BEFORE framing so the path arc is fully built
viewer.clock.currentTime = JulianDate.addSeconds(start, 180, new JulianDate());
viewer.clock.tick();
// Frame the entity -- without this the camera stays in space and the path is invisible
await viewer.zoomTo(aircraft);
// Or for long-range paths spanning a known region:
// viewer.camera.setView({ destination: Rectangle.fromDegrees(-130, 20, -60, 50) });
对于跨国采样飞行,使用相同的时钟/属性模式,但将本地取景替换为覆盖整条路线的矩形,并确保当前时间标记清晰可见:
import {
Viewer, JulianDate, ClockRange, SampledPositionProperty, VelocityOrientationProperty,
TimeIntervalCollection, TimeInterval, Cartesian3, LagrangePolynomialApproximation,
Color, Rectangle,
} from "cesium";
const viewer = new Viewer("cesiumContainer", { shouldAnimate: true });
const start = JulianDate.fromIso8601("2025-06-15T12:00:00Z");
const stop = JulianDate.addHours(start, 6, new JulianDate());
viewer.clock.startTime = start.clone();
viewer.clock.stopTime = stop.clone();
viewer.clock.currentTime = JulianDate.addHours(start, 3, new JulianDate());
viewer.clock.clockRange = ClockRange.LOOP_STOP;
viewer.clock.multiplier = 120;
viewer.timeline.zoomTo(start, stop);
const position = new SampledPositionProperty();
position.addSample(start, Cartesian3.fromDegrees(-73.7781, 40.6413, 10000)); // JFK
position.addSample(JulianDate.addHours(start, 1.5, new JulianDate()), Cartesian3.fromDegrees(-88.0, 41.8, 11500));
position.addSample(JulianDate.addHours(start, 3, new JulianDate()), Cartesian3.fromDegrees(-99.0, 39.0, 12000));
position.addSample(JulianDate.addHours(start, 4.5, new JulianDate()), Cartesian3.fromDegrees(-112.0, 36.5, 11000));
position.addSample(stop, Cartesian3.fromDegrees(-118.4085, 33.9416, 9000)); // LAX
position.setInterpolationOptions({
interpolationDegree: 5,
interpolationAlgorithm: LagrangePolynomialApproximation,
});
viewer.entities.add({
availability: new TimeIntervalCollection([new TimeInterval({ start, stop })]),
position,
orientation: new VelocityOrientationProperty(position),
point: {
pixelSize: 16,
color: Color.CYAN,
outlineColor: Color.BLACK,
outlineWidth: 2,
disableDepthTestDistance: Number.POSITIVE_INFINITY,
},
path: {
resolution: 60,
width: 4,
leadTime: 10800,
trailTime: 10800,
material: Color.YELLOW,
},
});
viewer.clock.tick();
viewer.camera.setView({
destination: Rectangle.fromDegrees(-130, 24, -66, 50),
});
性能提示
- 对于位置,优先使用
SampledPositionProperty而非CallbackProperty—— 二分查找比逐帧回调更快。 - 将
interpolationDegree保持在 5 或以下;稀疏数据使用更高的值可能会出现 Runge 现象。 - 在循环中复用
JulianDate结果参数,以减轻 GC 压力。 - 设置实体的
availability,以剔除当前时间范围之外的实体。 - 在
CallbackProperty中返回result对象,以避免分配内存。 - 使用
CzmlDataSource加载大量时间数据 —— 它针对批量插入采样进行了优化。 - 使用
ExtrapolationType.HOLD,而不是重复添加末尾采样。 - 使用
ClockStep.TICK_DEPENDENT以实现确定性回放;SYSTEM_CLOCK_MULTIPLIER会随帧率变化。 - 尽量减少
CallbackProperty的数量 —— 每个属性都会逐帧运行其函数。 - 优先使用基于区间的
PORTIONS路径材质;采样材质以平滑过渡为代价,每个resolution步长约对应一个线段。 - 对于评测截图,视觉上的稳健性很重要:稍大的标记和明确覆盖整条路线的相机设置,优于技术上正确但被裁切、未取景或缺少主体的路径。
时间动态场景的截图检查清单
在为时间动态场景截图之前,请确认:
- 时钟位于区间中段 —— 将
viewer.clock.currentTime设为远离startTime的时间,使路径具有可见的尾迹采样,然后调用viewer.clock.tick()。 - 相机对准实体取景 —— 调用
await viewer.zoomTo(entity)或viewer.camera.setView({ destination: Rectangle.fromDegrees(...) })。切勿依赖默认的太空视角相机。根据路径范围调整取景(参见上方的取景表):城市飞行使用局部缩放,跨国飞行使用Rectangle,轨道使用高空视角。 - 实体的
path图形已设置leadTime和trailTime,确保弧线会围绕当前时间绘制。对于轨道,使用足够大的值,至少覆盖半圈轨道,使曲线明显环绕地球。 - 移动主体在
currentTime时可见 —— 为同一实体添加point、billboard或已加载的model。对于全球或轨道视角,应将标记设得足够大,以便看清,并在适当情况下使用disableDepthTestDistance: Number.POSITIVE_INFINITY。 - 实体位于
availability范围内 —— 时钟的currentTime必须落在你设置的任何TimeIntervalCollection内,否则实体会被剔除。 - 跨国路线使用区域取景 —— JFK 到 LAX 或类似航线应使用
Rectangle.fromDegrees(...)展示路线全貌,而不是近距离拍摄某个机场附近被裁切的线段。 - CZML 主体不能只有路径 —— 加载 CZML 数据源后,确保数据包包含可见的
point/billboard,或使用ReferenceProperty.fromString(ds.entities, "id#position")添加查看器实体标记。 - 如果希望场景在渲染之间推进,请设置
shouldAnimate: true;否则请手动推进。 - 循环变色材质 —— 如果使用驱动
Color.fromHsl的CallbackProperty,循环中的任何色相都有效;不要假设截图时会呈现特定色相。必须将时钟推进到startTime之后,循环才会从初始色相开始变化。
关键枚举
ClockRange:UNBOUNDED、CLAMPED、LOOP_STOP。ClockStep:TICK_DEPENDENT、SYSTEM_CLOCK_MULTIPLIER、SYSTEM_CLOCK。ExtrapolationType:NONE、HOLD、EXTRAPOLATE。PathMode(v1.143+):WHOLE、PORTIONS。TimeStandard:UTC、TAI。ReferenceFrame:FIXED、INERTIAL。TrackingReferenceFrame(v1.124+):AUTODETECT、ECI、ECEF、INERTIAL、ENU。
另请参阅
- cesiumjs-entities —— Entity、Graphics 类型、DataSources(属性的使用方)
- cesiumjs-viewer-setup —— Viewer、ClockViewModel、Timeline 小部件
- cesiumjs-models-particles —— Model、ModelAnimation(使用时间系统进行播放)
- cesiumjs-camera ——
viewer.zoomTo、viewer.trackedEntity、camera.flyTo、Rectangle.fromDegrees,用于为时间动态场景取景