[GIS] Leaflet markers at same position: dynamically display all markers infos through one marker only

leaflet

I created a map using Leaflet, with a filtering system, custom icons with the leaflet.awesome-markers plugin, and jquery functions so that when you click on the markers, the corresponding information, inherited from a geoJson file, appears in turn in a div. (Every time I click on a marker, the marker info appears in my div, dynamically). I also use the markercluster plugin to display clusters when several points are in the same place.

Here is the rendering:

enter image description here

The filters are checkbox (multiple) so user can click on all filters to see all markers.

The markers represent hospitals, so when there is a cluster, it shows multiple markers for the same hospital.

The clusters are working well, and if I click on it, I can see the clustured markers.
Here I have two markers for the same hospital. And that's my problem!

enter image description here

Now I have to change the behavior I've gotten.

What I would need to do:

  • Keep the cluster system, but make sure that there is only one marker when zooming, and that the information of all the markers of the cluster are displayed in my dynamic div. (with a tabs system or other?)
    So I want to keep the cluster system to show that there are multiple informations at that point, but show only one marker when clicking on the cluster.

Is it possible ?

EDIT 1:
I edit this thread because after many search, I read https://digital-geography.com/working-with-clusters-in-leaflet-increasing-useability/ and this one and saw it was possible !

So Now just after my getJson function I added

var promise = $.getJSON("examen.json");
var clusters = L.markerClusterGroup( { <-- this is new
    spiderfyOnMaxZoom: false, 
    showCoverageOnHover: false, 
    zoomToBoundsOnClick: true 
} );

Then, at the end of my onEachFeature function, I added

clusters.on('clusterclick', function(a){            
    var markers = a.layer.getAllChildMarkers();
    for (var i = 0; i < markers.length; i++) {
   $('#layer_infos .fill').append(html);
    }
}); 

It is working partialy.

In the documentation, it is written

Returns an array of all markers contained within this cluster

But for me it doesn't show "this" marker info, but all the markers of my geoJson file… ! (image below with the scroll bar) maybe because my cluster function is wrapping all my markers in the code ? So how can I limit it to show only the clicked marker info ?

enter image description here

Best Answer

As suggested by TomazicM, there might be a simplier way to achieve this, with much more cleaner code! Here I'm duplicating a bunch of code, because for the moment I don't know how to simplify it. But it works !

I also managed to remove duplicate properties from a geoJSON file here.

I give here all my js code. It might help other newbies like me, and of course, if there are some advanced coders who want to improve it, you're welcome.

@TomazicM, again thank you very very much for all your help. Thanks to you I can improve my skill little by litte !! : )

Here is my code :

$(document).ready(function(){
    var cartoDb = L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
            attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
            subdomains: 'abcd',
            maxZoom: 18
        });
     var cartoDb2 = L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
            attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
            subdomains: 'abcd',
            maxZoom: 18
        });
/* Here is an example of how I define my custom icons . I'm using leaflet.awesome-markers */
        var ccl18Icon = L.AwesomeMarkers.icon({
            prefix: 'fa', //font awesome rather than bootstrap
            iconColor: 'white',
            markerColor: 'orange', // see colors above
            icon: 'ambulance' //http://fortawesome.github.io/Font-Awesome/icons/
        });

Then a function for each markers, similar to an info window, but I put it in a dynamic panel outside the map with : $('#layer_infos .fill').html(html);

    function onEachFeature(feature, layer) {

        var html = '';
        if (feature.properties.Professeur) {
            html += '<p class="prof">' + feature.properties.Professeur + '</p>';
        }
         if (feature.properties.Professeur2) {
            html += '<p class="prof2">' + feature.properties.Professeur2 + '</p>';
        }
        if (feature.properties.Chu) {
            html += '<p class="chu">' + feature.properties.Chu + '</p>';
        }
        if (feature.properties.Laboratoire) {
            html += '<p class="labo">' + feature.properties.Laboratoire + '</p>';
        }
        if (feature.properties.Prelevement) {
            html += '<p>' + feature.properties.Prelevement + '</p>';
        }
        if (feature.properties.Envoi) {
            html += '<p>' + feature.properties.Envoi + '</p>';
        }
        if (feature.properties.Adresse) {
            html += '<p class="adress">' + feature.properties.Adresse + '</p>';
        }
        if (feature.properties.Cp) {
            html += '<p class="cp">' + feature.properties.Cp + '</p>';
        }

        if (feature.properties.Tel) {
            html += '<p class="tel">' + feature.properties.Tel + '</p>';
        }
        if (feature.properties.Fax) {
            html += '<p class="fax">' + feature.properties.Fax + '</p>';
        }
        if (feature.properties.Mail) {
            html += '<p class="mail"><a href="mailto:' + feature.properties.Mail +'">' + feature.properties.Mail + '</a></p>';
        }
        if (feature.properties.Tel2) {
            html += '<p class="tel">' + feature.properties.Tel2 + '</p>';
        }
        if (feature.properties.Fax2) {
            html += '<p class="fax">' + feature.properties.Fax2 + '</p>';
        }
        if (feature.properties.Mail2) {
            html += '<p class="mail"><a href="mailto:' + feature.properties.Mail2 +'">' + feature.properties.Mail2 + '</a></p>';
        }
        if (feature.properties.Renseignement) {
            html += '<p class="rt">' + feature.properties.Renseignement + '</p>';
        }
        if (feature.properties.Url) {
            html += '<p class="url"><a href="' + feature.properties.Url + '">' + feature.properties.Url + '</a></p>';
        }
      html+='<div class="pictos">';   
        if (feature.properties.Examen) {
            html += '<span class="' + feature.properties.Examen + '">' + feature.properties.Examen + '</span>';
        }
            html+='</div>'; 
        layer.on('click', function() {
            $('#layer_infos .fill').html(html);
            if (L.Browser.mobile) {
                $('#infos').addClass("slide"); 
                $('#filters').removeClass('slide');
                $('.hamburger').text('Sélectionner un examen').fadeIn();
             }
        })  
    }    

I'm duplicating this last part for my second map in tab 2. Then I create my cluster groups

var promise = $.getJSON("examen.json");

/*cluster for the first map */
var clusters = L.markerClusterGroup({
    spiderfyOnMaxZoom: false, 
    showCoverageOnHover: false, 
    zoomToBoundsOnClick: true 
});

Here is my cluster click action with associated html. I'm targetting the markers inside the selected cluster only. I'm also duplicating this code for my second map !

clusters.on('clusterclick', function(a){
if(a.layer._zoom == 18){
 var html = '';

for (feat in a.layer._markers){
    if (a.layer._markers[feat].feature.properties['Professeur']) {
    html += '<p class="prof">' + a.layer._markers[feat].feature.properties['Professeur'] + '</p>';
}
 if (a.layer._markers[feat].feature.properties['Professeur2']) {
    html += '<p class="prof2">' + a.layer._markers[feat].feature.properties['Professeur2'] + '</p>';
}
if (a.layer._markers[feat].feature.properties['Chu']) {
    html += '<p class="chu">' + a.layer._markers[feat].feature.properties['Chu'] + '</p>';
}
if (a.layer._markers[feat].feature.properties['Laboratoire']) {
    html += '<p class="labo">' + a.layer._markers[feat].feature.properties['Laboratoire'] + '</p>';
}
if (a.layer._markers[feat].feature.properties['Prelevement']) {
    html += '<p>' + a.layer._markers[feat].feature.properties['Prelevement'] + '</p>';
}
if (a.layer._markers[feat].feature.properties['Envoi']) {
    html += '<p>' + a.layer._markers[feat].feature.properties['Envoi'] + '</p>';
}
if (a.layer._markers[feat].feature.properties['Adresse']) {
    html += '<p class="adress">' + a.layer._markers[feat].feature.properties['Adresse'] + '</p>';
}
if (a.layer._markers[feat].feature.properties['Cp']) {
    html += '<p class="cp">' + a.layer._markers[feat].feature.properties['Cp'] + '</p>';
}

if (a.layer._markers[feat].feature.properties['Tel']) {
    html += '<p class="tel">' + a.layer._markers[feat].feature.properties['Tel'] + '</p>';
}
if (a.layer._markers[feat].feature.properties['Fax']) {
    html += '<p class="fax">' + a.layer._markers[feat].feature.properties['Fax'] + '</p>';
}
if (a.layer._markers[feat].feature.properties['Mail']) {
    html += '<p class="mail"><a href="mailto:' + a.layer._markers[feat].feature.properties['Mail'] +'">' + a.layer._markers[feat].feature.properties['Mail'] + '</a></p>';
}
if (a.layer._markers[feat].feature.properties['Tel2']) {
    html += '<p class="tel">' + a.layer._markers[feat].feature.properties['Tel2'] + '</p>';
}
if (a.layer._markers[feat].feature.properties['Fax2']) {
    html += '<p class="fax">' + a.layer._markers[feat].feature.properties['Fax2'] + '</p>';
}
if (a.layer._markers[feat].feature.properties['Mail2']) {
    html += '<p class="mail"><a href="mailto:' + a.layer._markers[feat].feature.properties['Mail2'] +'">' + a.layer._markers[feat].feature.properties['Mail2'] + '</a></p>';
}
if (a.layer._markers[feat].feature.properties['Renseignement']) {
    html += '<p class="rt">' + a.layer._markers[feat].feature.properties['Renseignement'] + '</p>';
}
if (a.layer._markers[feat].feature.properties['Url']) {
    html += '<p class="url"><a href="' + a.layer._markers[feat].feature.properties['Url'] + '">' + a.layer._markers[feat].feature.properties['Url'] + '</a></p>';
}
html+='<div class="pictos">';
if (a.layer._markers[feat].feature.properties['Examen']) {
    html += '<span class="' + a.layer._markers[feat].feature.properties['Examen'] + '">' + a.layer._markers[feat].feature.properties['Examen'] + '</span>';
}

    html+='</div>';
}

$('#layer_infos .fill').html(html);
}
})

Then, here an example of how I get geoJson data

promise.then(function(data) {

        var allexamens = L.geoJson(data);
        /* related filters and makers */
        var ccl18 = L.geoJson(data, {
            filter: function(feature, layer) {
                return feature.properties.Examen == "ccl18";
            },
            onEachFeature: onEachFeature,
            pointToLayer: function(feature, latlng) {

                return L.marker(latlng, {
                    icon: ccl18Icon
                })
            }
        })

And here is an example of my click action on filters

$("#ccl18").click(function() {
    if (this.checked) {

         ccl18.addTo(clusters);
        map.fitBounds(allexamens.getBounds(), {
            padding: [50, 50]
        });



    } else {
         clusters.removeLayer(ccl18);
         map.fitBounds(allexamens.getBounds(), {
            padding: [50, 50]
        });

    }
});

Thenn I close my "promise.then(function(data) {"

});

And At the end I call my clusters, and close my $(document).ready(function(){..

clusters.addTo(map);
clusterade.addTo(map2);
});

With all this code, I have my map working as expected. Single markers, and clusters.

So before I had this :

enter image description here

And now, When I click on clusters, instead of having separated markers, I get the data of the markers contained in "this" cluster only, and display it in a pannel

enter image description here