首页 / 专栏 / Cesium / Cesium 获取地形高度 全面深度教程

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'
// });

总结

关键点回顾

  1. Cesium 地形高度获取的核心方法分为经纬度查询sampleTerrain)和屏幕拾取globe.pick()),前者适用于已知坐标,后者适用于交互场景。
  2. 获取纯地形高度需注意排除模型/实体遮挡,可临时隐藏图元或判断拾取对象类型。
  3. 批量查询地形高度时,需分批次、加缓存、降级级别优化性能;查询结果为 0 时,优先检查是否加载了地形数据源。

掌握这些方法后,你可以在任意场景下精准获取地形高度,实现贴地路径、海拔显示、地形分析等核心功能。

同分类推荐