Update leaflet marker cluster.

This commit is contained in:
David Molineus
2015-06-18 16:13:13 +02:00
parent 9c3c6c8d25
commit 8d9a6e7544
6 changed files with 248 additions and 117 deletions

View File

@@ -3,8 +3,7 @@
https://github.com/Leaflet/Leaflet.markercluster
(c) 2012-2013, Dave Leaver, smartrak
*/
(function (window, document, undefined) {
/*
(function (window, document, undefined) {/*
* L.MarkerClusterGroup extends L.FeatureGroup by clustering the markers contained within
*/
@@ -32,6 +31,15 @@ L.MarkerClusterGroup = L.FeatureGroup.extend({
//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' },
// 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: {}
},
@@ -148,8 +156,8 @@ L.MarkerClusterGroup = L.FeatureGroup.extend({
if (this._featureGroup.hasLayer(layer)) {
this._featureGroup.removeLayer(layer);
if (layer.setOpacity) {
layer.setOpacity(1);
if (layer.clusterShow) {
layer.clusterShow();
}
}
@@ -158,52 +166,90 @@ L.MarkerClusterGroup = L.FeatureGroup.extend({
//Takes an array of markers and adds them in bulk
addLayers: function (layersArray) {
var i, l, m,
onMap = this._map,
fg = this._featureGroup,
npg = this._nonPointGroup;
var fg = this._featureGroup,
npg = this._nonPointGroup,
chunked = this.options.chunkedLoading,
chunkInterval = this.options.chunkInterval,
chunkProgress = this.options.chunkProgress,
newMarkers, i, l, m;
for (i = 0, l = layersArray.length; i < l; i++) {
m = layersArray[i];
if (this._map) {
var offset = 0,
started = (new Date()).getTime();
var process = L.bind(function () {
var start = (new Date()).getTime();
for (; offset < layersArray.length; 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 :-)
}
}
//Not point data, can't be clustered
if (!m.getLatLng) {
npg.addLayer(m);
continue;
}
m = layersArray[offset];
if (this.hasLayer(m)) {
continue;
}
//Not point data, can't be clustered
if (!m.getLatLng) {
npg.addLayer(m);
continue;
}
if (!onMap) {
this._needsClustering.push(m);
continue;
}
if (this.hasLayer(m)) {
continue;
}
this._addLayer(m, this._maxZoom);
this._addLayer(m, this._maxZoom);
//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);
//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);
}
}
}
}
}
if (onMap) {
//Update the icons of all those visible clusters that were affected
fg.eachLayer(function (c) {
if (c instanceof L.MarkerCluster && c._iconNeedsUpdate) {
c._updateIcon();
if (chunkProgress) {
// report progress and time elapsed:
chunkProgress(offset, layersArray.length, (new Date()).getTime() - started);
}
});
this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds);
if (offset === layersArray.length) {
//Update the icons of all those visible clusters that were affected
this._featureGroup.eachLayer(function (c) {
if (c instanceof L.MarkerCluster && c._iconNeedsUpdate) {
c._updateIcon();
}
});
this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds);
} else {
setTimeout(process, this.options.chunkDelay);
}
}, this);
process();
} else {
newMarkers = [];
for (i = 0, l = layersArray.length; i < l; i++) {
m = layersArray[i];
//Not point data, can't be clustered
if (!m.getLatLng) {
npg.addLayer(m);
continue;
}
if (this.hasLayer(m)) {
continue;
}
newMarkers.push(m);
}
this._needsClustering = this._needsClustering.concat(newMarkers);
}
return this;
},
@@ -213,6 +259,10 @@ L.MarkerClusterGroup = L.FeatureGroup.extend({
fg = this._featureGroup,
npg = this._nonPointGroup;
if (this._unspiderfy) {
this._unspiderfy();
}
if (!this._map) {
for (i = 0, l = layersArray.length; i < l; i++) {
m = layersArray[i];
@@ -234,8 +284,8 @@ L.MarkerClusterGroup = L.FeatureGroup.extend({
if (fg.hasLayer(m)) {
fg.removeLayer(m);
if (m.setOpacity) {
m.setOpacity(1);
if (m.clusterShow) {
m.clusterShow();
}
}
}
@@ -286,12 +336,13 @@ L.MarkerClusterGroup = L.FeatureGroup.extend({
//Override FeatureGroup.getBounds as it doesn't work
getBounds: function () {
var bounds = new L.LatLngBounds();
if (this._topClusterLevel) {
bounds.extend(this._topClusterLevel._bounds);
} else {
for (var i = this._needsClustering.length - 1; i >= 0; i--) {
bounds.extend(this._needsClustering[i].getLatLng());
}
}
for (var i = this._needsClustering.length - 1; i >= 0; i--) {
bounds.extend(this._needsClustering[i].getLatLng());
}
bounds.extend(this._nonPointGroup.getBounds());
@@ -302,7 +353,7 @@ L.MarkerClusterGroup = L.FeatureGroup.extend({
//Overrides LayerGroup.eachLayer
eachLayer: function (method, context) {
var markers = this._needsClustering.slice(),
i;
i;
if (this._topClusterLevel) {
this._topClusterLevel.getAllChildMarkers(markers);
@@ -384,16 +435,27 @@ L.MarkerClusterGroup = L.FeatureGroup.extend({
};
if (layer._icon && this._map.getBounds().contains(layer.getLatLng())) {
//Layer is visible ond on screen, immediate return
callback();
} else if (layer.__parent._zoom < this._map.getZoom()) {
//Layer should be visible now but isn't on screen, just pan over to it
//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 {
var moveStart = function () {
this._map.off('movestart', moveStart, this);
moveStart = null;
};
this._map.on('movestart', moveStart, this);
this._map.on('moveend', showMarker, this);
this.on('animationend', showMarker, this);
this._map.setView(layer.getLatLng(), layer.__parent._zoom + 1);
layer.__parent.zoomToBounds();
if (moveStart) {
//Never started moving, must already be there, probably need clustering however
showMarker.call(this);
}
}
},
@@ -419,23 +481,9 @@ L.MarkerClusterGroup = L.FeatureGroup.extend({
}
this._needsRemoving = [];
for (i = 0, l = this._needsClustering.length; i < l; i++) {
layer = this._needsClustering[i];
//If the layer doesn't have a getLatLng then we can't cluster it, so add it to our child featureGroup
if (!layer.getLatLng) {
this._featureGroup.addLayer(layer);
continue;
}
if (layer.__parent) {
continue;
}
this._addLayer(layer, this._maxZoom);
}
this._needsClustering = [];
//Remember the current zoom level and bounds
this._zoom = this._map.getZoom();
this._currentShownBounds = this._getExpandedVisibleBounds();
this._map.on('zoomend', this._zoomEnd, this);
this._map.on('moveend', this._moveEnd, this);
@@ -446,15 +494,10 @@ L.MarkerClusterGroup = L.FeatureGroup.extend({
this._bindEvents();
//Actually add our markers to the map:
//Remember the current zoom level and bounds
this._zoom = this._map.getZoom();
this._currentShownBounds = this._getExpandedVisibleBounds();
//Make things appear on the map
this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds);
l = this._needsClustering;
this._needsClustering = [];
this.addLayers(l);
},
//Overrides FeatureGroup.onRemove
@@ -624,7 +667,11 @@ L.MarkerClusterGroup = L.FeatureGroup.extend({
_zoomOrSpiderfy: function (e) {
var map = this._map;
if (map.getMaxZoom() === map.getZoom()) {
if (e.layer._bounds._northEast.equals(e.layer._bounds._southWest)) {
if (this.options.spiderfyOnMaxZoom) {
e.layer.spiderfy();
}
} else if (map.getMaxZoom() === map.getZoom()) {
if (this.options.spiderfyOnMaxZoom) {
e.layer.spiderfy();
}
@@ -632,7 +679,7 @@ L.MarkerClusterGroup = L.FeatureGroup.extend({
e.layer.zoomToBounds();
}
// Focus the map again for keyboard users.
// Focus the map again for keyboard users.
if (e.originalEvent && e.originalEvent.keyCode === 13) {
map._container.focus();
}
@@ -701,7 +748,15 @@ L.MarkerClusterGroup = L.FeatureGroup.extend({
_generateInitialClusters: function () {
var maxZoom = this._map.getMaxZoom(),
radius = this.options.maxClusterRadius;
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 (this.options.disableClusteringAtZoom) {
maxZoom = this.options.disableClusteringAtZoom - 1;
@@ -709,11 +764,11 @@ L.MarkerClusterGroup = L.FeatureGroup.extend({
this._maxZoom = maxZoom;
this._gridClusters = {};
this._gridUnclustered = {};
//Set up DistanceGrids for each zoom
for (var zoom = maxZoom; zoom >= 0; zoom--) {
this._gridClusters[zoom] = new L.DistanceGrid(radius);
this._gridUnclustered[zoom] = new L.DistanceGrid(radius);
this._gridClusters[zoom] = new L.DistanceGrid(radiusFn(zoom));
this._gridUnclustered[zoom] = new L.DistanceGrid(radiusFn(zoom));
}
this._topClusterLevel = new L.MarkerCluster(this, -1);
@@ -813,7 +868,7 @@ L.MarkerClusterGroup = L.FeatureGroup.extend({
//Incase we are starting to split before the animation finished
this._processQueue();
if (this._zoom < this._map._zoom && this._currentShownBounds.contains(this._getExpandedVisibleBounds())) { //Zoom in, split
if (this._zoom < this._map._zoom && this._currentShownBounds.intersects(this._getExpandedVisibleBounds())) { //Zoom in, split
this._animationStart();
//Remove clusters now off screen
this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, this._zoom, this._getExpandedVisibleBounds());
@@ -832,7 +887,7 @@ L.MarkerClusterGroup = L.FeatureGroup.extend({
//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.getBounds();
return this._map.getBounds();
}
var map = this._map,
@@ -872,10 +927,16 @@ L.MarkerClusterGroup.include(!L.DomUtil.TRANSITION ? {
_animationZoomIn: function (previousZoomLevel, newZoomLevel) {
this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, 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, 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);
@@ -914,7 +975,7 @@ L.MarkerClusterGroup.include(!L.DomUtil.TRANSITION ? {
c._recursivelyAddChildrenToMap(null, newZoomLevel, bounds);
} else {
//Fade out old cluster
c.setOpacity(0);
c.clusterHide();
c._recursivelyAddChildrenToMap(startPos, newZoomLevel, bounds);
}
@@ -936,7 +997,7 @@ L.MarkerClusterGroup.include(!L.DomUtil.TRANSITION ? {
//TODO Maybe? Update markers in _recursivelyBecomeVisible
fg.eachLayer(function (n) {
if (!(n instanceof L.MarkerCluster) && n._icon) {
n.setOpacity(1);
n.clusterShow();
}
});
@@ -950,7 +1011,7 @@ L.MarkerClusterGroup.include(!L.DomUtil.TRANSITION ? {
//update the positions of the just added clusters/markers
this._topClusterLevel._recursively(bounds, previousZoomLevel, 0, function (c) {
fg.removeLayer(c);
c.setOpacity(1);
c.clusterShow();
});
this._animationEnd();
@@ -986,7 +1047,9 @@ L.MarkerClusterGroup.include(!L.DomUtil.TRANSITION ? {
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
m.setLatLng(m.getLatLng());
m.setOpacity(1);
if (m.clusterShow) {
m.clusterShow();
}
} else {
cluster._recursively(bounds, newZoomLevel, 0, function (c) {
c._recursivelyRemoveChildrenFromMap(bounds, previousZoomLevel + 1);
@@ -1008,11 +1071,11 @@ L.MarkerClusterGroup.include(!L.DomUtil.TRANSITION ? {
this._animationStart();
layer._setPos(this._map.latLngToLayerPoint(newCluster.getLatLng()));
layer.setOpacity(0);
layer.clusterHide();
this._enqueue(function () {
fg.removeLayer(layer);
layer.setOpacity(1);
layer.clusterShow();
me._animationEnd();
});
@@ -1094,7 +1157,7 @@ L.MarkerCluster = L.Marker.extend({
mapZoom = map.getZoom(),
i;
//calculate how fare we need to zoom down to see all of the markers
//calculate how far we need to zoom down to see all of the markers
while (childClusters.length > 0 && boundsZoom > zoom) {
zoom++;
var newClusters = [];
@@ -1212,7 +1275,7 @@ L.MarkerCluster = L.Marker.extend({
//Only do it if the icon is still on the map
if (m._icon) {
m._setPos(center);
m.setOpacity(0);
m.clusterHide();
}
}
},
@@ -1223,7 +1286,7 @@ L.MarkerCluster = L.Marker.extend({
cm = childClusters[j];
if (cm._icon) {
cm._setPos(center);
cm.setOpacity(0);
cm.clusterHide();
}
}
}
@@ -1238,10 +1301,10 @@ L.MarkerCluster = L.Marker.extend({
//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.setOpacity(1);
c.clusterShow();
c._recursivelyRemoveChildrenFromMap(bounds, previousZoomLevel); //Immediately remove our children as we are replacing them. TODO previousBounds not bounds
} else {
c.setOpacity(0);
c.clusterHide();
}
c._addToMap();
@@ -1251,7 +1314,7 @@ L.MarkerCluster = L.Marker.extend({
_recursivelyBecomeVisible: function (bounds, zoomLevel) {
this._recursively(bounds, 0, zoomLevel, null, function (c) {
c.setOpacity(1);
c.clusterShow();
});
},
@@ -1274,8 +1337,8 @@ L.MarkerCluster = L.Marker.extend({
nm._backupLatlng = nm.getLatLng();
nm.setLatLng(startPos);
if (nm.setOpacity) {
nm.setOpacity(0);
if (nm.clusterHide) {
nm.clusterHide();
}
}
@@ -1327,8 +1390,8 @@ L.MarkerCluster = L.Marker.extend({
m = c._markers[i];
if (!exceptBounds || !exceptBounds.contains(m._latlng)) {
c._group._featureGroup.removeLayer(m);
if (m.setOpacity) {
m.setOpacity(1);
if (m.clusterShow) {
m.clusterShow();
}
}
}
@@ -1339,8 +1402,8 @@ L.MarkerCluster = L.Marker.extend({
m = c._childClusters[i];
if (!exceptBounds || !exceptBounds.contains(m._latlng)) {
c._group._featureGroup.removeLayer(m);
if (m.setOpacity) {
m.setOpacity(1);
if (m.clusterShow) {
m.clusterShow();
}
}
}
@@ -1357,7 +1420,7 @@ L.MarkerCluster = L.Marker.extend({
_recursively: function (boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel) {
var childClusters = this._childClusters,
zoom = this._zoom,
i, c;
i, c;
if (zoomLevelToStart > zoom) { //Still going down to required depth, just recurse to child clusters
for (i = childClusters.length - 1; i >= 0; i--) {
@@ -1413,6 +1476,34 @@ L.MarkerCluster = L.Marker.extend({
/*
* Extends L.Marker to include two extra methods: clusterHide and clusterShow.
*
* They work as setOpacity(0) and setOpacity(1) respectively, but
* they will remember the marker's opacity when hiding and showing it again.
*
*/
L.Marker.include({
clusterHide: function () {
this.options.opacityWhenUnclustered = this.options.opacity || 1;
return this.setOpacity(0);
},
clusterShow: function () {
var ret = this.setOpacity(this.options.opacity || this.options.opacityWhenUnclustered);
delete this.options.opacityWhenUnclustered;
return ret;
}
});
L.DistanceGrid = function (cellSize) {
this._cellSize = cellSize;
this._sqCellSize = cellSize * cellSize;
@@ -1815,8 +1906,8 @@ L.MarkerCluster.include(!L.DomUtil.TRANSITION ? {
fg.addLayer(m);
leg = new L.Polyline([this._latlng, newPos], { weight: 1.5, color: '#222' });
var legOptions = this._group.options.spiderLegPolylineOptions;
leg = new L.Polyline([this._latlng, newPos], legOptions);
map.addLayer(leg);
m._spiderLeg = leg;
}
@@ -1848,7 +1939,7 @@ L.MarkerCluster.include(!L.DomUtil.TRANSITION ? {
//If it is a marker, add it now and we'll animate it out
if (m.setOpacity) {
m.setZIndexOffset(1000000); //Make these appear on top of EVERYTHING
m.setOpacity(0);
m.clusterHide();
fg.addLayer(m);
@@ -1875,12 +1966,16 @@ L.MarkerCluster.include(!L.DomUtil.TRANSITION ? {
m.setLatLng(newPos);
if (m.setOpacity) {
m.setOpacity(1);
m.clusterShow();
}
//Add Legs.
leg = new L.Polyline([me._latlng, newPos], { weight: 1.5, color: '#222', opacity: initialLegOpacity });
var legOptions = this._group.options.spiderLegPolylineOptions;
if (legOptions.opacity === undefined) {
legOptions.opacity = initialLegOpacity;
}
leg = new L.Polyline([me._latlng, newPos], legOptions);
map.addLayer(leg);
m._spiderLeg = leg;
@@ -1966,7 +2061,7 @@ L.MarkerCluster.include(!L.DomUtil.TRANSITION ? {
//Hack override the location to be our center
if (m.setOpacity) {
m._setPos(thisLayerPos);
m.setOpacity(0);
m.clusterHide();
} else {
fg.removeLayer(m);
}
@@ -2008,7 +2103,7 @@ L.MarkerCluster.include(!L.DomUtil.TRANSITION ? {
if (m.setOpacity) {
m.setOpacity(1);
m.clusterShow();
m.setZIndexOffset(0);
}