Optimize the cesium interface billboard (billboard) data volume is greater than 10w +, the map loads slowly, freezes, and the browser freezes or even crashes after loading

Optimize the cesium interface billboard (billboard) data volume is greater than 10w +, the map loads slowly, freezes, and the browser freezes or even crashes after loading

Foreword:

In the previous design of the project, the billboard billboard was bound under the entityCollection collection. In order to obtain more data information when obtaining a single entity (entity) (entity can inject attribute information other than its own attributes) the entityCollection collection is
added With the cluster aggregation function, the critical point of the data volume is around 3w~4w, and the interface will freeze. fps is below 20 and fluctuates a lot, latency stays around 100ms. When the amount of data is below the critical point, the way the entity presents the page is still relatively nice.
When the amount of data is greater than 10w+, the fps is basically 0-5, and the delay is greater than 200ms. When loading data, the delay may directly increase by several thousand. At the same time The large amount of data (of entityCollection) directly causes the browser to crash and fail to load

Solution (primitives collection of primitives)

Note:

I have tried many optimization methods, leaving aside the optimization of background interface data transfer processing, only for all optimization methods of the front-end cesium interface, the record I found has the best effect, and the optimization with the least overhead in connecting other functions that have been completed in the follow-up Method
This method is also suitable for displaying a large number of pointPrimitiveCollection (point collection) and labelCollection (label collection) on the interface
.

reference direction

Go to the official website documentation of primitives collection of primitives

Go to the official website related examples

Go to implement primitiveCluster primitive cluster reference blog

Optimization method: combine primitiveCollection primitive collection and primitiveCluster primitive cluster to optimize billboard display

In the absence of the need for the function of aggregation clusters, only using primitiveCollection can perfectly solve the problems of interface freezes and crashes caused by billboards 10w+. The recommendation is not to add a primitiveCluster primitive cluster to handle optimization when aggregation is not needed. Because in the aggregation method, the change event of the camera will be monitored to change the state of the aggregation quantity at any time, but there will be a freeze situation. According to the needs of the project, add the aggregation function! For the aggregation freeze, I made an optimization process of adding a timer and will post it below

primitiveCollection primitive collection

primitiveCollection usage example

Billboard collection add code (as follows), other collections such as point, label, official documents are similar

const billboardCollection = viewer.scene.primitives.add(
  new Cesium.BillboardCollection()
);

billboardCollection.add({
    
    
  position: Cesium.Cartesian3.fromDegrees(114.49, 41.23, 0),
  width: 38,
  height: 38,
  image: "xxxxx"
});

At this time, the billboard added to the billboardCollection will be directly displayed on the interface, and can easily handle the data volume of 100,000+. Compared with the previous addition method (as follows), the effect is obvious

// 之前的添加方式
const entityCollection = new Cesium.EntityCollection();

const billboard = new Cesium.BillboardGraphics({
    
    
  width: 38,
  height: 38,
  image: "xxxxx"
});
const entity = new Cesium.Entity({
    
    
  position: Cesium.Cartesian3.fromDegrees(114.49, 41.23, 0),
  billboard: billboard,
  s1: "xxx",
  s2: "xxx",
  s3: "xxx",
  s4: "xxx",
  s5: "xxx"
});
entityCollection.add(entity);

If you only need to optimize the large amount of data that causes the interface to freeze and crash, you don’t need to implement the aggregation function, and it’s completely OK now

primitiveCollection primitive cluster

The aggregation function of primitiveCollection is not provided by the native official, and only the EntityCluster method is provided in the official document to perform aggregation operations on the entityCollection collection. Aggregation through the EntityCluster method needs to be used with the datasource object, because the native datasource object itself has a clustering attribute (the EntityCluster aggregation blog I wrote before the jump ).
Since we directly use the Primitive way to add billboard to the map, we skip the step of datasource. Therefore, we need to define a PrimitiveCluster method to create a cluster object, aggregate the primitive collection, and combine the methods provided by other blogger documents. The specific implementation methods of PrimitiveCluster.js are summarized as follows

Add the PrimitiveCluster method to the cesium package file or dependency file

1. Added path:

1: In the npm package ----- node_modules\cesium\Source\DataSources\PrimitiveCluster.js
2: Importing external files ---- Source\DataSources\PrimitiveCluster.js

2. Copy the content of EntityCluster.js in the same directory to PrimitiveCluster.js

3. Globally modify the name in the file, EntityCluster -> PrimitiveCluster, entityCluster -> primitiveCluster

4. Shield the code block in the getScreenSpacePositions method at about line 191 (item.id in EntityCluster points to the entity entity object, and item.id in primitiveCollection is undefined, which will cause errors)

/* var canClusterLabels =
  primitiveCluster._clusterLabels && defined(item._labelCollection);
var canClusterBillboards =
  primitiveCluster._clusterBillboards && defined(item.id._billboard);
var canClusterPoints =
  primitiveCluster._clusterPoints && defined(item.id._point);
if (canClusterLabels && (canClusterPoints || canClusterBillboards)) {
  continue;
} */

In step 4, if your business needs to add a unique identification id to the billboard when adding the billboard (as shown below), you don’t need to block the source code, and the added id can avoid reporting errors here

billboardCollection.add({
    
    
  id: "xxx",
  position: Cesium.Cartesian3.fromDegrees(114.49, 41.23, 0),
  width: 38,
  height: 38,
  image: "xxxxx"
});

5. Find the entry file cesium.js in the parent directory of PrimitiveCluster.js (node_modules\cesium\Source\Cesium.js), and import the PrimitiveCluster method

export {
    
     default as PrimitiveCluster } from "./DataSources/PrimitiveCluster.js";

So far the PrimitiveCluster method has been added and can be called directly through new Cesium.PrimitiveCluster()

PrimitiveCluster methods to implement aggregation

1. Add a set of primitives used as the 'root' to scene.primitives

2. Create an empty billboardCollection billboard collection

3. Create a cluster instance object primitiveCluster through the PrimitiveCluster method

4. Add primitiveCluster to primitives collection primitives

5. Configure the basic parameters of the primitiveCluster object (default parameters can be provided without configuration)

6. (Important*) Assign an empty billboardCollection billboard collection to primitiveCluster._billboardCollection, and manually add aggregation content

To mention: label and point sets are added in the same way

primitiveCluster._labelCollection;
primitiveCluster._pointCollection;

7. (Important*) Call the _initialize method to initialize the event listener of the cluster instance

8. After that, it will be consistent with the .then method of the datasource aggregation method, only need to replace dataSource.clustering.clusterEvent.addEventListener with primitiveCluster.clusterEvent.addEventListener

as follows:

const primitives = viewer.scene.primitives.add(
  new Cesium.PrimitiveCollection()
);
const billboardCollection = new Cesium.BillboardCollection();

const primitiveCluster = new Cesium.PrimitiveCluster();
primitives.add(primitiveCluster);
primitiveCluster.enabled = true; //开启聚合功能
primitiveCluster.pixelRange = 15; //范围
primitiveCluster.minimumClusterSize = 2; //最小聚合数量
primitiveCluster._billboardCollection = billboardCollection;
primitiveCluster._initialize(viewer.scene);

primitiveCluster.clusterEvent.addEventListener(function(
  clusteredEntities,
  cluster
) {
    
    
  // ... 处理聚合显示广告牌代码块与dataSource处理方式一致
});

After the aggregation is completed in the above way, adding billboard billboards to the billboardCollection collection will be displayed on the page and aggregated. However, in the case of a data volume of 10w+, there will be a lag problem when processing the monitoring event of the camera viewing angle change. Here is a simple optimization method

Optimize the PrimitiveCluster lag problem

In the _initialize method of PrimitiveCluster.js, you can see that the original method uses the createDeclutterCallback method to create a callback method and add this callback method to the scene.camera.changed listener. Therefore, as long as the view angle of scene.camera changes, the aggregation processing logic method will be executed and the two parameters clusteredEntities and cluster will be returned.

primitiveCluster.clusterEvent.addEventListener(function(
  clusteredEntities,
  cluster
) {
    
    
  // ... 处理聚合显示广告牌代码块与dataSource处理方式一致
});

So you only need to add an anti-shake timer in the _initialize method to reduce the event processing frequency to achieve the optimization effect. At the same time, the delay time parameter is exposed and can be configured after instantiation

//1.PrimitiveCluster构造函数中添加_delay参数
this._delay = defaultValue(options.delay, 800)

//2.在PrimitiveCluster.prototype拦截器Object.defineProperties方法中添加_delay的访问以及设置方法
delay: {
    
    
  get: function () {
    
    
    return this._delay;
  },
  set: function (value) {
    
    
    this._delay = value;
  },
},

// 3._initialize方法改造
PrimitiveCluster.prototype._initialize = function(scene) {
    
    
  this._scene = scene;
  var cluster = createDeclutterCallback(this);
  this._cluster = cluster;
  var _t = null;
  const _self = this;
  this._removeEventListener = scene.camera.changed.addEventListener(function(amount) {
    
    
    if (_t) {
    
    
      clearTimeout(_t);
      _t = null;
    }
    _t = setTimeout(() => {
    
    
      cluster(amount);
    }, _self._delay);
  });
};

So far, the above content is about the method of optimizing the problem of slow loading of the cesium interface billboard (billboard) when the data volume is greater than 10w +, the map freezes, and the browser freezes severely after loading. The code record is posted below.

function code record

import * as Cesium from "cesium/Cesium";
import defaultValue from "./core/defaultValue";

/**
 * @_v 引入外部创建的Viewer实例(new Cesium.Viewer(...))
 * @myPrimitives 原语集合,可以包含页面显示的pointPrimitiveCollection、billboardCollection、labelCollection、primitiveCollection、primitiveCluster
 * @myPrimitiveCluster 自定义原语集群
 * @myBillboardCollection 广告牌集合(站点显示的内容数据)
 *
 * @desc 使用primitiveCollection原语集合与primitiveCluster原语集群,处理地图界面显示广告牌billboard数量 > 10w 级时,界面卡顿,浏览器崩溃等问题
 */
class CommomSiteTookit {
    
    
  static _v = null;
  myPrimitives = null;
  myPrimitiveCluster = null;
  myBillboardCollection = null;

  constructor() {
    
    }

  /**
   * @desc 使用commomSiteTookit实例前,必须先初始化该实例的_v对象
   */
  init(viewer) {
    
    
    this._v = viewer;
  }

  /**
   * @param [options] 具有以下属性的对象
   * @param [options.delay=800] 防抖处理定时器的time
   * @param [options.enabled=true] 是否启用集群
   * @param [options.pixelRange=15] 用于扩展屏幕空间包围框的像素范围
   * @param [options.minimumClusterSize=2] 可集群的屏幕空间对象的最小数量
   *
   * @desc 处理原语集合,并实现聚合集群功能方法
   * @return billboardCollection集合,可直接往集合里添加广告牌billboard,呈现在页面上
   */
  load(options = {
     
     }) {
    
    
    let billboardCollection = new Cesium.BillboardCollection();

    if (Cesium.defined(this.myPrimitives)) {
    
    
      this._v.scene.primitives.remove(this.myPrimitives);
    }
    this.myPrimitives = this._v.scene.primitives.add(
      new Cesium.PrimitiveCollection()
    );

    const primitiveCluster = new Cesium.PrimitiveCluster();
    this.myPrimitives.add(primitiveCluster);
    primitiveCluster.delay = defaultValue(options.delay, 800);
    primitiveCluster.enabled = defaultValue(options.enabled, true);
    primitiveCluster.pixelRange = defaultValue(options.pixelRange, 15);
    primitiveCluster.minimumClusterSize = defaultValue(
      options.minimumClusterSize,
      2
    );
    primitiveCluster._billboardCollection = billboardCollection;
    primitiveCluster._initialize(this._v.scene);

    let removeListener;
    let pinBuilder = new Cesium.PinBuilder();
    /* 定义广告牌 fromText(显示文字,颜色,大小) */
    let pin50 = pinBuilder.fromText("50+", Cesium.Color.RED, 40).toDataURL();
    let pin40 = pinBuilder.fromText("40+", Cesium.Color.ORANGE, 40).toDataURL();
    let pin30 = pinBuilder.fromText("30+", Cesium.Color.YELLOW, 40).toDataURL();
    let pin20 = pinBuilder.fromText("20+", Cesium.Color.GREEN, 40).toDataURL();
    let pin10 = pinBuilder.fromText("10+", Cesium.Color.BLUE, 40).toDataURL();
    /* 数量小于十个的聚合广告牌 */
    let singleDigitPins = new Array(8);
    for (let i = 0; i < singleDigitPins.length; ++i) {
    
    
      singleDigitPins[i] = pinBuilder
        .fromText("" + (i + 2), Cesium.Color.VIOLET, 40)
        .toDataURL();
    }

    const _ = this;
    function customStyle() {
    
    
      if (Cesium.defined(removeListener)) {
    
    
        removeListener();
        removeListener = undefined;
      } else {
    
    
        removeListener = primitiveCluster.clusterEvent.addEventListener(
          function(clusteredEntities, cluster) {
    
    
            cluster.label.show = false;
            cluster.billboard.show = true;
            cluster.billboard.id = cluster.label.id;
            cluster.billboard.verticalOrigin = Cesium.VerticalOrigin.BOTTOM;
            /* 根据站点(参数)的数量给予对应的广告牌  */
            if (clusteredEntities.length >= 50) {
    
    
              cluster.billboard.image = pin50;
            } else if (clusteredEntities.length >= 40) {
    
    
              cluster.billboard.image = pin40;
            } else if (clusteredEntities.length >= 30) {
    
    
              cluster.billboard.image = pin30;
            } else if (clusteredEntities.length >= 20) {
    
    
              cluster.billboard.image = pin20;
            } else if (clusteredEntities.length >= 10) {
    
    
              cluster.billboard.image = pin10;
            } else {
    
    
              cluster.billboard.image =
                singleDigitPins[clusteredEntities.length - 2];
            }
          }
        );
      }
      // force a re-cluster with the new styling
      let pixelRange = primitiveCluster.pixelRange;
      primitiveCluster.pixelRange = 0;
      primitiveCluster.pixelRange = pixelRange;
      _.myPrimitiveCluster = primitiveCluster;
    }
    this.myBillboardCollection = billboardCollection;
    // start with custom style
    customStyle();
    return billboardCollection;
  }

  /**
   * @params enable bool值控制开启或关闭集群
   * @desc 控制集群生效与否
   */
  enableCluster(enable) {
    
    
    if (Cesium.defined(this.myPrimitiveCluster)) {
    
    
      this.myPrimitiveCluster.enabled = enable;
    }
  }

  /**
   * @params id 站点ID
   * @return 返回可操作的广告牌[siteBillboard.image = 'xxxx']
   * @desc 根据id在集合中获取指定站点广告牌
   */
  getSiteBillboardById(id) {
    
    
    if (!Cesium.defined(this.myBillboardCollection)) return undefined;
    const _b = this.myBillboardCollection;
    const l = _b.length;
    let siteBillboard = undefined;
    for (let i = 0; i < l; i++) {
    
    
      if (id == _b.get(i).id) {
    
    
        siteBillboard = _b.get(i);
        break;
      }
    }
    return siteBillboard;
  }

  /**
   * @desc 删除所有站点广告牌
   */
  removeAll() {
    
    
    if (Cesium.defined(this.myPrimitives)) {
    
    
      this._v.scene.primitives.remove(this.myPrimitives);
    }
  }

  /**
   * @params show bool值 控制显示或隐藏
   * @desc 隐藏或显示所有站点广告牌
   */
  showStatus(show = true) {
    
    
    this.myPrimitives.show = show;
  }

  /**
   * @desc 根据id删除指定站点广告牌
   */
  remove(id) {
    
    
    const billboard = this.getSiteBillboardById(id);
    billboard && this.myBillboardCollection.remove(billboard);
  }

  /**
   * @desc 销毁(目前退出页面时直接viewer销毁)
   */
  destroy() {
    
    
    this.myPrimitives = null;
    this.myPrimitiveCluster = null;
    this.myBillboardCollection = null;
    // this._v.scene.primitives.destroy()
  }
}

export default new CommomSiteTookit();

After commomSiteTookit.init(viewer) is executed, the main operation of loading data is in the load method. The billboardCollection returned by load can dynamically add billboard data and directly present it on the interface. The code is as follows.

const list = ['10w+数据']
const l = list.length
const data = commomSiteTookit.load({
    
    
  enabled: true,
  delay: 1200,
  pixelRange: 20
});
for (let i = 0; i < l; i++) {
    
    
  data.add({
    
    
    image: `xxxx`,
    scaleByDistance: new Cesium.NearFarScalar(1.5e2, 1, 1.5e7, 0.2),
    width: 38, // default: undefined
    height: 38, // default: undefined
    position: Cesium.Cartesian3.fromDegrees(
      list[i].longitude,
      list[i].latitude,
      0
    ),
    id: list[i].id + ""
  });
}

Guess you like

Origin blog.csdn.net/weixin_42508580/article/details/126927483