Skip to content

Create and style clusters

Create and style point data as clusters.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <script src="https://cdn.smartmaps.cloud/packages/smartmaps/smartmaps-gl/v2/umd/smartmaps-gl.min.js"></script>
        <style>
            body {
                margin: 0;
                padding: 0;
            }

            html,
            body,
            #map {
                height: 100%;
            }
        </style>
    </head>

    <body>
        <div id="map"></div>
        <script>
            const map = new smartmapsgl.Map({
                apiKey: 'INSERT API-KEY',
                container: 'map',
                center: [-120, 50],
                zoom: 2,
                style: 'essential'
            });

            map.on('load', () => {
                // Add a new GeoJSON data source and set the 'cluster' option to true.
                map.addSource('earthquakes', {
                    type: 'geojson',
                    // Point to GeoJSON data
                    data: '../data/earthquakes.geojson',
                    cluster: true,
                    clusterMaxZoom: 14, // Max zoom to cluster points on
                    clusterRadius: 50 // Radius of each cluster when clustering points (defaults to 50)
                });

                map.addLayer({
                    id: 'clusters',
                    type: 'circle',
                    source: 'earthquakes',
                    filter: ['has', 'point_count'],
                    paint: {
                        // Use step expressions with three steps to implement three types of circles:
                        //   * Blue, 20px circles when point count is less than 100
                        //   * Yellow, 30px circles when point count is between 100 and 750
                        //   * Pink, 40px circles when point count is greater than or equal to 750
                        'circle-color': ['step', ['get', 'point_count'], '#51bbd6', 100, '#f1f075', 750, '#f28cb1'],
                        'circle-radius': ['step', ['get', 'point_count'], 20, 100, 30, 750, 40]
                    }
                });

                map.addLayer({
                    id: 'cluster-count',
                    type: 'symbol',
                    source: 'earthquakes',
                    filter: ['has', 'point_count'],
                    layout: {
                        'text-field': '{point_count_abbreviated}',
                        'text-size': 12
                    }
                });

                map.addLayer({
                    id: 'unclustered-point',
                    type: 'circle',
                    source: 'earthquakes',
                    filter: ['!', ['has', 'point_count']],
                    paint: {
                        'circle-color': '#11b4da',
                        'circle-radius': 4,
                        'circle-stroke-width': 1,
                        'circle-stroke-color': '#fff'
                    }
                });

                // Inspect a cluster on click
                map.on('click', 'clusters', async (e) => {
                    const features = map.queryRenderedFeatures(e.point, {
                        layers: ['clusters']
                    });
                    const clusterId = features[0].properties.cluster_id;
                    const zoom = await map.getSource('earthquakes').getClusterExpansionZoom(clusterId);
                    map.easeTo({
                        center: features[0].geometry.coordinates,
                        zoom
                    });
                });

                // When a click event occurs on a feature in the unclustered-point layer,
                // open a popup at the location of the feature,
                // with a description of its properties.
                map.on('click', 'unclustered-point', (e) => {
                    const coordinates = e.features[0].geometry.coordinates.slice();
                    const mag = e.features[0].properties.mag;
                    let tsunami;

                    if (e.features[0].properties.tsunami === 1) {
                        tsunami = 'yes';
                    } else {
                        tsunami = 'no';
                    }

                    // Ensure that if the map is zoomed out such that
                    // multiple copies of the feature are visible, the
                    // popup appears over the copy being pointed to.
                    while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
                        coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
                    }

                    new smartmapsgl.Popup()
                        .setLngLat(coordinates)
                        .setHTML(`magnitude: ${mag}<br>Was there a tsunami?: ${tsunami}`)
                        .addTo(map);
                });

                map.on('mouseenter', 'clusters', () => {
                    map.getCanvas().style.cursor = 'pointer';
                });
                map.on('mouseleave', 'clusters', () => {
                    map.getCanvas().style.cursor = '';
                });
            });
        </script>
    </body>
</html>