Search code examples
javascriptleafletgeospatialmarkerclustererleaflet.markercluster

How do I get the markercluster group to work with my csv data? (Leaflet.FeatureGroup.SubGroup?)


I am trying to make the markerclusters work so that when I turn on each layer, the markerclusters add together and subtract when I turn off each layer.

I had the markerclusters working when I was not using the layers, but now I'm not sure what to do. I've seen people talk about the L.featureGroup.subGroup, but I don't really know how to apply it to my code.

I'm new to Leaflet and just need some guidance on what I should do please.

Here's what it looks like when all layers are on. Note how the markers are not in the cluster groups: enter image description here

Here's my code:

var map = L.map('map', {
      center: [34.098907, -118.327759],
      zoom: 9,
      tap: false
    });

    var light = L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
      attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>, &copy; <a href="https://carto.com/attribution">CARTO</a>'
    }).addTo(map);

    var terrain = L.tileLayer('https://stamen-tiles.a.ssl.fastly.net/terrain/{z}/{x}/{y}.png', {
      attribution: 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, under <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a href="http://openstreetmap.org">OpenStreetMap</a>, under <a href="http://www.openstreetmap.org/copyright">ODbL</a>.'
    });

    var googleSat = L.tileLayer('http://{s}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}', {
      maxZoom: 20,
      subdomains: ['mt0', 'mt1', 'mt2', 'mt3']
    });


    var baseLayers = {
      "Carto Light Basemap": light,
      "Stamen Terrain Basemap": terrain,
      "Satelite Basemap": googleSat,
    };

    L.control.layers(baseLayers).addTo(map)


    var engineerLayers = {}; // Create an object to hold the engineer layers

    var controlLayers = L.control.layers(null, engineerLayers, {
      position: "bottomleft",
      collapsed: false
    }).addTo(map);

 

    var markers = L.markerClusterGroup();

    $.get('./data.csv', function (csvString) {
      var data = Papa.parse(csvString, { header: true, dynamicTyping: true }).data;

      // Loop through the data and create layers and markers for each engineer
      //***** MAKE SURE CSV FILE DOES NOT HAVE EXTRA LINES OF SPACE OR ELSE AN UNDEFINED LAYER WILL APPEAR*******///
      
      for (var i in data) {
        var row = data[i];
        var marker;
        var engineer = row.Engineer;

        if (!(engineer in engineerLayers)) {
          engineerLayers[engineer] = L.featureGroup();
          controlLayers.addOverlay(engineerLayers[engineer], engineer);
        }

        marker = L.circleMarker([row.Latitude, row.Longitude], {
          radius: 10,
          stroke: true,
          color: getColor(engineer),
          opacity: 1,
          weight: 1,
          fill: true,
          fillColor: getColor(engineer),
          fillOpacity: 0.5
        }).bindPopup('Plan File: ' + row.PFN + '</br>' + 'Engineer: ' + row.Engineer);
        markers.addLayer(marker);
        ;

        markers.addLayer(marker);
        marker.addTo(engineerLayers[engineer]);
      }

      map.addLayer(markers);
    });

    //must change these if engineers ever change
    function getColor(engineer) {
      switch (engineer) {
        case 'Mike':
          return 'green';
        case 'Salar':
          return 'blue';
        case 'Diego':
          return 'purple';
        case 'Saul':
          return 'orange';
        default:
          return 'black';
      }
    }

Here's what it looks like when all the layers are turned off. Note how there are clusters even with the layers off:

enter image description here

UPDATE:

So, I updated my code so that each layer now has it's own marker cluster.

What I can't figure out is how to make each of these subgroups into one parent group. What am I doing wrong?

<!DOCTYPE html>
<html>
<head>
  <title>leaflet-map-csv</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta charset="utf-8">

  <link rel="stylesheet" type="text/css" href="style.css">

  <!-- For the search box -->
  <link rel="stylesheet" href="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.css" />

  <link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster/dist/MarkerCluster.css" />
  <link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster/dist/MarkerCluster.Default.css" />

  <!-- Load Leaflet code library - see updates at http://leafletjs.com/download.html -->
  <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" />

  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.4.2/leaflet.draw.css" />

  <!-- Position the map with Cascading Style Sheet (CSS) -->
  <style>
    body {
      margin: 0;
      padding: 0;
    }

    #map {
      position: absolute;
      top: 0;
      bottom: 0;
      right: 0;
      left: 0;
    }
    
    .leaflet-bottom.leaflet-left {
      left: 10px;
      bottom: 10px;
    }
  </style>

</head>
<body>

  <!-- Insert HTML division tag to layout the map -->
  <div id="map"></div>

  <!-- Insert Javascript (.js) code to create the map -->
  <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>

  <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>

  <script src="https://unpkg.com/leaflet.markercluster/dist/leaflet.markercluster.js"></script>
  <script src="https://unpkg.com/leaflet.featuregroup.subgroup/dist/leaflet.featuregroup.subgroup-src.js"></script>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.0/papaparse.min.js"></script>
  <script src="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.js"></script>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.4.2/leaflet.draw.js"></script>

  <!-- Insert JavaScript code to create the map and add engineer layers -->
  <script>
    var map = L.map('map', {
      center: [34.098907, -118.327759],
      zoom: 9,
      tap: false
    });

    var light = L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
      attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>, &copy; <a href="https://carto.com/attribution">CARTO</a>'
    }).addTo(map);

    var terrain = L.tileLayer('https://stamen-tiles.a.ssl.fastly.net/terrain/{z}/{x}/{y}.png', {
      attribution: 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, under <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a href="http://openstreetmap.org">OpenStreetMap</a>, under <a href="http://www.openstreetmap.org/copyright">ODbL</a>.'
    });

    var googleSat = L.tileLayer('http://{s}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}', {
      maxZoom: 20,
      subdomains: ['mt0', 'mt1', 'mt2', 'mt3']
    });

    var baseLayers = {
      "Carto Light Basemap": light,
      "Stamen Terrain Basemap": terrain,
      "Satellite Basemap": googleSat,
    };

    var controlLayers = L.control.layers(baseLayers);

    var engineerLayers = {}; // Create an object to hold the engineer marker cluster groups

    $.get('./data.csv', function (csvString) {
      var data = Papa.parse(csvString, {
        header: true,
        dynamicTyping: true
      }).data;

      // Create a parent feature group
      var parentGroup = L.featureGroup.subGroup(null, []);

      // Loop through the data and create marker clusters for each engineer
      for (var i = 0; i < data.length; i++) {
        var row = data[i];
        var marker;
        var engineer = row.Engineer;

        if (!(engineer in engineerLayers)) {
          engineerLayers[engineer] = L.markerClusterGroup({
            spiderfyOnMaxZoom: false,
            showCoverageOnHover: true,
            zoomToBoundsOnClick: true
          });

          // Add the child layers to the parent group
          parentGroup.addLayer(engineerLayers[engineer]);
          controlLayers.addOverlay(engineerLayers[engineer], engineer);
        }

        marker = L.circleMarker([row.Latitude, row.Longitude], {
          radius: 10,
          stroke: true,
          color: getColor(engineer),
          opacity: 1,
          weight: 1,
          fill: true,
          fillColor: getColor(engineer),
          fillOpacity: 0.5
        }).bindPopup('Plan File: ' + row.PFN + '</br>' + 'Engineer: ' + row.Engineer);

        engineerLayers[engineer].addLayer(marker);
      }

      // Add the parent group to the map
      parentGroup.addTo(map);
      controlLayers.addTo(map);
    });

    // Add event listeners to handle showing and hiding markers and clusters
    controlLayers.on('overlayadd', function (eventLayer) {
      var engineer = eventLayer.name;
      if (engineer in engineerLayers) {
        engineerLayers[engineer].addTo(map);
      }
    });

    controlLayers.on('overlayremove', function (eventLayer) {
      var engineer = eventLayer.name;
      if (engineer in engineerLayers) {
        engineerLayers[engineer].removeFrom(map);
      }
    });

    // Must change these if engineers ever change
    function getColor(engineer) {
      switch (engineer) {
        case 'Mike':
          return 'green';
        case 'Salar':
          return 'blue';
        case 'Diego':
          return 'purple';
        case 'Saul':
          return 'orange';
        default:
          return 'black';
      }
    }
  </script>
</body>
</html>

Solution

  • In your first attempt, you add each marker to both your markers MarkerClusterGroup and your engineerLayers[engineer] overlay.

    This results in a situation almost identical to Leaflet markercluster markers and cluster icons both visible on load, where Markers are "simultaneously" counted in clusters and shown on map (or not, if the overlay is off in your case).

    Indeed L.featureGroup.subGroup sounds to cover your use case:

    Typical usage is to dynamically add/remove groups of markers from Marker Cluster.

    Make sure to follow the documentation:

    // The Parent is the MCG
    var parentGroup = L.markerClusterGroup();
    
    for (var i in data) {
      var row = data[i];
      var engineer = row.Engineer;
    
      if (!(engineer in engineerLayers)) {
        // Each overlay that should actually be a part of MCG
        // should be made as a Subgroup, with the MCG as their parent
        engineerLayers[engineer] = L.featureGroup.subGroup(parentGroup, []);
    
        controlLayers.addOverlay(engineerLayers[engineer], engineer);
      }
    
      var marker = L.circleMarker([row.Latitude, row.Longitude], {});
    
      // Add the marker only to its overlay
      marker.addTo(engineerLayers[engineer]; 
    }
    
    map.addLayer(parentGroup);
    

    Disclaimer: I am the author of Leaflet.FeatureGroup.SubGroup