前言:日常项目中,我们经常会遇到产品希望三维地图中的maker图标能实现动态效果,要实现三维地图内图标动态效果需要用到gif/apng动图,介于cesium原生的Billboard类仅支持单帧纹理贴图,网上查了一堆资料,基本都是通过第三方库如:libgif.js、gifler.js、apng-js对动图进行解析,在通过CallbackProperty方法,将解析下来的每帧图片进行轮播,从而实现动图效果;但这种方式存在两个问题:
1、大量加载动态图标的时候,会明显的消耗性能,影响体验;
2、对UI美术制作功底有一定要求,很多时候UI给出来的或网上下载的gif/apng图片显示不正常,甚至破图的情况,图一:为原数据apng动图,图二:为cesium通过apng-js解析后使用Billboard的CallbackProperty方法加载出来的效果;
图一 图二
思来想去决定另辟蹊径,最后完美解决,效果如图:
实现思路比较简单,就是通过Viewer.cesiumWidget.container.appendChild()方法加载div实现gif/apng动图的加载,代码如下:
// 经纬度位置
const gisPosition = Cesium.Cartesian3.fromDegrees(longitude, latitude, 0)
const waterworksMakerDiv = document.createElement(`waterworksMakerDivs`)
waterworksMakerDiv.id = 'waterworksMakerDivs'
waterworksMakerDiv.style.position = 'absolute'
waterworksMakerDiv.style.zIndex = 2
waterworksMakerDiv.innerHTML = `
<img src= "gismap/pump_icon_4.gif" class="waterworksMakerDiv_img" style="width: 50px; height: 60px;" />
`
CesiumViewer.cesiumWidget.container.appendChild(waterworksMakerDiv)
CesiumViewer.scene.postRender.addEventListener(() => {
let px = Cesium.SceneTransforms.wgs84ToWindowCoordinates(
CesiumViewer.scene,
gisPosition
)
waterworksMakerDiv.style.left = Number(px?.x - 25) + 'px' // 25为图片样式宽度/2
waterworksMakerDiv.style.top = Number(px?.y) - 60 + 'px' // 60为图片样式高度
})
但我们还会遇到一个问题:当场景加载地形的时候,使用这个方式直接加载div图标,没有贴地效果,造成场景相机旋转/缩放/平移的时候,出现图标错位的视觉错误情况,因为gisPosition变量里面的高度为0了,这里就需要用到Cesium.sampleTerrainMostDetailed方法通过经纬度计算出对应位置的高度,将高度赋予gisPosition,实现贴地效果,具体代码如下:
// 经纬度位置
const gisPosition = Cesium.Cartesian3.fromDegrees(longitude, latitude, 0)
// 使用SampleTerrainMostDetailed函数获取地形高度
Cesium.sampleTerrainMostDetailed(CesiumViewer.terrainProvider, [
gisPosition
])
.then((terrainSamples: any[]) => {
const resultPosition = terrainSamples[0]
const terHigh = resultPosition.height // 该位置下的地形高度
const terCartographic = Cesium.Cartesian3.fromDegrees(
longitude,
latitude,
terHigh
)
const waterworksMakerDiv = document.createElement(`waterworksMakerDivs`)
waterworksMakerDiv.id = 'waterworksMakerDivs'
waterworksMakerDiv.style.position = 'absolute'
waterworksMakerDiv.style.zIndex = 2
waterworksMakerDiv.innerHTML = `
<img src= "gismap/pump_icon_4.gif" class="waterworksMakerDiv_img" style="width: 50px; height: 60px;" />
`
CesiumViewer.cesiumWidget.container.appendChild(waterworksMakerDiv)
CesiumViewer.scene.postRender.addEventListener(() => {
let px = Cesium.SceneTransforms.wgs84ToWindowCoordinates(
CesiumViewer.scene,
terCartographic
)
waterworksMakerDiv.style.left = Number(px?.x - 25) + 'px' // 25为图片样式宽度/2
waterworksMakerDiv.style.top = Number(px?.y) - 60 + 'px' // 60为图片样式高度
})
})
.catch((error: any) => {
console.error('Error getting terrain height:', error)
})
另外,div贴模型也是一样的道理,通过Viewer.scene.sampleHeight方法返回对应位置的模型高度,具体代码如下:
function determineHeight(longitude: string | number, latitude: string | number) {
const terCartographic = Cesium.Cartographic.fromDegrees(
+longitude,
+latitude
);
let billboardHeight;
if (CesiumViewer.scene.sampleHeightSupported) {
billboardHeight = CesiumViewer.scene.sampleHeight(terCartographic);
}
return billboardHeight;
}
到此,cesium实现动态图标加载,且贴地、贴模型实现方式结束