From 1820aef3fb5c926664de1d4d484f64a5c9ba7099 Mon Sep 17 00:00:00 2001
From: YANGDL <114714267@qq.com>
Date: 星期二, 05 一月 2021 17:06:08 +0800
Subject: [PATCH] 优化逻辑
---
src/components/plugin/cluster-layer/leaflet.markercluster-src.js | 4503 +++++++++++++++++++++++++++++-----------------------------
1 files changed, 2,245 insertions(+), 2,258 deletions(-)
diff --git a/src/components/plugin/cluster-layer/leaflet.markercluster-src.js b/src/components/plugin/cluster-layer/leaflet.markercluster-src.js
index 85e3882..e0cfa31 100644
--- a/src/components/plugin/cluster-layer/leaflet.markercluster-src.js
+++ b/src/components/plugin/cluster-layer/leaflet.markercluster-src.js
@@ -1,1095 +1,1086 @@
-'use strict';
-function init(L) {
- /*
+'use strict'
+function init (L) {
+ /*
* L.MarkerClusterGroup extends L.FeatureGroup by clustering the markers contained within
*/
- var MarkerClusterGroup = L.MarkerClusterGroup = L.FeatureGroup.extend({
+ var MarkerClusterGroup = L.MarkerClusterGroup = L.FeatureGroup.extend({
- options: {
- maxClusterRadius: 80, // A cluster will cover at most this many pixels from its center
- iconCreateFunction: null,
- clusterPane: L.Marker.prototype.options.pane,
+ options: {
+ maxClusterRadius: 80, // A cluster will cover at most this many pixels from its center
+ iconCreateFunction: null,
+ clusterPane: L.Marker.prototype.options.pane,
- spiderfyOnMaxZoom: true,
- showCoverageOnHover: true,
- zoomToBoundsOnClick: true,
- singleMarkerMode: false,
+ spiderfyOnMaxZoom: true,
+ showCoverageOnHover: true,
+ zoomToBoundsOnClick: true,
+ singleMarkerMode: false,
- disableClusteringAtZoom: null,
+ disableClusteringAtZoom: null,
- // Setting this to false prevents the removal of any clusters outside of the viewpoint, which
- // is the default behaviour for performance reasons.
- removeOutsideVisibleBounds: true,
+ // Setting this to false prevents the removal of any clusters outside of the viewpoint, which
+ // is the default behaviour for performance reasons.
+ removeOutsideVisibleBounds: true,
- // Set to false to disable all animations (zoom and spiderfy).
- // If false, option animateAddingMarkers below has no effect.
- // If L.DomUtil.TRANSITION is falsy, this option has no effect.
- animate: true,
+ // Set to false to disable all animations (zoom and spiderfy).
+ // If false, option animateAddingMarkers below has no effect.
+ // If L.DomUtil.TRANSITION is falsy, this option has no effect.
+ animate: true,
- // Whether to animate adding markers after adding the MarkerClusterGroup to the map
- // If you are adding individual markers set to true, if adding bulk markers leave false for massive performance gains.
- animateAddingMarkers: false,
+ // Whether to animate adding markers after adding the MarkerClusterGroup to the map
+ // If you are adding individual markers set to true, if adding bulk markers leave false for massive performance gains.
+ animateAddingMarkers: false,
- // Increase to increase the distance away that spiderfied markers appear from the center
- spiderfyDistanceMultiplier: 1,
+ // Increase to increase the distance away that spiderfied markers appear from the center
+ spiderfyDistanceMultiplier: 1,
- // Make it possible to specify a polyline options on a spider leg
- spiderLegPolylineOptions: {weight: 1.5, color: '#222', opacity: 0.5},
+ // Make it possible to specify a polyline options on a spider leg
+ spiderLegPolylineOptions: { weight: 1.5, color: '#222', opacity: 0.5 },
- // When bulk adding layers, adds markers in chunks. Means addLayers may not add all the layers in the call, others will be loaded during setTimeouts
- chunkedLoading: false,
- chunkInterval: 200, // process markers for a maximum of ~ n milliseconds (then trigger the chunkProgress callback)
- chunkDelay: 50, // at the end of each interval, give n milliseconds back to system/browser
- chunkProgress: null, // progress callback: function(processed, total, elapsed) (e.g. for a progress indicator)
+ // When bulk adding layers, adds markers in chunks. Means addLayers may not add all the layers in the call, others will be loaded during setTimeouts
+ chunkedLoading: false,
+ chunkInterval: 200, // process markers for a maximum of ~ n milliseconds (then trigger the chunkProgress callback)
+ chunkDelay: 50, // at the end of each interval, give n milliseconds back to system/browser
+ chunkProgress: null, // progress callback: function(processed, total, elapsed) (e.g. for a progress indicator)
- // Options to pass to the L.Polygon constructor
- polygonOptions: {}
- },
+ // Options to pass to the L.Polygon constructor
+ polygonOptions: {}
+ },
- initialize: function(options) {
- L.Util.setOptions(this, options);
- if (!this.options.iconCreateFunction) {
- this.options.iconCreateFunction = this._defaultIconCreateFunction;
+ initialize: function (options) {
+ L.Util.setOptions(this, options)
+ if (!this.options.iconCreateFunction) {
+ this.options.iconCreateFunction = this._defaultIconCreateFunction
+ }
+
+ this._featureGroup = L.featureGroup()
+ this._featureGroup.addEventParent(this)
+
+ this._nonPointGroup = L.featureGroup()
+ this._nonPointGroup.addEventParent(this)
+
+ this._inZoomAnimation = 0
+ this._needsClustering = []
+ this._needsRemoving = [] // Markers removed while we aren't on the map need to be kept track of
+ // The bounds of the currently shown area (from _getExpandedVisibleBounds) Updated on zoom/move
+ this._currentShownBounds = null
+
+ this._queue = []
+
+ this._childMarkerEventHandlers = {
+ dragstart: this._childMarkerDragStart,
+ move: this._childMarkerMoved,
+ dragend: this._childMarkerDragEnd
+ }
+
+ // Hook the appropriate animation methods.
+ var animate = L.DomUtil.TRANSITION && this.options.animate
+ L.extend(this, animate ? this._withAnimation : this._noAnimation)
+ // Remember which MarkerCluster class to instantiate (animated or not).
+ this._markerCluster = animate ? L.MarkerCluster : L.MarkerClusterNonAnimated
+ },
+
+ addLayer: function (layer) {
+ if (layer instanceof L.LayerGroup) {
+ return this.addLayers([layer])
+ }
+
+ // Don't cluster non point data
+ if (!layer.getLatLng) {
+ this._nonPointGroup.addLayer(layer)
+ this.fire('layeradd', { layer: layer })
+ return this
+ }
+
+ if (!this._map) {
+ this._needsClustering.push(layer)
+ this.fire('layeradd', { layer: layer })
+ return this
+ }
+
+ if (this.hasLayer(layer)) {
+ return this
+ }
+
+ // If we have already clustered we'll need to add this one to a cluster
+
+ if (this._unspiderfy) {
+ this._unspiderfy()
+ }
+
+ this._addLayer(layer, this._maxZoom)
+ this.fire('layeradd', { layer: layer })
+
+ // Refresh bounds and weighted positions.
+ this._topClusterLevel._recalculateBounds()
+
+ this._refreshClustersIcons()
+
+ // Work out what is visible
+ var visibleLayer = layer
+ var currentZoom = this._zoom
+ if (layer.__parent) {
+ while (visibleLayer.__parent._zoom >= currentZoom) {
+ visibleLayer = visibleLayer.__parent
+ }
+ }
+
+ if (this._currentShownBounds.contains(visibleLayer.getLatLng())) {
+ if (this.options.animateAddingMarkers) {
+ this._animationAddLayer(layer, visibleLayer)
+ } else {
+ this._animationAddLayerNonAnimated(layer, visibleLayer)
+ }
+ }
+ return this
+ },
+
+ removeLayer: function (layer) {
+ if (layer instanceof L.LayerGroup) {
+ return this.removeLayers([layer])
+ }
+
+ // Non point layers
+ if (!layer.getLatLng) {
+ this._nonPointGroup.removeLayer(layer)
+ this.fire('layerremove', { layer: layer })
+ return this
+ }
+
+ if (!this._map) {
+ if (!this._arraySplice(this._needsClustering, layer) && this.hasLayer(layer)) {
+ this._needsRemoving.push({ layer: layer, latlng: layer._latlng })
+ }
+ this.fire('layerremove', { layer: layer })
+ return this
+ }
+
+ if (!layer.__parent) {
+ return this
+ }
+
+ if (this._unspiderfy) {
+ this._unspiderfy()
+ this._unspiderfyLayer(layer)
+ }
+
+ // Remove the marker from clusters
+ this._removeLayer(layer, true)
+ this.fire('layerremove', { layer: layer })
+
+ // Refresh bounds and weighted positions.
+ this._topClusterLevel._recalculateBounds()
+
+ this._refreshClustersIcons()
+
+ layer.off(this._childMarkerEventHandlers, this)
+
+ if (this._featureGroup.hasLayer(layer)) {
+ this._featureGroup.removeLayer(layer)
+ if (layer.clusterShow) {
+ layer.clusterShow()
+ }
+ }
+
+ return this
+ },
+
+ // Takes an array of markers and adds them in bulk
+ addLayers: function (layersArray, skipLayerAddEvent) {
+ if (!L.Util.isArray(layersArray)) {
+ return this.addLayer(layersArray)
+ }
+
+ var fg = this._featureGroup
+ var npg = this._nonPointGroup
+ var chunked = this.options.chunkedLoading
+ var chunkInterval = this.options.chunkInterval
+ var chunkProgress = this.options.chunkProgress
+ var l = layersArray.length
+ var offset = 0
+ var originalArray = true
+ var m
+
+ if (this._map) {
+ var started = (new Date()).getTime()
+ var process = L.bind(function () {
+ var start = (new Date()).getTime()
+ for (; offset < l; offset++) {
+ if (chunked && offset % 200 === 0) {
+ // every couple hundred markers, instrument the time elapsed since processing started:
+ var elapsed = (new Date()).getTime() - start
+ if (elapsed > chunkInterval) {
+ break // been working too hard, time to take a break :-)
+ }
}
- this._featureGroup = L.featureGroup();
- this._featureGroup.addEventParent(this);
+ m = layersArray[offset]
- this._nonPointGroup = L.featureGroup();
- this._nonPointGroup.addEventParent(this);
-
- this._inZoomAnimation = 0;
- this._needsClustering = [];
- this._needsRemoving = []; // Markers removed while we aren't on the map need to be kept track of
- // The bounds of the currently shown area (from _getExpandedVisibleBounds) Updated on zoom/move
- this._currentShownBounds = null;
-
- this._queue = [];
-
- this._childMarkerEventHandlers = {
- 'dragstart': this._childMarkerDragStart,
- 'move': this._childMarkerMoved,
- 'dragend': this._childMarkerDragEnd,
- };
-
- // Hook the appropriate animation methods.
- var animate = L.DomUtil.TRANSITION && this.options.animate;
- L.extend(this, animate ? this._withAnimation : this._noAnimation);
- // Remember which MarkerCluster class to instantiate (animated or not).
- this._markerCluster = animate ? L.MarkerCluster : L.MarkerClusterNonAnimated;
- },
-
- addLayer: function(layer) {
-
- if (layer instanceof L.LayerGroup) {
- return this.addLayers([layer]);
+ // Group of layers, append children to layersArray and skip.
+ // Side effects:
+ // - Total increases, so chunkProgress ratio jumps backward.
+ // - Groups are not included in this group, only their non-group child layers (hasLayer).
+ // Changing array length while looping does not affect performance in current browsers:
+ // http:// jsperf.com/for-loop-changing-length/6
+ if (m instanceof L.LayerGroup) {
+ if (originalArray) {
+ layersArray = layersArray.slice()
+ originalArray = false
+ }
+ this._extractNonGroupLayers(m, layersArray)
+ l = layersArray.length
+ continue
}
- // Don't cluster non point data
- if (!layer.getLatLng) {
- this._nonPointGroup.addLayer(layer);
- this.fire('layeradd', {layer: layer});
- return this;
+ // Not point data, can't be clustered
+ if (!m.getLatLng) {
+ npg.addLayer(m)
+ if (!skipLayerAddEvent) {
+ this.fire('layeradd', { layer: m })
+ }
+ continue
}
- if (!this._map) {
- this._needsClustering.push(layer);
- this.fire('layeradd', {layer: layer});
- return this;
+ if (this.hasLayer(m)) {
+ continue
}
- if (this.hasLayer(layer)) {
- return this;
+ this._addLayer(m, this._maxZoom)
+ if (!skipLayerAddEvent) {
+ this.fire('layeradd', { layer: m })
}
-
- // If we have already clustered we'll need to add this one to a cluster
-
- if (this._unspiderfy) {
- this._unspiderfy();
+ // If we just made a cluster of size 2 then we need to remove the other marker from the map (if it is) or we never will
+ if (m.__parent) {
+ if (m.__parent.getChildCount() === 2) {
+ var markers = m.__parent.getAllChildMarkers()
+ var otherMarker = markers[0] === m ? markers[1] : markers[0]
+ fg.removeLayer(otherMarker)
+ }
}
+ }
- this._addLayer(layer, this._maxZoom);
- this.fire('layeradd', {layer: layer});
+ if (chunkProgress) {
+ // report progress and time elapsed:
+ chunkProgress(offset, l, (new Date()).getTime() - started)
+ }
+ // Completed processing all markers.
+ if (offset === l) {
// Refresh bounds and weighted positions.
- this._topClusterLevel._recalculateBounds();
+ this._topClusterLevel._recalculateBounds()
- this._refreshClustersIcons();
+ this._refreshClustersIcons()
- // Work out what is visible
- var visibleLayer = layer,
- currentZoom = this._zoom;
- if (layer.__parent) {
- while (visibleLayer.__parent._zoom >= currentZoom) {
- visibleLayer = visibleLayer.__parent;
- }
+ this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds)
+ } else {
+ setTimeout(process, this.options.chunkDelay)
+ }
+ }, this)
+
+ process()
+ } else {
+ var needsClustering = this._needsClustering
+
+ for (; offset < l; offset++) {
+ m = layersArray[offset]
+
+ // Group of layers, append children to layersArray and skip.
+ if (m instanceof L.LayerGroup) {
+ if (originalArray) {
+ layersArray = layersArray.slice()
+ originalArray = false
}
+ this._extractNonGroupLayers(m, layersArray)
+ l = layersArray.length
+ continue
+ }
- if (this._currentShownBounds.contains(visibleLayer.getLatLng())) {
- if (this.options.animateAddingMarkers) {
- this._animationAddLayer(layer, visibleLayer);
- } else {
- this._animationAddLayerNonAnimated(layer, visibleLayer);
- }
+ // Not point data, can't be clustered
+ if (!m.getLatLng) {
+ npg.addLayer(m)
+ continue
+ }
+
+ if (this.hasLayer(m)) {
+ continue
+ }
+
+ needsClustering.push(m)
+ }
+ }
+ return this
+ },
+
+ // Takes an array of markers and removes them in bulk
+ removeLayers: function (layersArray) {
+ var i; var m
+ var l = layersArray.length
+ var fg = this._featureGroup
+ var npg = this._nonPointGroup
+ var originalArray = true
+
+ if (!this._map) {
+ for (i = 0; i < l; i++) {
+ m = layersArray[i]
+
+ // Group of layers, append children to layersArray and skip.
+ if (m instanceof L.LayerGroup) {
+ if (originalArray) {
+ layersArray = layersArray.slice()
+ originalArray = false
}
- return this;
- },
+ this._extractNonGroupLayers(m, layersArray)
+ l = layersArray.length
+ continue
+ }
- removeLayer: function(layer) {
+ this._arraySplice(this._needsClustering, m)
+ npg.removeLayer(m)
+ if (this.hasLayer(m)) {
+ this._needsRemoving.push({ layer: m, latlng: m._latlng })
+ }
+ this.fire('layerremove', { layer: m })
+ }
+ return this
+ }
- if (layer instanceof L.LayerGroup) {
- return this.removeLayers([layer]);
- }
+ if (this._unspiderfy) {
+ this._unspiderfy()
- // Non point layers
- if (!layer.getLatLng) {
- this._nonPointGroup.removeLayer(layer);
- this.fire('layerremove', {layer: layer});
- return this;
- }
+ // Work on a copy of the array, so that next loop is not affected.
+ var layersArray2 = layersArray.slice()
+ var l2 = l
+ for (i = 0; i < l2; i++) {
+ m = layersArray2[i]
- if (!this._map) {
- if (!this._arraySplice(this._needsClustering, layer) && this.hasLayer(layer)) {
- this._needsRemoving.push({layer: layer, latlng: layer._latlng});
- }
- this.fire('layerremove', {layer: layer});
- return this;
- }
+ // Group of layers, append children to layersArray and skip.
+ if (m instanceof L.LayerGroup) {
+ this._extractNonGroupLayers(m, layersArray2)
+ l2 = layersArray2.length
+ continue
+ }
- if (!layer.__parent) {
- return this;
- }
+ this._unspiderfyLayer(m)
+ }
+ }
- if (this._unspiderfy) {
- this._unspiderfy();
- this._unspiderfyLayer(layer);
- }
+ for (i = 0; i < l; i++) {
+ m = layersArray[i]
- // Remove the marker from clusters
- this._removeLayer(layer, true);
- this.fire('layerremove', {layer: layer});
+ // Group of layers, append children to layersArray and skip.
+ if (m instanceof L.LayerGroup) {
+ if (originalArray) {
+ layersArray = layersArray.slice()
+ originalArray = false
+ }
+ this._extractNonGroupLayers(m, layersArray)
+ l = layersArray.length
+ continue
+ }
- // Refresh bounds and weighted positions.
- this._topClusterLevel._recalculateBounds();
+ if (!m.__parent) {
+ npg.removeLayer(m)
+ this.fire('layerremove', { layer: m })
+ continue
+ }
- this._refreshClustersIcons();
+ this._removeLayer(m, true, true)
+ this.fire('layerremove', { layer: m })
- layer.off(this._childMarkerEventHandlers, this);
+ if (fg.hasLayer(m)) {
+ fg.removeLayer(m)
+ if (m.clusterShow) {
+ m.clusterShow()
+ }
+ }
+ }
- if (this._featureGroup.hasLayer(layer)) {
- this._featureGroup.removeLayer(layer);
- if (layer.clusterShow) {
- layer.clusterShow();
- }
- }
+ // Refresh bounds and weighted positions.
+ this._topClusterLevel._recalculateBounds()
- return this;
- },
+ this._refreshClustersIcons()
- // Takes an array of markers and adds them in bulk
- addLayers: function(layersArray, skipLayerAddEvent) {
- if (!L.Util.isArray(layersArray)) {
- return this.addLayer(layersArray);
- }
+ // Fix up the clusters and markers on the map
+ this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds)
- var fg = this._featureGroup,
- npg = this._nonPointGroup,
- chunked = this.options.chunkedLoading,
- chunkInterval = this.options.chunkInterval,
- chunkProgress = this.options.chunkProgress,
- l = layersArray.length,
- offset = 0,
- originalArray = true,
- m;
+ return this
+ },
- if (this._map) {
- var started = (new Date()).getTime();
- var process = L.bind(function() {
- var start = (new Date()).getTime();
- for (; offset < l; offset++) {
- if (chunked && offset % 200 === 0) {
- // every couple hundred markers, instrument the time elapsed since processing started:
- var elapsed = (new Date()).getTime() - start;
- if (elapsed > chunkInterval) {
- break; // been working too hard, time to take a break :-)
- }
- }
+ // Removes all layers from the MarkerClusterGroup
+ clearLayers: function () {
+ // Need our own special implementation as the LayerGroup one doesn't work for us
- m = layersArray[offset];
+ // If we aren't on the map (yet), blow away the markers we know of
+ if (!this._map) {
+ this._needsClustering = []
+ this._needsRemoving = []
+ delete this._gridClusters
+ delete this._gridUnclustered
+ }
- // Group of layers, append children to layersArray and skip.
- // Side effects:
- // - Total increases, so chunkProgress ratio jumps backward.
- // - Groups are not included in this group, only their non-group child layers (hasLayer).
- // Changing array length while looping does not affect performance in current browsers:
- // http:// jsperf.com/for-loop-changing-length/6
- if (m instanceof L.LayerGroup) {
- if (originalArray) {
- layersArray = layersArray.slice();
- originalArray = false;
- }
- this._extractNonGroupLayers(m, layersArray);
- l = layersArray.length;
- continue;
- }
+ if (this._noanimationUnspiderfy) {
+ this._noanimationUnspiderfy()
+ }
- // Not point data, can't be clustered
- if (!m.getLatLng) {
- npg.addLayer(m);
- if (!skipLayerAddEvent) {
- this.fire('layeradd', {layer: m});
- }
- continue;
- }
+ // Remove all the visible layers
+ this._featureGroup.clearLayers()
+ this._nonPointGroup.clearLayers()
- if (this.hasLayer(m)) {
- continue;
- }
+ this.eachLayer(function (marker) {
+ marker.off(this._childMarkerEventHandlers, this)
+ delete marker.__parent
+ }, this)
- this._addLayer(m, this._maxZoom);
- if (!skipLayerAddEvent) {
- this.fire('layeradd', {layer: m});
- }
+ if (this._map) {
+ // Reset _topClusterLevel and the DistanceGrids
+ this._generateInitialClusters()
+ }
- // If we just made a cluster of size 2 then we need to remove the other marker from the map (if it is) or we never will
- if (m.__parent) {
- if (m.__parent.getChildCount() === 2) {
- var markers = m.__parent.getAllChildMarkers(),
- otherMarker = markers[0] === m ? markers[1] : markers[0];
- fg.removeLayer(otherMarker);
- }
- }
- }
+ return this
+ },
- if (chunkProgress) {
- // report progress and time elapsed:
- chunkProgress(offset, l, (new Date()).getTime() - started);
- }
+ // Override FeatureGroup.getBounds as it doesn't work
+ getBounds: function () {
+ var bounds = new L.LatLngBounds()
- // Completed processing all markers.
- if (offset === l) {
+ if (this._topClusterLevel) {
+ bounds.extend(this._topClusterLevel._bounds)
+ }
- // Refresh bounds and weighted positions.
- this._topClusterLevel._recalculateBounds();
+ for (var i = this._needsClustering.length - 1; i >= 0; i--) {
+ bounds.extend(this._needsClustering[i].getLatLng())
+ }
- this._refreshClustersIcons();
+ bounds.extend(this._nonPointGroup.getBounds())
- this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds);
- } else {
- setTimeout(process, this.options.chunkDelay);
- }
- }, this);
+ return bounds
+ },
- process();
- } else {
- var needsClustering = this._needsClustering;
+ // Overrides LayerGroup.eachLayer
+ eachLayer: function (method, context) {
+ var markers = this._needsClustering.slice()
+ var needsRemoving = this._needsRemoving
+ var thisNeedsRemoving; var i; var j
- for (; offset < l; offset++) {
- m = layersArray[offset];
+ if (this._topClusterLevel) {
+ this._topClusterLevel.getAllChildMarkers(markers)
+ }
- // Group of layers, append children to layersArray and skip.
- if (m instanceof L.LayerGroup) {
- if (originalArray) {
- layersArray = layersArray.slice();
- originalArray = false;
- }
- this._extractNonGroupLayers(m, layersArray);
- l = layersArray.length;
- continue;
- }
+ for (i = markers.length - 1; i >= 0; i--) {
+ thisNeedsRemoving = true
- // Not point data, can't be clustered
- if (!m.getLatLng) {
- npg.addLayer(m);
- continue;
- }
+ for (j = needsRemoving.length - 1; j >= 0; j--) {
+ if (needsRemoving[j].layer === markers[i]) {
+ thisNeedsRemoving = false
+ break
+ }
+ }
- if (this.hasLayer(m)) {
- continue;
- }
+ if (thisNeedsRemoving) {
+ method.call(context, markers[i])
+ }
+ }
- needsClustering.push(m);
- }
- }
- return this;
- },
+ this._nonPointGroup.eachLayer(method, context)
+ },
- // Takes an array of markers and removes them in bulk
- removeLayers: function(layersArray) {
- var i, m,
- l = layersArray.length,
- fg = this._featureGroup,
- npg = this._nonPointGroup,
- originalArray = true;
+ // Overrides LayerGroup.getLayers
+ getLayers: function () {
+ var layers = []
+ this.eachLayer(function (l) {
+ layers.push(l)
+ })
+ return layers
+ },
- if (!this._map) {
- for (i = 0; i < l; i++) {
- m = layersArray[i];
+ // Overrides LayerGroup.getLayer, WARNING: Really bad performance
+ getLayer: function (id) {
+ var result = null
- // Group of layers, append children to layersArray and skip.
- if (m instanceof L.LayerGroup) {
- if (originalArray) {
- layersArray = layersArray.slice();
- originalArray = false;
- }
- this._extractNonGroupLayers(m, layersArray);
- l = layersArray.length;
- continue;
- }
+ id = parseInt(id, 10)
- this._arraySplice(this._needsClustering, m);
- npg.removeLayer(m);
- if (this.hasLayer(m)) {
- this._needsRemoving.push({layer: m, latlng: m._latlng});
- }
- this.fire('layerremove', {layer: m});
- }
- return this;
- }
+ this.eachLayer(function (l) {
+ if (L.stamp(l) === id) {
+ result = l
+ }
+ })
- if (this._unspiderfy) {
- this._unspiderfy();
+ return result
+ },
- // Work on a copy of the array, so that next loop is not affected.
- var layersArray2 = layersArray.slice(),
- l2 = l;
- for (i = 0; i < l2; i++) {
- m = layersArray2[i];
+ // Returns true if the given layer is in this MarkerClusterGroup
+ hasLayer: function (layer) {
+ if (!layer) {
+ return false
+ }
- // Group of layers, append children to layersArray and skip.
- if (m instanceof L.LayerGroup) {
- this._extractNonGroupLayers(m, layersArray2);
- l2 = layersArray2.length;
- continue;
- }
+ var i; var anArray = this._needsClustering
- this._unspiderfyLayer(m);
- }
- }
+ for (i = anArray.length - 1; i >= 0; i--) {
+ if (anArray[i] === layer) {
+ return true
+ }
+ }
- for (i = 0; i < l; i++) {
- m = layersArray[i];
+ anArray = this._needsRemoving
+ for (i = anArray.length - 1; i >= 0; i--) {
+ if (anArray[i].layer === layer) {
+ return false
+ }
+ }
- // Group of layers, append children to layersArray and skip.
- if (m instanceof L.LayerGroup) {
- if (originalArray) {
- layersArray = layersArray.slice();
- originalArray = false;
- }
- this._extractNonGroupLayers(m, layersArray);
- l = layersArray.length;
- continue;
- }
+ return !!(layer.__parent && layer.__parent._group === this) || this._nonPointGroup.hasLayer(layer)
+ },
- if (!m.__parent) {
- npg.removeLayer(m);
- this.fire('layerremove', {layer: m});
- continue;
- }
-
- this._removeLayer(m, true, true);
- this.fire('layerremove', {layer: m});
-
- if (fg.hasLayer(m)) {
- fg.removeLayer(m);
- if (m.clusterShow) {
- m.clusterShow();
- }
- }
- }
-
- // Refresh bounds and weighted positions.
- this._topClusterLevel._recalculateBounds();
-
- this._refreshClustersIcons();
-
- // Fix up the clusters and markers on the map
- this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds);
-
- return this;
- },
-
- // Removes all layers from the MarkerClusterGroup
- clearLayers: function() {
- // Need our own special implementation as the LayerGroup one doesn't work for us
-
- // If we aren't on the map (yet), blow away the markers we know of
- if (!this._map) {
- this._needsClustering = [];
- this._needsRemoving = [];
- delete this._gridClusters;
- delete this._gridUnclustered;
- }
-
- if (this._noanimationUnspiderfy) {
- this._noanimationUnspiderfy();
- }
-
- // Remove all the visible layers
- this._featureGroup.clearLayers();
- this._nonPointGroup.clearLayers();
-
- this.eachLayer(function(marker) {
- marker.off(this._childMarkerEventHandlers, this);
- delete marker.__parent;
- }, this);
-
- if (this._map) {
- // Reset _topClusterLevel and the DistanceGrids
- this._generateInitialClusters();
- }
-
- return this;
- },
-
- // Override FeatureGroup.getBounds as it doesn't work
- getBounds: function() {
- var bounds = new L.LatLngBounds();
-
- if (this._topClusterLevel) {
- bounds.extend(this._topClusterLevel._bounds);
- }
-
- for (var i = this._needsClustering.length - 1; i >= 0; i--) {
- bounds.extend(this._needsClustering[i].getLatLng());
- }
-
- bounds.extend(this._nonPointGroup.getBounds());
-
- return bounds;
- },
-
- // Overrides LayerGroup.eachLayer
- eachLayer: function(method, context) {
- var markers = this._needsClustering.slice(),
- needsRemoving = this._needsRemoving,
- thisNeedsRemoving, i, j;
-
- if (this._topClusterLevel) {
- this._topClusterLevel.getAllChildMarkers(markers);
- }
-
- for (i = markers.length - 1; i >= 0; i--) {
- thisNeedsRemoving = true;
-
- for (j = needsRemoving.length - 1; j >= 0; j--) {
- if (needsRemoving[j].layer === markers[i]) {
- thisNeedsRemoving = false;
- break;
- }
- }
-
- if (thisNeedsRemoving) {
- method.call(context, markers[i]);
- }
- }
-
- this._nonPointGroup.eachLayer(method, context);
- },
-
- // Overrides LayerGroup.getLayers
- getLayers: function() {
- var layers = [];
- this.eachLayer(function(l) {
- layers.push(l);
- });
- return layers;
- },
-
- // Overrides LayerGroup.getLayer, WARNING: Really bad performance
- getLayer: function(id) {
- var result = null;
-
- id = parseInt(id, 10);
-
- this.eachLayer(function(l) {
- if (L.stamp(l) === id) {
- result = l;
- }
- });
-
- return result;
- },
-
- // Returns true if the given layer is in this MarkerClusterGroup
- hasLayer: function(layer) {
- if (!layer) {
- return false;
- }
-
- var i, anArray = this._needsClustering;
-
- for (i = anArray.length - 1; i >= 0; i--) {
- if (anArray[i] === layer) {
- return true;
- }
- }
-
- anArray = this._needsRemoving;
- for (i = anArray.length - 1; i >= 0; i--) {
- if (anArray[i].layer === layer) {
- return false;
- }
- }
-
- return !!(layer.__parent && layer.__parent._group === this) || this._nonPointGroup.hasLayer(layer);
- },
-
- // Zoom down to show the given layer (spiderfying if necessary) then calls the callback
- zoomToShowLayer: function(layer, callback) {
-
- if (typeof callback !== 'function') {
- callback = function() {
- };
- }
+ // Zoom down to show the given layer (spiderfying if necessary) then calls the callback
+ zoomToShowLayer: function (layer, callback) {
+ if (typeof callback !== 'function') {
+ callback = function () {
+ }
+ }
- var showMarker = function() {
- if ((layer._icon || layer.__parent._icon) && !this._inZoomAnimation) {
- this._map.off('moveend', showMarker, this);
- this.off('animationend', showMarker, this);
+ var showMarker = function () {
+ if ((layer._icon || layer.__parent._icon) && !this._inZoomAnimation) {
+ this._map.off('moveend', showMarker, this)
+ this.off('animationend', showMarker, this)
- if (layer._icon) {
- callback();
- } else if (layer.__parent._icon) {
- this.once('spiderfied', callback, this);
- layer.__parent.spiderfy();
- }
- }
- };
+ if (layer._icon) {
+ callback()
+ } else if (layer.__parent._icon) {
+ this.once('spiderfied', callback, this)
+ layer.__parent.spiderfy()
+ }
+ }
+ }
- if (layer._icon && this._map.getBounds().contains(layer.getLatLng())) {
- // Layer is visible ond on screen, immediate return
- callback();
- } else if (layer.__parent._zoom < Math.round(this._map._zoom)) {
- // Layer should be visible at this zoom level. It must not be on screen so just pan over to it
- this._map.on('moveend', showMarker, this);
- this._map.panTo(layer.getLatLng());
- } else {
- this._map.on('moveend', showMarker, this);
- this.on('animationend', showMarker, this);
- layer.__parent.zoomToBounds();
- }
- },
+ if (layer._icon && this._map.getBounds().contains(layer.getLatLng())) {
+ // Layer is visible ond on screen, immediate return
+ callback()
+ } else if (layer.__parent._zoom < Math.round(this._map._zoom)) {
+ // Layer should be visible at this zoom level. It must not be on screen so just pan over to it
+ this._map.on('moveend', showMarker, this)
+ this._map.panTo(layer.getLatLng())
+ } else {
+ this._map.on('moveend', showMarker, this)
+ this.on('animationend', showMarker, this)
+ layer.__parent.zoomToBounds()
+ }
+ },
- // Overrides FeatureGroup.onAdd
- onAdd: function(map) {
- this._map = map;
- var i, l, layer;
+ // Overrides FeatureGroup.onAdd
+ onAdd: function (map) {
+ this._map = map
+ var i, l, layer
- if (!isFinite(this._map.getMaxZoom())) {
- throw "Map has no maxZoom specified";
- }
+ if (!isFinite(this._map.getMaxZoom())) {
+ // eslint-disable-next-line no-throw-literal
+ throw 'Map has no maxZoom specified'
+ }
- this._featureGroup.addTo(map);
- this._nonPointGroup.addTo(map);
+ this._featureGroup.addTo(map)
+ this._nonPointGroup.addTo(map)
- if (!this._gridClusters) {
- this._generateInitialClusters();
- }
+ if (!this._gridClusters) {
+ this._generateInitialClusters()
+ }
- this._maxLat = map.options.crs.projection.MAX_LATITUDE;
+ this._maxLat = map.options.crs.projection.MAX_LATITUDE
- // Restore all the positions as they are in the MCG before removing them
- for (i = 0, l = this._needsRemoving.length; i < l; i++) {
- layer = this._needsRemoving[i];
- layer.newlatlng = layer.layer._latlng;
- layer.layer._latlng = layer.latlng;
- }
- // Remove them, then restore their new positions
- for (i = 0, l = this._needsRemoving.length; i < l; i++) {
- layer = this._needsRemoving[i];
- this._removeLayer(layer.layer, true);
- layer.layer._latlng = layer.newlatlng;
- }
- this._needsRemoving = [];
+ // Restore all the positions as they are in the MCG before removing them
+ for (i = 0, l = this._needsRemoving.length; i < l; i++) {
+ layer = this._needsRemoving[i]
+ layer.newlatlng = layer.layer._latlng
+ layer.layer._latlng = layer.latlng
+ }
+ // Remove them, then restore their new positions
+ for (i = 0, l = this._needsRemoving.length; i < l; i++) {
+ layer = this._needsRemoving[i]
+ this._removeLayer(layer.layer, true)
+ layer.layer._latlng = layer.newlatlng
+ }
+ this._needsRemoving = []
- // Remember the current zoom level and bounds
- this._zoom = Math.round(this._map._zoom);
- this._currentShownBounds = this._getExpandedVisibleBounds();
+ // Remember the current zoom level and bounds
+ this._zoom = Math.round(this._map._zoom)
+ this._currentShownBounds = this._getExpandedVisibleBounds()
- this._map.on('zoomend', this._zoomEnd, this);
- this._map.on('moveend', this._moveEnd, this);
+ this._map.on('zoomend', this._zoomEnd, this)
+ this._map.on('moveend', this._moveEnd, this)
- if (this._spiderfierOnAdd) { // TODO FIXME: Not sure how to have spiderfier add something on here nicely
- this._spiderfierOnAdd();
- }
+ if (this._spiderfierOnAdd) { // TODO FIXME: Not sure how to have spiderfier add something on here nicely
+ this._spiderfierOnAdd()
+ }
- this._bindEvents();
+ this._bindEvents()
- // Actually add our markers to the map:
- l = this._needsClustering;
- this._needsClustering = [];
- this.addLayers(l, true);
- },
+ // Actually add our markers to the map:
+ l = this._needsClustering
+ this._needsClustering = []
+ this.addLayers(l, true)
+ },
- // Overrides FeatureGroup.onRemove
- onRemove: function(map) {
- map.off('zoomend', this._zoomEnd, this);
- map.off('moveend', this._moveEnd, this);
+ // Overrides FeatureGroup.onRemove
+ onRemove: function (map) {
+ map.off('zoomend', this._zoomEnd, this)
+ map.off('moveend', this._moveEnd, this)
- this._unbindEvents();
+ this._unbindEvents()
- // In case we are in a cluster animation
- this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-cluster-anim', '');
+ // In case we are in a cluster animation
+ this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-cluster-anim', '')
- if (this._spiderfierOnRemove) { // TODO FIXME: Not sure how to have spiderfier add something on here nicely
- this._spiderfierOnRemove();
- }
+ if (this._spiderfierOnRemove) { // TODO FIXME: Not sure how to have spiderfier add something on here nicely
+ this._spiderfierOnRemove()
+ }
- delete this._maxLat;
+ delete this._maxLat
- // Clean up all the layers we added to the map
- this._hideCoverage();
- this._featureGroup.remove();
- this._nonPointGroup.remove();
+ // Clean up all the layers we added to the map
+ this._hideCoverage()
+ this._featureGroup.remove()
+ this._nonPointGroup.remove()
- this._featureGroup.clearLayers();
+ this._featureGroup.clearLayers()
- this._map = null;
- },
+ this._map = null
+ },
- getVisibleParent: function(marker) {
- var vMarker = marker;
- while (vMarker && !vMarker._icon) {
- vMarker = vMarker.__parent;
- }
- return vMarker || null;
- },
+ getVisibleParent: function (marker) {
+ var vMarker = marker
+ while (vMarker && !vMarker._icon) {
+ vMarker = vMarker.__parent
+ }
+ return vMarker || null
+ },
- // Remove the given object from the given array
- _arraySplice: function(anArray, obj) {
- for (var i = anArray.length - 1; i >= 0; i--) {
- if (anArray[i] === obj) {
- anArray.splice(i, 1);
- return true;
- }
- }
- },
+ // Remove the given object from the given array
+ _arraySplice: function (anArray, obj) {
+ for (var i = anArray.length - 1; i >= 0; i--) {
+ if (anArray[i] === obj) {
+ anArray.splice(i, 1)
+ return true
+ }
+ }
+ },
- /**
+ /**
* Removes a marker from all _gridUnclustered zoom levels, starting at the supplied zoom.
* @param marker to be removed from _gridUnclustered.
* @param z integer bottom start zoom level (included)
* @private
*/
- _removeFromGridUnclustered: function(marker, z) {
- var map = this._map,
- gridUnclustered = this._gridUnclustered,
- minZoom = Math.floor(this._map.getMinZoom());
+ _removeFromGridUnclustered: function (marker, z) {
+ var map = this._map
+ var gridUnclustered = this._gridUnclustered
+ var minZoom = Math.floor(this._map.getMinZoom())
- for (; z >= minZoom; z--) {
- if (!gridUnclustered[z].removeObject(marker, map.project(marker.getLatLng(), z))) {
- break;
- }
+ for (; z >= minZoom; z--) {
+ if (!gridUnclustered[z].removeObject(marker, map.project(marker.getLatLng(), z))) {
+ break
+ }
+ }
+ },
+
+ _childMarkerDragStart: function (e) {
+ e.target.__dragStart = e.target._latlng
+ },
+
+ _childMarkerMoved: function (e) {
+ if (!this._ignoreMove && !e.target.__dragStart) {
+ var isPopupOpen = e.target._popup && e.target._popup.isOpen()
+
+ this._moveChild(e.target, e.oldLatLng, e.latlng)
+
+ if (isPopupOpen) {
+ e.target.openPopup()
+ }
+ }
+ },
+
+ _moveChild: function (layer, from, to) {
+ layer._latlng = from
+ this.removeLayer(layer)
+
+ layer._latlng = to
+ this.addLayer(layer)
+ },
+
+ _childMarkerDragEnd: function (e) {
+ var dragStart = e.target.__dragStart
+ delete e.target.__dragStart
+ if (dragStart) {
+ this._moveChild(e.target, dragStart, e.target._latlng)
+ }
+ },
+
+ // Internal function for removing a marker from everything.
+ // dontUpdateMap: set to true if you will handle updating the map manually (for bulk functions)
+ _removeLayer: function (marker, removeFromDistanceGrid, dontUpdateMap) {
+ var gridClusters = this._gridClusters
+ var gridUnclustered = this._gridUnclustered
+ var fg = this._featureGroup
+ var map = this._map
+ var minZoom = Math.floor(this._map.getMinZoom())
+
+ // Remove the marker from distance clusters it might be in
+ if (removeFromDistanceGrid) {
+ this._removeFromGridUnclustered(marker, this._maxZoom)
+ }
+
+ // Work our way up the clusters removing them as we go if required
+ var cluster = marker.__parent
+ var markers = cluster._markers
+ var otherMarker
+
+ // Remove the marker from the immediate parents marker list
+ this._arraySplice(markers, marker)
+
+ while (cluster) {
+ cluster._childCount--
+ cluster._boundsNeedUpdate = true
+
+ if (cluster._zoom < minZoom) {
+ // Top level, do nothing
+ break
+ } else if (removeFromDistanceGrid && cluster._childCount <= 1) { // Cluster no longer required
+ // We need to push the other marker up to the parent
+ otherMarker = cluster._markers[0] === marker ? cluster._markers[1] : cluster._markers[0]
+
+ // Update distance grid
+ gridClusters[cluster._zoom].removeObject(cluster, map.project(cluster._cLatLng, cluster._zoom))
+ gridUnclustered[cluster._zoom].addObject(otherMarker, map.project(otherMarker.getLatLng(), cluster._zoom))
+
+ // Move otherMarker up to parent
+ this._arraySplice(cluster.__parent._childClusters, cluster)
+ cluster.__parent._markers.push(otherMarker)
+ otherMarker.__parent = cluster.__parent
+
+ if (cluster._icon) {
+ // Cluster is currently on the map, need to put the marker on the map instead
+ fg.removeLayer(cluster)
+ if (!dontUpdateMap) {
+ fg.addLayer(otherMarker)
}
- },
+ }
+ } else {
+ cluster._iconNeedsUpdate = true
+ }
- _childMarkerDragStart: function(e) {
- e.target.__dragStart = e.target._latlng;
- },
+ cluster = cluster.__parent
+ }
- _childMarkerMoved: function(e) {
- if (!this._ignoreMove && !e.target.__dragStart) {
- var isPopupOpen = e.target._popup && e.target._popup.isOpen();
+ delete marker.__parent
+ },
- this._moveChild(e.target, e.oldLatLng, e.latlng);
+ _isOrIsParent: function (el, oel) {
+ while (oel) {
+ if (el === oel) {
+ return true
+ }
+ oel = oel.parentNode
+ }
+ return false
+ },
- if (isPopupOpen) {
- e.target.openPopup();
- }
- }
- },
+ // Override L.Evented.fire
+ fire: function (type, data, propagate) {
+ if (data && data.layer instanceof L.MarkerCluster) {
+ // Prevent multiple clustermouseover/off events if the icon is made up of stacked divs (Doesn't work in ie <= 8, no relatedTarget)
+ if (data.originalEvent && this._isOrIsParent(data.layer._icon, data.originalEvent.relatedTarget)) {
+ return
+ }
+ type = 'cluster' + type
+ }
- _moveChild: function(layer, from, to) {
- layer._latlng = from;
- this.removeLayer(layer);
+ L.FeatureGroup.prototype.fire.call(this, type, data, propagate)
+ },
- layer._latlng = to;
- this.addLayer(layer);
- },
+ // Override L.Evented.listens
+ listens: function (type, propagate) {
+ return L.FeatureGroup.prototype.listens.call(this, type, propagate) || L.FeatureGroup.prototype.listens.call(this, 'cluster' + type, propagate)
+ },
- _childMarkerDragEnd: function(e) {
- var dragStart = e.target.__dragStart;
- delete e.target.__dragStart;
- if (dragStart) {
- this._moveChild(e.target, dragStart, e.target._latlng);
- }
- },
+ // Default functionality
+ _defaultIconCreateFunction: function (cluster) {
+ var childCount = cluster.getChildCount()
+ var c = ' marker-cluster-'
+ if (childCount < 10) {
+ c += 'small'
+ } else if (childCount < 100) {
+ c += 'medium'
+ } else {
+ c += 'large'
+ }
- // Internal function for removing a marker from everything.
- // dontUpdateMap: set to true if you will handle updating the map manually (for bulk functions)
- _removeLayer: function(marker, removeFromDistanceGrid, dontUpdateMap) {
- var gridClusters = this._gridClusters,
- gridUnclustered = this._gridUnclustered,
- fg = this._featureGroup,
- map = this._map,
- minZoom = Math.floor(this._map.getMinZoom());
+ return new L.DivIcon({
+ html: '<div><span>' + childCount + '</span></div>',
+ className: 'marker-cluster' + c,
+ iconSize: new L.Point(40, 40)
+ })
+ },
- // Remove the marker from distance clusters it might be in
- if (removeFromDistanceGrid) {
- this._removeFromGridUnclustered(marker, this._maxZoom);
- }
+ _bindEvents: function () {
+ var map = this._map
+ var spiderfyOnMaxZoom = this.options.spiderfyOnMaxZoom
+ var showCoverageOnHover = this.options.showCoverageOnHover
+ var zoomToBoundsOnClick = this.options.zoomToBoundsOnClick
- // Work our way up the clusters removing them as we go if required
- var cluster = marker.__parent,
- markers = cluster._markers,
- otherMarker;
+ // Zoom on cluster click or spiderfy if we are at the lowest level
+ if (spiderfyOnMaxZoom || zoomToBoundsOnClick) {
+ this.on('clusterclick', this._zoomOrSpiderfy, this)
+ }
- // Remove the marker from the immediate parents marker list
- this._arraySplice(markers, marker);
+ // Show convex hull (boundary) polygon on mouse over
+ if (showCoverageOnHover) {
+ this.on('clustermouseover', this._showCoverage, this)
+ this.on('clustermouseout', this._hideCoverage, this)
+ map.on('zoomend', this._hideCoverage, this)
+ }
+ },
- while (cluster) {
- cluster._childCount--;
- cluster._boundsNeedUpdate = true;
+ _zoomOrSpiderfy: function (e) {
+ var cluster = e.layer
+ var bottomCluster = cluster
- if (cluster._zoom < minZoom) {
- // Top level, do nothing
- break;
- } else if (removeFromDistanceGrid && cluster._childCount <= 1) { // Cluster no longer required
- // We need to push the other marker up to the parent
- otherMarker = cluster._markers[0] === marker ? cluster._markers[1] : cluster._markers[0];
+ while (bottomCluster._childClusters.length === 1) {
+ bottomCluster = bottomCluster._childClusters[0]
+ }
- // Update distance grid
- gridClusters[cluster._zoom].removeObject(cluster, map.project(cluster._cLatLng, cluster._zoom));
- gridUnclustered[cluster._zoom].addObject(otherMarker, map.project(otherMarker.getLatLng(), cluster._zoom));
-
- // Move otherMarker up to parent
- this._arraySplice(cluster.__parent._childClusters, cluster);
- cluster.__parent._markers.push(otherMarker);
- otherMarker.__parent = cluster.__parent;
-
- if (cluster._icon) {
- // Cluster is currently on the map, need to put the marker on the map instead
- fg.removeLayer(cluster);
- if (!dontUpdateMap) {
- fg.addLayer(otherMarker);
- }
- }
- } else {
- cluster._iconNeedsUpdate = true;
- }
-
- cluster = cluster.__parent;
- }
-
- delete marker.__parent;
- },
-
- _isOrIsParent: function(el, oel) {
- while (oel) {
- if (el === oel) {
- return true;
- }
- oel = oel.parentNode;
- }
- return false;
- },
-
- // Override L.Evented.fire
- fire: function(type, data, propagate) {
- if (data && data.layer instanceof L.MarkerCluster) {
- // Prevent multiple clustermouseover/off events if the icon is made up of stacked divs (Doesn't work in ie <= 8, no relatedTarget)
- if (data.originalEvent && this._isOrIsParent(data.layer._icon, data.originalEvent.relatedTarget)) {
- return;
- }
- type = 'cluster' + type;
- }
-
- L.FeatureGroup.prototype.fire.call(this, type, data, propagate);
- },
-
- // Override L.Evented.listens
- listens: function(type, propagate) {
- return L.FeatureGroup.prototype.listens.call(this, type, propagate) || L.FeatureGroup.prototype.listens.call(this, 'cluster' + type, propagate);
- },
-
- // Default functionality
- _defaultIconCreateFunction: function(cluster) {
- var childCount = cluster.getChildCount();
-
- var c = ' marker-cluster-';
- if (childCount < 10) {
- c += 'small';
- } else if (childCount < 100) {
- c += 'medium';
- } else {
- c += 'large';
- }
-
- return new L.DivIcon({
- html: '<div><span>' + childCount + '</span></div>',
- className: 'marker-cluster' + c,
- iconSize: new L.Point(40, 40)
- });
- },
-
- _bindEvents: function() {
- var map = this._map,
- spiderfyOnMaxZoom = this.options.spiderfyOnMaxZoom,
- showCoverageOnHover = this.options.showCoverageOnHover,
- zoomToBoundsOnClick = this.options.zoomToBoundsOnClick;
-
- // Zoom on cluster click or spiderfy if we are at the lowest level
- if (spiderfyOnMaxZoom || zoomToBoundsOnClick) {
- this.on('clusterclick', this._zoomOrSpiderfy, this);
- }
-
- // Show convex hull (boundary) polygon on mouse over
- if (showCoverageOnHover) {
- this.on('clustermouseover', this._showCoverage, this);
- this.on('clustermouseout', this._hideCoverage, this);
- map.on('zoomend', this._hideCoverage, this);
- }
- },
-
- _zoomOrSpiderfy: function(e) {
- var cluster = e.layer,
- bottomCluster = cluster;
-
- while (bottomCluster._childClusters.length === 1) {
- bottomCluster = bottomCluster._childClusters[0];
- }
-
- if (bottomCluster._zoom === this._maxZoom &&
+ if (bottomCluster._zoom === this._maxZoom &&
bottomCluster._childCount === cluster._childCount &&
this.options.spiderfyOnMaxZoom) {
+ // All child markers are contained in a single cluster from this._maxZoom to this cluster.
+ cluster.spiderfy()
+ } else if (this.options.zoomToBoundsOnClick) {
+ cluster.zoomToBounds()
+ }
- // All child markers are contained in a single cluster from this._maxZoom to this cluster.
- cluster.spiderfy();
- } else if (this.options.zoomToBoundsOnClick) {
- cluster.zoomToBounds();
- }
+ // Focus the map again for keyboard users.
+ if (e.originalEvent && e.originalEvent.keyCode === 13) {
+ this._map._container.focus()
+ }
+ },
- // Focus the map again for keyboard users.
- if (e.originalEvent && e.originalEvent.keyCode === 13) {
- this._map._container.focus();
- }
- },
+ _showCoverage: function (e) {
+ var map = this._map
+ if (this._inZoomAnimation) {
+ return
+ }
+ if (this._shownPolygon) {
+ map.removeLayer(this._shownPolygon)
+ }
+ if (e.layer.getChildCount() > 2 && e.layer !== this._spiderfied) {
+ this._shownPolygon = new L.Polygon(e.layer.getConvexHull(), this.options.polygonOptions)
+ map.addLayer(this._shownPolygon)
+ }
+ },
- _showCoverage: function(e) {
- var map = this._map;
- if (this._inZoomAnimation) {
- return;
- }
- if (this._shownPolygon) {
- map.removeLayer(this._shownPolygon);
- }
- if (e.layer.getChildCount() > 2 && e.layer !== this._spiderfied) {
- this._shownPolygon = new L.Polygon(e.layer.getConvexHull(), this.options.polygonOptions);
- map.addLayer(this._shownPolygon);
- }
- },
+ _hideCoverage: function () {
+ if (this._shownPolygon) {
+ this._map.removeLayer(this._shownPolygon)
+ this._shownPolygon = null
+ }
+ },
- _hideCoverage: function() {
- if (this._shownPolygon) {
- this._map.removeLayer(this._shownPolygon);
- this._shownPolygon = null;
- }
- },
+ _unbindEvents: function () {
+ var spiderfyOnMaxZoom = this.options.spiderfyOnMaxZoom
+ var showCoverageOnHover = this.options.showCoverageOnHover
+ var zoomToBoundsOnClick = this.options.zoomToBoundsOnClick
+ var map = this._map
- _unbindEvents: function() {
- var spiderfyOnMaxZoom = this.options.spiderfyOnMaxZoom,
- showCoverageOnHover = this.options.showCoverageOnHover,
- zoomToBoundsOnClick = this.options.zoomToBoundsOnClick,
- map = this._map;
+ if (spiderfyOnMaxZoom || zoomToBoundsOnClick) {
+ this.off('clusterclick', this._zoomOrSpiderfy, this)
+ }
+ if (showCoverageOnHover) {
+ this.off('clustermouseover', this._showCoverage, this)
+ this.off('clustermouseout', this._hideCoverage, this)
+ map.off('zoomend', this._hideCoverage, this)
+ }
+ },
- if (spiderfyOnMaxZoom || zoomToBoundsOnClick) {
- this.off('clusterclick', this._zoomOrSpiderfy, this);
- }
- if (showCoverageOnHover) {
- this.off('clustermouseover', this._showCoverage, this);
- this.off('clustermouseout', this._hideCoverage, this);
- map.off('zoomend', this._hideCoverage, this);
- }
- },
+ _zoomEnd: function () {
+ if (!this._map) { // May have been removed from the map by a zoomEnd handler
+ return
+ }
+ this._mergeSplitClusters()
- _zoomEnd: function() {
- if (!this._map) { // May have been removed from the map by a zoomEnd handler
- return;
- }
- this._mergeSplitClusters();
+ this._zoom = Math.round(this._map._zoom)
+ this._currentShownBounds = this._getExpandedVisibleBounds()
+ },
- this._zoom = Math.round(this._map._zoom);
- this._currentShownBounds = this._getExpandedVisibleBounds();
- },
+ _moveEnd: function () {
+ if (this._inZoomAnimation) {
+ return
+ }
- _moveEnd: function() {
- if (this._inZoomAnimation) {
- return;
- }
+ var newBounds = this._getExpandedVisibleBounds()
- var newBounds = this._getExpandedVisibleBounds();
+ this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), this._zoom, newBounds)
+ this._topClusterLevel._recursivelyAddChildrenToMap(null, Math.round(this._map._zoom), newBounds)
- this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), this._zoom, newBounds);
- this._topClusterLevel._recursivelyAddChildrenToMap(null, Math.round(this._map._zoom), newBounds);
+ this._currentShownBounds = newBounds
+ },
- this._currentShownBounds = newBounds;
- return;
- },
+ _generateInitialClusters: function () {
+ var maxZoom = Math.ceil(this._map.getMaxZoom())
+ var minZoom = Math.floor(this._map.getMinZoom())
+ var radius = this.options.maxClusterRadius
+ var radiusFn = radius
- _generateInitialClusters: function() {
- var maxZoom = Math.ceil(this._map.getMaxZoom()),
- minZoom = Math.floor(this._map.getMinZoom()),
- radius = this.options.maxClusterRadius,
- radiusFn = radius;
+ // If we just set maxClusterRadius to a single number, we need to create
+ // a simple function to return that number. Otherwise, we just have to
+ // use the function we've passed in.
+ if (typeof radius !== 'function') {
+ radiusFn = function () {
+ return radius
+ }
+ }
- // If we just set maxClusterRadius to a single number, we need to create
- // a simple function to return that number. Otherwise, we just have to
- // use the function we've passed in.
- if (typeof radius !== "function") {
- radiusFn = function() {
- return radius;
- };
- }
+ if (this.options.disableClusteringAtZoom !== null) {
+ maxZoom = this.options.disableClusteringAtZoom - 1
+ }
+ this._maxZoom = maxZoom
+ this._gridClusters = {}
+ this._gridUnclustered = {}
- if (this.options.disableClusteringAtZoom !== null) {
- maxZoom = this.options.disableClusteringAtZoom - 1;
- }
- this._maxZoom = maxZoom;
- this._gridClusters = {};
- this._gridUnclustered = {};
+ // Set up DistanceGrids for each zoom
+ for (var zoom = maxZoom; zoom >= minZoom; zoom--) {
+ this._gridClusters[zoom] = new L.DistanceGrid(radiusFn(zoom))
+ this._gridUnclustered[zoom] = new L.DistanceGrid(radiusFn(zoom))
+ }
- // Set up DistanceGrids for each zoom
- for (var zoom = maxZoom; zoom >= minZoom; zoom--) {
- this._gridClusters[zoom] = new L.DistanceGrid(radiusFn(zoom));
- this._gridUnclustered[zoom] = new L.DistanceGrid(radiusFn(zoom));
- }
+ // Instantiate the appropriate L.MarkerCluster class (animated or not).
+ this._topClusterLevel = new this._markerCluster(this, minZoom - 1)
+ },
- // Instantiate the appropriate L.MarkerCluster class (animated or not).
- this._topClusterLevel = new this._markerCluster(this, minZoom - 1);
- },
+ // Zoom: Zoom to start adding at (Pass this._maxZoom to start at the bottom)
+ _addLayer: function (layer, zoom) {
+ var gridClusters = this._gridClusters
+ var gridUnclustered = this._gridUnclustered
+ var minZoom = Math.floor(this._map.getMinZoom())
+ var markerPoint; var z
- // Zoom: Zoom to start adding at (Pass this._maxZoom to start at the bottom)
- _addLayer: function(layer, zoom) {
- var gridClusters = this._gridClusters,
- gridUnclustered = this._gridUnclustered,
- minZoom = Math.floor(this._map.getMinZoom()),
- markerPoint, z;
+ if (this.options.singleMarkerMode) {
+ this._overrideMarkerIcon(layer)
+ }
- if (this.options.singleMarkerMode) {
- this._overrideMarkerIcon(layer);
- }
+ layer.on(this._childMarkerEventHandlers, this)
- layer.on(this._childMarkerEventHandlers, this);
+ // Find the lowest zoom level to slot this one in
+ for (; zoom >= minZoom; zoom--) {
+ markerPoint = this._map.project(layer.getLatLng(), zoom) // calculate pixel position
- // Find the lowest zoom level to slot this one in
- for (; zoom >= minZoom; zoom--) {
- markerPoint = this._map.project(layer.getLatLng(), zoom); // calculate pixel position
+ // Try find a cluster close by
+ var closest = gridClusters[zoom].getNearObject(markerPoint)
+ if (closest) {
+ closest._addChild(layer)
+ layer.__parent = closest
+ return
+ }
- // Try find a cluster close by
- var closest = gridClusters[zoom].getNearObject(markerPoint);
- if (closest) {
- closest._addChild(layer);
- layer.__parent = closest;
- return;
- }
+ // Try find a marker close by to form a new cluster with
+ closest = gridUnclustered[zoom].getNearObject(markerPoint)
+ if (closest) {
+ var parent = closest.__parent
+ if (parent) {
+ this._removeLayer(closest, false)
+ }
- // Try find a marker close by to form a new cluster with
- closest = gridUnclustered[zoom].getNearObject(markerPoint);
- if (closest) {
- var parent = closest.__parent;
- if (parent) {
- this._removeLayer(closest, false);
- }
+ // Create new cluster with these 2 in it
- // Create new cluster with these 2 in it
+ var newCluster = new this._markerCluster(this, zoom, closest, layer)
+ gridClusters[zoom].addObject(newCluster, this._map.project(newCluster._cLatLng, zoom))
+ closest.__parent = newCluster
+ layer.__parent = newCluster
- var newCluster = new this._markerCluster(this, zoom, closest, layer);
- gridClusters[zoom].addObject(newCluster, this._map.project(newCluster._cLatLng, zoom));
- closest.__parent = newCluster;
- layer.__parent = newCluster;
+ // First create any new intermediate parent clusters that don't exist
+ var lastParent = newCluster
+ for (z = zoom - 1; z > parent._zoom; z--) {
+ lastParent = new this._markerCluster(this, z, lastParent)
+ gridClusters[z].addObject(lastParent, this._map.project(closest.getLatLng(), z))
+ }
+ parent._addChild(lastParent)
- // First create any new intermediate parent clusters that don't exist
- var lastParent = newCluster;
- for (z = zoom - 1; z > parent._zoom; z--) {
- lastParent = new this._markerCluster(this, z, lastParent);
- gridClusters[z].addObject(lastParent, this._map.project(closest.getLatLng(), z));
- }
- parent._addChild(lastParent);
+ // Remove closest from this zoom level and any above that it is in, replace with newCluster
+ this._removeFromGridUnclustered(closest, zoom)
- // Remove closest from this zoom level and any above that it is in, replace with newCluster
- this._removeFromGridUnclustered(closest, zoom);
+ return
+ }
- return;
- }
+ // Didn't manage to cluster in at this zoom, record us as a marker here and continue upwards
+ gridUnclustered[zoom].addObject(layer, markerPoint)
+ }
- // Didn't manage to cluster in at this zoom, record us as a marker here and continue upwards
- gridUnclustered[zoom].addObject(layer, markerPoint);
- }
+ // Didn't get in anything, add us to the top
+ this._topClusterLevel._addChild(layer)
+ layer.__parent = this._topClusterLevel
+ },
- // Didn't get in anything, add us to the top
- this._topClusterLevel._addChild(layer);
- layer.__parent = this._topClusterLevel;
- return;
- },
-
- /**
+ /**
* Refreshes the icon of all "dirty" visible clusters.
* Non-visible "dirty" clusters will be updated when they are added to the map.
* @private
*/
- _refreshClustersIcons: function() {
- this._featureGroup.eachLayer(function(c) {
- if (c instanceof L.MarkerCluster && c._iconNeedsUpdate) {
- c._updateIcon();
- }
- });
- },
+ _refreshClustersIcons: function () {
+ this._featureGroup.eachLayer(function (c) {
+ if (c instanceof L.MarkerCluster && c._iconNeedsUpdate) {
+ c._updateIcon()
+ }
+ })
+ },
- // Enqueue code to fire after the marker expand/contract has happened
- _enqueue: function(fn) {
- this._queue.push(fn);
- if (!this._queueTimeout) {
- this._queueTimeout = setTimeout(L.bind(this._processQueue, this), 300);
- }
- },
- _processQueue: function() {
- for (var i = 0; i < this._queue.length; i++) {
- this._queue[i].call(this);
- }
- this._queue.length = 0;
- clearTimeout(this._queueTimeout);
- this._queueTimeout = null;
- },
+ // Enqueue code to fire after the marker expand/contract has happened
+ _enqueue: function (fn) {
+ this._queue.push(fn)
+ if (!this._queueTimeout) {
+ this._queueTimeout = setTimeout(L.bind(this._processQueue, this), 300)
+ }
+ },
+ _processQueue: function () {
+ for (var i = 0; i < this._queue.length; i++) {
+ this._queue[i].call(this)
+ }
+ this._queue.length = 0
+ clearTimeout(this._queueTimeout)
+ this._queueTimeout = null
+ },
- // Merge and split any existing clusters that are too big or small
- _mergeSplitClusters: function() {
- var mapZoom = Math.round(this._map._zoom);
+ // Merge and split any existing clusters that are too big or small
+ _mergeSplitClusters: function () {
+ var mapZoom = Math.round(this._map._zoom)
- // In case we are starting to split before the animation finished
- this._processQueue();
+ // In case we are starting to split before the animation finished
+ this._processQueue()
- if (this._zoom < mapZoom && this._currentShownBounds.intersects(this._getExpandedVisibleBounds())) { // Zoom in, split
- this._animationStart();
- // Remove clusters now off screen
- this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), this._zoom, this._getExpandedVisibleBounds());
+ if (this._zoom < mapZoom && this._currentShownBounds.intersects(this._getExpandedVisibleBounds())) { // Zoom in, split
+ this._animationStart()
+ // Remove clusters now off screen
+ this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), this._zoom, this._getExpandedVisibleBounds())
- this._animationZoomIn(this._zoom, mapZoom);
+ this._animationZoomIn(this._zoom, mapZoom)
+ } else if (this._zoom > mapZoom) { // Zoom out, merge
+ this._animationStart()
- } else if (this._zoom > mapZoom) { // Zoom out, merge
- this._animationStart();
+ this._animationZoomOut(this._zoom, mapZoom)
+ } else {
+ this._moveEnd()
+ }
+ },
- this._animationZoomOut(this._zoom, mapZoom);
- } else {
- this._moveEnd();
- }
- },
+ // Gets the maps visible bounds expanded in each direction by the size of the screen (so the user cannot see an area we do not cover in one pan)
+ _getExpandedVisibleBounds: function () {
+ if (!this.options.removeOutsideVisibleBounds) {
+ return this._mapBoundsInfinite
+ } else if (L.Browser.mobile) {
+ return this._checkBoundsMaxLat(this._map.getBounds())
+ }
- // Gets the maps visible bounds expanded in each direction by the size of the screen (so the user cannot see an area we do not cover in one pan)
- _getExpandedVisibleBounds: function() {
- if (!this.options.removeOutsideVisibleBounds) {
- return this._mapBoundsInfinite
- } else if (L.Browser.mobile) {
- return this._checkBoundsMaxLat(this._map.getBounds())
- }
+ return this._checkBoundsMaxLat(this._map.getBounds().pad(1)) // Padding expands the bounds by its own dimensions but scaled with the given factor.
+ },
- return this._checkBoundsMaxLat(this._map.getBounds().pad(1)) // Padding expands the bounds by its own dimensions but scaled with the given factor.
- },
-
- /**
+ /**
* Expands the latitude to Infinity (or -Infinity) if the input bounds reach the map projection maximum defined latitude
* (in the case of Web/Spherical Mercator, it is 85.0511287798 / see https:// en.wikipedia.org/wiki/Web_Mercator#Formulas).
* Otherwise, the removeOutsideVisibleBounds option will remove markers beyond that limit, whereas the same markers without
@@ -1099,691 +1090,689 @@
* @returns {L.LatLngBounds}
* @private
*/
- _checkBoundsMaxLat: function(bounds) {
- var maxLat = this._maxLat
+ _checkBoundsMaxLat: function (bounds) {
+ var maxLat = this._maxLat
- if (maxLat !== undefined) {
- if (bounds.getNorth() >= maxLat) {
- bounds._northEast.lat = Infinity
- }
- if (bounds.getSouth() <= -maxLat) {
- bounds._southWest.lat = -Infinity
- }
- }
+ if (maxLat !== undefined) {
+ if (bounds.getNorth() >= maxLat) {
+ bounds._northEast.lat = Infinity
+ }
+ if (bounds.getSouth() <= -maxLat) {
+ bounds._southWest.lat = -Infinity
+ }
+ }
- return bounds
- },
+ return bounds
+ },
- // Shared animation code
- _animationAddLayerNonAnimated: function(layer, newCluster) {
- if (newCluster === layer) {
- this._featureGroup.addLayer(layer)
- } else if (newCluster._childCount === 2) {
- newCluster._addToMap()
+ // Shared animation code
+ _animationAddLayerNonAnimated: function (layer, newCluster) {
+ if (newCluster === layer) {
+ this._featureGroup.addLayer(layer)
+ } else if (newCluster._childCount === 2) {
+ newCluster._addToMap()
- var markers = newCluster.getAllChildMarkers()
- this._featureGroup.removeLayer(markers[0])
- this._featureGroup.removeLayer(markers[1])
- } else {
- newCluster._updateIcon()
- }
- },
+ var markers = newCluster.getAllChildMarkers()
+ this._featureGroup.removeLayer(markers[0])
+ this._featureGroup.removeLayer(markers[1])
+ } else {
+ newCluster._updateIcon()
+ }
+ },
- /**
+ /**
* Extracts individual (i.e. non-group) layers from a Layer Group.
* @param group to extract layers from.
* @param output {Array} in which to store the extracted layers.
* @returns {*|Array}
* @private
*/
- _extractNonGroupLayers: function(group, output) {
- var layers = group.getLayers(),
- i = 0,
- layer
+ _extractNonGroupLayers: function (group, output) {
+ var layers = group.getLayers()
+ var i = 0
+ var layer
- output = output || []
+ output = output || []
- for (; i < layers.length; i++) {
- layer = layers[i]
+ for (; i < layers.length; i++) {
+ layer = layers[i]
- if (layer instanceof L.LayerGroup) {
- this._extractNonGroupLayers(layer, output)
- continue
- }
+ if (layer instanceof L.LayerGroup) {
+ this._extractNonGroupLayers(layer, output)
+ continue
+ }
- output.push(layer)
- }
+ output.push(layer)
+ }
- return output
- },
+ return output
+ },
- /**
+ /**
* Implements the singleMarkerMode option.
* @param layer Marker to re-style using the Clusters iconCreateFunction.
* @returns {L.Icon} The newly created icon.
* @private
*/
- _overrideMarkerIcon: function(layer) {
- var icon = layer.options.icon = this.options.iconCreateFunction({
- getChildCount: function() {
- return 1
- },
- getAllChildMarkers: function() {
- return [layer]
- }
+ _overrideMarkerIcon: function (layer) {
+ var icon = layer.options.icon = this.options.iconCreateFunction({
+ getChildCount: function () {
+ return 1
+ },
+ getAllChildMarkers: function () {
+ return [layer]
+ }
+ })
+
+ return icon
+ }
+ })
+
+ // Constant bounds used in case option "removeOutsideVisibleBounds" is set to false.
+ L.MarkerClusterGroup.include({
+ _mapBoundsInfinite: new L.LatLngBounds(new L.LatLng(-Infinity, -Infinity), new L.LatLng(Infinity, Infinity))
+ })
+
+ L.MarkerClusterGroup.include({
+ _noAnimation: {
+ // Non Animated versions of everything
+ _animationStart: function () {
+ // Do nothing...
+ },
+ _animationZoomIn: function (previousZoomLevel, newZoomLevel) {
+ this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), previousZoomLevel)
+ this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds())
+
+ // We didn't actually animate, but we use this event to mean "clustering animations have finished"
+ this.fire('animationend')
+ },
+ _animationZoomOut: function (previousZoomLevel, newZoomLevel) {
+ this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), previousZoomLevel)
+ this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds())
+
+ // We didn't actually animate, but we use this event to mean "clustering animations have finished"
+ this.fire('animationend')
+ },
+ _animationAddLayer: function (layer, newCluster) {
+ this._animationAddLayerNonAnimated(layer, newCluster)
+ }
+ },
+
+ _withAnimation: {
+ // Animated versions here
+ _animationStart: function () {
+ this._map._mapPane.className += ' leaflet-cluster-anim'
+ this._inZoomAnimation++
+ },
+
+ _animationZoomIn: function (previousZoomLevel, newZoomLevel) {
+ var bounds = this._getExpandedVisibleBounds()
+ var fg = this._featureGroup
+ var minZoom = Math.floor(this._map.getMinZoom())
+ var i
+
+ this._ignoreMove = true
+
+ // Add all children of current clusters to map and remove those clusters from map
+ this._topClusterLevel._recursively(bounds, previousZoomLevel, minZoom, function (c) {
+ var startPos = c._latlng
+ var markers = c._markers
+ var m
+
+ if (!bounds.contains(startPos)) {
+ startPos = null
+ }
+
+ if (c._isSingleParent() && previousZoomLevel + 1 === newZoomLevel) { // Immediately add the new child and remove us
+ fg.removeLayer(c)
+ c._recursivelyAddChildrenToMap(null, newZoomLevel, bounds)
+ } else {
+ // Fade out old cluster
+ c.clusterHide()
+ c._recursivelyAddChildrenToMap(startPos, newZoomLevel, bounds)
+ }
+
+ // Remove all markers that aren't visible any more
+ // TODO: Do we actually need to do this on the higher levels too?
+ for (i = markers.length - 1; i >= 0; i--) {
+ m = markers[i]
+ if (!bounds.contains(m._latlng)) {
+ fg.removeLayer(m)
+ }
+ }
+ })
+
+ this._forceLayout()
+
+ // Update opacities
+ this._topClusterLevel._recursivelyBecomeVisible(bounds, newZoomLevel)
+ // TODO Maybe? Update markers in _recursivelyBecomeVisible
+ fg.eachLayer(function (n) {
+ if (!(n instanceof L.MarkerCluster) && n._icon) {
+ n.clusterShow()
+ }
+ })
+
+ // update the positions of the just added clusters/markers
+ this._topClusterLevel._recursively(bounds, previousZoomLevel, newZoomLevel, function (c) {
+ c._recursivelyRestoreChildPositions(newZoomLevel)
+ })
+
+ this._ignoreMove = false
+
+ // Remove the old clusters and close the zoom animation
+ this._enqueue(function () {
+ // update the positions of the just added clusters/markers
+ this._topClusterLevel._recursively(bounds, previousZoomLevel, minZoom, function (c) {
+ fg.removeLayer(c)
+ c.clusterShow()
+ })
+
+ this._animationEnd()
+ })
+ },
+
+ _animationZoomOut: function (previousZoomLevel, newZoomLevel) {
+ this._animationZoomOutSingle(this._topClusterLevel, previousZoomLevel - 1, newZoomLevel)
+
+ // Need to add markers for those that weren't on the map before but are now
+ this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds())
+ // Remove markers that were on the map before but won't be now
+ this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), previousZoomLevel, this._getExpandedVisibleBounds())
+ },
+
+ _animationAddLayer: function (layer, newCluster) {
+ var me = this
+ var fg = this._featureGroup
+
+ fg.addLayer(layer)
+ if (newCluster !== layer) {
+ if (newCluster._childCount > 2) { // Was already a cluster
+ newCluster._updateIcon()
+ this._forceLayout()
+ this._animationStart()
+
+ layer._setPos(this._map.latLngToLayerPoint(newCluster.getLatLng()))
+ layer.clusterHide()
+
+ this._enqueue(function () {
+ fg.removeLayer(layer)
+ layer.clusterShow()
+
+ me._animationEnd()
})
+ } else { // Just became a cluster
+ this._forceLayout()
- return icon
+ me._animationStart()
+ me._animationZoomOutSingle(newCluster, this._map.getMaxZoom(), this._zoom)
+ }
}
- })
+ }
+ },
-// Constant bounds used in case option "removeOutsideVisibleBounds" is set to false.
- L.MarkerClusterGroup.include({
- _mapBoundsInfinite: new L.LatLngBounds(new L.LatLng(-Infinity, -Infinity), new L.LatLng(Infinity, Infinity))
- })
+ // Private methods for animated versions.
+ _animationZoomOutSingle: function (cluster, previousZoomLevel, newZoomLevel) {
+ var bounds = this._getExpandedVisibleBounds()
+ var minZoom = Math.floor(this._map.getMinZoom())
- L.MarkerClusterGroup.include({
- _noAnimation: {
- // Non Animated versions of everything
- _animationStart: function() {
- // Do nothing...
- },
- _animationZoomIn: function(previousZoomLevel, newZoomLevel) {
- this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), previousZoomLevel);
- this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds())
+ // Animate all of the markers in the clusters to move to their cluster center point
+ cluster._recursivelyAnimateChildrenInAndAddSelfToMap(bounds, minZoom, previousZoomLevel + 1, newZoomLevel)
- // We didn't actually animate, but we use this event to mean "clustering animations have finished"
- this.fire('animationend')
- },
- _animationZoomOut: function(previousZoomLevel, newZoomLevel) {
- this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), previousZoomLevel);
- this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds())
+ var me = this
- // We didn't actually animate, but we use this event to mean "clustering animations have finished"
- this.fire('animationend')
- },
- _animationAddLayer: function(layer, newCluster) {
- this._animationAddLayerNonAnimated(layer, newCluster)
- }
- },
+ // Update the opacity (If we immediately set it they won't animate)
+ this._forceLayout()
+ cluster._recursivelyBecomeVisible(bounds, newZoomLevel)
- _withAnimation: {
- // Animated versions here
- _animationStart: function() {
- this._map._mapPane.className += ' leaflet-cluster-anim'
- this._inZoomAnimation++
- },
-
- _animationZoomIn: function(previousZoomLevel, newZoomLevel) {
- var bounds = this._getExpandedVisibleBounds(),
- fg = this._featureGroup,
- minZoom = Math.floor(this._map.getMinZoom()),
- i
-
- this._ignoreMove = true
-
- // Add all children of current clusters to map and remove those clusters from map
- this._topClusterLevel._recursively(bounds, previousZoomLevel, minZoom, function(c) {
- var startPos = c._latlng,
- markers = c._markers,
- m
-
- if (!bounds.contains(startPos)) {
- startPos = null
- }
-
- if (c._isSingleParent() && previousZoomLevel + 1 === newZoomLevel) { // Immediately add the new child and remove us
- fg.removeLayer(c)
- c._recursivelyAddChildrenToMap(null, newZoomLevel, bounds)
- } else {
- // Fade out old cluster
- c.clusterHide()
- c._recursivelyAddChildrenToMap(startPos, newZoomLevel, bounds)
- }
-
- // Remove all markers that aren't visible any more
- // TODO: Do we actually need to do this on the higher levels too?
- for (i = markers.length - 1; i >= 0; i--) {
- m = markers[i]
- if (!bounds.contains(m._latlng)) {
- fg.removeLayer(m)
- }
- }
-
- });
-
- this._forceLayout()
-
- // Update opacities
- this._topClusterLevel._recursivelyBecomeVisible(bounds, newZoomLevel);
- // TODO Maybe? Update markers in _recursivelyBecomeVisible
- fg.eachLayer(function(n) {
- if (!(n instanceof L.MarkerCluster) && n._icon) {
- n.clusterShow()
- }
- });
-
- // update the positions of the just added clusters/markers
- this._topClusterLevel._recursively(bounds, previousZoomLevel, newZoomLevel, function(c) {
- c._recursivelyRestoreChildPositions(newZoomLevel);
- });
-
- this._ignoreMove = false;
-
- // Remove the old clusters and close the zoom animation
- this._enqueue(function() {
- // update the positions of the just added clusters/markers
- this._topClusterLevel._recursively(bounds, previousZoomLevel, minZoom, function(c) {
- fg.removeLayer(c)
- c.clusterShow()
- })
-
- this._animationEnd()
- })
- },
-
- _animationZoomOut: function(previousZoomLevel, newZoomLevel) {
- this._animationZoomOutSingle(this._topClusterLevel, previousZoomLevel - 1, newZoomLevel)
-
- // Need to add markers for those that weren't on the map before but are now
- this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds())
- // Remove markers that were on the map before but won't be now
- this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), previousZoomLevel, this._getExpandedVisibleBounds());
- },
-
- _animationAddLayer: function(layer, newCluster) {
- var me = this,
- fg = this._featureGroup
-
- fg.addLayer(layer)
- if (newCluster !== layer) {
- if (newCluster._childCount > 2) { // Was already a cluster
- newCluster._updateIcon()
- this._forceLayout()
- this._animationStart()
-
- layer._setPos(this._map.latLngToLayerPoint(newCluster.getLatLng()))
- layer.clusterHide()
-
- this._enqueue(function() {
- fg.removeLayer(layer)
- layer.clusterShow()
-
- me._animationEnd()
- });
-
- } else { // Just became a cluster
- this._forceLayout()
-
- me._animationStart()
- me._animationZoomOutSingle(newCluster, this._map.getMaxZoom(), this._zoom)
- }
- }
- }
- },
-
- // Private methods for animated versions.
- _animationZoomOutSingle: function(cluster, previousZoomLevel, newZoomLevel) {
- var bounds = this._getExpandedVisibleBounds(),
- minZoom = Math.floor(this._map.getMinZoom())
-
- // Animate all of the markers in the clusters to move to their cluster center point
- cluster._recursivelyAnimateChildrenInAndAddSelfToMap(bounds, minZoom, previousZoomLevel + 1, newZoomLevel)
-
- var me = this
-
- // Update the opacity (If we immediately set it they won't animate)
- this._forceLayout();
- cluster._recursivelyBecomeVisible(bounds, newZoomLevel)
-
- // TODO: Maybe use the transition timing stuff to make this more reliable
- // When the animations are done, tidy up
- this._enqueue(function() {
- // This cluster stopped being a cluster before the timeout fired
- if (cluster._childCount === 1) {
- var m = cluster._markers[0]
- // If we were in a cluster animation at the time then the opacity and position of our child could be wrong now, so fix it
- this._ignoreMove = true
- m.setLatLng(m.getLatLng())
- this._ignoreMove = false
- if (m.clusterShow) {
- m.clusterShow()
- }
- } else {
- cluster._recursively(bounds, newZoomLevel, minZoom, function(c) {
- c._recursivelyRemoveChildrenFromMap(bounds, minZoom, previousZoomLevel + 1)
- })
- }
- me._animationEnd()
- });
- },
-
- _animationEnd: function() {
- if (this._map) {
- this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-cluster-anim', '');
- }
- this._inZoomAnimation--
- this.fire('animationend')
- },
-
- // Force a browser layout of stuff in the map
- // Should apply the current opacity and location to all elements so we can update them again for an animation
- _forceLayout: function() {
- // In my testing this works, infact offsetWidth of any element seems to work.
- // Could loop all this._layers and do this for each _icon if it stops working
-
- L.Util.falseFn(document.body.offsetWidth)
+ // TODO: Maybe use the transition timing stuff to make this more reliable
+ // When the animations are done, tidy up
+ this._enqueue(function () {
+ // This cluster stopped being a cluster before the timeout fired
+ if (cluster._childCount === 1) {
+ var m = cluster._markers[0]
+ // If we were in a cluster animation at the time then the opacity and position of our child could be wrong now, so fix it
+ this._ignoreMove = true
+ m.setLatLng(m.getLatLng())
+ this._ignoreMove = false
+ if (m.clusterShow) {
+ m.clusterShow()
+ }
+ } else {
+ cluster._recursively(bounds, newZoomLevel, minZoom, function (c) {
+ c._recursivelyRemoveChildrenFromMap(bounds, minZoom, previousZoomLevel + 1)
+ })
}
- });
+ me._animationEnd()
+ })
+ },
- L.markerClusterGroup = function(options) {
- return new L.MarkerClusterGroup(options)
- };
+ _animationEnd: function () {
+ if (this._map) {
+ this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-cluster-anim', '')
+ }
+ this._inZoomAnimation--
+ this.fire('animationend')
+ },
- var MarkerCluster = L.MarkerCluster = L.Marker.extend({
- options: L.Icon.prototype.options,
+ // Force a browser layout of stuff in the map
+ // Should apply the current opacity and location to all elements so we can update them again for an animation
+ _forceLayout: function () {
+ // In my testing this works, infact offsetWidth of any element seems to work.
+ // Could loop all this._layers and do this for each _icon if it stops working
- initialize: function(group, zoom, a, b) {
- L.Marker.prototype.initialize.call(this, a ? (a._cLatLng || a.getLatLng()) : new L.LatLng(0, 0),
- { icon: this, pane: group.options.clusterPane })
+ L.Util.falseFn(document.body.offsetWidth)
+ }
+ })
- this._group = group
- this._zoom = zoom
+ L.markerClusterGroup = function (options) {
+ return new L.MarkerClusterGroup(options)
+ }
- this._markers = []
- this._childClusters = []
- this._childCount = 0
- this._iconNeedsUpdate = true
- this._boundsNeedUpdate = true
+ var MarkerCluster = L.MarkerCluster = L.Marker.extend({
+ options: L.Icon.prototype.options,
- this._bounds = new L.LatLngBounds()
+ initialize: function (group, zoom, a, b) {
+ L.Marker.prototype.initialize.call(this, a ? (a._cLatLng || a.getLatLng()) : new L.LatLng(0, 0),
+ { icon: this, pane: group.options.clusterPane })
- if (a) {
- this._addChild(a);
- }
- if (b) {
- this._addChild(b);
- }
- },
+ this._group = group
+ this._zoom = zoom
- // Recursively retrieve all child markers of this cluster
- getAllChildMarkers: function(storageArray, ignoreDraggedMarker) {
- storageArray = storageArray || []
+ this._markers = []
+ this._childClusters = []
+ this._childCount = 0
+ this._iconNeedsUpdate = true
+ this._boundsNeedUpdate = true
- for (var i = this._childClusters.length - 1; i >= 0; i--) {
- this._childClusters[i].getAllChildMarkers(storageArray)
- }
+ this._bounds = new L.LatLngBounds()
- for (var j = this._markers.length - 1; j >= 0; j--) {
- if (ignoreDraggedMarker && this._markers[j].__dragStart) {
- continue
- }
- storageArray.push(this._markers[j])
- }
+ if (a) {
+ this._addChild(a)
+ }
+ if (b) {
+ this._addChild(b)
+ }
+ },
- return storageArray
- },
+ // Recursively retrieve all child markers of this cluster
+ getAllChildMarkers: function (storageArray, ignoreDraggedMarker) {
+ storageArray = storageArray || []
- // Returns the count of how many child markers we have
- getChildCount: function() {
- return this._childCount;
- },
+ for (var i = this._childClusters.length - 1; i >= 0; i--) {
+ this._childClusters[i].getAllChildMarkers(storageArray)
+ }
- // Zoom to the minimum of showing all of the child markers, or the extents of this cluster
- zoomToBounds: function(fitBoundsOptions) {
- var childClusters = this._childClusters.slice(),
- map = this._group._map,
- boundsZoom = map.getBoundsZoom(this._bounds),
- zoom = this._zoom + 1,
- mapZoom = map.getZoom(),
- i;
+ for (var j = this._markers.length - 1; j >= 0; j--) {
+ if (ignoreDraggedMarker && this._markers[j].__dragStart) {
+ continue
+ }
+ storageArray.push(this._markers[j])
+ }
- // calculate how far we need to zoom down to see all of the markers
- while (childClusters.length > 0 && boundsZoom > zoom) {
- zoom++;
- var newClusters = [];
- for (i = 0; i < childClusters.length; i++) {
- newClusters = newClusters.concat(childClusters[i]._childClusters);
- }
- childClusters = newClusters;
- }
+ return storageArray
+ },
- if (boundsZoom > zoom) {
- this._group._map.setView(this._latlng, zoom);
- } else if (boundsZoom <= mapZoom) { // If fitBounds wouldn't zoom us down, zoom us down instead
- this._group._map.setView(this._latlng, mapZoom + 1);
- } else {
- this._group._map.fitBounds(this._bounds, fitBoundsOptions)
- }
- },
+ // Returns the count of how many child markers we have
+ getChildCount: function () {
+ return this._childCount
+ },
- getBounds: function() {
- var bounds = new L.LatLngBounds()
- bounds.extend(this._bounds)
- return bounds
- },
+ // Zoom to the minimum of showing all of the child markers, or the extents of this cluster
+ zoomToBounds: function (fitBoundsOptions) {
+ var childClusters = this._childClusters.slice()
+ var map = this._group._map
+ var boundsZoom = map.getBoundsZoom(this._bounds)
+ var zoom = this._zoom + 1
+ var mapZoom = map.getZoom()
+ var i
- _updateIcon: function() {
- this._iconNeedsUpdate = true
- if (this._icon) {
- this.setIcon(this)
- }
- },
+ // calculate how far we need to zoom down to see all of the markers
+ while (childClusters.length > 0 && boundsZoom > zoom) {
+ zoom++
+ var newClusters = []
+ for (i = 0; i < childClusters.length; i++) {
+ newClusters = newClusters.concat(childClusters[i]._childClusters)
+ }
+ childClusters = newClusters
+ }
- // Cludge for Icon, we pretend to be an icon for performance
- createIcon: function() {
- if (this._iconNeedsUpdate) {
- this._iconObj = this._group.options.iconCreateFunction(this);
- this._iconNeedsUpdate = false
- }
- return this._iconObj.createIcon()
- },
- createShadow: function() {
- return this._iconObj.createShadow()
- },
+ if (boundsZoom > zoom) {
+ this._group._map.setView(this._latlng, zoom)
+ } else if (boundsZoom <= mapZoom) { // If fitBounds wouldn't zoom us down, zoom us down instead
+ this._group._map.setView(this._latlng, mapZoom + 1)
+ } else {
+ this._group._map.fitBounds(this._bounds, fitBoundsOptions)
+ }
+ },
- _addChild: function(new1, isNotificationFromChild) {
- this._iconNeedsUpdate = true
+ getBounds: function () {
+ var bounds = new L.LatLngBounds()
+ bounds.extend(this._bounds)
+ return bounds
+ },
- this._boundsNeedUpdate = true
- this._setClusterCenter(new1)
+ _updateIcon: function () {
+ this._iconNeedsUpdate = true
+ if (this._icon) {
+ this.setIcon(this)
+ }
+ },
- if (new1 instanceof L.MarkerCluster) {
- if (!isNotificationFromChild) {
- this._childClusters.push(new1)
- new1.__parent = this
- }
- this._childCount += new1._childCount
- } else {
- if (!isNotificationFromChild) {
- this._markers.push(new1)
- }
- this._childCount++
- }
+ // Cludge for Icon, we pretend to be an icon for performance
+ createIcon: function () {
+ if (this._iconNeedsUpdate) {
+ this._iconObj = this._group.options.iconCreateFunction(this)
+ this._iconNeedsUpdate = false
+ }
+ return this._iconObj.createIcon()
+ },
+ createShadow: function () {
+ return this._iconObj.createShadow()
+ },
- if (this.__parent) {
- this.__parent._addChild(new1, true)
- }
- },
+ _addChild: function (new1, isNotificationFromChild) {
+ this._iconNeedsUpdate = true
- /**
+ this._boundsNeedUpdate = true
+ this._setClusterCenter(new1)
+
+ if (new1 instanceof L.MarkerCluster) {
+ if (!isNotificationFromChild) {
+ this._childClusters.push(new1)
+ new1.__parent = this
+ }
+ this._childCount += new1._childCount
+ } else {
+ if (!isNotificationFromChild) {
+ this._markers.push(new1)
+ }
+ this._childCount++
+ }
+
+ if (this.__parent) {
+ this.__parent._addChild(new1, true)
+ }
+ },
+
+ /**
* Makes sure the cluster center is set. If not, uses the child center if it is a cluster, or the marker position.
* @param child L.MarkerCluster|L.Marker that will be used as cluster center if not defined yet.
* @private
*/
- _setClusterCenter: function(child) {
- if (!this._cLatLng) {
- // when clustering, take position of the first point as the cluster center
- this._cLatLng = child._cLatLng || child._latlng
- }
- },
+ _setClusterCenter: function (child) {
+ if (!this._cLatLng) {
+ // when clustering, take position of the first point as the cluster center
+ this._cLatLng = child._cLatLng || child._latlng
+ }
+ },
- /**
+ /**
* Assigns impossible bounding values so that the next extend entirely determines the new bounds.
* This method avoids having to trash the previous L.LatLngBounds object and to create a new one, which is much slower for this class.
* As long as the bounds are not extended, most other methods would probably fail, as they would with bounds initialized but not extended.
* @private
*/
- _resetBounds: function() {
- var bounds = this._bounds
+ _resetBounds: function () {
+ var bounds = this._bounds
- if (bounds._southWest) {
- bounds._southWest.lat = Infinity
- bounds._southWest.lng = Infinity
- }
- if (bounds._northEast) {
- bounds._northEast.lat = -Infinity
- bounds._northEast.lng = -Infinity
- }
- },
+ if (bounds._southWest) {
+ bounds._southWest.lat = Infinity
+ bounds._southWest.lng = Infinity
+ }
+ if (bounds._northEast) {
+ bounds._northEast.lat = -Infinity
+ bounds._northEast.lng = -Infinity
+ }
+ },
- _recalculateBounds: function() {
- var markers = this._markers,
- childClusters = this._childClusters,
- latSum = 0,
- lngSum = 0,
- totalCount = this._childCount,
- i, child, childLatLng, childCount
+ _recalculateBounds: function () {
+ var markers = this._markers
+ var childClusters = this._childClusters
+ var latSum = 0
+ var lngSum = 0
+ var totalCount = this._childCount
+ var i; var child; var childLatLng; var childCount
- // Case where all markers are removed from the map and we are left with just an empty _topClusterLevel.
- if (totalCount === 0) {
- return
- }
+ // Case where all markers are removed from the map and we are left with just an empty _topClusterLevel.
+ if (totalCount === 0) {
+ return
+ }
- // Reset rather than creating a new object, for performance.
- this._resetBounds()
+ // Reset rather than creating a new object, for performance.
+ this._resetBounds()
- // Child markers.
- for (i = 0; i < markers.length; i++) {
- childLatLng = markers[i]._latlng
+ // Child markers.
+ for (i = 0; i < markers.length; i++) {
+ childLatLng = markers[i]._latlng
- this._bounds.extend(childLatLng)
+ this._bounds.extend(childLatLng)
- latSum += childLatLng.lat
- lngSum += childLatLng.lng
- }
+ latSum += childLatLng.lat
+ lngSum += childLatLng.lng
+ }
- // Child clusters.
- for (i = 0; i < childClusters.length; i++) {
- child = childClusters[i]
+ // Child clusters.
+ for (i = 0; i < childClusters.length; i++) {
+ child = childClusters[i]
- // Re-compute child bounds and weighted position first if necessary.
- if (child._boundsNeedUpdate) {
- child._recalculateBounds()
- }
-
- this._bounds.extend(child._bounds)
-
- childLatLng = child._wLatLng
- childCount = child._childCount
-
- latSum += childLatLng.lat * childCount
- lngSum += childLatLng.lng * childCount
- }
-
- this._latlng = this._wLatLng = new L.LatLng(latSum / totalCount, lngSum / totalCount)
-
- // Reset dirty flag.
- this._boundsNeedUpdate = false
- },
-
- // Set our markers position as given and add it to the map
- _addToMap: function(startPos) {
- if (startPos) {
- this._backupLatlng = this._latlng
- this.setLatLng(startPos)
- }
- this._group._featureGroup.addLayer(this)
- },
-
- _recursivelyAnimateChildrenIn: function(bounds, center, maxZoom) {
- this._recursively(bounds, this._group._map.getMinZoom(), maxZoom - 1,
- function(c) {
- var markers = c._markers,
- i, m
- for (i = markers.length - 1; i >= 0; i--) {
- m = markers[i]
-
- // Only do it if the icon is still on the map
- if (m._icon) {
- m._setPos(center)
- m.clusterHide()
- }
- }
- },
- function(c) {
- var childClusters = c._childClusters,
- j, cm
- for (j = childClusters.length - 1; j >= 0; j--) {
- cm = childClusters[j]
- if (cm._icon) {
- cm._setPos(center)
- cm.clusterHide()
- }
- }
- }
- );
- },
-
- _recursivelyAnimateChildrenInAndAddSelfToMap: function(bounds, mapMinZoom, previousZoomLevel, newZoomLevel) {
- this._recursively(bounds, newZoomLevel, mapMinZoom,
- function(c) {
- c._recursivelyAnimateChildrenIn(bounds, c._group._map.latLngToLayerPoint(c.getLatLng()).round(), previousZoomLevel);
-
- // TODO: depthToAnimateIn affects _isSingleParent, if there is a multizoom we may/may not be.
- // As a hack we only do a animation free zoom on a single level zoom, if someone does multiple levels then we always animate
- if (c._isSingleParent() && previousZoomLevel - 1 === newZoomLevel) {
- c.clusterShow()
- c._recursivelyRemoveChildrenFromMap(bounds, mapMinZoom, previousZoomLevel) // Immediately remove our children as we are replacing them. TODO previousBounds not bounds
- } else {
- c.clusterHide()
- }
-
- c._addToMap()
- }
- )
- },
-
- _recursivelyBecomeVisible: function(bounds, zoomLevel) {
- this._recursively(bounds, this._group._map.getMinZoom(), zoomLevel, null, function(c) {
- c.clusterShow()
- })
- },
-
- _recursivelyAddChildrenToMap: function(startPos, zoomLevel, bounds) {
- this._recursively(bounds, this._group._map.getMinZoom() - 1, zoomLevel,
- function(c) {
- if (zoomLevel === c._zoom) {
- return
- }
-
- // Add our child markers at startPos (so they can be animated out)
- for (var i = c._markers.length - 1; i >= 0; i--) {
- var nm = c._markers[i]
-
- if (!bounds.contains(nm._latlng)) {
- continue
- }
-
- if (startPos) {
- nm._backupLatlng = nm.getLatLng()
-
- nm.setLatLng(startPos)
- if (nm.clusterHide) {
- nm.clusterHide()
- }
- }
-
- c._group._featureGroup.addLayer(nm)
- }
- },
- function(c) {
- c._addToMap(startPos)
- }
- )
- },
-
- _recursivelyRestoreChildPositions: function(zoomLevel) {
- // Fix positions of child markers
- for (var i = this._markers.length - 1; i >= 0; i--) {
- var nm = this._markers[i]
- if (nm._backupLatlng) {
- nm.setLatLng(nm._backupLatlng)
- delete nm._backupLatlng
- }
- }
-
- if (zoomLevel - 1 === this._zoom) {
- // Reposition child clusters
- for (var j = this._childClusters.length - 1; j >= 0; j--) {
- this._childClusters[j]._restorePosition();
- }
- } else {
- for (var k = this._childClusters.length - 1; k >= 0; k--) {
- this._childClusters[k]._recursivelyRestoreChildPositions(zoomLevel)
- }
- }
- },
-
- _restorePosition: function() {
- if (this._backupLatlng) {
- this.setLatLng(this._backupLatlng)
- delete this._backupLatlng
- }
- },
-
- // exceptBounds: If set, don't remove any markers/clusters in it
- _recursivelyRemoveChildrenFromMap: function(previousBounds, mapMinZoom, zoomLevel, exceptBounds) {
- var m, i
- this._recursively(previousBounds, mapMinZoom - 1, zoomLevel - 1,
- function(c) {
- // Remove markers at every level
- for (i = c._markers.length - 1; i >= 0; i--) {
- m = c._markers[i]
- if (!exceptBounds || !exceptBounds.contains(m._latlng)) {
- c._group._featureGroup.removeLayer(m)
- if (m.clusterShow) {
- m.clusterShow()
- }
- }
- }
- },
- function(c) {
- // Remove child clusters at just the bottom level
- for (i = c._childClusters.length - 1; i >= 0; i--) {
- m = c._childClusters[i]
- if (!exceptBounds || !exceptBounds.contains(m._latlng)) {
- c._group._featureGroup.removeLayer(m)
- if (m.clusterShow) {
- m.clusterShow()
- }
- }
- }
- }
- )
- },
-
- // Run the given functions recursively to this and child clusters
- // boundsToApplyTo: a L.LatLngBounds representing the bounds of what clusters to recurse in to
- // zoomLevelToStart: zoom level to start running functions (inclusive)
- // zoomLevelToStop: zoom level to stop running functions (inclusive)
- // runAtEveryLevel: function that takes an L.MarkerCluster as an argument that should be applied on every level
- // runAtBottomLevel: function that takes an L.MarkerCluster as an argument that should be applied at only the bottom level
- _recursively: function(boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel) {
- var childClusters = this._childClusters,
- zoom = this._zoom,
- i, c
-
- if (zoomLevelToStart <= zoom) {
- if (runAtEveryLevel) {
- runAtEveryLevel(this)
- }
- if (runAtBottomLevel && zoom === zoomLevelToStop) {
- runAtBottomLevel(this)
- }
- }
-
- if (zoom < zoomLevelToStart || zoom < zoomLevelToStop) {
- for (i = childClusters.length - 1; i >= 0; i--) {
- c = childClusters[i]
- if (c._boundsNeedUpdate) {
- c._recalculateBounds()
- }
- if (boundsToApplyTo.intersects(c._bounds)) {
- c._recursively(boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel)
- }
- }
- }
- },
-
- // Returns true if we are the parent of only one cluster and that cluster is the same as us
- _isSingleParent: function() {
- // Don't need to check this._markers as the rest won't work if there are any
- return this._childClusters.length > 0 && this._childClusters[0]._childCount === this._childCount
+ // Re-compute child bounds and weighted position first if necessary.
+ if (child._boundsNeedUpdate) {
+ child._recalculateBounds()
}
- });
- /*
+ this._bounds.extend(child._bounds)
+
+ childLatLng = child._wLatLng
+ childCount = child._childCount
+
+ latSum += childLatLng.lat * childCount
+ lngSum += childLatLng.lng * childCount
+ }
+
+ this._latlng = this._wLatLng = new L.LatLng(latSum / totalCount, lngSum / totalCount)
+
+ // Reset dirty flag.
+ this._boundsNeedUpdate = false
+ },
+
+ // Set our markers position as given and add it to the map
+ _addToMap: function (startPos) {
+ if (startPos) {
+ this._backupLatlng = this._latlng
+ this.setLatLng(startPos)
+ }
+ this._group._featureGroup.addLayer(this)
+ },
+
+ _recursivelyAnimateChildrenIn: function (bounds, center, maxZoom) {
+ this._recursively(bounds, this._group._map.getMinZoom(), maxZoom - 1,
+ function (c) {
+ var markers = c._markers
+ var i; var m
+ for (i = markers.length - 1; i >= 0; i--) {
+ m = markers[i]
+
+ // Only do it if the icon is still on the map
+ if (m._icon) {
+ m._setPos(center)
+ m.clusterHide()
+ }
+ }
+ },
+ function (c) {
+ var childClusters = c._childClusters
+ var j; var cm
+ for (j = childClusters.length - 1; j >= 0; j--) {
+ cm = childClusters[j]
+ if (cm._icon) {
+ cm._setPos(center)
+ cm.clusterHide()
+ }
+ }
+ }
+ )
+ },
+
+ _recursivelyAnimateChildrenInAndAddSelfToMap: function (bounds, mapMinZoom, previousZoomLevel, newZoomLevel) {
+ this._recursively(bounds, newZoomLevel, mapMinZoom,
+ function (c) {
+ c._recursivelyAnimateChildrenIn(bounds, c._group._map.latLngToLayerPoint(c.getLatLng()).round(), previousZoomLevel)
+
+ // TODO: depthToAnimateIn affects _isSingleParent, if there is a multizoom we may/may not be.
+ // As a hack we only do a animation free zoom on a single level zoom, if someone does multiple levels then we always animate
+ if (c._isSingleParent() && previousZoomLevel - 1 === newZoomLevel) {
+ c.clusterShow()
+ c._recursivelyRemoveChildrenFromMap(bounds, mapMinZoom, previousZoomLevel) // Immediately remove our children as we are replacing them. TODO previousBounds not bounds
+ } else {
+ c.clusterHide()
+ }
+
+ c._addToMap()
+ }
+ )
+ },
+
+ _recursivelyBecomeVisible: function (bounds, zoomLevel) {
+ this._recursively(bounds, this._group._map.getMinZoom(), zoomLevel, null, function (c) {
+ c.clusterShow()
+ })
+ },
+
+ _recursivelyAddChildrenToMap: function (startPos, zoomLevel, bounds) {
+ this._recursively(bounds, this._group._map.getMinZoom() - 1, zoomLevel,
+ function (c) {
+ if (zoomLevel === c._zoom) {
+ return
+ }
+
+ // Add our child markers at startPos (so they can be animated out)
+ for (var i = c._markers.length - 1; i >= 0; i--) {
+ var nm = c._markers[i]
+
+ if (!bounds.contains(nm._latlng)) {
+ continue
+ }
+
+ if (startPos) {
+ nm._backupLatlng = nm.getLatLng()
+
+ nm.setLatLng(startPos)
+ if (nm.clusterHide) {
+ nm.clusterHide()
+ }
+ }
+
+ c._group._featureGroup.addLayer(nm)
+ }
+ },
+ function (c) {
+ c._addToMap(startPos)
+ }
+ )
+ },
+
+ _recursivelyRestoreChildPositions: function (zoomLevel) {
+ // Fix positions of child markers
+ for (var i = this._markers.length - 1; i >= 0; i--) {
+ var nm = this._markers[i]
+ if (nm._backupLatlng) {
+ nm.setLatLng(nm._backupLatlng)
+ delete nm._backupLatlng
+ }
+ }
+
+ if (zoomLevel - 1 === this._zoom) {
+ // Reposition child clusters
+ for (var j = this._childClusters.length - 1; j >= 0; j--) {
+ this._childClusters[j]._restorePosition()
+ }
+ } else {
+ for (var k = this._childClusters.length - 1; k >= 0; k--) {
+ this._childClusters[k]._recursivelyRestoreChildPositions(zoomLevel)
+ }
+ }
+ },
+
+ _restorePosition: function () {
+ if (this._backupLatlng) {
+ this.setLatLng(this._backupLatlng)
+ delete this._backupLatlng
+ }
+ },
+
+ // exceptBounds: If set, don't remove any markers/clusters in it
+ _recursivelyRemoveChildrenFromMap: function (previousBounds, mapMinZoom, zoomLevel, exceptBounds) {
+ var m, i
+ this._recursively(previousBounds, mapMinZoom - 1, zoomLevel - 1,
+ function (c) {
+ // Remove markers at every level
+ for (i = c._markers.length - 1; i >= 0; i--) {
+ m = c._markers[i]
+ if (!exceptBounds || !exceptBounds.contains(m._latlng)) {
+ c._group._featureGroup.removeLayer(m)
+ if (m.clusterShow) {
+ m.clusterShow()
+ }
+ }
+ }
+ },
+ function (c) {
+ // Remove child clusters at just the bottom level
+ for (i = c._childClusters.length - 1; i >= 0; i--) {
+ m = c._childClusters[i]
+ if (!exceptBounds || !exceptBounds.contains(m._latlng)) {
+ c._group._featureGroup.removeLayer(m)
+ if (m.clusterShow) {
+ m.clusterShow()
+ }
+ }
+ }
+ }
+ )
+ },
+
+ // Run the given functions recursively to this and child clusters
+ // boundsToApplyTo: a L.LatLngBounds representing the bounds of what clusters to recurse in to
+ // zoomLevelToStart: zoom level to start running functions (inclusive)
+ // zoomLevelToStop: zoom level to stop running functions (inclusive)
+ // runAtEveryLevel: function that takes an L.MarkerCluster as an argument that should be applied on every level
+ // runAtBottomLevel: function that takes an L.MarkerCluster as an argument that should be applied at only the bottom level
+ _recursively: function (boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel) {
+ var childClusters = this._childClusters
+ var zoom = this._zoom
+ var i; var c
+
+ if (zoomLevelToStart <= zoom) {
+ if (runAtEveryLevel) {
+ runAtEveryLevel(this)
+ }
+ if (runAtBottomLevel && zoom === zoomLevelToStop) {
+ runAtBottomLevel(this)
+ }
+ }
+
+ if (zoom < zoomLevelToStart || zoom < zoomLevelToStop) {
+ for (i = childClusters.length - 1; i >= 0; i--) {
+ c = childClusters[i]
+ if (c._boundsNeedUpdate) {
+ c._recalculateBounds()
+ }
+ if (boundsToApplyTo.intersects(c._bounds)) {
+ c._recursively(boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel)
+ }
+ }
+ }
+ },
+
+ // Returns true if we are the parent of only one cluster and that cluster is the same as us
+ _isSingleParent: function () {
+ // Don't need to check this._markers as the rest won't work if there are any
+ return this._childClusters.length > 0 && this._childClusters[0]._childCount === this._childCount
+ }
+ })
+
+ /*
* Extends L.Marker to include two extra methods: clusterHide and clusterShow.
*
* They work as setOpacity(0) and setOpacity(1) respectively, but
@@ -1791,134 +1780,134 @@
*
*/
- L.Marker.include({
- clusterHide: function() {
- var backup = this.options.opacity
- this.setOpacity(0)
- this.options.opacity = backup
- return this
- },
+ L.Marker.include({
+ clusterHide: function () {
+ var backup = this.options.opacity
+ this.setOpacity(0)
+ this.options.opacity = backup
+ return this
+ },
- clusterShow: function() {
- return this.setOpacity(this.options.opacity)
- }
- })
-
- L.DistanceGrid = function(cellSize) {
- this._cellSize = cellSize
- this._sqCellSize = cellSize * cellSize
- this._grid = {}
- this._objectPoint = {}
+ clusterShow: function () {
+ return this.setOpacity(this.options.opacity)
}
+ })
- L.DistanceGrid.prototype = {
+ L.DistanceGrid = function (cellSize) {
+ this._cellSize = cellSize
+ this._sqCellSize = cellSize * cellSize
+ this._grid = {}
+ this._objectPoint = {}
+ }
- addObject: function(obj, point) {
- var x = this._getCoord(point.x),
- y = this._getCoord(point.y),
- grid = this._grid,
- row = grid[y] = grid[y] || {},
- cell = row[x] = row[x] || [],
- stamp = L.Util.stamp(obj)
+ L.DistanceGrid.prototype = {
- this._objectPoint[stamp] = point
+ addObject: function (obj, point) {
+ var x = this._getCoord(point.x)
+ var y = this._getCoord(point.y)
+ var grid = this._grid
+ var row = grid[y] = grid[y] || {}
+ var cell = row[x] = row[x] || []
+ var stamp = L.Util.stamp(obj)
- cell.push(obj)
- },
+ this._objectPoint[stamp] = point
- updateObject: function(obj, point) {
- this.removeObject(obj)
- this.addObject(obj, point)
- },
+ cell.push(obj)
+ },
- // Returns true if the object was found
- removeObject: function(obj, point) {
- var x = this._getCoord(point.x),
- y = this._getCoord(point.y),
- grid = this._grid,
- row = grid[y] = grid[y] || {},
- cell = row[x] = row[x] || [],
- i, len;
+ updateObject: function (obj, point) {
+ this.removeObject(obj)
+ this.addObject(obj, point)
+ },
- delete this._objectPoint[L.Util.stamp(obj)];
+ // Returns true if the object was found
+ removeObject: function (obj, point) {
+ var x = this._getCoord(point.x)
+ var y = this._getCoord(point.y)
+ var grid = this._grid
+ var row = grid[y] = grid[y] || {}
+ var cell = row[x] = row[x] || []
+ var i; var len
- for (i = 0, len = cell.length; i < len; i++) {
- if (cell[i] === obj) {
- cell.splice(i, 1)
+ delete this._objectPoint[L.Util.stamp(obj)]
- if (len === 1) {
- delete row[x]
- }
+ for (i = 0, len = cell.length; i < len; i++) {
+ if (cell[i] === obj) {
+ cell.splice(i, 1)
- return true
- }
- }
- },
+ if (len === 1) {
+ delete row[x]
+ }
- eachObject: function(fn, context) {
- var i, j, k, len, row, cell, removed,
- grid = this._grid
-
- for (i in grid) {
- row = grid[i]
-
- for (j in row) {
- cell = row[j]
-
- for (k = 0, len = cell.length; k < len; k++) {
- removed = fn.call(context, cell[k]);
- if (removed) {
- k--
- len--
- }
- }
- }
- }
- },
-
- getNearObject: function(point) {
- var x = this._getCoord(point.x),
- y = this._getCoord(point.y),
- i, j, k, row, cell, len, obj, dist,
- objectPoint = this._objectPoint,
- closestDistSq = this._sqCellSize,
- closest = null
-
- for (i = y - 1; i <= y + 1; i++) {
- row = this._grid[i]
- if (row) {
- for (j = x - 1; j <= x + 1; j++) {
- cell = row[j]
- if (cell) {
- for (k = 0, len = cell.length; k < len; k++) {
- obj = cell[k]
- dist = this._sqDist(objectPoint[L.Util.stamp(obj)], point);
- if (dist < closestDistSq ||
- dist <= closestDistSq && closest === null) {
- closestDistSq = dist
- closest = obj
- }
- }
- }
- }
- }
- }
- return closest
- },
-
- _getCoord: function(x) {
- var coord = Math.floor(x / this._cellSize)
- return isFinite(coord) ? coord : x
- },
-
- _sqDist: function(p, p2) {
- var dx = p2.x - p.x,
- dy = p2.y - p.y
- return dx * dx + dy * dy
+ return true
}
- };
+ }
+ },
- /* Copyright (c) 2012 the authors listed at the following URL, and/or
+ eachObject: function (fn, context) {
+ var i; var j; var k; var len; var row; var cell; var removed
+ var grid = this._grid
+
+ for (i in grid) {
+ row = grid[i]
+
+ for (j in row) {
+ cell = row[j]
+
+ for (k = 0, len = cell.length; k < len; k++) {
+ removed = fn.call(context, cell[k])
+ if (removed) {
+ k--
+ len--
+ }
+ }
+ }
+ }
+ },
+
+ getNearObject: function (point) {
+ var x = this._getCoord(point.x)
+ var y = this._getCoord(point.y)
+ var i; var j; var k; var row; var cell; var len; var obj; var dist
+ var objectPoint = this._objectPoint
+ var closestDistSq = this._sqCellSize
+ var closest = null
+
+ for (i = y - 1; i <= y + 1; i++) {
+ row = this._grid[i]
+ if (row) {
+ for (j = x - 1; j <= x + 1; j++) {
+ cell = row[j]
+ if (cell) {
+ for (k = 0, len = cell.length; k < len; k++) {
+ obj = cell[k]
+ dist = this._sqDist(objectPoint[L.Util.stamp(obj)], point)
+ if (dist < closestDistSq ||
+ dist <= closestDistSq && closest === null) {
+ closestDistSq = dist
+ closest = obj
+ }
+ }
+ }
+ }
+ }
+ }
+ return closest
+ },
+
+ _getCoord: function (x) {
+ var coord = Math.floor(x / this._cellSize)
+ return isFinite(coord) ? coord : x
+ },
+
+ _sqDist: function (p, p2) {
+ var dx = p2.x - p.x
+ var dy = p2.y - p.y
+ return dx * dx + dy * dy
+ }
+ };
+
+ /* Copyright (c) 2012 the authors listed at the following URL, and/or
the authors of referenced articles or incorporated external code:
http:// en.literateprograms.org/Quickhull_(Javascript)?action=history&offset=20120410175256
@@ -1944,629 +1933,628 @@
Retrieved from: http:// en.literateprograms.org/Quickhull_(Javascript)?oldid=18434
*/
- (function() {
- L.QuickHull = {
+ (function () {
+ L.QuickHull = {
- /*
+ /*
* @param {Object} cpt a point to be measured from the baseline
* @param {Array} bl the baseline, as represented by a two-element
* array of latlng objects.
* @returns {Number} an approximate distance measure
*/
- getDistant: function(cpt, bl) {
- var vY = bl[1].lat - bl[0].lat,
- vX = bl[0].lng - bl[1].lng
- return (vX * (cpt.lat - bl[0].lat) + vY * (cpt.lng - bl[0].lng))
- },
+ getDistant: function (cpt, bl) {
+ var vY = bl[1].lat - bl[0].lat
+ var vX = bl[0].lng - bl[1].lng
+ return (vX * (cpt.lat - bl[0].lat) + vY * (cpt.lng - bl[0].lng))
+ },
- /*
+ /*
* @param {Array} baseLine a two-element array of latlng objects
* representing the baseline to project from
* @param {Array} latLngs an array of latlng objects
* @returns {Object} the maximum point and all new points to stay
* in consideration for the hull.
*/
- findMostDistantPointFromBaseLine: function(baseLine, latLngs) {
- var maxD = 0,
- maxPt = null,
- newPoints = [],
- i, pt, d
+ findMostDistantPointFromBaseLine: function (baseLine, latLngs) {
+ var maxD = 0
+ var maxPt = null
+ var newPoints = []
+ var i; var pt; var d
- for (i = latLngs.length - 1; i >= 0; i--) {
- pt = latLngs[i]
- d = this.getDistant(pt, baseLine)
+ for (i = latLngs.length - 1; i >= 0; i--) {
+ pt = latLngs[i]
+ d = this.getDistant(pt, baseLine)
- if (d > 0) {
- newPoints.push(pt)
- } else {
- continue
- }
+ if (d > 0) {
+ newPoints.push(pt)
+ } else {
+ continue
+ }
- if (d > maxD) {
- maxD = d
- maxPt = pt
- }
- }
+ if (d > maxD) {
+ maxD = d
+ maxPt = pt
+ }
+ }
- return { maxPoint: maxPt, newPoints: newPoints }
- },
+ return { maxPoint: maxPt, newPoints: newPoints }
+ },
- /*
+ /*
* Given a baseline, compute the convex hull of latLngs as an array
* of latLngs.
*
* @param {Array} latLngs
* @returns {Array}
*/
- buildConvexHull: function(baseLine, latLngs) {
- var convexHullBaseLines = [],
- t = this.findMostDistantPointFromBaseLine(baseLine, latLngs);
+ buildConvexHull: function (baseLine, latLngs) {
+ var convexHullBaseLines = []
+ var t = this.findMostDistantPointFromBaseLine(baseLine, latLngs)
- if (t.maxPoint) { // if there is still a point "outside" the base line
- convexHullBaseLines =
+ if (t.maxPoint) { // if there is still a point "outside" the base line
+ convexHullBaseLines =
convexHullBaseLines.concat(
- this.buildConvexHull([baseLine[0], t.maxPoint], t.newPoints)
- );
- convexHullBaseLines =
- convexHullBaseLines.concat(
- this.buildConvexHull([t.maxPoint, baseLine[1]], t.newPoints)
+ this.buildConvexHull([baseLine[0], t.maxPoint], t.newPoints)
)
- return convexHullBaseLines
- } else { // if there is no more point "outside" the base line, the current base line is part of the convex hull
- return [baseLine[0]]
- }
- },
+ convexHullBaseLines =
+ convexHullBaseLines.concat(
+ this.buildConvexHull([t.maxPoint, baseLine[1]], t.newPoints)
+ )
+ return convexHullBaseLines
+ } else { // if there is no more point "outside" the base line, the current base line is part of the convex hull
+ return [baseLine[0]]
+ }
+ },
- /*
+ /*
* Given an array of latlngs, compute a convex hull as an array
* of latlngs
*
* @param {Array} latLngs
* @returns {Array}
*/
- getConvexHull: function(latLngs) {
- // find first baseline
- var maxLat = false, minLat = false,
- maxLng = false, minLng = false,
- maxLatPt = null, minLatPt = null,
- maxLngPt = null, minLngPt = null,
- maxPt = null, minPt = null,
- i
+ getConvexHull: function (latLngs) {
+ // find first baseline
+ var maxLat = false; var minLat = false
+ var maxLng = false; var minLng = false
+ var maxLatPt = null; var minLatPt = null
+ var maxLngPt = null; var minLngPt = null
+ var maxPt = null; var minPt = null
+ var i
- for (i = latLngs.length - 1; i >= 0; i--) {
- var pt = latLngs[i]
- if (maxLat === false || pt.lat > maxLat) {
- maxLatPt = pt
- maxLat = pt.lat
- }
- if (minLat === false || pt.lat < minLat) {
- minLatPt = pt
- minLat = pt.lat
- }
- if (maxLng === false || pt.lng > maxLng) {
- maxLngPt = pt
- maxLng = pt.lng
- }
- if (minLng === false || pt.lng < minLng) {
- minLngPt = pt
- minLng = pt.lng
- }
- }
-
- if (minLat !== maxLat) {
- minPt = minLatPt
- maxPt = maxLatPt
- } else {
- minPt = minLngPt
- maxPt = maxLngPt
- }
-
- var ch = [].concat(this.buildConvexHull([minPt, maxPt], latLngs),
- this.buildConvexHull([maxPt, minPt], latLngs))
- return ch
- }
+ for (i = latLngs.length - 1; i >= 0; i--) {
+ var pt = latLngs[i]
+ if (maxLat === false || pt.lat > maxLat) {
+ maxLatPt = pt
+ maxLat = pt.lat
+ }
+ if (minLat === false || pt.lat < minLat) {
+ minLatPt = pt
+ minLat = pt.lat
+ }
+ if (maxLng === false || pt.lng > maxLng) {
+ maxLngPt = pt
+ maxLng = pt.lng
+ }
+ if (minLng === false || pt.lng < minLng) {
+ minLngPt = pt
+ minLng = pt.lng
+ }
}
- }())
- L.MarkerCluster.include({
- getConvexHull: function() {
- var childMarkers = this.getAllChildMarkers(),
- points = [],
- p, i
-
- for (i = childMarkers.length - 1; i >= 0; i--) {
- p = childMarkers[i].getLatLng()
- points.push(p)
- }
-
- return L.QuickHull.getConvexHull(points);
+ if (minLat !== maxLat) {
+ minPt = minLatPt
+ maxPt = maxLatPt
+ } else {
+ minPt = minLngPt
+ maxPt = maxLngPt
}
- })
-// This code is 100% based on https:// github.com/jawj/OverlappingMarkerSpiderfier-Leaflet
-// Huge thanks to jawj for implementing it first to make my job easy :-)
+ var ch = [].concat(this.buildConvexHull([minPt, maxPt], latLngs),
+ this.buildConvexHull([maxPt, minPt], latLngs))
+ return ch
+ }
+ }
+ }())
- L.MarkerCluster.include({
+ L.MarkerCluster.include({
+ getConvexHull: function () {
+ var childMarkers = this.getAllChildMarkers()
+ var points = []
+ var p; var i
- _2PI: Math.PI * 2,
- _circleFootSeparation: 25, // related to circumference of circle
- _circleStartAngle: 0,
+ for (i = childMarkers.length - 1; i >= 0; i--) {
+ p = childMarkers[i].getLatLng()
+ points.push(p)
+ }
- _spiralFootSeparation: 28, // related to size of spiral (experiment!)
- _spiralLengthStart: 11,
- _spiralLengthFactor: 5,
+ return L.QuickHull.getConvexHull(points)
+ }
+ })
- _circleSpiralSwitchover: 9, // show spiral instead of circle from this marker count upwards.
- // 0 -> always spiral; Infinity -> always circle
+ // This code is 100% based on https:// github.com/jawj/OverlappingMarkerSpiderfier-Leaflet
+ // Huge thanks to jawj for implementing it first to make my job easy :-)
- spiderfy: function() {
- if (this._group._spiderfied === this || this._group._inZoomAnimation) {
- return
- }
+ L.MarkerCluster.include({
- var childMarkers = this.getAllChildMarkers(null, true),
- group = this._group,
- map = group._map,
- center = map.latLngToLayerPoint(this._latlng),
- positions;
+ _2PI: Math.PI * 2,
+ _circleFootSeparation: 25, // related to circumference of circle
+ _circleStartAngle: 0,
- this._group._unspiderfy();
- this._group._spiderfied = this;
+ _spiralFootSeparation: 28, // related to size of spiral (experiment!)
+ _spiralLengthStart: 11,
+ _spiralLengthFactor: 5,
- // TODO Maybe: childMarkers order by distance to center
+ _circleSpiralSwitchover: 9, // show spiral instead of circle from this marker count upwards.
+ // 0 -> always spiral; Infinity -> always circle
- if (childMarkers.length >= this._circleSpiralSwitchover) {
- positions = this._generatePointsSpiral(childMarkers.length, center)
- } else {
- center.y += 10 // Otherwise circles look wrong => hack for standard blue icon, renders differently for other icons.
- positions = this._generatePointsCircle(childMarkers.length, center)
- }
+ spiderfy: function () {
+ if (this._group._spiderfied === this || this._group._inZoomAnimation) {
+ return
+ }
- this._animationSpiderfy(childMarkers, positions)
- },
+ var childMarkers = this.getAllChildMarkers(null, true)
+ var group = this._group
+ var map = group._map
+ var center = map.latLngToLayerPoint(this._latlng)
+ var positions
- unspiderfy: function(zoomDetails) {
- // <param Name="zoomDetails">Argument from zoomanim if being called in a zoom animation or null otherwise</param>
- if (this._group._inZoomAnimation) {
- return
- }
- this._animationUnspiderfy(zoomDetails)
+ this._group._unspiderfy()
+ this._group._spiderfied = this
- this._group._spiderfied = null
- },
+ // TODO Maybe: childMarkers order by distance to center
- _generatePointsCircle: function(count, centerPt) {
- var circumference = this._group.options.spiderfyDistanceMultiplier * this._circleFootSeparation * (2 + count),
- legLength = circumference / this._2PI, // radius from circumference
- angleStep = this._2PI / count,
- res = [],
- i, angle
+ if (childMarkers.length >= this._circleSpiralSwitchover) {
+ positions = this._generatePointsSpiral(childMarkers.length, center)
+ } else {
+ center.y += 10 // Otherwise circles look wrong => hack for standard blue icon, renders differently for other icons.
+ positions = this._generatePointsCircle(childMarkers.length, center)
+ }
- legLength = Math.max(legLength, 35) // Minimum distance to get outside the cluster icon.
+ this._animationSpiderfy(childMarkers, positions)
+ },
- res.length = count
+ unspiderfy: function (zoomDetails) {
+ // <param Name="zoomDetails">Argument from zoomanim if being called in a zoom animation or null otherwise</param>
+ if (this._group._inZoomAnimation) {
+ return
+ }
+ this._animationUnspiderfy(zoomDetails)
- for (i = 0; i < count; i++) { // Clockwise, like spiral.
- angle = this._circleStartAngle + i * angleStep;
- res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round();
- }
+ this._group._spiderfied = null
+ },
- return res
- },
+ _generatePointsCircle: function (count, centerPt) {
+ var circumference = this._group.options.spiderfyDistanceMultiplier * this._circleFootSeparation * (2 + count)
+ var legLength = circumference / this._2PI // radius from circumference
+ var angleStep = this._2PI / count
+ var res = []
+ var i; var angle
- _generatePointsSpiral: function(count, centerPt) {
- var spiderfyDistanceMultiplier = this._group.options.spiderfyDistanceMultiplier,
- legLength = spiderfyDistanceMultiplier * this._spiralLengthStart,
- separation = spiderfyDistanceMultiplier * this._spiralFootSeparation,
- lengthFactor = spiderfyDistanceMultiplier * this._spiralLengthFactor * this._2PI,
- angle = 0,
- res = [],
- i
+ legLength = Math.max(legLength, 35) // Minimum distance to get outside the cluster icon.
- res.length = count
+ res.length = count
- // Higher index, closer position to cluster center.
- for (i = count; i >= 0; i--) {
- // Skip the first position, so that we are already farther from center and we avoid
- // being under the default cluster icon (especially important for Circle Markers).
- if (i < count) {
- res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round()
- }
- angle += separation / legLength + i * 0.0005
- legLength += lengthFactor / angle
- }
- return res;
- },
+ for (i = 0; i < count; i++) { // Clockwise, like spiral.
+ angle = this._circleStartAngle + i * angleStep
+ res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round()
+ }
- _noanimationUnspiderfy: function() {
- var group = this._group,
- map = group._map,
- fg = group._featureGroup,
- childMarkers = this.getAllChildMarkers(null, true),
- m, i
+ return res
+ },
- group._ignoreMove = true
+ _generatePointsSpiral: function (count, centerPt) {
+ var spiderfyDistanceMultiplier = this._group.options.spiderfyDistanceMultiplier
+ var legLength = spiderfyDistanceMultiplier * this._spiralLengthStart
+ var separation = spiderfyDistanceMultiplier * this._spiralFootSeparation
+ var lengthFactor = spiderfyDistanceMultiplier * this._spiralLengthFactor * this._2PI
+ var angle = 0
+ var res = []
+ var i
- this.setOpacity(1)
- for (i = childMarkers.length - 1; i >= 0; i--) {
- m = childMarkers[i]
+ res.length = count
- fg.removeLayer(m)
-
- if (m._preSpiderfyLatlng) {
- m.setLatLng(m._preSpiderfyLatlng)
- delete m._preSpiderfyLatlng
- }
- if (m.setZIndexOffset) {
- m.setZIndexOffset(0)
- }
-
- if (m._spiderLeg) {
- map.removeLayer(m._spiderLeg)
- delete m._spiderLeg
- }
- }
-
- group.fire('unspiderfied', {
- cluster: this,
- markers: childMarkers
- })
- group._ignoreMove = false
- group._spiderfied = null
+ // Higher index, closer position to cluster center.
+ for (i = count; i >= 0; i--) {
+ // Skip the first position, so that we are already farther from center and we avoid
+ // being under the default cluster icon (especially important for Circle Markers).
+ if (i < count) {
+ res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round()
}
- })
+ angle += separation / legLength + i * 0.0005
+ legLength += lengthFactor / angle
+ }
+ return res
+ },
-// Non Animated versions of everything
- L.MarkerClusterNonAnimated = L.MarkerCluster.extend({
- _animationSpiderfy: function(childMarkers, positions) {
- var group = this._group,
- map = group._map,
- fg = group._featureGroup,
- legOptions = this._group.options.spiderLegPolylineOptions,
- i, m, leg, newPos
+ _noanimationUnspiderfy: function () {
+ var group = this._group
+ var map = group._map
+ var fg = group._featureGroup
+ var childMarkers = this.getAllChildMarkers(null, true)
+ var m; var i
- group._ignoreMove = true
+ group._ignoreMove = true
- // Traverse in ascending order to make sure that inner circleMarkers are on top of further legs. Normal markers are re-ordered by newPosition.
- // The reverse order trick no longer improves performance on modern browsers.
- for (i = 0; i < childMarkers.length; i++) {
- newPos = map.layerPointToLatLng(positions[i])
- m = childMarkers[i]
+ this.setOpacity(1)
+ for (i = childMarkers.length - 1; i >= 0; i--) {
+ m = childMarkers[i]
- // Add the leg before the marker, so that in case the latter is a circleMarker, the leg is behind it.
- leg = new L.Polyline([this._latlng, newPos], legOptions)
- map.addLayer(leg)
- m._spiderLeg = leg
+ fg.removeLayer(m)
- // Now add the marker.
- m._preSpiderfyLatlng = m._latlng
- m.setLatLng(newPos)
- if (m.setZIndexOffset) {
- m.setZIndexOffset(1000000) // Make these appear on top of EVERYTHING
- }
-
- fg.addLayer(m)
- }
- this.setOpacity(0.3)
-
- group._ignoreMove = false
- group.fire('spiderfied', {
- cluster: this,
- markers: childMarkers
- })
- },
-
- _animationUnspiderfy: function() {
- this._noanimationUnspiderfy()
+ if (m._preSpiderfyLatlng) {
+ m.setLatLng(m._preSpiderfyLatlng)
+ delete m._preSpiderfyLatlng
}
- })
-
-// Animated versions here
- L.MarkerCluster.include({
-
- _animationSpiderfy: function(childMarkers, positions) {
- var me = this,
- group = this._group,
- map = group._map,
- fg = group._featureGroup,
- thisLayerLatLng = this._latlng,
- thisLayerPos = map.latLngToLayerPoint(thisLayerLatLng),
- svg = L.Path.SVG,
- legOptions = L.extend({}, this._group.options.spiderLegPolylineOptions), // Copy the options so that we can modify them for animation.
- finalLegOpacity = legOptions.opacity,
- i, m, leg, legPath, legLength, newPos
-
- if (finalLegOpacity === undefined) {
- finalLegOpacity = L.MarkerClusterGroup.prototype.options.spiderLegPolylineOptions.opacity;
- }
-
- if (svg) {
- // If the initial opacity of the spider leg is not 0 then it appears before the animation starts.
- legOptions.opacity = 0
-
- // Add the class for CSS transitions.
- legOptions.className = (legOptions.className || '') + ' leaflet-cluster-spider-leg'
- } else {
- // Make sure we have a defined opacity.
- legOptions.opacity = finalLegOpacity
- }
-
- group._ignoreMove = true
-
- // Add markers and spider legs to map, hidden at our center point.
- // Traverse in ascending order to make sure that inner circleMarkers are on top of further legs. Normal markers are re-ordered by newPosition.
- // The reverse order trick no longer improves performance on modern browsers.
- for (i = 0; i < childMarkers.length; i++) {
- m = childMarkers[i]
-
- newPos = map.layerPointToLatLng(positions[i])
-
- // Add the leg before the marker, so that in case the latter is a circleMarker, the leg is behind it.
- leg = new L.Polyline([thisLayerLatLng, newPos], legOptions)
- map.addLayer(leg)
- m._spiderLeg = leg
-
- // Explanations: https:// jakearchibald.com/2013/animated-line-drawing-svg/
- // In our case the transition property is declared in the CSS file.
- if (svg) {
- legPath = leg._path
- legLength = legPath.getTotalLength() + 0.1 // Need a small extra length to avoid remaining dot in Firefox.
- legPath.style.strokeDasharray = legLength // Just 1 length is enough, it will be duplicated.
- legPath.style.strokeDashoffset = legLength
- }
-
- // If it is a marker, add it now and we'll animate it out
- if (m.setZIndexOffset) {
- m.setZIndexOffset(1000000) // Make normal markers appear on top of EVERYTHING
- }
- if (m.clusterHide) {
- m.clusterHide()
- }
-
- // Vectors just get immediately added
- fg.addLayer(m)
-
- if (m._setPos) {
- m._setPos(thisLayerPos)
- }
- }
-
- group._forceLayout()
- group._animationStart()
-
- // Reveal markers and spider legs.
- for (i = childMarkers.length - 1; i >= 0; i--) {
- newPos = map.layerPointToLatLng(positions[i])
- m = childMarkers[i]
-
- // Move marker to new position
- m._preSpiderfyLatlng = m._latlng
- m.setLatLng(newPos)
-
- if (m.clusterShow) {
- m.clusterShow()
- }
-
- // Animate leg (animation is actually delegated to CSS transition).
- if (svg) {
- leg = m._spiderLeg
- legPath = leg._path
- legPath.style.strokeDashoffset = 0
- // legPath.style.strokeOpacity = finalLegOpacity;
- leg.setStyle({ opacity: finalLegOpacity })
- }
- }
- this.setOpacity(0.3)
-
- group._ignoreMove = false
-
- setTimeout(function() {
- group._animationEnd()
- group.fire('spiderfied', {
- cluster: me,
- markers: childMarkers
- })
- }, 200)
- },
-
- _animationUnspiderfy: function(zoomDetails) {
- var me = this,
- group = this._group,
- map = group._map,
- fg = group._featureGroup,
- thisLayerPos = zoomDetails ? map._latLngToNewLayerPoint(this._latlng, zoomDetails.zoom, zoomDetails.center) : map.latLngToLayerPoint(this._latlng),
- childMarkers = this.getAllChildMarkers(null, true),
- svg = L.Path.SVG,
- m, i, leg, legPath, legLength, nonAnimatable
-
- group._ignoreMove = true
- group._animationStart()
-
- // Make us visible and bring the child markers back in
- this.setOpacity(1)
- for (i = childMarkers.length - 1; i >= 0; i--) {
- m = childMarkers[i]
-
- // Marker was added to us after we were spiderfied
- if (!m._preSpiderfyLatlng) {
- continue
- }
-
- // Close any popup on the marker first, otherwise setting the location of the marker will make the map scroll
- m.closePopup()
-
- // Fix up the location to the real one
- m.setLatLng(m._preSpiderfyLatlng)
- delete m._preSpiderfyLatlng
-
- // Hack override the location to be our center
- nonAnimatable = true
- if (m._setPos) {
- m._setPos(thisLayerPos)
- nonAnimatable = false
- }
- if (m.clusterHide) {
- m.clusterHide()
- nonAnimatable = false
- }
- if (nonAnimatable) {
- fg.removeLayer(m)
- }
-
- // Animate the spider leg back in (animation is actually delegated to CSS transition).
- if (svg) {
- leg = m._spiderLeg
- legPath = leg._path
- legLength = legPath.getTotalLength() + 0.1
- legPath.style.strokeDashoffset = legLength
- leg.setStyle({ opacity: 0 })
- }
- }
-
- group._ignoreMove = false
-
- setTimeout(function() {
- // If we have only <= one child left then that marker will be shown on the map so don't remove it!
- var stillThereChildCount = 0;
- for (i = childMarkers.length - 1; i >= 0; i--) {
- m = childMarkers[i]
- if (m._spiderLeg) {
- stillThereChildCount++
- }
- }
-
- for (i = childMarkers.length - 1; i >= 0; i--) {
- m = childMarkers[i]
-
- if (!m._spiderLeg) { // Has already been unspiderfied
- continue
- }
-
- if (m.clusterShow) {
- m.clusterShow()
- }
- if (m.setZIndexOffset) {
- m.setZIndexOffset(0)
- }
-
- if (stillThereChildCount > 1) {
- fg.removeLayer(m)
- }
-
- map.removeLayer(m._spiderLeg)
- delete m._spiderLeg
- }
- group._animationEnd()
- group.fire('unspiderfied', {
- cluster: me,
- markers: childMarkers
- })
- }, 200)
+ if (m.setZIndexOffset) {
+ m.setZIndexOffset(0)
}
- })
- L.MarkerClusterGroup.include({
- // The MarkerCluster currently spiderfied (if any)
- _spiderfied: null,
-
- unspiderfy: function() {
- this._unspiderfy.apply(this, arguments);
- },
-
- _spiderfierOnAdd: function() {
- this._map.on('click', this._unspiderfyWrapper, this)
-
- if (this._map.options.zoomAnimation) {
- this._map.on('zoomstart', this._unspiderfyZoomStart, this)
- }
- // Browsers without zoomAnimation or a big zoom don't fire zoomstart
- this._map.on('zoomend', this._noanimationUnspiderfy, this)
-
- if (!L.Browser.touch) {
- this._map.getRenderer(this);
- // Needs to happen in the pageload, not after, or animations don't work in webkit
- // http:// stackoverflow.com/questions/8455200/svg-animate-with-dynamically-added-elements
- // Disable on touch browsers as the animation messes up on a touch zoom and isn't very noticable
- }
- },
-
- _spiderfierOnRemove: function() {
- this._map.off('click', this._unspiderfyWrapper, this)
- this._map.off('zoomstart', this._unspiderfyZoomStart, this)
- this._map.off('zoomanim', this._unspiderfyZoomAnim, this)
- this._map.off('zoomend', this._noanimationUnspiderfy, this)
-
- // Ensure that markers are back where they should be
- // Use no animation to avoid a sticky leaflet-cluster-anim class on mapPane
- this._noanimationUnspiderfy()
- },
-
- // On zoom start we add a zoomanim handler so that we are guaranteed to be last (after markers are animated)
- // This means we can define the animation they do rather than Markers doing an animation to their actual location
- _unspiderfyZoomStart: function() {
- if (!this._map) { // May have been removed from the map by a zoomEnd handler
- return
- }
-
- this._map.on('zoomanim', this._unspiderfyZoomAnim, this);
- },
-
- _unspiderfyZoomAnim: function(zoomDetails) {
- // Wait until the first zoomanim after the user has finished touch-zooming before running the animation
- if (L.DomUtil.hasClass(this._map._mapPane, 'leaflet-touching')) {
- return
- }
-
- this._map.off('zoomanim', this._unspiderfyZoomAnim, this)
- this._unspiderfy(zoomDetails)
- },
-
- _unspiderfyWrapper: function() {
- // <summary>_unspiderfy but passes no arguments</summary>
- this._unspiderfy()
- },
-
- _unspiderfy: function(zoomDetails) {
- if (this._spiderfied) {
- this._spiderfied.unspiderfy(zoomDetails)
- }
- },
-
- _noanimationUnspiderfy: function() {
- if (this._spiderfied) {
- this._spiderfied._noanimationUnspiderfy()
- }
- },
-
- // If the given layer is currently being spiderfied then we unspiderfy it so it isn't on the map anymore etc
- _unspiderfyLayer: function(layer) {
- if (layer._spiderLeg) {
- this._featureGroup.removeLayer(layer)
-
- if (layer.clusterShow) {
- layer.clusterShow()
- }
- // Position will be fixed up immediately in _animationUnspiderfy
- if (layer.setZIndexOffset) {
- layer.setZIndexOffset(0)
- }
-
- this._map.removeLayer(layer._spiderLeg)
- delete layer._spiderLeg
- }
+ if (m._spiderLeg) {
+ map.removeLayer(m._spiderLeg)
+ delete m._spiderLeg
}
- });
+ }
- /**
+ group.fire('unspiderfied', {
+ cluster: this,
+ markers: childMarkers
+ })
+ group._ignoreMove = false
+ group._spiderfied = null
+ }
+ })
+
+ // Non Animated versions of everything
+ L.MarkerClusterNonAnimated = L.MarkerCluster.extend({
+ _animationSpiderfy: function (childMarkers, positions) {
+ var group = this._group
+ var map = group._map
+ var fg = group._featureGroup
+ var legOptions = this._group.options.spiderLegPolylineOptions
+ var i; var m; var leg; var newPos
+
+ group._ignoreMove = true
+
+ // Traverse in ascending order to make sure that inner circleMarkers are on top of further legs. Normal markers are re-ordered by newPosition.
+ // The reverse order trick no longer improves performance on modern browsers.
+ for (i = 0; i < childMarkers.length; i++) {
+ newPos = map.layerPointToLatLng(positions[i])
+ m = childMarkers[i]
+
+ // Add the leg before the marker, so that in case the latter is a circleMarker, the leg is behind it.
+ leg = new L.Polyline([this._latlng, newPos], legOptions)
+ map.addLayer(leg)
+ m._spiderLeg = leg
+
+ // Now add the marker.
+ m._preSpiderfyLatlng = m._latlng
+ m.setLatLng(newPos)
+ if (m.setZIndexOffset) {
+ m.setZIndexOffset(1000000) // Make these appear on top of EVERYTHING
+ }
+
+ fg.addLayer(m)
+ }
+ this.setOpacity(0.3)
+
+ group._ignoreMove = false
+ group.fire('spiderfied', {
+ cluster: this,
+ markers: childMarkers
+ })
+ },
+
+ _animationUnspiderfy: function () {
+ this._noanimationUnspiderfy()
+ }
+ })
+
+ // Animated versions here
+ L.MarkerCluster.include({
+
+ _animationSpiderfy: function (childMarkers, positions) {
+ var me = this
+ var group = this._group
+ var map = group._map
+ var fg = group._featureGroup
+ var thisLayerLatLng = this._latlng
+ var thisLayerPos = map.latLngToLayerPoint(thisLayerLatLng)
+ var svg = L.Path.SVG
+ var legOptions = L.extend({}, this._group.options.spiderLegPolylineOptions) // Copy the options so that we can modify them for animation.
+ var finalLegOpacity = legOptions.opacity
+ var i; var m; var leg; var legPath; var legLength; var newPos
+
+ if (finalLegOpacity === undefined) {
+ finalLegOpacity = L.MarkerClusterGroup.prototype.options.spiderLegPolylineOptions.opacity
+ }
+
+ if (svg) {
+ // If the initial opacity of the spider leg is not 0 then it appears before the animation starts.
+ legOptions.opacity = 0
+
+ // Add the class for CSS transitions.
+ legOptions.className = (legOptions.className || '') + ' leaflet-cluster-spider-leg'
+ } else {
+ // Make sure we have a defined opacity.
+ legOptions.opacity = finalLegOpacity
+ }
+
+ group._ignoreMove = true
+
+ // Add markers and spider legs to map, hidden at our center point.
+ // Traverse in ascending order to make sure that inner circleMarkers are on top of further legs. Normal markers are re-ordered by newPosition.
+ // The reverse order trick no longer improves performance on modern browsers.
+ for (i = 0; i < childMarkers.length; i++) {
+ m = childMarkers[i]
+
+ newPos = map.layerPointToLatLng(positions[i])
+
+ // Add the leg before the marker, so that in case the latter is a circleMarker, the leg is behind it.
+ leg = new L.Polyline([thisLayerLatLng, newPos], legOptions)
+ map.addLayer(leg)
+ m._spiderLeg = leg
+
+ // Explanations: https:// jakearchibald.com/2013/animated-line-drawing-svg/
+ // In our case the transition property is declared in the CSS file.
+ if (svg) {
+ legPath = leg._path
+ legLength = legPath.getTotalLength() + 0.1 // Need a small extra length to avoid remaining dot in Firefox.
+ legPath.style.strokeDasharray = legLength // Just 1 length is enough, it will be duplicated.
+ legPath.style.strokeDashoffset = legLength
+ }
+
+ // If it is a marker, add it now and we'll animate it out
+ if (m.setZIndexOffset) {
+ m.setZIndexOffset(1000000) // Make normal markers appear on top of EVERYTHING
+ }
+ if (m.clusterHide) {
+ m.clusterHide()
+ }
+
+ // Vectors just get immediately added
+ fg.addLayer(m)
+
+ if (m._setPos) {
+ m._setPos(thisLayerPos)
+ }
+ }
+
+ group._forceLayout()
+ group._animationStart()
+
+ // Reveal markers and spider legs.
+ for (i = childMarkers.length - 1; i >= 0; i--) {
+ newPos = map.layerPointToLatLng(positions[i])
+ m = childMarkers[i]
+
+ // Move marker to new position
+ m._preSpiderfyLatlng = m._latlng
+ m.setLatLng(newPos)
+
+ if (m.clusterShow) {
+ m.clusterShow()
+ }
+
+ // Animate leg (animation is actually delegated to CSS transition).
+ if (svg) {
+ leg = m._spiderLeg
+ legPath = leg._path
+ legPath.style.strokeDashoffset = 0
+ // legPath.style.strokeOpacity = finalLegOpacity;
+ leg.setStyle({ opacity: finalLegOpacity })
+ }
+ }
+ this.setOpacity(0.3)
+
+ group._ignoreMove = false
+
+ setTimeout(function () {
+ group._animationEnd()
+ group.fire('spiderfied', {
+ cluster: me,
+ markers: childMarkers
+ })
+ }, 200)
+ },
+
+ _animationUnspiderfy: function (zoomDetails) {
+ var me = this
+ var group = this._group
+ var map = group._map
+ var fg = group._featureGroup
+ var thisLayerPos = zoomDetails ? map._latLngToNewLayerPoint(this._latlng, zoomDetails.zoom, zoomDetails.center) : map.latLngToLayerPoint(this._latlng)
+ var childMarkers = this.getAllChildMarkers(null, true)
+ var svg = L.Path.SVG
+ var m; var i; var leg; var legPath; var legLength; var nonAnimatable
+
+ group._ignoreMove = true
+ group._animationStart()
+
+ // Make us visible and bring the child markers back in
+ this.setOpacity(1)
+ for (i = childMarkers.length - 1; i >= 0; i--) {
+ m = childMarkers[i]
+
+ // Marker was added to us after we were spiderfied
+ if (!m._preSpiderfyLatlng) {
+ continue
+ }
+
+ // Close any popup on the marker first, otherwise setting the location of the marker will make the map scroll
+ m.closePopup()
+
+ // Fix up the location to the real one
+ m.setLatLng(m._preSpiderfyLatlng)
+ delete m._preSpiderfyLatlng
+
+ // Hack override the location to be our center
+ nonAnimatable = true
+ if (m._setPos) {
+ m._setPos(thisLayerPos)
+ nonAnimatable = false
+ }
+ if (m.clusterHide) {
+ m.clusterHide()
+ nonAnimatable = false
+ }
+ if (nonAnimatable) {
+ fg.removeLayer(m)
+ }
+
+ // Animate the spider leg back in (animation is actually delegated to CSS transition).
+ if (svg) {
+ leg = m._spiderLeg
+ legPath = leg._path
+ legLength = legPath.getTotalLength() + 0.1
+ legPath.style.strokeDashoffset = legLength
+ leg.setStyle({ opacity: 0 })
+ }
+ }
+
+ group._ignoreMove = false
+
+ setTimeout(function () {
+ // If we have only <= one child left then that marker will be shown on the map so don't remove it!
+ var stillThereChildCount = 0
+ for (i = childMarkers.length - 1; i >= 0; i--) {
+ m = childMarkers[i]
+ if (m._spiderLeg) {
+ stillThereChildCount++
+ }
+ }
+
+ for (i = childMarkers.length - 1; i >= 0; i--) {
+ m = childMarkers[i]
+
+ if (!m._spiderLeg) { // Has already been unspiderfied
+ continue
+ }
+
+ if (m.clusterShow) {
+ m.clusterShow()
+ }
+ if (m.setZIndexOffset) {
+ m.setZIndexOffset(0)
+ }
+
+ if (stillThereChildCount > 1) {
+ fg.removeLayer(m)
+ }
+
+ map.removeLayer(m._spiderLeg)
+ delete m._spiderLeg
+ }
+ group._animationEnd()
+ group.fire('unspiderfied', {
+ cluster: me,
+ markers: childMarkers
+ })
+ }, 200)
+ }
+ })
+
+ L.MarkerClusterGroup.include({
+ // The MarkerCluster currently spiderfied (if any)
+ _spiderfied: null,
+
+ unspiderfy: function () {
+ this._unspiderfy.apply(this, arguments)
+ },
+
+ _spiderfierOnAdd: function () {
+ this._map.on('click', this._unspiderfyWrapper, this)
+
+ if (this._map.options.zoomAnimation) {
+ this._map.on('zoomstart', this._unspiderfyZoomStart, this)
+ }
+ // Browsers without zoomAnimation or a big zoom don't fire zoomstart
+ this._map.on('zoomend', this._noanimationUnspiderfy, this)
+
+ if (!L.Browser.touch) {
+ this._map.getRenderer(this)
+ // Needs to happen in the pageload, not after, or animations don't work in webkit
+ // http:// stackoverflow.com/questions/8455200/svg-animate-with-dynamically-added-elements
+ // Disable on touch browsers as the animation messes up on a touch zoom and isn't very noticable
+ }
+ },
+
+ _spiderfierOnRemove: function () {
+ this._map.off('click', this._unspiderfyWrapper, this)
+ this._map.off('zoomstart', this._unspiderfyZoomStart, this)
+ this._map.off('zoomanim', this._unspiderfyZoomAnim, this)
+ this._map.off('zoomend', this._noanimationUnspiderfy, this)
+
+ // Ensure that markers are back where they should be
+ // Use no animation to avoid a sticky leaflet-cluster-anim class on mapPane
+ this._noanimationUnspiderfy()
+ },
+
+ // On zoom start we add a zoomanim handler so that we are guaranteed to be last (after markers are animated)
+ // This means we can define the animation they do rather than Markers doing an animation to their actual location
+ _unspiderfyZoomStart: function () {
+ if (!this._map) { // May have been removed from the map by a zoomEnd handler
+ return
+ }
+
+ this._map.on('zoomanim', this._unspiderfyZoomAnim, this)
+ },
+
+ _unspiderfyZoomAnim: function (zoomDetails) {
+ // Wait until the first zoomanim after the user has finished touch-zooming before running the animation
+ if (L.DomUtil.hasClass(this._map._mapPane, 'leaflet-touching')) {
+ return
+ }
+
+ this._map.off('zoomanim', this._unspiderfyZoomAnim, this)
+ this._unspiderfy(zoomDetails)
+ },
+
+ _unspiderfyWrapper: function () {
+ // <summary>_unspiderfy but passes no arguments</summary>
+ this._unspiderfy()
+ },
+
+ _unspiderfy: function (zoomDetails) {
+ if (this._spiderfied) {
+ this._spiderfied.unspiderfy(zoomDetails)
+ }
+ },
+
+ _noanimationUnspiderfy: function () {
+ if (this._spiderfied) {
+ this._spiderfied._noanimationUnspiderfy()
+ }
+ },
+
+ // If the given layer is currently being spiderfied then we unspiderfy it so it isn't on the map anymore etc
+ _unspiderfyLayer: function (layer) {
+ if (layer._spiderLeg) {
+ this._featureGroup.removeLayer(layer)
+
+ if (layer.clusterShow) {
+ layer.clusterShow()
+ }
+ // Position will be fixed up immediately in _animationUnspiderfy
+ if (layer.setZIndexOffset) {
+ layer.setZIndexOffset(0)
+ }
+
+ this._map.removeLayer(layer._spiderLeg)
+ delete layer._spiderLeg
+ }
+ }
+ })
+
+ /**
* Adds 1 public method to MCG and 1 to L.Marker to facilitate changing
* markers' icon options and refreshing their icon and their parent clusters
* accordingly (case where their iconCreateFunction uses data of childMarkers
* to make up the cluster icon).
*/
-
- L.MarkerClusterGroup.include({
- /**
+ L.MarkerClusterGroup.include({
+ /**
* Updates the icon of all clusters which are parents of the given marker(s).
* In singleMarkerMode, also updates the given marker(s) icon.
* @param layers L.MarkerClusterGroup|L.LayerGroup|Array(L.Marker)|Map(L.Marker)|
@@ -2574,105 +2562,104 @@
* clusters need to be updated. If not provided, retrieves all child markers of this.
* @returns {L.MarkerClusterGroup}
*/
- refreshClusters: function(layers) {
- if (!layers) {
- layers = this._topClusterLevel.getAllChildMarkers()
- } else if (layers instanceof L.MarkerClusterGroup) {
- layers = layers._topClusterLevel.getAllChildMarkers()
- } else if (layers instanceof L.LayerGroup) {
- layers = layers._layers
- } else if (layers instanceof L.MarkerCluster) {
- layers = layers.getAllChildMarkers()
- } else if (layers instanceof L.Marker) {
- layers = [layers]
- } // else: must be an Array(L.Marker)|Map(L.Marker)
- this._flagParentsIconsNeedUpdate(layers)
- this._refreshClustersIcons()
+ refreshClusters: function (layers) {
+ if (!layers) {
+ layers = this._topClusterLevel.getAllChildMarkers()
+ } else if (layers instanceof L.MarkerClusterGroup) {
+ layers = layers._topClusterLevel.getAllChildMarkers()
+ } else if (layers instanceof L.LayerGroup) {
+ layers = layers._layers
+ } else if (layers instanceof L.MarkerCluster) {
+ layers = layers.getAllChildMarkers()
+ } else if (layers instanceof L.Marker) {
+ layers = [layers]
+ } // else: must be an Array(L.Marker)|Map(L.Marker)
+ this._flagParentsIconsNeedUpdate(layers)
+ this._refreshClustersIcons()
- // In case of singleMarkerMode, also re-draw the markers.
- if (this.options.singleMarkerMode) {
- this._refreshSingleMarkerModeMarkers(layers)
- }
+ // In case of singleMarkerMode, also re-draw the markers.
+ if (this.options.singleMarkerMode) {
+ this._refreshSingleMarkerModeMarkers(layers)
+ }
- return this
- },
+ return this
+ },
- /**
+ /**
* Simply flags all parent clusters of the given markers as having a "dirty" icon.
* @param layers Array(L.Marker)|Map(L.Marker) list of markers.
* @private
*/
- _flagParentsIconsNeedUpdate: function(layers) {
- var id, parent
+ _flagParentsIconsNeedUpdate: function (layers) {
+ var id, parent
- // Assumes layers is an Array or an Object whose prototype is non-enumerable.
- for (id in layers) {
- // Flag parent clusters' icon as "dirty", all the way up.
- // Dumb process that flags multiple times upper parents, but still
- // much more efficient than trying to be smart and make short lists,
- // at least in the case of a hierarchy following a power law:
- // http:// jsperf.com/flag-nodes-in-power-hierarchy/2
- parent = layers[id].__parent
- while (parent) {
- parent._iconNeedsUpdate = true
- parent = parent.__parent
- }
- }
- },
+ // Assumes layers is an Array or an Object whose prototype is non-enumerable.
+ for (id in layers) {
+ // Flag parent clusters' icon as "dirty", all the way up.
+ // Dumb process that flags multiple times upper parents, but still
+ // much more efficient than trying to be smart and make short lists,
+ // at least in the case of a hierarchy following a power law:
+ // http:// jsperf.com/flag-nodes-in-power-hierarchy/2
+ parent = layers[id].__parent
+ while (parent) {
+ parent._iconNeedsUpdate = true
+ parent = parent.__parent
+ }
+ }
+ },
- /**
+ /**
* Re-draws the icon of the supplied markers.
* To be used in singleMarkerMode only.
* @param layers Array(L.Marker)|Map(L.Marker) list of markers.
* @private
*/
- _refreshSingleMarkerModeMarkers: function(layers) {
- var id, layer
+ _refreshSingleMarkerModeMarkers: function (layers) {
+ var id, layer
- for (id in layers) {
- layer = layers[id]
+ for (id in layers) {
+ layer = layers[id]
- // Make sure we do not override markers that do not belong to THIS group.
- if (this.hasLayer(layer)) {
- // Need to re-create the icon first, then re-draw the marker.
- layer.setIcon(this._overrideMarkerIcon(layer))
- }
- }
+ // Make sure we do not override markers that do not belong to THIS group.
+ if (this.hasLayer(layer)) {
+ // Need to re-create the icon first, then re-draw the marker.
+ layer.setIcon(this._overrideMarkerIcon(layer))
}
- });
+ }
+ }
+ })
- L.Marker.include({
- /**
+ L.Marker.include({
+ /**
* Updates the given options in the marker's icon and refreshes the marker.
* @param options map object of icon options.
* @param directlyRefreshClusters boolean (optional) true to trigger
* MCG.refreshClustersOf() right away with this single marker.
* @returns {L.Marker}
*/
- refreshIconOptions: function(options, directlyRefreshClusters) {
- var icon = this.options.icon
+ refreshIconOptions: function (options, directlyRefreshClusters) {
+ var icon = this.options.icon
- L.setOptions(icon, options)
+ L.setOptions(icon, options)
- this.setIcon(icon)
+ this.setIcon(icon)
- // Shortcut to refresh the associated MCG clusters right away.
- // To be used when refreshing a single marker.
- // Otherwise, better use MCG.refreshClusters() once at the end with
- // the list of modified markers.
- if (directlyRefreshClusters && this.__parent) {
- this.__parent._group.refreshClusters(this)
- }
+ // Shortcut to refresh the associated MCG clusters right away.
+ // To be used when refreshing a single marker.
+ // Otherwise, better use MCG.refreshClusters() once at the end with
+ // the list of modified markers.
+ if (directlyRefreshClusters && this.__parent) {
+ this.__parent._group.refreshClusters(this)
+ }
- return this
- }
- })
+ return this
+ }
+ })
- exports.MarkerClusterGroup = MarkerClusterGroup
- exports.MarkerCluster = MarkerCluster
-
+ exports.MarkerClusterGroup = MarkerClusterGroup
+ exports.MarkerCluster = MarkerCluster
}
export default {
- init
+ init
}
--
Gitblit v1.8.0