问题说明
在地图开发中, 当地图中绘制大量的标记点后, 无论是拖动或者缩放, 都会感觉到明显的卡顿现象.(一般超过 800 个点后就比较明显了). 在平时的工作业务中, 由于公司的实时监控页面需要展现 5000-20000 车辆的实时定位跟踪, 特别是切换到车辆密集的港口码头卡顿现象非常严重(如下图), 看起来非常难看, 用户体验也非常差. 在此写下一些开发优化中的心得体会(本文中使用的是高德地图为参考).
解决思路
首先我们应该将地图上的所有覆盖物 (包括 Marker,Icon,Text,Polygon) 分组添加到多个 OverlayGroup 中对每一类统一管理. 部分覆盖物默认隐藏, 需要时才展现.
不要将大量的覆盖物信息都直接挂载在地图对象上, 可以按需加载, 减少地图在移动后重新渲染的工作量
合理的使用覆盖物聚合(聚合虽然会减少卡顿, 但是覆盖物数量太多依旧会比较卡)
解决方法
首先, 添加一个地图设置勾选面板, 将除车辆点标记以外的所有覆盖物按类型分类, 添加到一个个单独的 OverlayGroup 中默认不挂载在地图上, 在勾选时才调用 OverlayGroup.setMap(map)方法添加到地图上. 去掉了这些不必要覆盖物, 首次进入时立马清爽了许多.
尽管去掉了各类网点, 文字, 等覆盖物, 但是我们需要渲染的车辆标记点依旧是非常多的. 在此基础上, 我们采用按屏幕加载, 筛选出屏幕内的点, 屏幕外的标记点不渲染, 监控每次地图的 moveend,zoomend,resize, 每次的改变后重新计算屏幕内的标记点, 当屏幕内标记点较少, 比如 200 以下(这个随个人需要定), 直接绘制在地图上. 当屏幕内标记点比较多, 大于 200, 调用聚合方法, 将标记点聚合后绘制在地图上.
参考代码
- /** 监听地图拖拽, 放大事件, 根据屏幕范围动态渲染点位 */
- reloadMarks(){
- AMap.event.addListener(this.map,'moveend',()=> {
- this.computeMarkers();
- })
- AMap.event.addListener(this.map,'zoomend',()=> {
- this.computeMarkers();
- })
- AMap.event.addListener(this.map,'resize',()=> {
- this.computeMarkers();
- })
- },
- /** 根据当前屏幕范围帅选 marker */
- computeMarkers(){
- this.newViewData={};
- // 获取当前视图范围
- let now_bounds = this.map.getBounds();
- // 遍历车辆数据, 为了减少后台传入的重复数据, 我的车辆信息列表一直使用对象保存
- for(let sel_deviceNo in this.carDataObj0){
- let item=this.carDataObj0[sel_deviceNo];
- // 判断当前点的坐标是否存在于视图内
- if(now_bounds.contains(item.gcj02_coords.split(','))){
- // 将当前屏幕内的视图点保存
- this.newViewData[sel_deviceNo] = item;
- }
- }
- this.renderMarker();
- now_bounds=null;
- },
- /** 创建聚合 **/
- createCluser(markerArr) {
- AMap.plugin(['AMap.MarkerClusterer'], () => {
- this.cluster = new AMap.MarkerClusterer(this.map, markerArr, {
- gridSize: 80,
- renderCluserMarker,
- minClusterSize: 1,
- maxZoom: 18
- });
- })
- },
- /** 将 markers 渲染到 map 上 */
- renderMarker(){
- // 判断目前是否有当前覆盖物组
- !this.overLayGroup && this.overLayGroup = new AMap.OverlayGroup([]);
- let MarkerAddArr = [];
- for(let sel_deviceNo in this.newViewData){
- /** 我的所有创建的 Marker 点对象都是长期存在于 this.markerObj 中, 并不销毁, 每次数据更
- 新后调用 marker 对象的 setPosition(),setAngle(),setIcon()等方法改变状态, 再筛选出
- 需要挂载上的一起加到地图上 **/
- if(this.markerObj[sel_deviceNo]){
- MarkerAddArr.push(this.markerObj[sel_deviceNo]);
- }
- }
- this.overLayGroup.clearOverlays();
- // 超过 200 个点则开启聚合模式 且在聚合模式下不渲染面板展示点
- if(MarkerAddArr.length>=200){
- if(this.cluster){
- this.cluster.clearMarkers();
- this.cluster.setMarkers(MarkerAddArr);
- }else{
- this.createCluser(MarkerAddArr);
- }
- }else{
- if(this.cluster){
- this.cluster.clearMarkers();
- }
- this.overLayGroup.addOverlays(MarkerAddArr);
- this.overLayGroup.setMap(this.map);
- }
- MarkerAddArr=null;
- },
复制代码
代码直接从业务中复制的, 耦合度比较高, 还请见谅. 做完了标记点按屏幕分组加载之后, 我们在添加一个简单的防抖方法, 防止连续的拖动或者缩放导致 computeMarkers 方法被触发, 减少方法调用次数
- const debounce = (fn, wait=500) =>{
- return function() {
- clearTimeout(fn.timer)
- fn.timer = setTimeout(fn.bind(this, ...arguments), wait)
- }
- }
复制代码
完成后效果如下: 减少了地图的计算与渲染, 每次只计算视野内的点数, 因此总数的大小不会影响地图性能, 当视野内超过 200 个点都会聚合, 200 以下时显示.
平时写文章比较少, 文笔很差, 还请多多见谅, 谢谢.
来源: https://juejin.im/post/5b7cb5716fb9a01a143fd0f7