Mars3D加载大量Label实体时卡顿的一种解决方法

1,511 阅读5分钟

前言

有部分用户在使用 Mars3D 加载大量label文本时,直到数据量越来越大,地图上的实体越来越多,首屏加载的时候经常会卡顿,客户那边的机器性能太差,有时候卡顿的同时还出现浏览器无响应问题。测试把这个问题归为BUG,要求必须解决~,下面我们来看具体的排查问题过程和解决方法。

加载成百上千实体时出现的问题

让我们先来看下加载Label实体出现了什么问题?

function addBillboardEntity(bsm, name, buildingType, position) {
    var graphic = new mars3d.graphic.BillboardEntity({
      id: bsm,
      name,
      position: position,
      style: {
        image: "img/marker/di3.png",
        width: 300,
        height: 140,
        pixelOffset: new Cesium.Cartesian2(90, -25), // 偏移量
        distanceDisplayCondition: new Cesium.DistanceDisplayCondition(10.0, 2000.0),
        disableDepthTestDistance: 100.0,
        scaleByDistance: new Cesium.NearFarScalar(500, 1, 1400, 0.0),
        translucencyByDistance: new Cesium.NearFarScalar(500, 1, 1400, 0.0),
        label: {
          text: name,
          font_size: 40,
          scale: 0.5,
          color: "#ffffff",
          pixelOffsetX: 110,
          pixelOffsetY: -70,
          outlineColor: "#ffffff", // 边框颜色
          outlineWidth: 1, // 边框宽度
          background: true,
          backgroundColor: "rgba(255,124,125,0.5)",
          distanceDisplayCondition: new Cesium.DistanceDisplayCondition(10.0, 2000.0),
          scaleByDistance: new Cesium.NearFarScalar(500, 1, 1400, 0.0),
          translucencyByDistance: new Cesium.NearFarScalar(500, 1, 1400, 0.0),
        },
      },
      attr: { poiType: "buildingPOI", name, buildingType, bsm, position },
    });
    graphicLayer.addGraphic(graphic);
}

当我需要创建400多个上面这样的实体时,页面总会卡顿一段时间。于是我上网查找相关资料,可能我表述有问题,一直没找到我想要的答案。所以我进行了浏览器DevTools。

pic_b2681676.png 

在加载地图的时候,js执行了4.75秒,公司电脑配置如下: pic_7648b1cf.png 

性能不算差吧,继续看Performance分析: pic_c23aff20.png 

这个getImageData怎么执行了2秒多,我当时想难道我传进去的图片Mars3D要做其他的处理?

于是我把BillboardEntity图标点对象改为直接用LabelEntity文本对象:

function addLabelEntity(bsm, name, buildingType, position) {
    var graphic = new mars3d.graphic.LabelEntity({
      id: bsm,
      name,
      position: position,
      style: {
        text: name,
        font_size: 40,
        scale: 0.5,
        color: "#ffffff",
        pixelOffsetX: 110,
        pixelOffsetY: -70,
        outlineColor: "#ffffff", // 边框颜色
        outlineWidth: 1, // 边框宽度
        background: true,
        backgroundColor: "rgba(255,124,125,0.5)",
        distanceDisplayCondition: new Cesium.DistanceDisplayCondition(10.0, 2000.0),
        scaleByDistance: new Cesium.NearFarScalar(500, 1, 1400, 0.0),
        translucencyByDistance: new Cesium.NearFarScalar(500, 1, 1400, 0.0),
      },
      attr: { poiType: "buildingPOI", name, buildingType, bsm, position },
    });
    graphicLayer.addGraphic(graphic);
}

pic_3659fbb3.png 

少了20%的时间,但Mars3D依然调了getImageData,这就奇怪了?

难道这是Mars3D在生成图片,已经用LabelEntity来渲染了,其他都是纯数据的。难道label不是文本?

带着这个问题,我把LabelEntity实体的style属性也注释掉了。进行了一次Performancepic_756462ec.png 

getImageData方法没有调用: pic_1a5ccdd5.png 

至此,我断定Mars3D在创建含有label的entitie时,会动态生成对应label.name值的图片。

解决方案:自行动态生成图片

找到问题,接下来就好办了,自己尝试构造这个label,不要用到Mars3D的Label。原本项目中实体的BillboardEntity是一张png格式的底图。

pic_588c6b1f.png

思路: 使用Canvas绘制一张带有文本的图片,再将base64码给BillboardEntitystyle.image参数

//绘制图片:原来的图片+绘制的name的文字
async function createImage(name) {
  const image = await Cesium.Resource.fetchImage({ url: "img/marker/di3.png" })

  const canvas = document.createElement("canvas")
  canvas.width = image.width
  canvas.height = image.height

  const ctx = canvas.getContext("2d")
  ctx.drawImage(image, 0, 0, image.width, image.height)
  ctx.font = "bold 20px Arial"
  ctx.textAlign = "center"
  ctx.textBaseline = "bottom"
  ctx.fillStyle = "#FFF"
  ctx.fillText(name, 220, 70)
  return canvas.toDataURL("image/png")
}

//创建图标对象
function addBillboardEntity(bsm, name, buildingType, position) {
  var graphic = new mars3d.graphic.BillboardEntity({
    id: bsm,
    name,
    position: position,
    style: {
      image: await createImage(name), //生成图片
      width: 300,
      height: 140,
      pixelOffset: new Cesium.Cartesian2(90, -25), // 偏移量
      distanceDisplayCondition: new Cesium.DistanceDisplayCondition(10.0, 2000.0),
      disableDepthTestDistance: 100.0,
      scaleByDistance: new Cesium.NearFarScalar(500, 1, 1400, 0.0),
      translucencyByDistance: new Cesium.NearFarScalar(500, 1, 1400, 0.0),
    },
    attr: { poiType: "buildingPOI", name, buildingType, bsm, position },
  });
  graphicLayer.addGraphic(graphic);
}

再进行一次Performance看看, 页面秒开了,没有卡顿: pic_0f69adf6.png

标注也正常显示: pic_06a0fd2e.png 

可以参考Mars3D功能示例的 CanvasBillboard.js 内的代码来理解Canvas的绘制与使用。

新的问题: 加入客户端缓存处理

考虑到客户的电脑性能不好,每次开启页面都要动态生成几百张图片,也挺浪费性能的。那就做存储吧。

存后端Or前端? 由于这些点位信息是动态配置的,有专门的页面进行管理,所以这些文本和底图会有改变的可能。这种情况把图片存在后端只能手动去控制生成图片。

IndexedDB存储

大量图片,体积是很大的,Storage存不了这么多,只能放到WebSQLIndexedDB,WebSQL已经不维护了,那就放在IndexedDB吧。但原生API不太方便,我们可以用localforage库来存取缓存,修改后代码为

//绘制图片:原来的图片+绘制的name的文字

const singleDigitPins = {}
async function getImageByCache (name) {
    const key = "type1-" + name// 唯一标识,不同图层需要设置不一样

    let image = singleDigitPins[key]
    if (image) {
      return image // 当前页面变量有记录
    }

    image = await localforage.getItem(key)
    if (image) {
      singleDigitPins[key] = image
      return image // 浏览器客户端缓存有记录
    }

    image = await createImage(name) // 生成图片
    singleDigitPins[key] = image // 记录到当前页面变量,未刷新页面时可直接使用
    localforage.setItem(key, image) // 记录到浏览器客户端缓存,刷新页面后也可以继续复用

    return image
}

async function createImage(name) {
  const image = await Cesium.Resource.fetchImage({ url: "img/marker/di3.png" })

  const canvas = document.createElement("canvas")
  canvas.width = image.width
  canvas.height = image.height

  const ctx = canvas.getContext("2d")
  ctx.drawImage(image, 0, 0, image.width, image.height)
  ctx.font = "bold 20px Arial"
  ctx.textAlign = "center"
  ctx.textBaseline = "bottom"
  ctx.fillStyle = "#FFF"
  ctx.fillText(name, 220, 70)
  return canvas.toDataURL("image/png")
}

//创建图标对象
function addBillboardEntity(bsm, name, buildingType, position) {
  var graphic = new mars3d.graphic.BillboardEntity({
    id: bsm,
    name,
    position: position,
    style: {
      image: await getImageByCache(name), //生成图片
      width: 300,
      height: 140,
      pixelOffset: new Cesium.Cartesian2(90, -25), // 偏移量
      distanceDisplayCondition: new Cesium.DistanceDisplayCondition(10.0, 2000.0),
      disableDepthTestDistance: 100.0,
      scaleByDistance: new Cesium.NearFarScalar(500, 1, 1400, 0.0),
      translucencyByDistance: new Cesium.NearFarScalar(500, 1, 1400, 0.0),
    },
    attr: { poiType: "buildingPOI", name, buildingType, bsm, position },
  });
  graphicLayer.addGraphic(graphic);
}

最后: 在添加BillboardEntity的时候先查询库里对应的bsm存不存在,若存在,判断各类信息是否一致,一致的话使用存储的base64图片作为BillboardEntityimage;若不存在或者各类信息不一致,重新生成图片并存入库,再作为BillboardEntityimage

结语

至此,Mars3D加载大量Label实体时卡顿的问题基本解决,进入页面也能达到秒开的速度。