OpenLayers基础教程——点要素图层的聚合显示
1、前言在很多情况下,点要素图层中的要素数量可能会成百上千,这时候如果不做任何处理直接加载到地图上不仅会使用户视觉体验下降,而且也会造成地图界面的卡顿。下面这段代码创建了1000个随机点进行显示:<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content="text/html; ch
1、前言
在某些情况下,图层中的点数量可能会成百上千,这时候如果不做任何处理将其直接加载到地图上,那么不仅会使用户视觉体验下降,同时也会造成地图界面的卡顿。下面这段代码创建了200
个随机点进行显示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>聚合</title>
<style>
html,
body,
#map {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
</style>
<link rel="stylesheet" href="libs/ol/ol.css" />
<script src="libs/ol/ol.js"></script>
</head>
<body>
<div id="map"></div>
<script>
// 随机创建200个要素
var source = new ol.source.Vector();
for (var i = 1; i <= 200; i++) {
var coordinates = [120.00 + Math.random(), 30.00 + Math.random()];
var feature = new ol.Feature(new ol.geom.Point(coordinates));
source.addFeature(feature);
}
// 创建图层
var layer = new ol.layer.Vector({
source: source,
style: function (feature, resolution) {
var style = new ol.style.Style({
image: new ol.style.Icon({
src: 'img/point.png'
})
})
return style;
}
});
// 创建地图
var map = new ol.Map({
target: 'map',
layers: [
layer
],
view: new ol.View({
projection: 'EPSG:4326',
center: [120, 30],
zoom: 10,
minZoom: 5,
maxZoom: 14
})
});
</script>
</body>
</html>
运行结果如下图所示:
一般情况下,如果一个点要素图层中的点数量很多,可以采用图层聚合
的方式对其进行处理。但需要注意:图层聚合只对点要素图层有效,对线和面图层无效
。
2、点要素图层的聚合
在openlayers
中,图层聚合的一般步骤如下:
- 创建要素
- 创建数据源添加要素
- 创建聚合数据源,设置聚合的距离
- 创建图层,将数据源设置为聚合数据源
- 创建地图,添加聚合图层
图层聚合代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>聚合</title>
<style>
html,
body,
#map {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
</style>
<link rel="stylesheet" href="libs/ol/ol.css" />
<script src="libs/ol/ol.js"></script>
</head>
<body>
<div id="map"></div>
<script>
// 随机创建200个要素
var source = new ol.source.Vector();
for (var i = 1; i <= 200; i++) {
var coordinates = [120.00 + Math.random(), 30.00 + Math.random()];
var feature = new ol.Feature(new ol.geom.Point(coordinates));
source.addFeature(feature);
}
// 创建聚合数据源
var cluster = new ol.source.Cluster({
source: source,
distance: 100
});
// 创建图层
var layer = new ol.layer.Vector({
source: cluster,
style: function (feature, resolution) {
var size = feature.get('features').length;
var style = new ol.style.Style({
image: new ol.style.Circle({
radius: 30,
stroke: new ol.style.Stroke({
color: 'white'
}),
fill: new ol.style.Fill({
color: 'blue'
})
}),
text: new ol.style.Text({
text: size.toString(),
fill: new ol.style.Fill({
color: 'white'
})
})
})
return style;
}
});
// 创建地图
var map = new ol.Map({
target: 'map',
layers: [
layer
],
view: new ol.View({
projection: 'EPSG:4326',
center: [120, 30],
zoom: 10,
minZoom: 5,
maxZoom: 14
})
});
</script>
</body>
</html>
运行结果如下图所示:
3、聚合特殊处理一
上面的代码虽然实现了点要素图层的聚合,但其实存在着一个问题:地图缩放层级最大时仍然保持着聚合效果
,如下图所示:
一般情况下,当某处只有一个点时就应该取消聚合效果
,这时候就需要对style: function (feature, resolution)
这个回调函数进行处理,代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>聚合</title>
<style>
html,
body,
#map {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
</style>
<link rel="stylesheet" href="libs/ol/ol.css" />
<script src="libs/ol/ol.js"></script>
</head>
<body>
<div id="map"></div>
<script>
// 随机创建200个要素
var source = new ol.source.Vector();
for (var i = 1; i <= 200; i++) {
var coordinates = [120.00 + Math.random(), 30.00 + Math.random()];
var feature = new ol.Feature(new ol.geom.Point(coordinates));
source.addFeature(feature);
}
// 创建聚合数据源
var cluster = new ol.source.Cluster({
source: source,
distance: 100
});
// 创建图层
var layer = new ol.layer.Vector({
source: cluster,
style: function (feature, resolution) {
var size = feature.get('features').length;
if (size == 1) {
return new ol.style.Style({
image: new ol.style.Icon({
src: 'img/point.png'
})
})
}
else {
return new ol.style.Style({
image: new ol.style.Circle({
radius: 30,
stroke: new ol.style.Stroke({
color: 'white'
}),
fill: new ol.style.Fill({
color: 'blue'
})
}),
text: new ol.style.Text({
text: size.toString(),
fill: new ol.style.Fill({
color: 'white'
})
})
})
}
}
});
// 创建地图
var map = new ol.Map({
target: 'map',
layers: [
layer
],
view: new ol.View({
projection: 'EPSG:4326',
center: [120, 30],
zoom: 10,
minZoom: 5,
maxZoom: 14
})
});
</script>
</body>
</html>
运行结果如下图所示:
其实这个效果实现起来很简单,核心代码就是:var size = feature.get('features').length;
,如果size>1
,则返回聚合样式,反之则返回图片样式。
4、聚合特殊处理二
在上面的代码中,我把地图的最大缩放层级设置为14
,这也就导致了一个问题:当地图缩放到最大层级时,还有很多点保持着聚合效果
。有时候用户可能会要求:当地图缩放到最大层级时,取消全部聚合效果
。如果要实现这个功能,需要对地图事件进行监听,代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>聚合</title>
<style>
html,
body,
#map {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
</style>
<link rel="stylesheet" href="libs/ol/ol.css" />
<script src="libs/ol/ol.js"></script>
</head>
<body>
<div id="map"></div>
<script>
// 随机创建200个要素
var source = new ol.source.Vector();
for (var i = 1; i <= 200; i++) {
var coordinates = [120.00 + Math.random(), 30.00 + Math.random()];
var feature = new ol.Feature(new ol.geom.Point(coordinates));
source.addFeature(feature);
}
// 聚合
var cluster = new ol.source.Cluster({
source: source,
distance: 100
})
// 创建图层
var layer = new ol.layer.Vector({
source: cluster,
style: function (feature, resolution) {
var size = feature.get('features').length;
if (size == 1) {
return new ol.style.Style({
image: new ol.style.Icon({
src: 'img/point.png'
})
})
}
else {
return new ol.style.Style({
image: new ol.style.Circle({
radius: 30,
stroke: new ol.style.Stroke({
color: 'white'
}),
fill: new ol.style.Fill({
color: 'blue'
})
}),
text: new ol.style.Text({
text: size.toString(),
fill: new ol.style.Fill({
color: 'white'
})
})
})
}
}
});
// 创建地图
var map = new ol.Map({
target: 'map',
layers: [
layer
],
view: new ol.View({
projection: 'EPSG:4326',
center: [120, 30],
zoom: 10,
minZoom: 5,
maxZoom: 14
})
});
// 监听地图分辨率改变事件
map.getView().on('change:resolution', function (event) {
if (map.getView().getZoom() == map.getView().getMaxZoom()) {
cluster.setDistance(0);
}
else {
cluster.setDistance(100);
}
})
</script>
</body>
</html>
运行结果如下图所示:
这个效果的实现也很简单,只需要监听当前地图的分辨率变化事件,如果当前缩放层级已经是最大层级,则将聚合的距离设置为0
即可。
5、聚合特殊处理三
某些情况下,我们可能需要获取某个聚合要素下包含的所有要素,代码如下所示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>聚合</title>
<style>
html,
body,
#map {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
</style>
<link rel="stylesheet" href="libs/ol/ol.css" />
<script src="libs/ol/ol.js"></script>
</head>
<body>
<div id="map"></div>
<script>
// 随机创建200个要素
var source = new ol.source.Vector();
for (var i = 1; i <= 200; i++) {
var coordinates = [120.00 + Math.random(), 30.00 + Math.random()];
var feature = new ol.Feature(new ol.geom.Point(coordinates));
source.addFeature(feature);
}
// 聚合
var cluster = new ol.source.Cluster({
source: source,
distance: 100
})
// 创建图层
var layer = new ol.layer.Vector({
source: cluster,
style: function (feature, resolution) {
var size = feature.get('features').length;
if (size == 1) {
return new ol.style.Style({
image: new ol.style.Icon({
src: 'img/point.png'
})
})
}
else {
return new ol.style.Style({
image: new ol.style.Circle({
radius: 30,
stroke: new ol.style.Stroke({
color: 'white'
}),
fill: new ol.style.Fill({
color: 'blue'
})
}),
text: new ol.style.Text({
text: size.toString(),
fill: new ol.style.Fill({
color: 'white'
})
})
})
}
}
});
// 创建地图
var map = new ol.Map({
target: 'map',
layers: [
layer
],
view: new ol.View({
projection: 'EPSG:4326',
center: [120, 30],
zoom: 10,
minZoom: 5,
maxZoom: 14
})
});
// 监听地图单击事件
map.on('singleclick', function (e) {
var pixel = map.getEventPixel(e.originalEvent);
var currentFeature = map.forEachFeatureAtPixel(pixel, function (feature, layer) {
return feature;
});
if (currentFeature) {
var features = currentFeature.get('features');
if (features.length > 1) {
window.alert('当前聚合圈下共有' + features.length + '个要素');
}
if (features.length == 1) {
window.alert('当前要素未被聚合,只有' + features.length + '个要素');
}
}
});
</script>
</body>
</html>
运行结果如下图所示:
在获取了某个聚合圈后,只需要通过feature.get('features')
就能获取该聚合圈下所有的要素。
6、聚合样式的扩展
之前很过同志都问过同一个问题:聚合时只能用圆圈来显示吗?能不能设置其他的形状样式?
答案当然是可以的,这里就来说明一下。在OpenLayers
中有一个ol.style.RegularShape
类,通过该类可以绘制很多规则图形,下面代码演示了正三角形
的聚合效果:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>聚合</title>
<style>
html,
body,
#map {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
</style>
<link rel="stylesheet" href="libs/ol/ol.css" />
<script src="libs/ol/ol.js"></script>
</head>
<body>
<div id="map"></div>
<script>
// 随机创建200个要素
var source = new ol.source.Vector();
for (var i = 1; i <= 200; i++) {
var coordinates = [120.00 + Math.random(), 30.00 + Math.random()];
var feature = new ol.Feature(new ol.geom.Point(coordinates));
source.addFeature(feature);
}
// 聚合
var cluster = new ol.source.Cluster({
source: source,
distance: 100
})
// 创建图层
var layer = new ol.layer.Vector({
source: cluster,
style: function (feature, resolution) {
var size = feature.get('features').length;
if (size == 1) {
return new ol.style.Style({
image: new ol.style.Icon({
src: 'img/point.png'
})
})
}
else {
return new ol.style.Style({
image: new ol.style.RegularShape({
radius: 30,
points: 3,
fill: new ol.style.Fill({
color: 'blue'
})
}),
text: new ol.style.Text({
text: size.toString(),
fill: new ol.style.Fill({
color: 'white'
})
})
})
}
}
});
// 创建地图
var map = new ol.Map({
target: 'map',
layers: [
layer
],
view: new ol.View({
projection: 'EPSG:4326',
center: [120, 30],
zoom: 10,
minZoom: 5,
maxZoom: 14
})
});
</script>
</body>
</html>
运行结果如下图所示:
如果将points
设置为4
,则显示为正方形
:
如果将points
设置为6
,则显示为正六边形
:
ol.style.RegularShape
类包含radius1
和radius2
,通过这两个属性我们可以绘制凹多边形,下面代码演示了五角星
聚合效果:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>聚合</title>
<style>
html,
body,
#map {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
</style>
<link rel="stylesheet" href="libs/ol/ol.css" />
<script src="libs/ol/ol.js"></script>
</head>
<body>
<div id="map"></div>
<script>
// 随机创建200个要素
var source = new ol.source.Vector();
for (var i = 1; i <= 200; i++) {
var coordinates = [120.00 + Math.random(), 30.00 + Math.random()];
var feature = new ol.Feature(new ol.geom.Point(coordinates));
source.addFeature(feature);
}
// 聚合
var cluster = new ol.source.Cluster({
source: source,
distance: 100
})
// 创建图层
var layer = new ol.layer.Vector({
source: cluster,
style: function (feature, resolution) {
var size = feature.get('features').length;
if (size == 1) {
return new ol.style.Style({
image: new ol.style.Icon({
src: 'img/point.png'
})
})
}
else {
return new ol.style.Style({
image: new ol.style.RegularShape({
radius1: 20,
radius2: 40,
points: 5,
fill: new ol.style.Fill({
color: 'blue'
})
}),
text: new ol.style.Text({
text: size.toString(),
fill: new ol.style.Fill({
color: 'white'
})
})
})
}
}
});
// 创建地图
var map = new ol.Map({
target: 'map',
layers: [
layer
],
view: new ol.View({
projection: 'EPSG:4326',
center: [120, 30],
zoom: 10,
minZoom: 5,
maxZoom: 14
})
});
</script>
</body>
</html>
我们也可以通过组合的方式来扩展样式,代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>聚合</title>
<style>
html,
body,
#map {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
</style>
<link rel="stylesheet" href="libs/ol/ol.css" />
<script src="libs/ol/ol.js"></script>
</head>
<body>
<div id="map"></div>
<script>
// 随机创建200个要素
var source = new ol.source.Vector();
for (var i = 1; i <= 200; i++) {
var coordinates = [120.00 + Math.random(), 30.00 + Math.random()];
var feature = new ol.Feature(new ol.geom.Point(coordinates));
source.addFeature(feature);
}
// 聚合
var cluster = new ol.source.Cluster({
source: source,
distance: 100
})
// 创建图层
var layer = new ol.layer.Vector({
source: cluster,
style: function (feature, resolution) {
var size = feature.get('features').length;
if (size == 1) {
return new ol.style.Style({
image: new ol.style.Icon({
src: 'img/point.png'
})
})
}
else {
var styles = [];
styles.push(
new ol.style.Style({
image: new ol.style.Circle({
radius: 30,
fill: new ol.style.Fill({
color: 'greenyellow'
}),
stroke: new ol.style.Stroke({
color: 'greenyellow',
width: 1
})
})
})
);
styles.push(
new ol.style.Style({
geometry: feature.getGeometry(),
image: new ol.style.RegularShape({
radius1: 12,
radius2: 25,
points: 8,
fill: new ol.style.Fill({
color: 'blue'
})
}),
text: new ol.style.Text({
text: size.toString(),
fill: new ol.style.Fill({
color: 'white'
})
})
})
);
return styles;
}
}
});
// 创建地图
var map = new ol.Map({
target: 'map',
layers: [
layer
],
view: new ol.View({
projection: 'EPSG:4326',
center: [120, 30],
zoom: 10,
minZoom: 5,
maxZoom: 14
})
});
</script>
</body>
</html>
运行结果如下图所示:
7、使用Canvas绘制自定义聚合样式
ol.style.RegularShape
最大的缺点就是只能绘制规则图形,如果我们要绘制矩形
或者其他的一些不规则图形
,那它就无能为力了,此时就需要通过canvas
绘制自定义图形来实现。我在这里做了一个圆角矩形气泡框+图片
的聚合例子供大家参考,代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
html,
body,
#map {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
</style>
<link rel="stylesheet" href="libs/ol/ol.css" />
<script src="libs/ol/ol.js"></script>
</head>
<body>
<div id="map">
<img src="img/002.PNG" id="img" style="display: none;">
</div>
<script>
// 随机创建200个要素
var source = new ol.source.Vector();
for (var i = 1; i <= 100; i++) {
var coordinates = [120.00 + Math.random(), 30.00 + Math.random()];
var feature = new ol.Feature(new ol.geom.Point(coordinates));
source.addFeature(feature);
}
// 聚合
var cluster = new ol.source.Cluster({
source: source,
distance: 100
})
// 创建图层
var layer = new ol.layer.Vector({
source: cluster,
style: function (feature, resolution) {
var size = feature.get('features').length;
if (size == 1) {
return new ol.style.Style({
image: new ol.style.Icon({
src: 'img/point.png'
})
})
}
else {
var canvas = draw(30, 30, 90, 40, 10, 'blue', 'blue', size);
var style = new ol.style.Style({
image: new ol.style.Icon({
img: canvas,
imgSize: [300, 200],
})
});
return style;
}
}
});
// 创建地图
var map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
}),
layer
],
view: new ol.View({
projection: 'EPSG:4326',
center: [120, 30],
zoom: 10,
minZoom: 5,
maxZoom: 14
})
});
function draw(x, y, width, height, radius, strokeStyle, fillStyle, size) {
var canvas = document.createElement('canvas');
var ctx = canvas.getContext("2d");
ctx.strokeStyle = strokeStyle;
ctx.fillStyle = fillStyle;
ctx.beginPath();
// 绘制弧长、直线
ctx.arc(x + radius, y + radius, radius, Math.PI, Math.PI * 3 / 2, false);
ctx.lineTo(x + width - radius, y);
ctx.arc(x + width - radius, y + radius, radius, Math.PI * 3 / 2, Math.PI * 2, false);
ctx.lineTo(x + width, y + height - radius);
ctx.arc(x + width - radius, y + height - radius, radius, 0, Math.PI / 2, false);
ctx.lineTo(x + radius + 20, y + height);
ctx.lineTo(x, y + height + 15);
ctx.lineTo(x + radius, y + height);
ctx.arc(x + radius, y + height - radius, radius, Math.PI / 2, Math.PI, false);
ctx.lineTo(x, y + radius);
ctx.closePath();
ctx.fill();
// 绘制文字
var textX = 45;
var textY = 55;
ctx.font = '12px Arial';
ctx.fillStyle = 'white';
ctx.fillText(size + '个摄像头', textX, textY);
// 绘制图片
var img = document.getElementById("img");
ctx.drawImage(img, 0, 80);
// 返回
return canvas;
}
</script>
</body>
</html>
8、结语
在点数量较多的情况下,应该考虑对其进行聚合处理,这样不仅可以提升用户体验,而且也可以避免界面卡顿。如果实际业务对聚合的形状样式有较高要求,则应该考虑使用canvas
自行绘制图案。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)