Clustering point features
This example provides an overview of how to use the Mapbox Tiling Service (MTS) to cluster points into regions and visualize the point data as clusters in a map.
For a full tutorial on how to process your own data and render the clustered tileset in a map, see our Cluster point data with Mapbox Tiling Service tutorial.
To cluster point data in a tileset, you must use the Vector Recipe specification on a tileset source. The code snippet below shows the recipe used to create a tileset with clustered data in this example.
{
"version": 1,
"layers": {
"store-locations-clustered": {
"minzoom": 0,
"maxzoom": 9,
"source": "mapbox://tileset-source/examples/store-locations-source",
"features": {
"attributes": {
"set": { "count": 1 } // Sets the count property to 1 for each point feature
}
},
"tiles": {
"union": [
{
"group_by": [], // empty group_by to union all features
"cluster": true, // enable clustering
"region_count": 64, // number of regions to create
"aggregate": { "count": "sum" } // sum the count property
}
]
}
},
"store-locations": {
"minzoom": 10,
"maxzoom": 14,
"source": "mapbox://tileset-source/examples/store-locations-source"
}
}
}
The finished tileset can be viewed in the Tileset Explorer. Exploring this data shows how it is clustered into regions which split (or join) into new regions as you zoom in and out.
Once your data is processed into a tileset, you can use Mapbox GL JS or one of the mobile Maps SDKs to style the clusters. If you were to log the returned point data from a click event in a renderer, from the tileset above, the returned point's GeoJSON would look like this:
{
type: "Feature",
id: 7183393275474299,
layer: {
id: 'locations',
type: 'circle',
source: 'clustered-data',
source-layer: 'store-locations-clustered',
filter: ['has', 'count'],
paint: {...}
},
properties : {
count: 78
},
source: "clustered-data",
sourceLayer: "store-locations-clustered",
state : {},
tile: {
z: 3,
x: 1,
y: 3,
key: 12848
},
}
The cluster feature's properties include count
, specifying how many point features from the original dataset it represents. The count
property can be used to add a number label or for data driven styling.
The code snippet below shows how to add the clustered tileset to a web map and style it at runtime using step
expressions, which leverage the count
property to determine the color and size of the cluster circle.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Clustering point features</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<link href="https://api.mapbox.com/mapbox-gl-js/v3.5.1/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v3.5.1/mapbox-gl.js"></script>
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<div id="map"></div>
<script>
// TO MAKE THE MAP APPEAR YOU MUST
// ADD YOUR ACCESS TOKEN FROM
// https://account.mapbox.com
mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/dark-v11',
zoom: 3,
center: [-100.10899, 39.15742]
});
map.on('load', () => {
map.addSource('clustered-data', {
type: 'vector',
// Load our custom tileset as a vector source
// Formatted as 'mapbox://{username}.{tileset-id}'
url: 'mapbox://examples.cafe-locations-clustered'
});
map.addLayer({
id: 'locations',
type: 'circle',
source: 'clustered-data',
// 'source-layer' must reference the layer within the tileset that contains the clustered data
'source-layer': 'locations',
filter: ['has', 'count'],
paint: {
// The circle-color value uses a step expression to change the color of the cluster based on the count - more about step expressions https://docs.mapbox.com/style-spec/reference/expressions/#step
'circle-color': [
'step',
['get', 'count'],
'#51bbd6',
10,
'#f1f075',
20,
'#f28cb1',
75,
'#ac41bf'
],
'circle-opacity': 0.8,
// The circle-radius value uses a step expression to change the size of the circle based on the count
'circle-radius': [
'step',
['get', 'count'],
5,
2,
12,
10,
17,
20,
25,
50,
32,
100,
36
]
}
});
map.addLayer({
id: 'cluster-count',
type: 'symbol',
source: 'clustered-data',
'source-layer': 'locations',
filter: ['>', ['get', 'count'], 1],
layout: {
'text-field': ['get', 'count'],
'text-font': ['DIN Offc Pro Medium', 'Arial Unicode MS Bold'],
'text-size': 14
}
});
});
</script>
</body>
</html>