简述
聚合效果,在不同的缩放级别下,将临近的点变成一个点,在视觉上能够清晰的知道点分布情况。举一个场景,全国建立了许多充电站,如果我想将所有的充电站都标出来,那么整个地图就会变得十分拥挤,那如果在全国的视角下,可以使用一个点去代表周围有多少充电站数量(大概是一个省一点),如果在市级别,我们也可以采用同样的方案去展示充电站数量。
在通常我们实现聚类效果有两种办法,一种是Marker(html),另一种是纯Symbol(也可以是circle),两者的原理其实是相同的,Marker更适合对复杂图标效果的情景,因为Symbol能力有限,并不能表达所有的图标样式(比如饼图)。本节优先介绍Symbol
聚合有两个例子,先将一个简单例子,然后再讲一个官方的例子
示例1
代码
js
// 添加图片资源
function addImageSource() {
const images = [
png1,
png2,
png3,
png4,
png5,
png6,
greenMarkerPng,
];
const imageNames = ["1", "2", "3", "4", "5", "6", "greenMarker"];
images.forEach((item, index) => {
map.loadImage(item, (error, image) => {
if (error) throw error;
map.addImage(imageNames[index], image);
});
});
}
addImageSource();
// 随机造数据
const geojson = {
type: "FeatureCollection",
features: [],
};
for(let i = 0;i<100;i++){
geojson.features.push({
type: "Feature",
properties: {
type: 1 + Math.floor(Math.random() * 5),
},
geometry: {
type: "Point",
coordinates: [110 + Math.random()*10, 30 + Math.random()*10],
},
});
}
// 添加source
map.addSource("cluster", {
type: "geojson",
data: geojson,
cluster: true,
clusterRadius: 30,
});
// 添加layer
map.addLayer({
id: "cluster",
type: "symbol",
source: "cluster",
layout: {
"icon-size": ["case", ["has", "point_count"], 0.5, 0.3],
"icon-image": [
"case",
["has", "point_count"],
"greenMarker",
["get", "type"],
],
"text-field": ["get", "point_count_abbreviated"],
"text-offset": [0.5, -0.1],
"text-size": 20,
"icon-ignore-placement": true,
},
paint: {
"text-color": "#F0F0F0",
"text-halo-width": 0.5,
"text-halo-color": "#F0F0F0",
},
});
// 添加点击事件
map.on("click", "cluster", (e) => {
if (e.features[0].properties.point_count !== undefined) {
console.log("cluster");
// 放大地图
map.flyTo({
center: e.lngLat,
zoom: map.getZoom() + 2.5,
});
} else {
console.log("single");
}
});
map.flyTo({
center: [110, 35],
zoom: 3,
});
示例逻辑
- 添加需要的图片资源
- 生成一些示例数据
- 添加source
- 添加layer
- 添加点击事件
解析
- 添加需要的图片资源
js
function addImageSource() {
const images = [
png1,
png2,
png3,
png4,
png5,
png6,
greenMarkerPng,
];
const imageNames = ["1", "2", "3", "4", "5", "6", "greenMarker"];
images.forEach((item, index) => {
map.loadImage(item, (error, image) => {
if (error) throw error;
map.addImage(imageNames[index], image);
});
});
}
这一步使用了api两个方法 loadImage
和addImage
, 两个方法在1.8中介绍过,不再重复介绍。这一步创建了图片资源和图片名称两个数组,长度相同,随后循环添加图片。
- 生成一些示例数据
js
// 随机造数据
const geojson = {
type: "FeatureCollection",
features: [],
};
for(let i = 0;i<100;i++){
geojson.features.push({
type: "Feature",
properties: {
type: 1 + Math.floor(Math.random() * 5),
},
geometry: {
type: "Point",
coordinates: [110 + Math.random()*10, 30 + Math.random()*10],
},
});
}
创建geojson格式的点数据,生成范围在经度110120,纬度30 40之间,同时生成一个属性type
其值为1-6之间的整数
- 添加source
js
// 添加source
map.addSource("cluster", {
type: "geojson",
data: geojson,
cluster: true,
clusterRadius: 30,
});
addSource
添加上一步生成的随机数据,与以往不同的是,这里设置了两个参数 cluster
和clusterRadius
。这里需要注意的是,虽然聚合效果是在layer
上所展现的,但是设置聚合却是在source
中设置的。
参数 | 描述 |
---|---|
cluster | 是否聚合,默认为false |
clusterRadius | 聚合半径,在cluster为true时生效,默认为50 |
示例这一步的效果为:添加名为cluster的数据源,并开启聚合,且聚合半径为30,需要注意的是,这里的聚合半径的单位即不是像素,也不是实际的长度单位。官方解释为:如果聚合半径为512,那么就等于瓦片的宽,也就是和所在瓦片有关(或者说是层级),如果上述解释还是不太理解,当作像素理解也可以。
addSource
还可以设置其他一些属性,感兴趣的可以自行查阅官方文档
- 添加layer
js
// 添加layer
map.addLayer({
id: "cluster",
type: "symbol",
source: "cluster",
layout: {
"icon-size": ["case", ["has", "point_count"], 0.5, 0.3],
"icon-image": [
"case",
["has", "point_count"],
"greenMarker",
["get", "type"],
],
"text-field": ["get", "point_count_abbreviated"],
"text-offset": [0.5, -0.1],
"text-size": 20,
"icon-ignore-placement": true,
},
paint: {
"text-color": "#F0F0F0",
"text-halo-width": 0.5,
"text-halo-color": "#F0F0F0",
},
});
添加一个点图层cluster
,数据源设置为cluster
, 在上面代码中,首先需要说明的是以下两个
js
"icon-size": ["case", ["has", "point_count"], 0.5, 0.3],
"text-field": ["get", "point_count_abbreviated"],
以往的入门文章中,很少去讲点图层的样式,是因为这里面的东西非常多,只能见一个讲一个,不增加理解负担。首先需要知道的是,在mapbox中,点图层包含了图标和文字两个部分,可以是仅图标,可以是仅文字,也可以两者都有(同一图层)。而其他线、面图层都是没有文字的,需额外的点图层,这部分会在2.x中讲解。
icon-size
和text-field
分别设置的是图标的大小和展示文字的字段,icon-size
用了case
表达式,意思为如果有 point_count
字段,设置为0.5,否则是0.3。这里最大疑惑是point_count
和point_count_abbreviated
两个字段,在我们生成的数据里,只有type一个属性字段,这两个是哪里来的呢?
在设置聚合后,当多个点聚合成1个聚合点时,这个聚合点的属性中就会出现新的字段:
参数 | 描述 |
---|---|
cluster | 是否是聚合点 |
cluster_id | 聚合ID(不重复) |
point_count | 被聚合的数量 |
point_count_abbreviated | 被聚合的数量(缩写) |
根据以上字段,可以区分聚合点和原始点,这样便可以设置各自的样式。
js
"icon-image": [
"case",
["has", "point_count"],
"greenMarker",
["get", "type"],
],
icon-image
: 当有字段point_count
时,图标是greenMarker,否则用type字段值的图片,前面步骤中,造的type字段数据为16的整数,添加的图片名称相应的也是16,这样便匹配上了。
js
"text-offset": [0.5, -0.1], // 文字的偏移,分别是左右偏移(右为正),上下偏移(下为正)
"text-size": 20, // 文字大小 单位是像素
"icon-ignore-placement": true, //是否忽略碰撞
// paint
"text-color": "#F0F0F0", // 文字颜色
"text-halo-width": 0.5, // 字体轮廓宽度
"text-halo-color": "#F0F0F0",// 字体轮廓颜色
// 上面三个是为了加粗字体,其他加粗字体的方法比较麻烦
到了这一步,我们可以先看一下效果
聚合

放大

再放大

- 添加点击事件
js
map.on("click", "cluster", (e) => {
if (e.features[0].properties.point_count !== undefined) {
console.log("cluster");
// 放大地图
map.flyTo({
center: e.lngLat,
zoom: map.getZoom() + 2.5,
});
} else {
console.log("single");
}
});
在这一步中,给cluster
图层注册点击事件,聚合点的属性字段有point_count
,根据这个判断我们点击的是聚合点和原始点,在这步可以打印e.features[0]查看其他的属性。通过判断,对聚合点和原始点执行不同的事件
总结
本节是聚合的第一个例子,整个过程相对简单,只是需要理解聚合多出来的属性和数据源的设置,接下来是官网的一个例子。
如有错误,欢迎指正;如有疑问,评论留言。