在地图上点位显示过多,会造成页面卡顿、点位重叠看不清信息,模拟实现人员聚合,单人显示名称,多人聚合显示人数。
Cluster详解
在OpenLayers中,点聚合是一种常见的需求,可以通过集成第三方库ol-cluster
来实现。聚合的原理是将距离较近的点位合并为一个点,并计算合并后点位的属性值。当一个点被添加进来时,会检查该点与已有聚合点的距离是否在指定的聚合距离之内,如果是,则将该点加入到该聚合点中,同时更新聚合点的属性值(例如点数等)。如果该点与已有聚合点的距离都超出聚合距离,则将该点作为新的聚合点,加入到聚合源中。
在渲染时,对于聚合后的点,可以根据聚合点的属性值设置不同的样式,以区别于普通的点位。我这里就是通过设置两种不同的聚合样式来体现多人和单人。也可以理解为将聚合数量大于1和等于1分别设置样式。
安装ol-cluster
库:
npm install ol-cluster
实现聚合的步骤:
- 将点位数据通过
ol/Feature
和ol/geom/Point
按坐标生成对应的feature, - 通过
ol/source/Vector
创建集群点矢量数据源。 - 使用ol/source/Cluster创建集群数据源。
- 创建一个集群点矢量图层
ol/layer/Vector
,并设置集群点样式ol/style/Style
。 - 将集群点图层添加到Map上。
集群点按坐标生成对应feature
for (let i = 0; i < pointArr.length; i++) {
const item = pointArr[i];
let feature = new Feature({
geometry: new Point(new Proj.fromLonLat(item.position)),
name: item.name,
type: 'point'
});
}
创建集群点矢量数据源
const source = new VectorSource();
// 将集群点feature添加到集群点资源source中
source.addFeature(feature);
创建集群数据源
distance
是设置的聚合距离,单位 像素。在这个范围内的点就会聚合在一起。设置为0时,所有点位都不聚合,即可达到不聚合的效果。
在聚合时,OpenLayers会计算每个点在屏幕上的像素位置,并根据像素位置计算聚合距离。因此,聚合距离不是以地理距离的方式计算的,而是以屏幕上的像素距离为基础。聚合距离的大小取决于地图缩放级别、地图分辨率和聚合距离参数的值。
// 创建集群资源
const clusterSource = new Cluster({
distance: 50,
source: source // 集群点矢量数据源
});
创建集群矢量图层
// 集群点图层
let clusters = new VectorLayer({
source: clusterSource,
style: (feature) => {
return setClusterStyle(feature);
}
});
map.addLayer(clusters);
设置集群点样式
设置了聚合数据等于1和大于1的样式,区分了单人和多人,等于1也可为不聚合样式。
等于1时展示人员姓名,聚合时展示聚合人数。
// 集群点样式
function setClusterStyle(feature) {
// feature.get('features')这一步得到的是feature所在的集群的feature数组
const features = feature.get('features');
const name = feature.values_.features[0].values_.name; // 获取feature名称
const size = features.length;
let style;
if (size > 1) {
// 聚合样式
style = new Style({
image: new Icon({
scale: 0.5,
src: clusterImg,
anchor: [0.5, 1]
}),
text: new Text({
text: size.toString(), // 数字需要toString()转换
fill: new Fill({
color: '#FFFFFF'
}),
backgroundFill: new Fill({
color: '#555555'
}),
padding: [2, 2, 0, 4],
offsetY: -48,
scale: 1.4
})
});
} else {
// 单点,不聚合样式
style = new Style({
image: new Icon({
scale: 0.5,
src: pointImg,
anchor: [0.5, 1]
}),
text: new Text({
text: name, // 数字需要toString()转换
fill: new Fill({
color: '#FFFFFF'
}),
backgroundFill: new Fill({
color: '#555555'
}),
padding: [2, 2, 0, 4],
offsetY: -48,
scale: 1.4
})
});
}
return style;
}
完整示例代码
// openlayers点聚合
import React, { useState, useEffect } from 'react';
import { Map, View, Feature } from 'ol';// 地图Collection
import * as Proj from 'ol/proj'; // 转化
import { XYZ, Vector as VectorSource, Cluster } from 'ol/source'; // 资源
import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer'; // 图层
import { Point } from 'ol/geom';
import { Style, Icon, Fill, Text } from 'ol/style'; // 样式
const clusterImg = require('@/static/image/more.png');
const pointImg = require('@/static/image/one.png');
const OpenlayerCluster = () => {
const [map, setMap] = useState(null); // 地图
const [view, setView] = useState(null); // 地图视图
// 集群点样式
function setClusterStyle(feature) {
// feature.get('features')这一步得到的是feature所在的集群的feature数组
const features = feature.get('features');
const name = feature.values_.features[0].values_.name; // 获取feature名称
const size = features.length;
let style;
if (size > 1) {
// 聚合样式
style = new Style({
image: new Icon({
scale: 0.5,
src: clusterImg,
anchor: [0.5, 1]
}),
text: new Text({
text: size.toString(), // 数字需要toString()转换
fill: new Fill({
color: '#FFFFFF'
}),
backgroundFill: new Fill({
color: '#555555'
}),
padding: [2, 2, 0, 4],
offsetY: -48,
scale: 1.4
})
});
} else {
// 单点,不聚合样式
style = new Style({
image: new Icon({
scale: 0.5,
src: pointImg,
anchor: [0.5, 1]
}),
text: new Text({
text: name, // 数字需要toString()转换
fill: new Fill({
color: '#FFFFFF'
}),
backgroundFill: new Fill({
color: '#555555'
}),
padding: [2, 2, 0, 4],
offsetY: -48,
scale: 1.4
})
});
}
return style;
}
useEffect(() => {
if (map) {
// 点坐标
let pointArr = [
{
name: '张三',
position: [104.1005229950, 30.6743128087],
}, {
name: '李四',
position: [103.9271879196, 30.7462617980],
}, {
name: '王五',
position: [103.6227035522, 30.9932085864],
}, {
name: '赵六',
position: [103.5752069950, 31.4663367378],
}, {
name: '安娜',
position: [103.4307861328, 30.1941019446],
}, {
name: '麻子',
position: [106.5885615349, 29.5679608922],
}, {
name: '嘎子',
position: [106.4500522614, 29.5811456252],
}, {
name: '翠花',
position: [107.7666950226, 29.4161988273],
}
]
// 集群点资源Source
let source = new VectorSource();
// 集群点按坐标生成对应feature
for (let i = 0; i < pointArr.length; i++) {
const item = pointArr[i];
let feature = new Feature({
geometry: new Point(new Proj.fromLonLat(item.position)),
name: item.name,
type: 'point'
});
// 将集群点feature添加到集群点资源source中
source.addFeature(feature);
}
// 创建集群资源
let clusterSource = new Cluster({
distance: 50,
source: source
});
// 集群点图层
let clusters = new VectorLayer({
source: clusterSource,
style: (feature) => {
return setClusterStyle(feature);
}
});
// 将集群点添加到Map
map.addLayer(clusters);
}
}, [map]);
useEffect(() => {
// 监听地图视图,创建地图
if (view) {
// 使用高德图层
const tileLayer = new TileLayer({
source: new XYZ({
url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&scl=1&size=1&style=7&x={x}&y={y}&z={z}'
}),
name: 'mapLayer'
});
// 创建实例
const _map = new Map({
target: 'map',
layers: [tileLayer], // 使用高德图层
view: view
});
setMap(_map);
}
}, [view]);
useEffect(() => {
// View用于创建2D视图
const viewObj = new View({
// 设定中心点,因为默认坐标系为 3587,所以要将我们常用的经纬度坐标系4326 转换为 3587坐标系
center: Proj.transform([103.9271879196, 30.7462617980], 'EPSG:4326', 'EPSG:3857'),
zoom: 7
});
setView(viewObj);
}, []);
return <div id='map' style={
{ width: '100%', height: '100%' }}></div>;
}
export default OpenlayerCluster;