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中,图层聚合的一般步骤如下:

  1. 创建要素
  2. 创建数据源添加要素
  3. 创建聚合数据源,设置聚合的距离
  4. 创建图层,将数据源设置为聚合数据源
  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;
                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类包含radius1radius2,通过这两个属性我们可以绘制凹多边形,下面代码演示了五角星聚合效果:

<!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自行绘制图案。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐