2012-07-25 10:53:38 +12:00
/ *
2013-06-14 14:48:48 +12:00
Leaflet . markercluster , Provides Beautiful Animated Marker Clustering functionality for Leaflet , a JS library for interactive maps .
https : //github.com/Leaflet/Leaflet.markercluster
( c ) 2012 - 2013 , Dave Leaver , smartrak
2012-07-25 10:53:38 +12:00
* /
2014-01-20 10:38:19 +13:00
( function ( window , document , undefined ) { / *
2012-07-25 10:53:38 +12:00
* L . MarkerClusterGroup extends L . FeatureGroup by clustering the markers contained within
* /
L . MarkerClusterGroup = L . FeatureGroup . extend ( {
options : {
2012-08-13 10:40:04 +12:00
maxClusterRadius : 80 , //A cluster will cover at most this many pixels from its center
2012-07-26 11:18:12 +12:00
iconCreateFunction : null ,
spiderfyOnMaxZoom : true ,
showCoverageOnHover : true ,
2012-08-20 10:19:40 +12:00
zoomToBoundsOnClick : true ,
2012-09-03 09:24:03 +12:00
singleMarkerMode : false ,
2012-08-20 10:19:40 +12:00
2012-09-03 10:35:13 +12:00
disableClusteringAtZoom : null ,
2013-06-21 15:18:32 +12:00
// Setting this to false prevents the removal of any clusters outside of the viewpoint, which
// is the default behaviour for performance reasons.
removeOutsideVisibleBounds : true ,
2013-02-12 09:20:37 +13:00
2012-09-06 16:20:02 +12:00
//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.
2012-09-25 13:41:09 +12:00
animateAddingMarkers : false ,
2012-12-05 11:30:14 +13:00
//Increase to increase the distance away that spiderfied markers appear from the center
spiderfyDistanceMultiplier : 1 ,
2014-01-22 09:26:23 +13:00
// 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
2013-12-20 10:04:11 +13:00
chunkedLoading : false ,
2014-01-22 09:26:23 +13:00
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)
2013-12-20 10:04:11 +13:00
2012-09-25 13:41:09 +12:00
//Options to pass to the L.Polygon constructor
polygonOptions : { }
2012-07-25 10:53:38 +12:00
} ,
initialize : function ( options ) {
L . Util . setOptions ( this , options ) ;
2012-07-26 11:18:12 +12:00
if ( ! this . options . iconCreateFunction ) {
this . options . iconCreateFunction = this . _defaultIconCreateFunction ;
}
2012-07-25 10:53:38 +12:00
2013-06-21 16:19:31 +12:00
this . _featureGroup = L . featureGroup ( ) ;
this . _featureGroup . on ( L . FeatureGroup . EVENTS , this . _propagateEvent , this ) ;
2012-07-25 10:53:38 +12:00
2013-06-22 10:47:22 +12:00
this . _nonPointGroup = L . featureGroup ( ) ;
this . _nonPointGroup . on ( L . FeatureGroup . EVENTS , this . _propagateEvent , this ) ;
2012-07-25 10:53:38 +12:00
this . _inZoomAnimation = 0 ;
this . _needsClustering = [ ] ;
2013-04-26 09:59:28 +12:00
this . _needsRemoving = [ ] ; //Markers removed while we aren't on the map need to be kept track of
2012-07-25 10:53:38 +12:00
//The bounds of the currently shown area (from _getExpandedVisibleBounds) Updated on zoom/move
this . _currentShownBounds = null ;
2013-12-19 16:27:52 +13:00
this . _queue = [ ] ;
2012-07-25 10:53:38 +12:00
} ,
2012-07-26 11:18:12 +12:00
addLayer : function ( layer ) {
2012-08-27 13:03:21 +12:00
2012-09-03 10:35:13 +12:00
if ( layer instanceof L . LayerGroup ) {
2012-10-11 10:23:24 +13:00
var array = [ ] ;
2012-08-27 13:03:21 +12:00
for ( var i in layer . _layers ) {
2013-04-24 10:48:09 +12:00
array . push ( layer . _layers [ i ] ) ;
2012-08-27 13:03:21 +12:00
}
2012-10-11 10:23:24 +13:00
return this . addLayers ( array ) ;
2012-08-27 13:03:21 +12:00
}
2013-06-22 10:47:22 +12:00
//Don't cluster non point data
if ( ! layer . getLatLng ) {
this . _nonPointGroup . addLayer ( layer ) ;
return this ;
}
2012-07-26 11:18:12 +12:00
if ( ! this . _map ) {
this . _needsClustering . push ( layer ) ;
return this ;
}
2012-10-11 10:23:24 +13:00
if ( this . hasLayer ( layer ) ) {
2012-09-03 10:35:13 +12:00
return this ;
}
2013-06-22 10:47:22 +12:00
2012-07-26 11:18:12 +12:00
//If we have already clustered we'll need to add this one to a cluster
2012-07-27 14:44:55 +12:00
if ( this . _unspiderfy ) {
this . _unspiderfy ( ) ;
}
2012-09-11 15:33:22 +12:00
this . _addLayer ( layer , this . _maxZoom ) ;
//Work out what is visible
var visibleLayer = layer ,
currentZoom = this . _map . getZoom ( ) ;
if ( layer . _ _parent ) {
while ( visibleLayer . _ _parent . _zoom >= currentZoom ) {
visibleLayer = visibleLayer . _ _parent ;
}
}
2012-07-26 11:18:12 +12:00
2012-09-28 09:37:35 +12:00
if ( this . _currentShownBounds . contains ( visibleLayer . getLatLng ( ) ) ) {
if ( this . options . animateAddingMarkers ) {
this . _animationAddLayer ( layer , visibleLayer ) ;
} else {
this . _animationAddLayerNonAnimated ( layer , visibleLayer ) ;
}
2012-09-06 16:20:02 +12:00
}
2012-07-26 11:18:12 +12:00
return this ;
} ,
removeLayer : function ( layer ) {
2012-09-20 11:29:21 +12:00
2013-08-26 15:39:46 +12:00
if ( layer instanceof L . LayerGroup )
{
var array = [ ] ;
for ( var i in layer . _layers ) {
array . push ( layer . _layers [ i ] ) ;
}
return this . removeLayers ( array ) ;
}
2013-06-22 10:47:22 +12:00
//Non point layers
2013-06-21 16:55:51 +12:00
if ( ! layer . getLatLng ) {
2013-06-22 10:47:22 +12:00
this . _nonPointGroup . removeLayer ( layer ) ;
2013-06-21 16:55:51 +12:00
return this ;
}
2012-09-20 11:29:21 +12:00
if ( ! this . _map ) {
2013-04-26 09:59:28 +12:00
if ( ! this . _arraySplice ( this . _needsClustering , layer ) && this . hasLayer ( layer ) ) {
this . _needsRemoving . push ( layer ) ;
}
2012-09-20 11:29:21 +12:00
return this ;
}
2012-09-12 17:49:59 +12:00
if ( ! layer . _ _parent ) {
return this ;
}
2012-07-27 14:44:55 +12:00
if ( this . _unspiderfy ) {
this . _unspiderfy ( ) ;
2012-07-27 16:00:42 +12:00
this . _unspiderfyLayer ( layer ) ;
2012-07-27 14:44:55 +12:00
}
2012-09-11 15:33:22 +12:00
//Remove the marker from clusters
this . _removeLayer ( layer , true ) ;
2012-07-26 11:18:12 +12:00
2013-06-21 16:19:31 +12:00
if ( this . _featureGroup . hasLayer ( layer ) ) {
this . _featureGroup . removeLayer ( layer ) ;
2013-06-14 17:09:56 +12:00
if ( layer . setOpacity ) {
layer . setOpacity ( 1 ) ;
}
2012-09-11 15:33:22 +12:00
}
2012-10-17 15:16:45 +13:00
2012-07-26 11:18:12 +12:00
return this ;
} ,
2012-10-11 10:23:24 +13:00
//Takes an array of markers and adds them in bulk
addLayers : function ( layersArray ) {
2013-12-20 10:04:11 +13:00
var fg = this . _featureGroup ,
npg = this . _nonPointGroup ,
2014-01-31 09:07:39 +13:00
chunked = this . options . chunkedLoading ,
2014-01-22 09:26:23 +13:00
chunkInterval = this . options . chunkInterval ,
chunkProgress = this . options . chunkProgress ,
2013-12-20 10:04:11 +13:00
newMarkers , i , l , m ;
2012-10-11 10:23:24 +13:00
2013-12-20 10:04:11 +13:00
if ( this . _map ) {
2014-01-22 09:26:23 +13:00
var offset = 0 ,
started = ( new Date ( ) ) . getTime ( ) ;
2013-12-20 10:04:11 +13:00
var process = L . bind ( function ( ) {
2014-01-22 09:26:23 +13:00
var start = ( new Date ( ) ) . getTime ( ) ;
for ( ; offset < layersArray . length ; offset ++ ) {
2014-01-31 09:07:39 +13:00
if ( chunked && offset % 200 === 0 ) {
2014-01-22 09:26:23 +13:00
// 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 :-)
}
}
m = layersArray [ offset ] ;
2013-12-20 10:04:11 +13:00
//Not point data, can't be clustered
if ( ! m . getLatLng ) {
npg . addLayer ( m ) ;
continue ;
}
2012-11-01 09:45:37 +13:00
2013-12-20 10:04:11 +13:00
if ( this . hasLayer ( m ) ) {
continue ;
}
2013-06-21 16:55:51 +12:00
2013-12-20 10:04:11 +13:00
this . _addLayer ( m , this . _maxZoom ) ;
2012-11-01 09:45:37 +13:00
2013-12-20 10:04:11 +13:00
//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 ) ;
}
}
}
2014-01-22 09:26:23 +13:00
if ( chunkProgress ) {
// report progress and time elapsed:
chunkProgress ( offset , layersArray . length , ( new Date ( ) ) . getTime ( ) - started ) ;
}
if ( offset === layersArray . length ) {
2013-12-20 10:04:11 +13:00
//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 {
2014-01-22 09:26:23 +13:00
setTimeout ( process , this . options . chunkDelay ) ;
2013-12-20 10:04:11 +13:00
}
} , this ) ;
2013-06-22 10:47:22 +12:00
2013-12-20 10:04:11 +13:00
process ( ) ;
} else {
newMarkers = [ ] ;
for ( i = 0 , l = layersArray . length ; i < l ; i ++ ) {
m = layersArray [ i ] ;
2012-10-11 10:23:24 +13:00
2013-12-20 10:04:11 +13:00
//Not point data, can't be clustered
if ( ! m . getLatLng ) {
npg . addLayer ( m ) ;
continue ;
2012-10-11 10:23:24 +13:00
}
2013-01-09 11:28:49 +13:00
2013-12-20 10:04:11 +13:00
if ( this . hasLayer ( m ) ) {
continue ;
2013-06-22 10:47:22 +12:00
}
2013-01-09 11:28:49 +13:00
2013-12-20 10:04:11 +13:00
newMarkers . push ( m ) ;
}
this . _needsClustering = this . _needsClustering . concat ( newMarkers ) ;
2013-06-22 10:47:22 +12:00
}
2012-10-11 10:23:24 +13:00
return this ;
} ,
//Takes an array of markers and removes them in bulk
removeLayers : function ( layersArray ) {
2013-06-21 16:55:51 +12:00
var i , l , m ,
2013-06-22 10:47:22 +12:00
fg = this . _featureGroup ,
npg = this . _nonPointGroup ;
2012-10-11 10:23:24 +13:00
if ( ! this . _map ) {
for ( i = 0 , l = layersArray . length ; i < l ; i ++ ) {
2013-06-22 10:47:22 +12:00
m = layersArray [ i ] ;
this . _arraySplice ( this . _needsClustering , m ) ;
npg . removeLayer ( m ) ;
2012-10-11 10:23:24 +13:00
}
return this ;
}
for ( i = 0 , l = layersArray . length ; i < l ; i ++ ) {
m = layersArray [ i ] ;
2012-11-14 09:58:30 +13:00
if ( ! m . _ _parent ) {
2013-06-22 10:47:22 +12:00
npg . removeLayer ( m ) ;
2012-11-14 09:58:30 +13:00
continue ;
}
2012-10-11 10:23:24 +13:00
this . _removeLayer ( m , true , true ) ;
2013-06-21 16:55:51 +12:00
if ( fg . hasLayer ( m ) ) {
fg . removeLayer ( m ) ;
2013-06-21 16:19:31 +12:00
if ( m . setOpacity ) {
m . setOpacity ( 1 ) ;
}
2012-10-11 10:23:24 +13:00
}
}
//Fix up the clusters and markers on the map
this . _topClusterLevel . _recursivelyAddChildrenToMap ( null , this . _zoom , this . _currentShownBounds ) ;
2013-06-21 16:55:51 +12:00
fg . eachLayer ( function ( c ) {
if ( c instanceof L . MarkerCluster ) {
c . _updateIcon ( ) ;
2012-10-11 10:23:24 +13:00
}
2013-06-21 16:55:51 +12:00
} ) ;
2012-10-11 10:23:24 +13:00
return this ;
} ,
//Removes all layers from the MarkerClusterGroup
2012-09-11 15:33:22 +12:00
clearLayers : function ( ) {
//Need our own special implementation as the LayerGroup one doesn't work for us
2012-10-19 10:22:58 +13:00
//If we aren't on the map (yet), blow away the markers we know of
2012-08-07 11:32:18 +12:00
if ( ! this . _map ) {
this . _needsClustering = [ ] ;
2012-10-19 10:22:58 +13:00
delete this . _gridClusters ;
delete this . _gridUnclustered ;
2012-08-07 11:32:18 +12:00
}
2013-04-24 10:48:09 +12:00
if ( this . _noanimationUnspiderfy ) {
this . _noanimationUnspiderfy ( ) ;
2012-09-11 15:33:22 +12:00
}
2012-08-02 10:16:22 +12:00
//Remove all the visible layers
2013-06-21 16:19:31 +12:00
this . _featureGroup . clearLayers ( ) ;
2013-06-22 10:47:22 +12:00
this . _nonPointGroup . clearLayers ( ) ;
2012-08-02 10:16:22 +12:00
2012-12-06 13:42:41 +13:00
this . eachLayer ( function ( marker ) {
delete marker . _ _parent ;
} ) ;
2012-10-17 12:08:22 +01:00
if ( this . _map ) {
//Reset _topClusterLevel and the DistanceGrids
this . _generateInitialClusters ( ) ;
}
2012-08-02 10:16:22 +12:00
return this ;
} ,
2012-10-19 10:08:35 +13:00
//Override FeatureGroup.getBounds as it doesn't work
getBounds : function ( ) {
var bounds = new L . LatLngBounds ( ) ;
2014-03-15 10:56:03 +13:00
2012-10-19 10:08:35 +13:00
if ( this . _topClusterLevel ) {
bounds . extend ( this . _topClusterLevel . _bounds ) ;
2014-03-15 10:56:03 +13:00
}
for ( var i = this . _needsClustering . length - 1 ; i >= 0 ; i -- ) {
bounds . extend ( this . _needsClustering [ i ] . getLatLng ( ) ) ;
2012-10-19 10:08:35 +13:00
}
2013-06-22 10:47:22 +12:00
2013-10-29 09:05:47 +13:00
bounds . extend ( this . _nonPointGroup . getBounds ( ) ) ;
2013-06-22 10:47:22 +12:00
2012-10-19 10:08:35 +13:00
return bounds ;
} ,
2012-12-06 13:42:41 +13:00
//Overrides LayerGroup.eachLayer
2012-12-06 13:23:00 +13:00
eachLayer : function ( method , context ) {
2012-12-06 09:47:51 +13:00
var markers = this . _needsClustering . slice ( ) ,
2014-01-22 09:26:23 +13:00
i ;
2012-12-06 09:47:51 +13:00
if ( this . _topClusterLevel ) {
this . _topClusterLevel . getAllChildMarkers ( markers ) ;
}
for ( i = markers . length - 1 ; i >= 0 ; i -- ) {
method . call ( context , markers [ i ] ) ;
}
2013-06-22 10:47:22 +12:00
this . _nonPointGroup . eachLayer ( method , context ) ;
2012-12-06 09:47:51 +13:00
} ,
2013-10-29 09:05:47 +13:00
//Overrides LayerGroup.getLayers
getLayers : function ( ) {
var layers = [ ] ;
this . eachLayer ( function ( l ) {
layers . push ( l ) ;
} ) ;
return layers ;
} ,
2013-12-18 13:47:05 +13:00
//Overrides LayerGroup.getLayer, WARNING: Really bad performance
getLayer : function ( id ) {
var result = null ;
this . eachLayer ( function ( l ) {
if ( L . stamp ( l ) === id ) {
result = l ;
}
} ) ;
return result ;
} ,
2012-10-11 10:23:24 +13:00
//Returns true if the given layer is in this MarkerClusterGroup
2012-09-03 10:35:13 +12:00
hasLayer : function ( layer ) {
2013-06-21 16:19:31 +12:00
if ( ! layer ) {
2013-04-22 09:31:28 +12:00
return false ;
}
2013-04-26 09:59:28 +12:00
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 ) {
return false ;
2012-10-11 10:23:24 +13:00
}
}
2013-06-22 10:47:22 +12:00
return ! ! ( layer . _ _parent && layer . _ _parent . _group === this ) || this . _nonPointGroup . hasLayer ( layer ) ;
2012-09-03 10:35:13 +12:00
} ,
2012-10-11 10:23:24 +13:00
//Zoom down to show the given layer (spiderfying if necessary) then calls the callback
2012-09-11 16:24:40 +12:00
zoomToShowLayer : function ( layer , callback ) {
2012-09-12 11:17:34 +12:00
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 ) {
2012-09-11 16:24:40 +12:00
callback ( ) ;
2012-09-12 11:17:34 +12:00
} else if ( layer . _ _parent . _icon ) {
var afterSpiderfy = function ( ) {
this . off ( 'spiderfied' , afterSpiderfy , this ) ;
callback ( ) ;
} ;
this . on ( 'spiderfied' , afterSpiderfy , this ) ;
layer . _ _parent . spiderfy ( ) ;
}
2012-09-11 16:24:40 +12:00
}
2012-09-12 11:17:34 +12:00
} ;
2013-12-19 16:27:52 +13:00
if ( layer . _icon && this . _map . getBounds ( ) . contains ( layer . getLatLng ( ) ) ) {
2014-05-28 11:01:36 +12:00
//Layer is visible ond on screen, immediate return
2012-09-20 11:29:21 +12:00
callback ( ) ;
} else if ( layer . _ _parent . _zoom < this . _map . getZoom ( ) ) {
2014-05-28 11:01:36 +12:00
//Layer should be visible at this zoom level. It must not be on screen so just pan over to it
2012-09-20 11:29:21 +12:00
this . _map . on ( 'moveend' , showMarker , this ) ;
2013-12-19 16:27:52 +13:00
this . _map . panTo ( layer . getLatLng ( ) ) ;
2012-09-12 11:17:34 +12:00
} else {
2014-05-28 11:01:36 +12:00
var moveStart = function ( ) {
this . _map . off ( 'movestart' , moveStart , this ) ;
moveStart = null ;
} ;
this . _map . on ( 'movestart' , moveStart , this ) ;
2012-09-12 11:17:34 +12:00
this . _map . on ( 'moveend' , showMarker , this ) ;
this . on ( 'animationend' , showMarker , this ) ;
layer . _ _parent . zoomToBounds ( ) ;
2014-05-28 11:01:36 +12:00
if ( moveStart ) {
//Never started moving, must already be there, probably need clustering however
showMarker . call ( this ) ;
}
2012-09-12 11:17:34 +12:00
}
2012-09-11 16:24:40 +12:00
} ,
2012-07-26 11:18:12 +12:00
//Overrides FeatureGroup.onAdd
onAdd : function ( map ) {
2012-12-06 13:23:00 +13:00
this . _map = map ;
2013-04-26 09:59:28 +12:00
var i , l , layer ;
2012-07-26 11:18:12 +12:00
2013-07-05 11:28:41 +12:00
if ( ! isFinite ( this . _map . getMaxZoom ( ) ) ) {
throw "Map has no maxZoom specified" ;
}
2013-06-21 16:19:31 +12:00
this . _featureGroup . onAdd ( map ) ;
2013-06-22 10:47:22 +12:00
this . _nonPointGroup . onAdd ( map ) ;
2013-06-21 16:19:31 +12:00
2012-09-11 15:33:22 +12:00
if ( ! this . _gridClusters ) {
2012-07-30 11:59:18 +12:00
this . _generateInitialClusters ( ) ;
}
2012-08-02 10:16:22 +12:00
2013-04-26 09:59:28 +12:00
for ( i = 0 , l = this . _needsRemoving . length ; i < l ; i ++ ) {
layer = this . _needsRemoving [ i ] ;
2013-07-18 11:24:33 +12:00
this . _removeLayer ( layer , true ) ;
2013-04-26 09:59:28 +12:00
}
this . _needsRemoving = [ ] ;
2013-12-20 10:04:11 +13:00
//Remember the current zoom level and bounds
this . _zoom = this . _map . getZoom ( ) ;
this . _currentShownBounds = this . _getExpandedVisibleBounds ( ) ;
2013-04-26 09:59:28 +12:00
2012-07-26 11:18:12 +12:00
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 ( ) ;
}
this . _bindEvents ( ) ;
2012-09-11 15:33:22 +12:00
//Actually add our markers to the map:
2013-12-20 10:04:11 +13:00
l = this . _needsClustering ;
this . _needsClustering = [ ] ;
this . addLayers ( l ) ;
2012-07-26 11:18:12 +12:00
} ,
2012-07-30 11:59:18 +12:00
//Overrides FeatureGroup.onRemove
onRemove : function ( map ) {
2013-04-15 10:12:00 +12:00
map . off ( 'zoomend' , this . _zoomEnd , this ) ;
map . off ( 'moveend' , this . _moveEnd , this ) ;
2012-07-30 11:59:18 +12:00
2012-11-22 17:05:54 +13:00
this . _unbindEvents ( ) ;
2012-08-27 13:33:03 +12:00
//In case we are in a cluster animation
this . _map . _mapPane . className = this . _map . _mapPane . className . replace ( ' leaflet-cluster-anim' , '' ) ;
2012-07-30 11:59:18 +12:00
if ( this . _spiderfierOnRemove ) { //TODO FIXME: Not sure how to have spiderfier add something on here nicely
this . _spiderfierOnRemove ( ) ;
}
2013-10-29 09:05:47 +13:00
2012-11-22 17:05:54 +13:00
//Clean up all the layers we added to the map
2013-10-29 09:05:47 +13:00
this . _hideCoverage ( ) ;
2013-06-21 16:19:31 +12:00
this . _featureGroup . onRemove ( map ) ;
2013-06-22 10:47:22 +12:00
this . _nonPointGroup . onRemove ( map ) ;
2012-12-21 15:01:23 +13:00
2013-07-10 11:05:07 +12:00
this . _featureGroup . clearLayers ( ) ;
2012-12-21 15:01:23 +13:00
this . _map = null ;
2012-07-30 11:59:18 +12:00
} ,
2013-06-21 15:18:32 +12:00
getVisibleParent : function ( marker ) {
var vMarker = marker ;
2013-10-29 09:05:47 +13:00
while ( vMarker && ! vMarker . _icon ) {
2013-06-21 15:18:32 +12:00
vMarker = vMarker . _ _parent ;
}
2013-10-29 09:05:47 +13:00
return vMarker || null ;
2013-06-21 15:18:32 +12:00
} ,
2012-09-11 16:24:40 +12:00
//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 ) ;
2013-04-26 09:59:28 +12:00
return true ;
2012-09-11 16:24:40 +12:00
}
}
} ,
2012-10-11 10:23:24 +13:00
//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 ) {
2012-09-11 16:24:40 +12:00
var gridClusters = this . _gridClusters ,
gridUnclustered = this . _gridUnclustered ,
2013-06-21 16:19:31 +12:00
fg = this . _featureGroup ,
2012-09-11 16:24:40 +12:00
map = this . _map ;
//Remove the marker from distance clusters it might be in
if ( removeFromDistanceGrid ) {
for ( var z = this . _maxZoom ; z >= 0 ; z -- ) {
if ( ! gridUnclustered [ z ] . removeObject ( marker , map . project ( marker . getLatLng ( ) , z ) ) ) {
break ;
}
}
}
//Work our way up the clusters removing them as we go if required
var cluster = marker . _ _parent ,
markers = cluster . _markers ,
otherMarker ;
//Remove the marker from the immediate parents marker list
this . _arraySplice ( markers , marker ) ;
while ( cluster ) {
cluster . _childCount -- ;
if ( cluster . _zoom < 0 ) {
//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
2013-06-21 16:19:31 +12:00
fg . removeLayer ( cluster ) ;
2012-10-11 10:23:24 +13:00
if ( ! dontUpdateMap ) {
2013-06-21 16:19:31 +12:00
fg . addLayer ( otherMarker ) ;
2012-10-11 10:23:24 +13:00
}
2012-09-11 16:24:40 +12:00
}
} else {
cluster . _recalculateBounds ( ) ;
2012-10-11 10:23:24 +13:00
if ( ! dontUpdateMap || ! cluster . _icon ) {
cluster . _updateIcon ( ) ;
}
2012-09-11 16:24:40 +12:00
}
cluster = cluster . _ _parent ;
}
2012-11-01 09:45:37 +13:00
delete marker . _ _parent ;
2012-09-11 16:24:40 +12:00
} ,
2013-12-18 13:47:05 +13:00
_isOrIsParent : function ( el , oel ) {
while ( oel ) {
if ( el === oel ) {
return true ;
}
oel = oel . parentNode ;
}
return false ;
} ,
2012-07-25 10:53:38 +12:00
_propagateEvent : function ( e ) {
2013-06-21 16:19:31 +12:00
if ( e . layer instanceof L . MarkerCluster ) {
2013-12-18 13:47:05 +13:00
//Prevent multiple clustermouseover/off events if the icon is made up of stacked divs (Doesn't work in ie <= 8, no relatedTarget)
if ( e . originalEvent && this . _isOrIsParent ( e . layer . _icon , e . originalEvent . relatedTarget ) ) {
return ;
}
2012-07-25 10:53:38 +12:00
e . type = 'cluster' + e . type ;
}
2013-06-21 16:19:31 +12:00
this . fire ( e . type , e ) ;
2012-07-25 10:53:38 +12:00
} ,
2012-07-26 11:18:12 +12:00
//Default functionality
2012-08-30 11:45:52 +12:00
_defaultIconCreateFunction : function ( cluster ) {
var childCount = cluster . getChildCount ( ) ;
2012-07-26 11:18:12 +12:00
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 ( ) {
2013-06-24 10:23:59 +12:00
var map = this . _map ,
spiderfyOnMaxZoom = this . options . spiderfyOnMaxZoom ,
showCoverageOnHover = this . options . showCoverageOnHover ,
zoomToBoundsOnClick = this . options . zoomToBoundsOnClick ;
2012-07-26 11:18:12 +12:00
//Zoom on cluster click or spiderfy if we are at the lowest level
if ( spiderfyOnMaxZoom || zoomToBoundsOnClick ) {
2013-06-24 10:23:59 +12:00
this . on ( 'clusterclick' , this . _zoomOrSpiderfy , this ) ;
2012-07-26 11:18:12 +12:00
}
//Show convex hull (boundary) polygon on mouse over
if ( showCoverageOnHover ) {
2013-06-24 10:23:59 +12:00
this . on ( 'clustermouseover' , this . _showCoverage , this ) ;
this . on ( 'clustermouseout' , this . _hideCoverage , this ) ;
map . on ( 'zoomend' , this . _hideCoverage , this ) ;
}
} ,
_zoomOrSpiderfy : function ( e ) {
var map = this . _map ;
if ( map . getMaxZoom ( ) === map . getZoom ( ) ) {
if ( this . options . spiderfyOnMaxZoom ) {
e . layer . spiderfy ( ) ;
}
} else if ( this . options . zoomToBoundsOnClick ) {
e . layer . zoomToBounds ( ) ;
}
2013-12-18 13:47:05 +13:00
2013-12-20 10:04:11 +13:00
// Focus the map again for keyboard users.
2013-12-18 13:47:05 +13:00
if ( e . originalEvent && e . originalEvent . keyCode === 13 ) {
map . _container . focus ( ) ;
}
2013-06-24 10:23:59 +12:00
} ,
_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 ;
}
} ,
2012-11-22 17:05:54 +13:00
_unbindEvents : function ( ) {
var spiderfyOnMaxZoom = this . options . spiderfyOnMaxZoom ,
showCoverageOnHover = this . options . showCoverageOnHover ,
zoomToBoundsOnClick = this . options . zoomToBoundsOnClick ,
map = this . _map ;
if ( spiderfyOnMaxZoom || zoomToBoundsOnClick ) {
2013-06-24 10:23:59 +12:00
this . off ( 'clusterclick' , this . _zoomOrSpiderfy , this ) ;
2012-11-22 17:05:54 +13:00
}
if ( showCoverageOnHover ) {
2013-06-24 10:23:59 +12:00
this . off ( 'clustermouseover' , this . _showCoverage , this ) ;
this . off ( 'clustermouseout' , this . _hideCoverage , this ) ;
map . off ( 'zoomend' , this . _hideCoverage , this ) ;
2012-11-22 17:05:54 +13:00
}
} ,
2012-07-25 10:53:38 +12:00
_zoomEnd : function ( ) {
2012-08-27 13:55:55 +12:00
if ( ! this . _map ) { //May have been removed from the map by a zoomEnd handler
return ;
}
2012-07-25 10:53:38 +12:00
this . _mergeSplitClusters ( ) ;
this . _zoom = this . _map . _zoom ;
this . _currentShownBounds = this . _getExpandedVisibleBounds ( ) ;
} ,
_moveEnd : function ( ) {
2012-07-26 12:03:02 +12:00
if ( this . _inZoomAnimation ) {
2012-07-25 10:53:38 +12:00
return ;
}
2012-09-11 15:33:22 +12:00
var newBounds = this . _getExpandedVisibleBounds ( ) ;
2012-07-25 10:53:38 +12:00
2012-09-11 15:33:22 +12:00
this . _topClusterLevel . _recursivelyRemoveChildrenFromMap ( this . _currentShownBounds , this . _zoom , newBounds ) ;
2013-12-06 13:41:34 +13:00
this . _topClusterLevel . _recursivelyAddChildrenToMap ( null , this . _map . _zoom , newBounds ) ;
2012-07-25 10:53:38 +12:00
this . _currentShownBounds = newBounds ;
return ;
} ,
_generateInitialClusters : function ( ) {
2012-09-11 15:33:22 +12:00
var maxZoom = this . _map . getMaxZoom ( ) ,
2014-01-20 10:38:19 +13:00
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 ; } ;
}
2012-07-25 10:53:38 +12:00
2012-08-20 10:19:40 +12:00
if ( this . options . disableClusteringAtZoom ) {
maxZoom = this . options . disableClusteringAtZoom - 1 ;
}
2012-09-11 15:33:22 +12:00
this . _maxZoom = maxZoom ;
this . _gridClusters = { } ;
this . _gridUnclustered = { } ;
2014-01-20 10:38:19 +13:00
2012-09-11 15:33:22 +12:00
//Set up DistanceGrids for each zoom
for ( var zoom = maxZoom ; zoom >= 0 ; zoom -- ) {
2014-01-20 10:38:19 +13:00
this . _gridClusters [ zoom ] = new L . DistanceGrid ( radiusFn ( zoom ) ) ;
this . _gridUnclustered [ zoom ] = new L . DistanceGrid ( radiusFn ( zoom ) ) ;
2012-09-11 15:33:22 +12:00
}
2012-08-20 10:19:40 +12:00
2012-09-11 15:33:22 +12:00
this . _topClusterLevel = new L . MarkerCluster ( this , - 1 ) ;
} ,
2012-07-25 10:53:38 +12:00
2012-09-11 15:33:22 +12:00
//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 ,
markerPoint , z ;
2012-10-16 22:17:49 +01:00
if ( this . options . singleMarkerMode ) {
layer . options . icon = this . options . iconCreateFunction ( {
getChildCount : function ( ) {
return 1 ;
} ,
getAllChildMarkers : function ( ) {
return [ layer ] ;
}
} ) ;
}
2012-09-11 15:33:22 +12:00
//Find the lowest zoom level to slot this one in
for ( ; zoom >= 0 ; 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 ;
}
2012-07-25 10:53:38 +12:00
2012-09-11 15:33:22 +12:00
//Try find a marker close by to form a new cluster with
closest = gridUnclustered [ zoom ] . getNearObject ( markerPoint ) ;
if ( closest ) {
2012-11-01 09:45:37 +13:00
var parent = closest . _ _parent ;
if ( parent ) {
2012-09-11 15:33:22 +12:00
this . _removeLayer ( closest , false ) ;
}
2012-07-25 10:53:38 +12:00
2012-09-11 15:33:22 +12:00
//Create new cluster with these 2 in it
var newCluster = new L . 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 L . 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
for ( z = zoom ; z >= 0 ; z -- ) {
if ( ! gridUnclustered [ z ] . removeObject ( closest , this . _map . project ( closest . getLatLng ( ) , z ) ) ) {
break ;
}
}
return ;
}
2013-02-23 21:26:39 -05:00
2012-09-11 15:33:22 +12:00
//Didn't manage to cluster in at this zoom, record us as a marker here and continue upwards
gridUnclustered [ zoom ] . addObject ( layer , markerPoint ) ;
}
2012-09-13 11:03:06 +12:00
//Didn't get in anything, add us to the top
this . _topClusterLevel . _addChild ( layer ) ;
layer . _ _parent = this . _topClusterLevel ;
2012-09-11 15:33:22 +12:00
return ;
2012-07-25 10:53:38 +12:00
} ,
2013-12-19 16:27:52 +13:00
//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 ;
} ,
2012-07-25 10:53:38 +12:00
//Merge and split any existing clusters that are too big or small
_mergeSplitClusters : function ( ) {
2013-12-19 16:27:52 +13:00
//Incase we are starting to split before the animation finished
this . _processQueue ( ) ;
2014-05-28 11:01:36 +12:00
if ( this . _zoom < this . _map . _zoom && this . _currentShownBounds . intersects ( this . _getExpandedVisibleBounds ( ) ) ) { //Zoom in, split
2012-07-26 12:03:02 +12:00
this . _animationStart ( ) ;
2012-07-25 10:53:38 +12:00
//Remove clusters now off screen
2012-09-13 17:21:00 +12:00
this . _topClusterLevel . _recursivelyRemoveChildrenFromMap ( this . _currentShownBounds , this . _zoom , this . _getExpandedVisibleBounds ( ) ) ;
2012-07-25 10:53:38 +12:00
this . _animationZoomIn ( this . _zoom , this . _map . _zoom ) ;
} else if ( this . _zoom > this . _map . _zoom ) { //Zoom out, merge
2012-07-26 12:03:02 +12:00
this . _animationStart ( ) ;
2012-07-25 10:53:38 +12:00
this . _animationZoomOut ( this . _zoom , this . _map . _zoom ) ;
2012-07-26 12:03:02 +12:00
} else {
this . _moveEnd ( ) ;
2012-07-25 10:53:38 +12:00
}
} ,
2013-02-23 21:26:39 -05:00
2012-07-25 10:53:38 +12:00
//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 ( ) {
2013-06-21 15:18:32 +12:00
if ( ! this . options . removeOutsideVisibleBounds ) {
2014-10-28 09:26:13 +13:00
return this . _map . getBounds ( ) ;
2013-06-21 15:18:32 +12:00
}
2013-02-12 09:20:37 +13:00
2012-07-25 10:53:38 +12:00
var map = this . _map ,
2012-10-17 09:49:47 +13:00
bounds = map . getBounds ( ) ,
sw = bounds . _southWest ,
ne = bounds . _northEast ,
latDiff = L . Browser . mobile ? 0 : Math . abs ( sw . lat - ne . lat ) ,
lngDiff = L . Browser . mobile ? 0 : Math . abs ( sw . lng - ne . lng ) ;
return new L . LatLngBounds (
new L . LatLng ( sw . lat - latDiff , sw . lng - lngDiff , true ) ,
new L . LatLng ( ne . lat + latDiff , ne . lng + lngDiff , true ) ) ;
2012-09-06 16:20:02 +12:00
} ,
//Shared animation code
_animationAddLayerNonAnimated : function ( layer , newCluster ) {
2012-09-11 15:33:22 +12:00
if ( newCluster === layer ) {
2013-06-21 16:19:31 +12:00
this . _featureGroup . addLayer ( layer ) ;
2012-09-06 16:20:02 +12:00
} else if ( newCluster . _childCount === 2 ) {
newCluster . _addToMap ( ) ;
2012-09-11 15:33:22 +12:00
var markers = newCluster . getAllChildMarkers ( ) ;
2013-06-21 16:19:31 +12:00
this . _featureGroup . removeLayer ( markers [ 0 ] ) ;
this . _featureGroup . removeLayer ( markers [ 1 ] ) ;
2012-09-11 15:33:22 +12:00
} else {
newCluster . _updateIcon ( ) ;
2012-09-06 16:20:02 +12:00
}
2012-07-25 10:53:38 +12:00
}
} ) ;
L . MarkerClusterGroup . include ( ! L . DomUtil . TRANSITION ? {
//Non Animated versions of everything
_animationStart : function ( ) {
//Do nothing...
} ,
_animationZoomIn : function ( previousZoomLevel , newZoomLevel ) {
2012-09-13 17:21:00 +12:00
this . _topClusterLevel . _recursivelyRemoveChildrenFromMap ( this . _currentShownBounds , previousZoomLevel ) ;
2012-09-11 15:33:22 +12:00
this . _topClusterLevel . _recursivelyAddChildrenToMap ( null , newZoomLevel , this . _getExpandedVisibleBounds ( ) ) ;
2014-01-31 09:07:39 +13:00
//We didn't actually animate, but we use this event to mean "clustering animations have finished"
this . fire ( 'animationend' ) ;
2012-07-25 10:53:38 +12:00
} ,
_animationZoomOut : function ( previousZoomLevel , newZoomLevel ) {
2012-09-13 17:21:00 +12:00
this . _topClusterLevel . _recursivelyRemoveChildrenFromMap ( this . _currentShownBounds , previousZoomLevel ) ;
2012-09-11 15:33:22 +12:00
this . _topClusterLevel . _recursivelyAddChildrenToMap ( null , newZoomLevel , this . _getExpandedVisibleBounds ( ) ) ;
2014-01-31 09:07:39 +13:00
//We didn't actually animate, but we use this event to mean "clustering animations have finished"
this . fire ( 'animationend' ) ;
2012-07-25 10:53:38 +12:00
} ,
_animationAddLayer : function ( layer , newCluster ) {
2012-09-06 16:20:02 +12:00
this . _animationAddLayerNonAnimated ( layer , newCluster ) ;
2012-07-25 10:53:38 +12:00
}
} : {
//Animated versions here
_animationStart : function ( ) {
this . _map . _mapPane . className += ' leaflet-cluster-anim' ;
2012-07-26 12:03:02 +12:00
this . _inZoomAnimation ++ ;
2012-07-25 10:53:38 +12:00
} ,
_animationEnd : function ( ) {
2012-08-27 13:33:03 +12:00
if ( this . _map ) {
this . _map . _mapPane . className = this . _map . _mapPane . className . replace ( ' leaflet-cluster-anim' , '' ) ;
}
2012-07-25 10:53:38 +12:00
this . _inZoomAnimation -- ;
2012-09-12 11:17:34 +12:00
this . fire ( 'animationend' ) ;
2012-07-25 10:53:38 +12:00
} ,
_animationZoomIn : function ( previousZoomLevel , newZoomLevel ) {
2013-12-19 16:27:52 +13:00
var bounds = this . _getExpandedVisibleBounds ( ) ,
fg = this . _featureGroup ,
2012-09-11 15:33:22 +12:00
i ;
2012-07-25 10:53:38 +12:00
//Add all children of current clusters to map and remove those clusters from map
2012-09-11 15:33:22 +12:00
this . _topClusterLevel . _recursively ( bounds , previousZoomLevel , 0 , function ( c ) {
2012-07-25 10:53:38 +12:00
var startPos = c . _latlng ,
markers = c . _markers ,
m ;
2013-07-10 11:05:07 +12:00
if ( ! bounds . contains ( startPos ) ) {
startPos = null ;
}
2012-09-11 15:33:22 +12:00
if ( c . _isSingleParent ( ) && previousZoomLevel + 1 === newZoomLevel ) { //Immediately add the new child and remove us
2013-06-21 16:19:31 +12:00
fg . removeLayer ( c ) ;
2012-09-11 15:33:22 +12:00
c . _recursivelyAddChildrenToMap ( null , newZoomLevel , bounds ) ;
2012-07-25 10:53:38 +12:00
} else {
//Fade out old cluster
c . setOpacity ( 0 ) ;
2012-09-11 15:33:22 +12:00
c . _recursivelyAddChildrenToMap ( startPos , newZoomLevel , bounds ) ;
2012-07-25 10:53:38 +12:00
}
//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 ) ) {
2013-06-21 16:19:31 +12:00
fg . removeLayer ( m ) ;
2012-07-25 10:53:38 +12:00
}
}
} ) ;
2012-07-25 16:44:42 +12:00
this . _forceLayout ( ) ;
2012-07-25 10:53:38 +12:00
2012-07-25 16:44:42 +12:00
//Update opacities
2013-12-19 16:27:52 +13:00
this . _topClusterLevel . _recursivelyBecomeVisible ( bounds , newZoomLevel ) ;
2012-07-25 16:44:42 +12:00
//TODO Maybe? Update markers in _recursivelyBecomeVisible
2013-06-21 16:55:51 +12:00
fg . eachLayer ( function ( n ) {
2013-04-24 10:48:09 +12:00
if ( ! ( n instanceof L . MarkerCluster ) && n . _icon ) {
n . setOpacity ( 1 ) ;
2012-07-25 10:53:38 +12:00
}
2013-06-21 16:55:51 +12:00
} ) ;
2012-07-25 10:53:38 +12:00
2012-07-25 16:44:42 +12:00
//update the positions of the just added clusters/markers
2013-12-19 16:27:52 +13:00
this . _topClusterLevel . _recursively ( bounds , previousZoomLevel , newZoomLevel , function ( c ) {
2012-09-11 15:33:22 +12:00
c . _recursivelyRestoreChildPositions ( newZoomLevel ) ;
2012-07-25 16:44:42 +12:00
} ) ;
2012-07-25 10:53:38 +12:00
//Remove the old clusters and close the zoom animation
2013-12-19 16:27:52 +13:00
this . _enqueue ( function ( ) {
2012-07-25 10:53:38 +12:00
//update the positions of the just added clusters/markers
2013-12-19 16:27:52 +13:00
this . _topClusterLevel . _recursively ( bounds , previousZoomLevel , 0 , function ( c ) {
2013-06-21 16:19:31 +12:00
fg . removeLayer ( c ) ;
2012-09-13 17:21:00 +12:00
c . setOpacity ( 1 ) ;
2012-07-25 10:53:38 +12:00
} ) ;
2013-12-19 16:27:52 +13:00
this . _animationEnd ( ) ;
} ) ;
2012-07-25 10:53:38 +12:00
} ,
_animationZoomOut : function ( previousZoomLevel , newZoomLevel ) {
2012-09-13 11:03:06 +12:00
this . _animationZoomOutSingle ( this . _topClusterLevel , previousZoomLevel - 1 , newZoomLevel ) ;
2012-07-25 10:53:38 +12:00
//Need to add markers for those that weren't on the map before but are now
2012-09-11 15:33:22 +12:00
this . _topClusterLevel . _recursivelyAddChildrenToMap ( null , newZoomLevel , this . _getExpandedVisibleBounds ( ) ) ;
2012-09-20 11:29:21 +12:00
//Remove markers that were on the map before but won't be now
this . _topClusterLevel . _recursivelyRemoveChildrenFromMap ( this . _currentShownBounds , previousZoomLevel , this . _getExpandedVisibleBounds ( ) ) ;
2012-07-25 10:53:38 +12:00
} ,
2012-09-13 11:03:06 +12:00
_animationZoomOutSingle : function ( cluster , previousZoomLevel , newZoomLevel ) {
2012-07-25 10:53:38 +12:00
var bounds = this . _getExpandedVisibleBounds ( ) ;
//Animate all of the markers in the clusters to move to their cluster center point
2012-09-13 17:21:00 +12:00
cluster . _recursivelyAnimateChildrenInAndAddSelfToMap ( bounds , previousZoomLevel + 1 , newZoomLevel ) ;
2012-07-25 10:53:38 +12:00
var me = this ;
2012-07-25 16:44:42 +12:00
//Update the opacity (If we immediately set it they won't animate)
this . _forceLayout ( ) ;
2012-09-13 11:03:06 +12:00
cluster . _recursivelyBecomeVisible ( bounds , newZoomLevel ) ;
2012-07-25 10:53:38 +12:00
//TODO: Maybe use the transition timing stuff to make this more reliable
//When the animations are done, tidy up
2013-12-19 16:27:52 +13:00
this . _enqueue ( function ( ) {
2012-07-25 10:53:38 +12:00
2012-09-14 10:43:17 +12:00
//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
m . setLatLng ( m . getLatLng ( ) ) ;
2014-01-08 10:25:33 +13:00
if ( m . setOpacity ) {
m . setOpacity ( 1 ) ;
}
2013-04-10 09:05:51 +12:00
} else {
cluster . _recursively ( bounds , newZoomLevel , 0 , function ( c ) {
c . _recursivelyRemoveChildrenFromMap ( bounds , previousZoomLevel + 1 ) ;
} ) ;
2012-09-14 10:43:17 +12:00
}
2012-07-25 10:53:38 +12:00
me . _animationEnd ( ) ;
2013-12-19 16:27:52 +13:00
} ) ;
2012-07-25 10:53:38 +12:00
} ,
_animationAddLayer : function ( layer , newCluster ) {
2013-06-21 16:19:31 +12:00
var me = this ,
fg = this . _featureGroup ;
2012-07-25 10:53:38 +12:00
2013-06-21 16:19:31 +12:00
fg . addLayer ( layer ) ;
2012-09-11 15:33:22 +12:00
if ( newCluster !== layer ) {
2012-07-25 10:53:38 +12:00
if ( newCluster . _childCount > 2 ) { //Was already a cluster
2012-09-11 15:33:22 +12:00
newCluster . _updateIcon ( ) ;
2012-07-25 16:44:42 +12:00
this . _forceLayout ( ) ;
2012-07-25 10:53:38 +12:00
this . _animationStart ( ) ;
2012-08-01 15:17:51 +12:00
layer . _setPos ( this . _map . latLngToLayerPoint ( newCluster . getLatLng ( ) ) ) ;
2012-07-25 16:44:42 +12:00
layer . setOpacity ( 0 ) ;
2012-07-25 10:53:38 +12:00
2013-12-19 16:27:52 +13:00
this . _enqueue ( function ( ) {
2013-06-21 16:19:31 +12:00
fg . removeLayer ( layer ) ;
2012-07-27 16:00:42 +12:00
layer . setOpacity ( 1 ) ;
2012-07-25 10:53:38 +12:00
2012-07-25 16:44:42 +12:00
me . _animationEnd ( ) ;
2013-12-19 16:27:52 +13:00
} ) ;
2012-07-25 10:53:38 +12:00
} else { //Just became a cluster
2012-07-25 16:44:42 +12:00
this . _forceLayout ( ) ;
me . _animationStart ( ) ;
2012-09-11 15:33:22 +12:00
me . _animationZoomOutSingle ( newCluster , this . _map . getMaxZoom ( ) , this . _map . getZoom ( ) ) ;
2012-07-25 10:53:38 +12:00
}
}
2012-07-25 16:44:42 +12:00
} ,
//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 ) ;
2012-07-25 10:53:38 +12:00
}
} ) ;
2013-06-14 17:09:56 +12:00
L . markerClusterGroup = function ( options ) {
return new L . MarkerClusterGroup ( options ) ;
} ;
2012-08-10 15:58:57 +12:00
2013-06-21 15:18:32 +12:00
2012-07-25 10:53:38 +12:00
L . MarkerCluster = L . Marker . extend ( {
2012-09-11 15:33:22 +12:00
initialize : function ( group , zoom , a , b ) {
L . Marker . prototype . initialize . call ( this , a ? ( a . _cLatLng || a . getLatLng ( ) ) : new L . LatLng ( 0 , 0 ) , { icon : this } ) ;
2012-07-25 10:53:38 +12:00
this . _group = group ;
2012-09-11 15:33:22 +12:00
this . _zoom = zoom ;
2012-07-25 10:53:38 +12:00
this . _markers = [ ] ;
this . _childClusters = [ ] ;
this . _childCount = 0 ;
2012-09-11 15:33:22 +12:00
this . _iconNeedsUpdate = true ;
2012-07-25 10:53:38 +12:00
this . _bounds = new L . LatLngBounds ( ) ;
2012-07-30 11:12:54 +12:00
if ( a ) {
this . _addChild ( a ) ;
}
2012-07-25 10:53:38 +12:00
if ( b ) {
this . _addChild ( b ) ;
}
} ,
//Recursively retrieve all child markers of this cluster
getAllChildMarkers : function ( storageArray ) {
storageArray = storageArray || [ ] ;
for ( var i = this . _childClusters . length - 1 ; i >= 0 ; i -- ) {
this . _childClusters [ i ] . getAllChildMarkers ( storageArray ) ;
}
for ( var j = this . _markers . length - 1 ; j >= 0 ; j -- ) {
storageArray . push ( this . _markers [ j ] ) ;
}
return storageArray ;
} ,
//Returns the count of how many child markers we have
getChildCount : function ( ) {
return this . _childCount ;
} ,
2013-11-14 09:40:33 +13:00
//Zoom to the minimum of showing all of the child markers, or the extents of this cluster
2012-07-25 10:53:38 +12:00
zoomToBounds : function ( ) {
2013-11-14 09:40:33 +13:00
var childClusters = this . _childClusters . slice ( ) ,
map = this . _group . _map ,
boundsZoom = map . getBoundsZoom ( this . _bounds ) ,
zoom = this . _zoom + 1 ,
2013-12-18 13:47:05 +13:00
mapZoom = map . getZoom ( ) ,
2013-11-14 09:40:33 +13:00
i ;
2014-05-28 11:01:36 +12:00
//calculate how far we need to zoom down to see all of the markers
2013-11-14 09:40:33 +13:00
while ( childClusters . length > 0 && boundsZoom > zoom ) {
zoom ++ ;
var newClusters = [ ] ;
for ( i = 0 ; i < childClusters . length ; i ++ ) {
newClusters = newClusters . concat ( childClusters [ i ] . _childClusters ) ;
}
childClusters = newClusters ;
}
if ( boundsZoom > zoom ) {
this . _group . _map . setView ( this . _latlng , zoom ) ;
2013-12-18 13:47:05 +13:00
} else if ( boundsZoom <= mapZoom ) { //If fitBounds wouldn't zoom us down, zoom us down instead
this . _group . _map . setView ( this . _latlng , mapZoom + 1 ) ;
2013-11-14 09:40:33 +13:00
} else {
this . _group . _map . fitBounds ( this . _bounds ) ;
}
2012-07-25 10:53:38 +12:00
} ,
2013-04-10 09:05:51 +12:00
getBounds : function ( ) {
var bounds = new L . LatLngBounds ( ) ;
bounds . extend ( this . _bounds ) ;
return bounds ;
} ,
2012-09-11 15:33:22 +12:00
_updateIcon : function ( ) {
this . _iconNeedsUpdate = true ;
if ( this . _icon ) {
this . setIcon ( this ) ;
}
} ,
//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 ( ) ;
2012-07-25 10:53:38 +12:00
} ,
2012-09-11 15:33:22 +12:00
createShadow : function ( ) {
return this . _iconObj . createShadow ( ) ;
} ,
_addChild : function ( new1 , isNotificationFromChild ) {
2012-08-10 16:05:57 +12:00
2012-09-11 15:33:22 +12:00
this . _iconNeedsUpdate = true ;
2012-08-10 16:05:57 +12:00
this . _expandBounds ( new1 ) ;
2012-09-11 15:33:22 +12:00
2012-07-25 10:53:38 +12:00
if ( new1 instanceof L . MarkerCluster ) {
2012-09-11 15:33:22 +12:00
if ( ! isNotificationFromChild ) {
this . _childClusters . push ( new1 ) ;
new1 . _ _parent = this ;
}
2012-07-25 10:53:38 +12:00
this . _childCount += new1 . _childCount ;
} else {
2012-09-11 15:33:22 +12:00
if ( ! isNotificationFromChild ) {
this . _markers . push ( new1 ) ;
}
2012-07-25 10:53:38 +12:00
this . _childCount ++ ;
}
2012-09-11 15:33:22 +12:00
if ( this . _ _parent ) {
this . _ _parent . _addChild ( new1 , true ) ;
2012-07-25 10:53:38 +12:00
}
} ,
2012-09-11 15:33:22 +12:00
//Expand our bounds and tell our parent to
2012-07-25 10:53:38 +12:00
_expandBounds : function ( marker ) {
2012-08-10 16:05:57 +12:00
var addedCount ,
2012-09-11 15:33:22 +12:00
addedLatLng = marker . _wLatLng || marker . _latlng ;
2012-08-10 16:05:57 +12:00
2012-07-25 10:53:38 +12:00
if ( marker instanceof L . MarkerCluster ) {
this . _bounds . extend ( marker . _bounds ) ;
2012-08-10 16:05:57 +12:00
addedCount = marker . _childCount ;
2012-07-25 10:53:38 +12:00
} else {
2012-08-10 16:05:57 +12:00
this . _bounds . extend ( addedLatLng ) ;
addedCount = 1 ;
2012-07-25 10:53:38 +12:00
}
2012-09-11 15:33:22 +12:00
if ( ! this . _cLatLng ) {
2012-08-13 10:40:04 +12:00
// when clustering, take position of the first point as the cluster center
2012-09-11 15:33:22 +12:00
this . _cLatLng = marker . _cLatLng || addedLatLng ;
2012-08-13 10:40:04 +12:00
}
// when showing clusters, take weighted average of all points as cluster center
2012-08-10 16:05:57 +12:00
var totalCount = this . _childCount + addedCount ;
2012-08-13 10:40:04 +12:00
//Calculate weighted latlng for display
if ( ! this . _wLatLng ) {
2012-09-11 15:33:22 +12:00
this . _latlng = this . _wLatLng = new L . LatLng ( addedLatLng . lat , addedLatLng . lng ) ;
2012-08-10 16:05:57 +12:00
} else {
2012-08-13 10:40:04 +12:00
this . _wLatLng . lat = ( addedLatLng . lat * addedCount + this . _wLatLng . lat * this . _childCount ) / totalCount ;
this . _wLatLng . lng = ( addedLatLng . lng * addedCount + this . _wLatLng . lng * this . _childCount ) / totalCount ;
2012-08-10 16:05:57 +12:00
}
2012-07-25 10:53:38 +12:00
} ,
//Set our markers position as given and add it to the map
_addToMap : function ( startPos ) {
if ( startPos ) {
this . _backupLatlng = this . _latlng ;
this . setLatLng ( startPos ) ;
}
2013-06-21 16:19:31 +12:00
this . _group . _featureGroup . addLayer ( this ) ;
2012-07-25 10:53:38 +12:00
} ,
2013-02-23 21:26:39 -05:00
2012-09-13 11:03:06 +12:00
_recursivelyAnimateChildrenIn : function ( bounds , center , maxZoom ) {
2012-09-13 17:21:00 +12:00
this . _recursively ( bounds , 0 , maxZoom - 1 ,
2012-07-25 10:53:38 +12:00
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 . setOpacity ( 0 ) ;
}
}
} ,
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 . setOpacity ( 0 ) ;
}
}
}
) ;
} ,
2012-09-11 15:33:22 +12:00
_recursivelyAnimateChildrenInAndAddSelfToMap : function ( bounds , previousZoomLevel , newZoomLevel ) {
this . _recursively ( bounds , newZoomLevel , 0 ,
2012-07-25 10:53:38 +12:00
function ( c ) {
2012-09-11 15:33:22 +12:00
c . _recursivelyAnimateChildrenIn ( bounds , c . _group . _map . latLngToLayerPoint ( c . getLatLng ( ) ) . round ( ) , previousZoomLevel ) ;
2012-07-25 10:53:38 +12:00
//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
2012-09-11 15:33:22 +12:00
if ( c . _isSingleParent ( ) && previousZoomLevel - 1 === newZoomLevel ) {
2012-07-25 10:53:38 +12:00
c . setOpacity ( 1 ) ;
2012-09-13 17:21:00 +12:00
c . _recursivelyRemoveChildrenFromMap ( bounds , previousZoomLevel ) ; //Immediately remove our children as we are replacing them. TODO previousBounds not bounds
2012-07-25 10:53:38 +12:00
} else {
c . setOpacity ( 0 ) ;
}
c . _addToMap ( ) ;
}
) ;
} ,
2012-09-11 15:33:22 +12:00
_recursivelyBecomeVisible : function ( bounds , zoomLevel ) {
this . _recursively ( bounds , 0 , zoomLevel , null , function ( c ) {
2012-07-25 10:53:38 +12:00
c . setOpacity ( 1 ) ;
} ) ;
} ,
2012-09-11 15:33:22 +12:00
_recursivelyAddChildrenToMap : function ( startPos , zoomLevel , bounds ) {
2012-09-13 11:03:06 +12:00
this . _recursively ( bounds , - 1 , zoomLevel ,
2012-09-11 15:33:22 +12:00
function ( c ) {
if ( zoomLevel === c . _zoom ) {
2012-07-25 10:53:38 +12:00
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 ) ;
2013-06-14 17:09:56 +12:00
if ( nm . setOpacity ) {
nm . setOpacity ( 0 ) ;
}
2012-07-25 10:53:38 +12:00
}
2013-06-21 16:19:31 +12:00
c . _group . _featureGroup . addLayer ( nm ) ;
2012-07-25 10:53:38 +12:00
}
} ,
function ( c ) {
c . _addToMap ( startPos ) ;
}
) ;
} ,
2012-09-11 15:33:22 +12:00
_recursivelyRestoreChildPositions : function ( zoomLevel ) {
2012-07-25 10:53:38 +12:00
//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 ;
}
}
2012-09-11 15:33:22 +12:00
if ( zoomLevel - 1 === this . _zoom ) {
2012-07-25 10:53:38 +12:00
//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 -- ) {
2012-09-11 15:33:22 +12:00
this . _childClusters [ k ] . _recursivelyRestoreChildPositions ( zoomLevel ) ;
2012-07-25 10:53:38 +12:00
}
}
} ,
_restorePosition : function ( ) {
if ( this . _backupLatlng ) {
this . setLatLng ( this . _backupLatlng ) ;
delete this . _backupLatlng ;
}
} ,
2012-08-10 16:05:57 +12:00
2012-07-25 10:53:38 +12:00
//exceptBounds: If set, don't remove any markers/clusters in it
2012-09-11 15:33:22 +12:00
_recursivelyRemoveChildrenFromMap : function ( previousBounds , zoomLevel , exceptBounds ) {
2012-07-25 10:53:38 +12:00
var m , i ;
2012-09-13 17:21:00 +12:00
this . _recursively ( previousBounds , - 1 , zoomLevel - 1 ,
2012-07-25 10:53:38 +12:00
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 ) ) {
2013-06-21 16:19:31 +12:00
c . _group . _featureGroup . removeLayer ( m ) ;
2013-06-14 17:09:56 +12:00
if ( m . setOpacity ) {
m . setOpacity ( 1 ) ;
}
2012-07-25 10:53:38 +12:00
}
}
} ,
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 ) ) {
2013-06-21 16:19:31 +12:00
c . _group . _featureGroup . removeLayer ( m ) ;
2013-06-14 17:09:56 +12:00
if ( m . setOpacity ) {
m . setOpacity ( 1 ) ;
}
2012-07-25 10:53:38 +12:00
}
}
}
) ;
} ,
//Run the given functions recursively to this and child clusters
// boundsToApplyTo: a L.LatLngBounds representing the bounds of what clusters to recurse in to
2012-09-11 15:33:22 +12:00
// zoomLevelToStart: zoom level to start running functions (inclusive)
// zoomLevelToStop: zoom level to stop running functions (inclusive)
2012-07-25 10:53:38 +12:00
// 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
2012-09-11 15:33:22 +12:00
_recursively : function ( boundsToApplyTo , zoomLevelToStart , zoomLevelToStop , runAtEveryLevel , runAtBottomLevel ) {
2012-07-25 10:53:38 +12:00
var childClusters = this . _childClusters ,
2012-09-11 15:33:22 +12:00
zoom = this . _zoom ,
2012-07-25 10:53:38 +12:00
i , c ;
2012-09-11 15:33:22 +12:00
if ( zoomLevelToStart > zoom ) { //Still going down to required depth, just recurse to child clusters
2012-07-25 10:53:38 +12:00
for ( i = childClusters . length - 1 ; i >= 0 ; i -- ) {
c = childClusters [ i ] ;
if ( boundsToApplyTo . intersects ( c . _bounds ) ) {
2012-09-11 15:33:22 +12:00
c . _recursively ( boundsToApplyTo , zoomLevelToStart , zoomLevelToStop , runAtEveryLevel , runAtBottomLevel ) ;
2012-07-25 10:53:38 +12:00
}
}
} else { //In required depth
if ( runAtEveryLevel ) {
2012-09-11 15:33:22 +12:00
runAtEveryLevel ( this ) ;
2012-07-25 10:53:38 +12:00
}
2012-09-11 15:33:22 +12:00
if ( runAtBottomLevel && this . _zoom === zoomLevelToStop ) {
2012-07-25 10:53:38 +12:00
runAtBottomLevel ( this ) ;
}
//TODO: This loop is almost the same as above
2012-09-11 15:33:22 +12:00
if ( zoomLevelToStop > zoom ) {
2012-07-25 10:53:38 +12:00
for ( i = childClusters . length - 1 ; i >= 0 ; i -- ) {
c = childClusters [ i ] ;
if ( boundsToApplyTo . intersects ( c . _bounds ) ) {
2012-09-11 15:33:22 +12:00
c . _recursively ( boundsToApplyTo , zoomLevelToStart , zoomLevelToStop , runAtEveryLevel , runAtBottomLevel ) ;
2012-07-25 10:53:38 +12:00
}
}
}
}
} ,
_recalculateBounds : function ( ) {
var markers = this . _markers ,
childClusters = this . _childClusters ,
i ;
this . _bounds = new L . LatLngBounds ( ) ;
2012-09-12 17:49:59 +12:00
delete this . _wLatLng ;
2012-07-25 10:53:38 +12:00
for ( i = markers . length - 1 ; i >= 0 ; i -- ) {
2012-09-12 17:49:59 +12:00
this . _expandBounds ( markers [ i ] ) ;
2012-07-25 10:53:38 +12:00
}
for ( i = childClusters . length - 1 ; i >= 0 ; i -- ) {
2012-09-12 17:49:59 +12:00
this . _expandBounds ( childClusters [ i ] ) ;
2012-07-30 11:12:54 +12:00
}
2012-07-25 10:53:38 +12:00
} ,
//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 ;
}
} ) ;
2012-08-10 15:58:57 +12:00
2012-08-10 16:05:57 +12:00
2012-08-10 15:58:57 +12:00
L . DistanceGrid = function ( cellSize ) {
this . _cellSize = cellSize ;
this . _sqCellSize = cellSize * cellSize ;
this . _grid = { } ;
2012-09-11 15:33:22 +12:00
this . _objectPoint = { } ;
2012-08-10 15:58:57 +12:00
} ;
L . DistanceGrid . prototype = {
addObject : function ( obj , point ) {
var x = this . _getCoord ( point . x ) ,
y = this . _getCoord ( point . y ) ,
grid = this . _grid ,
row = grid [ y ] = grid [ y ] || { } ,
2012-09-11 15:33:22 +12:00
cell = row [ x ] = row [ x ] || [ ] ,
stamp = L . Util . stamp ( obj ) ;
2012-08-10 15:58:57 +12:00
2012-09-11 15:33:22 +12:00
this . _objectPoint [ stamp ] = point ;
2012-08-10 15:58:57 +12:00
cell . push ( obj ) ;
} ,
updateObject : function ( obj , point ) {
this . removeObject ( obj ) ;
this . addObject ( obj , point ) ;
} ,
2012-09-11 15:33:22 +12:00
//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 ;
delete this . _objectPoint [ L . Util . stamp ( obj ) ] ;
2012-08-10 15:58:57 +12:00
2012-09-11 15:33:22 +12:00
for ( i = 0 , len = cell . length ; i < len ; i ++ ) {
if ( cell [ i ] === obj ) {
2012-08-10 15:58:57 +12:00
2012-09-11 15:33:22 +12:00
cell . splice ( i , 1 ) ;
2012-08-10 15:58:57 +12:00
if ( len === 1 ) {
2012-09-11 15:33:22 +12:00
delete row [ x ] ;
2012-08-10 15:58:57 +12:00
}
2012-09-11 15:33:22 +12:00
return true ;
2012-08-10 15:58:57 +12:00
}
}
2012-09-11 15:33:22 +12:00
2012-08-10 15:58:57 +12:00
} ,
eachObject : function ( fn , context ) {
var i , j , k , len , row , cell , removed ,
grid = this . _grid ;
for ( i in grid ) {
2013-04-24 10:48:09 +12:00
row = grid [ i ] ;
2012-08-10 15:58:57 +12:00
2013-04-24 10:48:09 +12:00
for ( j in row ) {
cell = row [ j ] ;
2012-08-10 15:58:57 +12:00
2013-04-24 10:48:09 +12:00
for ( k = 0 , len = cell . length ; k < len ; k ++ ) {
removed = fn . call ( context , cell [ k ] ) ;
if ( removed ) {
k -- ;
len -- ;
2012-08-10 15:58:57 +12:00
}
}
}
}
} ,
getNearObject : function ( point ) {
var x = this . _getCoord ( point . x ) ,
y = this . _getCoord ( point . y ) ,
2012-08-27 13:03:21 +12:00
i , j , k , row , cell , len , obj , dist ,
2012-09-11 15:33:22 +12:00
objectPoint = this . _objectPoint ,
2012-08-27 13:03:21 +12:00
closestDistSq = this . _sqCellSize ,
2012-09-11 15:33:22 +12:00
closest = null ;
2012-08-10 15:58:57 +12:00
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 ] ;
2012-09-11 15:33:22 +12:00
dist = this . _sqDist ( objectPoint [ L . Util . stamp ( obj ) ] , point ) ;
2012-08-27 13:03:21 +12:00
if ( dist < closestDistSq ) {
closestDistSq = dist ;
closest = obj ;
2012-08-10 15:58:57 +12:00
}
}
}
}
}
}
2012-08-27 13:03:21 +12:00
return closest ;
2012-08-10 15:58:57 +12:00
} ,
_getCoord : function ( x ) {
return Math . floor ( x / this . _cellSize ) ;
} ,
_sqDist : function ( p , p2 ) {
var dx = p2 . x - p . x ,
dy = p2 . y - p . y ;
return dx * dx + dy * dy ;
}
} ;
2012-07-25 10:53:38 +12:00
/ * C o p y r i g h t ( c ) 2 0 1 2 t h e a u t h o r s l i s t e d a t t h e f o l l o w i n g U R L , a n d / o r
the authors of referenced articles or incorporated external code :
http : //en.literateprograms.org/Quickhull_(Javascript)?action=history&offset=20120410175256
Permission is hereby granted , free of charge , to any person obtaining
a copy of this software and associated documentation files ( the
"Software" ) , to deal in the Software without restriction , including
without limitation the rights to use , copy , modify , merge , publish ,
distribute , sublicense , and / or sell copies of the Software , and to
permit persons to whom the Software is furnished to do so , subject to
the following conditions :
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software .
THE SOFTWARE IS PROVIDED "AS IS" , WITHOUT WARRANTY OF ANY KIND ,
EXPRESS OR IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY , FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT .
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM , DAMAGES OR OTHER LIABILITY , WHETHER IN AN ACTION OF CONTRACT ,
TORT OR OTHERWISE , ARISING FROM , OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE .
Retrieved from : http : //en.literateprograms.org/Quickhull_(Javascript)?oldid=18434
* /
( function ( ) {
L . QuickHull = {
2013-10-29 09:05:47 +13:00
/ *
* @ 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
* /
2012-07-25 10:53:38 +12:00
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 ) ) ;
} ,
2013-10-29 09:05:47 +13:00
/ *
* @ 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 .
* /
2012-07-25 10:53:38 +12:00
findMostDistantPointFromBaseLine : function ( baseLine , latLngs ) {
var maxD = 0 ,
maxPt = null ,
newPoints = [ ] ,
i , pt , d ;
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 > maxD ) {
maxD = d ;
maxPt = pt ;
}
}
2013-10-29 09:05:47 +13:00
return { maxPoint : maxPt , newPoints : newPoints } ;
2012-07-25 10:53:38 +12:00
} ,
2013-10-29 09:05:47 +13:00
/ *
* Given a baseline , compute the convex hull of latLngs as an array
* of latLngs .
*
* @ param { Array } latLngs
* @ returns { Array }
* /
2012-07-25 10:53:38 +12:00
buildConvexHull : function ( baseLine , latLngs ) {
var convexHullBaseLines = [ ] ,
t = this . findMostDistantPointFromBaseLine ( baseLine , latLngs ) ;
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 )
) ;
return convexHullBaseLines ;
} else { // if there is no more point "outside" the base line, the current base line is part of the convex hull
2013-10-29 09:05:47 +13:00
return [ baseLine [ 0 ] ] ;
2012-07-25 10:53:38 +12:00
}
} ,
2013-10-29 09:05:47 +13:00
/ *
* Given an array of latlngs , compute a convex hull as an array
* of latlngs
*
* @ param { Array } latLngs
* @ returns { Array }
* /
2012-07-25 10:53:38 +12:00
getConvexHull : function ( latLngs ) {
2013-10-29 09:05:47 +13:00
// find first baseline
2012-07-25 10:53:38 +12:00
var maxLat = false , minLat = false ,
maxPt = null , minPt = null ,
i ;
for ( i = latLngs . length - 1 ; i >= 0 ; i -- ) {
var pt = latLngs [ i ] ;
if ( maxLat === false || pt . lat > maxLat ) {
maxPt = pt ;
maxLat = pt . lat ;
}
if ( minLat === false || pt . lat < minLat ) {
minPt = pt ;
minLat = pt . lat ;
}
}
var ch = [ ] . concat ( this . buildConvexHull ( [ minPt , maxPt ] , latLngs ) ,
this . buildConvexHull ( [ maxPt , minPt ] , latLngs ) ) ;
return ch ;
}
} ;
} ( ) ) ;
L . MarkerCluster . include ( {
getConvexHull : function ( ) {
var childMarkers = this . getAllChildMarkers ( ) ,
points = [ ] ,
2013-10-29 09:05:47 +13:00
p , i ;
2012-07-25 10:53:38 +12:00
for ( i = childMarkers . length - 1 ; i >= 0 ; i -- ) {
p = childMarkers [ i ] . getLatLng ( ) ;
points . push ( p ) ;
}
2013-10-29 09:05:47 +13:00
return L . QuickHull . getConvexHull ( points ) ;
2012-07-25 10:53:38 +12:00
}
} ) ;
2013-10-29 09:05:47 +13:00
2012-07-25 10:53:38 +12:00
//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 :-)
L . MarkerCluster . include ( {
_2PI : Math . PI * 2 ,
_circleFootSeparation : 25 , //related to circumference of circle
_circleStartAngle : Math . PI / 6 ,
_spiralFootSeparation : 28 , //related to size of spiral (experiment!)
_spiralLengthStart : 11 ,
_spiralLengthFactor : 5 ,
_circleSpiralSwitchover : 9 , //show spiral instead of circle from this marker count upwards.
// 0 -> always spiral; Infinity -> always circle
spiderfy : function ( ) {
2012-07-27 14:44:55 +12:00
if ( this . _group . _spiderfied === this || this . _group . _inZoomAnimation ) {
2012-07-25 10:53:38 +12:00
return ;
}
var childMarkers = this . getAllChildMarkers ( ) ,
group = this . _group ,
map = group . _map ,
center = map . latLngToLayerPoint ( this . _latlng ) ,
positions ;
this . _group . _unspiderfy ( ) ;
this . _group . _spiderfied = this ;
//TODO Maybe: childMarkers order by distance to center
if ( childMarkers . length >= this . _circleSpiralSwitchover ) {
positions = this . _generatePointsSpiral ( childMarkers . length , center ) ;
} else {
center . y += 10 ; //Otherwise circles look wrong
positions = this . _generatePointsCircle ( childMarkers . length , center ) ;
}
this . _animationSpiderfy ( childMarkers , positions ) ;
} ,
2012-08-07 11:32:18 +12:00
unspiderfy : function ( zoomDetails ) {
/// <param Name="zoomDetails">Argument from zoomanim if being called in a zoom animation or null otherwise</param>
2012-07-27 14:44:55 +12:00
if ( this . _group . _inZoomAnimation ) {
return ;
}
2012-08-07 11:32:18 +12:00
this . _animationUnspiderfy ( zoomDetails ) ;
2012-07-25 10:53:38 +12:00
this . _group . _spiderfied = null ;
} ,
_generatePointsCircle : function ( count , centerPt ) {
2012-12-05 11:30:14 +13:00
var circumference = this . _group . options . spiderfyDistanceMultiplier * this . _circleFootSeparation * ( 2 + count ) ,
2012-07-25 10:53:38 +12:00
legLength = circumference / this . _2PI , //radius from circumference
angleStep = this . _2PI / count ,
res = [ ] ,
i , angle ;
res . length = count ;
for ( i = count - 1 ; i >= 0 ; i -- ) {
angle = this . _circleStartAngle + i * angleStep ;
2012-07-27 14:44:55 +12:00
res [ i ] = new L . Point ( centerPt . x + legLength * Math . cos ( angle ) , centerPt . y + legLength * Math . sin ( angle ) ) . _round ( ) ;
2012-07-25 10:53:38 +12:00
}
return res ;
} ,
_generatePointsSpiral : function ( count , centerPt ) {
2012-12-05 11:30:14 +13:00
var legLength = this . _group . options . spiderfyDistanceMultiplier * this . _spiralLengthStart ,
separation = this . _group . options . spiderfyDistanceMultiplier * this . _spiralFootSeparation ,
lengthFactor = this . _group . options . spiderfyDistanceMultiplier * this . _spiralLengthFactor ,
2012-07-25 10:53:38 +12:00
angle = 0 ,
res = [ ] ,
i ;
res . length = count ;
for ( i = count - 1 ; i >= 0 ; i -- ) {
2012-12-05 11:30:14 +13:00
angle += separation / legLength + i * 0.0005 ;
2012-07-27 14:44:55 +12:00
res [ i ] = new L . Point ( centerPt . x + legLength * Math . cos ( angle ) , centerPt . y + legLength * Math . sin ( angle ) ) . _round ( ) ;
2012-12-05 11:30:14 +13:00
legLength += this . _2PI * lengthFactor / angle ;
2012-07-25 10:53:38 +12:00
}
return res ;
2013-04-24 10:48:09 +12:00
} ,
_noanimationUnspiderfy : function ( ) {
var group = this . _group ,
map = group . _map ,
2013-06-21 16:19:31 +12:00
fg = group . _featureGroup ,
2013-04-24 10:48:09 +12:00
childMarkers = this . getAllChildMarkers ( ) ,
m , i ;
this . setOpacity ( 1 ) ;
for ( i = childMarkers . length - 1 ; i >= 0 ; i -- ) {
m = childMarkers [ i ] ;
2013-06-21 16:19:31 +12:00
fg . removeLayer ( m ) ;
2013-04-24 10:48:09 +12:00
if ( m . _preSpiderfyLatlng ) {
m . setLatLng ( m . _preSpiderfyLatlng ) ;
delete m . _preSpiderfyLatlng ;
}
2013-07-05 11:28:41 +12:00
if ( m . setZIndexOffset ) {
m . setZIndexOffset ( 0 ) ;
}
2013-04-24 10:48:09 +12:00
if ( m . _spiderLeg ) {
map . removeLayer ( m . _spiderLeg ) ;
delete m . _spiderLeg ;
}
}
2013-11-14 09:40:33 +13:00
group . _spiderfied = null ;
2012-07-25 10:53:38 +12:00
}
} ) ;
L . MarkerCluster . include ( ! L . DomUtil . TRANSITION ? {
//Non Animated versions of everything
_animationSpiderfy : function ( childMarkers , positions ) {
var group = this . _group ,
map = group . _map ,
2013-06-21 16:19:31 +12:00
fg = group . _featureGroup ,
2012-08-01 15:17:51 +12:00
i , m , leg , newPos ;
2012-07-25 10:53:38 +12:00
for ( i = childMarkers . length - 1 ; i >= 0 ; i -- ) {
2012-08-01 15:17:51 +12:00
newPos = map . layerPointToLatLng ( positions [ i ] ) ;
2012-07-25 10:53:38 +12:00
m = childMarkers [ i ] ;
2012-08-01 15:17:51 +12:00
m . _preSpiderfyLatlng = m . _latlng ;
m . setLatLng ( newPos ) ;
2013-07-05 11:28:41 +12:00
if ( m . setZIndexOffset ) {
m . setZIndexOffset ( 1000000 ) ; //Make these appear on top of EVERYTHING
}
2012-08-01 15:17:51 +12:00
2013-06-21 16:19:31 +12:00
fg . addLayer ( m ) ;
2012-07-27 14:44:55 +12:00
2012-07-25 10:53:38 +12:00
2012-08-01 15:17:51 +12:00
leg = new L . Polyline ( [ this . _latlng , newPos ] , { weight : 1.5 , color : '#222' } ) ;
2012-07-25 10:53:38 +12:00
map . addLayer ( leg ) ;
m . _spiderLeg = leg ;
}
this . setOpacity ( 0.3 ) ;
2012-09-12 11:17:34 +12:00
group . fire ( 'spiderfied' ) ;
2012-07-25 10:53:38 +12:00
} ,
_animationUnspiderfy : function ( ) {
2013-04-24 10:48:09 +12:00
this . _noanimationUnspiderfy ( ) ;
2012-07-25 10:53:38 +12:00
}
} : {
//Animated versions here
2012-11-01 09:45:37 +13:00
SVG _ANIMATION : ( function ( ) {
return document . createElementNS ( 'http://www.w3.org/2000/svg' , 'animate' ) . toString ( ) . indexOf ( 'SVGAnimate' ) > - 1 ;
} ( ) ) ,
2012-07-25 10:53:38 +12:00
_animationSpiderfy : function ( childMarkers , positions ) {
var me = this ,
group = this . _group ,
map = group . _map ,
2013-06-21 16:19:31 +12:00
fg = group . _featureGroup ,
2012-07-27 14:44:55 +12:00
thisLayerPos = map . latLngToLayerPoint ( this . _latlng ) ,
2012-08-01 15:17:51 +12:00
i , m , leg , newPos ;
2012-07-25 10:53:38 +12:00
2012-08-01 15:17:51 +12:00
//Add markers to map hidden at our center point
2012-07-25 10:53:38 +12:00
for ( i = childMarkers . length - 1 ; i >= 0 ; i -- ) {
m = childMarkers [ i ] ;
2013-07-05 11:28:41 +12:00
//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 ) ;
fg . addLayer ( m ) ;
2012-07-25 10:53:38 +12:00
2013-07-05 11:28:41 +12:00
m . _setPos ( thisLayerPos ) ;
} else {
//Vectors just get immediately added
fg . addLayer ( m ) ;
}
2012-07-25 10:53:38 +12:00
}
2012-08-01 15:17:51 +12:00
group . _forceLayout ( ) ;
2012-07-25 16:44:42 +12:00
group . _animationStart ( ) ;
2012-07-25 10:53:38 +12:00
2012-09-28 09:37:35 +12:00
var initialLegOpacity = L . Path . SVG ? 0 : 0.3 ,
2012-07-25 16:44:42 +12:00
xmlns = L . Path . SVG _NS ;
2012-07-25 10:53:38 +12:00
2012-07-25 16:44:42 +12:00
for ( i = childMarkers . length - 1 ; i >= 0 ; i -- ) {
2012-08-01 15:17:51 +12:00
newPos = map . layerPointToLatLng ( positions [ i ] ) ;
2012-07-25 16:44:42 +12:00
m = childMarkers [ i ] ;
2012-07-25 10:53:38 +12:00
2012-08-01 15:17:51 +12:00
//Move marker to new position
m . _preSpiderfyLatlng = m . _latlng ;
m . setLatLng ( newPos ) ;
2013-07-05 11:28:41 +12:00
if ( m . setOpacity ) {
m . setOpacity ( 1 ) ;
}
2012-07-25 10:53:38 +12:00
2012-08-01 15:17:51 +12:00
//Add Legs.
leg = new L . Polyline ( [ me . _latlng , newPos ] , { weight : 1.5 , color : '#222' , opacity : initialLegOpacity } ) ;
2012-07-25 16:44:42 +12:00
map . addLayer ( leg ) ;
m . _spiderLeg = leg ;
2012-07-25 10:53:38 +12:00
2012-07-25 16:44:42 +12:00
//Following animations don't work for canvas
2012-11-01 09:45:37 +13:00
if ( ! L . Path . SVG || ! this . SVG _ANIMATION ) {
2012-07-25 16:44:42 +12:00
continue ;
2012-07-25 10:53:38 +12:00
}
2012-07-25 16:44:42 +12:00
//How this works:
//http://stackoverflow.com/questions/5924238/how-do-you-animate-an-svg-path-in-ios
//http://dev.opera.com/articles/view/advanced-svg-animation-techniques/
//Animate length
var length = leg . _path . getTotalLength ( ) ;
leg . _path . setAttribute ( "stroke-dasharray" , length + "," + length ) ;
var anim = document . createElementNS ( xmlns , "animate" ) ;
anim . setAttribute ( "attributeName" , "stroke-dashoffset" ) ;
anim . setAttribute ( "begin" , "indefinite" ) ;
anim . setAttribute ( "from" , length ) ;
anim . setAttribute ( "to" , 0 ) ;
anim . setAttribute ( "dur" , 0.25 ) ;
leg . _path . appendChild ( anim ) ;
anim . beginElement ( ) ;
//Animate opacity
anim = document . createElementNS ( xmlns , "animate" ) ;
anim . setAttribute ( "attributeName" , "stroke-opacity" ) ;
anim . setAttribute ( "attributeName" , "stroke-opacity" ) ;
anim . setAttribute ( "begin" , "indefinite" ) ;
anim . setAttribute ( "from" , 0 ) ;
anim . setAttribute ( "to" , 0.5 ) ;
anim . setAttribute ( "dur" , 0.25 ) ;
leg . _path . appendChild ( anim ) ;
anim . beginElement ( ) ;
}
me . setOpacity ( 0.3 ) ;
2012-07-25 10:53:38 +12:00
2012-07-25 16:44:42 +12:00
//Set the opacity of the spiderLegs back to their correct value
// The animations above override this until they complete.
// If the initial opacity of the spiderlegs isn't 0 then they appear before the animation starts.
2012-09-28 09:37:35 +12:00
if ( L . Path . SVG ) {
2012-07-25 16:44:42 +12:00
this . _group . _forceLayout ( ) ;
for ( i = childMarkers . length - 1 ; i >= 0 ; i -- ) {
m = childMarkers [ i ] . _spiderLeg ;
m . options . opacity = 0.5 ;
m . _path . setAttribute ( 'stroke-opacity' , 0.5 ) ;
2012-07-25 10:53:38 +12:00
}
2012-07-25 16:44:42 +12:00
}
2012-07-25 10:53:38 +12:00
2012-07-25 16:44:42 +12:00
setTimeout ( function ( ) {
group . _animationEnd ( ) ;
2012-09-12 11:17:34 +12:00
group . fire ( 'spiderfied' ) ;
2013-03-01 14:15:04 +13:00
} , 200 ) ;
2012-07-25 10:53:38 +12:00
} ,
2012-08-07 11:32:18 +12:00
_animationUnspiderfy : function ( zoomDetails ) {
2012-07-25 10:53:38 +12:00
var group = this . _group ,
map = group . _map ,
2013-06-21 16:19:31 +12:00
fg = group . _featureGroup ,
2012-08-07 11:32:18 +12:00
thisLayerPos = zoomDetails ? map . _latLngToNewLayerPoint ( this . _latlng , zoomDetails . zoom , zoomDetails . center ) : map . latLngToLayerPoint ( this . _latlng ) ,
2012-07-25 10:53:38 +12:00
childMarkers = this . getAllChildMarkers ( ) ,
2012-11-01 09:45:37 +13:00
svg = L . Path . SVG && this . SVG _ANIMATION ,
2012-07-25 10:53:38 +12:00
m , i , a ;
group . _animationStart ( ) ;
2013-02-23 21:26:39 -05:00
2012-07-25 10:53:38 +12:00
//Make us visible and bring the child markers back in
this . setOpacity ( 1 ) ;
for ( i = childMarkers . length - 1 ; i >= 0 ; i -- ) {
m = childMarkers [ i ] ;
2013-01-09 11:28:49 +13:00
//Marker was added to us after we were spidified
if ( ! m . _preSpiderfyLatlng ) {
continue ;
}
2012-08-01 15:17:51 +12:00
//Fix up the location to the real one
m . setLatLng ( m . _preSpiderfyLatlng ) ;
delete m . _preSpiderfyLatlng ;
//Hack override the location to be our center
2013-07-05 11:28:41 +12:00
if ( m . setOpacity ) {
m . _setPos ( thisLayerPos ) ;
m . setOpacity ( 0 ) ;
} else {
fg . removeLayer ( m ) ;
}
2012-07-25 10:53:38 +12:00
//Animate the spider legs back in
if ( svg ) {
a = m . _spiderLeg . _path . childNodes [ 0 ] ;
a . setAttribute ( 'to' , a . getAttribute ( 'from' ) ) ;
a . setAttribute ( 'from' , 0 ) ;
a . beginElement ( ) ;
a = m . _spiderLeg . _path . childNodes [ 1 ] ;
a . setAttribute ( 'from' , 0.5 ) ;
a . setAttribute ( 'to' , 0 ) ;
a . setAttribute ( 'stroke-opacity' , 0 ) ;
a . beginElement ( ) ;
m . _spiderLeg . _path . setAttribute ( 'stroke-opacity' , 0 ) ;
}
}
setTimeout ( function ( ) {
2012-07-27 16:00:42 +12:00
//If we have only <= one child left then that marker will be shown on the map so don't remove it!
var stillThereChildCount = 0 ;
2012-07-25 10:53:38 +12:00
for ( i = childMarkers . length - 1 ; i >= 0 ; i -- ) {
m = childMarkers [ i ] ;
2012-07-27 16:00:42 +12:00
if ( m . _spiderLeg ) {
stillThereChildCount ++ ;
}
}
for ( i = childMarkers . length - 1 ; i >= 0 ; i -- ) {
m = childMarkers [ i ] ;
if ( ! m . _spiderLeg ) { //Has already been unspiderfied
continue ;
}
2013-07-05 11:28:41 +12:00
if ( m . setOpacity ) {
m . setOpacity ( 1 ) ;
m . setZIndexOffset ( 0 ) ;
}
2012-07-25 10:53:38 +12:00
2012-07-27 16:00:42 +12:00
if ( stillThereChildCount > 1 ) {
2013-06-21 16:19:31 +12:00
fg . removeLayer ( m ) ;
2012-07-27 16:00:42 +12:00
}
2012-07-25 10:53:38 +12:00
map . removeLayer ( m . _spiderLeg ) ;
delete m . _spiderLeg ;
}
2012-07-26 12:03:02 +12:00
group . _animationEnd ( ) ;
2013-03-01 14:15:04 +13:00
} , 200 ) ;
2012-07-25 10:53:38 +12:00
}
} ) ;
L . MarkerClusterGroup . include ( {
//The MarkerCluster currently spiderfied (if any)
_spiderfied : null ,
_spiderfierOnAdd : function ( ) {
2012-08-07 11:32:18 +12:00
this . _map . on ( 'click' , this . _unspiderfyWrapper , this ) ;
2012-09-12 11:17:34 +12:00
if ( this . _map . options . zoomAnimation ) {
this . _map . on ( 'zoomstart' , this . _unspiderfyZoomStart , this ) ;
}
2013-11-14 09:40:33 +13:00
//Browsers without zoomAnimation or a big zoom don't fire zoomstart
this . _map . on ( 'zoomend' , this . _noanimationUnspiderfy , this ) ;
2012-07-25 10:53:38 +12:00
2012-09-28 09:37:35 +12:00
if ( L . Path . SVG && ! L . Browser . touch ) {
2012-08-07 11:32:18 +12:00
this . _map . _initPathRoot ( ) ;
//Needs to happen in the pageload, not after, or animations don't work in webkit
2012-07-25 10:53:38 +12:00
// http://stackoverflow.com/questions/8455200/svg-animate-with-dynamically-added-elements
2012-08-07 11:32:18 +12:00
//Disable on touch browsers as the animation messes up on a touch zoom and isn't very noticable
2012-07-25 10:53:38 +12:00
}
} ,
2012-07-30 11:59:18 +12:00
_spiderfierOnRemove : function ( ) {
2012-08-07 11:32:18 +12:00
this . _map . off ( 'click' , this . _unspiderfyWrapper , this ) ;
this . _map . off ( 'zoomstart' , this . _unspiderfyZoomStart , this ) ;
2012-08-27 13:55:55 +12:00
this . _map . off ( 'zoomanim' , this . _unspiderfyZoomAnim , this ) ;
2012-09-06 16:20:02 +12:00
this . _unspiderfy ( ) ; //Ensure that markers are back where they should be
2012-08-07 11:32:18 +12:00
} ,
//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 ( ) {
2012-08-27 13:55:55 +12:00
if ( ! this . _map ) { //May have been removed from the map by a zoomEnd handler
return ;
}
2012-08-07 11:32:18 +12:00
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 ( ) ;
2012-07-30 11:59:18 +12:00
} ,
2012-08-07 11:32:18 +12:00
_unspiderfy : function ( zoomDetails ) {
2012-07-25 10:53:38 +12:00
if ( this . _spiderfied ) {
2012-08-07 11:32:18 +12:00
this . _spiderfied . unspiderfy ( zoomDetails ) ;
2012-07-25 10:53:38 +12:00
}
2012-07-27 16:00:42 +12:00
} ,
2013-04-24 10:48:09 +12:00
_noanimationUnspiderfy : function ( ) {
if ( this . _spiderfied ) {
this . _spiderfied . _noanimationUnspiderfy ( ) ;
}
} ,
2012-07-27 16:00:42 +12:00
//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 ) {
2013-06-21 16:19:31 +12:00
this . _featureGroup . removeLayer ( layer ) ;
2012-08-01 15:17:51 +12:00
2012-07-27 16:00:42 +12:00
layer . setOpacity ( 1 ) ;
2012-08-01 15:17:51 +12:00
//Position will be fixed up immediately in _animationUnspiderfy
2012-07-27 16:00:42 +12:00
layer . setZIndexOffset ( 0 ) ;
2012-08-01 15:17:51 +12:00
2012-07-27 16:00:42 +12:00
this . _map . removeLayer ( layer . _spiderLeg ) ;
delete layer . _spiderLeg ;
}
2012-07-25 10:53:38 +12:00
}
} ) ;
2013-06-14 14:48:48 +12:00
} ( window , document ) ) ;