一种解决Cesium使用Billboard无法正常加载gif/png动图图标的替代方案

前言:日常项目中,我们经常会遇到产品希望三维地图中的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实现动态图标加载,且贴地、贴模型实现方式结束

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值