cesium加载大量entity优化之路

1,366 阅读3分钟

cesium加载大量entity优化之路

起因:我在地图上标注点位目前有100个点位,为了方便用了entity,加上后会阻塞主线程导致页面假死页面动不了也就是广告牌需求说后续有几千个,我就先模拟了1000个点位,(100个大概假死5秒,1000个大概假死11秒),

addPoint(monitor: Monitor, active = false) {
  const requireImg = active
    ? require('@/assets/img/common/monitorA.png')
    : require('@/assets/img/common/monitorB.png')
  this.dataSource.entities.add(
    new Cesium.Entity({
      position: Cesium.Cartesian3.fromDegrees(
        monitor.position[0],
        monitor.position[1]
      ), //从item对象获取经纬度
      billboard: {
        image: requireImg,
        horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
        verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
        heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
        scaleByDistance: new Cesium.NearFarScalar(10.0e4, 1, 1.0e6, 0.001),
        scale: 1, // 标注点icon缩放比例
        width: 64, // 设置标注点icon的高和宽,单位默认时px
        height: 276
      },
      label: {
        show: true, //是否显示标注点文本
        scale: 0.6,
        font: 'normal 900 24px MicroSoft YaHei', //字体
        fillColor: Cesium.Color.fromCssColorString('#ffffff'), //字体颜色
        text: monitor.name, //从接口获取点的标记文本
        pixelOffset: new Cesium.Cartesian2(0, -300), //文字显示的偏移量
        horizontalOrigin: Cesium.HorizontalOrigin.CENTER, //水平居中
        verticalOrigin: Cesium.VerticalOrigin.CENTER,
        heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
        style: Cesium.LabelStyle.FILL,
        showBackground: false, //设置文本背景
        distanceDisplayCondition: new Cesium.DistanceDisplayCondition(
          10.0,
          4000.0
        )
      },
      dataId: monitor.id
    } as never)
  )
}

for (let i = 1; i < 1000; i++) {
  let tObj = {
    position: [113.37994904018065 + i * 0.001, 31.693163991],
    name: 'camera1'
  }
  this.monitorList.push(tObj)
}
this.monitorList.forEach((item: Monitor) => this.addPoint(item))

因为点位包含了图表和标题所以我先将entity改成用BillboardCollection和LabelCollection(1000个优化至6秒)

addPoint(monitor: Monitor, active = false) {
  const requireImg = active
    ? require('@/assets/img/common/monitorA.png')
    : require('@/assets/img/common/monitorB.png')
  const billboard = this.billboards.add({
    position: Cesium.Cartesian3.fromDegrees(
      monitor.position[0],
      monitor.position[1]
    ),
    image: requireImg,
    scale: 1, // 图元icon缩放比例
    width: 64, // 设置图元icon的宽度,单位默认是像素
    height: 276, // 设置图元icon的高度,单位默认是像素
    horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
    verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
    heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
    scaleByDistance: new Cesium.NearFarScalar(10.0e4, 1, 1.0e6, 0.001)
  })
  const label = this.labels.add({
    position: Cesium.Cartesian3.fromDegrees(
      monitor.position[0],
      monitor.position[1]
    ),
    show: true, //是否显示标注点文本
    scale: 0.6,
    font: 'normal 900 24px MicroSoft YaHei', //字体
    fillColor: Cesium.Color.fromCssColorString('#ffffff'), //字体颜色
    text: monitor.name, //从接口获取点的标记文本
    pixelOffset: new Cesium.Cartesian2(0, -300), //文字显示的偏移量
    horizontalOrigin: Cesium.HorizontalOrigin.CENTER, //水平居中
    verticalOrigin: Cesium.VerticalOrigin.CENTER,
    heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
    style: Cesium.LabelStyle.FILL,
    showBackground: false, //设置文本背景
    distanceDisplayCondition: new Cesium.DistanceDisplayCondition(
      10.0,
      4000.0
    )
  })
}

image.png

优化了加载速度但是还是会假死,怎么解决呢?刚好在b站上听了一位老师的课,讲的是时间切片(有点像定时任务),将一个要花费时间很长的任务,分解成一个个小任务,比如用户点了全选,有1000个单选框,先勾选100个,再以100个逐级勾选,这样用户也感知不到,如果不用时间切片,将会假死,

for (let i = 1; i < 1000; i++) {
  let tObj = {
    position: [113.37994904018065 + i * 0.001, 31.693163991],
    name: 'camera1'
  }
  this.monitorList.push(tObj)
}
this.addEntitiesInChunks()
requestNum = 0
requestStep = 15 // 一次切片的循环次数
addEntitiesInChunks() {
  requestAnimationFrame(() => {
    console.log(this.requestNum, 'this.requestStep')
    const tmpNum = this.requestNum + this.requestStep
    for (; this.requestNum < tmpNum; this.requestNum++) {
      if (this.requestNum < this.monitorList.length) {
        this.addPoint(this.monitorList[this.requestNum])
      }
    }
    if (this.requestNum <= 1000) {
      this.addEntitiesInChunks()
    }
  })
}

这样requestStep调大了,加载速度快,卡顿越明显,requestStep调小了,加载速度慢,卡顿小,但是调到一个合适的值,效果还是可以接受的,起码假死解决了

还是会卡顿怎么办,还得从billboard和label入手,经过调试发现 heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,这个属性比较耗时,那总不可能去掉,毕竟地形项目上是一定会加载的,我想去掉其中一个,不就快一倍吗,发现网上有文章通过canvas把标题放入图片里然后转出base64的做法,这label不就去掉了,做的过程中发现this.canvas.toDataURL('image/png')转的这一步也比较耗时间,于是我用结合了时间切片的做法,最后时间短了,也不卡了

for (let i = 1; i < 2000; i++) {  
      let tObj = {  
        position: [113.37994904018065 + i * 0.001, 31.693163991],  
        name: 'camera1'  
      }  
      this.monitorList.push(tObj)  
    }  
    this.canvas = document.createElement('canvas')  
    this.ctx = this.canvas.getContext('2d')  
    this.canvas.width = 64  
    this.canvas.height = 300  
    const base64Image = ''  
    // 加载图片  
    this.image = new Image()  
    this.image.onload = () => {  
      console.log('on')  
      this.ctx.drawImage(  
        this.image,  
        0,  
        0,  
        this.canvas.width,  
        this.canvas.height  
      )  
      this.ctx.font = '16px Arial'  
      this.ctx.fillStyle = '#000000'  
      this.ctx.textAlign = 'center'  
    }  
    this.image.src = require('@/assets/img/common/monitorA.png')  
    this.addEntitiesInChunks()
  tt = 0  
  tarray: any = []  
  addEntitiesInChunks() {  
    requestAnimationFrame(() => {  
      console.log(this.tt, 'this.tt')  
      const step = this.tt + 10  
      for (; this.tt < step; this.tt++) {  
        this.ctx.save() // 保存 Canvas 状态  
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height) // 清除 Canvas  
        this.ctx.drawImage(  
          this.image,  
          0,  
          0,  
          this.canvas.width,  
          this.canvas.height  
        )  
        this.ctx.fillText(`Monitor ${this.tt}`, this.canvas.width / 2, 10)  
  
        let t1 = {  
          position: Cesium.Cartesian3.fromDegrees(  
            ...this.monitorList[this.tt].position  
          ),  
          image: this.canvas.toDataURL('image/png'),  
          scale: 1,  
          width: 64,  
          height: 276,  
          horizontalOrigin: Cesium.HorizontalOrigin.CENTER,  
          verticalOrigin: Cesium.VerticalOrigin.BOTTOM,  
          heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,  
          scaleByDistance: new Cesium.NearFarScalar(10.0e4, 1, 1.0e6, 0.001)  
        }  
        this.tarray.push(t1)  
        this.billboards.add(t1)  
        this.ctx.restore() // 恢复 Canvas 状态  
      }  
      if (this.tt < 200) {  
        this.addEntitiesInChunks()  
      }  
    })   
  }

image.png

因为我的初衷是想页面不假死不阻塞主线程就行,最后我也尝试了一下web woker,发现cesium的对象不知道怎么获取,后面有时间再研究一下web woker。这个技术也是b站的一位老师讲的,感谢老师!!!