mcpField
C
cesiumjs-time-properties

cesiumjs-time-properties

github.com/cesiumgs開発193213 回インストール6 週間前に更新
リポジトリ

cesiumjs-time-properties について

時間システム、時間変化するプロパティ、補間、アニメーション、マテリアル、CZMLを扱うCesiumJS向けスキルです。シミュレーションや時系列可視化を開発する人を対象としています。

主な機能

  1. 01JulianDateとクロック制御
  2. 02時間区間とプロパティ階層
  3. 03サンプル値とオンデマンド計算
  4. 04位置の向きとスプライン
  5. 05時間変化するマテリアルとパス
  6. 06CZMLの時系列データ

活用シーン

  1. 01サンプル位置に沿ったエンティティのアニメーション
  2. 02再生範囲と速度の制御
  3. 03時間フェーズ別のパス区間スタイル設定
  4. 04CZMLから時間依存データを読み込み

インストール

コマンドをコピーしてターミナルで実行します。

$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 階層、補間アルゴリズム、スプライン、マテリアルプロパティを取り上げます。SampledProperty と CallbackProperty は Clock/JulianDate がなければ意味をなさないため、プロパティは Entities ではなくここで扱います。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 を自動的に作成します。再生速度と範囲を制御するには、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 } });
});

手動でのクロック進行 -- レンダリングループの外でクロックを 1 フレーム分進めるには、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ティックごとに 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 -- 必要に応じた計算

関数をフレームごとに評価します。値が変化する場合、2 番目の引数(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 に上限を設定してください。長い時間範囲に小さなステップを設定すると、多数のポリラインが生成されます。

  • 正の小数の resolution は有効です。PORTIONS モードでは、0 以下の resolution は安全に 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)
QuaternionSplineSLERP による回転補間(C1)
ConstantSplineすべての時刻で単一の値
SteppedSpline次の制御点まで値を保持
MorphWeightSplineglTF のモーフターゲットウェイト(C1)

CZML 時系列データ

CZML は時間変化するデータをストリーミングします。document パケットでクロックを設定し、エンティティパケットでは epoch とオフセット配列を使って位置をコンパクトに表現します。位置の形式:[秒数(epoch からの相対値)、経度、緯度、高度、...]。

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 のデフォルトカメラは空間に配置されており、エンティティに自動でズームしません。スクリーンショットを撮る場合におすすめする順に、次の 3 つの方法があります。

  1. viewer.zoomTo(entityOrDataSource) -- タイルセットやデータソースの準備が完了すると Promise を返す、最適なフレーミングです。CZML データソースや、一度だけ実行するローカルセットアップに使用します。動くエンティティが 1 つの場合、現在のサンプル位置にあるエンティティのバウンディングスフィアをフレーミングするため、地表にかなり接近した表示になることがよくあります。長いルート全体を視覚化する場合は、オプション 3 をおすすめします。
  2. viewer.trackedEntity = entity -- カメラをエンティティに固定し、時間とともに追跡します。エンティティを毎フレーム中央に表示したい場合や、経路が広範囲にわたる場合(長距離フライトや軌道)に最適です。
  3. 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
地域/大陸規模(国内横断フライト)両端の地点と約 5° の余白を含む Rectangle.fromDegrees(...) を指定した setView
軌道(LEO 衛星、約 90 分の軌道)可視半球の周囲を弧全体がカーブするように、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),
});

パフォーマンスに関するヒント

  1. 位置にはCallbackPropertyよりもSampledPositionPropertyを優先してください。フレームごとのコールバックより、二分探索のほうが高速です。
  2. interpolationDegreeは5以下にしてください。値を大きくすると、データが疎な場合にルンゲ現象が発生するリスクがあります。
  3. ループ内ではJulianDateの結果パラメーターを再利用し、GCへの負荷を避けてください。
  4. 現在の時間範囲外のエンティティを除外するには、エンティティのavailabilityを設定してください。
  5. CallbackPropertyでは、割り当てを避けるためにresultオブジェクトを返してください。
  6. 大量の時系列データは、バッチでのサンプル挿入に最適化されたCzmlDataSourceを使って読み込んでください。
  7. 末尾に重複したサンプルを追加する代わりに、ExtrapolationType.HOLDを使ってください。
  8. 再生を決定論的に行うにはClockStep.TICK_DEPENDENTを使ってください。SYSTEM_CLOCK_MULTIPLIERはフレームレートによって変化します。
  9. CallbackPropertyの数を最小限にしてください。それぞれの関数は毎フレーム実行されます。
  10. 区間ベースのPORTIONSパスマテリアルを優先してください。サンプルベースのマテリアルは、遷移がより滑らかになる代わりに、resolutionのステップごとにおよそ1セグメントとなります。
  11. 評価用のスクリーンショットでは、見た目の確実性が重要です。技術的には正しいパスでも、クリップされたり、画面内に収まっていなかったり、対象が見えなかったりする場合は、マーカーを少し大きくし、ルート全体を明示的にカメラに収めるほうが適切です。

時間変化するシーンのスクリーンショット確認項目

時間変化するシーンのスクリーンショットを撮影する前に、次の項目を確認してください。

  1. クロックが区間の中間に設定されている -- viewer.clock.currentTimeをstartTimeから離れた時刻に設定し、パスに表示可能なトレイルサンプルができるようにしてから、viewer.clock.tick()を呼び出します。
  2. エンティティがカメラ内に収まっている -- await viewer.zoomTo(entity)を呼び出すか、viewer.camera.setView({ destination: Rectangle.fromDegrees(...) })を使います。デフォルトの宇宙ビューカメラに頼らないでください。パスの規模に合わせてフレーミングを調整します(上記のフレーミング表を参照)。都市内の飛行ではローカルズーム、国をまたぐ飛行ではRectangle、軌道では高高度に設定します。
  3. エンティティのpathグラフィックにleadTimeとtrailTimeが設定されている -- これにより、現在時刻の周囲に弧が実際に描画されます。軌道の場合は、曲線が地球を一周する様子を目視できるよう、少なくとも半周分をカバーする値を設定します。
  4. currentTimeに移動する対象が表示されている -- 同じエンティティにpoint、billboard、または読み込み済みのmodelを追加します。地球規模または軌道のビューでは、マーカーを見える大きさにし、適切な場合はdisableDepthTestDistance: Number.POSITIVE_INFINITYを使います。
  5. エンティティがavailabilityの範囲内にある -- クロックのcurrentTimeが、設定したTimeIntervalCollection内にある必要があります。範囲外の場合、エンティティは除外されます。
  6. 国をまたぐルートには地域全体が見えるフレーミングを使う -- JFKからLAXへの飛行などでは、空港付近でクリップされた線のクローズアップではなく、Rectangle.fromDegrees(...)を使ってルート全体が分かるように表示します。
  7. CZMLの対象をパスだけにしない -- CZMLデータソースの読み込み後、パケットに表示可能なpoint/billboardが含まれていることを確認するか、ReferenceProperty.fromString(ds.entities, "id#position")を使ってビューアのエンティティマーカーを追加します。
  8. シーンをレンダリング間に進める場合は、shouldAnimate: trueを設定してください。そうでない場合は、手動で進めます。
  9. 色が循環するマテリアル -- 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

コメント