问题
I am working on a visualisation that utilises a mapbox choropleth map and leafletjs markers. At the moment, I am struggling on getting the markers to merge together on zoom out at country level. I have tried reading the my code through but I have not been able to find a way to solve this issue.
Attached is my code for my map.
 (function() {
  window.petMap = {
    map: '',
    mapCenter: [36.19109202182454, -95.99853515625001],
    tileUrl: 'https://api.mapbox.com/styles/v1/sourcetop/ck3yaryi60z7z1co7p7ttw4kf/tiles/256/{z}/{x}/{y}@2x?access_token=pk.eyJ1Ijoic291cmNldG9wIiwiYSI6ImNrM3lhYXMyazA2ZmczZ3FsbDM0aThhYnIifQ.eyPqNH3WmEFy8D1aw2vn6w',
    mapAttribution: 'Map data © <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors',
    bounds: [],
    zoom: 5,
    zoomLevel: 'country',
    callbackUrl: URL.com
    locationCallbackUrl: URL.com
    dmaJsonUrl: URL.com
    markers: {
      profile: {},
      conditions: {}
    },
    dataLevel: 'profile',
    locationMarkes: {},
    dmaMarkers: [],
    dmaPoly: [],
    progress: 0,
    progressTimer: '',
    initMap: function() {
      this.mapZoomClass();
      this.map = L.map('map', {
        center: this.mapCenter,
        zoom: 5,
        zoomControl: false,
        zoomDelta: 2,
        trackResize: true,
        wheelPxPerZoomLevel: 25,
        scrollEnable: true
      });
      L.control.zoom({
        position: 'bottomright'
      }).addTo(this.map);
      L.tileLayer(this.tileUrl, {
    attribution: this.mapAttribution,
    minZoom: 2,
    maxZoom: 13
  }).addTo(this.map);
  this.getBounds();
  this.getZoomLevel();
  this.mapAction();
},
plotMap: function(dataLevel) {
  this.progressBarInit();
  this.dataLevel = dataLevel;
  this.dataLevelFixer();
  this.requestDataFromServer();
},
requestDataParams: function() {
  return {
    bounds: this.bounds,
    zoom: this.zoom,
    level: this.zoomLevel,
    year: petMapFilters.year,
    animal: Object.keys(window.petMapFilters.animal),
    age: Object.keys(window.petMapFilters.age),
    breeds: Object.keys(window.petMapFilters.breeds),
    ailments: Object.keys(window.petMapFilters.ailments)
  };
},
requestDataFromServer: function() {
  $.ajax({
    url : this.callbackUrl,
    data: this.requestDataParams(),
  })
  .done(function(response) {
    petMap.progressBarReset();
    if(petMap.dataLevel == 'conditions') {
      petMap.dataLevel = 'profile';
      petMap.plotMarkers(response);
      petMap.dataLevel = 'conditions';
    }
    petMap.plotMarkers(response);
  })
  .fail(function(jqXHR, textStatus) {
    console.log('Request failed: ' + textStatus);
  });
},
// Plot markers.
plotMarkers: function(response) {
  this.clearmarkerLayer();
  var color = window.petMapFilters.colors[this.dataLevel];
  if(this.zoomLevel != 'state') {
    this.initClusterMarker(color, this.dataLevel);
  } else {
    this.markers[petMap.dataLevel] = L.featureGroup();
  }
  var markerList = [];
  response.forEach(function(data){
    var count = petMap.getResultCount(data);
    if(data.location != null && count) {
      var icon = L.divIcon({
        html: petMap.getMarkerHtml(count, color)
      });
      var marker = L.marker(
        new L.LatLng(data.location[1], data.location[0]),
        {icon: icon, zIndexOffset: 2000}
      );
      marker.count = count;
      if(petMap.zoomLevel == 'state') {
        marker.on('click', function(e) {
          petMap.map.setView(e.latlng, 1);
        });
        petMap.markers[petMap.dataLevel].addLayer(marker);
      } else {
        markerList.push(marker);
      }
    }
  });
  if(this.zoomLevel != 'state') {
    this.markers[this.dataLevel].addLayers(markerList);
  }
  this.map.addLayer(this.markers[this.dataLevel]);
},
// init cluster markers.
initClusterMarker: function(color, type) {
  this.markers[type] = L.markerClusterGroup({
    chunkedLoading: true,
    spiderfyOnMaxZoom: true,
    showCoverageOnHover: false,
    iconCreateFunction: function(cluster) {
      var markers = cluster.getAllChildMarkers();
      var markerCount = 0;
      markers.forEach(function(m){
        markerCount = markerCount + m.count;
      });
      return new L.DivIcon({
        html: petMap.getMarkerHtml(markerCount, color),
        className: 'marker-type-cluster-' + (petMapFilters.colors.profile == color ? 'profile' : 'condition')
      });
    }
  });
},
clearmarkerLayer: function() {
  if(Object.keys(this.markers.profile).length && this.dataLevel == 'profile') {
    this.markers.profile.clearLayers();
  }
  if(Object.keys(this.markers.conditions).length) {
    this.markers.conditions.clearLayers();
  }
},
/**********************************************************/
plotLocations: function() {
  if(Object.keys(petMapFilters.locationSelected).length) {
    for(var i in petMapFilters.locationSelected) {
      if(typeof this.locationMarkes[i] == 'undefined') {
        this.plotLocation(i);
      }
    }
  }
},
removeLocationMarker: function(id) {
  if(typeof this.locationMarkes[id] != 'undefined' && Object.keys(this.locationMarkes[id]).length) {
    this.locationMarkes[id].clearLayers();
    delete this.locationMarkes[id];
  }
},
plotLocation: function(id) {
  this.progressBarInit();
  $.ajax({
    url: this.locationCallbackUrl,
    data: {
      // bounds: this.bounds,
      collection: id
    }
  })
  .done(function(response) {
    petMap.progressBarReset();
    petMap.plotLocationMarkers(id, response);
  })
  .fail(function(jqXHR, textStatus) {
    console.log('Request failed: ' + textStatus);
  });
},
plotLocationMarkers: function(id, response) {
  if(typeof this.locationMarkes[id] != 'undefined' && Object.keys(this.locationMarkes[id]).length) {
    this.locationMarkes[id].clearLayers();
    this.locationMarkes[id] = [];
  }
  this.initLocationClusterMarker(id);
  var markerList = [];
  response.forEach(function(data){
    if(data.location != null) {
      var marker = L.marker(new L.LatLng(data.location.coordinates[1], data.location.coordinates[0]), {icon: petMap.getLocationIcon(id, 'marker'), zIndexOffset: 1000}).bindPopup(petMap.getLocationPopup(data));
      markerList.push(marker);
    }
  });
  this.locationMarkes[id].addLayers(markerList);
},
initLocationClusterMarker: function(id) {
  this.locationMarkes[id] = L.markerClusterGroup({
    chunkedLoading: true,
    spiderfyOnMaxZoom: true,
    showCoverageOnHover: false,
    iconCreateFunction: function(cluster) {
      return petMap.getLocationIcon(id, 'cluster', cluster.getAllChildMarkers().length);
    }
  });
  this.map.addLayer(this.locationMarkes[id]);
},
getLocationIcon: function(id, type, count) {
  var sizeMap = {
    'shop-marker': [16, 19],
    'shop-cluster': [35, 40],
    'clinic-marker': [18, 17],
    'clinic-cluster': [38, 35],
  };
  var dt = id.split(':');
  var icon = dt[0] == 'shop' ? 'shop-' + type + '.svg' : 'hospital-' + type + '.svg';
  var anchor = dt[0] == 'shop' ? [-1, -10] : [0, -8];
  if(type == 'cluster') {
    return new L.DivIcon({
      html: '<div class="location-marker location-type-' + dt[0] + '" tabindex="0">' + count + '</div>',
      className: 'marker-type-cluster-location'
    });
  } else {
    return L.icon({
      iconUrl: 'images/' + icon,
      iconSize: sizeMap[dt[0] + '-' + type],
      popupAnchor: anchor,
      className: 'location-single-marker'
    });
  }
},
getLocationPopup: function(data) {
  var details = [];
  if(typeof data.street != 'undefined') { details.push(data.street); }
  if(typeof data.city != 'undefined') { details.push(data.city); }
  if(typeof data.state != 'undefined') { details.push(data.state); }
  if(typeof data.contact != 'undefined') { details.push(data.contact); }
  details = details.join('<br/>');
  if(details.length) {
    details = '<div class="location">' + details + '</div>';
  }
  return '<div class="hospital-popup"><div class="color-red">' + data.name + '</div>' + details + '</div>';
},
/**********************************************************/
plotDmaData: function(op) {
  if(op == 'add') {
    if(!Object.keys(this.dmaMarkers).length) {
      this.getDmaData();
    } else {
      this.map.addLayer(this.dmaMarkers);
      this.map.addLayer(this.dmaPoly);
    }
  } else {
    if(Object.keys(this.dmaMarkers).length) {
      this.map.removeLayer(this.dmaMarkers);
      this.map.removeLayer(this.dmaPoly);
    }
  }
},
getDmaData: function() {
  $.getJSON(this.dmaJsonUrl, function(response) {
    petMap.plotDmaMarkers(response);
  });
},
plotDmaMarkers: function(response) {
  this.dmaMarkers = L.featureGroup();
  this.dmaPoly = L.geoJson(response, {
    style: {
      opacity: 0,
      fillOpacity: 0
    },
    onEachFeature: function(feature, layer) {
      var latlng = new L.LatLng(feature.properties.latitude, feature.properties.longitude);
      var icon = petMap.getDmaMarkerIcon(feature.properties);
      var marker = L.marker(latlng, {icon: icon, riseOnHover: true, riseOffset: 3000});
      marker.data = feature.properties;
      marker.layer = layer;
      marker.on('click', function(e) {
        petMapDmaPopup.init(e.target.data);
      });
      marker.on('mouseover', function(e) {
        var layer = e.target.layer;
        layer.setStyle({
            weight: 1,
            color: '#8A8D8F',
            dashArray: '',
            fillOpacity: 0.5
        });
        if(!L.Browser.ie && !L.Browser.opera && !L.Browser.edge) {
          layer.bringToFront();
        }
      });
      marker.on('mouseout', function(e) {
        petMap.dmaPoly.resetStyle(e.target.layer);
      });
      petMap.dmaMarkers.addLayer(marker);
    }
  });
  this.map.addLayer(this.dmaMarkers);
  this.map.addLayer(this.dmaPoly);
},
getDmaMarkerIcon: function(data) {
  var cluster_classes = [
    'dma-marker-icon',
    'leaflet-marker-icon',
    'leaflet-zoom-animated',
    'leaflet-clickable'
  ];
  return L.divIcon({
    html: '<div class="' + cluster_classes.join(' ') + '" tabindex="0">' + data.dma_name + '</div>'
  });
},
/**********************************************************/
mapAction: function() {
  this.map.on('zoomend dragend',function(e) {
    petMap.getBounds();
    petMap.getZoomLevel();
    petMap.progressBarInit();
    petMap.requestDataFromServer();
  });
},
resetMap: function() {
  this.dataLevel = 'profile';
  this.clearmarkerLayer();
  if(Object.keys(this.locationMarkes).length) {
    for(var id in this.locationMarkes) {
      this.locationMarkes[id].clearLayers();
      delete this.locationMarkes[id];
    }
  }
  this.map.removeLayer(this.dmaMarkers);
  this.map.setView(new L.LatLng(this.mapCenter[0], this.mapCenter[1]), 5);
},
getBounds: function() {
  var bounds = this.map.getBounds();
  this.bounds = {
    tr: {
      lat: bounds.getNorthEast().lat,
      lng: bounds.getNorthEast().lng
    },
    bl: {
      lat: bounds.getSouthWest().lat,
      lng: bounds.getSouthWest().lng
    }
  };
},
getZoomLevel: function() {
  this.zoom = this.map.getZoom();
  this.zoomLevel = 'state';
  if(this.zoom > 6) {
    this.zoomLevel = 'city';
  }
  if(this.zoom > 8) {
    this.zoomLevel = 'zip';
  }
  // dma data.
  if(this.zoom > 6) {
    this.plotDmaData('add');
  } else {
    this.plotDmaData('remove');
  }
},
mapZoomClass: function() {
  L.Map.mergeOptions({
    zoomCss: true
  });
  L.Map.ZoomCSS = L.Handler.extend({
    addHooks: function() {
      this._zoomCSS();
      this._map.on('zoomend', this._zoomCSS, this);
    },
    removeHooks: function() {
      this._map.off('zoomend', this._zoomCSS, this);
    },
    _zoomCSS: function(e) {
      var map = this._map,
          zoom = map.getZoom(),
          container = map.getContainer();
      container.className = container.className.replace( /\smap-zoom-[0-9]{1,2}/g, '' ) + ' map-zoom-' + zoom;
    }
  });
  L.Map.addInitHook('addHandler', 'zoomCss', L.Map.ZoomCSS);
},
/**********************************************************/
progressBarInit: function() {
  clearTimeout(petMap.progressTimer);
  $('.progress-bar .progress').css('width', '0%');
  $('.progress-bar').removeClass('hide');
  this.progressBarProress();
},
progressBarProress: function() {
  this.progressTimer = setTimeout(function() {
    petMap.progress += 0.5;
    $('.progress-bar .progress').css('width', petMap.progress + '%');
    if(petMap.progress != 100) {
      petMap.progressBarProress();
    }
  }, 25);
},
progressBarReset: function() {
  $('.progress-bar').addClass('hide');
  $('.progress-bar .progress').css('width', '0%');
  clearTimeout(petMap.progressTimer);
  petMap.progress = 0;
},
/**********************************************************/
formatCount: function(count) {
  if(count > 1000) {
    count = Math.floor(count / 1000) + 'K' + (count % 1000 !== 0 ? '+' : '');
  }
  return count;
},
getMarkerSize: function(count) {
  size = 30;
  if(count > 100) {
    size = 35;
  }
  if(count > 500) {
    size = 40;
  }
  if(count > 1000) {
    size = 45;
  }
  if(count > 5000) {
    size = 50;
  }
  if(count > 10000) {
    size = 55;
  }
  if(count > 20000) {
    size = 60;
  }
  if(count > 30000) {
    size = 65;
  }
  if(count > 50000) {
    size = 70;
  }
  if(count > 70000) {
    size = 75;
  }
  if(count > 90000) {
    size = 80;
  }
  if(count > 120000) {
    size = 85;
  }
  if(count > 140000) {
    size = 90;
  }
  if(count > 160000) {
    size = 95;
  }
  if(count > 180000) {
    size = 100;
  }
  if(count > 200000) {
    size = 105;
  }
  if(count > 220000) {
    size = 110;
  }
  if(count > 240000) {
    size = 115;
  }
  if(count > 260000) {
    size = 120;
  }
  if(count > 280000) {
    size = 125;
  }
  if(count > 300000) {
    size = 130;
  }
  if(count > 320000) {
    size = 135;
  }
  if(count > 340000) {
    size = 140;
  }
  if(count > 360000) {
    size = 145;
  }
  if(count > 380000) {
    size = 150;
  }
  if(count > 400000) {
    size = 155;
  }
  if(count > 500000) {
    size = 160;
  }
  if(count > 600000) {
    size = 165;
  }
  if(count > 700000) {
    size = 170;
  }
  if(count > 800000) {
    size = 175;
  }
  return size;
},
getMarkerHtml: function(count, color) {
  var size = this.getMarkerSize(count);
  var hsize = (size / 2) - 6;
  var font = count < 1000 ? Math.ceil(size / 3) : Math.ceil(size / 4);
  if(count < 100) {
    font = font + 3;
  }
  var cluster_classes = [
    'map-marker',
    'marker-bg-' + (petMapFilters.colors.profile === color ? 'profile' : 'condition')
  ];
  if(this.zoomLevel !== 'zip') {
    size = size * 1.5;
    if(petMapFilters.colors.profile !== color) {
      hsize = size / 2;
    }
  }
  var cluster_styles = [
    'margin-left: -' + hsize + 'px;',
    'margin-top: -' + hsize + 'px;',
    'width: ' + size + 'px;',
    'height: ' + size + 'px;',
    'font-size: ' + font + 'px;'
  ];
  var div_style = [
    'line-height: ' + (size - (size * 0.3)) + 'px;'
  ];
  count = this.formatCount(count);
  return '<div class="' + cluster_classes.join(' ') + '" tabindex="0" style="' + cluster_styles.join(' ') + '"><div style="' + div_style.join(' ') + '">' + count + '</div></div>';
},
getResultCount: function(data) {
  if(this.dataLevel === 'conditions') {
    if(typeof data.total_ailments != 'undefined') {
          return data.total_ailments;
        }
        return 0;
      }
      return data.total;
    },
    dataLevelFixer: function() {
      this.dataLevel = 'profile';
      if(Object.keys(petMapFilters.ailments).length) {
        this.dataLevel = 'conditions';
      }
    }
  };
})(jQuery);
    回答1:
If I understand correctly, you build 1 MarkerClusterGroup per State. But at lowest zoom level, you would like only a single cluster to be displayed, so that it shows the count for the whole country?
In that case, unfortunately there is no built-in way to achieve this. However, the easy workaround is simply to build an extra MarkerClusterGroup where you add all of your individual Markers as well, and add it to map only at lowest zoom level, while you remove all the other State MCG's. Use the map "zoomend" event to read map zoom and and add / remove the appropriate Layer Groups. If your country level MCG is not totally clustered, simply increase the maxClusterRadius option.
回答2:
The plot markers sections was preventing me from initialising the markers due to the original zoom level.
plotMarkers: function(response) {
  this.clearmarkerLayer();
  var color = window.petMapFilters.colors[this.dataLevel];
    this.initClusterMarker(color, this.dataLevel);
Worked fine.
来源:https://stackoverflow.com/questions/61468094/leafletjs-struggling-to-get-markers-to-merge-at-world-view