1

I have a dataset of training sessions provided at various sites in Greater Manchester. The training sessions have different names, but in many cases, the sites may be the same. Here is a short example version of the dataset:

var json_Typesofsupport_2 = {
"type": "FeatureCollection",
"name": "Typesofsupport_2",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{ "type": "Feature", "properties": { "Get Online GM Support": "Kashmir Youth Project (KYP)", "Address": "Kashmir Youth Project, Belfield Road, Rochdale OL16 2UP", "Telephone": "01706 630140", "Email": "[email protected]", "Website": "https:\/\/kyp.org.uk\/", "More info": "Home - KYP - Delivering to the community, by the community\nMonday-Friday 09:00-17:00", "Type": "Digital Inclusion Hub", "District": "Rochdale" }, "geometry": { "type": "Point", "coordinates": [ -2.14083, 53.6228218 ] } },
{ "type": "Feature", "properties": { "Get Online GM Support": "Yaran Northwest", "Address": "Yaran Northwest, 307 Dickenson Road, Greater, Longsight, Manchester M13 0YW", "Telephone": "07428 600 944", "Email": "[email protected]", "Website": "https:\/\/www.yarannorthwest.com\/", "More info": "Home - Yaran Northwest\n\nPlease contact Yaran Northwest to find out about their opening times.\n", "Type": "GM Databank", "District": "Manchester" }, "geometry": { "type": "Point", "coordinates": [ -2.2059561, 53.4553278 ] } },
{ "type": "Feature", "properties": { "Get Online GM Support": "Ingeus Restart Hyde", "Address": "Ingeus, Orient House, Newton Street, Hyde SK14 4RY", "Telephone": "0207 265 3000", "Email": "[email protected]", "Website": "https:\/\/ingeus.co.uk\/services\/employment\/restartscheme\/greater-manchester", "More info": "Restart Scheme - Employer Services | Ingeus\n\nMonday-Friday 09:00- 17:30", "Type": "Digital Inclusion Hub", "District": "Tameside" }, "geometry": { "type": "Point", "coordinates": [ -2.0834132, 53.4542421 ] } }...

I have used the qgis2web plugin on QGIS to create a Leaflet map, with the points colour coded based on "Type" (there are three types), and with two filters: one on "Type" and one on "District". I have three data layers on there - the training sessions, local authority boundaries, and an OSM baselayer.

However, sometimes two or more training sessions - and sometimes two or more of the same type - occur at the same location.

I want users to be able to see that there are multiple training sessions, regardless of type, at the same location without having to use a filter, AND provide users with the option to filter data too.

The various solutions I can think of (and have tried to implement) are:

  1. Use the Leaflet.markercluster option. I have tried this solution, but the filter doesn't seem to work when the marker cluster approach is used.

  2. Using the Lodash approach identified here. However, that didn't seem to allow the filter to work either, as it grouped the data into single features.

  3. Changing the look of the different types of training session, so that they can be seen underneath one another on the map. This works somewhat, but is not as aesthetically pleasing as the markercluster option above, and doesn't work in cases where there are more than one training session of the same type.

  4. Somehow collate all the content at each site together in an attribute field that can be called. I have not yet tried this, however I strongly suspect that the popup will not be aesthetically pleasing in this case.

Are there any options I am not considering?

TL;DR: How can I create an interactive map that allows me to see that there are multiple training sessions of multiple types at the same place, AND allow me to filter those training sessions based on their type?

Here's the JS code I have for the map, with the Lodash option mentioned above commented out:

// Create the map
    var map = L.map('map', {
           zoomControl:true, maxZoom:28, minZoom:1
        }).fitBounds([[53.32525488176548,-2.711565009334601],[53.68318410119,-1.9606685992106514]]);
        
// Add the ability to create dynamic hashtags at the end of the URL to identify the location and zoom - see https://github.com/mlevans/leaflet-hash for more info
        var hash = new L.Hash(map);
        
// Add the attribution for the map
        map.attributionControl.setPrefix('<a href="https://github.com/tomchadwin/qgis2web" target="_blank">qgis2web</a> &middot; <a href="https://leafletjs.com" title="A JS library for interactive maps">Leaflet</a> &middot; <a href="https://qgis.org">QGIS</a>');
        
// Truncates all links to be a maximum of 50 characters - see https://github.com/gregjacobs/Autolinker.js/ for more info
        var autolinker = new Autolinker({truncate: {length: 50, location: 'smart'}});
        
// Create a merged point using the same location
//const groupedByCoords = _.groupBy(
      //json_Typesofsupport_2.features,
      //(feature) => feature.geometry.coordinates
    //);

    //const features = [];

    // Rework the Features to merge content
    //for (const key in groupedByCoords) {
    //  const items = groupedByCoords[key];
    //  const mergedContent = items.map((item) => item.properties.prop0).join("<br/><hr>");
    //  const feature = _.cloneDeep(items[0]);
    //  feature.properties.prop0 = mergedContent;
    //  features.push(feature);
    //}

    // Replace your features
    // json_Typesofsupport_2.features = features;

   // L.geoJSON(json_Typesofsupport_2, {
   //     pointToLayer: function(feature, coordinates) {
   //       return L.marker(coordinates);
   //     }
   //   })
   //   .bindPopup((layer) => layer.feature.properties.prop0)
   //   .addTo(myMap);
    
// Removes rows where there is no content in the original spreadsheet
        function removeEmptyRowsFromPopupContent(content, feature) {
         var tempDiv = document.createElement('div');
         tempDiv.innerHTML = content;
         var rows = tempDiv.querySelectorAll('tr');
         for (var i = 0; i < rows.length; i++) {
             var td = rows[i].querySelector('td.visible-with-data');
             var key = td ? td.id : '';
             if (td && td.classList.contains('visible-with-data') && feature.properties[key] == null) {
                 rows[i].parentNode.removeChild(rows[i]);
             }
         }
         return tempDiv.innerHTML;
        }
        
// Creates a new feature group
        var bounds_group = new L.featureGroup([]);
        function setBounds() {
        }
        
// Creates a background map and adds this to the map as a layer
        map.createPane('pane_Backgroundmap_0');
        map.getPane('pane_Backgroundmap_0').style.zIndex = 400;
        var layer_Backgroundmap_0 = L.tileLayer('http://tile.openstreetmap.org/{z}/{x}/{y}.png', {
            pane: 'pane_Backgroundmap_0',
            opacity: 1.0,
            attribution: '<a href="https://www.openstreetmap.org/copyright">© OpenStreetMap contributors, CC-BY-SA</a>',
            minZoom: 1,
            maxZoom: 28,
            minNativeZoom: 0,
            maxNativeZoom: 19
        });
        layer_Backgroundmap_0;
        map.addLayer(layer_Backgroundmap_0);
        
// Create local authority popup content as a table showing different bits of data
        function pop_Localauthorityboundaries_1(feature, layer) {
            var popupContent = '<table>\
                    <tr>\
                        <td colspan="2">' + (feature.properties['OBJECTID'] !== null ? autolinker.link(feature.properties['OBJECTID'].toLocaleString()) : '') + '</td>\
                    </tr>\
                    <tr>\
                        <td colspan="2">' + (feature.properties['LAD20CD'] !== null ? autolinker.link(feature.properties['LAD20CD'].toLocaleString()) : '') + '</td>\
                    </tr>\
                    <tr>\
                        <td colspan="2">' + (feature.properties['LAD20NM'] !== null ? autolinker.link(feature.properties['LAD20NM'].toLocaleString()) : '') + '</td>\
                    </tr>\
                    <tr>\
                        <td colspan="2">' + (feature.properties['LAD20NMW'] !== null ? autolinker.link(feature.properties['LAD20NMW'].toLocaleString()) : '') + '</td>\
                    </tr>\
                    <tr>\
                        <td colspan="2">' + (feature.properties['BNG_E'] !== null ? autolinker.link(feature.properties['BNG_E'].toLocaleString()) : '') + '</td>\
                    </tr>\
                    <tr>\
                        <td colspan="2">' + (feature.properties['BNG_N'] !== null ? autolinker.link(feature.properties['BNG_N'].toLocaleString()) : '') + '</td>\
                    </tr>\
                    <tr>\
                        <td colspan="2">' + (feature.properties['LONG'] !== null ? autolinker.link(feature.properties['LONG'].toLocaleString()) : '') + '</td>\
                    </tr>\
                    <tr>\
                        <td colspan="2">' + (feature.properties['LAT'] !== null ? autolinker.link(feature.properties['LAT'].toLocaleString()) : '') + '</td>\
                    </tr>\
                    <tr>\
                        <td colspan="2">' + (feature.properties['Shape__Are'] !== null ? autolinker.link(feature.properties['Shape__Are'].toLocaleString()) : '') + '</td>\
                    </tr>\
                    <tr>\
                        <td colspan="2">' + (feature.properties['Shape__Len'] !== null ? autolinker.link(feature.properties['Shape__Len'].toLocaleString()) : '') + '</td>\
                    </tr>\
                    <tr>\
                        <td colspan="2">' + (feature.properties['ADDiff'] !== null ? autolinker.link(feature.properties['ADDiff'].toLocaleString()) : '') + '</td>\
                    </tr>\
                </table>';
            layer.bindPopup(popupContent, {maxHeight: 400});
            var popup = layer.getPopup();
            var content = popup.getContent();
            var updatedContent = removeEmptyRowsFromPopupContent(content, feature);
            popup.setContent(updatedContent);
        }
        
// Create the style for the local authority layer, with no interactivity
        function style_Localauthorityboundaries_1_0() {
            return {
                pane: 'pane_Localauthorityboundaries_1',
                opacity: 1,
                color: 'rgba(35,35,35,1.0)',
                dashArray: '',
                lineCap: 'butt',
                lineJoin: 'miter',
                weight: 4.0, 
                fillOpacity: 0,
                interactive: false,
            }
        }
        
// Creates the local authority pane and adds it on top of the base layer
        map.createPane('pane_Localauthorityboundaries_1');
        map.getPane('pane_Localauthorityboundaries_1').style.zIndex = 401;
        map.getPane('pane_Localauthorityboundaries_1').style['mix-blend-mode'] = 'normal';
        
// Sources the data from the data file, adds the style, and adds it to the map
        var layer_Localauthorityboundaries_1 = new L.geoJson(json_Localauthorityboundaries_1, {
            attribution: '',
            interactive: false,
            dataVar: 'json_Localauthorityboundaries_1',
            layerName: 'layer_Localauthorityboundaries_1',
            pane: 'pane_Localauthorityboundaries_1',
            onEachFeature: pop_Localauthorityboundaries_1,
            style: style_Localauthorityboundaries_1_0,
        });
        bounds_group.addLayer(layer_Localauthorityboundaries_1);
        map.addLayer(layer_Localauthorityboundaries_1);
        
// Creates popup content for the type of support
        function pop_Typesofsupport_2(feature, layer) {
            var popupContent = '<table>\
                    <tr>\
                        <td colspan="2"><strong>Get Online GM Support</strong><br />' + (feature.properties['Get Online GM Support'] !== null ? autolinker.link(feature.properties['Get Online GM Support'].toLocaleString()) : '') + '</td>\
                    </tr>\
                    <tr>\
                        <th scope="row">Address:</th>\
                        <td class="visible-with-data" id="Address">' + (feature.properties['Address'] !== null ? autolinker.link(feature.properties['Address'].toLocaleString()) : '') + '</td>\
                    </tr>\
                    <tr>\
                        <th scope="row">Telephone:</th>\
                        <td class="visible-with-data" id="Telephone">' + (feature.properties['Telephone'] !== null ? autolinker.link(feature.properties['Telephone'].toLocaleString()) : '') + '</td>\
                    </tr>\
                    <tr>\
                        <th scope="row">Email:</th>\
                        <td class="visible-with-data" id="Email">' + (feature.properties['Email'] !== null ? autolinker.link(feature.properties['Email'].toLocaleString()) : '') + '</td>\
                    </tr>\
                    <tr>\
                        <th scope="row">Website:</th>\
                        <td class="visible-with-data" id="Website">' + (feature.properties['Website'] !== null ? autolinker.link(feature.properties['Website'].toLocaleString()) : '') + '</td>\
                    </tr>\
                    <tr>\
                        <th scope="row">More info:</th>\
                        <td class="visible-with-data" id="More info">' + (feature.properties['More info'] !== null ? autolinker.link(feature.properties['More info'].toLocaleString()) : '') + '</td>\
                    </tr>\
                    <tr>\
                        <th scope="row">Type of support:</th>\
                        <td class="visible-with-data" id="Type">' + (feature.properties['Type'] !== null ? autolinker.link(feature.properties['Type'].toLocaleString()) : '') + '</td>\
                    </tr>\
                </table>';
            layer.bindPopup(popupContent, {maxHeight: 400});
            var popup = layer.getPopup();
            var content = popup.getContent();
            var updatedContent = removeEmptyRowsFromPopupContent(content, feature);
            popup.setContent(updatedContent);
        }

// Creates the style for the type of support
        function style_Typesofsupport_2_0(feature) {
            switch(String(feature.properties['Type'])) {
                case 'Digital Inclusion Hub':
                    return {
                pane: 'pane_Typesofsupport_2',
                radius: 7.2,
                opacity: 1,
                color: 'rgba(250,42,188,1.0)',
                dashArray: '',
                lineCap: 'butt',
                lineJoin: 'miter',
                weight: 3.0,
                fill: true,
                fillOpacity: 1,
                fillColor: 'rgba(255,255,255,1.0)',
                interactive: true,
            }
                    break;
                case 'Digital Skills':
                    return {
                pane: 'pane_Typesofsupport_2',
                radius: 7.2,
                opacity: 1,
                color: 'rgba(31,120,180,1.0)',
                dashArray: '',
                lineCap: 'butt',
                lineJoin: 'miter',
                weight: 3.0,
                fill: true,
                fillOpacity: 1,
                fillColor: 'rgba(255,255,255,1.0)',
                interactive: true,
            }
                    break;
                case 'GM Databank':
                    return {
                pane: 'pane_Typesofsupport_2',
                radius: 7.2,
                opacity: 1,
                color: 'rgba(51,160,44,1.0)',
                dashArray: '',
                lineCap: 'butt',
                lineJoin: 'miter',
                weight: 3.0,
                fill: true,
                fillOpacity: 1,
                fillColor: 'rgba(255,255,255,1.0)',
                interactive: true,
            }
                    break;
            }
        }
        
// Creates a map pane and layer for the type of support, then adds it to the map
        map.createPane('pane_Typesofsupport_2');
        map.getPane('pane_Typesofsupport_2').style.zIndex = 402;
        map.getPane('pane_Typesofsupport_2').style['mix-blend-mode'] = 'normal';
        var layer_Typesofsupport_2 = new L.geoJson(json_Typesofsupport_2, {
            attribution: '',
            interactive: true,
            dataVar: 'json_Typesofsupport_2',
            layerName: 'layer_Typesofsupport_2',
            pane: 'pane_Typesofsupport_2',
            onEachFeature: pop_Typesofsupport_2,
            pointToLayer: function (feature, latlng) {
                var context = {
                    feature: feature,
                    variables: {}
                };
                return L.circleMarker(latlng, style_Typesofsupport_2_0(feature));
            },
        });
        bounds_group.addLayer(layer_Typesofsupport_2);
        map.addLayer(layer_Typesofsupport_2);
        
// Adds a map search function using the OSM Geocoder
        var osmGeocoder = new L.Control.Geocoder({
            collapsed: true,
            position: 'topleft',
            text: 'Search',
            title: 'Testing'
        }).addTo(map);
        
// Gets the map search function and adds a title to it that displays when you hover
        document.getElementsByClassName('leaflet-control-geocoder-icon')[0]
        .className += ' fa fa-search';
        document.getElementsByClassName('leaflet-control-geocoder-icon')[0]
        .title += 'Search for a place, postcode, or street';
        
// Creates a legend with the layers added in
        var baseMaps = {};
        L.control.layers(baseMaps,{'Types of support<br /><table><tr><td style="text-align: center;"><img src="legend/Typesofsupport_2_DigitalInclusionHub0.png" /></td><td>Digital Inclusion Hub</td></tr><tr><td style="text-align: center;"><img src="legend/Typesofsupport_2_DigitalSkills1.png" /></td><td>Digital Skills</td></tr><tr><td style="text-align: center;"><img src="legend/Typesofsupport_2_GMDatabank2.png" /></td><td>GM Databank</td></tr></table>': layer_Typesofsupport_2,'<img src="legend/Localauthorityboundaries_1.png" /> Local authority boundaries': layer_Localauthorityboundaries_1,"Background map": layer_Backgroundmap_0,}).addTo(map);
        setBounds();
        
// Creates a space for the filters
        var mapDiv = document.getElementById('map');
        var row = document.createElement('div');
        row.className="row";
        row.id="all";
        row.style.height = "100%";
        var col1 = document.createElement('div');
        col1.className="col9";
        col1.id = "mapWindow";
        col1.style.height = "99%";
        col1.style.width = "80%";
        col1.style.display = "inline-block";
        var col2 = document.createElement('div');
        col2.className="col3";
        col2.id = "menu";
        col2.style.display = "inline-block";
        mapDiv.parentNode.insertBefore(row, mapDiv);
        document.getElementById("all").appendChild(col1);
        document.getElementById("all").appendChild(col2);
        col1.appendChild(mapDiv)
        
// Creates two filters, both string types
        var Filters = {"District": "str","Type": "str"};
        function filterFunc() {
          map.eachLayer(function(lyr){
          if ("options" in lyr && "dataVar" in lyr["options"]){
            features = this[lyr["options"]["dataVar"]].features.slice(0);
            try{
              for (key in Filters){
                keyS = key.replace(/[^a-zA-Z0-9_]/g, "")
                if (Filters[key] == "str" || Filters[key] == "bool"){
                  var selection = [];
                  var options = document.getElementById("sel_" + keyS).options
                  for (var i=0; i < options.length; i++) {
                    if (options[i].selected) selection.push(options[i].value);
                  }
                    try{
                      if (key in features[0].properties){
                        for (i = features.length - 1;
                          i >= 0; --i){
                          if (selection.indexOf(
                          features[i].properties[key])<0
                          && selection.length>0) {
                          features.splice(i,1);
                          }
                        }
                      }
                    } catch(err){
                  }
                }
                if (Filters[key] == "int"){
                  sliderVals =  document.getElementById(
                    "div_" + keyS).noUiSlider.get();
                  try{
                    if (key in features[0].properties){
                    for (i = features.length - 1; i >= 0; --i){
                      if (parseInt(features[i].properties[key])
                          < sliderVals[0]
                          || parseInt(features[i].properties[key])
                          > sliderVals[1]){
                            features.splice(i,1);
                          }
                        }
                      }
                    } catch(err){
                    }
                  }
                if (Filters[key] == "real"){
                  sliderVals =  document.getElementById(
                    "div_" + keyS).noUiSlider.get();
                  try{
                    if (key in features[0].properties){
                    for (i = features.length - 1; i >= 0; --i){
                      if (features[i].properties[key]
                          < sliderVals[0]
                          || features[i].properties[key]
                          > sliderVals[1]){
                            features.splice(i,1);
                          }
                        }
                      }
                    } catch(err){
                    }
                  }
                if (Filters[key] == "date"
                  || Filters[key] == "datetime"
                  || Filters[key] == "time"){
                  try{
                    if (key in features[0].properties){
                      HTMLkey = key.replace(/[&\/\\#,+()$~%.'":*?<>{} ]/g, '');
                      startdate = document.getElementById("dat_" +
                        HTMLkey + "_date1").value.replace(" ", "T");
                      enddate = document.getElementById("dat_" +
                        HTMLkey + "_date2").value.replace(" ", "T");
                      for (i = features.length - 1; i >= 0; --i){
                        if (features[i].properties[key] < startdate
                          || features[i].properties[key] > enddate){
                          features.splice(i,1);
                        }
                      }
                    }
                  } catch(err){
                  }
                }
              }
            } catch(err){
            }
          this[lyr["options"]["layerName"]].clearLayers();
          this[lyr["options"]["layerName"]].addData(features);
          }
          })
        }
        
// Creates a new section in the filter panel        
            document.getElementById("menu").appendChild(
                document.createElement("div"));
                
// Creates a new div in the filter panel for the district filter
            var div_Title = document.createElement('div');
            document.getElementById("menu").appendChild(div_Title);
            
// Creates the title for the filter panel
            var lab_Title = document.createElement('div');
            lab_Title.innerHTML = '<h1><strong>Filter the map</strong></h1><h2>Use this section to filter the types of support on the map.</h2>';
            div_Title.appendChild(lab_Title);
                
// Creates a new section in the filter panel        
            document.getElementById("menu").appendChild(
                document.createElement("div"));
            
// Creates a new div in the filter panel for the district filter
            var div_District = document.createElement('div');
            div_District.id = "div_District";
            div_District.className= "filterselect";
            document.getElementById("menu").appendChild(div_District);
            sel_District = document.createElement('select');
            sel_District.multiple = true;
            sel_District.size = 10;
            sel_District.id = "sel_District";
            
// Creates the title for the district filter
            var lab_District = document.createElement('div');
            lab_District.innerHTML = '<strong>Choose a district:</strong>';
            lab_District.className = 'filterlabel';
            div_District.appendChild(lab_District);
            
// Creates the options for the district filter
            var District_options_str = "";
            sel_District.onchange = function(){filterFunc()};
            District_options_str  += '<option value="Bolton">Bolton</option>';
            District_options_str  += '<option value="Bury">Bury</option>';
            District_options_str  += '<option value="Manchester">Manchester</option>';
            District_options_str  += '<option value="Oldham">Oldham</option>';
            District_options_str  += '<option value="Rochdale">Rochdale</option>';
            District_options_str  += '<option value="Salford">Salford</option>';
            District_options_str  += '<option value="Stockport">Stockport</option>';
            District_options_str  += '<option value="Tameside">Tameside</option>';
            District_options_str  += '<option value="Trafford">Trafford</option>';
            District_options_str  += '<option value="Wigan">Wigan</option>';
            sel_District.innerHTML = District_options_str;
            div_District.appendChild(sel_District);
            
// Creates the 'clear the filter' option
            var reset_District = document.createElement('div');
            reset_District.innerHTML = 'Clear the filter';
            reset_District.className = 'filterlabel';
            reset_District.onclick = function() {
                var options = document.getElementById("sel_District").options;
                for (var i=0; i < options.length; i++) {
                    options[i].selected = false;
                }
                filterFunc();
            };
            div_District.appendChild(reset_District);
            
// Creates a new section in the filter panel
            document.getElementById("menu").appendChild(
                document.createElement("div"));
            
// Creates a new div in the filter panel for the type of support filter
            var div_Type = document.createElement('div');
            div_Type.id = "div_Type";
            div_Type.className= "filterselect";
            document.getElementById("menu").appendChild(div_Type);
            sel_Type = document.createElement('select');
            sel_Type.multiple = true;
            sel_Type.size = 3;
            sel_Type.id = "sel_Type";
            
// Creates a title for the type of support filter
            var lab_Type = document.createElement('div');
            lab_Type.innerHTML = '<strong>Choose a type of support:</strong>';
            lab_Type.className = 'filterlabel';
            div_Type.appendChild(lab_Type);
            
// Creates the options for the type of support filter
            var Type_options_str = "";
            sel_Type.onchange = function(){filterFunc()};
            Type_options_str  += '<option value="Digital Inclusion Hub">Digital Inclusion Hub</option>';
            Type_options_str  += '<option value="Digital Skills">Digital Skills</option>';
            Type_options_str  += '<option value="GM Databank">GM Databank</option>';
            sel_Type.innerHTML = Type_options_str;
            div_Type.appendChild(sel_Type);
            
// Creates a 'clear the filter' option
            var reset_Type = document.createElement('div');
            reset_Type.innerHTML = 'Clear the filter';
            reset_Type.className = 'filterlabel';
            reset_Type.onclick = function() {
                var options = document.getElementById("sel_Type").options;
                for (var i=0; i < options.length; i++) {
                    options[i].selected = false;
                }
                filterFunc();
            };
            div_Type.appendChild(reset_Type);
7
  • 1
    Coding question on GIS SE site should include relevant existing code of what has been tried so far, otherwise it's most likely to be closed as not compliant with the site policy. Please edit your question and add relevant existing code.
    – TomazicM
    Commented Feb 23 at 17:42
  • openlayers export can query multiple overlapping points, even in clusters, but it does not have the filter function. leaflet export cannot query multiple overlapping points, it will only query the point above, but it has the filter function. if the filter is essential for you, you must export with leaflet and modify the symbology of your layers, providing transparency to the points and making the points below larger, so that you can see multiple points in the same coordinate and be able to query them. Commented Feb 24 at 8:44
  • if you need help, upload an excerpt of your data online and put the link here Commented Feb 24 at 8:45
  • @TomazicM Thanks, and apologies. I have included more of the code I have, and tried to be clearer.
    – Chris Pope
    Commented Feb 26 at 19:45
  • @andreaordonselli Thanks for your response. I have just updated the question with more detail and code. I have tried the transparency option, but this doesn't work as well when there are multiple training sessions of the same type at the same location. Given this, are you saying the basic choice is between clustering the data to be able to see all markers at a location, or having the ability to filter?
    – Chris Pope
    Commented Feb 26 at 19:48

1 Answer 1

0

Since your code is quite complex, it's practically impossible to say why your filtering is not working with marker clusters. It definitely should work if, when filtering is done, marker cluster layer is cleared and filtered features are then added to it.

Below is an example of such code. Markers are created randomly, with some being added to be in the same position. Checking filter filters out one third of markers.

<body>
  <input type="checkbox" name="filter" onclick="filter(this)">
  <label for="filter"> Filter</label><br>
  
  <div id="map"></div>
  
  <script type="text/javascript">
        var osmLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
                maxZoom: 18,
                attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Points &copy 2012 LINZ'
            })
        var map = L.map('map', {
          center: [-37.82, 175.24],
          zoom: 8,
          layers: [osmLayer],
          doubleClickZoom: false
        });

    var iconType = ['both', 'soft', 'na'];
    var points = turf.randomPoint(200, {bbox: [174, -39, 176, -36]});
    var n = points.features.length;
    for (var i = 0; i < n; i++) {
      points.features[i].properties.name = 'Point ' + i;
      points.features[i].properties.icontype = i % 3;
      if ((i % 5) == 0) {
        var newPoint = turf.clone(points.features[i]);
        newPoint.properties.name += ' - 2';
        newPoint.properties.icontype = (newPoint.properties.icontype + 1) % 3;
        points.features.push(newPoint);
      }
    }

    var both = L.divIcon({html: '<div style="width: 20px; height: 20px; border-radius: 50%; background-color: red;"></div>'});
    var soft = L.divIcon({html: '<div style="width: 20px; height: 20px; border-radius: 50%; background-color: blue;"></div>'});
    
    function pointToLayer(feature, latLng) {
      var popupContent = "<h1>" + feature.properties.name + "</h1><p>" + feature.properties.icontype + "</p>";
      var marker;
      switch(feature.properties.icontype) {
        case 0:
          marker = L.marker(latLng, {icon: both}).bindPopup(popupContent);
          break;
        case 1:
          marker = L.marker(latLng, {icon: soft}).bindPopup(popupContent);
          break;
        case 2:
          marker = L.circleMarker(latLng).bindPopup(popupContent);
          break;
      }
      return(marker);
    };
    
        var geojsonLayer = L.geoJSON(points, {
      pointToLayer: pointToLayer
    });     

        var clusterLayer = L.markerClusterGroup({
          spiderfyDistanceMultiplier: 2
        });
        clusterLayer.addLayer(geojsonLayer);
        clusterLayer.addTo(map);

   function filter(element) {
     clusterLayer.clearLayers();
     if (element.checked) {
       var layers = geojsonLayer.getLayers();
       layers.forEach(function(layer) {
         if (layer.feature.properties.icontype != 2) {
           clusterLayer.addLayer(layer);
         }
       });
       }
     else {
       clusterLayer.addLayer(geojsonLayer);
     }
   }
    </script>
</body>

Here is working JSFiddle: https://jsfiddle.net/TomazicM/3w0snptx/

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.