在 Cesium 中获取某点的**高程(Elevation)**、**坡度(Slope)** 和 **坡向(Aspect / 方位角)**,需要结合地形数据进行计算。Cesium 提供了高精度的地形服务(如 `CesiumWorldTerrain`),我们可以利用这些数据来实现。
以下是使用 **Cesium JS 1.131+** 实现“点击地图获取某点高程、坡度、坡向”的完整方案。
---
### ✅ 步骤说明
1. 启用地形(必须)
2. 获取某点经纬度对应的高程
3. 计算该点周围邻域的高程差,用于估算坡度和坡向
4. 使用有限差分法(Finite Difference)计算坡度与坡向
---
### ✅ 完整代码示例
```javascript
const viewer = new Cesium.Viewer('cesiumContainer', {
terrainProvider: Cesium.createWorldTerrain() // 必须启用地形
});
// 点击事件监听
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(async function(click) {
const pickedObject = viewer.scene.pick(click.position);
if (pickedObject && !Cesium.defined(pickedObject.id)) return;
// 转换屏幕坐标为笛卡尔坐标
const cartesian = viewer.scene.camera.pickEllipsoid(
click.position,
viewer.scene.globe.ellipsoid
);
if (!cartesian) {
console.log("未选中地面点");
return;
}
// 转换为经纬度(弧度 → 度)
const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
const lon = Cesium.Math.toDegrees(cartographic.longitude);
const lat = Cesium.Math.toDegrees(cartographic.latitude);
try {
// 获取高程(包含地形高度)
const height = await getElevation(lon, lat);
const slope = await calculateSlope(lon, lat); // 坡度(角度)
const aspect = await calculateAspect(lon, lat); // 坡向(0~360°)
console.log(`经度: ${lon.toFixed(6)}°`);
console.log(`纬度: ${lat.toFixed(6)}°`);
console.log(`高程: ${height.toFixed(2)} 米`);
console.log(`坡度: ${slope.toFixed(2)}°`);
console.log(`坡向: ${aspect.toFixed(2)}°`);
} catch (err) {
console.error("计算失败:", err);
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
// ==================== 工具函数 ====================
// 获取指定经纬度的高程
async function getElevation(lon, lat) {
const hasTerrain = viewer.scene.mode !== Cesium.SceneMode.SCENE2D;
if (!hasTerrain) throw new Error("地形未启用");
const promise = Cesium.sampleTerrainMostDetailed([
Cesium.Cartographic.fromDegrees(lon, lat)
]);
const updatedTessellations = await promise;
if (!updatedTessellations || updatedTessellations.length === 0) {
throw new Error("无法获取地形高程");
}
return updatedTessellations[0].height;
}
// 计算坡度(单位:度)
async function calculateSlope(lon, lat, spacing = 100) { // spacing 单位:米
const { dzdx, dzdy } = await computeGradient(lon, lat, spacing);
const slopeRadians = Math.atan(Math.sqrt(dzdx * dzdx + dzdy * dzdy));
return Cesium.Math.toDegrees(slopeRadians); // 转为角度
}
// 计算坡向(单位:度,0=北,90=东)
async function calculateAspect(lon, lat, spacing = 100) {
const { dzdx, dzdy } = await computeGradient(lon, lat, spacing);
if (dzdx === 0 && dzdy === 0) return 0; // 平坦区域
let aspectRadians = Math.atan2(dzdy, -dzdx); // 注意方向修正
let aspectDegrees = Cesium.Math.toDegrees(aspectRadians);
// 归一化到 0~360°
if (aspectDegrees < 0) aspectDegrees += 360;
return aspectDegrees;
}
// 使用中心差分法计算梯度(dz/dx, dz/dy)
async function computeGradient(lon, lat, spacing = 100) {
const ellipsoid = Cesium.Ellipsoid.WGS84;
// 将经纬度转为笛卡尔坐标
const center = Cesium.Cartographic.fromDegrees(lon, lat);
const hCenter = await getElevation(lon, lat);
// X 方向偏移(东)
const lonEast = lon + Cesium.Math.toRadians(spacing / ellipsoid.maximumRadius);
const lonWest = lon - Cesium.Math.toRadians(spacing / ellipsoid.maximumRadius);
// Y 方向偏移(北)
const latNorth = lat + Cesium.Math.toRadians(spacing / ellipsoid.maximumRadius);
const latSouth = lat - Cesium.Math.toRadians(spacing / ellipsoid.maximumRadius);
// 获取四个方向的高程
const [hEast, hWest, hNorth, hSouth] = await Promise.all([
getElevation(lonEast, lat),
getElevation(lonWest, lat),
getElevation(lon, latNorth),
getElevation(lon, latSouth)
]);
// 中心差分法求导
const dzdx = (hEast - hWest) / (2 * spacing);
const dzdy = (hNorth - hSouth) / (2 * spacing);
return { dzdx, dzdy };
}
```
---
### 🔍 解释说明
| 概念 | 说明 |
|------|------|
| **高程(Elevation)** | 使用 `sampleTerrainMostDetailed()` 从当前加载的地形中采样真实地表高度 |
| **坡度(Slope)** | 地表变化率,即法向量与垂直方向夹角,单位通常为“度” |
| **坡向(Aspect)** | 地形最陡下降方向的水平投影方位角,0°=正北,90°=正东 |
| **有限差分法** | 利用中心点周围的高程差异近似空间导数,是 GIS 中常用方法 |
> ⚠️ 注意:
- 需要网络连接以加载 `Cesium World Terrain`
- `sampleTerrainMostDetailed` 是异步的,需等待数据加载完成
- 若地形未覆盖该区域,可能返回 `undefined`
---
### ✅ 可视化增强建议
你可以添加一个标签或弹窗显示结果:
```js
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(lon, lat, height + 10),
label: {
text: `高程: ${height.toFixed(1)}m\n坡度: ${slope.toFixed(1)}°`,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
pixelOffset: new Cesium.Cartesian2(0, -20)
}
});
```
---