项目场景:
由于cesium默认的地形初次加载跨域option请求资源很慢,一般需要一两分钟才能加载完成,影响用户体验,于是改成天地图三维地形,解决了首次加载慢问题
问题描述
在使用过程中发现存在问题,无法通过正常的cesium.sampleTerrain 方法获取地形高度,获得高度undefined
原因分析:
对比了下两种地形的加载方式,发现存在很大差别
1.cesium 地形先加载了一个远程的json文件,也就是这一步首次加载非常耗时,然后再加载terrain元数据文件
2.天地图的加载方式看起来貌似只是一张地形图片
所以是不可能有数据的(因为sampleTerrain 方法通过发送请求获取地形数据的,显然这只是一张图片)
初步分析可能天地图没有提供地形高度方面的数据?
解决方案:
但是通过点击地球射线的方式依然可以获取到地形高度
3d.addEventListener(e => {
let ray = viewer.camera.getPickRay(e.position);
let cartesian = viewer.scene.globe.pick(ray, viewer.scene);
if (cartesian) {
let cartographic = Cesium.Cartographic.fromCartesian(cartesian);
console.log('cartographic',cartographic)
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
于是就可以通过创建射线Ray的方式,拾取射线与地形的交点高度、
export const getTerrainHeightByRay = (lon, lat) => {
// 转换为三维坐标(假设初始高度为 0 或高空)
const targetPosition = Cesium.Cartesian3.fromDegrees(lon, lat, 0); // 目标点(椭球表面)
const startPosition = viewer.camera.positionWC; // 相机世界坐标
// 计算射线方向(从起点指向目标点)
const direction = Cesium.Cartesian3.normalize(
Cesium.Cartesian3.subtract(targetPosition, startPosition, new Cesium.Cartesian3()),
new Cesium.Cartesian3()
);
// 创建射线
const ray = new Cesium.Ray(startPosition, direction);
// 拾取地形表面的交点
const cartesian = viewer.scene.globe.pick(ray, viewer.scene);
if (cartesian) {
// 转换为经纬度和高度
const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
const longitude = Cesium.Math.toDegrees(cartographic.longitude);
const latitude = Cesium.Math.toDegrees(cartographic.latitude);
const height = cartographic.height;
console.log(
`经度: ${longitude.toFixed(4)}, 纬度: ${latitude.toFixed(4)}, 高度: ${height.toFixed(2)} 米`
);
return height;
} else {
console.log('未拾取到地形表面');
return null;
}
};
切记这种方法存在问题:只会在地形加载过才会奏效(或者说在视角内),否则拾取不到地形的交点, 解决办法是相机先flyto到目标上方一个足够高的位置(需要自己判断),然后在飞行结束complete回调里面通过创建射线的方式获取到地形交点的高度,最后在飞到指定目标位置处。
const showPlot = async data => {
const v = 3d.getViewer();
const coord = data[0]
.replace('MULTIPOLYGON', '')
.replace(/[()]/g, '')
.replace(/\s/g, ',')
.split(',')
.map(item => Number(item));
v.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(coord[0], coord[1], 5000),
complete: () => loadEntity(data),
});
};
const loadEntity = async data => {
const v = 3d.getViewer();
v.entities.removeAll();
await Promise.all(
data.map(async (i, index) => {
let geoms = i
.replace('MULTIPOLYGON', '')
.replace(/[()]/g, '')
.replace(/\s/g, ',')
.split(',')
.map(item => Number(item));
const height = getTerrainHeightByRay(geoms[0], geoms[1]); // 获取地形高度
const entityHeight = height + 30; // 高出地形30米
v.entities.add({
id: `clickItem_polygon${index}`,
polygon: {
hierarchy: new Cesium.PolygonHierarchy(Cesium.Cartesian3.fromDegreesArray(geoms)),
outline: true,
extrudedHeight: entityHeight,
outlineColor: new Cesium.Color(1.0, 1.0, 0.0, 1.0),
material: new Cesium.Color(1.0, 1.0, 0.0, 0.8),
},
});
})
);
// 飞入到目标实体
v.flyTo(v.entities.values, {
duration: 1.5,
});
viewer.scene.requestRender(); //渲染
};
上面的获取天地图地形高度的方式仅仅是我个人的尝试,属于是奇淫技巧,目前在网上没有搜到任何关于如何获取到天地图地形高度的资料,准确性还没有得到验证,目前也没发现有什么问题,只是地形高度可能会有一定的偏差,有待进一步考量。如果有大佬对这方面熟悉的也可以指导一二,欢迎留言!