三维坐标、高度与局部坐标系
同样一个「北京」,在 Cesium 里可以写成经纬度、写成地心直角坐标, 也可以写成相对某个点的局部坐标。混用这三种表达,是坐标类问题的共同来源。
三种表达,三种单位
| 类型 | 类 | 单位 | 使用场景 |
|---|---|---|---|
| 大地坐标 | Cesium.Cartographic | 经度、纬度用弧度,高度用米 | 地形采样、测地线、瓦片范围 |
| 地心直角坐标 | Cesium.Cartesian3 | 米,原点在地球质心 | 实体位置、相机、着色器、模型矩阵 |
| 局部坐标 | Matrix4 变换后的 Cartesian3 | 米,原点自定 | 建筑局部建模、模型摆放、批量构件 |
// 经纬度 -> 大地坐标(弧度)
var carto = Cesium.Cartographic.fromDegrees(116.391263, 39.907359, 50);
console.log(carto.longitude); // 2.0313...
console.log(carto.latitude); // 0.6965...
console.log(carto.height); // 50
// 大地坐标 -> 地心直角坐标
var cartesian = Cesium.Cartesian3.fromRadians(
carto.longitude, carto.latitude, carto.height
);
// 地心直角坐标 -> 大地坐标
var back = Cesium.Cartographic.fromCartesian(cartesian, Cesium.Ellipsoid.WGS84);
// 批量转换:一次给一串经纬度
var positions = Cesium.Cartesian3.fromDegreesArray([
116.391, 39.907,
116.404, 39.915,
116.418, 39.921
]);
Cartographic.fromDegrees 收的是度,存的是弧度。
把 carto.longitude 直接塞回 fromDegrees,坐标会跑到地球另一边。
读写 Cartographic 时始终记住它是弧度。
高度是米,不是弧度。Cartographic 的三个字段单位不统一,
这是 Cesium 的历史设计。所有涉及 height 的计算都按米处理。
高度参考与贴地
实体的 position 高度为 0 时,点会落在椭球面上,而不是地形表面。
要让点随地形起伏,需要设置 heightReference,由渲染器在每帧按地形调整。
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(116.391263, 39.907359),
point: {
pixelSize: 10,
color: Cesium.Color.fromCssColorString('#4d9bff'),
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
},
label: {
text: '地面点',
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
disableDepthTestDistance: Number.POSITIVE_INFINITY
}
});
// 需要知道某点地面高度时,查当前地形
var carto = Cesium.Cartographic.fromDegrees(116.391263, 39.907359);
var ground = viewer.scene.globe.getHeight(carto); // 地形未加载完时返回 undefined
HeightReference 有三个取值:NONE 不贴地、
CLAMP_TO_GROUND 贴地形、RELATIVE_TO_GROUND
在给定高度基础上叠加地形。折线要贴地则用 clampToGround: true,
它走的是完全另一条渲染管线(GroundPrimitive),比普通折线贵。
局部坐标系:把建筑放到一个点上
Transforms.eastNorthUpToFixedFrame 生成一个以某点为原点、
X 轴朝东、Y 轴朝北、Z 轴朝天的变换矩阵。局部坐标 (x, y, z)
乘上这个矩阵,就得到地心直角坐标。
var origin = Cesium.Cartesian3.fromDegrees(116.397, 39.908, 0);
var enu = Cesium.Transforms.eastNorthUpToFixedFrame(origin);
// 原点以东 120 米、以北 30 米、高 8 米
var local = new Cesium.Cartesian3(120, 30, 8);
var world = Cesium.Matrix4.multiplyByPoint(enu, local, new Cesium.Cartesian3());
// 反过来:世界坐标 -> 局部坐标
var inverse = Cesium.Matrix4.inverseTransformation(enu, new Cesium.Matrix4());
var backLocal = Cesium.Matrix4.multiplyByPoint(inverse, world, new Cesium.Cartesian3());
// 一次算多个点
var ring = [
new Cesium.Cartesian3(-40, -40, 0),
new Cesium.Cartesian3( 40, -40, 0),
new Cesium.Cartesian3( 40, 40, 0),
new Cesium.Cartesian3(-40, 40, 0)
];
var worldRing = ring.map(function (p) {
return Cesium.Matrix4.multiplyByPoint(enu, p, new Cesium.Cartesian3());
});
局部坐标非常适合「一栋楼内部有许多构件」这类场景:构件坐标保持小数值, 精度稳定;换一个位置只需换一次矩阵,不用重新计算每个构件。
屏幕坐标与地理坐标互转
交互里拿到的鼠标位置是画布像素。要换算成三维点,
用 camera.pickEllipsoid 打到椭球面,或用
scene.pickPosition 打到模型或地形表面。两者结果可能相差几十米。
var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(function (movement) {
var screen = movement.position;
// 打到椭球面(忽略地形与模型)
var onEllipsoid = viewer.camera.pickEllipsoid(
screen, viewer.scene.globe.ellipsoid
);
// 打到当前渲染的深度缓冲(地形、模型都算)
var onScene = viewer.scene.pickPosition(screen);
if (Cesium.defined(onEllipsoid)) {
var carto = Cesium.Cartographic.fromCartesian(onEllipsoid);
console.log(Cesium.Math.toDegrees(carto.longitude), Cesium.Math.toDegrees(carto.latitude));
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
// 反向:三维点 -> 画布像素
var canvasPos = viewer.scene.cartesianToCanvasCoordinates(
Cesium.Cartesian3.fromDegrees(116.391, 39.907, 100)
);
注意事项
Cartographic.fromCartesian不传椭球时使用 WGS84,国内 CGCS2000 与 WGS84 在米级上相容,但不要和北京 54 混用。pickPosition依赖深度缓冲,部分低端设备或关闭scene.pickTranslucentDepth时对透明物体会失效。- 批量坐标用
fromDegreesArray、fromDegreesArrayHeights,比循环调用fromDegrees少大量临时对象。 - 局部坐标系原点选在场景中心附近,离原点几公里外的局部坐标会开始出现浮点抖动。
scene.globe.getHeight只反映当前已加载地形,和sampleTerrainMostDetailed的结果可能不同。
一条排查原则:坐标看起来「偏了几十公里」,先查经纬度是否被当成弧度; 偏了几十米,查高程基准与投影;偏移随缩放变化,才是渲染精度问题。
经纬志(2026)。《三维坐标、高度与局部坐标系》。https://www.jingweizhi.com/cesium/tutorial/coordinates.html(访问日期 2026-11-03)。