cesiumjs-spatial-math について
CesiumJSを扱う開発者向けの空間数学・変換リファレンススキルです。地理座標、地球中心の幾何、座標系変換、距離計算、投影、交差判定を扱います。
主な機能
- 01CartesianとCartographicの変換
- 02ベクトル・行列演算
- 03ローカル参照座標系の変換
- 04楕円体測地線距離
- 05境界体と交差判定
- 06地理投影ユーティリティ
活用シーン
- 01地理的位置を3D座標に変換
- 02ローカルENU座標系でモデルを配置
- 03地点間の地表距離を計算
- 04座標投影や幾何交差の判定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ cesiumgs/ cesiumjs-skills --skill cesiumjs-spatial-mathインストール後、Claude Code で/cesiumjs-spatial-mathを入力して読み込まれたか確認します。
詳細
CesiumJS の空間数学と変換
バージョンの基準:CesiumJS v1.144(2026-08-01)
CesiumJS アプリケーションのあらゆる基盤となる数学です。座標型、単位変換、楕円体の幾何、基準座標系の変換、バウンディングボリューム、交差判定、投影について説明します。
コアコンセプト
CesiumJS は右手系の地球中心・地球固定(ECEF)座標系を使用します。
- Cartesian3 -- ECEF(x、y、z)、単位はメートルです。すべての3D位置を表す内部形式です。
- Cartographic -- (経度、緯度、高さ)です。角度の単位はラジアン、高さの単位は楕円体面からのメートルです。
コア数学における角度の値はすべてラジアンです。Math.toRadians() / Math.toDegrees() を使用してください。数学型では静的メソッドに結果を渡すパターンを使用します。割り当てを再利用するには、result パラメーターを渡してください。
Cartesian3 -- 位置とベクトル
import { Cartesian3, Math as CesiumMath } from "cesium";
// From lon/lat degrees -- most common entry point
const pos = Cartesian3.fromDegrees(-105.0, 40.0);
const elevated = Cartesian3.fromDegrees(-105.0, 40.0, 1500.0); // with height
// Batch creation: [lon, lat, lon, lat, ...]
const ring = Cartesian3.fromDegreesArray([-105, 40, -100, 40, -100, 35]);
// With heights: [lon, lat, h, lon, lat, h, ...]
const wall = Cartesian3.fromDegreesArrayHeights([-105, 40, 500, -100, 40, 1000]);
// From raw ECEF or from radians
const raw = new Cartesian3(-1275096.0, -4797180.0, 4075270.0);
const fromRad = Cartesian3.fromRadians(-1.8326, 0.6981, 1500.0);
// Constants
Cartesian3.ZERO; // (0,0,0)
Cartesian3.UNIT_X; // (1,0,0)
Cartesian3.UNIT_Y; // (0,1,0)
Cartesian3.UNIT_Z; // (0,0,1)
破壊的変更(1.139、#8359):
Cartesian2、Cartesian3、Cartesian4は ES6 クラスになりました。静的なファクトリメソッドにnewを付けて呼び出すと、エラーが発生します。new Cartesian3.fromArray([...])とnew Cartesian3.fromDegrees(...)は エラーになります。ファクトリメソッドではnewを外し(Cartesian3.fromArray([...]))、実際のコンストラクター(new Cartesian3(x, y, z))にのみ付けてください。ほかのクラスも ES6 クラスへ移行しているため、このルールをすべての箇所に適用してください。
ベクトル演算
const a = new Cartesian3(1.0, 2.0, 3.0);
const b = new Cartesian3(4.0, 5.0, 6.0);
const r = new Cartesian3(); // reusable scratch
Cartesian3.add(a, b, r); // a + b
Cartesian3.subtract(a, b, r); // a - b
Cartesian3.multiplyByScalar(a, 2.0, r); // a * 2
Cartesian3.negate(a, r); // -a
Cartesian3.cross(a, b, r); // cross product
Cartesian3.normalize(a, r); // unit vector
Cartesian3.lerp(a, b, 0.5, r); // linear interpolation
Cartesian3.midpoint(a, b, r); // midpoint
const dot = Cartesian3.dot(a, b); // dot product
const len = Cartesian3.magnitude(a); // ||a||
const dist = Cartesian3.distance(a, b); // Euclidean distance
const distSq = Cartesian3.distanceSquared(a, b); // faster for comparisons
const angle = Cartesian3.angleBetween(a, b); // radians
Cartographic -- 地理座標
import { Cartographic, Cartesian3, Math as CesiumMath } from "cesium";
const carto = Cartographic.fromDegrees(-105.0, 40.0, 1500.0);
const cartoRad = Cartographic.fromRadians(-1.8326, 0.6981, 1500.0);
// Cartesian3 <-> Cartographic
const position = Cartesian3.fromDegrees(-105.0, 40.0, 1500.0);
const geo = Cartographic.fromCartesian(position);
const lonDeg = CesiumMath.toDegrees(geo.longitude); // -105.0
const latDeg = CesiumMath.toDegrees(geo.latitude); // 40.0
const backToCart = Cartographic.toCartesian(geo);
CesiumMath ユーティリティ
import { Math as CesiumMath } from "cesium";
// Degree/radian conversion
const rad = CesiumMath.toRadians(90.0); // PI/2
const deg = CesiumMath.toDegrees(Math.PI); // 180
// Constants: PI, TWO_PI, PI_OVER_TWO, PI_OVER_FOUR, RADIANS_PER_DEGREE
// EPSILON1 (0.1) through EPSILON21 (1e-21)
const clamped = CesiumMath.clamp(value, 0.0, 1.0);
const interp = CesiumMath.lerp(0.0, 100.0, 0.5); // 50
const norm = CesiumMath.negativePiToPi(angle); // [-PI, PI]
const pos = CesiumMath.zeroToTwoPi(angle); // [0, 2*PI]
const safeLon = CesiumMath.convertLongitudeRange(angle); // [-PI, PI)
const eq = CesiumMath.equalsEpsilon(a, b, CesiumMath.EPSILON7); // float compare
楕円体
import { Ellipsoid, Cartesian3, Cartographic } from "cesium";
// Built-in ellipsoids
Ellipsoid.WGS84; // Earth (default)
Ellipsoid.UNIT_SPHERE; // radius 1
Ellipsoid.MOON; // lunar sphere
Ellipsoid.MARS; // Mars (v1.133+)
// Change default (affects Ellipsoid.default everywhere)
Ellipsoid.default = Ellipsoid.MOON;
// Conversions on a specific ellipsoid
const cart = Ellipsoid.WGS84.cartographicToCartesian(
Cartographic.fromDegrees(-75.0, 40.0, 100.0),
);
const carto = Ellipsoid.WGS84.cartesianToCartographic(cart);
// Surface normal at a position
const normal = Ellipsoid.WGS84.geodeticSurfaceNormal(cart, new Cartesian3());
// Project point onto ellipsoid surface
const onSurface = Ellipsoid.WGS84.scaleToGeodeticSurface(cart, new Cartesian3());
Transforms -- 基準座標系
Transforms は、ローカル座標系と ECEF を関連付ける4x4行列を構築します。最もよく使われる関数は eastNorthUpToFixedFrame です。
東・北・上(ENU)
ENU:X = 東、Y = 北、Z = 上です。地球上にモデルを配置するための標準的な座標系です。
import { Cartesian3, Transforms, Matrix4 } from "cesium";
const origin = Cartesian3.fromDegrees(-105.0, 40.0);
const enuMatrix = Transforms.eastNorthUpToFixedFrame(origin);
// Columns: [east, north, up, origin] in ECEF
Heading-Pitch-Roll モデル行列
3Dモデルの位置と向きを指定する標準的な方法です。
import { Cartesian3, Transforms, HeadingPitchRoll, Math as CesiumMath } from "cesium";
const position = Cartesian3.fromDegrees(-105.0, 40.0, 0.0);
const hpr = new HeadingPitchRoll(
CesiumMath.toRadians(90.0), // heading: 90 deg east
0.0, // pitch: level
0.0, // roll: none
);
const modelMatrix = Transforms.headingPitchRollToFixedFrame(position, hpr);
// Just the orientation quaternion (e.g., for Entity.orientation)
const orientation = Transforms.headingPitchRollQuaternion(position, hpr);
HeadingPitchRoll
Heading = -Z 軸周りの回転(コンパス方位、時計回り)です。Pitch = -Y 軸周りです。Roll = +X 軸周りです。単位はラジアンです。
import { HeadingPitchRoll, Math as CesiumMath } from "cesium";
const hpr = new HeadingPitchRoll(CesiumMath.toRadians(45.0), CesiumMath.toRadians(-10.0), 0.0);
const hprDeg = HeadingPitchRoll.fromDegrees(45.0, -10.0, 0.0); // convenience
その他のローカル座標系
import { Transforms, Cartesian3 } from "cesium";
const origin = Cartesian3.fromDegrees(-105.0, 40.0);
Transforms.northEastDownToFixedFrame(origin); // NED (aviation)
Transforms.northUpEastToFixedFrame(origin); // NUE
// Custom frame from any combo of east|north|up|west|south|down
const customFn = Transforms.localFrameToFixedFrameGenerator("north", "west");
const matrix = customFn(origin);
// Recover heading/pitch/roll from an existing model matrix
const hpr = Transforms.fixedFrameToHeadingPitchRoll(modelMatrix);
Matrix4 -- 4x4変換
列優先の格納形式です(WebGL の規約)。コンストラクターでは、読みやすさを考慮して行優先の値を指定します。
import { Matrix4, Matrix3, Cartesian3, Quaternion } from "cesium";
// Factory methods
Matrix4.fromTranslation(new Cartesian3(10, 20, 30));
Matrix4.fromRotationTranslation(Matrix3.fromRotationZ(Math.PI / 4), new Cartesian3(100, 0, 0));
Matrix4.fromTranslationQuaternionRotationScale(
new Cartesian3(0, 0, 0), Quaternion.IDENTITY, new Cartesian3(2, 2, 2),
);
Matrix4.fromUniformScale(5.0);
// Combine, transform, invert
const combined = Matrix4.multiply(matA, matB, new Matrix4());
const worldPt = Matrix4.multiplyByPoint(enuMatrix, new Cartesian3(100, 0, 0), new Cartesian3());
const inv = Matrix4.inverseTransformation(enuMatrix, new Matrix4()); // rigid-body only
// Decompose
Matrix4.getTranslation(enuMatrix, new Cartesian3());
Matrix4.getMatrix3(enuMatrix, new Matrix3());
Matrix4.getScale(enuMatrix, new Cartesian3());
Quaternion -- 回転
import { Quaternion, Cartesian3, HeadingPitchRoll, Math as CesiumMath, Matrix3, Matrix4, Transforms } from "cesium";
Quaternion.IDENTITY; // (0, 0, 0, 1)
const q1 = Quaternion.fromAxisAngle(Cartesian3.UNIT_Z, CesiumMath.toRadians(45.0));
const q2 = Quaternion.fromHeadingPitchRoll(new HeadingPitchRoll(CesiumMath.toRadians(90), 0, 0));
const q3 = Quaternion.fromRotationMatrix(Matrix3.fromRotationZ(Math.PI / 2));
const mid = Quaternion.slerp(q1, q2, 0.5, new Quaternion()); // interpolate
const composed = Quaternion.multiply(q1, q2, new Quaternion()); // compose
Quaternion → Matrix3 → Matrix4 の合成パターン
モデルの向きを軸角で明示的に制御し、その後 ENU ローカル座標系と合成する必要がある場合は、このパターンを使用してください。
import { Cartesian3, Quaternion, Matrix3, Matrix4, Transforms, Math as CesiumMath } from "cesium";
const origin = Cartesian3.fromDegrees(-115.17, 36.11, 3000.0);
// 1. Build local-to-ECEF frame at origin
const enuFrame = Transforms.eastNorthUpToFixedFrame(origin);
// 2. Build quaternion for 45-deg yaw about local up axis
const q = Quaternion.fromAxisAngle(Cartesian3.UNIT_Z, CesiumMath.toRadians(45.0));
// 3. Convert quaternion → Matrix3 → Matrix4 (zero translation in local frame)
const rot3 = Matrix3.fromQuaternion(q, new Matrix3());
const rotMatrix4 = Matrix4.fromRotationTranslation(rot3, Cartesian3.ZERO, new Matrix4());
// 4. Compose: ENU frame * local rotation = final model matrix
const modelMatrix = Matrix4.multiply(enuFrame, rotMatrix4, new Matrix4());
これは、任意の回転を指定してモデルを地理的位置に配置するための標準的なパターンです。Transforms.headingPitchRollToFixedFrame は HPR 回転用の便利なラッパーです。軸角またはクォータニオンで制御する必要がある場合は、上記の手動合成を使用してください。
測地線距離
重要: 「2つの経度・緯度地点間の距離」は、ほとんどの場合、地球を貫く直線の弦ではなく、大円に沿った地表距離を意味します。2つの fromDegrees の結果に Cartesian3.distance を使用すると、弦の長さが得られます。これは地表距離より短く、大陸間の距離では大きな誤差になります(例:NYC↔ロンドンの弦は、約5,837 km の地表距離に対して100 km以上短くなります)。「この2地点間の距離」を示すラベルには、必ず EllipsoidGeodesic.surfaceDistance を使用してください。
import { Cartographic, EllipsoidGeodesic, Cartesian3 } from "cesium";
// Surface distance (great-circle via Vincenty) -- correct for "distance between cities"
const geodesic = new EllipsoidGeodesic(
Cartographic.fromDegrees(-73.985, 40.758), // New York
Cartographic.fromDegrees(-0.1276, 51.5074), // London
);
const surfaceDist = geodesic.surfaceDistance; // ~5,837,000 m (~5,837 km)
const midCarto = geodesic.interpolateUsingFraction(0.5); // midpoint on surface
// Chord (straight-line through ellipsoid interior) -- rarely what you want for geography
const chord = Cartesian3.distance(
Cartesian3.fromDegrees(-73.985, 40.758),
Cartesian3.fromDegrees(-0.1276, 51.5074),
); // shorter than surfaceDistance; do NOT use for "distance between cities"
距離をラベルに表示する場合は、surfaceDistance(メートル)を1000で割り、最も近い km 単位に丸めて表示してください。ごく短い距離(約1 km未満)では、弦と地表距離の差は丸め誤差の範囲内です。大陸間の距離には EllipsoidGeodesic を使用してください。
測地線のサンプリングによる Cartesian3 位置の生成
interpolateUsingFraction は Cartographic を返します。ポリラインやその他のジオメトリ API に渡す前に、各サンプルを Cartesian3 に変換してください。ポリラインが直線的な等角航路のように見えず、曲線として表示されるよう、十分な数のサンプルを使用してください(大洋を横断する弧では64個以上が目安です)。
import { Cartographic, EllipsoidGeodesic, Cartesian3 } from "cesium";
const start = Cartographic.fromDegrees(-73.985, 40.758); // NYC
const end = Cartographic.fromDegrees(-0.1276, 51.507); // London
const geodesic = new EllipsoidGeodesic(start, end);
const N = 64;
const positions = [];
for (let i = 0; i <= N; i++) {
const carto = geodesic.interpolateUsingFraction(i / N);
// Convert Cartographic (radians) to Cartesian3
positions.push(Cartesian3.fromRadians(carto.longitude, carto.latitude, carto.height));
}
// positions is now a Cartesian3[] suitable for polyline entity positions
// Label the midpoint with the great-circle distance, not the chord
const midCarto = geodesic.interpolateUsingFraction(0.5);
const midPos = Cartesian3.fromRadians(midCarto.longitude, midCarto.latitude, midCarto.height);
const km = (geodesic.surfaceDistance / 1000).toFixed(0);
viewer.entities.add({
position: midPos,
label: { text: `Distance: ${km} km` },
});
BoundingSphere
import { BoundingSphere, Cartesian3 } from "cesium";
const sphere = BoundingSphere.fromPoints(
Cartesian3.fromDegreesArray([-105, 40, -100, 40, -100, 35]),
); // sphere.center (Cartesian3), sphere.radius (number)
const inside = Cartesian3.distance(sphere.center, Cartesian3.fromDegrees(-102, 37.5)) <= sphere.radius;
sphere.center は Cartesian3(ECEF)で、エンティティの位置としてそのまま使用できます。sphere.radius はメートル単位で、可視化用の楕円体の半径として渡せます。球の内側の点や背後にある地形が見えるよう、アルファ値は低く(≈0.3)設定してください。不透明度が高すぎると、球で囲む対象のデータが見えなくなります。
import { Color, Cartesian3 } from "cesium";
viewer.entities.add({
position: sphere.center,
ellipsoid: {
radii: new Cartesian3(sphere.radius, sphere.radius, sphere.radius),
material: Color.YELLOW.withAlpha(0.3), // translucent so input points stay visible
outline: true,
outlineColor: Color.YELLOW,
},
});
Ray と交差判定
import { Ray, IntersectionTests, Plane, Cartesian3, Ellipsoid } from "cesium";
const ray = new Ray(new Cartesian3(0, 0, 6378137), new Cartesian3(0, 0, -1)); // auto-normalized
const ptOnRay = Ray.getPoint(ray, 1000.0, new Cartesian3());
// Ray-plane: returns Cartesian3 or undefined
const plane = Plane.fromPointNormal(Cartesian3.ZERO, Cartesian3.UNIT_Z);
const hit = IntersectionTests.rayPlane(ray, plane);
// Ray-ellipsoid: returns Interval {start, stop} or undefined
const camRay = new Ray(new Cartesian3(0, 0, 20000000), new Cartesian3(0, 0, -1));
const interval = IntersectionTests.rayEllipsoid(camRay, Ellipsoid.WGS84);
if (interval) {
const nearPt = Ray.getPoint(camRay, interval.start, new Cartesian3());
}
// Ray-triangle: returns parametric t or undefined
const t = IntersectionTests.rayTriangleParametric(ray, p0, p1, p2, true);
SceneTransforms -- ワールド座標からスクリーン座標へ
import { SceneTransforms, Cartesian3 } from "cesium";
// World -> pixel coordinates (Cartesian2 or undefined if off-screen)
const winPos = SceneTransforms.worldToWindowCoordinates(viewer.scene, Cartesian3.fromDegrees(-105, 40));
// High-DPI aware variant
const bufPos = SceneTransforms.worldToDrawingBufferCoordinates(viewer.scene, worldPos);
地理投影法
import { GeographicProjection, WebMercatorProjection, Cartographic, Ellipsoid } from "cesium";
const carto = Cartographic.fromDegrees(-105.0, 40.0);
// Plate Carree: project/unproject between Cartographic and Cartesian3
const geoProj = new GeographicProjection(Ellipsoid.WGS84);
const xy = geoProj.project(carto); // Cartesian3
const back = geoProj.unproject(xy); // Cartographic
// Web Mercator (EPSG:3857)
const merc = new WebMercatorProjection(Ellipsoid.WGS84);
const mercXY = merc.project(carto);
よく使われるパターン
ローカル ENU 座標で位置をオフセットする
import { Cartesian3, Transforms, Matrix4 } from "cesium";
const origin = Cartesian3.fromDegrees(-105.0, 40.0, 0.0);
const enu = Transforms.eastNorthUpToFixedFrame(origin);
// Move 500m east, 200m north, 100m up in local frame
const worldPt = Matrix4.multiplyByPoint(enu, new Cartesian3(500, 200, 100), new Cartesian3());
許容誤差を考慮して位置を比較する
import { Cartesian3, Math as CesiumMath } from "cesium";
const a = Cartesian3.fromDegrees(-105.0, 40.0);
const b = Cartesian3.fromDegrees(-105.0001, 40.0001);
Cartesian3.equalsEpsilon(a, b, CesiumMath.EPSILON7); // preferred over ===
if (Cartesian3.distance(a, b) < 10.0) { /* within 10m */ }
パフォーマンスのヒント
- スクラッチ変数を再利用します。 GC による一時停止を避けるため、ループの外で
resultオブジェクトを事前に割り当てます。 - 比較には
distanceではなくdistanceSquaredを使用します。Math.sqrtの呼び出しを避けられます。 - 手動で
Cartographicを作成してから変換するより、Cartesian3.fromDegreesを優先します。 - モデル行列をキャッシュします。 位置が固定されている場合は、
Transforms.eastNorthUpToFixedFrameを一度だけ呼び出します。 - 剛体変換には
Matrix4.inverseTransformationを使用します。inverseより高速で、安定性にも優れています。 fromDegreesをループ内で呼び出す代わりに、fromDegreesArray/fromDegreesArrayHeightsで 位置をまとめて作成します。Cartesian3.normalizeの呼び出し前にチェックします。長さがゼロのベクトルを渡すと例外が発生するため、先に大きさを確認します。- 浮動小数点数の比較には
equalsEpsilonを使用します。許容誤差の既定値として、CesiumMath.EPSILON7が適しています。 - レンダリングループの外で HPR を事前計算します。向きが変わる場合に限り、クォータニオン/行列に変換します。
- 用途に合った距離を選びます。
Cartesian3.distanceは地球を貫く弦の長さです(地理用途では、通常求める距離ではありません)。EllipsoidGeodesic.surfaceDistanceは大円に沿った地表距離です(都市間のラベルにはこちらを使用します)。
関連項目
- cesiumjs-camera -- これらの座標型を使用するカメラの位置設定とフライトアニメーション
- cesiumjs-primitives -- Transforms のモデル行列を使用する Geometry および Primitive API
- cesiumjs-terrain-environment -- 地形の高さの取得と地球表面とのインタラクション