优化 cesium 界面广告牌(billboard)数据量大于 10w +时,地图加载缓慢、卡顿、加载完成后浏览器严重卡顿甚至崩溃问题

前言:

项目之前的设计,billboard 广告牌是绑在 entityCollection 集合下的,为了能够在获取单个实体(entity)时能够获取更多数据信息(entity 能够注入除了它本身的属性之外的属性信息)
entityCollection 集合加上集群聚合功能,数据量临界点在 3w~4w 左右,就会出现界面卡顿。fps 低于 20 并且波动很大,延迟保持在 100ms 左右。数据量低于临界点时,entity 的方式呈现页面还是比较奈斯的
当数据量大于 10w+时,基本上 fps 处于 0-5,延迟大于 200ms,加载数据时延迟直接飙升几千都可能出现,同时(entityCollection 的)数据量过大直接导致浏览器崩溃无法加载

解决方法(primitives 原语集合)

注:

尝试过很多优化的方法,抛开后台接口数据传递处理的优化,只针对前端 cesium 界面的所有优化方法中,记录我找到的效果最好,并且在衔接后续已经完成的其他功能方面开销最小的优化方式
该方法也适合界面显示大量的 pointPrimitiveCollection(点集合)、labelCollection(label 集合)造成的界面卡顿同理可得嘛
我使用的是显示 billboardCollection 广告牌集合

参考方向

前往 primitives 原语集合官网文档

前往 官网相关例子

前往 实现 primitiveCluster 原语集群参考博客

优化方法:primitiveCollection 原语集合与 primitiveCluster 原语集群结合使用优化广告牌显示

在不需要聚合集群这一项功能的情况下,只使用 primitiveCollection 其实就能够完美的解决广告牌 10w+造成的界面卡顿崩溃等问题。建议是不需要聚合功能时,就不要添加 primitiveCluster 原语集群来处理优化。因为在聚合的方法,会监听摄像机的改变事件时刻改变聚合数量状态,反而会出现卡顿情况。应项目需求,添加聚合功能!针对聚合卡顿我做了加定时器的优化处理下面也会贴出来

primitiveCollection 原语集合

primitiveCollection 使用例子

广告牌集合添加代码(如下),其他的集合如 point、label,官方有文档都大同小异

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"
});

此时往 billboardCollection 中添加的 billboard 就会直接呈现在界面上,并且能够轻松应对 10w+的数据量。对比之前的添加方式(如下)效果很明显

// 之前的添加方式
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);

如果你只需要优化数据量大导致界面卡顿崩溃等问题,不用实现聚合功能,到此就完全 OK 了

primitiveCollection 原语集群

primitiveCollection 的聚合功能原生官方并没有提供,在官方文档中只提供了 EntityCluster 方法,来对 entityCollection 集合进行聚合操作。通过 EntityCluster 方法聚合时需要配合 datasource 对象使用,因为在原生的 datasource 对象自身有 clustering 属性(跳转 之前我写的 EntityCluster 聚合博客)。
由于我们直接使用的 Primitive 方式将 billboard 添加到地图中,就跳过了 datasource 的步骤。因此我们需要自己来定义一个 PrimitiveCluster 方法来创建一个 cluster 对象,针对原语集合进行聚合,结合其他博主文档提供的方法,PrimitiveCluster.js 具体实现方法总结如下

往 cesium 的包文件或者依赖文件中添加 PrimitiveCluster 方法

1.添加的路径:

1:npm 包中----- node_modules\cesium\Source\DataSources\PrimitiveCluster.js
2:引入外部文件方式 ---- Source\DataSources\PrimitiveCluster.js

2.复制同目录下 EntityCluster.js 内容到 PrimitiveCluster.js 中

3.文件内全局修改名称,EntityCluster -> PrimitiveCluster、 entityCluster -> primitiveCluster

4.屏蔽大概在 191 行左右 getScreenSpacePositions 方法中的代码块(EntityCluster 中 item.id 指向的就是 entity 实体对象,在 primitiveCollection 中 item.id 为 undefined 会包错)

/* 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;
} */

第 4 步时 如果你的业务需求在添加广告牌时需要为广告牌添加唯一的标识 id(如下添加方式),则可以不用屏蔽源代码,添加的 id 能够规避此处报错

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

5.在 PrimitiveCluster.js 的上级目录(node_modules\cesium\Source\Cesium.js)中找到入口文件 cesium.js,导入 PrimitiveCluster 方法

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

至此 PrimitiveCluster 方法就添加完成,可以直接通过 new Cesium.PrimitiveCluster()的方式来调用

PrimitiveCluster 方法来实现聚合

1.往 scene.primitives 中添加一个用作‘根’的原语集合 primitives

2.创建一个空 billboardCollection 广告牌集合

3.通过 PrimitiveCluster 方法创建一个 cluster 实例对象 primitiveCluster

4.将 primitiveCluster 添加到原语集合 primitives 中

5.配置 primitiveCluster 对象的基本参数(可以不配置有提供默认参数)

6.(重要*)将空 billboardCollection 广告牌集合赋予 primitiveCluster._billboardCollection,手动添加聚合内容

提一下: label、point 集合添加方式一致

primitiveCluster._labelCollection;
primitiveCluster._pointCollection;

7.(重要*)调用_initialize 方法初始化 cluster 实例的事件监听

8.之后就与 datasource 聚合方式的.then 方法一致,只需要将 dataSource.clustering.clusterEvent.addEventListener 换成 primitiveCluster.clusterEvent.addEventListener

如下:

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处理方式一致
});

按照上面的方式完成聚合后,往 billboardCollection 集合中添加 billboard 广告牌就会在页面呈现出来并且聚合显示。但是数据量 10w+的情况下,在处理摄像机视角改变的监听事件时会出现卡顿问题。下面贴一下简单优化的方法

优化 PrimitiveCluster 卡顿问题

在 PrimitiveCluster.js 的_initialize 方法中,可以看到原方法使用 createDeclutterCallback 方法创建了一个回调方法,并将这个回调方法添加到了 scene.camera.changed 监听中。因此只要 scene.camera 视角改变,就会执行聚合的处理逻辑方法返回两个参数 clusteredEntities 与 cluster

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

所以只需要在_initialize 方法加一个防抖的定时器,让它事件处理频率降低就能达到优化的效果。同时暴露 delay 时间参数可以在实例化后进行配置改变

//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);});
};

到此上文的内容就是关于优化 cesium 界面广告牌(billboard)数据量大于 10w +时,地图加载缓慢、卡顿、加载完成后浏览器严重卡顿等问题的方法思路,下面贴代码记录。

功能代码记录

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 stylinglet pixelRange = primitiveCluster.pixelRange;primitiveCluster.pixelRange = 0;primitiveCluster.pixelRange = pixelRange;_.myPrimitiveCluster = primitiveCluster;}this.myBillboardCollection = billboardCollection;// start with custom stylecustomStyle();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();

在执行commomSiteTookit.init(viewer)后,加载数据主要的操作在 load 方法中,load 返回的 billboardCollection,可以动态的添加 billboard 数据,直接呈现在界面,代码如下。

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: undefinedheight: 38, // default: undefinedposition: Cesium.Cartesian3.fromDegrees(list[i].longitude,list[i].latitude,0),id: list[i].id + ""});
}

优化 cesium 界面广告牌(billboard)数据量大于 10w +时,地图加载缓慢、卡顿、加载完成后浏览器严重卡顿甚至崩溃问题相关推荐

  1. MongoDB数据量大于2亿后遇到的问题 及原因分析

    MongoDB数据量大于2亿后遇到的问题 及原因分析 一.数据增长情况 每月增长量最大达到了1.9亿,每天增长约300W-500W     (增长数据具体可看页尾) 二.遇到的情况及解决方法 1.数据 ...

  2. 提高大数据量并发访问时效率

    最近在做windows服务方面的开发,主要用它来解决A服务和其他服务发送数据失败后,重新发送的问题. 为了提高大数据量并发访问时效率问题,要在多台服务器上安装服务并采用多线程,就像是超市的收银,利用多 ...

  3. bootstrap 树形表格渲染慢_bootstrap-table-treegrid数据量较大时渲染太久了

    bootstrap-table-treegrid数据量较大时渲染太久了 森姐姐 2019-10-23 16:48:51 2260 收藏 2 分类专栏: 遇到的问题 最后发布:2019-10-23 16 ...

  4. POI3.8解决导出大数据量excel文件时内存溢出的问题

    POI3.8解决导出大数据量excel文件时内存溢出的问题 参考文章: (1)POI3.8解决导出大数据量excel文件时内存溢出的问题 (2)https://www.cnblogs.com/feng ...

  5. 两组数据量相对大时,如何高效进行比对

    前言 前阵子项目因业务需要,要对接兄弟部门的用户数据,因为兄弟部门并不提供增量用户数据接口,每次只能从兄弟部门那边同步全量用户数据.全量的用户数据大概有几万条.因为是全量数据,因此我们这边要做数据比对 ...

  6. android预加载布局,Android 懒加载优化

    目录介绍 1.什么是懒加载 1.1 什么是预加载 1.2 懒加载介绍 1.3 懒加载概括 2.实际应用中有哪些懒加载案例 2.1 ViewPager+Fragment组合 2.2 分析源码 3.Vie ...

  7. 28 Java类的加载机制、什么是类的加载、类的生命周期、加载:查找并加载类的二进制数据、连接、初始化、类加载器、双亲委派模型、自定义类加载器

    28Java类的加载机制 28.1.什么是类的加载 28.2.类的生命周期 28.2.1.加载:查找并加载类的二进制数据 28.2.2.连接 28.2.3.初始化 28.3.类加载器 28.4.类的加 ...

  8. 电脑开机进不了系统卡在加载界面怎么办?

    电脑开机进不了系统卡在加载界面怎么办?有用户电脑弹出需要进行系统更新,不小心点到了系统更新的选项.因为自己不想进行系统更新,所以马上将电脑关机了.但是关机之后却发现系统一直卡在开机的界面中,无法进入桌 ...

  9. 一个简单的页面加载管理类(包含加载中,加载失败,数据为空,加载成功)

    在最近公布的比赛框架中,发现了页面加载管理类,觉得挺有用的,所以做个简单的笔记. 什么是页面加载管理类呢?(大佬可直接跳过翻看实现过程) 如果能有这个问题,那么很好,哈哈哈,你和我一样,刚开始都挺疑惑 ...

最新文章

  1. R语言sys方法:sys.chmod函数改变指定文件的权限、Sys.Date函数返回系统的当前日期、Sys.time函数返回系统的当前时间
  2. 用 CentOS 做一个 BGP 路由器
  3. 你绝对能懂的“机器学习”(二)
  4. 【PHP】__autoload()魔术方法与spl_autoload_register
  5. boost::mp11::mp_sort相关用法的测试程序
  6. android和flask交互,java - 当我从Android向Flask Web服务发送参数时,如何解决“ SSL库故障”? - 堆栈内存溢出...
  7. canvas 粒子效果 - 手残实践纪录
  8. 思科交换机配置试题_【干货】思科交换机路由器怎么配置密码?
  9. 学会提问,你就成功了一大半!
  10. java类可视化doxygen_安装doxygen(一个自动文档生成工具)+Graphviz图形可视化软件...
  11. read H264 Nal
  12. matlab快速实现线性规划求解
  13. spss java vm_如何解决spss无法创建java虚拟机的问题
  14. 遍地是钱,为什么捡不到?
  15. python中delay__python delay函数
  16. 宠物王国6java变态版,宠物王国外传999999级变态版
  17. 面对换领导或空降管理者,该怎么办?
  18. 51单片机入门学习------环境搭建
  19. python实操实例100例_趣学Python算法100例
  20. Vue源码之渲染watcher

热门文章

  1. 使用SVM+Word2Vec 解决外卖的好评坏评分类问题
  2. Mysql索引优化详解
  3. typora设置首行缩进
  4. grx1660linux看视频掉帧,游戏卡顿疯狂掉帧?买它们重获流畅体验
  5. 解决Xampp中mysql无法启动的问题
  6. 一键就能去除视频水印,简单实用,重点是免费的哦!
  7. 准时下班,也是一种能力
  8. Bound Found
  9. Android Studio —— 弹窗
  10. 【elabsim 示波器】基本使用以及示波器按键面板上主要按键含义的介绍