One issue we may face while having large number of points in map is that, it can get very clumsy and increase the loading time. To resolve this issue, we can cluster the points into a single circle with a number that represent the count of points in that circle. In this tutorial, we will learn how to implement clustering on point data.
To get full source code for this tutorial, click here.
To check the live demo for this tutorial, click here.
Get Point data
We are not using any external point data source. We are creating some thousands of random points on the fly and then applying clustering on it.
1 2 3 4 5 6 7 8 |
// Create ramdom point features const count = 20000; const features = new Array(count); const e = 4500000; for (let i = 0; i < count; ++i) { const coordinates = [2 * e * Math.random() - e, 2 * e * Math.random() - e]; features[i] = new ol.Feature(new ol.geom.Point(coordinates)); } |
Implement Clustering
Once we have the points data, we can start apply clustering on points data. To apply clustering, OpenLayers provide ol.source.Cluster module. See below code snippet. We are defining regular vector layer. The data source for this layer will be ol.source.Cluster. The important parameters in ol.source.Cluster that need to be given are distance and source. Distance is in pixels within which features will be clustered together. Source is the layer source. In our case, it is point data source.
We need to use style for cluster layer. The styling in cluster layer is very important. By using style only, the cluster point can be represented as a circle and a number label on it.
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 |
/* Create Cluster Layer */ const styleCache = {}; let cluster_layer = new ol.layer.Vector({ source: new ol.source.Cluster({ distance: 100, minDistance: 100, source: new ol.source.Vector({ features: features, projection:map.getView().projection }) }), style: function (feature) { const size = feature.get('features').length; let style = styleCache[size]; if (!style) { style = new ol.style.Style({ image: new ol.style.Circle({ radius: 30, stroke: new ol.style.Stroke({ color: [255, 255, 255, 0.7], width: 10, }), fill: new ol.style.Fill({ color: [0, 153, 255, 0.7], }), }), text: new ol.style.Text({ text: size.toString(), font: 'bold 20px serif', fill: new ol.style.Fill({ color: [0, 0, 0, 1], }), }), }); styleCache[size] = style; } return style; } }); |
Display extent boundary for each cluster on mouse over
We will display an extent boundary for every cluster on mouse over. Basically, this extent boundary will give you an idea to what extent the points in this cluster are spread out. The highlight_layer in the following code snippet at line number 2 will be used for storing and display extent boundaries of cluster feature on mouse over.
The map event define in the following code snippet at line number 11 will generate extent boundary on mouse over. The event for that is pointermove event.
We will use jsts library to get convex hull of cluster at line number 19. The convex hull is the minimum boundary for the set of points it contains. We will use this convex hull polygon as extent boundary and display it on map on mouse over.
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 |
// Layer for mouse over const highlight_layer = new ol.layer.Vector({ title: "Highlight", source: new ol.source.Vector({ projection:map.getView().projection }) }) let parser = new jsts.io.OL3Parser(); // Display extent for each cluster on mouse over map.on("pointermove", (e) => { highlight_layer.getSource().clear(); cluster_layer.getFeatures(e.pixel).then((hoverFeatures) => { if (hoverFeatures.length) { // Get clustered Coordinates const features = hoverFeatures[0].get('features'); if (features.length > 1) { let geom_points = new ol.geom.MultiPoint(features.map((r) => r.getGeometry().getCoordinates())); let geom_points_convexhull = parser.write(parser.read(geom_points).convexHull()); let cluster_polygon = new ol.Feature(geom_points_convexhull); highlight_layer.getSource().addFeature(cluster_polygon); } } }); }); |
Zoom to Cluster Extent on Click
We will zoom to cluster extent on click. We will use openlayers click event for that behavior. On click event, we are getting clustered features and then creating extent for these features and finally zooming into it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// Zoom to cluster on click map.on('click', (e) => { cluster_layer.getFeatures(e.pixel).then((clickedFeatures) => { if (clickedFeatures.length) { // Get clustered Coordinates const features = clickedFeatures[0].get('features'); if (features.length > 1) { const extent = new ol.extent.boundingExtent( features.map((r) => r.getGeometry().getCoordinates()) ); map.getView().fit(extent, {duration: 1000, padding: [50, 50, 50, 50]}); } } }); }); |
I hope this tutorial will create a good foundation for you. If you want tutorials on another GIS topic or you have any queries, please send an email at contact@spatial-dev.guru.
Very good Congratulations. But I have a question: as it is, it groups points from the same layer. If I have two layers, the grouping is done separately. How do I group all layers that are visible on the map? I’ve done it with leaflet, it was very simple, but I’m not getting it with Openlayers.