POI cluster

Create and style POI cluster on the map.

HTML Copy
<!DOCTYPE html>
<html>
<head>
    <title>POI cluster</title>
    <script src="https://maps-api.planplus.rs/script/maplibre/maplibre-gl/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"></script>    
    <link href="https://maps-api.planplus.rs/style/maplibre/maplibre-gl.css" rel="stylesheet" />
</head>
<body>
    <div id="map" style="width: 500px; height: 500px;"></div>
    <script>
        // your license id
        var lid = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
        // map instance
        var map = new maplibregl.Map({
            container: 'map',
            style: 'https://vt.planplus.rs/mapstyle/PPMapaStyle.json?lid=' + lid,
            center: [20.472570, 44.812017],
            zoom: 12
        });
        // some raw data
        var data = [{ id: 2476, name: 'Osnovna škola Dragan Hercog', lat: 44.8024504366899, lng: 20.4608906892426 }, { id: 2482, name: 'Specijalna osnovna škola Dragan Kovačević', lat: 44.8161291934067, lng: 20.4665546723983 }, { id: 4197, name: 'Osnovna škola Vladislav Ribnikar', lat: 44.8057945189666, lng: 20.4677998486387 }, { id: 4201, name: 'Osnovna škola Jovan Miodragović', lat: 44.7998695434026, lng: 20.4788510009828 }, { id: 4202, name: 'Osnovna škola Sveti Sava', lat: 44.8000884216416, lng: 20.4696596074578 }, { id: 4209, name: 'Osnovna škola 1300 kaplara', lat: 44.8049315083437, lng: 20.4979698110708 }, { id: 4211, name: 'Osnovna škola Ivan Goran Kovačić', lat: 44.8053040345129, lng: 20.4825567914832 }, { id: 4247, name: 'Osnovna škola Vlada Aksentijević', lat: 44.8188103810756, lng: 20.4761776695795 }, { id: 4250, name: 'Osnovna škola Jovan Cvijić', lat: 44.8167406418087, lng: 20.5004345743275 }, { id: 4251, name: 'Osnovna škola Oslobodioci Beograda', lat: 44.8126286669451, lng: 20.4843374905621 }, { id: 4252, name: 'Osnovna škola Starina Novak', lat: 44.8096394823043, lng: 20.4780681311934 }, { id: 4267, name: 'Osnovna škola Isidora Sekulić', lat: 44.8108715322556, lng: 20.4565520698824 }, { id: 4268, name: 'Osnovna škola Petar Petrović Njegoš', lat: 44.802788838479, lng: 20.4601291044859 }, { id: 4269, name: 'Osnovna škola Radojka Lakić', lat: 44.8064522998918, lng: 20.4564543141468 }, { id: 4273, name: 'Osnovna škola Vuk Karadžić', lat: 44.8140236331575, lng: 20.472465748008 }, { id: 4274, name: 'Osnovna škola Drinka Pavlović', lat: 44.8133598674518, lng: 20.4651640532474 }, { id: 4277, name: 'Osnovna škola Mihailo Petrović Alas', lat: 44.8218888111007, lng: 20.4588516064445 }, { id: 4278, name: 'Osnovna škola Skadarlija', lat: 44.8187682283535, lng: 20.4650890062026 }, { id: 11354, name: 'Specijalna osnovna škola Boško Buha', lat: 44.802978417045, lng: 20.493827827491 }, { id: 11367, name: 'Osnovna škola Kralj Petar I', lat: 44.8158135538611, lng: 20.4549254151438 }, { id: 71003, name: 'Specijalna osnovna škola za decu oštećenog sluha Stefan Dečanski', lat: 44.8011381638102, lng: 20.4612441445446 }, { id: 4062, name: 'Tehnička škola GSP', lat: 44.7981468607353, lng: 20.4800040132043 }, { id: 4063, name: 'Treća beogradska gimnazija', lat: 44.8052678735687, lng: 20.4667933606173 }, { id: 4064, name: 'Četrnaesta beogradska gimnazija', lat: 44.8031567485232, lng: 20.4754211542058 }, { id: 4067, name: 'Građevinska škola Beograd', lat: 44.8037591936532, lng: 20.4953758787323 }, { id: 4069, name: 'Škola za mašinstvo i umetničke zanate Tehnoart', lat: 44.8040012269763, lng: 20.4951588812373 }, { id: 4070, name: 'Medicinska škola', lat: 44.8041655238696, lng: 20.4997967394873 }, { id: 4666, name: 'Elektrotehnička škola Rade Končar', lat: 44.8149300626102, lng: 20.4817883915164 }, { id: 4667, name: 'Železnička tehnička škola', lat: 44.8140167676959, lng: 20.4828206342186 }, { id: 4668, name: 'Peta beogradska gimnazija', lat: 44.8107018427197, lng: 20.4722556759641 }, { id: 4670, name: 'Srednja tehnička PTT škola', lat: 44.8144802782574, lng: 20.4838279800114 }, { id: 4675, name: 'Zubotehnička škola Beograd', lat: 44.80501344595, lng: 20.4900177351456 }, { id: 4676, name: 'Tehnička škola za dizajn kože', lat: 44.8146757163608, lng: 20.4548755680376 }, { id: 4678, name: 'Filološka gimnazija', lat: 44.8131831697602, lng: 20.4562902433345 }, { id: 4684, name: 'Elektrotehnička škola Nikola Tesla', lat: 44.810214913363, lng: 20.4604394306718 }, { id: 4686, name: 'Elektrotehnička škola Stari grad', lat: 44.8246793137258, lng: 20.4628216268233 }, { id: 4689, name: 'Pravno poslovna škola', lat: 44.8126247149949, lng: 20.4700519714297 }, { id: 4690, name: 'Prva beogradska gimnazija', lat: 44.8208656311035, lng: 20.4651069641113 }, { id: 4691, name: 'Prva ekonomska škola', lat: 44.8164132246178, lng: 20.4657049331477 }, { id: 4692, name: 'Tehnička škola Drvo art', lat: 44.8241493768562, lng: 20.4592099214078 }, { id: 4694, name: 'Trgovačka škola', lat: 44.8155604840529, lng: 20.4661805830109 }, { id: 5236, name: 'Matematička gimnazija', lat: 44.8092130068109, lng: 20.4617255902319 }, { id: 42796, name: 'Srednja škola Sveti Sava', lat: 44.8146133422852, lng: 20.4620800018311 }, { id: 67290, name: 'Privatna gimnazija Milena Pavlović Barili', lat: 44.8143925368513, lng: 20.4590284028988 }, { id: 73994, name: 'Srpska pravoslavna Bogoslovija Svetog Save', lat: 44.815658549811, lng: 20.4917450648867 }, { id: 88716, name: 'Srednja tehnička škola Mihajlo Pupin', lat: 44.8132438659668, lng: 20.4634075164795 }, { id: 100846, name: 'Gimnazija plus Beograd', lat: 44.8130511721321, lng: 20.461484173 }, { id: 101002, name: 'Sportska gimnazija', lat: 44.8209514844281, lng: 20.4701268219548 }, { id: 150135, name: 'Gimnazija The Merit School', lat: 44.8169898986816, lng: 20.4630126953125 }];
        // geoJson data set
        var geojsonData = {
            type: 'FeatureCollection',
            features: data.map(i => {
                return {
                    type: 'Feature',
                    properties: {
                        id: i.id,
                        name: i.name
                    },
                    geometry: {
                        type: 'Point',
                        coordinates: [i.lng, i.lat]
                    }
                }
            })
        }
        // add cluster after map load
        map.on('load', () => {
            // add cluster data source
            map.addSource('poi-cluster', {
                type: 'geojson',
                data: geojsonData,
                cluster: true,
                clusterMaxZoom: 16, // max zoom to cluster points on
                clusterRadius: 50 // radius of each cluster when clustering points (defaults to 50)
            });
            // add cluster layer
            map.addLayer({
                id: 'clusters',
                type: 'circle',
                source: 'poi-cluster',
                filter: ['has', 'point_count'],
                paint: {
                    // style using point_count
                    'circle-color': [
                        'step',
                        ['get', 'point_count'],
                        '#6ecc3999', 10, // less then 10
                        '#f0c20c99', 25, // less then 25
                        '#f1801799' // else
                    ],
                    'circle-radius': [
                        'step',
                        ['get', 'point_count'],
                        20, 10, // less then 10
                        30, 25, // less then 25
                        40 // else
                    ]
                }
            });
            // point count layer
            map.addLayer({
                id: 'cluster-count',
                type: 'symbol',
                source: 'poi-cluster',
                filter: ['has', 'point_count'],
                layout: {
                    'text-field': '{point_count_abbreviated}',
                    'text-font': ['Roboto Regular'],
                    'text-size': 12
                }
            });
            // unclustered point layer
            map.addLayer({
                id: 'unclustered-point',
                type: 'circle',
                source: 'poi-cluster',
                filter: ['!', ['has', 'point_count']],
                paint: {
                    'circle-color': '#0071e3',
                    'circle-radius': 4,
                    'circle-stroke-width': 1,
                    'circle-stroke-color': '#fff'
                }
            });
            // inspect a cluster on click
            map.on('click', 'clusters', async (e) => {
                var features = map.queryRenderedFeatures(e.point, {
                    layers: ['clusters']
                });
                var clusterId = features[0].properties.cluster_id;
                var zoom = await map.getSource('poi-cluster').getClusterExpansionZoom(clusterId);
                map.easeTo({
                    center: features[0].geometry.coordinates,
                    zoom
                });
            });
            // unclustered point on click
            map.on('click', 'unclustered-point', (e) => {
                var coordinates = e.features[0].geometry.coordinates.slice();
                var name = e.features[0].properties.name;
                // 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;
                }
                // open popup
                new maplibregl.Popup()
                    .setLngLat(coordinates)
                    .setHTML(`<h4>${name}</h4>`)
                    .addTo(map);
            });
            // mouse onhover events
            map.on('mouseenter', 'clusters', () => {
                map.getCanvas().style.cursor = 'pointer';
            });
            map.on('mouseleave', 'clusters', () => {
                map.getCanvas().style.cursor = '';
            });
            map.on('mouseenter', 'unclustered-point', () => {
                map.getCanvas().style.cursor = 'pointer';
            });
            map.on('mouseleave', 'unclustered-point', () => {
                map.getCanvas().style.cursor = '';
            });
        });
    </script>
</body>
</html>