Applying clustering on OpenLayers map

With OpenLayers you can do many wonderful map-related things on your web page that you would normally use Google Map or Bing Map for. And, unlike Google or Bing, you won’t have to pay a penny to do so.

In my previous article, I have showed you the simplest way of setting up a map with a clickable location marker. Although the example showed you many basic concepts behind OpenLayers, it was somewhat limited. It certainly wasn’t suitable for a production-ready map.

In this article, we will expand on it further. In fact, you will learn enough to have a fully functioning map that you will be able to add to your public-facing website. You will just be able to take the code provided, modify the data to suit your needs and insert it directly into WordPress editor, or any other CMS you are using.

Getting started

In this example, we will be, once again, using a single HTML file that contains everything the web page requires, except for the links to third party resources.

Full code is available below:

OpenLayers clustering markup

The fully-functioning map is available here:

OpenLayers clustering example

Getting layer data dynamically

In our previous example, we have provided a single marker point that opens up a details dialog when clicked. However, in a real-life scenario, you would extract these values from the data.

In the current example, we have this data structure:

var jsonData = 
{
 "odessa": {
  "title": "Odessa Restaurant, Kyiv",
  "long": 30.5191,
  "lat": 50.4227,
  "imgSrc": "https://media-cdn.tripadvisor.com/media/photo-s/04/49/ca/08/odessa-restaurant.jpg",
  "url": "https://odessarest.com.ua"
 },
 "example": {
  "title": "Example marker",
  "long": 31.5191,
  "lat": 51.4227,
  "imgSrc": "https://images-na.ssl-images-amazon.com/images/I/61PcbNuRRfL._SX425_.jpg",
  "url": "https://mobiletechtracker.co.uk/"
 }
};

We could still populate the data dynamically by making a request to a back-end service. AJAX is a good way of doing so. However, to demonstrate what the data consists of, we have it on the page.

We have our data populated with the values above. However, you can use any values, as long as they adhere to the same format. And you can use any arbitrary number of data points.

The place where we use the variable that contains the data points doesn’t care what exact values you have in there. It looks like this:

function setIconFeatures() {
 for(var key in jsonData) {			
  var jsonItem = jsonData[key];
				
  var iconFeature = new ol.Feature(new ol.geom.Point(ol.proj.fromLonLat([jsonItem.long, jsonItem.lat])));
  iconFeature.setId(key);
  iconFeature.set('style', createStyle('https://openlayers.org/en/latest/examples/data/icon.png', undefined));
  iconFeatures.push(iconFeature);
 }
}

Now, if you click on any of these points, it will populate the modal dialog box with the data specific to the point. It’s doing so like this:

var key = feature.getId();		
if (key in jsonData) {
 var jsonItem = jsonData[key];
						
 document.getElementById("modalTitle").innerText = jsonItem.title;
 document.getElementById("modalImage").src = jsonItem.imgSrc;
 document.getElementById("modalUrl").href = jsonItem.url;
					
 modal.style.display = "block";
}

Applying clustering

One issue with having many points on the map is that your map becomes crowded when you zoom out. High number of points on the screen at once may also negatively affect loading times.

To solve this issue, OpenLayers comes with the ability to cluster points. Instead of always showing individual points, it will show a circle with a number that tells you how many points you have within the vicinity of that location.

So, if you have 10 markers around a particular area, it will show them as individual markers when you zoom in closely and a single circle with the number “10” when you zoom out.

To implement this feature in OpenLayers, we need to create a variable containing a clustered layer. This can be done like this:

var clusters = new ol.layer.Vector({
 source: clusterSource,
 style: function(feature) {
  var size = feature.get('features').length;
  var style = styleCache[size];
  if (!style) {
   style = new ol.style.Style({
    image: new ol.style.Circle({
     radius: 10,
     stroke: new ol.style.Stroke({
      color: '#fff'
     }),
     fill: new ol.style.Fill({
      color: '#3399CC'
     })
    }),
    text: new ol.style.Text({
     text: size.toString(),
     fill: new ol.style.Fill({
      color: '#fff'
     })
    })
   });
   styleCache[size] = style;
  }
  return style;
 },
 minResolution: 2001
});

Then, we just add this layer to the map along with the original layer and the base layer:

var map = new ol.Map({
 target: 'map',
 layers: [raster, clusters, unclusteredLayer],
 view: new ol.View({
  center: ol.proj.fromLonLat([30.5191, 50.4227]),
  zoom: 6
 })
});

Hide layers at different zoom levels

One disadvantage of clustered layer is that it keeps showing circles with “1” written on them when you zoom in close enough to see individual markers. Therefore, we need to disable this layer at particular zoom levels. Likewise, you will need to disable the original layer containing individual markers when you are zoomed out.

To achieve both of these goals, you apply “maxResolution” and “minResolution” fields on both layers. You can fiddle the exact values to see what suits you best.

Automatically zoom from clustered layer

Having a clustered layer is useful when you are panning over a large area. However, one additional feature you can add is the ability to instantly zoom into a particular area when you click on any of the cluster circles.

This can be achieved by expanding onclick event of the map with the flowing:

else if (map.getView().getZoom() < 7) {
 map.getView().fit(feature.getGeometry(), {padding: [170, 50, 30, 150], minResolution: 1000});
}

Going further

The example I have provided is good enough to be used in production if you are dealing with relatively few (less than 100) data points. However, if you are likely to deal with more, you will need to learn how to populate the data from the back-end dynamically. AJAX will help with that, but you will also need a back-end service that you can make AJAX calls to via REST API.

If your back-end is built in ASP.NET, you can use SignalR instead of AJAX. This way, you will be able to get real-time data updates to your web page as soon as it changes in the back-end.

Another important point to note is that this example relied on pre-built OpenLayers components that can be linked to via the web. However, you can also choose to install and compile OpenLayers components via NPM.

This approach will ensure that you will only ever load those modules that you need, instead of the entire OpenLayers code. On the other hand, this approach requires higher level of expertise in front-end development, so won’t be suitable for everyone.

It is beyond the scope of this article how to do it, but a lot of the code will be similar. The official OpenLayers examples page contains a lot of examples that implement modular approach.

Happy hacking!