2012-07-25 10:53:38 +12:00
/ *
Copyright ( c ) 2012 , Smartrak , David Leaver
Leaflet . markercluster is an open - source JavaScript library for Marker Clustering on leaflet powered maps .
https : //github.com/danzel/Leaflet.markercluster
* /
( function ( window , undefined ) {
/ *
* 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 ,
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 ,
//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
L . FeatureGroup . prototype . initialize . call ( this , [ ] ) ;
this . _inZoomAnimation = 0 ;
this . _needsClustering = [ ] ;
//The bounds of the currently shown area (from _getExpandedVisibleBounds) Updated on zoom/move
this . _currentShownBounds = null ;
} ,
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 ) {
if ( layer . _layers . hasOwnProperty ( i ) ) {
2012-10-11 10:23:24 +13: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
}
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 ;
}
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
if ( ! this . _map ) {
this . _arraySplice ( this . _needsClustering , layer ) ;
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
2012-09-11 15:33:22 +12:00
if ( layer . _icon ) {
L . FeatureGroup . prototype . removeLayer . call ( this , layer ) ;
2012-09-14 10:43:17 +12:00
layer . setOpacity ( 1 ) ;
2012-09-11 15:33:22 +12:00
}
2012-10-17 15:16:45 +13:00
delete layer . _ _parent ;
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 ) {
if ( ! this . _map ) {
this . _needsClustering = this . _needsClustering . concat ( layersArray ) ;
return this ;
}
for ( var i = 0 , l = layersArray . length ; i < l ; i ++ ) {
var m = layersArray [ i ] ;
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 ] ;
L . FeatureGroup . prototype . removeLayer . call ( this , otherMarker ) ;
}
}
}
this . _topClusterLevel . _recursivelyAddChildrenToMap ( null , this . _zoom , this . _currentShownBounds ) ;
return this ;
} ,
//Takes an array of markers and removes them in bulk
removeLayers : function ( layersArray ) {
var i , l , m ;
if ( ! this . _map ) {
for ( i = 0 , l = layersArray . length ; i < l ; i ++ ) {
2012-10-11 11:22:01 +13:00
this . _arraySplice ( this . _needsClustering , layersArray [ i ] ) ;
2012-10-11 10:23:24 +13:00
}
return this ;
}
for ( i = 0 , l = layersArray . length ; i < l ; i ++ ) {
m = layersArray [ i ] ;
this . _removeLayer ( m , true , true ) ;
if ( m . _icon ) {
L . FeatureGroup . prototype . removeLayer . call ( this , m ) ;
m . setOpacity ( 1 ) ;
}
}
//Fix up the clusters and markers on the map
this . _topClusterLevel . _recursivelyAddChildrenToMap ( null , this . _zoom , this . _currentShownBounds ) ;
for ( i in this . _layers ) {
if ( this . _layers . hasOwnProperty ( i ) ) {
m = this . _layers [ i ] ;
if ( m instanceof L . MarkerCluster ) {
m . _updateIcon ( ) ;
}
}
}
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-08-07 11:32:18 +12:00
//If we aren't on the map yet, just blow away the markers we know of
if ( ! this . _map ) {
this . _needsClustering = [ ] ;
return this ;
}
2012-09-11 15:33:22 +12:00
if ( this . _unspiderfy ) {
this . _unspiderfy ( ) ;
}
2012-08-02 10:16:22 +12:00
//Remove all the visible layers
for ( var i in this . _layers ) {
if ( this . _layers . hasOwnProperty ( i ) ) {
L . FeatureGroup . prototype . removeLayer . call ( this , this . _layers [ i ] ) ;
}
}
2012-09-11 15:33:22 +12:00
//Reset _topClusterLevel and the DistanceGrids
2012-08-02 10:16:22 +12:00
this . _generateInitialClusters ( ) ;
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 ( ) ;
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 ( ) ) ;
}
}
return bounds ;
} ,
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 ) {
2012-10-11 10:23:24 +13:00
if ( this . _needsClustering . length > 0 ) {
var anArray = this . _needsClustering ;
for ( var i = anArray . length - 1 ; i >= 0 ; i -- ) {
if ( anArray [ i ] === layer ) {
return true ;
2012-09-03 10:35:13 +12:00
}
2012-10-11 10:23:24 +13:00
}
}
return ! ! ( layer . _ _parent && layer . _ _parent . _group === this ) ;
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
} ;
2012-09-20 11:29:21 +12:00
if ( layer . _icon ) {
callback ( ) ;
} else if ( layer . _ _parent . _zoom < this . _map . getZoom ( ) ) {
//Layer should be visible now but isn't on screen, just pan over to it
this . _map . on ( 'moveend' , showMarker , this ) ;
if ( ! layer . _icon ) {
this . _map . panTo ( layer . getLatLng ( ) ) ;
}
2012-09-12 11:17:34 +12:00
} else {
this . _map . on ( 'moveend' , showMarker , this ) ;
this . on ( 'animationend' , showMarker , this ) ;
2012-09-20 11:29:21 +12:00
this . _map . setView ( layer . getLatLng ( ) , layer . _ _parent . _zoom + 1 ) ;
2012-09-12 11:17:34 +12:00
layer . _ _parent . zoomToBounds ( ) ;
}
2012-09-11 16:24:40 +12:00
} ,
2012-07-26 11:18:12 +12:00
//Overrides FeatureGroup.onAdd
onAdd : function ( map ) {
L . FeatureGroup . prototype . onAdd . call ( this , map ) ;
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
2012-09-11 15:33:22 +12:00
for ( var i = 0 , l = this . _needsClustering . length ; i < l ; i ++ ) {
this . _addLayer ( this . _needsClustering [ i ] , this . _maxZoom ) ;
}
this . _needsClustering = [ ] ;
2012-08-20 10:59:37 +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:
//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 ) ;
2012-07-26 11:18:12 +12:00
} ,
2012-07-30 11:59:18 +12:00
//Overrides FeatureGroup.onRemove
onRemove : function ( map ) {
this . _map . off ( 'zoomend' , this . _zoomEnd , this ) ;
this . _map . off ( 'moveend' , this . _moveEnd , this ) ;
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 ( ) ;
}
L . FeatureGroup . prototype . onRemove . call ( this , map ) ;
} ,
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 ) ;
return ;
}
}
} ,
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 ,
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
L . FeatureGroup . prototype . removeLayer . call ( this , cluster ) ;
2012-10-11 10:23:24 +13:00
if ( ! dontUpdateMap ) {
L . FeatureGroup . prototype . addLayer . call ( this , otherMarker ) ;
}
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-07-25 10:53:38 +12:00
//Overrides FeatureGroup._propagateEvent
_propagateEvent : function ( e ) {
if ( e . target instanceof L . MarkerCluster ) {
e . type = 'cluster' + e . type ;
}
L . FeatureGroup . prototype . _propagateEvent . call ( this , e ) ;
} ,
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 ( ) {
var shownPolygon = null ,
map = this . _map ,
spiderfyOnMaxZoom = this . options . spiderfyOnMaxZoom ,
showCoverageOnHover = this . options . showCoverageOnHover ,
zoomToBoundsOnClick = this . options . zoomToBoundsOnClick ;
//Zoom on cluster click or spiderfy if we are at the lowest level
if ( spiderfyOnMaxZoom || zoomToBoundsOnClick ) {
this . on ( 'clusterclick' , function ( a ) {
if ( map . getMaxZoom ( ) === map . getZoom ( ) ) {
if ( spiderfyOnMaxZoom ) {
a . layer . spiderfy ( ) ;
}
} else if ( zoomToBoundsOnClick ) {
a . layer . zoomToBounds ( ) ;
}
} , this ) ;
}
//Show convex hull (boundary) polygon on mouse over
if ( showCoverageOnHover ) {
this . on ( 'clustermouseover' , function ( a ) {
if ( this . _inZoomAnimation ) {
return ;
}
if ( shownPolygon ) {
map . removeLayer ( shownPolygon ) ;
}
if ( a . layer . getChildCount ( ) > 2 ) {
2012-09-25 13:41:09 +12:00
shownPolygon = new L . Polygon ( a . layer . getConvexHull ( ) , this . options . polygonOptions ) ;
2012-07-26 11:18:12 +12:00
map . addLayer ( shownPolygon ) ;
}
} , this ) ;
this . on ( 'clustermouseout' , function ( ) {
if ( shownPolygon ) {
map . removeLayer ( shownPolygon ) ;
shownPolygon = null ;
}
} , this ) ;
map . on ( 'zoomend' , function ( ) {
if ( shownPolygon ) {
map . removeLayer ( shownPolygon ) ;
shownPolygon = null ;
}
} , this ) ;
2012-08-20 10:59:37 +12:00
map . on ( 'layerremove' , function ( opt ) {
if ( shownPolygon && opt . layer === this ) {
map . removeLayer ( shownPolygon ) ;
shownPolygon = null ;
}
} , this ) ;
2012-07-26 11:18:12 +12: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 ) ;
this . _topClusterLevel . _recursivelyAddChildrenToMap ( null , this . _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 ( ) ,
radius = this . options . maxClusterRadius ;
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 = { } ;
//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 ) ;
}
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 ) {
if ( closest . _ _parent ) {
this . _removeLayer ( closest , false ) ;
}
2012-09-13 11:03:06 +12:00
var parent = closest . _ _parent ;
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 ;
}
//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
} ,
//Merge and split any existing clusters that are too big or small
_mergeSplitClusters : function ( ) {
if ( this . _zoom < this . _map . _zoom ) { //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
}
} ,
2012-09-11 15:33:22 +12: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 ( ) {
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 ) {
2012-09-06 16:20:02 +12:00
L . FeatureGroup . prototype . addLayer . call ( this , layer ) ;
} else if ( newCluster . _childCount === 2 ) {
newCluster . _addToMap ( ) ;
2012-09-11 15:33:22 +12:00
var markers = newCluster . getAllChildMarkers ( ) ;
L . FeatureGroup . prototype . removeLayer . call ( this , markers [ 0 ] ) ;
L . FeatureGroup . prototype . removeLayer . call ( this , markers [ 1 ] ) ;
} 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 ( ) ) ;
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 ( ) ) ;
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 ) {
var me = this ,
bounds = this . _getExpandedVisibleBounds ( ) ,
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 ;
2012-09-11 15:33:22 +12:00
if ( c . _isSingleParent ( ) && previousZoomLevel + 1 === newZoomLevel ) { //Immediately add the new child and remove us
2012-07-25 10:53:38 +12:00
L . FeatureGroup . prototype . removeLayer . call ( me , 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 ) ) {
L . FeatureGroup . prototype . removeLayer . call ( me , m ) ;
}
}
} ) ;
2012-07-25 16:44:42 +12:00
this . _forceLayout ( ) ;
var j , n ;
2012-07-25 10:53:38 +12:00
2012-07-25 16:44:42 +12:00
//Update opacities
2012-09-11 15:33:22 +12:00
me . _topClusterLevel . _recursivelyBecomeVisible ( bounds , newZoomLevel ) ;
2012-07-25 16:44:42 +12:00
//TODO Maybe? Update markers in _recursivelyBecomeVisible
for ( j in me . _layers ) {
if ( me . _layers . hasOwnProperty ( j ) ) {
n = me . _layers [ j ] ;
2012-07-25 10:53:38 +12:00
2012-07-25 16:44:42 +12:00
if ( ! ( n instanceof L . MarkerCluster ) && n . _icon ) {
n . setOpacity ( 1 ) ;
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
//update the positions of the just added clusters/markers
2012-09-11 15:33:22 +12:00
me . _topClusterLevel . _recursively ( bounds , previousZoomLevel , newZoomLevel , function ( c ) {
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
2012-08-10 15:58:57 +12:00
2012-07-25 10:53:38 +12:00
setTimeout ( function ( ) {
//update the positions of the just added clusters/markers
2012-09-11 15:33:22 +12:00
me . _topClusterLevel . _recursively ( bounds , previousZoomLevel , 0 , function ( c ) {
2012-07-25 10:53:38 +12:00
L . FeatureGroup . prototype . removeLayer . call ( me , c ) ;
2012-09-13 17:21:00 +12:00
c . setOpacity ( 1 ) ;
2012-07-25 10:53:38 +12:00
} ) ;
me . _animationEnd ( ) ;
} , 250 ) ;
} ,
_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
setTimeout ( function ( ) {
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 ( ) ) ;
m . setOpacity ( 1 ) ;
return ;
}
2012-09-13 11:03:06 +12:00
cluster . _recursively ( bounds , newZoomLevel , 0 , function ( c ) {
2012-09-13 17:21:00 +12:00
c . _recursivelyRemoveChildrenFromMap ( bounds , previousZoomLevel + 1 ) ;
2012-07-25 10:53:38 +12:00
} ) ;
me . _animationEnd ( ) ;
} , 250 ) ;
} ,
_animationAddLayer : function ( layer , newCluster ) {
var me = this ;
L . FeatureGroup . prototype . addLayer . call ( this , 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
2012-07-25 16:44:42 +12:00
setTimeout ( function ( ) {
L . FeatureGroup . prototype . removeLayer . call ( me , 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 ( ) ;
} , 250 ) ;
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
}
} ) ;
2012-08-10 15:58:57 +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 ;
} ,
//Zoom to the extents of this cluster
zoomToBounds : function ( ) {
this . _group . _map . fitBounds ( this . _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 ) ;
}
L . FeatureGroup . prototype . addLayer . call ( this . _group , this ) ;
} ,
2012-09-11 15:33:22 +12: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 ) ;
nm . setOpacity ( 0 ) ;
}
L . FeatureGroup . prototype . addLayer . call ( c . _group , nm ) ;
}
} ,
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 ) ) {
L . FeatureGroup . prototype . removeLayer . call ( c . _group , m ) ;
m . setOpacity ( 1 ) ;
}
}
} ,
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 ) ) {
L . FeatureGroup . prototype . removeLayer . call ( c . _group , m ) ;
m . setOpacity ( 1 ) ;
}
}
}
) ;
} ,
//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 ) {
if ( grid . hasOwnProperty ( i ) ) {
row = grid [ i ] ;
for ( j in row ) {
if ( row . hasOwnProperty ( j ) ) {
cell = row [ j ] ;
for ( k = 0 , len = cell . length ; k < len ; k ++ ) {
removed = fn . call ( context , cell [ k ] ) ;
if ( removed ) {
k -- ;
len -- ;
}
}
}
}
}
}
} ,
getNearObject : function ( point ) {
var x = this . _getCoord ( point . x ) ,
y = this . _getCoord ( point . y ) ,
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 = {
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 ) ) ;
} ,
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 ;
}
}
return { 'maxPoint' : maxPt , 'newPoints' : newPoints } ;
} ,
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
return [ baseLine ] ;
}
} ,
getConvexHull : function ( latLngs ) {
//find first baseline
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 = [ ] ,
hullLatLng = [ ] ,
hull , p , i ;
for ( i = childMarkers . length - 1 ; i >= 0 ; i -- ) {
p = childMarkers [ i ] . getLatLng ( ) ;
points . push ( p ) ;
}
hull = L . QuickHull . getConvexHull ( points ) ;
for ( i = hull . length - 1 ; i >= 0 ; i -- ) {
hullLatLng . push ( hull [ i ] [ 0 ] ) ;
}
return hullLatLng ;
}
} ) ;
//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 ) {
var circumference = this . _circleFootSeparation * ( 2 + count ) ,
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 ) {
var legLength = this . _spiralLengthStart ,
angle = 0 ,
res = [ ] ,
i ;
res . length = count ;
for ( i = count - 1 ; i >= 0 ; i -- ) {
angle += this . _spiralFootSeparation / 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-07-25 10:53:38 +12:00
legLength += this . _2PI * this . _spiralLengthFactor / angle ;
}
return res ;
}
} ) ;
L . MarkerCluster . include ( ! L . DomUtil . TRANSITION ? {
//Non Animated versions of everything
_animationSpiderfy : function ( childMarkers , positions ) {
var group = this . _group ,
map = group . _map ,
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 ) ;
2012-07-25 10:53:38 +12:00
m . setZIndexOffset ( 1000000 ) ; //Make these appear on top of EVERYTHING
2012-08-01 15:17:51 +12:00
2012-07-25 10:53:38 +12:00
L . FeatureGroup . prototype . addLayer . call ( group , 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 ( ) {
var group = this . _group ,
map = group . _map ,
childMarkers = this . getAllChildMarkers ( ) ,
m , i ;
this . setOpacity ( 1 ) ;
for ( i = childMarkers . length - 1 ; i >= 0 ; i -- ) {
m = childMarkers [ i ] ;
L . FeatureGroup . prototype . removeLayer . call ( group , m ) ;
2012-08-01 15:17:51 +12:00
m . setLatLng ( m . _preSpiderfyLatlng ) ;
delete m . _preSpiderfyLatlng ;
m . setZIndexOffset ( 0 ) ;
2012-07-25 10:53:38 +12:00
map . removeLayer ( m . _spiderLeg ) ;
delete m . _spiderLeg ;
}
}
} : {
//Animated versions here
_animationSpiderfy : function ( childMarkers , positions ) {
var me = this ,
group = this . _group ,
map = group . _map ,
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 ] ;
m . setZIndexOffset ( 1000000 ) ; //Make these appear on top of EVERYTHING
m . setOpacity ( 0 ) ;
L . FeatureGroup . prototype . addLayer . call ( group , m ) ;
2012-07-27 14:44:55 +12:00
m . _setPos ( thisLayerPos ) ;
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 ) ;
2012-07-25 16:44:42 +12:00
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-09-28 09:37:35 +12:00
if ( ! L . Path . SVG ) {
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' ) ;
2012-07-25 16:44:42 +12:00
} , 250 ) ;
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 ,
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-09-28 09:37:35 +12:00
svg = L . Path . SVG ,
2012-07-25 10:53:38 +12:00
m , i , a ;
group . _animationStart ( ) ;
//Make us visible and bring the child markers back in
this . setOpacity ( 1 ) ;
for ( i = childMarkers . length - 1 ; i >= 0 ; i -- ) {
m = childMarkers [ i ] ;
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
2012-07-27 14:44:55 +12:00
m . _setPos ( thisLayerPos ) ;
2012-07-25 10:53:38 +12:00
m . setOpacity ( 0 ) ;
//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 ;
}
m . setOpacity ( 1 ) ;
2012-07-25 10:53:38 +12:00
m . setZIndexOffset ( 0 ) ;
2012-07-27 16:00:42 +12:00
if ( stillThereChildCount > 1 ) {
L . FeatureGroup . prototype . removeLayer . call ( group , m ) ;
}
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 ( ) ;
2012-07-25 10:53:38 +12:00
} , 250 ) ;
}
} ) ;
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 ) ;
} else {
//Browsers without zoomAnimation don't fire zoomstart
this . _map . on ( 'zoomend' , this . _unspiderfyWrapper , 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
} ,
//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 ) {
L . FeatureGroup . prototype . removeLayer . call ( this , 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
}
} ) ;
} ( this ) ) ;