DurandA
DurandA

Reputation: 1549

Display HTML clusters with custom properties using react-map-gl (Mapbox)

I am trying to adapt the example Display HTML clusters with custom properties for react-map-gl.

I got basic clusters without custom styling working (adapted from Create and style clusters):

<ReactMapGL ref={mapRef}>
    <Source id="poi-modal-geojson" type="geojson" data={pointsToGeoJSONFeatureCollection(points)}
        cluster={true}
        clusterMaxZoom={14}
        clusterRadius={50}
    >
        <Layer {...{
                id: 'clusters',
                type: 'circle',
                source: 'poi-modal-geojson',
                filter: ['has', 'point_count'],
                paint: {
                    'circle-color': [
                        'step',
                        ['get', 'point_count'],
                        '#51bbd6',
                        100,
                        '#f1f075',
                        750,
                        '#f28cb1'
                    ],
                    'circle-radius': [
                        'step',
                        ['get', 'point_count'],
                        20,
                        100,
                        30,
                        750,
                        40
                    ]
                }
        }} />
        <Layer {...{
            id: 'unclustered-point',
            type: 'circle',
            source: 'poi-modal-geojson',
            filter: ['!', ['has', 'point_count']],
            paint: {
                'circle-color': '#11b4da',
                'circle-radius': 4,
                'circle-stroke-width': 1,
                'circle-stroke-color': '#fff'
            }
        }} />
    </Source>
</ReactMapGL>

Here, pointsToGeoJSONFeatureCollection(points: any[]): GeoJSON.FeatureCollection<GeoJSON.Geometry> is a function returning a GeoJSON (adapted from here).

However, I need more complex styling of markers and I am trying to adapt Display HTML clusters with custom properties without success so far. I mainly tried to adapt updateMarkers() and to call it inside useEffect():

const mapRef: React.Ref<MapRef> = React.createRef();
const markers: any = {};
let markersOnScreen: any = {};

useEffect(() => {
    const map = mapRef.current.getMap();

    function updateMarkers() {
        const newMarkers: any = {};
        const features = map.querySourceFeatures('poi-modal-geojson');

        // for every cluster on the screen, create an HTML marker for it (if we didn't yet),
        // and add it to the map if it's not there already
        for (const feature of features) {
            const coords = feature.geometry.coordinates;
            const props = feature.properties;
            if (!props.cluster) continue;
            const id = props.cluster_id;
            
            let marker = markers[id];
            if (!marker) {
                let markerProps = {
                    key: 'marker' + id,
                    longitude: coords[0],
                    latitude: coords[1],
                    className: 'mapboxgl-marker-start'
                }
                const el = React.createElement(Marker, markerProps, null),
                marker = markers[id] = el;
            }
            newMarkers[id] = marker;
            
            if (!markersOnScreen[id]) {
                // TODO re-add
                // marker.addTo(map);
            }
        }
        // for every marker we've added previously, remove those that are no longer visible
        for (const id in markersOnScreen) {
            if (!newMarkers[id]) delete markersOnScreen[id];
        }
        markersOnScreen = newMarkers;
    }

    // after the GeoJSON data is loaded, update markers on the screen on every frame
    map.on('render', () => {
        if (!map.isSourceLoaded('poi-modal-geojson')) return;
        updateMarkers();
    });
}, [points]);

Unfortunately, the Marker created using React.createElement() isn't displayed I am not sure what is the right approach to create Marker elements in updateMarkers() or if my approach is completely wrong.

Upvotes: 4

Views: 2086

Answers (1)

dimimitrije
dimimitrije

Reputation: 9

There is a great article on marker clustering which uses the supercluster and use-supercluster libraries and it makes clustering really easy not only for map box but for other map libraries as well, you can find it here.

You just have to convert your points into GeoJSON Feature objects in order to pass them to the useSupercluster hook and for the calculations to work. It will return an array of points and clusters depending on your current viewport, and you can map through it and display the elements accordingly based on the element.properties.cluster flag.

The properties property of the GeoJSON Feature object can be custom so you can pass whatever you need to display the markers later on when you get the final cluster array.

Upvotes: 1

Related Questions