Leafletjs-努力使标记在世界视图中合并 [英] Leafletjs - Struggling to get markers to merge at world view

查看:200
本文介绍了Leafletjs-努力使标记在世界视图中合并的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在开发一个利用mapbox choropleth映射和leafletjs标记的可视化工具.目前,我正在努力使标记在国家/地区缩小范围内合并在一起.我已经尝试通读代码,但是找不到解决此问题的方法.

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 &copy; <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个MarkerClusterGroup.但是在最低缩放级别下,您只希望显示一个群集,以便显示整个国家/地区的数量?

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?

不幸的是,在这种情况下,没有内置的方法可以实现此目的.但是,简单的解决方法是简单地构建一个额外的MarkerClusterGroup,在其中添加所有单独的Marker,并将其添加为仅以最低缩放级别进行映射,同时删除所有其他State MCG.使用地图"zoomend"事件读取地图缩放并添加/删除适当的图层组.如果您所在国家/地区的MCG并非完全集群,只需增加 maxClusterRadius 选项.

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.

这篇关于Leafletjs-努力使标记在世界视图中合并的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆