高德地图API之海量标注

标签: 无 分类: 未分类 创建时间:2022-09-19 00:49:19 更新时间:2025-01-17 10:39:24

前言

这篇文章主要探讨了使用高德API加载海量标注点的示例,并针对发现的问题,比如标注点文字和图片共同存在的情况下产生的bug问题复现及其解决过程。

官方的海量点 点示例中,展示了如何增加3万个点,但是这个有一个细节,就是没有底图的label标注。在我自己的项目中,虽然标注点不是很多,只有五百多个,但还是会出现卡顿,我并不觉得是我自己的代码问题,可能这是一个官方的bug。

为了验证我的猜想,我在示例 中添加了标注,导致示例特别特别的卡,当不显示标注的时候,倒是可以拖动地图,一旦显示了标注,结果就会连标注都无法拖动了,无论是火狐还是谷歌浏览器,都会出现这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 为了证明数据量的问题,我把生成的数据的量降到了1500
let _events = events[0].events.slice(0,1500);

// 加载标注图层
let labelsLayes = new Loca.LabelsLayer({
collision: false,
opacity: 1,
zIndex: 120,
allowCollision: false,
zooms: [19, 20],
});
labelsLayes.setSource(geo);
labelsLayes.setStyle({
text: {
content: "测试",
offset:[0,-5],
style:{
fillColor:"#fff",
fold:true,
},
},
});
loca.add(labelsLayes);

2.测试方法

官方老师给出的答案,是 Loca.LabelsLayer 封装了 AMap.LabelsLayer,最多承载5000个label,数量过大,就会出现卡顿。为此,我专门做了测试,当时我的测试效果并没有老师给的答案那么明朗,同样还是非常的卡。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 生成测试数据
function getData() {
var list = [];
for (let i = 0; i < 500; i++) {
list.push({
"type": "Feature",
"properties": {
"name": i,
},
"geometry": {
"type": "Point",
"coordinates": [Number((115.601 + i * 0.002).toFixed(5)), Number((40.32 + i * 0.002).toFixed(5))]
}
});
}

var data = {
"type": "FeatureCollection",
"features": list,
};
return data;
}
var data2 = getData();
var geo2 = new Loca.GeoJSONSource({
data: data2,
});
var labelsLayer = new Loca.LabelsLayer({
collision: false,
opacity: 1,
zIndex: 120,
allowCollision: false,
});
labelsLayer.setSource(geo2);
labelsLayer.setStyle({
text: {
content: "测试",
offset:[0,-5],
style:{
fillColor:"#fff",
fold:true,
},
},
});
loca.add(labelsLayer);

我这里测试,同时打开两个页面,一个加了500个label,一个没有加label,非常明显的加了label的会卡,不加label的拖动起来非常的流畅。

后来按照老师的要求,进行了谷歌的性能记录,还打开了无痕模式,我虽然还不是很懂这个性能记录,我是这么理解的,每一个函数调用都要六七十毫秒,已经超过了浏览器渲染最小间隔16ms,自然就会产生卡顿的情况。

老师说管网的环境可能有问题,他本地测试看看。我自己把官网的例子拿到本地,修改了自己key,进行测试的时候,数量在1100左右就开始有了明显的卡顿了,到1500的时候,已经非常的慢了。

最后老师也没有办法了,说是会出一版性能优化,只能通过升级版本来解决了。我用的时候,是loca 2.0和js sdk 2.0

解决方案

根据老师的回答,还有我自己的测试,我总结出来了下面的几种方案。

1.关闭地图配置的 showLabel

后来我在标注和标注图层-海量点 这个示例里面,发现了奇怪的是,在这个示例中,没有卡顿现象的产生,添加如下内容,为30000个点添加标注,也没有卡顿的情况发生。

1
2
3
4
5
6
7
var curData = {
position: curPosition,
icon,
text:{
content:"测试"
}
};

直到我修改了地图的配置,将原先的 showLabel: false 改为了 showLabel: true,页面开始卡顿了,说明这个和 LabelsLayer 这个有冲突,目前只能测试到这个程度了。

2.设置 collision 和 allowCollision

还有一个点,就是我在代码中设置 collision: false 和 allowCollision: false, 关闭图标避让。也会导致页面卡,不知道是什么问题,需要改为 collision: true 和 allowCollision: true。这里在配置的时候,需要注意。

3.使用 AMap.LabelsLayer 而不是 Loca.LabelsLayer

还有一个点需要注意的地方,就是在使用 Loca.LabelsLayer 比使用 AMap.LabelsLayer 要卡,这是我的测试结果,不知道官方活着其他人是不是也是这种情况,因为官方说 Loca.Labelslayer是AMap.LabelsLayerd 封装,我觉得可能还是有性能的损失吧。

参考文章:
1.高德地图 web 端 JS API 遇到的坑及性能优化_YYCCMMKK的博客-程序员秘密 LabelMarker 部分点位在地图放大到一定程度后 不再触发click mouseover 等事件,极少量点位会出现这种情况,目测是某些点位附近可能有什么隐藏物吧,总之就是不触发,但是奇怪的是,同样的经纬度有时确是可以正确触发事件,也可能是LabelMarker 有问题。

4.动态创建和销毁label

这个思路的意思就是:

  • 通过将 海量标注 延迟加载(懒加载)的方式,加快页面首屏渲染速度
  • 检测 海量标注 中的数据项,判断其坐标是否在浏览器视口区域,进行分片渲染
  • 监听地图缩放及移动事件,先删除原标注图层,再根据第二步渲染新图层
  • 对 海量标注 中的公共部分进行提取,通过 海量点标记 的方式渲染,减少 DOM 节点数
  • 针对点击后高亮被选中的标注,单独添加图层进行叠加,减少第三步带来的延迟

主要代码就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
export class RfLayer{
constructor(){
this.map_=store.state.map;
this.loca_=store.getters.loca;
this.rfLabelsLayer=null; // 标注图层
this.rfLabelsMarkers=[]; // 标注点
this.init();
}
init(){
let map=this.map_;
// 监听地图缩放结束后的等级
let zoom=2;
let that=this;
function zoomFun(){
zoom = map.getZoom()
if (zoom >= 16) {
that.executeConditionRender()
}
}
map.on("zoomend",()=>{
zoom = map.getZoom()
if (zoom >= 16) {
that.executeConditionRender()
}
})
// 防抖
// map.on('zoomend',_debounce(zoomFun,1000));

// 监听地图中心点的位置变化
function moveFun(){
if (zoom >= 16) {
that.executeConditionRender()
}
}
// 防抖
// map.on('moveend', _debounce(moveFun,1000))
map.on('moveend',()=>{
if (zoom >= 16) {
that.executeConditionRender()
}
})

}
/**
* 执行条件渲染
*/
executeConditionRender(){
let map=this.map_;
let screenCoordinateRange = map.getBounds()
let northEast = [screenCoordinateRange.northEast.lng, screenCoordinateRange.northEast.lat];
let southEast = [screenCoordinateRange.southWest.lng, screenCoordinateRange.northEast.lat];
let southWest = [screenCoordinateRange.southWest.lng, screenCoordinateRange.southWest.lat];
let northWest = [screenCoordinateRange.northEast.lng, screenCoordinateRange.southWest.lat];
let rfdwd_data=store.state.rfdwd_data; // 数据内容
let pointList=rfdwd_data.features;
let screenPointList = pointList.filter(item => {
let coordinates=item.geometry.coordinates;
return AMap.GeometryUtil.isPointInRing(coordinates, [northEast, southEast, southWest, northWest])
})
console.log(screenPointList.length);
// 设置海量标注 具体代码在上面
this.drawRfdwdLabel(screenPointList)
}
/**
* 创建图标层和点图层
*/
drawRfdwdPoint(){
let loca=this.loca_;
let geojson=store.state.rfdwd_data;
let dataSource = new Loca.GeoJSONSource({
data: geojson,
});
// 创建圆点图层
let pointLayer = new Loca.PointLayer({
zIndex: 10,
opacity: 1,
visible: true,
blend: 'lighter',
zooms: [2, 16],
});

// 图层添加数据
pointLayer.setSource(dataSource);
// 设置样式
pointLayer.setStyle({
radius: 6,
color: 'rgba(42, 167, 184, 1)',
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.7)',
unit: 'px',
zIndex:20,
});
// 添加到地图上
loca.add(pointLayer);

// 增加图标图层
let iconLayer = new Loca.IconLayer({
zooms: [16, 20],
zIndex: 10,
visible: true,
});
iconLayer.setSource(dataSource);
// 设置样式
iconLayer.setStyle({
icon: process.env.VUE_APP_URL+"/static/img/renfang.png",
iconSize: [25, 25],
});
loca.add(iconLayer);
}

/**
* 绘制标签
*/
drawRfdwdLabel(features){
let mainmap=this.map_;
// 清空数据
let labelsLayer =null;
if(this.rfLabelsLayer){ // 清空数据
this.rfLabelsLayer.remove(this.rfLabelsMarkers);
labelsLayer=this.rfLabelsLayer;
this.rfLabelsMarkers=[];
}else{
// 创建label
labelsLayer = new AMap.LabelsLayer({
collision: true,
opacity: 1,
zIndex: 120,
allowCollision: false,
zooms: [14, 20],
});
this.rfLabelsLayer=labelsLayer;
// 将标注层添加到地图上
mainmap.add(labelsLayer);
}
// 加载标注
for (var i = features.length-1; i>=0; i--) {
let item=features[i];
let name=item.properties.JZMC;
let geom=item.geometry;
let coordinates=geom.coordinates;
var labelsMarker = new AMap.LabelMarker({
name:name,
position:new AMap.LngLat(coordinates[0], coordinates[1]),
text:{
content:name,
offset:[0,-5],
style:{
fillColor:"#fff",
fold:true,
},
extData:item, // 自定义数据
}
});
labelsLayer.add(labelsMarker);
// 保存引用
this.rfLabelsMarkers.push(labelsMarker);
}
}

注意:这个方法也是不行的,在我的vue3.0写的项目中,还是会出现卡顿的问题

小额赞助
本人提供免费与付费咨询服务,感谢您的支持!赞助请发邮件通知,方便公布您的善意!
**光 3.01 元
Sun 3.00 元
bibichuan 3.00 元
微信公众号
广告位
诚心邀请广大金主爸爸洽谈合作
每日一省
isNaN 和 Number.isNaN 函数的区别?

1.函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。

2.函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。

每日二省
为什么0.1+0.2 ! == 0.3,如何让其相等?

一个直接的解决方法就是设置一个误差范围,通常称为“机器精度”。对JavaScript来说,这个值通常为2-52,在ES6中,提供了Number.EPSILON属性,而它的值就是2-52,只要判断0.1+0.2-0.3是否小于Number.EPSILON,如果小于,就可以判断为0.1+0.2 ===0.3。

每日三省
== 操作符的强制类型转换规则?

1.首先会判断两者类型是否**相同,**相同的话就比较两者的大小。

2.类型不相同的话,就会进行类型转换。

3.会先判断是否在对比 null 和 undefined,是的话就会返回 true。

4.判断两者类型是否为 string 和 number,是的话就会将字符串转换为 number。

5.判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断。

6.判断其中一方是否为 object 且另一方为 string、number 或者 symbol,是的话就会把 object 转为原始类型再进行判断。

每日英语
Happiness is time precipitation, smile is the lonely sad.
幸福是年华的沉淀,微笑是寂寞的悲伤。