Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support clustering for HTML "Marker"s #4491

Closed
D1M opened this issue Mar 24, 2017 · 88 comments
Closed

Support clustering for HTML "Marker"s #4491

D1M opened this issue Mar 24, 2017 · 88 comments
Assignees

Comments

@D1M
Copy link

D1M commented Mar 24, 2017

Hello!
As we can see here https://www.mapbox.com/mapbox-gl-js/example/custom-marker-icons/
we could create a custom Marker and add it to map.
e.g. new mapboxgl.Marker(...).setLngLat(...).addTo(map);

I'm trying to find the way how to add marker to the layer and organize into the Cluster.
Smth like https://www.mapbox.com/mapbox-gl-js/example/cluster/ but with custom Markers:

  1. var map = new mapboxgl.Map...
  2. map.addSource(...);
  3. map.addLayer(...);
  4. and here map.getLayer(...).addMarker(Marker). or marker.addto(Layer/Source) or ...
    As a result we should see a Cluster with custom markers.
@lucaswoj lucaswoj changed the title Organize a custom Marker into the Cluster Support clustering for HTML "Marker"s Mar 24, 2017
@lucaswoj
Copy link
Contributor

Hi @D1M! We do not support clustering on Markers, only on symbol layers. This is a feature we may support in the future but have no immediate plans to add support.

@lucaswoj
Copy link
Contributor

You can use custom icons with a symbol layer via the new Map#addImage method (going out in the next release) or by adding the icons to your sprite in Studio.

@D1M
Copy link
Author

D1M commented Mar 24, 2017

@lucaswoj thanks, looking forward to see such feature in future releases of mapbox-gl!

@D1M D1M closed this as completed Mar 24, 2017
@lucaswoj lucaswoj reopened this Mar 24, 2017
@lucaswoj
Copy link
Contributor

Let's keep this issue open to track the feature 😄

@1ec5
Copy link
Contributor

1ec5 commented Mar 26, 2017

The Android and iOS analogue of this issue is being tracked in mapbox/mapbox-gl-native#5815.

@realph
Copy link

realph commented Apr 5, 2017

+1 I too would like to have custom markers that work with clustering.

@DarkKnight1992
Copy link

+1 would really love to have this feature

@peterqliu
Copy link
Contributor

this is possible now, with https://github.com/mapbox/supercluster. Was originally built for leaflet maps, but can be adapted to GL marker.

@DarkKnight1992
Copy link

that's amazing, you guys are the best !!

@DarkKnight1992
Copy link

How to use this cluster plugin with custom html marker there is not much documentation about it ?? ;(

@peterqliu
Copy link
Contributor

@DarkKnight1992 you are correct that there is no documentation specifically combining supercluster with GL custom markers. The former does provide GeoJSON as output, though, which you can figure out how to adapt as an input for the latter.

@DarkKnight1992
Copy link

@peterqliu but yo can't use custom marker as geojson in Mapbox Gl if i am not wrong ? Since you can't add a source and get a source layer very are using ajax for getting the getting and iteration for the former geojson created for mapbox leaflet to create new markers in Mapbox Gl. Is that the correct way of adding custom markers or mapbox gl provides some kind of api like loadurl() in mapbox leaflet ?

@peterqliu
Copy link
Contributor

but yo can't use custom marker as geojson in Mapbox Gl if i am not wrong ?

@DarkKnight1992 see the first link in the original post above for an example of how to do this

@DarkKnight1992
Copy link

that's how its been done currently

@Wykks
Copy link

Wykks commented May 24, 2017

Implemented in https://github.com/alex3165/react-mapbox-gl
But, as you can see in the demo : http://alex3165.github.io/react-mapbox-gl/, when we rotate/pitch the map, the cluster are messed up.
Because it's not sending the real viewport to supercluster, it's based on the current axis-aligned rectangular bounding box (==> #2112)

const bounds = map.getBounds();
const zoom = map.getZoom();
const newPoints = supercluster.getClusters(
      [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()],
      Math.round(zoom)
);

Does supercluster have a way to get the real viewport ?
How it is done in mapbox-gl ? Because the cluster demo (https://www.mapbox.com/mapbox-gl-js/example/custom-marker-icons/) works fine with rotation/pich, and as far I know, it use supercluster internally.

@peterqliu
Copy link
Contributor

Does supercluster have a way to get the real viewport ?

@Wykks you can adapt unproject() on the four corners, for this purpose.

@DarkKnight1992
Copy link

@peterqliu ran into another problem today and paying very big price for using Mapbox Gl if there is no workaround for this problem. As i know mapbox-gl uses webgl to render images but a vast amount of our user base may not have webgl support. Can we render raster images instead of webgl? is that a possibilty ? If not then it's really been a huge waste of time using mapbox gl :(

@Wykks
Copy link

Wykks commented May 25, 2017

Whoa true, totally missed that, thanks !

@DarkKnight1992 Yep, as far I know mapbox-gl require webgl (hence the suffix -gl), you can use this plugin to test if mapbox-gl is supported : https://github.com/mapbox/mapbox-gl-supported

@DarkKnight1992
Copy link

@Wykks thanks. We didn't wanted to use other plugin for checking the webgl support and only a small script did the trick. But in the end i was really disappointed as we had to load to different libraries for doing the same thing. Shouldn't there be better work around for this ? Because loading a two different libraries mean using two different sets of code as a library and the actual code for the map. which goes against the basic rule of programming - "Never write code again fro the same result".

@peterqliu
Copy link
Contributor

There is documentation for checking webgl support

@atelierhyper
Copy link

atelierhyper commented Aug 1, 2017

Any improvement on this one?

I managed to add custom icon by generating them with the addImage but I get a "SpriteAtlas out of space"...
So I tought that we could have manage our marker manually and add them to a layer and the clustering would work out of the box without having to rebuild everything... it suck that it don't... I have over 500 markers on my map and each of them have an icon with their logos... so I have to add over 500 custom icon and without clustering it's a mess...

Why not, adding the possibility to pass a custom icon url in the GeoJSON to display???

@plucile
Copy link

plucile commented Sep 28, 2017

+1 for marker clustering support

Also, Is there an event triggered when a symbol disappears to be part of a cluster? Using this code https://www.mapbox.com/mapbox-gl-js/example/cluster/

I have a map with markers. On click on a marker, the icon changes to an "active" style and a popup shows up.
Since I want to use clustering, I've changed all my markers to be symbols. The only way I found to have an "active" icon on click is by adding a layer on top of all the symbols with just this point using a different icon. However when zooming out this symbol doesn't get clustered when the "default" one is resulting in the "active" icon displaying on top of the cluster...

Thanks

@guanioramon
Copy link

+1 For marker clustering support
Hi Guys ,
please work around on this.This is most important feature.
I am still using leaflet cluster on mapboxgl to make it working but this is very important feature.

@liampmccabe
Copy link

+1

Also after this, a custom icon url would be perfect!

@rbrundritt
Copy link

What I've done is wait for the load event of the map to fire, then load my source and layer. Then following that call the same logic I have in the moveend event. If there is a decent amount of data in the source, the clustering logic would likely still be running and the initial query for rendered data will be blank. To address this, use the sourcechange event and have it run the same logic as moveend. This will fire when the source data updates which will address this issue and will also allow you to update the source data and have it automatically reflected in your HTML marker layer.

@mourner mourner self-assigned this Jan 16, 2019
@matyushen
Copy link

@rbrundritt Thank you for all the info! It would be really cool if mapbox-gl supported clustering with html markers out of the box.

@miloshevmitko
Copy link

miloshevmitko commented Jan 21, 2019

+1
I believe this feature (clustering of custom html markers) is long overdue.

@matthiasott
Copy link

+1 for clustering of markers. Honestly, I was surprised to learn that this is not possible yet. It just makes so much sense and is a feature many people are used to these days.

@rbrundritt
Copy link

I don't work for Mapbox, so guessing here, but suspect this was left out as the focus appears to be on native render (i.e. symbol layer) which supports much larger data sets. The native functionality supports clustering. HTML markers I think were added more so for those moving from legacy apps or who render a few points but need full HTML/CSS support.

@jonaseberle
Copy link

From first reading this, it might seem to some it is not possible to have clusters/markers with custom icons/HTML popups on mapbox GL which absolutely is possible.

Don't let yourself be fooled! It is absolutely possible and not too hard. Here are some pointers.

  1. use exclusively geoJSON. Add a property to hold your icon name.
  2. make the layer with the unclustered items a symbol layer
    type: 'symbol',
    layout: {
      'icon-image':'{yourIconPropertyName}'
    }
    
  3. add your custom icons with map.loadImage()
  4. react to mouse clicks onto markers and create and open popups
    map.on(
            'click',
            'unclustered-points',
            function (e) {
                var coordinates = e.features[0].geometry.coordinates.slice()
                while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
                    coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360
                }
    
                new mapboxgl.Popup()
                    .setLngLat(coordinates)
                    .setHTML(
                        JSON.stringify(e.features[0].properties) // just an example ;)
                    )
                    .addTo(map)
            }
        )
    

grafik

Dear Mapbox, I suggest you add an example showcasing that.

@rbrundritt
Copy link

rbrundritt commented Mar 7, 2019

@jonaseberle I think you missed the ask for this task. The ask is for the ability to cluster HTML markers. Clustering symbols and displaying popups with HTML content is already well known, and not what people are asking for here. Symbols are great for rendering large data set, but is not nearly as customizable as HTML markers where you have access to the full CSS stack. Here is an example where HTML markers are used to represent clusters. Each cluster marker is a pie chart where each slice of the pie is fully interactive.

image

@jonaseberle
Copy link

Ah, I see. Thank you for the example @rbrundritt . Sorry to having derailed that issue.
It is just that the first post mentions an example where the marker is just an image - which is exactly possible with a symbol geoJSON layer.

@mourner
Copy link
Member

mourner commented Mar 7, 2019

Hey, heads up that I'm working on an official example at the moment — will aim for something like on the screenshot above. Stay tuned in the nearest week!

@mourner
Copy link
Member

mourner commented Mar 12, 2019

The new example with HTML clusters and property aggregation is live — check it out! Thanks to everyone who participated in the thread, and let me know if there's anything we can improve / clarify. https://docs.mapbox.com/mapbox-gl-js/example/cluster-html/

@mourner mourner closed this as completed Mar 12, 2019
@deathlock
Copy link

Excellent work!! Although, I think the only thing missing is when we click on the marker it should zoom in the map. Is there any way I can achieve this functionality?

@andrewharvey
Copy link
Collaborator

@Smitraval The normal symbol clustering demo does this https://docs.mapbox.com/mapbox-gl-js/example/cluster/

// inspect a cluster on click
map.on('click', 'clusters', function (e) {
  var features = map.queryRenderedFeatures(e.point, { layers: ['clusters'] });
  var clusterId = features[0].properties.cluster_id;
  map.getSource('earthquakes').getClusterExpansionZoom(clusterId, function (err, zoom) {
    if (err)
      return;
 
    map.easeTo({
      center: features[0].geometry.coordinates,
      zoom: zoom
    });
  });
});

@deathlock
Copy link

@andrewharvey Thank you very much for your quick reply. I will try the same and update the final result here for future reference.

@deathlock
Copy link

Hi @andrewharvey , Using your example I have successfully created marker clusters with images, However I am still stuck at a point where I need to replace un-clustered points with custom html div. Here is my code

`map.on('load', function () {

        // Add a new source from our GeoJSON data and set the
        // 'cluster' option to true. GL-JS will add the point_count property to your source data.
        map.addSource("hotels", {
            type: "geojson",
            // Point to GeoJSON data. This example visualizes all M1.0+ earthquakes
            // from 12/22/15 to 1/21/16 as logged by USGS' Earthquake hazards program.
            data: hotelGeo,//"https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson",
            cluster: true,
            //clusterMaxZoom: 14, // Max zoom to cluster points on
            //clusterRadius: 40 // Radius of each cluster when clustering points (defaults to 50)
        });

        map.addLayer({
            id: "clusters",
            type: "circle",
            source: "hotels",
            filter: ["has", "point_count"],
            paint: {
                // Use step expressions (https://docs.mapbox.com/mapbox-gl-js/style-spec/#expressions-step)
                // 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": "blue",
                "circle-radius": 0
            }
        });

        // map.addLayer({
        //     id: "cluster-count",
        //     type: "symbol",
        //     source: "hotels",
        //     filter: ["has", "point_count"],
        //     layout: {
        //         "text-field": "{point_count_abbreviated}",
        //         "text-font": ["DIN Offc Pro Medium", "Arial Unicode MS Bold"],
        //         "text-size": 0
        //     }
        // });

        map.addLayer({
            id: "unclustered-point",
            type: "circle",
            source: "hotels",
            filter: ["!=", "cluster", true],
            paint: {
                "circle-color": "red",
                "circle-radius": 4,
                "circle-stroke-width": 1,
                "circle-stroke-color": "#fff"
            }
        });

        // objects for caching and keeping track of HTML marker objects (for performance)
        var markers = {};
        var markersOnScreen = {};
        var coordinates = {};

        function updateMarkers() {
            var newMarkers = {};
            var features = map.querySourceFeatures('hotels');
            // 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 (var i = 0; i < features.length; i++) {
                if (!features[i]) continue;

                var coords = features[i].geometry.coordinates;
                var props = features[i].properties;
                if (!props.cluster) continue;
                var id = props.cluster_id;
                coordinates[id] = coords;

                // console.log("check id", id)

                var marker = markers[id];
                if (!marker) {

                    /*** For cluster */
                    var el = document.createElement('div');
                    var features = map.queryRenderedFeatures({ layers: ['clusters'] });

                    if (!features[i]) continue;

                    var point_count = features[i].properties.point_count_abbreviated;
                    el.className = 'marker';
                    el.id = id;
                    el.style.backgroundImage = 'url("' + Marker + '")';
                    el.style.width = '41px';
                    el.style.height = '48px';
                    el.innerHTML = "<span style='width: 41px;line-height: 42px;text-align:center;display:block'>" + point_count + "</span>"

                    // console.log("check counts", point_count);
                    marker = markers[id] = new mapboxgl.Marker({ element: el }).setLngLat(coords);

                    el.addEventListener('click', () => {
                        var features = map.queryRenderedFeatures({ layers: ['clusters'] });
                        if (!features[0]) return;
                        var clusterId = features[0].properties.cluster_id;
                        map.getSource('hotels').getClusterExpansionZoom(clusterId, function (err, zoom) {
                            if (err)
                                return;

                            // map.easeTo({
                            //     center: coordinates[el.id],
                            //     zoom: zoom
                            // });

                            map.flyTo({
                                center: coordinates[el.id],
                                zoom: zoom,
                                speed: .75
                            });

                            //console.log("check coords", coordinates)
                            // for every marker we've added previously, remove those that are no longer visible
                            //map.off("zoom");

                        });

                    }

                    );

                }
                newMarkers[id] = marker;

                if (!markersOnScreen[id])
                    marker.addTo(map);
            }
            //console.log("check coords", coordinates)
            // for every marker we've added previously, remove those that are no longer visible
            for (id in markersOnScreen) {
                //console.log("in for loop", newMarkers[id])
                if (!newMarkers[id])
                    markersOnScreen[id].remove();
            }
            markersOnScreen = newMarkers;
        }

        // after the GeoJSON data is loaded, update markers on the screen and do so on every map move/moveend
        map.on('data', function (e) {
            if (e.sourceId !== 'hotels' || !e.isSourceLoaded) return;
            map.on('zoom', updateMarkers);
            map.on('moveend', updateMarkers);
            updateMarkers();
        });

        map.on('mouseenter', 'clusters', function () {
            map.getCanvas().style.cursor = 'pointer';
        });
        map.on('mouseleave', 'clusters', function () {
            map.getCanvas().style.cursor = '';
        });
    });`

See those red points in map? I need to change them with HTML div. Any help would be appreciated.

hotels-map

@NeonCreativeStudios
Copy link

NeonCreativeStudios commented Jun 12, 2019

@Smitraval I've simplified your code and it now works for custom markers and clusters. And at the end of the updateMarkers function, there's a simple loop that removes unused markers and clusters :)

image

    map.addSource("addresses", {
        type: "geojson",
        data: geojson,  //"https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson",
        cluster: true,
        clusterMaxZoom: 13, // 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: "addresses",
        filter: ["has", "point_count"],
        paint: {
            "circle-radius": 0
        }
    });


    // Store IDs and cluster/marker HTMLElements
    const markers = new Map();

    function updateMarkers(){
        const features = map.querySourceFeatures('addresses');
        const keepMarkers = [];

        for (let i = 0; i < features.length; i++) {
            const coords = features[ i ].geometry.coordinates;
            const props = features[ i ].properties;
            const featureID = features[ i ].id;
            
            const clusterID = props.cluster_id || null;

            if (props.cluster && markers.has('cluster_'+clusterID)) {

                //Cluster marker is already on screen
                keepMarkers.push('cluster_'+clusterID);

            } else if (props.cluster) {

                //This feature is clustered, create an icon for it and use props.point_count for its count

                var el = document.createElement('div');
                el.className = 'mapCluster';
                el.style.width = '60px';
                el.style.height = '60px';
                el.style.textAlign = 'center';
                el.style.color = 'white';
                el.style.background = '#16d3f9';
                el.style.borderRadius = '50%';
                el.innerText = props.point_count;
                const marker = new mapboxgl.Marker(el).setLngLat(coords);
                marker.addTo(map);
                keepMarkers.push('cluster_'+featureID);
                markers.set('cluster_'+clusterID,el);
                
            } else if (markers.has(featureID)) {

                //Feature marker is already on screen
                keepMarkers.push(featureID);

            } else {
                
                //Feature is not clustered and has not been created, create an icon for it
                const el = new Image();
                el.style.backgroundImage = 'url(https://placekitten.com/g/50/50)';
                el.className = 'mapMarker';
                el.style.width = '50px';
                el.style.height = '50px';
                el.style.borderRadius = '50%';
                el.dataset.type = props.type;
                const marker = new mapboxgl.Marker(el).setLngLat(coords);
                marker.addTo(map);
                keepMarkers.push(featureID);
                markers.set(featureID,el);
                
            }
            
        }

        //Let's clean-up any old markers. Loop through all markers
        markers.forEach((value,key,map) => {
            //If marker exists but is not in the keep array
            if (keepMarkers.indexOf(key) === -1) {
                console.log('deleting key: '+key);
                //Remove it from the page
                value.remove();
                //Remove it from markers map
                map.delete(key);
            }
        });

    };`

    map.on('data', function (e) {
        if (e.sourceId !== 'addresses' || !e.isSourceLoaded) return;
        map.on('moveend', updateMarkers); // moveend also fires on zoomend
        updateMarkers();
    });

Console output showing markers and clusters getting removed. They are removed when they go off screen, but also when they switch from a marker to a cluster and vice versa.
image

@livthomas
Copy link

I have just read this thread and if I understand it correctly it is simply not possible to easily create a cluster of HTML markers. You either need to add them as a GeoJSON layer and render them using images. Or you can add them to a map directly as markers but you need to manage the hiding logic manually. Am I right or am I missing something?

I create my markers purely by using HTML and CSS. I want to add them to a cluster which would deal with the hiding logic and show the number of hidden markers (just like Leaflet plugins do it) without me having to implement anything. Is it possible or not?

@mvtenney
Copy link

mvtenney commented Jul 9, 2019

@NeonCreativeStudios, thanks for your efforts! While running your example code, I am seeing the clustering work beautifully, but the max zoom seems to be ignored, and it is not unclustering into individual markers when zooming far enough.

@livthomas
Copy link

@mvtenney I am not sure if you encountered the same problem as me but unclustering was very unreliable in my application so I had to add the following line to onMoveEnd callback:

map.once('idle', () => updateMarkers());

The only problem is that it waits not only for the zooming animation to end but also for map tiles loading so markers will appear after some time. However, AFAIK there is no animationend event in Mapbox GL JS and so I find this to be the only reliable solution.

@Bjmn7
Copy link

Bjmn7 commented Feb 23, 2022

Hello,
first of all, I thank you for your work to improve mapbox-gl-js.

I have been using it for a few months now and I am very satisfied with it, both in terms of performance and ease of use. But for almost 2 weeks now, I’ve been dealing with a problem concerning the HTML clustering. I think I've identified where the problem comes from, from which function but I don't know how to solve it, so I'm calling you. Below, the result and the function that, for me, is responsible for this rendering.

My data can change with different filters, the state of the data but also a date. When I change the date, some clusters present at the previous date remain on the map, they are not deleted as they should be in the function. And strangely, the ones that remain seem to prevent some new clusters from appearing.

I thank you in advance for the time you will take to answer me.

const markers: any = {};
let markersOnScreen: any = {};

function clusterConstructor(features: any, map: any) {
    const newMarkers: any = {};

    for (const feature of features) {
       const coords = feature.geometry.coordinates;
       const props = feature.properties;

       if (!props.cluster) continue;
       const id: number = props.cluster_id;

       let marker = markers[id];
       if (!marker) {
          const el = createDonutChart(props);
         marker = markers[id] = new mapboxgl.Marker({ element: el }).setLngLat(coords);
        }
      newMarkers[id] = marker;
      if (!markersOnScreen[id]) marker.addTo(map);
      }
    for (const id in markersOnScreen) {
       if (!newMarkers[id]) markersOnScreen[id].remove();
     }
    markersOnScreen = newMarkers;
   }

Capture d’écran 2022-02-23 093724

@thuanmb
Copy link

thuanmb commented Feb 23, 2022

@Bjmn7 the issue should come from here:

       let marker = markers[id];

       if (!marker) {

          const el = createDonutChart(props);

         marker = markers[id] = new mapboxgl.Marker({ element: el }).setLngLat(coords);

        }

       newMarkers[id] = marker;
       if (!markersOnScreen[id]) marker.addTo(map);

in above code, if the marker already in here, you don't update with the new content (data for another day). that why it's not get update.
you should always re-create/update the content of the marker (donut chart) when the date changed:

       let marker = markers[id];
       if (marker) {
          marker.remove()
        } 
        const el = createDonutChart(props);
        marker = markers[id] = new mapboxgl.Marker({ element: el }).setLngLat(coords);
        marker.addTo(map);
        newMarkers[id] = marker;

@Bjmn7
Copy link

Bjmn7 commented Feb 23, 2022

@thuanmb Thank you very much, it works perfectly, great day to you.

@Bjmn7
Copy link

Bjmn7 commented Feb 24, 2022

Hello, after multiple tests, it seems this solution slows down the map a lot when the function is fired by an idle event. If it is fired by a render event, it is even slower and the clusters move to the corner of the map at each render. Do you have any idea what could cause this @thuanmb ?

@thuanmb
Copy link

thuanmb commented Feb 25, 2022

@Bjmn7 you can use some check to compare the props before re-rendering the clusters/markers. if the data (used to render the donut chart) has changed, we will re-render the chart. Otherwise keep the chart as is (by re-using previous mapboxgl.Marker instance) or remove it from the map (using map.removeLayer function)

@OlejarzRafal
Copy link

OlejarzRafal commented Feb 23, 2023

@Smitraval Uprościłem twój kod i teraz działa on dla niestandardowych znaczników i klastrów. A na końcu funkcji updateMarkers znajduje się prosta pętla, która usuwa nieużywane znaczniki i klastry :)

obraz

    map.addSource("addresses", {
        type: "geojson",
        data: geojson,  //"https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson",
        cluster: true,
        clusterMaxZoom: 13, // 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: "addresses",
        filter: ["has", "point_count"],
        paint: {
            "circle-radius": 0
        }
    });


    // Store IDs and cluster/marker HTMLElements
    const markers = new Map();

    function updateMarkers(){
        const features = map.querySourceFeatures('addresses');
        const keepMarkers = [];

        for (let i = 0; i < features.length; i++) {
            const coords = features[ i ].geometry.coordinates;
            const props = features[ i ].properties;
            const featureID = features[ i ].id;
            
            const clusterID = props.cluster_id || null;

            if (props.cluster && markers.has('cluster_'+clusterID)) {

                //Cluster marker is already on screen
                keepMarkers.push('cluster_'+clusterID);

            } else if (props.cluster) {

                //This feature is clustered, create an icon for it and use props.point_count for its count

                var el = document.createElement('div');
                el.className = 'mapCluster';
                el.style.width = '60px';
                el.style.height = '60px';
                el.style.textAlign = 'center';
                el.style.color = 'white';
                el.style.background = '#16d3f9';
                el.style.borderRadius = '50%';
                el.innerText = props.point_count;
                const marker = new mapboxgl.Marker(el).setLngLat(coords);
                marker.addTo(map);
                keepMarkers.push('cluster_'+featureID);
                markers.set('cluster_'+clusterID,el);
                
            } else if (markers.has(featureID)) {

                //Feature marker is already on screen
                keepMarkers.push(featureID);

            } else {
                
                //Feature is not clustered and has not been created, create an icon for it
                const el = new Image();
                el.style.backgroundImage = 'url(https://placekitten.com/g/50/50)';
                el.className = 'mapMarker';
                el.style.width = '50px';
                el.style.height = '50px';
                el.style.borderRadius = '50%';
                el.dataset.type = props.type;
                const marker = new mapboxgl.Marker(el).setLngLat(coords);
                marker.addTo(map);
                keepMarkers.push(featureID);
                markers.set(featureID,el);
                
            }
            
        }

        //Let's clean-up any old markers. Loop through all markers
        markers.forEach((value,key,map) => {
            //If marker exists but is not in the keep array
            if (keepMarkers.indexOf(key) === -1) {
                console.log('deleting key: '+key);
                //Remove it from the page
                value.remove();
                //Remove it from markers map
                map.delete(key);
            }
        });

    };`

    map.on('data', function (e) {
        if (e.sourceId !== 'addresses' || !e.isSourceLoaded) return;
        map.on('moveend', updateMarkers); // moveend also fires on zoomend
        updateMarkers();
    });

Dane wyjściowe konsoli pokazujące usuwanie znaczników i klastrów. Są usuwane, gdy znikają z ekranu, ale także wtedy, gdy przechodzą ze znacznika do grupy i odwrotnie. obraz

@NeonCreativeStudios @Bjmn7
i have a similar problem, when zooming the cluster, the markers are not visible. I tried to solve this problem but I couldn't. Can anyone help

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests