空间分析:测距、剖面与可视域
Cesium 不提供现成的分析工具箱,只提供几何与地形采样两类基础能力。 测距、剖面、通视判断都要自己组合这几块积木,组合方式决定了结果是否可信。
先分清三种距离
| 距离 | 算法 | 结果 | 典型用途 |
|---|---|---|---|
| 椭球面距离 | Cesium.EllipsoidGeodesic | 沿椭球面的测地线长度,米 | 地图量距、航线里程 |
| 空间直线距离 | Cesium.Cartesian3.distance | 两点在 ECEF 中的直线长度,米 | 通视、遮挡、三维间距 |
| 屏幕像素距离 | SceneTransforms | 画布上的像素数 | 交互判定、标注避让 |
var start = Cesium.Cartographic.fromDegrees(116.30, 39.90);
var end = Cesium.Cartographic.fromDegrees(116.46, 39.92);
var geodesic = new Cesium.EllipsoidGeodesic(start, end);
console.log('地表距离', geodesic.surfaceDistance.toFixed(1), '米');
console.log('方位角', Cesium.Math.toDegrees(geodesic.startHeading).toFixed(1), '度');
var a = Cesium.Cartesian3.fromDegrees(116.30, 39.90, 40);
var b = Cesium.Cartesian3.fromDegrees(116.46, 39.92, 120);
console.log('直线距离', Cesium.Cartesian3.distance(a, b).toFixed(1), '米');
两者在几十公里范围内差别不大,但在地形起伏明显的山区, 直线距离会因为高差而明显短于地表距离。报表里写「里程」应当用测地线, 写「视线长度」才用直线距离。
沿线段采样地形,得到剖面
Cesium.sampleTerrainMostDetailed 接收一个
Cartographic 数组,返回带 height 的新数组。
它内部会请求对应层级的地形瓦片,返回是异步的,点数越多耗时越长。
function sampleLine(start, end, count) {
var out = [];
for (var i = 0; i <= count; i++) {
var t = i / count;
out.push(new Cesium.Cartographic(
start.longitude + (end.longitude - start.longitude) * t,
start.latitude + (end.latitude - start.latitude) * t
));
}
return out;
}
async function buildProfile(start, end, count) {
var samples = sampleLine(start, end, count || 120);
await Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, samples);
return samples.map(function (c) {
return {
lng: Cesium.Math.toDegrees(c.longitude),
lat: Cesium.Math.toDegrees(c.latitude),
height: c.height
};
});
}
var profile = await buildProfile(
Cesium.Cartographic.fromDegrees(116.30, 39.90),
Cesium.Cartographic.fromDegrees(116.46, 39.92),
120
);
var maxHeight = Math.max.apply(null, profile.map(function (p) { return p.height; }));
viewer.terrainProvider 为 EllipsoidTerrainProvider 时,
所有采样点的高程都是 0。剖面是一条直线,不代表算法错了,只代表场景没有加载地形。
采样密度看用途。画一张示意图,60 个点足够;做坡度统计或工程断面, 要按地形瓦片分辨率反推点数,通常每 5 到 10 米一个采样点,否则细窄的沟壑会被跨过去。
两点通视判断
观察点与目标点之间逐点比较「视线高度」和「地面高度」,任意一个采样点地面高出视线, 两点即不可见。这是最常用的近似可视域算法,代价是一次地形采样请求。
async function isVisible(observer, target, steps) {
var n = steps || 100;
var samples = sampleLine(observer, target, n);
await Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, samples);
for (var i = 1; i < n; i++) {
var t = i / n;
var sightHeight = observer.height + (target.height - observer.height) * t;
if (samples[i].height > sightHeight + 0.3) {
return { visible: false, blockedAt: samples[i] };
}
}
return { visible: true, blockedAt: null };
}
var observer = Cesium.Cartographic.fromDegrees(116.391, 39.907, 28);
var target = Cesium.Cartographic.fromDegrees(116.412, 39.921, 60);
var result = await isVisible(observer, target, 100);
observer.height 在 Cartographic 里是米,不是弧度,
这一点与经纬度不同,容易在拷贝代码时改错。0.3 米的余量用来抵消地形采样误差。
把结果画回场景
分析结果最终要落到地图上:测量线、剖面采样点、遮挡位置。用实体绘制, 并把临时结果放在单独的数据源里,方便一次清空。
var analysisSource = new Cesium.CustomDataSource('analysis');
viewer.dataSources.add(analysisSource);
function drawMeasureLine(positions, labelText) {
analysisSource.entities.add({
polyline: {
positions: positions,
width: 2,
material: Cesium.Color.fromCssColorString('#4d9bff'),
clampToGround: false
},
label: {
text: labelText,
font: '500 13px sans-serif',
fillColor: Cesium.Color.WHITE,
showBackground: true,
backgroundColor: Cesium.Color.fromCssColorString('rgba(14, 17, 22, 0.85)'),
pixelOffset: new Cesium.Cartesian2(0, -18),
disableDepthTestDistance: Number.POSITIVE_INFINITY
},
position: positions[positions.length - 1]
});
}
function clearAnalysis() {
analysisSource.entities.removeAll();
}
注意事项
sampleTerrainMostDetailed会等待整块地形瓦片下载完成,首屏调用可能耗时数秒;界面上要有加载状态。- 相机移动后再做一次分析,必须重新采样,不能复用上一次的结果。
- 可视域是「点对点」判断,不是「面」判断。要得到可视范围,需要从观察点向四周发射多条视线。
- 实体数量多时用
viewer.entities.suspendEvents()与resumeEvents()包住批量增删。 - 剖面数据只用于展示,不要直接当工程量算;地形瓦片本身有简化,高程存在米级误差。
性能底线:一次通视判断采样 100 个点,交互中连续触发就会堆积请求。 用计数器限制并发,或者把结果按「起终点网格」缓存,避免同一区域重复采样。
经纬志(2026)。《空间分析:测距、剖面与可视域》。https://www.jingweizhi.com/cesium/tutorial/spatial-analysis.html(访问日期 2026-11-03)。