| name | cesiumjs-spatial-math |
| description | CesiumJS spatial math - Cartesian3, Cartographic, Matrix4, Quaternion, Transforms, Ellipsoid, BoundingSphere, projections, coordinate conversions. Use when converting between coordinate systems, computing positions on the ellipsoid, performing spatial intersection tests, building model matrices, or working with geographic projections. |
CesiumJS Spatial Math & Transforms
Version baseline: CesiumJS v1.142 (2026-06-01)
Mathematical foundation for every CesiumJS application: coordinate types, unit conversions, ellipsoid geometry, reference frame transforms, bounding volumes, intersection tests, and projections.
Core Concepts
CesiumJS uses a right-handed Earth-Centered Earth-Fixed (ECEF) coordinate system:
- Cartesian3 -- ECEF (x, y, z) in meters. Internal representation for all 3D positions.
- Cartographic -- (longitude, latitude, height). Angles are radians, height in meters above ellipsoid.
All angular values in core math are radians. Use Math.toRadians() / Math.toDegrees(). Math types use a static-method-with-result pattern: pass a result parameter to reuse allocations.
Cartesian3 -- Positions and Vectors
import { Cartesian3, Math as CesiumMath } from "cesium";
const pos = Cartesian3.fromDegrees(-105.0, 40.0);
const elevated = Cartesian3.fromDegrees(-105.0, 40.0, 1500.0);
const ring = Cartesian3.fromDegreesArray([-105, 40, -100, 40, -100, 35]);
const wall = Cartesian3.fromDegreesArrayHeights([-105, 40, 500, -100, 40, 1000]);
const raw = new Cartesian3(-1275096.0, -4797180.0, 4075270.0);
const fromRad = Cartesian3.fromRadians(-1.8326, 0.6981, 1500.0);
Cartesian3.ZERO;
Cartesian3.UNIT_X;
Cartesian3.UNIT_Y;
Cartesian3.UNIT_Z;
Breaking change (1.139, #8359): Cartesian2, Cartesian3, and Cartesian4
are now ES6 classes. Calling new on a static factory method now throws --
new Cartesian3.fromArray([...]) and new Cartesian3.fromDegrees(...) are
errors. Drop new for factory methods (Cartesian3.fromArray([...])); keep it
only for the real constructor (new Cartesian3(x, y, z)). More classes are
migrating to ES6 classes, so apply this rule everywhere.
Vector Operations
const a = new Cartesian3(1.0, 2.0, 3.0);
const b = new Cartesian3(4.0, 5.0, 6.0);
const r = new Cartesian3();
Cartesian3.add(a, b, r);
Cartesian3.subtract(a, b, r);
Cartesian3.multiplyByScalar(a, 2.0, r);
Cartesian3.negate(a, r);
Cartesian3.cross(a, b, r);
Cartesian3.normalize(a, r);
Cartesian3.lerp(a, b, 0.5, r);
Cartesian3.midpoint(a, b, r);
const dot = Cartesian3.dot(a, b);
const len = Cartesian3.magnitude(a);
const dist = Cartesian3.distance(a, b);
const distSq = Cartesian3.distanceSquared(a, b);
const angle = Cartesian3.angleBetween(a, b);
Cartographic -- Geographic Coordinates
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);
const position = Cartesian3.fromDegrees(-105.0, 40.0, 1500.0);
const geo = Cartographic.fromCartesian(position);
const lonDeg = CesiumMath.toDegrees(geo.longitude);
const latDeg = CesiumMath.toDegrees(geo.latitude);
const backToCart = Cartographic.toCartesian(geo);
CesiumMath Utilities
import { Math as CesiumMath } from "cesium";
const rad = CesiumMath.toRadians(90.0);
const deg = CesiumMath.toDegrees(Math.PI);
const clamped = CesiumMath.clamp(value, 0.0, 1.0);
const interp = CesiumMath.lerp(0.0, 100.0, 0.5);
const norm = CesiumMath.negativePiToPi(angle);
const pos = CesiumMath.zeroToTwoPi(angle);
const safeLon = CesiumMath.convertLongitudeRange(angle);
const eq = CesiumMath.equalsEpsilon(a, b, CesiumMath.EPSILON7);
Ellipsoid
import { Ellipsoid, Cartesian3, Cartographic } from "cesium";
Ellipsoid.WGS84;
Ellipsoid.UNIT_SPHERE;
Ellipsoid.MOON;
Ellipsoid.MARS;
Ellipsoid.default = Ellipsoid.MOON;
const cart = Ellipsoid.WGS84.cartographicToCartesian(
Cartographic.fromDegrees(-75.0, 40.0, 100.0),
);
const carto = Ellipsoid.WGS84.cartesianToCartographic(cart);
const normal = Ellipsoid.WGS84.geodeticSurfaceNormal(cart, new Cartesian3());
const onSurface = Ellipsoid.WGS84.scaleToGeodeticSurface(cart, new Cartesian3());
Transforms -- Reference Frames
Transforms builds 4x4 matrices relating local frames to ECEF. The most commonly used function is eastNorthUpToFixedFrame.
East-North-Up (ENU)
ENU: X = east, Y = north, Z = up. Standard frame for placing models on the globe.
import { Cartesian3, Transforms, Matrix4 } from "cesium";
const origin = Cartesian3.fromDegrees(-105.0, 40.0);
const enuMatrix = Transforms.eastNorthUpToFixedFrame(origin);
Heading-Pitch-Roll Model Matrix
Standard way to position and orient a 3D model.
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),
0.0,
0.0,
);
const modelMatrix = Transforms.headingPitchRollToFixedFrame(position, hpr);
const orientation = Transforms.headingPitchRollQuaternion(position, hpr);
HeadingPitchRoll
Heading = rotation about -Z (compass bearing, clockwise). Pitch = about -Y. Roll = about +X. Radians.
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);
Other Local Frames
import { Transforms, Cartesian3 } from "cesium";
const origin = Cartesian3.fromDegrees(-105.0, 40.0);
Transforms.northEastDownToFixedFrame(origin);
Transforms.northUpEastToFixedFrame(origin);
const customFn = Transforms.localFrameToFixedFrameGenerator("north", "west");
const matrix = customFn(origin);
const hpr = Transforms.fixedFrameToHeadingPitchRoll(modelMatrix);
Matrix4 -- 4x4 Transforms
Column-major storage (WebGL convention). Constructor takes row-major for readability.
import { Matrix4, Matrix3, Cartesian3, Quaternion } from "cesium";
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);
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());
Matrix4.getTranslation(enuMatrix, new Cartesian3());
Matrix4.getMatrix3(enuMatrix, new Matrix3());
Matrix4.getScale(enuMatrix, new Cartesian3());
Quaternion -- Rotation
import { Quaternion, Cartesian3, HeadingPitchRoll, Math as CesiumMath, Matrix3 } from "cesium";
Quaternion.IDENTITY;
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());
const composed = Quaternion.multiply(q1, q2, new Quaternion());
Geodesic Distance
import { Cartographic, EllipsoidGeodesic, Cartesian3 } from "cesium";
const geodesic = new EllipsoidGeodesic(
Cartographic.fromDegrees(-73.985, 40.758),
Cartographic.fromDegrees(-0.1276, 51.5074),
);
const surfaceDist = geodesic.surfaceDistance;
const midCarto = geodesic.interpolateUsingFraction(0.5);
const chord = Cartesian3.distance(Cartesian3.fromDegrees(-105, 40), Cartesian3.fromDegrees(-104, 40));
BoundingSphere
import { BoundingSphere, Cartesian3 } from "cesium";
const sphere = BoundingSphere.fromPoints(
Cartesian3.fromDegreesArray([-105, 40, -100, 40, -100, 35]),
);
const inside = Cartesian3.distance(sphere.center, Cartesian3.fromDegrees(-102, 37.5)) <= sphere.radius;
Ray and Intersection Tests
import { Ray, IntersectionTests, Plane, Cartesian3, Ellipsoid } from "cesium";
const ray = new Ray(new Cartesian3(0, 0, 6378137), new Cartesian3(0, 0, -1));
const ptOnRay = Ray.getPoint(ray, 1000.0, new Cartesian3());
const plane = Plane.fromPointNormal(Cartesian3.ZERO, Cartesian3.UNIT_Z);
const hit = IntersectionTests.rayPlane(ray, plane);
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());
}
const t = IntersectionTests.rayTriangleParametric(ray, p0, p1, p2, true);
SceneTransforms -- World to Screen
import { SceneTransforms, Cartesian3 } from "cesium";
const winPos = SceneTransforms.worldToWindowCoordinates(viewer.scene, Cartesian3.fromDegrees(-105, 40));
const bufPos = SceneTransforms.worldToDrawingBufferCoordinates(viewer.scene, worldPos);
Geographic Projections
import { GeographicProjection, WebMercatorProjection, Cartographic, Ellipsoid } from "cesium";
const carto = Cartographic.fromDegrees(-105.0, 40.0);
const geoProj = new GeographicProjection(Ellipsoid.WGS84);
const xy = geoProj.project(carto);
const back = geoProj.unproject(xy);
const merc = new WebMercatorProjection(Ellipsoid.WGS84);
const mercXY = merc.project(carto);
Common Patterns
Offset a Position in Local ENU
import { Cartesian3, Transforms, Matrix4 } from "cesium";
const origin = Cartesian3.fromDegrees(-105.0, 40.0, 0.0);
const enu = Transforms.eastNorthUpToFixedFrame(origin);
const worldPt = Matrix4.multiplyByPoint(enu, new Cartesian3(500, 200, 100), new Cartesian3());
Compare Positions with Tolerance
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);
if (Cartesian3.distance(a, b) < 10.0) { }
Performance Tips
- Reuse scratch variables. Pre-allocate
result objects outside loops to avoid GC pauses.
- Use
distanceSquared instead of distance when comparing -- avoids Math.sqrt.
- Prefer
Cartesian3.fromDegrees over manual Cartographic creation then conversion.
- Cache model matrices. Call
Transforms.eastNorthUpToFixedFrame once if position is static.
- Use
Matrix4.inverseTransformation for rigid-body transforms -- faster and more stable than inverse.
- Batch position creation with
fromDegreesArray / fromDegreesArrayHeights instead of looping fromDegrees.
- Guard
Cartesian3.normalize -- it throws on zero-length vectors. Check magnitude first.
- Use
equalsEpsilon for float comparisons. CesiumMath.EPSILON7 is a good default tolerance.
- Pre-compute HPR outside render loops. Convert to quaternion/matrix only when orientation changes.
- Choose the right distance.
Cartesian3.distance = chord through Earth. EllipsoidGeodesic.surfaceDistance = great-circle.
See Also
- cesiumjs-camera -- Camera positioning and flight animations that consume these coordinate types
- cesiumjs-primitives -- Geometry and Primitive API that uses model matrices from Transforms
- cesiumjs-terrain-environment -- Terrain height queries and globe surface interactions