Ibra
Ibra

Reputation: 1072

How to cluster custom HTML markers in mapbox-gl-js

I want to create html markers with clusters, following the mapbox-gl-js example/cluster-html and this stackoverflow question.

Traditionally, I used map.addLayer to add individual 'symbol' markers.

    // Unclustrered; Single Listing Styling
    // map.addLayer({
    //     id: 'unclustered-point',
    //     type: 'symbol',
    //     source: 'earthquakes',
    //     filter: ['!', ['has', 'point_count']],
    //     paint: {
    //         'text-color': 'white',
    //         'text-halo-width': 5, // Adjust the halo width as needed
    //         'text-halo-color': [
    //             'case',
    //             ['boolean', ['feature-state', 'hover'], false],
    //             '#07364B', // Replace with the desired hover halo color
    //             '#0097A7' // Replace with the default halo color
    //         ],
    //     },
    //     layout: {
    //         'text-field': ['get', 'price'],
    //         // 'text-font': ['DIN Offc Pro Medium', 'Arial Unicode MS Bold'],
    //         'text-size': 25,
    //         'text-offset': [0, 0.6], // Adjust the offset as needed
    //         'text-anchor': 'top',
    //         'text-allow-overlap': true
    //     },
    // });

Current approach:

Since type 'symbol' layer doesn't support HTML elements as a marker, I'm using a combination of both examples. After the GeoJSON data is loaded, update markers on the screen on every frame using

map.on('render', () => {
if (!map.isSourceLoaded('earthquakes')) return;
updateMarkers();
});

Where UpdateMarkers() is:

// objects for caching and keeping track of HTML marker objects (for performance)
const markers = {};
let markersOnScreen = {};
 
function updateMarkers() {
const newMarkers = {};
const features = map.querySourceFeatures('earthquakes');
 
// 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) {
                    const el = document.createElement('div');
                    el.classList.add('mapCluster');
                    el.innerText = props.price;



marker = markers[id] = new mapboxgl.Marker({
element: el
}).setLngLat(coords);
}
newMarkers[id] = marker;
 
if (!markersOnScreen[id]) 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]) markersOnScreen[id].remove();
}
markersOnScreen = newMarkers;

}

Current Approach yields following result

Desired functionality:

The current approach doesn't render/cluster the features correctly and undefined marker value show up. How can I display the markers using the current approach while clustering and feature.price value show up correct.

In addition, I logged in feature while looping through features and it appears that it also returns cluster properties.

Upvotes: 0

Views: 429

Answers (1)

Ibra
Ibra

Reputation: 1072

I solved it by targeting the listings instead of clusters.

Changed (!props.cluster) continue to (props.cluster) continue.

This skips the 'feature' that is a cluster in the for loop.

Then instead of const id = props.cluster_id; to get the id of the cluster, I'm using the individual id of the unclustered feature.You can use any other UID property in your feature to distinguish marker.

Upvotes: 0

Related Questions