Cesium 获取地形高度 全面深度教程
- 发布时间: 2026-02-27 14:31:17
- 相关标签: cesium pick sampleTerrain
- 简介: 你想要系统掌握 Cesium 中**地形高度**的所有获取方法,包括单点拾取、批量查询、实时跟随等场景,同时理解地形高度的核心原理和避坑技巧,我会从基础概念、核心 API、实战案例到性能优化,全方位讲解地形高度的获取逻辑。
手机扫码查看
一、地形高度核心概念
1.1 地形高度的定义
Cesium 中的地形高度(Terrain Height) 是指某一经纬度位置相对于地球真实地形表面的高度(也叫海拔/高程),区别于:
- 椭球体高度:仅相对于 WGS84 椭球表面的高度(无地形时的默认值);
- 模型/实体高度:叠加在地形上的人工对象(建筑、模型)的高度。
地形高度的获取依赖 Cesium 加载的地形数据源(如 Cesium World Terrain、自定义 STK/QuantumGIS 地形),无地形数据时,地形高度等价于椭球体高度(通常为 0)。
1.2 核心坐标系关系
graph LR
A[经纬度(lon/lat)] -->|Cartographic| B[地形高度查询]
C[屏幕坐标(Cartesian2)] -->|PickRay| D[地形拾取]
B & D --> E[笛卡尔坐标(Cartesian3)]
E -->|Cartographic.fromCartesian| F[地形高度(height)]
二、获取地形高度的核心方法
2.1 方法分类与适用场景
| 方法 | 核心 API | 适用场景 | 精度 | 性能 |
|---|---|---|---|---|
| 单点经纬度查询 | sampleTerrain / sampleTerrainMostDetailed |
已知经纬度,批量/单点查高度 | 高(取决于地形级别) | 中 |
| 屏幕坐标拾取 | globe.pick() + 射线 |
鼠标点击/移动获取地形高度 | 高 | 高 |
| 相机位置高度 | camera.position 转换 |
获取当前相机所在地形高度 | 中 | 极高 |
| 批量地形采样 | sampleTerrain 循环 |
批量坐标(如路径)高度查询 | 高 | 中(可异步) |
2.2 核心 API 深度解析
(1)经纬度查询:sampleTerrain / sampleTerrainMostDetailed
最基础的地形高度查询方式,直接传入经纬度和地形级别,返回该位置的地形高度。
sampleTerrain:指定地形细化级别(level)查询,级别越高精度越高;sampleTerrainMostDetailed:自动使用当前视图最高级别查询,无需指定 level。
基础示例:
/**
* 根据经纬度查询地形高度
* @param {Cesium.Viewer} viewer - Cesium实例
* @param {number} lon - 经度(度数)
* @param {number} lat - 纬度(度数)
* @returns {Promise<number>} 地形高度(米)
*/
async function getTerrainHeightByLonLat(viewer, lon, lat) {
// 1. 转换为弧度(Cesium 内部计算用弧度)
const cartographic = Cesium.Cartographic.fromDegrees(lon, lat);
// 2. 执行地形采样(使用最高精度)
const terrainProvider = viewer.terrainProvider;
const sampledPositions = await Cesium.sampleTerrainMostDetailed(
terrainProvider,
[cartographic] // 传入数组,支持批量查询
);
// 3. 返回地形高度(米)
return sampledPositions[0].height;
}
// 使用示例
getTerrainHeightByLonLat(viewer, 116.39, 39.9)
.then(height => {
console.log('北京天安门地形高度:', height, '米');
})
.catch(err => {
console.error('查询失败:', err);
});
批量查询示例:
// 批量查询多个经纬度的地形高度
async function batchGetTerrainHeight(viewer, lonLatList) {
// 转换为Cartographic数组
const cartographics = lonLatList.map(item =>
Cesium.Cartographic.fromDegrees(item.lon, item.lat)
);
// 批量采样
const sampled = await Cesium.sampleTerrainMostDetailed(
viewer.terrainProvider,
cartographics
);
// 整理结果
return sampled.map((item, index) => ({
lon: lonLatList[index].lon,
lat: lonLatList[index].lat,
height: item.height
}));
}
// 调用示例
const points = [
{ lon: 116.39, lat: 39.9 },
{ lon: 120.12, lat: 30.25 },
{ lon: 113.23, lat: 23.16 }
];
batchGetTerrainHeight(viewer, points).then(res => {
console.log('批量地形高度结果:', res);
});
(2)屏幕坐标拾取:globe.pick()
通过鼠标点击/移动的屏幕坐标,拾取对应位置的地形高度(忽略模型/实体遮挡),是交互场景中最常用的方式。
核心示例:
/**
* 根据屏幕坐标获取地形高度
* @param {Cesium.Viewer} viewer - Cesium实例
* @param {Cesium.Cartesian2} windowPosition - 屏幕坐标(x,y)
* @returns {number|null} 地形高度(米),无结果返回null
*/
function getTerrainHeightByScreenPos(viewer, windowPosition) {
// 1. 生成拾取射线(从相机到屏幕坐标的射线)
const ray = viewer.camera.getPickRay(windowPosition);
if (!ray) return null;
// 2. 拾取地形(仅地形,忽略模型/实体)
const cartesian = viewer.scene.globe.pick(ray, viewer.scene);
if (!cartesian) return null;
// 3. 转换为经纬度和高度
const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
return cartographic.height;
}
// 鼠标点击获取地形高度示例
viewer.screenSpaceEventHandler.setInputAction((event) => {
const height = getTerrainHeightByScreenPos(viewer, event.position);
if (height !== null) {
console.log('点击位置地形高度:', height, '米');
// 可选:在点击位置添加标记
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(
Cesium.Math.toDegrees(cartographic.longitude),
Cesium.Math.toDegrees(cartographic.latitude),
height
),
point: {
pixelSize: 8,
color: Cesium.Color.RED,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 2
},
label: {
text: `高度:${height.toFixed(2)}米`,
font: '14px sans-serif',
pixelOffset: new Cesium.Cartesian2(0, -20)
}
});
} else {
console.log('未拾取到地形(指向天空/海洋)');
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
(3)相机位置地形高度
获取当前相机所在位置对应的地形高度,适用于实时显示相机海拔、飞行模拟等场景。
示例:
/**
* 获取相机位置对应的地形高度
* @param {Cesium.Viewer} viewer - Cesium实例
* @returns {Promise<number>} 相机位置地形高度(米)
*/
async function getCameraTerrainHeight(viewer) {
// 1. 获取相机经纬度
const cameraCartographic = Cesium.Cartographic.fromCartesian(
viewer.camera.position
);
const lon = Cesium.Math.toDegrees(cameraCartographic.longitude);
const lat = Cesium.Math.toDegrees(cameraCartographic.latitude);
// 2. 查询该位置的地形高度
return await getTerrainHeightByLonLat(viewer, lon, lat);
}
// 实时显示相机海拔(每1秒更新)
setInterval(async () => {
const terrainHeight = await getCameraTerrainHeight(viewer);
// 相机绝对高度 - 地形高度 = 相机相对地形的高度(离地高度)
const cameraHeight = Cesium.Cartographic.fromCartesian(viewer.camera.position).height;
const relativeHeight = cameraHeight - terrainHeight;
console.log(`相机海拔:${cameraHeight.toFixed(2)}米,地形高度:${terrainHeight.toFixed(2)}米,离地高度:${relativeHeight.toFixed(2)}米`);
}, 1000);
三、实战场景应用
3.1 场景1:鼠标移动实时显示地形高度
实现鼠标移动时,在页面上实时显示当前鼠标位置的地形高度(带节流优化):
// 节流函数:限制高频事件执行频率
function throttle(fn, delay = 100) {
let timer = null;
return (...args) => {
if (!timer) {
timer = setTimeout(() => {
fn(...args);
timer = null;
}, delay);
}
};
}
// 鼠标移动实时获取地形高度(节流处理)
const updateTerrainHeight = throttle((event) => {
const height = getTerrainHeightByScreenPos(viewer, event.endPosition);
if (height !== null) {
// 更新页面显示(假设页面有id为terrain-height的元素)
document.getElementById('terrain-height').innerText =
`当前地形高度:${height.toFixed(2)} 米`;
} else {
document.getElementById('terrain-height').innerText = '未拾取到地形';
}
}, 100);
// 绑定鼠标移动事件
viewer.screenSpaceEventHandler.setInputAction(updateTerrainHeight, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
3.2 场景2:忽略模型/实体,仅拾取纯地形高度
当场景中有模型、实体遮挡时,确保只获取地形高度(而非模型表面高度):
/**
* 仅拾取纯地形高度(忽略模型/实体)
* @param {Cesium.Viewer} viewer - Cesium实例
* @param {Cesium.Cartesian2} windowPosition - 屏幕坐标
* @returns {number|null} 纯地形高度
*/
function getPureTerrainHeight(viewer, windowPosition) {
const ray = viewer.camera.getPickRay(windowPosition);
if (!ray) return null;
// 临时隐藏所有模型/实体,拾取地形后恢复
const primitives = viewer.scene.primitives;
const entities = viewer.entities;
// 1. 保存当前状态并隐藏
const primitivesShow = primitives.show;
const entitiesShow = entities.show;
primitives.show = false;
entities.show = false;
// 2. 拾取地形高度
const cartesian = viewer.scene.globe.pick(ray, viewer.scene);
let height = null;
if (cartesian) {
height = Cesium.Cartographic.fromCartesian(cartesian).height;
}
// 3. 恢复显示
primitives.show = primitivesShow;
entities.show = entitiesShow;
return height;
}
// 使用示例:点击获取纯地形高度
viewer.screenSpaceEventHandler.setInputAction((event) => {
const height = getPureTerrainHeight(viewer, event.position);
console.log('纯地形高度:', height);
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
3.3 场景3:路径点地形高度补全
给一组二维路径点(仅经纬度)补全地形高度,生成三维路径:
/**
* 补全路径点的地形高度
* @param {Cesium.Viewer} viewer - Cesium实例
* @param {Array<{lon: number, lat: number}>} pathPoints - 二维路径点
* @returns {Promise<Array<Cesium.Cartesian3>>} 三维路径点(带地形高度)
*/
async function completePathWithTerrainHeight(viewer, pathPoints) {
// 1. 批量查询地形高度
const heightResult = await batchGetTerrainHeight(viewer, pathPoints);
// 2. 转换为Cartesian3三维坐标
return heightResult.map(item =>
Cesium.Cartesian3.fromDegrees(item.lon, item.lat, item.height)
);
}
// 使用示例:绘制带地形高度的路径
const path2D = [
{ lon: 116.39, lat: 39.9 },
{ lon: 116.40, lat: 39.91 },
{ lon: 116.41, lat: 39.905 }
];
completePathWithTerrainHeight(viewer, path2D).then(path3D => {
// 添加路径实体
viewer.entities.add({
polyline: {
positions: path3D,
width: 5,
material: Cesium.Color.RED,
clampToGround: true // 贴地(可选)
}
});
});
四、避坑技巧与性能优化
4.1 常见坑点及解决方案
| 坑点 | 现象 | 解决方案 |
|---|---|---|
| 地形高度返回 0 | 查询结果始终为 0,无真实地形高度 | 1. 确认加载了地形数据源(如 Cesium World Terrain); 2. 检查地形服务是否正常; 3. 确保使用 sampleTerrain 而非直接取 Cartographic.height |
| 拾取到模型/实体高度 | 想要地形高度却拿到了建筑高度 | 1. 使用 getPureTerrainHeight 临时隐藏模型;2. 判断拾取对象类型( pickedObject.primitive 是否为 TerrainMesh) |
| 批量查询卡顿 | 大量坐标查询导致页面卡死 | 1. 分批次查询(如每50个点一批); 2. 使用异步查询,避免阻塞主线程; 3. 降级地形级别(非必要时不用最高精度) |
| 海洋区域高度异常 | 海洋区域返回负高度或 NaN | 1. 判断高度是否为负数,海洋区域默认设为 0; 2. 增加异常值处理(NaN/Infinity 替换为 0) |
4.2 性能优化策略
(1)分批次批量查询
// 分批次查询大量坐标(避免一次性查询卡死)
async function batchQueryByBatch(viewer, lonLatList, batchSize = 50) {
const results = [];
// 分批次
for (let i = 0; i < lonLatList.length; i += batchSize) {
const batch = lonLatList.slice(i, i + batchSize);
const batchResult = await batchGetTerrainHeight(viewer, batch);
results.push(...batchResult);
// 每批查询后短暂休眠,释放主线程
await new Promise(resolve => setTimeout(resolve, 50));
}
return results;
}
(2)缓存查询结果
避免重复查询相同经纬度的地形高度:
// 地形高度缓存
const terrainHeightCache = new Map();
async function getTerrainHeightWithCache(viewer, lon, lat) {
// 生成缓存key
const key = `${lon.toFixed(6)},${lat.toFixed(6)}`;
// 命中缓存直接返回
if (terrainHeightCache.has(key)) {
return terrainHeightCache.get(key);
}
// 未命中则查询并缓存
const height = await getTerrainHeightByLonLat(viewer, lon, lat);
terrainHeightCache.set(key, height);
// 可选:设置缓存过期时间(如10分钟)
setTimeout(() => terrainHeightCache.delete(key), 600000);
return height;
}
(3)降级地形级别
非高精度场景下,降低 sampleTerrain 的级别,提升查询速度:
// 降级查询(级别越低速度越快)
async function getTerrainHeightWithLevel(viewer, lon, lat, level = 10) {
const cartographic = Cesium.Cartographic.fromDegrees(lon, lat);
const sampled = await Cesium.sampleTerrain(viewer.terrainProvider, level, [cartographic]);
return sampled[0].height;
}
五、地形数据源配置(基础前提)
获取真实地形高度的前提是加载了地形数据源,默认 Cesium 未加载地形,需手动配置:
// 加载 Cesium 官方全球地形(需联网)
viewer.terrainProvider = new Cesium.CesiumTerrainProvider({
url: Cesium.IonResource.fromAssetId(1), // Cesium World Terrain
requestWaterMask: true, // 加载水面掩码(海洋/湖泊)
requestVertexNormals: true // 加载法向量(提升光照效果)
});
// 加载自定义地形(如 STK 地形)
// viewer.terrainProvider = new Cesium.CesiumTerrainProvider({
// url: './terrain', // 本地地形服务地址
// fileExtension: 'terrain'
// });
总结
关键点回顾
- Cesium 地形高度获取的核心方法分为经纬度查询(
sampleTerrain)和屏幕拾取(globe.pick()),前者适用于已知坐标,后者适用于交互场景。 - 获取纯地形高度需注意排除模型/实体遮挡,可临时隐藏图元或判断拾取对象类型。
- 批量查询地形高度时,需分批次、加缓存、降级级别优化性能;查询结果为 0 时,优先检查是否加载了地形数据源。
掌握这些方法后,你可以在任意场景下精准获取地形高度,实现贴地路径、海拔显示、地形分析等核心功能。
书签篮