When displaying a large data set that might contain several thousand points on a map, you might encounter some potential issues. Firstly, performance degradation might occur if all the points (also called "markers") are visible at lower zoom levels. Secondly, markers in close geographic proximity might overlap and even obscure each other at lower zoom levels.
To address these issues, you can use clustering. Clustering involves an algorithm that collapses two or more points that are located near each other on the screen into a single cluster point. All other points that are not collapsed are still visible on the map as "noise points."
The following figure shows the noise points as blue dots and clusters as green and orange markers, with each cluster displaying the cumulative weight of the data points it combines:
Figure 1. A Map with clusters and noise points
The following sections use a simple practical example of data points that represent airports in London, UK, to walk you through the process of creating a cluster data set and then displaying it on a map:
To create a web page with a map and basic interactions, start by creating an HTML file that references a JavaScript code snippet responsible for rendering the map.
Within yuor JavaScript snippet, create a variable that stores data points to display as clusters on the map, as shown in the following example:
var dataPoints =[];
dataPoints.push(newH.clustering.DataPoint(51.4748,-0.368));// City Airport (LCY)
dataPoints.push(newH.clustering.DataPoint(51.8897,0.2629));// Biggin Hill Airport (BQH)
dataPoints.push(newH.clustering.DataPoint(51.4508,-0.1546));// Heathrow Airport (LHR)
dataPoints.push(newH.clustering.DataPoint(51.3483,-0.5037));// Gatwick Airport (LGW)
dataPoints.push(newH.clustering.DataPoint(51.1537,-0.1821));// Luton Airport (LTN)
dataPoints.push(newH.clustering.DataPoint(51.7588,-0.262));// Stansted Airport (STN)
In this example, the code declares a variable called dataPoints and initializes it as an empty array. Then, the code adds new H.clustering.DataPoint objects to the array by using the push() method. Each H.clustering.DataPoint object represents a location of an airport in London, UK, and is created with latitude and longitude values.
Note
You can modify the data set by adding, removing or completely replacing the datapoints. Calling these methods triggers reclustering.
Display cluster data on the map
Initialize a HERE Maps clustering data provider and layer. Then, add the data points to the map as a clustered layer. For example, see the following code snippet:
// Create a data provider:var clusteredDataProvider =newH.clustering.Provider(dataPoints);// Create a layer that includes the data provider and its data points: var layer =newH.map.layer.ObjectLayer(clusteredDataProvider);// Add the layer to the map:
map.addLayer(layer);
This code creates an H.clustering.Provider object that runs the clustering algorithm and groups data points, depending on their screen density. The H.map.layer.ObjectLayer object adds the clustered data points on the map.
Result: The following figure shows how the HERE clustering algorithm renders the airport data set on the map as clusters or individual data points, depending on the zoom level:
Figure 2. Clustering (initial view)
Zooming in causes the cluster to divide into individual data points:
Figure 3. Clustering (zoom in)
Fine-tune cluster creation
To customize the clustering algorithm for your use case, you can adjust the parameters of the H.clustering.Provider class.
This class includes properties that determine how clusters and noise points are visually represented, the maximum radius for data points to be considered part of a cluster, and the minimum weight required to form a cluster.
To adjust clustering behavior to your needs, update the H.clustering.Provider instance with your customizations. For example:
var clusteredDataProvider =newH.clustering.Provider(dataPoints,{min:4,max:10,clusteringOptions:{eps:32,minWeight:3}});
where:
The min and max properties are not part of the configurable options of the H.clustering.Provider class. These options define the zoom level range in which clustering occurs.
The eps property determines the maximum radius within which data points are considered as part of a cluster.
For example, in the code example, the eps value is set to 32. This means that if two data points are within 32 pixels of each other on the map, the clustering algorithm considers them as part of the same cluster.
The minWeight property determines the minimum weight required to form a cluster.
In the example provided, the minWeight property is set to 3, which means that three data points with a combined weight of at least 3 are required to form a cluster. For example, if there are three data points each with a weight of 1, or two data points with a weight of 2 and one data point with a weight of 1, they are clustered together. However, if there are only two data points each with a weight of 1, they are not clustered together.
Customize theming
On the map, clusters and noise points are depicted by using markers. By default, the clustering provider utilizes the default bitmap markers theme, which incorporates weight information to display clusters and noise points on the map. To adjust marker look and feel to your use case, you can create a custom theme and set it as the value of the theme property when calling the provider.
Define an SVG template to use for noise icons. The following template consists of an SVG element with a green circle inside:
// SVG template to use for noise iconsvar noiseSvg ='<svg xmlns="http://www.w3.org/2000/svg" height="20px" width="20px"><circle cx="5px" cy="5px" r="5px" fill="green" /></svg>';
Create an H.map.Icon object by using the noiseSvg template. Specify the size and anchor of the icon in pixels:
// Create an icon to represent the noise points// Note that same icon will be used for all noise pointsvar noiseIcon =newH.map.Icon(noiseSvg,{size:{w:20,h:20},anchor:{x:10,y:10},});
Define an SVG template to use for cluster icons. The following template consists of an SVG element with a red circle inside, with the size and position of the circle specified using variables:
// SVG template to use for cluster iconsvar clusterSvgTemplate ='<svg xmlns="http://www.w3.org/2000/svg" height="{diameter}" width="{diameter}">'+'<circle cx="{radius}px" cy="{radius}px" r="{radius}px" fill="red" />'+'</svg>';
Modify the H.clustering.Provider object to take an array of dataPoints and a theme implementation as parameters:
// Create a clustered data provider and a theme implementationvar clusteredDataProvider =newH.clustering.Provider(dataPoints,{theme:{getClusterPresentation:function(cluster){// Use cluster weight to change the icon sizevar weight = cluster.getWeight(),// Calculate circle size
radius = weight *5,
diameter = radius *2,// Replace variables in the icon template
svgString = clusterSvgTemplate.replace(/\{radius\}/g, radius).replace(/\{diameter\}/g, diameter);// Create an icon// Note that we create a different icon depending from the weight of the cluster
clusterIcon =newH.map.Icon(svgString,{size:{w: diameter,h: diameter},anchor:{x: radius,y: radius}}),// Create a marker for the cluster
clusterMarker =newH.map.Marker(cluster.getPosition(),{icon: clusterIcon,// Set min/max zoom with values from the cluster, otherwise// clusters will be shown at all zoom levelsmin: cluster.getMinZoom(),max: cluster.getMaxZoom()});// Bind cluster data to the marker
clusterMarker.setData(cluster);return clusterMarker;},getNoisePresentation:function(noisePoint){// Create a marker for noise points:var noiseMarker =newH.map.Marker(noisePoint.getPosition(),{icon: noiseIcon,// Use min zoom from a noise point to show it correctly at certain zoom levelsmin: noisePoint.getMinZoom()});// Bind noise point data to the marker:
noiseMarker.setData(noisePoint);return noiseMarker;}}});
The theme implementation specifies how the clusters and noise points are displayed on the map, where:
The getClusterPresentation() function calculates the size and position of the cluster icon using the weight of the cluster and the SVG template that you defined earlier. The function then creates an H.map.Marker object for the cluster by using the icon, minimum and maximum zoom levels, and data for the marker.
The getNoisePresentation() function creates an H.map.Marker object for each noise point by using the same SVG template that you defined earlier and sets the icon and minimum zoom level for the marker.
Result: The following figure shows the result of using a custom theme for clustering data points. In this figure, cluster markers are represented as red circles and noise points are represented as green circles.:
Figure 4. Clustering custom theme
Interact with markers
You can interact with markers representing clusters and noise points and access cluster (H.clustering.ICluster) or noise point (H.clustering.INoisePoint) data on demand by using map events.
To process a specific event type in the clustering provider, you can add an event listener for that type.
In the following example, an event listener is added to the clustered data provider to detect when a marker has been tapped. When a marker is tapped, the latitude and longitude of the marker are logged to the console, depending on the marker type.
// Add an event listener to the Provider - this listener is called when a marker has been tapped:
clusteredDataProvider.addEventListener('tap',function(event){// Get the clicked marker - either a cluster or a noise point:var marker = event.target;// Get the marker implementing IResult interface:var point = marker.getData();// Is the marker a cluster?var isCluster = point.isCluster();if(isCluster){// Log all the noise points inside the cluster along with their geographical position:
console.group("Cluster was tapped");
point.forEachDataPoint((dataPoint)=>{
console.log("Noise point at "+ dataPoint.getPosition().lat +", "+ dataPoint.getPosition().lng);});
console.groupEnd();}else{// We tapped a noise point
console.log("Noise point at "+ point.getPosition().lat +", "+ point.getPosition().lng +" was tapped.");}},false);
The following figure shows the output in the developer console window:
Source code
This section contains the full JavaScript code that was used in this tutorial. This code sets up a map and adds multiple markers to it, which are clustered together based on their proximity. The code also defines how the markers should look and behave when clicked, as well as adding an event listener to log the latitude and longitude of any clicked markers. Finally, the code enables default pan and zoom interactions on the map.
const platform =newH.service.Platform({'apikey':'YOUR API KEY'});// Obtain the default map types from the platform object:const defaultLayers = platform.createDefaultLayers();// Instantiate (and display) a map:var map =newH.Map(
document.getElementById("map"),
defaultLayers.vector.normal.map,{zoom:7,center:{lat:51.509865,lng:-0.118092}});var dataPoints =[];
dataPoints.push(newH.clustering.DataPoint(51.4748,-0.368));//London City Airport (LCY)
dataPoints.push(newH.clustering.DataPoint(51.8897,0.2629));//London Biggin Hill Airport (BQH)
dataPoints.push(newH.clustering.DataPoint(51.4508,-0.1546));//London Heathrow Airport (LHR)
dataPoints.push(newH.clustering.DataPoint(51.3483,-0.5037));//London Gatwick Airport (LGW)
dataPoints.push(newH.clustering.DataPoint(51.1537,-0.1821));//London Luton Airport (LTN)
dataPoints.push(newH.clustering.DataPoint(51.7588,-0.262));//London Stansted Airport (STN)// SVG template to use for noise iconsvar noiseSvg ='<svg xmlns="http://www.w3.org/2000/svg" height="20px" width="20px"><circle cx="5px" cy="5px" r="5px" fill="green" /></svg>';// Create an icon to represent the noise points// Note that same icon will be used for all noise pointsvar noiseIcon =newH.map.Icon(noiseSvg,{size:{w:20,h:20},anchor:{x:10,y:10},});// SVG template to use for cluster iconsvar clusterSvgTemplate ='<svg xmlns="http://www.w3.org/2000/svg" height="{diameter}" width="{diameter}">'+'<circle cx="{radius}px" cy="{radius}px" r="{radius}px" fill="red" />'+'</svg>';// Create a clustered data provider and a theme implementationvar clusteredDataProvider =newH.clustering.Provider(dataPoints,{theme:{getClusterPresentation:function(cluster){// Use cluster weight to change the icon sizevar weight = cluster.getWeight(),// Calculate circle size
radius = weight *5,
diameter = radius *2,// Replace variables in the icon template
svgString = clusterSvgTemplate.replace(/\{radius\}/g, radius).replace(/\{diameter\}/g, diameter);// Create an icon// Note that we create a different icon depending from the weight of the cluster
clusterIcon =newH.map.Icon(svgString,{size:{w: diameter,h: diameter},anchor:{x: radius,y: radius}}),// Create a marker for the cluster
clusterMarker =newH.map.Marker(cluster.getPosition(),{icon: clusterIcon,// Set min/max zoom with values from the cluster, otherwise// clusters will be shown at all zoom levelsmin: cluster.getMinZoom(),max: cluster.getMaxZoom()});// Bind cluster data to the marker
clusterMarker.setData(cluster);return clusterMarker;},getNoisePresentation:function(noisePoint){// Create a marker for noise points:var noiseMarker =newH.map.Marker(noisePoint.getPosition(),{icon: noiseIcon,// Use min zoom from a noise point to show it correctly at certain zoom levelsmin: noisePoint.getMinZoom()});// Bind noise point data to the marker:
noiseMarker.setData(noisePoint);return noiseMarker;}}});// Create a layer that includes the data provider and its data points: var layer =newH.map.layer.ObjectLayer(clusteredDataProvider);// Add the layer to the map:
map.addLayer(layer);// Add an event listener to the Provider - this listener is called when a marker has been tapped:
clusteredDataProvider.addEventListener('tap',function(event){// Get the clicked marker - either a cluster or a noise point:var marker = event.target;// Get the marker implementing IResult interface:var point = marker.getData();// Is the marker a cluster?var isCluster = point.isCluster();if(isCluster){// Log all the noise points inside the cluster along with their geographical position:
console.group("Cluster was tapped");
point.forEachDataPoint((dataPoint)=>{
console.log("Noise point at "+ dataPoint.getPosition().lat +", "+ dataPoint.getPosition().lng);});
console.groupEnd();}else{// We tapped a noise point
console.log("Noise point at "+ point.getPosition().lat +", "+ point.getPosition().lng +" was tapped.");}},false);// MapEvents enables the event system// Behavior implements default interactions for pan/zoom (also on mobile touch environments)const behavior =newH.mapevents.Behavior(newH.mapevents.MapEvents(map));
Next steps
For more information on other Maps API for JavaScript use cases, see Examples.
For more information on the Maps API for JavaScript design, see API Reference.