jekyll-theme-chaos 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +51 -0
  4. data/_includes/accordion.html +14 -0
  5. data/_includes/block.html +22 -0
  6. data/_includes/blog-list.html +122 -0
  7. data/_includes/counter.html +1 -0
  8. data/_includes/demands.html +11 -0
  9. data/_includes/desktop-header.html +21 -0
  10. data/_includes/events-gallery.html +91 -0
  11. data/_includes/footer.html +41 -0
  12. data/_includes/google-analytics.html +8 -0
  13. data/_includes/head.html +30 -0
  14. data/_includes/header.html +17 -0
  15. data/_includes/image.html +4 -0
  16. data/_includes/logo-facebook +4 -0
  17. data/_includes/logo-instagram +9 -0
  18. data/_includes/logo-share.svg +4 -0
  19. data/_includes/logo-twitter +4 -0
  20. data/_includes/logo-whatsapp.svg +8 -0
  21. data/_includes/map.html +15 -0
  22. data/_includes/mobile-header.html +41 -0
  23. data/_includes/partner-list.html +14 -0
  24. data/_includes/resources.html +8 -0
  25. data/_includes/share-links.html +26 -0
  26. data/_includes/signup-form.html +295 -0
  27. data/_includes/single-accordion.html +33 -0
  28. data/_includes/site-title.html +10 -0
  29. data/_includes/social-links.html +17 -0
  30. data/_includes/trainings_this_week +17 -0
  31. data/_includes/tweets.html +26 -0
  32. data/_layouts/an-event.html +69 -0
  33. data/_layouts/default.html +114 -0
  34. data/_layouts/event.html +30 -0
  35. data/_layouts/home.html +5 -0
  36. data/_layouts/landing.html +70 -0
  37. data/_layouts/page.html +12 -0
  38. data/_layouts/post.html +16 -0
  39. data/_layouts/redirect.html +24 -0
  40. data/_sass/an-form.scss +65 -0
  41. data/_sass/chaos/base.scss +273 -0
  42. data/_sass/chaos/block.scss +77 -0
  43. data/_sass/chaos/content.scss +546 -0
  44. data/_sass/chaos/desktop-header.scss +87 -0
  45. data/_sass/chaos/footer.scss +87 -0
  46. data/_sass/chaos/form.scss +96 -0
  47. data/_sass/chaos/header.scss +118 -0
  48. data/_sass/chaos/map.scss +54 -0
  49. data/_sass/chaos/mobile-header.scss +228 -0
  50. data/_sass/chaos/print.scss +21 -0
  51. data/_sass/chaos.scss +43 -0
  52. data/_sass/custom.scss +1 -0
  53. data/_templates/an-event.md +23 -0
  54. data/assets/css/MarkerCluster.Default.css +60 -0
  55. data/assets/css/MarkerCluster.css +14 -0
  56. data/assets/css/chaos-style.scss +173 -0
  57. data/assets/css/style.scss +2 -0
  58. data/assets/fonts/DrukCond-Super.ttf +0 -0
  59. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-Bold.eot +0 -0
  60. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-Bold.ttf +0 -0
  61. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-Bold.woff +0 -0
  62. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-Bold.woff2 +0 -0
  63. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-BoldItalic.eot +0 -0
  64. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-BoldItalic.ttf +0 -0
  65. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-BoldItalic.woff +0 -0
  66. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-BoldItalic.woff2 +0 -0
  67. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-Italic.eot +0 -0
  68. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-Italic.ttf +0 -0
  69. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-Italic.woff +0 -0
  70. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-Italic.woff2 +0 -0
  71. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-Light.eot +0 -0
  72. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-Light.ttf +0 -0
  73. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-Light.woff +0 -0
  74. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-Light.woff2 +0 -0
  75. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-LightItalic.eot +0 -0
  76. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-LightItalic.ttf +0 -0
  77. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-LightItalic.woff +0 -0
  78. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-LightItalic.woff2 +0 -0
  79. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-Medium.eot +0 -0
  80. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-Medium.ttf +0 -0
  81. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-Medium.woff +0 -0
  82. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-Medium.woff2 +0 -0
  83. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-MediumItalic.eot +0 -0
  84. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-MediumItalic.ttf +0 -0
  85. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-MediumItalic.woff +0 -0
  86. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-MediumItalic.woff2 +0 -0
  87. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-Regular.eot +0 -0
  88. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-Regular.ttf +0 -0
  89. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-Regular.woff +0 -0
  90. data/assets/fonts/aktiv-grotesk/AktivGroteskCorp-Regular.woff2 +0 -0
  91. data/assets/fonts/aktiv-grotesk/stylesheet.css +96 -0
  92. data/assets/fonts/druk/DrukCondensed-Super.woff +0 -0
  93. data/assets/fonts/druk/DrukCondensed-Super.woff2 +0 -0
  94. data/assets/fonts/druk/DrukText-Medium.woff +0 -0
  95. data/assets/fonts/druk/DrukText-Medium.woff2 +0 -0
  96. data/assets/fonts/druk/DrukText-Super-Web.woff +0 -0
  97. data/assets/fonts/druk/DrukText-Super-Web.woff2 +0 -0
  98. data/assets/js/actions-map.js +175 -0
  99. data/assets/js/an-event-page.js +31 -0
  100. data/assets/js/jquery.js +2 -0
  101. data/assets/js/leaflet.markercluster-src.js +2690 -0
  102. data/assets/js/mailing-list.js +57 -0
  103. data/assets/js.js +112 -0
  104. data/assets/link.svg +8 -0
  105. metadata +231 -0
@@ -0,0 +1,2690 @@
1
+ /*
2
+ * Leaflet.markercluster 1.4.1+master.37ab9a2,
3
+ * Provides Beautiful Animated Marker Clustering functionality for Leaflet, a JS library for interactive maps.
4
+ * https://github.com/Leaflet/Leaflet.markercluster
5
+ * (c) 2012-2017, Dave Leaver, smartrak
6
+ */
7
+ (function (global, factory) {
8
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
9
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
10
+ (factory((global.Leaflet = global.Leaflet || {}, global.Leaflet.markercluster = global.Leaflet.markercluster || {})));
11
+ }(this, (function (exports) { 'use strict';
12
+
13
+ /*
14
+ * L.MarkerClusterGroup extends L.FeatureGroup by clustering the markers contained within
15
+ */
16
+
17
+ var MarkerClusterGroup = L.MarkerClusterGroup = L.FeatureGroup.extend({
18
+
19
+ options: {
20
+ maxClusterRadius: 80, //A cluster will cover at most this many pixels from its center
21
+ iconCreateFunction: null,
22
+ clusterPane: L.Marker.prototype.options.pane,
23
+
24
+ spiderfyOnMaxZoom: true,
25
+ showCoverageOnHover: true,
26
+ zoomToBoundsOnClick: true,
27
+ singleMarkerMode: false,
28
+
29
+ disableClusteringAtZoom: null,
30
+
31
+ // Setting this to false prevents the removal of any clusters outside of the viewpoint, which
32
+ // is the default behaviour for performance reasons.
33
+ removeOutsideVisibleBounds: true,
34
+
35
+ // Set to false to disable all animations (zoom and spiderfy).
36
+ // If false, option animateAddingMarkers below has no effect.
37
+ // If L.DomUtil.TRANSITION is falsy, this option has no effect.
38
+ animate: true,
39
+
40
+ //Whether to animate adding markers after adding the MarkerClusterGroup to the map
41
+ // If you are adding individual markers set to true, if adding bulk markers leave false for massive performance gains.
42
+ animateAddingMarkers: false,
43
+
44
+ //Increase to increase the distance away that spiderfied markers appear from the center
45
+ spiderfyDistanceMultiplier: 1,
46
+
47
+ // Make it possible to specify a polyline options on a spider leg
48
+ spiderLegPolylineOptions: { weight: 1.5, color: '#222', opacity: 0.5 },
49
+
50
+ // 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
51
+ chunkedLoading: false,
52
+ chunkInterval: 200, // process markers for a maximum of ~ n milliseconds (then trigger the chunkProgress callback)
53
+ chunkDelay: 50, // at the end of each interval, give n milliseconds back to system/browser
54
+ chunkProgress: null, // progress callback: function(processed, total, elapsed) (e.g. for a progress indicator)
55
+
56
+ //Options to pass to the L.Polygon constructor
57
+ polygonOptions: {}
58
+ },
59
+
60
+ initialize: function (options) {
61
+ L.Util.setOptions(this, options);
62
+ if (!this.options.iconCreateFunction) {
63
+ this.options.iconCreateFunction = this._defaultIconCreateFunction;
64
+ }
65
+
66
+ this._featureGroup = L.featureGroup();
67
+ this._featureGroup.addEventParent(this);
68
+
69
+ this._nonPointGroup = L.featureGroup();
70
+ this._nonPointGroup.addEventParent(this);
71
+
72
+ this._inZoomAnimation = 0;
73
+ this._needsClustering = [];
74
+ this._needsRemoving = []; //Markers removed while we aren't on the map need to be kept track of
75
+ //The bounds of the currently shown area (from _getExpandedVisibleBounds) Updated on zoom/move
76
+ this._currentShownBounds = null;
77
+
78
+ this._queue = [];
79
+
80
+ this._childMarkerEventHandlers = {
81
+ 'dragstart': this._childMarkerDragStart,
82
+ 'move': this._childMarkerMoved,
83
+ 'dragend': this._childMarkerDragEnd,
84
+ };
85
+
86
+ // Hook the appropriate animation methods.
87
+ var animate = L.DomUtil.TRANSITION && this.options.animate;
88
+ L.extend(this, animate ? this._withAnimation : this._noAnimation);
89
+ // Remember which MarkerCluster class to instantiate (animated or not).
90
+ this._markerCluster = animate ? L.MarkerCluster : L.MarkerClusterNonAnimated;
91
+ },
92
+
93
+ addLayer: function (layer) {
94
+
95
+ if (layer instanceof L.LayerGroup) {
96
+ return this.addLayers([layer]);
97
+ }
98
+
99
+ //Don't cluster non point data
100
+ if (!layer.getLatLng) {
101
+ this._nonPointGroup.addLayer(layer);
102
+ this.fire('layeradd', { layer: layer });
103
+ return this;
104
+ }
105
+
106
+ if (!this._map) {
107
+ this._needsClustering.push(layer);
108
+ this.fire('layeradd', { layer: layer });
109
+ return this;
110
+ }
111
+
112
+ if (this.hasLayer(layer)) {
113
+ return this;
114
+ }
115
+
116
+
117
+ //If we have already clustered we'll need to add this one to a cluster
118
+
119
+ if (this._unspiderfy) {
120
+ this._unspiderfy();
121
+ }
122
+
123
+ this._addLayer(layer, this._maxZoom);
124
+ this.fire('layeradd', { layer: layer });
125
+
126
+ // Refresh bounds and weighted positions.
127
+ this._topClusterLevel._recalculateBounds();
128
+
129
+ this._refreshClustersIcons();
130
+
131
+ //Work out what is visible
132
+ var visibleLayer = layer,
133
+ currentZoom = this._zoom;
134
+ if (layer.__parent) {
135
+ while (visibleLayer.__parent._zoom >= currentZoom) {
136
+ visibleLayer = visibleLayer.__parent;
137
+ }
138
+ }
139
+
140
+ if (this._currentShownBounds.contains(visibleLayer.getLatLng())) {
141
+ if (this.options.animateAddingMarkers) {
142
+ this._animationAddLayer(layer, visibleLayer);
143
+ } else {
144
+ this._animationAddLayerNonAnimated(layer, visibleLayer);
145
+ }
146
+ }
147
+ return this;
148
+ },
149
+
150
+ removeLayer: function (layer) {
151
+
152
+ if (layer instanceof L.LayerGroup) {
153
+ return this.removeLayers([layer]);
154
+ }
155
+
156
+ //Non point layers
157
+ if (!layer.getLatLng) {
158
+ this._nonPointGroup.removeLayer(layer);
159
+ this.fire('layerremove', { layer: layer });
160
+ return this;
161
+ }
162
+
163
+ if (!this._map) {
164
+ if (!this._arraySplice(this._needsClustering, layer) && this.hasLayer(layer)) {
165
+ this._needsRemoving.push({ layer: layer, latlng: layer._latlng });
166
+ }
167
+ this.fire('layerremove', { layer: layer });
168
+ return this;
169
+ }
170
+
171
+ if (!layer.__parent) {
172
+ return this;
173
+ }
174
+
175
+ if (this._unspiderfy) {
176
+ this._unspiderfy();
177
+ this._unspiderfyLayer(layer);
178
+ }
179
+
180
+ //Remove the marker from clusters
181
+ this._removeLayer(layer, true);
182
+ this.fire('layerremove', { layer: layer });
183
+
184
+ // Refresh bounds and weighted positions.
185
+ this._topClusterLevel._recalculateBounds();
186
+
187
+ this._refreshClustersIcons();
188
+
189
+ layer.off(this._childMarkerEventHandlers, this);
190
+
191
+ if (this._featureGroup.hasLayer(layer)) {
192
+ this._featureGroup.removeLayer(layer);
193
+ if (layer.clusterShow) {
194
+ layer.clusterShow();
195
+ }
196
+ }
197
+
198
+ return this;
199
+ },
200
+
201
+ //Takes an array of markers and adds them in bulk
202
+ addLayers: function (layersArray, skipLayerAddEvent) {
203
+ if (!L.Util.isArray(layersArray)) {
204
+ return this.addLayer(layersArray);
205
+ }
206
+
207
+ var fg = this._featureGroup,
208
+ npg = this._nonPointGroup,
209
+ chunked = this.options.chunkedLoading,
210
+ chunkInterval = this.options.chunkInterval,
211
+ chunkProgress = this.options.chunkProgress,
212
+ l = layersArray.length,
213
+ offset = 0,
214
+ originalArray = true,
215
+ m;
216
+
217
+ if (this._map) {
218
+ var started = (new Date()).getTime();
219
+ var process = L.bind(function () {
220
+ var start = (new Date()).getTime();
221
+ for (; offset < l; offset++) {
222
+ if (chunked && offset % 200 === 0) {
223
+ // every couple hundred markers, instrument the time elapsed since processing started:
224
+ var elapsed = (new Date()).getTime() - start;
225
+ if (elapsed > chunkInterval) {
226
+ break; // been working too hard, time to take a break :-)
227
+ }
228
+ }
229
+
230
+ m = layersArray[offset];
231
+
232
+ // Group of layers, append children to layersArray and skip.
233
+ // Side effects:
234
+ // - Total increases, so chunkProgress ratio jumps backward.
235
+ // - Groups are not included in this group, only their non-group child layers (hasLayer).
236
+ // Changing array length while looping does not affect performance in current browsers:
237
+ // http://jsperf.com/for-loop-changing-length/6
238
+ if (m instanceof L.LayerGroup) {
239
+ if (originalArray) {
240
+ layersArray = layersArray.slice();
241
+ originalArray = false;
242
+ }
243
+ this._extractNonGroupLayers(m, layersArray);
244
+ l = layersArray.length;
245
+ continue;
246
+ }
247
+
248
+ //Not point data, can't be clustered
249
+ if (!m.getLatLng) {
250
+ npg.addLayer(m);
251
+ if (!skipLayerAddEvent) {
252
+ this.fire('layeradd', { layer: m });
253
+ }
254
+ continue;
255
+ }
256
+
257
+ if (this.hasLayer(m)) {
258
+ continue;
259
+ }
260
+
261
+ this._addLayer(m, this._maxZoom);
262
+ if (!skipLayerAddEvent) {
263
+ this.fire('layeradd', { layer: m });
264
+ }
265
+
266
+ //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
267
+ if (m.__parent) {
268
+ if (m.__parent.getChildCount() === 2) {
269
+ var markers = m.__parent.getAllChildMarkers(),
270
+ otherMarker = markers[0] === m ? markers[1] : markers[0];
271
+ fg.removeLayer(otherMarker);
272
+ }
273
+ }
274
+ }
275
+
276
+ if (chunkProgress) {
277
+ // report progress and time elapsed:
278
+ chunkProgress(offset, l, (new Date()).getTime() - started);
279
+ }
280
+
281
+ // Completed processing all markers.
282
+ if (offset === l) {
283
+
284
+ // Refresh bounds and weighted positions.
285
+ this._topClusterLevel._recalculateBounds();
286
+
287
+ this._refreshClustersIcons();
288
+
289
+ this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds);
290
+ } else {
291
+ setTimeout(process, this.options.chunkDelay);
292
+ }
293
+ }, this);
294
+
295
+ process();
296
+ } else {
297
+ var needsClustering = this._needsClustering;
298
+
299
+ for (; offset < l; offset++) {
300
+ m = layersArray[offset];
301
+
302
+ // Group of layers, append children to layersArray and skip.
303
+ if (m instanceof L.LayerGroup) {
304
+ if (originalArray) {
305
+ layersArray = layersArray.slice();
306
+ originalArray = false;
307
+ }
308
+ this._extractNonGroupLayers(m, layersArray);
309
+ l = layersArray.length;
310
+ continue;
311
+ }
312
+
313
+ //Not point data, can't be clustered
314
+ if (!m.getLatLng) {
315
+ npg.addLayer(m);
316
+ continue;
317
+ }
318
+
319
+ if (this.hasLayer(m)) {
320
+ continue;
321
+ }
322
+
323
+ needsClustering.push(m);
324
+ }
325
+ }
326
+ return this;
327
+ },
328
+
329
+ //Takes an array of markers and removes them in bulk
330
+ removeLayers: function (layersArray) {
331
+ var i, m,
332
+ l = layersArray.length,
333
+ fg = this._featureGroup,
334
+ npg = this._nonPointGroup,
335
+ originalArray = true;
336
+
337
+ if (!this._map) {
338
+ for (i = 0; i < l; i++) {
339
+ m = layersArray[i];
340
+
341
+ // Group of layers, append children to layersArray and skip.
342
+ if (m instanceof L.LayerGroup) {
343
+ if (originalArray) {
344
+ layersArray = layersArray.slice();
345
+ originalArray = false;
346
+ }
347
+ this._extractNonGroupLayers(m, layersArray);
348
+ l = layersArray.length;
349
+ continue;
350
+ }
351
+
352
+ this._arraySplice(this._needsClustering, m);
353
+ npg.removeLayer(m);
354
+ if (this.hasLayer(m)) {
355
+ this._needsRemoving.push({ layer: m, latlng: m._latlng });
356
+ }
357
+ this.fire('layerremove', { layer: m });
358
+ }
359
+ return this;
360
+ }
361
+
362
+ if (this._unspiderfy) {
363
+ this._unspiderfy();
364
+
365
+ // Work on a copy of the array, so that next loop is not affected.
366
+ var layersArray2 = layersArray.slice(),
367
+ l2 = l;
368
+ for (i = 0; i < l2; i++) {
369
+ m = layersArray2[i];
370
+
371
+ // Group of layers, append children to layersArray and skip.
372
+ if (m instanceof L.LayerGroup) {
373
+ this._extractNonGroupLayers(m, layersArray2);
374
+ l2 = layersArray2.length;
375
+ continue;
376
+ }
377
+
378
+ this._unspiderfyLayer(m);
379
+ }
380
+ }
381
+
382
+ for (i = 0; i < l; i++) {
383
+ m = layersArray[i];
384
+
385
+ // Group of layers, append children to layersArray and skip.
386
+ if (m instanceof L.LayerGroup) {
387
+ if (originalArray) {
388
+ layersArray = layersArray.slice();
389
+ originalArray = false;
390
+ }
391
+ this._extractNonGroupLayers(m, layersArray);
392
+ l = layersArray.length;
393
+ continue;
394
+ }
395
+
396
+ if (!m.__parent) {
397
+ npg.removeLayer(m);
398
+ this.fire('layerremove', { layer: m });
399
+ continue;
400
+ }
401
+
402
+ this._removeLayer(m, true, true);
403
+ this.fire('layerremove', { layer: m });
404
+
405
+ if (fg.hasLayer(m)) {
406
+ fg.removeLayer(m);
407
+ if (m.clusterShow) {
408
+ m.clusterShow();
409
+ }
410
+ }
411
+ }
412
+
413
+ // Refresh bounds and weighted positions.
414
+ this._topClusterLevel._recalculateBounds();
415
+
416
+ this._refreshClustersIcons();
417
+
418
+ //Fix up the clusters and markers on the map
419
+ this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds);
420
+
421
+ return this;
422
+ },
423
+
424
+ //Removes all layers from the MarkerClusterGroup
425
+ clearLayers: function () {
426
+ //Need our own special implementation as the LayerGroup one doesn't work for us
427
+
428
+ //If we aren't on the map (yet), blow away the markers we know of
429
+ if (!this._map) {
430
+ this._needsClustering = [];
431
+ this._needsRemoving = [];
432
+ delete this._gridClusters;
433
+ delete this._gridUnclustered;
434
+ }
435
+
436
+ if (this._noanimationUnspiderfy) {
437
+ this._noanimationUnspiderfy();
438
+ }
439
+
440
+ //Remove all the visible layers
441
+ this._featureGroup.clearLayers();
442
+ this._nonPointGroup.clearLayers();
443
+
444
+ this.eachLayer(function (marker) {
445
+ marker.off(this._childMarkerEventHandlers, this);
446
+ delete marker.__parent;
447
+ }, this);
448
+
449
+ if (this._map) {
450
+ //Reset _topClusterLevel and the DistanceGrids
451
+ this._generateInitialClusters();
452
+ }
453
+
454
+ return this;
455
+ },
456
+
457
+ //Override FeatureGroup.getBounds as it doesn't work
458
+ getBounds: function () {
459
+ var bounds = new L.LatLngBounds();
460
+
461
+ if (this._topClusterLevel) {
462
+ bounds.extend(this._topClusterLevel._bounds);
463
+ }
464
+
465
+ for (var i = this._needsClustering.length - 1; i >= 0; i--) {
466
+ bounds.extend(this._needsClustering[i].getLatLng());
467
+ }
468
+
469
+ bounds.extend(this._nonPointGroup.getBounds());
470
+
471
+ return bounds;
472
+ },
473
+
474
+ //Overrides LayerGroup.eachLayer
475
+ eachLayer: function (method, context) {
476
+ var markers = this._needsClustering.slice(),
477
+ needsRemoving = this._needsRemoving,
478
+ thisNeedsRemoving, i, j;
479
+
480
+ if (this._topClusterLevel) {
481
+ this._topClusterLevel.getAllChildMarkers(markers);
482
+ }
483
+
484
+ for (i = markers.length - 1; i >= 0; i--) {
485
+ thisNeedsRemoving = true;
486
+
487
+ for (j = needsRemoving.length - 1; j >= 0; j--) {
488
+ if (needsRemoving[j].layer === markers[i]) {
489
+ thisNeedsRemoving = false;
490
+ break;
491
+ }
492
+ }
493
+
494
+ if (thisNeedsRemoving) {
495
+ method.call(context, markers[i]);
496
+ }
497
+ }
498
+
499
+ this._nonPointGroup.eachLayer(method, context);
500
+ },
501
+
502
+ //Overrides LayerGroup.getLayers
503
+ getLayers: function () {
504
+ var layers = [];
505
+ this.eachLayer(function (l) {
506
+ layers.push(l);
507
+ });
508
+ return layers;
509
+ },
510
+
511
+ //Overrides LayerGroup.getLayer, WARNING: Really bad performance
512
+ getLayer: function (id) {
513
+ var result = null;
514
+
515
+ id = parseInt(id, 10);
516
+
517
+ this.eachLayer(function (l) {
518
+ if (L.stamp(l) === id) {
519
+ result = l;
520
+ }
521
+ });
522
+
523
+ return result;
524
+ },
525
+
526
+ //Returns true if the given layer is in this MarkerClusterGroup
527
+ hasLayer: function (layer) {
528
+ if (!layer) {
529
+ return false;
530
+ }
531
+
532
+ var i, anArray = this._needsClustering;
533
+
534
+ for (i = anArray.length - 1; i >= 0; i--) {
535
+ if (anArray[i] === layer) {
536
+ return true;
537
+ }
538
+ }
539
+
540
+ anArray = this._needsRemoving;
541
+ for (i = anArray.length - 1; i >= 0; i--) {
542
+ if (anArray[i].layer === layer) {
543
+ return false;
544
+ }
545
+ }
546
+
547
+ return !!(layer.__parent && layer.__parent._group === this) || this._nonPointGroup.hasLayer(layer);
548
+ },
549
+
550
+ //Zoom down to show the given layer (spiderfying if necessary) then calls the callback
551
+ zoomToShowLayer: function (layer, callback) {
552
+
553
+ if (typeof callback !== 'function') {
554
+ callback = function () {};
555
+ }
556
+
557
+ var showMarker = function () {
558
+ if ((layer._icon || layer.__parent._icon) && !this._inZoomAnimation) {
559
+ this._map.off('moveend', showMarker, this);
560
+ this.off('animationend', showMarker, this);
561
+
562
+ if (layer._icon) {
563
+ callback();
564
+ } else if (layer.__parent._icon) {
565
+ this.once('spiderfied', callback, this);
566
+ layer.__parent.spiderfy();
567
+ }
568
+ }
569
+ };
570
+
571
+ if (layer._icon && this._map.getBounds().contains(layer.getLatLng())) {
572
+ //Layer is visible ond on screen, immediate return
573
+ callback();
574
+ } else if (layer.__parent._zoom < Math.round(this._map._zoom)) {
575
+ //Layer should be visible at this zoom level. It must not be on screen so just pan over to it
576
+ this._map.on('moveend', showMarker, this);
577
+ this._map.panTo(layer.getLatLng());
578
+ } else {
579
+ this._map.on('moveend', showMarker, this);
580
+ this.on('animationend', showMarker, this);
581
+ layer.__parent.zoomToBounds();
582
+ }
583
+ },
584
+
585
+ //Overrides FeatureGroup.onAdd
586
+ onAdd: function (map) {
587
+ this._map = map;
588
+ var i, l, layer;
589
+
590
+ if (!isFinite(this._map.getMaxZoom())) {
591
+ throw "Map has no maxZoom specified";
592
+ }
593
+
594
+ this._featureGroup.addTo(map);
595
+ this._nonPointGroup.addTo(map);
596
+
597
+ if (!this._gridClusters) {
598
+ this._generateInitialClusters();
599
+ }
600
+
601
+ this._maxLat = map.options.crs.projection.MAX_LATITUDE;
602
+
603
+ //Restore all the positions as they are in the MCG before removing them
604
+ for (i = 0, l = this._needsRemoving.length; i < l; i++) {
605
+ layer = this._needsRemoving[i];
606
+ layer.newlatlng = layer.layer._latlng;
607
+ layer.layer._latlng = layer.latlng;
608
+ }
609
+ //Remove them, then restore their new positions
610
+ for (i = 0, l = this._needsRemoving.length; i < l; i++) {
611
+ layer = this._needsRemoving[i];
612
+ this._removeLayer(layer.layer, true);
613
+ layer.layer._latlng = layer.newlatlng;
614
+ }
615
+ this._needsRemoving = [];
616
+
617
+ //Remember the current zoom level and bounds
618
+ this._zoom = Math.round(this._map._zoom);
619
+ this._currentShownBounds = this._getExpandedVisibleBounds();
620
+
621
+ this._map.on('zoomend', this._zoomEnd, this);
622
+ this._map.on('moveend', this._moveEnd, this);
623
+
624
+ if (this._spiderfierOnAdd) { //TODO FIXME: Not sure how to have spiderfier add something on here nicely
625
+ this._spiderfierOnAdd();
626
+ }
627
+
628
+ this._bindEvents();
629
+
630
+ //Actually add our markers to the map:
631
+ l = this._needsClustering;
632
+ this._needsClustering = [];
633
+ this.addLayers(l, true);
634
+ },
635
+
636
+ //Overrides FeatureGroup.onRemove
637
+ onRemove: function (map) {
638
+ map.off('zoomend', this._zoomEnd, this);
639
+ map.off('moveend', this._moveEnd, this);
640
+
641
+ this._unbindEvents();
642
+
643
+ //In case we are in a cluster animation
644
+ this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-cluster-anim', '');
645
+
646
+ if (this._spiderfierOnRemove) { //TODO FIXME: Not sure how to have spiderfier add something on here nicely
647
+ this._spiderfierOnRemove();
648
+ }
649
+
650
+ delete this._maxLat;
651
+
652
+ //Clean up all the layers we added to the map
653
+ this._hideCoverage();
654
+ this._featureGroup.remove();
655
+ this._nonPointGroup.remove();
656
+
657
+ this._featureGroup.clearLayers();
658
+
659
+ this._map = null;
660
+ },
661
+
662
+ getVisibleParent: function (marker) {
663
+ var vMarker = marker;
664
+ while (vMarker && !vMarker._icon) {
665
+ vMarker = vMarker.__parent;
666
+ }
667
+ return vMarker || null;
668
+ },
669
+
670
+ //Remove the given object from the given array
671
+ _arraySplice: function (anArray, obj) {
672
+ for (var i = anArray.length - 1; i >= 0; i--) {
673
+ if (anArray[i] === obj) {
674
+ anArray.splice(i, 1);
675
+ return true;
676
+ }
677
+ }
678
+ },
679
+
680
+ /**
681
+ * Removes a marker from all _gridUnclustered zoom levels, starting at the supplied zoom.
682
+ * @param marker to be removed from _gridUnclustered.
683
+ * @param z integer bottom start zoom level (included)
684
+ * @private
685
+ */
686
+ _removeFromGridUnclustered: function (marker, z) {
687
+ var map = this._map,
688
+ gridUnclustered = this._gridUnclustered,
689
+ minZoom = Math.floor(this._map.getMinZoom());
690
+
691
+ for (; z >= minZoom; z--) {
692
+ if (!gridUnclustered[z].removeObject(marker, map.project(marker.getLatLng(), z))) {
693
+ break;
694
+ }
695
+ }
696
+ },
697
+
698
+ _childMarkerDragStart: function (e) {
699
+ e.target.__dragStart = e.target._latlng;
700
+ },
701
+
702
+ _childMarkerMoved: function (e) {
703
+ if (!this._ignoreMove && !e.target.__dragStart) {
704
+ var isPopupOpen = e.target._popup && e.target._popup.isOpen();
705
+
706
+ this._moveChild(e.target, e.oldLatLng, e.latlng);
707
+
708
+ if (isPopupOpen) {
709
+ e.target.openPopup();
710
+ }
711
+ }
712
+ },
713
+
714
+ _moveChild: function (layer, from, to) {
715
+ layer._latlng = from;
716
+ this.removeLayer(layer);
717
+
718
+ layer._latlng = to;
719
+ this.addLayer(layer);
720
+ },
721
+
722
+ _childMarkerDragEnd: function (e) {
723
+ var dragStart = e.target.__dragStart;
724
+ delete e.target.__dragStart;
725
+ if (dragStart) {
726
+ this._moveChild(e.target, dragStart, e.target._latlng);
727
+ }
728
+ },
729
+
730
+
731
+ //Internal function for removing a marker from everything.
732
+ //dontUpdateMap: set to true if you will handle updating the map manually (for bulk functions)
733
+ _removeLayer: function (marker, removeFromDistanceGrid, dontUpdateMap) {
734
+ var gridClusters = this._gridClusters,
735
+ gridUnclustered = this._gridUnclustered,
736
+ fg = this._featureGroup,
737
+ map = this._map,
738
+ minZoom = Math.floor(this._map.getMinZoom());
739
+
740
+ //Remove the marker from distance clusters it might be in
741
+ if (removeFromDistanceGrid) {
742
+ this._removeFromGridUnclustered(marker, this._maxZoom);
743
+ }
744
+
745
+ //Work our way up the clusters removing them as we go if required
746
+ var cluster = marker.__parent,
747
+ markers = cluster._markers,
748
+ otherMarker;
749
+
750
+ //Remove the marker from the immediate parents marker list
751
+ this._arraySplice(markers, marker);
752
+
753
+ while (cluster) {
754
+ cluster._childCount--;
755
+ cluster._boundsNeedUpdate = true;
756
+
757
+ if (cluster._zoom < minZoom) {
758
+ //Top level, do nothing
759
+ break;
760
+ } else if (removeFromDistanceGrid && cluster._childCount <= 1) { //Cluster no longer required
761
+ //We need to push the other marker up to the parent
762
+ otherMarker = cluster._markers[0] === marker ? cluster._markers[1] : cluster._markers[0];
763
+
764
+ //Update distance grid
765
+ gridClusters[cluster._zoom].removeObject(cluster, map.project(cluster._cLatLng, cluster._zoom));
766
+ gridUnclustered[cluster._zoom].addObject(otherMarker, map.project(otherMarker.getLatLng(), cluster._zoom));
767
+
768
+ //Move otherMarker up to parent
769
+ this._arraySplice(cluster.__parent._childClusters, cluster);
770
+ cluster.__parent._markers.push(otherMarker);
771
+ otherMarker.__parent = cluster.__parent;
772
+
773
+ if (cluster._icon) {
774
+ //Cluster is currently on the map, need to put the marker on the map instead
775
+ fg.removeLayer(cluster);
776
+ if (!dontUpdateMap) {
777
+ fg.addLayer(otherMarker);
778
+ }
779
+ }
780
+ } else {
781
+ cluster._iconNeedsUpdate = true;
782
+ }
783
+
784
+ cluster = cluster.__parent;
785
+ }
786
+
787
+ delete marker.__parent;
788
+ },
789
+
790
+ _isOrIsParent: function (el, oel) {
791
+ while (oel) {
792
+ if (el === oel) {
793
+ return true;
794
+ }
795
+ oel = oel.parentNode;
796
+ }
797
+ return false;
798
+ },
799
+
800
+ //Override L.Evented.fire
801
+ fire: function (type, data, propagate) {
802
+ if (data && data.layer instanceof L.MarkerCluster) {
803
+ //Prevent multiple clustermouseover/off events if the icon is made up of stacked divs (Doesn't work in ie <= 8, no relatedTarget)
804
+ if (data.originalEvent && this._isOrIsParent(data.layer._icon, data.originalEvent.relatedTarget)) {
805
+ return;
806
+ }
807
+ type = 'cluster' + type;
808
+ }
809
+
810
+ L.FeatureGroup.prototype.fire.call(this, type, data, propagate);
811
+ },
812
+
813
+ //Override L.Evented.listens
814
+ listens: function (type, propagate) {
815
+ return L.FeatureGroup.prototype.listens.call(this, type, propagate) || L.FeatureGroup.prototype.listens.call(this, 'cluster' + type, propagate);
816
+ },
817
+
818
+ //Default functionality
819
+ _defaultIconCreateFunction: function (cluster) {
820
+ var childCount = cluster.getChildCount();
821
+
822
+ var c = ' marker-cluster-';
823
+ if (childCount < 10) {
824
+ c += 'small';
825
+ } else if (childCount < 100) {
826
+ c += 'medium';
827
+ } else {
828
+ c += 'large';
829
+ }
830
+
831
+ return new L.DivIcon({ html: '<div><span>' + childCount + '</span></div>', className: 'marker-cluster' + c, iconSize: new L.Point(40, 40) });
832
+ },
833
+
834
+ _bindEvents: function () {
835
+ var map = this._map,
836
+ spiderfyOnMaxZoom = this.options.spiderfyOnMaxZoom,
837
+ showCoverageOnHover = this.options.showCoverageOnHover,
838
+ zoomToBoundsOnClick = this.options.zoomToBoundsOnClick;
839
+
840
+ //Zoom on cluster click or spiderfy if we are at the lowest level
841
+ if (spiderfyOnMaxZoom || zoomToBoundsOnClick) {
842
+ this.on('clusterclick', this._zoomOrSpiderfy, this);
843
+ }
844
+
845
+ //Show convex hull (boundary) polygon on mouse over
846
+ if (showCoverageOnHover) {
847
+ this.on('clustermouseover', this._showCoverage, this);
848
+ this.on('clustermouseout', this._hideCoverage, this);
849
+ map.on('zoomend', this._hideCoverage, this);
850
+ }
851
+ },
852
+
853
+ _zoomOrSpiderfy: function (e) {
854
+ var cluster = e.layer,
855
+ bottomCluster = cluster;
856
+
857
+ while (bottomCluster._childClusters.length === 1) {
858
+ bottomCluster = bottomCluster._childClusters[0];
859
+ }
860
+
861
+ if (bottomCluster._zoom === this._maxZoom &&
862
+ bottomCluster._childCount === cluster._childCount &&
863
+ this.options.spiderfyOnMaxZoom) {
864
+
865
+ // All child markers are contained in a single cluster from this._maxZoom to this cluster.
866
+ cluster.spiderfy();
867
+ } else if (this.options.zoomToBoundsOnClick) {
868
+ cluster.zoomToBounds();
869
+ }
870
+
871
+ // Focus the map again for keyboard users.
872
+ if (e.originalEvent && e.originalEvent.keyCode === 13) {
873
+ this._map._container.focus();
874
+ }
875
+ },
876
+
877
+ _showCoverage: function (e) {
878
+ var map = this._map;
879
+ if (this._inZoomAnimation) {
880
+ return;
881
+ }
882
+ if (this._shownPolygon) {
883
+ map.removeLayer(this._shownPolygon);
884
+ }
885
+ if (e.layer.getChildCount() > 2 && e.layer !== this._spiderfied) {
886
+ this._shownPolygon = new L.Polygon(e.layer.getConvexHull(), this.options.polygonOptions);
887
+ map.addLayer(this._shownPolygon);
888
+ }
889
+ },
890
+
891
+ _hideCoverage: function () {
892
+ if (this._shownPolygon) {
893
+ this._map.removeLayer(this._shownPolygon);
894
+ this._shownPolygon = null;
895
+ }
896
+ },
897
+
898
+ _unbindEvents: function () {
899
+ var spiderfyOnMaxZoom = this.options.spiderfyOnMaxZoom,
900
+ showCoverageOnHover = this.options.showCoverageOnHover,
901
+ zoomToBoundsOnClick = this.options.zoomToBoundsOnClick,
902
+ map = this._map;
903
+
904
+ if (spiderfyOnMaxZoom || zoomToBoundsOnClick) {
905
+ this.off('clusterclick', this._zoomOrSpiderfy, this);
906
+ }
907
+ if (showCoverageOnHover) {
908
+ this.off('clustermouseover', this._showCoverage, this);
909
+ this.off('clustermouseout', this._hideCoverage, this);
910
+ map.off('zoomend', this._hideCoverage, this);
911
+ }
912
+ },
913
+
914
+ _zoomEnd: function () {
915
+ if (!this._map) { //May have been removed from the map by a zoomEnd handler
916
+ return;
917
+ }
918
+ this._mergeSplitClusters();
919
+
920
+ this._zoom = Math.round(this._map._zoom);
921
+ this._currentShownBounds = this._getExpandedVisibleBounds();
922
+ },
923
+
924
+ _moveEnd: function () {
925
+ if (this._inZoomAnimation) {
926
+ return;
927
+ }
928
+
929
+ var newBounds = this._getExpandedVisibleBounds();
930
+
931
+ this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), this._zoom, newBounds);
932
+ this._topClusterLevel._recursivelyAddChildrenToMap(null, Math.round(this._map._zoom), newBounds);
933
+
934
+ this._currentShownBounds = newBounds;
935
+ return;
936
+ },
937
+
938
+ _generateInitialClusters: function () {
939
+ var maxZoom = Math.ceil(this._map.getMaxZoom()),
940
+ minZoom = Math.floor(this._map.getMinZoom()),
941
+ radius = this.options.maxClusterRadius,
942
+ radiusFn = radius;
943
+
944
+ //If we just set maxClusterRadius to a single number, we need to create
945
+ //a simple function to return that number. Otherwise, we just have to
946
+ //use the function we've passed in.
947
+ if (typeof radius !== "function") {
948
+ radiusFn = function () { return radius; };
949
+ }
950
+
951
+ if (this.options.disableClusteringAtZoom !== null) {
952
+ maxZoom = this.options.disableClusteringAtZoom - 1;
953
+ }
954
+ this._maxZoom = maxZoom;
955
+ this._gridClusters = {};
956
+ this._gridUnclustered = {};
957
+
958
+ //Set up DistanceGrids for each zoom
959
+ for (var zoom = maxZoom; zoom >= minZoom; zoom--) {
960
+ this._gridClusters[zoom] = new L.DistanceGrid(radiusFn(zoom));
961
+ this._gridUnclustered[zoom] = new L.DistanceGrid(radiusFn(zoom));
962
+ }
963
+
964
+ // Instantiate the appropriate L.MarkerCluster class (animated or not).
965
+ this._topClusterLevel = new this._markerCluster(this, minZoom - 1);
966
+ },
967
+
968
+ //Zoom: Zoom to start adding at (Pass this._maxZoom to start at the bottom)
969
+ _addLayer: function (layer, zoom) {
970
+ var gridClusters = this._gridClusters,
971
+ gridUnclustered = this._gridUnclustered,
972
+ minZoom = Math.floor(this._map.getMinZoom()),
973
+ markerPoint, z;
974
+
975
+ if (this.options.singleMarkerMode) {
976
+ this._overrideMarkerIcon(layer);
977
+ }
978
+
979
+ layer.on(this._childMarkerEventHandlers, this);
980
+
981
+ //Find the lowest zoom level to slot this one in
982
+ for (; zoom >= minZoom; zoom--) {
983
+ markerPoint = this._map.project(layer.getLatLng(), zoom); // calculate pixel position
984
+
985
+ //Try find a cluster close by
986
+ var closest = gridClusters[zoom].getNearObject(markerPoint);
987
+ if (closest) {
988
+ closest._addChild(layer);
989
+ layer.__parent = closest;
990
+ return;
991
+ }
992
+
993
+ //Try find a marker close by to form a new cluster with
994
+ closest = gridUnclustered[zoom].getNearObject(markerPoint);
995
+ if (closest) {
996
+ var parent = closest.__parent;
997
+ if (parent) {
998
+ this._removeLayer(closest, false);
999
+ }
1000
+
1001
+ //Create new cluster with these 2 in it
1002
+
1003
+ var newCluster = new this._markerCluster(this, zoom, closest, layer);
1004
+ gridClusters[zoom].addObject(newCluster, this._map.project(newCluster._cLatLng, zoom));
1005
+ closest.__parent = newCluster;
1006
+ layer.__parent = newCluster;
1007
+
1008
+ //First create any new intermediate parent clusters that don't exist
1009
+ var lastParent = newCluster;
1010
+ for (z = zoom - 1; z > parent._zoom; z--) {
1011
+ lastParent = new this._markerCluster(this, z, lastParent);
1012
+ gridClusters[z].addObject(lastParent, this._map.project(closest.getLatLng(), z));
1013
+ }
1014
+ parent._addChild(lastParent);
1015
+
1016
+ //Remove closest from this zoom level and any above that it is in, replace with newCluster
1017
+ this._removeFromGridUnclustered(closest, zoom);
1018
+
1019
+ return;
1020
+ }
1021
+
1022
+ //Didn't manage to cluster in at this zoom, record us as a marker here and continue upwards
1023
+ gridUnclustered[zoom].addObject(layer, markerPoint);
1024
+ }
1025
+
1026
+ //Didn't get in anything, add us to the top
1027
+ this._topClusterLevel._addChild(layer);
1028
+ layer.__parent = this._topClusterLevel;
1029
+ return;
1030
+ },
1031
+
1032
+ /**
1033
+ * Refreshes the icon of all "dirty" visible clusters.
1034
+ * Non-visible "dirty" clusters will be updated when they are added to the map.
1035
+ * @private
1036
+ */
1037
+ _refreshClustersIcons: function () {
1038
+ this._featureGroup.eachLayer(function (c) {
1039
+ if (c instanceof L.MarkerCluster && c._iconNeedsUpdate) {
1040
+ c._updateIcon();
1041
+ }
1042
+ });
1043
+ },
1044
+
1045
+ //Enqueue code to fire after the marker expand/contract has happened
1046
+ _enqueue: function (fn) {
1047
+ this._queue.push(fn);
1048
+ if (!this._queueTimeout) {
1049
+ this._queueTimeout = setTimeout(L.bind(this._processQueue, this), 300);
1050
+ }
1051
+ },
1052
+ _processQueue: function () {
1053
+ for (var i = 0; i < this._queue.length; i++) {
1054
+ this._queue[i].call(this);
1055
+ }
1056
+ this._queue.length = 0;
1057
+ clearTimeout(this._queueTimeout);
1058
+ this._queueTimeout = null;
1059
+ },
1060
+
1061
+ //Merge and split any existing clusters that are too big or small
1062
+ _mergeSplitClusters: function () {
1063
+ var mapZoom = Math.round(this._map._zoom);
1064
+
1065
+ //In case we are starting to split before the animation finished
1066
+ this._processQueue();
1067
+
1068
+ if (this._zoom < mapZoom && this._currentShownBounds.intersects(this._getExpandedVisibleBounds())) { //Zoom in, split
1069
+ this._animationStart();
1070
+ //Remove clusters now off screen
1071
+ this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), this._zoom, this._getExpandedVisibleBounds());
1072
+
1073
+ this._animationZoomIn(this._zoom, mapZoom);
1074
+
1075
+ } else if (this._zoom > mapZoom) { //Zoom out, merge
1076
+ this._animationStart();
1077
+
1078
+ this._animationZoomOut(this._zoom, mapZoom);
1079
+ } else {
1080
+ this._moveEnd();
1081
+ }
1082
+ },
1083
+
1084
+ //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)
1085
+ _getExpandedVisibleBounds: function () {
1086
+ if (!this.options.removeOutsideVisibleBounds) {
1087
+ return this._mapBoundsInfinite;
1088
+ } else if (L.Browser.mobile) {
1089
+ return this._checkBoundsMaxLat(this._map.getBounds());
1090
+ }
1091
+
1092
+ return this._checkBoundsMaxLat(this._map.getBounds().pad(1)); // Padding expands the bounds by its own dimensions but scaled with the given factor.
1093
+ },
1094
+
1095
+ /**
1096
+ * Expands the latitude to Infinity (or -Infinity) if the input bounds reach the map projection maximum defined latitude
1097
+ * (in the case of Web/Spherical Mercator, it is 85.0511287798 / see https://en.wikipedia.org/wiki/Web_Mercator#Formulas).
1098
+ * Otherwise, the removeOutsideVisibleBounds option will remove markers beyond that limit, whereas the same markers without
1099
+ * this option (or outside MCG) will have their position floored (ceiled) by the projection and rendered at that limit,
1100
+ * making the user think that MCG "eats" them and never displays them again.
1101
+ * @param bounds L.LatLngBounds
1102
+ * @returns {L.LatLngBounds}
1103
+ * @private
1104
+ */
1105
+ _checkBoundsMaxLat: function (bounds) {
1106
+ var maxLat = this._maxLat;
1107
+
1108
+ if (maxLat !== undefined) {
1109
+ if (bounds.getNorth() >= maxLat) {
1110
+ bounds._northEast.lat = Infinity;
1111
+ }
1112
+ if (bounds.getSouth() <= -maxLat) {
1113
+ bounds._southWest.lat = -Infinity;
1114
+ }
1115
+ }
1116
+
1117
+ return bounds;
1118
+ },
1119
+
1120
+ //Shared animation code
1121
+ _animationAddLayerNonAnimated: function (layer, newCluster) {
1122
+ if (newCluster === layer) {
1123
+ this._featureGroup.addLayer(layer);
1124
+ } else if (newCluster._childCount === 2) {
1125
+ newCluster._addToMap();
1126
+
1127
+ var markers = newCluster.getAllChildMarkers();
1128
+ this._featureGroup.removeLayer(markers[0]);
1129
+ this._featureGroup.removeLayer(markers[1]);
1130
+ } else {
1131
+ newCluster._updateIcon();
1132
+ }
1133
+ },
1134
+
1135
+ /**
1136
+ * Extracts individual (i.e. non-group) layers from a Layer Group.
1137
+ * @param group to extract layers from.
1138
+ * @param output {Array} in which to store the extracted layers.
1139
+ * @returns {*|Array}
1140
+ * @private
1141
+ */
1142
+ _extractNonGroupLayers: function (group, output) {
1143
+ var layers = group.getLayers(),
1144
+ i = 0,
1145
+ layer;
1146
+
1147
+ output = output || [];
1148
+
1149
+ for (; i < layers.length; i++) {
1150
+ layer = layers[i];
1151
+
1152
+ if (layer instanceof L.LayerGroup) {
1153
+ this._extractNonGroupLayers(layer, output);
1154
+ continue;
1155
+ }
1156
+
1157
+ output.push(layer);
1158
+ }
1159
+
1160
+ return output;
1161
+ },
1162
+
1163
+ /**
1164
+ * Implements the singleMarkerMode option.
1165
+ * @param layer Marker to re-style using the Clusters iconCreateFunction.
1166
+ * @returns {L.Icon} The newly created icon.
1167
+ * @private
1168
+ */
1169
+ _overrideMarkerIcon: function (layer) {
1170
+ var icon = layer.options.icon = this.options.iconCreateFunction({
1171
+ getChildCount: function () {
1172
+ return 1;
1173
+ },
1174
+ getAllChildMarkers: function () {
1175
+ return [layer];
1176
+ }
1177
+ });
1178
+
1179
+ return icon;
1180
+ }
1181
+ });
1182
+
1183
+ // Constant bounds used in case option "removeOutsideVisibleBounds" is set to false.
1184
+ L.MarkerClusterGroup.include({
1185
+ _mapBoundsInfinite: new L.LatLngBounds(new L.LatLng(-Infinity, -Infinity), new L.LatLng(Infinity, Infinity))
1186
+ });
1187
+
1188
+ L.MarkerClusterGroup.include({
1189
+ _noAnimation: {
1190
+ //Non Animated versions of everything
1191
+ _animationStart: function () {
1192
+ //Do nothing...
1193
+ },
1194
+ _animationZoomIn: function (previousZoomLevel, newZoomLevel) {
1195
+ this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), previousZoomLevel);
1196
+ this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds());
1197
+
1198
+ //We didn't actually animate, but we use this event to mean "clustering animations have finished"
1199
+ this.fire('animationend');
1200
+ },
1201
+ _animationZoomOut: function (previousZoomLevel, newZoomLevel) {
1202
+ this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), previousZoomLevel);
1203
+ this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds());
1204
+
1205
+ //We didn't actually animate, but we use this event to mean "clustering animations have finished"
1206
+ this.fire('animationend');
1207
+ },
1208
+ _animationAddLayer: function (layer, newCluster) {
1209
+ this._animationAddLayerNonAnimated(layer, newCluster);
1210
+ }
1211
+ },
1212
+
1213
+ _withAnimation: {
1214
+ //Animated versions here
1215
+ _animationStart: function () {
1216
+ this._map._mapPane.className += ' leaflet-cluster-anim';
1217
+ this._inZoomAnimation++;
1218
+ },
1219
+
1220
+ _animationZoomIn: function (previousZoomLevel, newZoomLevel) {
1221
+ var bounds = this._getExpandedVisibleBounds(),
1222
+ fg = this._featureGroup,
1223
+ minZoom = Math.floor(this._map.getMinZoom()),
1224
+ i;
1225
+
1226
+ this._ignoreMove = true;
1227
+
1228
+ //Add all children of current clusters to map and remove those clusters from map
1229
+ this._topClusterLevel._recursively(bounds, previousZoomLevel, minZoom, function (c) {
1230
+ var startPos = c._latlng,
1231
+ markers = c._markers,
1232
+ m;
1233
+
1234
+ if (!bounds.contains(startPos)) {
1235
+ startPos = null;
1236
+ }
1237
+
1238
+ if (c._isSingleParent() && previousZoomLevel + 1 === newZoomLevel) { //Immediately add the new child and remove us
1239
+ fg.removeLayer(c);
1240
+ c._recursivelyAddChildrenToMap(null, newZoomLevel, bounds);
1241
+ } else {
1242
+ //Fade out old cluster
1243
+ c.clusterHide();
1244
+ c._recursivelyAddChildrenToMap(startPos, newZoomLevel, bounds);
1245
+ }
1246
+
1247
+ //Remove all markers that aren't visible any more
1248
+ //TODO: Do we actually need to do this on the higher levels too?
1249
+ for (i = markers.length - 1; i >= 0; i--) {
1250
+ m = markers[i];
1251
+ if (!bounds.contains(m._latlng)) {
1252
+ fg.removeLayer(m);
1253
+ }
1254
+ }
1255
+
1256
+ });
1257
+
1258
+ this._forceLayout();
1259
+
1260
+ //Update opacities
1261
+ this._topClusterLevel._recursivelyBecomeVisible(bounds, newZoomLevel);
1262
+ //TODO Maybe? Update markers in _recursivelyBecomeVisible
1263
+ fg.eachLayer(function (n) {
1264
+ if (!(n instanceof L.MarkerCluster) && n._icon) {
1265
+ n.clusterShow();
1266
+ }
1267
+ });
1268
+
1269
+ //update the positions of the just added clusters/markers
1270
+ this._topClusterLevel._recursively(bounds, previousZoomLevel, newZoomLevel, function (c) {
1271
+ c._recursivelyRestoreChildPositions(newZoomLevel);
1272
+ });
1273
+
1274
+ this._ignoreMove = false;
1275
+
1276
+ //Remove the old clusters and close the zoom animation
1277
+ this._enqueue(function () {
1278
+ //update the positions of the just added clusters/markers
1279
+ this._topClusterLevel._recursively(bounds, previousZoomLevel, minZoom, function (c) {
1280
+ fg.removeLayer(c);
1281
+ c.clusterShow();
1282
+ });
1283
+
1284
+ this._animationEnd();
1285
+ });
1286
+ },
1287
+
1288
+ _animationZoomOut: function (previousZoomLevel, newZoomLevel) {
1289
+ this._animationZoomOutSingle(this._topClusterLevel, previousZoomLevel - 1, newZoomLevel);
1290
+
1291
+ //Need to add markers for those that weren't on the map before but are now
1292
+ this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds());
1293
+ //Remove markers that were on the map before but won't be now
1294
+ this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), previousZoomLevel, this._getExpandedVisibleBounds());
1295
+ },
1296
+
1297
+ _animationAddLayer: function (layer, newCluster) {
1298
+ var me = this,
1299
+ fg = this._featureGroup;
1300
+
1301
+ fg.addLayer(layer);
1302
+ if (newCluster !== layer) {
1303
+ if (newCluster._childCount > 2) { //Was already a cluster
1304
+
1305
+ newCluster._updateIcon();
1306
+ this._forceLayout();
1307
+ this._animationStart();
1308
+
1309
+ layer._setPos(this._map.latLngToLayerPoint(newCluster.getLatLng()));
1310
+ layer.clusterHide();
1311
+
1312
+ this._enqueue(function () {
1313
+ fg.removeLayer(layer);
1314
+ layer.clusterShow();
1315
+
1316
+ me._animationEnd();
1317
+ });
1318
+
1319
+ } else { //Just became a cluster
1320
+ this._forceLayout();
1321
+
1322
+ me._animationStart();
1323
+ me._animationZoomOutSingle(newCluster, this._map.getMaxZoom(), this._zoom);
1324
+ }
1325
+ }
1326
+ }
1327
+ },
1328
+
1329
+ // Private methods for animated versions.
1330
+ _animationZoomOutSingle: function (cluster, previousZoomLevel, newZoomLevel) {
1331
+ var bounds = this._getExpandedVisibleBounds(),
1332
+ minZoom = Math.floor(this._map.getMinZoom());
1333
+
1334
+ //Animate all of the markers in the clusters to move to their cluster center point
1335
+ cluster._recursivelyAnimateChildrenInAndAddSelfToMap(bounds, minZoom, previousZoomLevel + 1, newZoomLevel);
1336
+
1337
+ var me = this;
1338
+
1339
+ //Update the opacity (If we immediately set it they won't animate)
1340
+ this._forceLayout();
1341
+ cluster._recursivelyBecomeVisible(bounds, newZoomLevel);
1342
+
1343
+ //TODO: Maybe use the transition timing stuff to make this more reliable
1344
+ //When the animations are done, tidy up
1345
+ this._enqueue(function () {
1346
+
1347
+ //This cluster stopped being a cluster before the timeout fired
1348
+ if (cluster._childCount === 1) {
1349
+ var m = cluster._markers[0];
1350
+ //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
1351
+ this._ignoreMove = true;
1352
+ m.setLatLng(m.getLatLng());
1353
+ this._ignoreMove = false;
1354
+ if (m.clusterShow) {
1355
+ m.clusterShow();
1356
+ }
1357
+ } else {
1358
+ cluster._recursively(bounds, newZoomLevel, minZoom, function (c) {
1359
+ c._recursivelyRemoveChildrenFromMap(bounds, minZoom, previousZoomLevel + 1);
1360
+ });
1361
+ }
1362
+ me._animationEnd();
1363
+ });
1364
+ },
1365
+
1366
+ _animationEnd: function () {
1367
+ if (this._map) {
1368
+ this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-cluster-anim', '');
1369
+ }
1370
+ this._inZoomAnimation--;
1371
+ this.fire('animationend');
1372
+ },
1373
+
1374
+ //Force a browser layout of stuff in the map
1375
+ // Should apply the current opacity and location to all elements so we can update them again for an animation
1376
+ _forceLayout: function () {
1377
+ //In my testing this works, infact offsetWidth of any element seems to work.
1378
+ //Could loop all this._layers and do this for each _icon if it stops working
1379
+
1380
+ L.Util.falseFn(document.body.offsetWidth);
1381
+ }
1382
+ });
1383
+
1384
+ L.markerClusterGroup = function (options) {
1385
+ return new L.MarkerClusterGroup(options);
1386
+ };
1387
+
1388
+ var MarkerCluster = L.MarkerCluster = L.Marker.extend({
1389
+ options: L.Icon.prototype.options,
1390
+
1391
+ initialize: function (group, zoom, a, b) {
1392
+
1393
+ L.Marker.prototype.initialize.call(this, a ? (a._cLatLng || a.getLatLng()) : new L.LatLng(0, 0),
1394
+ { icon: this, pane: group.options.clusterPane });
1395
+
1396
+ this._group = group;
1397
+ this._zoom = zoom;
1398
+
1399
+ this._markers = [];
1400
+ this._childClusters = [];
1401
+ this._childCount = 0;
1402
+ this._iconNeedsUpdate = true;
1403
+ this._boundsNeedUpdate = true;
1404
+
1405
+ this._bounds = new L.LatLngBounds();
1406
+
1407
+ if (a) {
1408
+ this._addChild(a);
1409
+ }
1410
+ if (b) {
1411
+ this._addChild(b);
1412
+ }
1413
+ },
1414
+
1415
+ //Recursively retrieve all child markers of this cluster
1416
+ getAllChildMarkers: function (storageArray, ignoreDraggedMarker) {
1417
+ storageArray = storageArray || [];
1418
+
1419
+ for (var i = this._childClusters.length - 1; i >= 0; i--) {
1420
+ this._childClusters[i].getAllChildMarkers(storageArray);
1421
+ }
1422
+
1423
+ for (var j = this._markers.length - 1; j >= 0; j--) {
1424
+ if (ignoreDraggedMarker && this._markers[j].__dragStart) {
1425
+ continue;
1426
+ }
1427
+ storageArray.push(this._markers[j]);
1428
+ }
1429
+
1430
+ return storageArray;
1431
+ },
1432
+
1433
+ //Returns the count of how many child markers we have
1434
+ getChildCount: function () {
1435
+ return this._childCount;
1436
+ },
1437
+
1438
+ //Zoom to the minimum of showing all of the child markers, or the extents of this cluster
1439
+ zoomToBounds: function (fitBoundsOptions) {
1440
+ var childClusters = this._childClusters.slice(),
1441
+ map = this._group._map,
1442
+ boundsZoom = map.getBoundsZoom(this._bounds),
1443
+ zoom = this._zoom + 1,
1444
+ mapZoom = map.getZoom(),
1445
+ i;
1446
+
1447
+ //calculate how far we need to zoom down to see all of the markers
1448
+ while (childClusters.length > 0 && boundsZoom > zoom) {
1449
+ zoom++;
1450
+ var newClusters = [];
1451
+ for (i = 0; i < childClusters.length; i++) {
1452
+ newClusters = newClusters.concat(childClusters[i]._childClusters);
1453
+ }
1454
+ childClusters = newClusters;
1455
+ }
1456
+
1457
+ if (boundsZoom > zoom) {
1458
+ this._group._map.setView(this._latlng, zoom);
1459
+ } else if (boundsZoom <= mapZoom) { //If fitBounds wouldn't zoom us down, zoom us down instead
1460
+ this._group._map.setView(this._latlng, mapZoom + 1);
1461
+ } else {
1462
+ this._group._map.fitBounds(this._bounds, fitBoundsOptions);
1463
+ }
1464
+ },
1465
+
1466
+ getBounds: function () {
1467
+ var bounds = new L.LatLngBounds();
1468
+ bounds.extend(this._bounds);
1469
+ return bounds;
1470
+ },
1471
+
1472
+ _updateIcon: function () {
1473
+ this._iconNeedsUpdate = true;
1474
+ if (this._icon) {
1475
+ this.setIcon(this);
1476
+ }
1477
+ },
1478
+
1479
+ //Cludge for Icon, we pretend to be an icon for performance
1480
+ createIcon: function () {
1481
+ if (this._iconNeedsUpdate) {
1482
+ this._iconObj = this._group.options.iconCreateFunction(this);
1483
+ this._iconNeedsUpdate = false;
1484
+ }
1485
+ return this._iconObj.createIcon();
1486
+ },
1487
+ createShadow: function () {
1488
+ return this._iconObj.createShadow();
1489
+ },
1490
+
1491
+
1492
+ _addChild: function (new1, isNotificationFromChild) {
1493
+
1494
+ this._iconNeedsUpdate = true;
1495
+
1496
+ this._boundsNeedUpdate = true;
1497
+ this._setClusterCenter(new1);
1498
+
1499
+ if (new1 instanceof L.MarkerCluster) {
1500
+ if (!isNotificationFromChild) {
1501
+ this._childClusters.push(new1);
1502
+ new1.__parent = this;
1503
+ }
1504
+ this._childCount += new1._childCount;
1505
+ } else {
1506
+ if (!isNotificationFromChild) {
1507
+ this._markers.push(new1);
1508
+ }
1509
+ this._childCount++;
1510
+ }
1511
+
1512
+ if (this.__parent) {
1513
+ this.__parent._addChild(new1, true);
1514
+ }
1515
+ },
1516
+
1517
+ /**
1518
+ * Makes sure the cluster center is set. If not, uses the child center if it is a cluster, or the marker position.
1519
+ * @param child L.MarkerCluster|L.Marker that will be used as cluster center if not defined yet.
1520
+ * @private
1521
+ */
1522
+ _setClusterCenter: function (child) {
1523
+ if (!this._cLatLng) {
1524
+ // when clustering, take position of the first point as the cluster center
1525
+ this._cLatLng = child._cLatLng || child._latlng;
1526
+ }
1527
+ },
1528
+
1529
+ /**
1530
+ * Assigns impossible bounding values so that the next extend entirely determines the new bounds.
1531
+ * This method avoids having to trash the previous L.LatLngBounds object and to create a new one, which is much slower for this class.
1532
+ * As long as the bounds are not extended, most other methods would probably fail, as they would with bounds initialized but not extended.
1533
+ * @private
1534
+ */
1535
+ _resetBounds: function () {
1536
+ var bounds = this._bounds;
1537
+
1538
+ if (bounds._southWest) {
1539
+ bounds._southWest.lat = Infinity;
1540
+ bounds._southWest.lng = Infinity;
1541
+ }
1542
+ if (bounds._northEast) {
1543
+ bounds._northEast.lat = -Infinity;
1544
+ bounds._northEast.lng = -Infinity;
1545
+ }
1546
+ },
1547
+
1548
+ _recalculateBounds: function () {
1549
+ var markers = this._markers,
1550
+ childClusters = this._childClusters,
1551
+ latSum = 0,
1552
+ lngSum = 0,
1553
+ totalCount = this._childCount,
1554
+ i, child, childLatLng, childCount;
1555
+
1556
+ // Case where all markers are removed from the map and we are left with just an empty _topClusterLevel.
1557
+ if (totalCount === 0) {
1558
+ return;
1559
+ }
1560
+
1561
+ // Reset rather than creating a new object, for performance.
1562
+ this._resetBounds();
1563
+
1564
+ // Child markers.
1565
+ for (i = 0; i < markers.length; i++) {
1566
+ childLatLng = markers[i]._latlng;
1567
+
1568
+ this._bounds.extend(childLatLng);
1569
+
1570
+ latSum += childLatLng.lat;
1571
+ lngSum += childLatLng.lng;
1572
+ }
1573
+
1574
+ // Child clusters.
1575
+ for (i = 0; i < childClusters.length; i++) {
1576
+ child = childClusters[i];
1577
+
1578
+ // Re-compute child bounds and weighted position first if necessary.
1579
+ if (child._boundsNeedUpdate) {
1580
+ child._recalculateBounds();
1581
+ }
1582
+
1583
+ this._bounds.extend(child._bounds);
1584
+
1585
+ childLatLng = child._wLatLng;
1586
+ childCount = child._childCount;
1587
+
1588
+ latSum += childLatLng.lat * childCount;
1589
+ lngSum += childLatLng.lng * childCount;
1590
+ }
1591
+
1592
+ this._latlng = this._wLatLng = new L.LatLng(latSum / totalCount, lngSum / totalCount);
1593
+
1594
+ // Reset dirty flag.
1595
+ this._boundsNeedUpdate = false;
1596
+ },
1597
+
1598
+ //Set our markers position as given and add it to the map
1599
+ _addToMap: function (startPos) {
1600
+ if (startPos) {
1601
+ this._backupLatlng = this._latlng;
1602
+ this.setLatLng(startPos);
1603
+ }
1604
+ this._group._featureGroup.addLayer(this);
1605
+ },
1606
+
1607
+ _recursivelyAnimateChildrenIn: function (bounds, center, maxZoom) {
1608
+ this._recursively(bounds, this._group._map.getMinZoom(), maxZoom - 1,
1609
+ function (c) {
1610
+ var markers = c._markers,
1611
+ i, m;
1612
+ for (i = markers.length - 1; i >= 0; i--) {
1613
+ m = markers[i];
1614
+
1615
+ //Only do it if the icon is still on the map
1616
+ if (m._icon) {
1617
+ m._setPos(center);
1618
+ m.clusterHide();
1619
+ }
1620
+ }
1621
+ },
1622
+ function (c) {
1623
+ var childClusters = c._childClusters,
1624
+ j, cm;
1625
+ for (j = childClusters.length - 1; j >= 0; j--) {
1626
+ cm = childClusters[j];
1627
+ if (cm._icon) {
1628
+ cm._setPos(center);
1629
+ cm.clusterHide();
1630
+ }
1631
+ }
1632
+ }
1633
+ );
1634
+ },
1635
+
1636
+ _recursivelyAnimateChildrenInAndAddSelfToMap: function (bounds, mapMinZoom, previousZoomLevel, newZoomLevel) {
1637
+ this._recursively(bounds, newZoomLevel, mapMinZoom,
1638
+ function (c) {
1639
+ c._recursivelyAnimateChildrenIn(bounds, c._group._map.latLngToLayerPoint(c.getLatLng()).round(), previousZoomLevel);
1640
+
1641
+ //TODO: depthToAnimateIn affects _isSingleParent, if there is a multizoom we may/may not be.
1642
+ //As a hack we only do a animation free zoom on a single level zoom, if someone does multiple levels then we always animate
1643
+ if (c._isSingleParent() && previousZoomLevel - 1 === newZoomLevel) {
1644
+ c.clusterShow();
1645
+ c._recursivelyRemoveChildrenFromMap(bounds, mapMinZoom, previousZoomLevel); //Immediately remove our children as we are replacing them. TODO previousBounds not bounds
1646
+ } else {
1647
+ c.clusterHide();
1648
+ }
1649
+
1650
+ c._addToMap();
1651
+ }
1652
+ );
1653
+ },
1654
+
1655
+ _recursivelyBecomeVisible: function (bounds, zoomLevel) {
1656
+ this._recursively(bounds, this._group._map.getMinZoom(), zoomLevel, null, function (c) {
1657
+ c.clusterShow();
1658
+ });
1659
+ },
1660
+
1661
+ _recursivelyAddChildrenToMap: function (startPos, zoomLevel, bounds) {
1662
+ this._recursively(bounds, this._group._map.getMinZoom() - 1, zoomLevel,
1663
+ function (c) {
1664
+ if (zoomLevel === c._zoom) {
1665
+ return;
1666
+ }
1667
+
1668
+ //Add our child markers at startPos (so they can be animated out)
1669
+ for (var i = c._markers.length - 1; i >= 0; i--) {
1670
+ var nm = c._markers[i];
1671
+
1672
+ if (!bounds.contains(nm._latlng)) {
1673
+ continue;
1674
+ }
1675
+
1676
+ if (startPos) {
1677
+ nm._backupLatlng = nm.getLatLng();
1678
+
1679
+ nm.setLatLng(startPos);
1680
+ if (nm.clusterHide) {
1681
+ nm.clusterHide();
1682
+ }
1683
+ }
1684
+
1685
+ c._group._featureGroup.addLayer(nm);
1686
+ }
1687
+ },
1688
+ function (c) {
1689
+ c._addToMap(startPos);
1690
+ }
1691
+ );
1692
+ },
1693
+
1694
+ _recursivelyRestoreChildPositions: function (zoomLevel) {
1695
+ //Fix positions of child markers
1696
+ for (var i = this._markers.length - 1; i >= 0; i--) {
1697
+ var nm = this._markers[i];
1698
+ if (nm._backupLatlng) {
1699
+ nm.setLatLng(nm._backupLatlng);
1700
+ delete nm._backupLatlng;
1701
+ }
1702
+ }
1703
+
1704
+ if (zoomLevel - 1 === this._zoom) {
1705
+ //Reposition child clusters
1706
+ for (var j = this._childClusters.length - 1; j >= 0; j--) {
1707
+ this._childClusters[j]._restorePosition();
1708
+ }
1709
+ } else {
1710
+ for (var k = this._childClusters.length - 1; k >= 0; k--) {
1711
+ this._childClusters[k]._recursivelyRestoreChildPositions(zoomLevel);
1712
+ }
1713
+ }
1714
+ },
1715
+
1716
+ _restorePosition: function () {
1717
+ if (this._backupLatlng) {
1718
+ this.setLatLng(this._backupLatlng);
1719
+ delete this._backupLatlng;
1720
+ }
1721
+ },
1722
+
1723
+ //exceptBounds: If set, don't remove any markers/clusters in it
1724
+ _recursivelyRemoveChildrenFromMap: function (previousBounds, mapMinZoom, zoomLevel, exceptBounds) {
1725
+ var m, i;
1726
+ this._recursively(previousBounds, mapMinZoom - 1, zoomLevel - 1,
1727
+ function (c) {
1728
+ //Remove markers at every level
1729
+ for (i = c._markers.length - 1; i >= 0; i--) {
1730
+ m = c._markers[i];
1731
+ if (!exceptBounds || !exceptBounds.contains(m._latlng)) {
1732
+ c._group._featureGroup.removeLayer(m);
1733
+ if (m.clusterShow) {
1734
+ m.clusterShow();
1735
+ }
1736
+ }
1737
+ }
1738
+ },
1739
+ function (c) {
1740
+ //Remove child clusters at just the bottom level
1741
+ for (i = c._childClusters.length - 1; i >= 0; i--) {
1742
+ m = c._childClusters[i];
1743
+ if (!exceptBounds || !exceptBounds.contains(m._latlng)) {
1744
+ c._group._featureGroup.removeLayer(m);
1745
+ if (m.clusterShow) {
1746
+ m.clusterShow();
1747
+ }
1748
+ }
1749
+ }
1750
+ }
1751
+ );
1752
+ },
1753
+
1754
+ //Run the given functions recursively to this and child clusters
1755
+ // boundsToApplyTo: a L.LatLngBounds representing the bounds of what clusters to recurse in to
1756
+ // zoomLevelToStart: zoom level to start running functions (inclusive)
1757
+ // zoomLevelToStop: zoom level to stop running functions (inclusive)
1758
+ // runAtEveryLevel: function that takes an L.MarkerCluster as an argument that should be applied on every level
1759
+ // runAtBottomLevel: function that takes an L.MarkerCluster as an argument that should be applied at only the bottom level
1760
+ _recursively: function (boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel) {
1761
+ var childClusters = this._childClusters,
1762
+ zoom = this._zoom,
1763
+ i, c;
1764
+
1765
+ if (zoomLevelToStart <= zoom) {
1766
+ if (runAtEveryLevel) {
1767
+ runAtEveryLevel(this);
1768
+ }
1769
+ if (runAtBottomLevel && zoom === zoomLevelToStop) {
1770
+ runAtBottomLevel(this);
1771
+ }
1772
+ }
1773
+
1774
+ if (zoom < zoomLevelToStart || zoom < zoomLevelToStop) {
1775
+ for (i = childClusters.length - 1; i >= 0; i--) {
1776
+ c = childClusters[i];
1777
+ if (c._boundsNeedUpdate) {
1778
+ c._recalculateBounds();
1779
+ }
1780
+ if (boundsToApplyTo.intersects(c._bounds)) {
1781
+ c._recursively(boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel);
1782
+ }
1783
+ }
1784
+ }
1785
+ },
1786
+
1787
+ //Returns true if we are the parent of only one cluster and that cluster is the same as us
1788
+ _isSingleParent: function () {
1789
+ //Don't need to check this._markers as the rest won't work if there are any
1790
+ return this._childClusters.length > 0 && this._childClusters[0]._childCount === this._childCount;
1791
+ }
1792
+ });
1793
+
1794
+ /*
1795
+ * Extends L.Marker to include two extra methods: clusterHide and clusterShow.
1796
+ *
1797
+ * They work as setOpacity(0) and setOpacity(1) respectively, but
1798
+ * don't overwrite the options.opacity
1799
+ *
1800
+ */
1801
+
1802
+ L.Marker.include({
1803
+ clusterHide: function () {
1804
+ var backup = this.options.opacity;
1805
+ this.setOpacity(0);
1806
+ this.options.opacity = backup;
1807
+ return this;
1808
+ },
1809
+
1810
+ clusterShow: function () {
1811
+ return this.setOpacity(this.options.opacity);
1812
+ }
1813
+ });
1814
+
1815
+ L.DistanceGrid = function (cellSize) {
1816
+ this._cellSize = cellSize;
1817
+ this._sqCellSize = cellSize * cellSize;
1818
+ this._grid = {};
1819
+ this._objectPoint = { };
1820
+ };
1821
+
1822
+ L.DistanceGrid.prototype = {
1823
+
1824
+ addObject: function (obj, point) {
1825
+ var x = this._getCoord(point.x),
1826
+ y = this._getCoord(point.y),
1827
+ grid = this._grid,
1828
+ row = grid[y] = grid[y] || {},
1829
+ cell = row[x] = row[x] || [],
1830
+ stamp = L.Util.stamp(obj);
1831
+
1832
+ this._objectPoint[stamp] = point;
1833
+
1834
+ cell.push(obj);
1835
+ },
1836
+
1837
+ updateObject: function (obj, point) {
1838
+ this.removeObject(obj);
1839
+ this.addObject(obj, point);
1840
+ },
1841
+
1842
+ //Returns true if the object was found
1843
+ removeObject: function (obj, point) {
1844
+ var x = this._getCoord(point.x),
1845
+ y = this._getCoord(point.y),
1846
+ grid = this._grid,
1847
+ row = grid[y] = grid[y] || {},
1848
+ cell = row[x] = row[x] || [],
1849
+ i, len;
1850
+
1851
+ delete this._objectPoint[L.Util.stamp(obj)];
1852
+
1853
+ for (i = 0, len = cell.length; i < len; i++) {
1854
+ if (cell[i] === obj) {
1855
+
1856
+ cell.splice(i, 1);
1857
+
1858
+ if (len === 1) {
1859
+ delete row[x];
1860
+ }
1861
+
1862
+ return true;
1863
+ }
1864
+ }
1865
+
1866
+ },
1867
+
1868
+ eachObject: function (fn, context) {
1869
+ var i, j, k, len, row, cell, removed,
1870
+ grid = this._grid;
1871
+
1872
+ for (i in grid) {
1873
+ row = grid[i];
1874
+
1875
+ for (j in row) {
1876
+ cell = row[j];
1877
+
1878
+ for (k = 0, len = cell.length; k < len; k++) {
1879
+ removed = fn.call(context, cell[k]);
1880
+ if (removed) {
1881
+ k--;
1882
+ len--;
1883
+ }
1884
+ }
1885
+ }
1886
+ }
1887
+ },
1888
+
1889
+ getNearObject: function (point) {
1890
+ var x = this._getCoord(point.x),
1891
+ y = this._getCoord(point.y),
1892
+ i, j, k, row, cell, len, obj, dist,
1893
+ objectPoint = this._objectPoint,
1894
+ closestDistSq = this._sqCellSize,
1895
+ closest = null;
1896
+
1897
+ for (i = y - 1; i <= y + 1; i++) {
1898
+ row = this._grid[i];
1899
+ if (row) {
1900
+
1901
+ for (j = x - 1; j <= x + 1; j++) {
1902
+ cell = row[j];
1903
+ if (cell) {
1904
+
1905
+ for (k = 0, len = cell.length; k < len; k++) {
1906
+ obj = cell[k];
1907
+ dist = this._sqDist(objectPoint[L.Util.stamp(obj)], point);
1908
+ if (dist < closestDistSq ||
1909
+ dist <= closestDistSq && closest === null) {
1910
+ closestDistSq = dist;
1911
+ closest = obj;
1912
+ }
1913
+ }
1914
+ }
1915
+ }
1916
+ }
1917
+ }
1918
+ return closest;
1919
+ },
1920
+
1921
+ _getCoord: function (x) {
1922
+ var coord = Math.floor(x / this._cellSize);
1923
+ return isFinite(coord) ? coord : x;
1924
+ },
1925
+
1926
+ _sqDist: function (p, p2) {
1927
+ var dx = p2.x - p.x,
1928
+ dy = p2.y - p.y;
1929
+ return dx * dx + dy * dy;
1930
+ }
1931
+ };
1932
+
1933
+ /* Copyright (c) 2012 the authors listed at the following URL, and/or
1934
+ the authors of referenced articles or incorporated external code:
1935
+ http://en.literateprograms.org/Quickhull_(Javascript)?action=history&offset=20120410175256
1936
+
1937
+ Permission is hereby granted, free of charge, to any person obtaining
1938
+ a copy of this software and associated documentation files (the
1939
+ "Software"), to deal in the Software without restriction, including
1940
+ without limitation the rights to use, copy, modify, merge, publish,
1941
+ distribute, sublicense, and/or sell copies of the Software, and to
1942
+ permit persons to whom the Software is furnished to do so, subject to
1943
+ the following conditions:
1944
+
1945
+ The above copyright notice and this permission notice shall be
1946
+ included in all copies or substantial portions of the Software.
1947
+
1948
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1949
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1950
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
1951
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
1952
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
1953
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
1954
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1955
+
1956
+ Retrieved from: http://en.literateprograms.org/Quickhull_(Javascript)?oldid=18434
1957
+ */
1958
+
1959
+ (function () {
1960
+ L.QuickHull = {
1961
+
1962
+ /*
1963
+ * @param {Object} cpt a point to be measured from the baseline
1964
+ * @param {Array} bl the baseline, as represented by a two-element
1965
+ * array of latlng objects.
1966
+ * @returns {Number} an approximate distance measure
1967
+ */
1968
+ getDistant: function (cpt, bl) {
1969
+ var vY = bl[1].lat - bl[0].lat,
1970
+ vX = bl[0].lng - bl[1].lng;
1971
+ return (vX * (cpt.lat - bl[0].lat) + vY * (cpt.lng - bl[0].lng));
1972
+ },
1973
+
1974
+ /*
1975
+ * @param {Array} baseLine a two-element array of latlng objects
1976
+ * representing the baseline to project from
1977
+ * @param {Array} latLngs an array of latlng objects
1978
+ * @returns {Object} the maximum point and all new points to stay
1979
+ * in consideration for the hull.
1980
+ */
1981
+ findMostDistantPointFromBaseLine: function (baseLine, latLngs) {
1982
+ var maxD = 0,
1983
+ maxPt = null,
1984
+ newPoints = [],
1985
+ i, pt, d;
1986
+
1987
+ for (i = latLngs.length - 1; i >= 0; i--) {
1988
+ pt = latLngs[i];
1989
+ d = this.getDistant(pt, baseLine);
1990
+
1991
+ if (d > 0) {
1992
+ newPoints.push(pt);
1993
+ } else {
1994
+ continue;
1995
+ }
1996
+
1997
+ if (d > maxD) {
1998
+ maxD = d;
1999
+ maxPt = pt;
2000
+ }
2001
+ }
2002
+
2003
+ return { maxPoint: maxPt, newPoints: newPoints };
2004
+ },
2005
+
2006
+
2007
+ /*
2008
+ * Given a baseline, compute the convex hull of latLngs as an array
2009
+ * of latLngs.
2010
+ *
2011
+ * @param {Array} latLngs
2012
+ * @returns {Array}
2013
+ */
2014
+ buildConvexHull: function (baseLine, latLngs) {
2015
+ var convexHullBaseLines = [],
2016
+ t = this.findMostDistantPointFromBaseLine(baseLine, latLngs);
2017
+
2018
+ if (t.maxPoint) { // if there is still a point "outside" the base line
2019
+ convexHullBaseLines =
2020
+ convexHullBaseLines.concat(
2021
+ this.buildConvexHull([baseLine[0], t.maxPoint], t.newPoints)
2022
+ );
2023
+ convexHullBaseLines =
2024
+ convexHullBaseLines.concat(
2025
+ this.buildConvexHull([t.maxPoint, baseLine[1]], t.newPoints)
2026
+ );
2027
+ return convexHullBaseLines;
2028
+ } else { // if there is no more point "outside" the base line, the current base line is part of the convex hull
2029
+ return [baseLine[0]];
2030
+ }
2031
+ },
2032
+
2033
+ /*
2034
+ * Given an array of latlngs, compute a convex hull as an array
2035
+ * of latlngs
2036
+ *
2037
+ * @param {Array} latLngs
2038
+ * @returns {Array}
2039
+ */
2040
+ getConvexHull: function (latLngs) {
2041
+ // find first baseline
2042
+ var maxLat = false, minLat = false,
2043
+ maxLng = false, minLng = false,
2044
+ maxLatPt = null, minLatPt = null,
2045
+ maxLngPt = null, minLngPt = null,
2046
+ maxPt = null, minPt = null,
2047
+ i;
2048
+
2049
+ for (i = latLngs.length - 1; i >= 0; i--) {
2050
+ var pt = latLngs[i];
2051
+ if (maxLat === false || pt.lat > maxLat) {
2052
+ maxLatPt = pt;
2053
+ maxLat = pt.lat;
2054
+ }
2055
+ if (minLat === false || pt.lat < minLat) {
2056
+ minLatPt = pt;
2057
+ minLat = pt.lat;
2058
+ }
2059
+ if (maxLng === false || pt.lng > maxLng) {
2060
+ maxLngPt = pt;
2061
+ maxLng = pt.lng;
2062
+ }
2063
+ if (minLng === false || pt.lng < minLng) {
2064
+ minLngPt = pt;
2065
+ minLng = pt.lng;
2066
+ }
2067
+ }
2068
+
2069
+ if (minLat !== maxLat) {
2070
+ minPt = minLatPt;
2071
+ maxPt = maxLatPt;
2072
+ } else {
2073
+ minPt = minLngPt;
2074
+ maxPt = maxLngPt;
2075
+ }
2076
+
2077
+ var ch = [].concat(this.buildConvexHull([minPt, maxPt], latLngs),
2078
+ this.buildConvexHull([maxPt, minPt], latLngs));
2079
+ return ch;
2080
+ }
2081
+ };
2082
+ }());
2083
+
2084
+ L.MarkerCluster.include({
2085
+ getConvexHull: function () {
2086
+ var childMarkers = this.getAllChildMarkers(),
2087
+ points = [],
2088
+ p, i;
2089
+
2090
+ for (i = childMarkers.length - 1; i >= 0; i--) {
2091
+ p = childMarkers[i].getLatLng();
2092
+ points.push(p);
2093
+ }
2094
+
2095
+ return L.QuickHull.getConvexHull(points);
2096
+ }
2097
+ });
2098
+
2099
+ //This code is 100% based on https://github.com/jawj/OverlappingMarkerSpiderfier-Leaflet
2100
+ //Huge thanks to jawj for implementing it first to make my job easy :-)
2101
+
2102
+ L.MarkerCluster.include({
2103
+
2104
+ _2PI: Math.PI * 2,
2105
+ _circleFootSeparation: 25, //related to circumference of circle
2106
+ _circleStartAngle: 0,
2107
+
2108
+ _spiralFootSeparation: 28, //related to size of spiral (experiment!)
2109
+ _spiralLengthStart: 11,
2110
+ _spiralLengthFactor: 5,
2111
+
2112
+ _circleSpiralSwitchover: 9, //show spiral instead of circle from this marker count upwards.
2113
+ // 0 -> always spiral; Infinity -> always circle
2114
+
2115
+ spiderfy: function () {
2116
+ if (this._group._spiderfied === this || this._group._inZoomAnimation) {
2117
+ return;
2118
+ }
2119
+
2120
+ var childMarkers = this.getAllChildMarkers(null, true),
2121
+ group = this._group,
2122
+ map = group._map,
2123
+ center = map.latLngToLayerPoint(this._latlng),
2124
+ positions;
2125
+
2126
+ this._group._unspiderfy();
2127
+ this._group._spiderfied = this;
2128
+
2129
+ //TODO Maybe: childMarkers order by distance to center
2130
+
2131
+ if (childMarkers.length >= this._circleSpiralSwitchover) {
2132
+ positions = this._generatePointsSpiral(childMarkers.length, center);
2133
+ } else {
2134
+ center.y += 10; // Otherwise circles look wrong => hack for standard blue icon, renders differently for other icons.
2135
+ positions = this._generatePointsCircle(childMarkers.length, center);
2136
+ }
2137
+
2138
+ this._animationSpiderfy(childMarkers, positions);
2139
+ },
2140
+
2141
+ unspiderfy: function (zoomDetails) {
2142
+ /// <param Name="zoomDetails">Argument from zoomanim if being called in a zoom animation or null otherwise</param>
2143
+ if (this._group._inZoomAnimation) {
2144
+ return;
2145
+ }
2146
+ this._animationUnspiderfy(zoomDetails);
2147
+
2148
+ this._group._spiderfied = null;
2149
+ },
2150
+
2151
+ _generatePointsCircle: function (count, centerPt) {
2152
+ var circumference = this._group.options.spiderfyDistanceMultiplier * this._circleFootSeparation * (2 + count),
2153
+ legLength = circumference / this._2PI, //radius from circumference
2154
+ angleStep = this._2PI / count,
2155
+ res = [],
2156
+ i, angle;
2157
+
2158
+ legLength = Math.max(legLength, 35); // Minimum distance to get outside the cluster icon.
2159
+
2160
+ res.length = count;
2161
+
2162
+ for (i = 0; i < count; i++) { // Clockwise, like spiral.
2163
+ angle = this._circleStartAngle + i * angleStep;
2164
+ res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round();
2165
+ }
2166
+
2167
+ return res;
2168
+ },
2169
+
2170
+ _generatePointsSpiral: function (count, centerPt) {
2171
+ var spiderfyDistanceMultiplier = this._group.options.spiderfyDistanceMultiplier,
2172
+ legLength = spiderfyDistanceMultiplier * this._spiralLengthStart,
2173
+ separation = spiderfyDistanceMultiplier * this._spiralFootSeparation,
2174
+ lengthFactor = spiderfyDistanceMultiplier * this._spiralLengthFactor * this._2PI,
2175
+ angle = 0,
2176
+ res = [],
2177
+ i;
2178
+
2179
+ res.length = count;
2180
+
2181
+ // Higher index, closer position to cluster center.
2182
+ for (i = count; i >= 0; i--) {
2183
+ // Skip the first position, so that we are already farther from center and we avoid
2184
+ // being under the default cluster icon (especially important for Circle Markers).
2185
+ if (i < count) {
2186
+ res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round();
2187
+ }
2188
+ angle += separation / legLength + i * 0.0005;
2189
+ legLength += lengthFactor / angle;
2190
+ }
2191
+ return res;
2192
+ },
2193
+
2194
+ _noanimationUnspiderfy: function () {
2195
+ var group = this._group,
2196
+ map = group._map,
2197
+ fg = group._featureGroup,
2198
+ childMarkers = this.getAllChildMarkers(null, true),
2199
+ m, i;
2200
+
2201
+ group._ignoreMove = true;
2202
+
2203
+ this.setOpacity(1);
2204
+ for (i = childMarkers.length - 1; i >= 0; i--) {
2205
+ m = childMarkers[i];
2206
+
2207
+ fg.removeLayer(m);
2208
+
2209
+ if (m._preSpiderfyLatlng) {
2210
+ m.setLatLng(m._preSpiderfyLatlng);
2211
+ delete m._preSpiderfyLatlng;
2212
+ }
2213
+ if (m.setZIndexOffset) {
2214
+ m.setZIndexOffset(0);
2215
+ }
2216
+
2217
+ if (m._spiderLeg) {
2218
+ map.removeLayer(m._spiderLeg);
2219
+ delete m._spiderLeg;
2220
+ }
2221
+ }
2222
+
2223
+ group.fire('unspiderfied', {
2224
+ cluster: this,
2225
+ markers: childMarkers
2226
+ });
2227
+ group._ignoreMove = false;
2228
+ group._spiderfied = null;
2229
+ }
2230
+ });
2231
+
2232
+ //Non Animated versions of everything
2233
+ L.MarkerClusterNonAnimated = L.MarkerCluster.extend({
2234
+ _animationSpiderfy: function (childMarkers, positions) {
2235
+ var group = this._group,
2236
+ map = group._map,
2237
+ fg = group._featureGroup,
2238
+ legOptions = this._group.options.spiderLegPolylineOptions,
2239
+ i, m, leg, newPos;
2240
+
2241
+ group._ignoreMove = true;
2242
+
2243
+ // Traverse in ascending order to make sure that inner circleMarkers are on top of further legs. Normal markers are re-ordered by newPosition.
2244
+ // The reverse order trick no longer improves performance on modern browsers.
2245
+ for (i = 0; i < childMarkers.length; i++) {
2246
+ newPos = map.layerPointToLatLng(positions[i]);
2247
+ m = childMarkers[i];
2248
+
2249
+ // Add the leg before the marker, so that in case the latter is a circleMarker, the leg is behind it.
2250
+ leg = new L.Polyline([this._latlng, newPos], legOptions);
2251
+ map.addLayer(leg);
2252
+ m._spiderLeg = leg;
2253
+
2254
+ // Now add the marker.
2255
+ m._preSpiderfyLatlng = m._latlng;
2256
+ m.setLatLng(newPos);
2257
+ if (m.setZIndexOffset) {
2258
+ m.setZIndexOffset(1000000); //Make these appear on top of EVERYTHING
2259
+ }
2260
+
2261
+ fg.addLayer(m);
2262
+ }
2263
+ this.setOpacity(0.3);
2264
+
2265
+ group._ignoreMove = false;
2266
+ group.fire('spiderfied', {
2267
+ cluster: this,
2268
+ markers: childMarkers
2269
+ });
2270
+ },
2271
+
2272
+ _animationUnspiderfy: function () {
2273
+ this._noanimationUnspiderfy();
2274
+ }
2275
+ });
2276
+
2277
+ //Animated versions here
2278
+ L.MarkerCluster.include({
2279
+
2280
+ _animationSpiderfy: function (childMarkers, positions) {
2281
+ var me = this,
2282
+ group = this._group,
2283
+ map = group._map,
2284
+ fg = group._featureGroup,
2285
+ thisLayerLatLng = this._latlng,
2286
+ thisLayerPos = map.latLngToLayerPoint(thisLayerLatLng),
2287
+ svg = L.Path.SVG,
2288
+ legOptions = L.extend({}, this._group.options.spiderLegPolylineOptions), // Copy the options so that we can modify them for animation.
2289
+ finalLegOpacity = legOptions.opacity,
2290
+ i, m, leg, legPath, legLength, newPos;
2291
+
2292
+ if (finalLegOpacity === undefined) {
2293
+ finalLegOpacity = L.MarkerClusterGroup.prototype.options.spiderLegPolylineOptions.opacity;
2294
+ }
2295
+
2296
+ if (svg) {
2297
+ // If the initial opacity of the spider leg is not 0 then it appears before the animation starts.
2298
+ legOptions.opacity = 0;
2299
+
2300
+ // Add the class for CSS transitions.
2301
+ legOptions.className = (legOptions.className || '') + ' leaflet-cluster-spider-leg';
2302
+ } else {
2303
+ // Make sure we have a defined opacity.
2304
+ legOptions.opacity = finalLegOpacity;
2305
+ }
2306
+
2307
+ group._ignoreMove = true;
2308
+
2309
+ // Add markers and spider legs to map, hidden at our center point.
2310
+ // Traverse in ascending order to make sure that inner circleMarkers are on top of further legs. Normal markers are re-ordered by newPosition.
2311
+ // The reverse order trick no longer improves performance on modern browsers.
2312
+ for (i = 0; i < childMarkers.length; i++) {
2313
+ m = childMarkers[i];
2314
+
2315
+ newPos = map.layerPointToLatLng(positions[i]);
2316
+
2317
+ // Add the leg before the marker, so that in case the latter is a circleMarker, the leg is behind it.
2318
+ leg = new L.Polyline([thisLayerLatLng, newPos], legOptions);
2319
+ map.addLayer(leg);
2320
+ m._spiderLeg = leg;
2321
+
2322
+ // Explanations: https://jakearchibald.com/2013/animated-line-drawing-svg/
2323
+ // In our case the transition property is declared in the CSS file.
2324
+ if (svg) {
2325
+ legPath = leg._path;
2326
+ legLength = legPath.getTotalLength() + 0.1; // Need a small extra length to avoid remaining dot in Firefox.
2327
+ legPath.style.strokeDasharray = legLength; // Just 1 length is enough, it will be duplicated.
2328
+ legPath.style.strokeDashoffset = legLength;
2329
+ }
2330
+
2331
+ // If it is a marker, add it now and we'll animate it out
2332
+ if (m.setZIndexOffset) {
2333
+ m.setZIndexOffset(1000000); // Make normal markers appear on top of EVERYTHING
2334
+ }
2335
+ if (m.clusterHide) {
2336
+ m.clusterHide();
2337
+ }
2338
+
2339
+ // Vectors just get immediately added
2340
+ fg.addLayer(m);
2341
+
2342
+ if (m._setPos) {
2343
+ m._setPos(thisLayerPos);
2344
+ }
2345
+ }
2346
+
2347
+ group._forceLayout();
2348
+ group._animationStart();
2349
+
2350
+ // Reveal markers and spider legs.
2351
+ for (i = childMarkers.length - 1; i >= 0; i--) {
2352
+ newPos = map.layerPointToLatLng(positions[i]);
2353
+ m = childMarkers[i];
2354
+
2355
+ //Move marker to new position
2356
+ m._preSpiderfyLatlng = m._latlng;
2357
+ m.setLatLng(newPos);
2358
+
2359
+ if (m.clusterShow) {
2360
+ m.clusterShow();
2361
+ }
2362
+
2363
+ // Animate leg (animation is actually delegated to CSS transition).
2364
+ if (svg) {
2365
+ leg = m._spiderLeg;
2366
+ legPath = leg._path;
2367
+ legPath.style.strokeDashoffset = 0;
2368
+ //legPath.style.strokeOpacity = finalLegOpacity;
2369
+ leg.setStyle({opacity: finalLegOpacity});
2370
+ }
2371
+ }
2372
+ this.setOpacity(0.3);
2373
+
2374
+ group._ignoreMove = false;
2375
+
2376
+ setTimeout(function () {
2377
+ group._animationEnd();
2378
+ group.fire('spiderfied', {
2379
+ cluster: me,
2380
+ markers: childMarkers
2381
+ });
2382
+ }, 200);
2383
+ },
2384
+
2385
+ _animationUnspiderfy: function (zoomDetails) {
2386
+ var me = this,
2387
+ group = this._group,
2388
+ map = group._map,
2389
+ fg = group._featureGroup,
2390
+ thisLayerPos = zoomDetails ? map._latLngToNewLayerPoint(this._latlng, zoomDetails.zoom, zoomDetails.center) : map.latLngToLayerPoint(this._latlng),
2391
+ childMarkers = this.getAllChildMarkers(null, true),
2392
+ svg = L.Path.SVG,
2393
+ m, i, leg, legPath, legLength, nonAnimatable;
2394
+
2395
+ group._ignoreMove = true;
2396
+ group._animationStart();
2397
+
2398
+ //Make us visible and bring the child markers back in
2399
+ this.setOpacity(1);
2400
+ for (i = childMarkers.length - 1; i >= 0; i--) {
2401
+ m = childMarkers[i];
2402
+
2403
+ //Marker was added to us after we were spiderfied
2404
+ if (!m._preSpiderfyLatlng) {
2405
+ continue;
2406
+ }
2407
+
2408
+ //Close any popup on the marker first, otherwise setting the location of the marker will make the map scroll
2409
+ m.closePopup();
2410
+
2411
+ //Fix up the location to the real one
2412
+ m.setLatLng(m._preSpiderfyLatlng);
2413
+ delete m._preSpiderfyLatlng;
2414
+
2415
+ //Hack override the location to be our center
2416
+ nonAnimatable = true;
2417
+ if (m._setPos) {
2418
+ m._setPos(thisLayerPos);
2419
+ nonAnimatable = false;
2420
+ }
2421
+ if (m.clusterHide) {
2422
+ m.clusterHide();
2423
+ nonAnimatable = false;
2424
+ }
2425
+ if (nonAnimatable) {
2426
+ fg.removeLayer(m);
2427
+ }
2428
+
2429
+ // Animate the spider leg back in (animation is actually delegated to CSS transition).
2430
+ if (svg) {
2431
+ leg = m._spiderLeg;
2432
+ legPath = leg._path;
2433
+ legLength = legPath.getTotalLength() + 0.1;
2434
+ legPath.style.strokeDashoffset = legLength;
2435
+ leg.setStyle({opacity: 0});
2436
+ }
2437
+ }
2438
+
2439
+ group._ignoreMove = false;
2440
+
2441
+ setTimeout(function () {
2442
+ //If we have only <= one child left then that marker will be shown on the map so don't remove it!
2443
+ var stillThereChildCount = 0;
2444
+ for (i = childMarkers.length - 1; i >= 0; i--) {
2445
+ m = childMarkers[i];
2446
+ if (m._spiderLeg) {
2447
+ stillThereChildCount++;
2448
+ }
2449
+ }
2450
+
2451
+
2452
+ for (i = childMarkers.length - 1; i >= 0; i--) {
2453
+ m = childMarkers[i];
2454
+
2455
+ if (!m._spiderLeg) { //Has already been unspiderfied
2456
+ continue;
2457
+ }
2458
+
2459
+ if (m.clusterShow) {
2460
+ m.clusterShow();
2461
+ }
2462
+ if (m.setZIndexOffset) {
2463
+ m.setZIndexOffset(0);
2464
+ }
2465
+
2466
+ if (stillThereChildCount > 1) {
2467
+ fg.removeLayer(m);
2468
+ }
2469
+
2470
+ map.removeLayer(m._spiderLeg);
2471
+ delete m._spiderLeg;
2472
+ }
2473
+ group._animationEnd();
2474
+ group.fire('unspiderfied', {
2475
+ cluster: me,
2476
+ markers: childMarkers
2477
+ });
2478
+ }, 200);
2479
+ }
2480
+ });
2481
+
2482
+
2483
+ L.MarkerClusterGroup.include({
2484
+ //The MarkerCluster currently spiderfied (if any)
2485
+ _spiderfied: null,
2486
+
2487
+ unspiderfy: function () {
2488
+ this._unspiderfy.apply(this, arguments);
2489
+ },
2490
+
2491
+ _spiderfierOnAdd: function () {
2492
+ this._map.on('click', this._unspiderfyWrapper, this);
2493
+
2494
+ if (this._map.options.zoomAnimation) {
2495
+ this._map.on('zoomstart', this._unspiderfyZoomStart, this);
2496
+ }
2497
+ //Browsers without zoomAnimation or a big zoom don't fire zoomstart
2498
+ this._map.on('zoomend', this._noanimationUnspiderfy, this);
2499
+
2500
+ if (!L.Browser.touch) {
2501
+ this._map.getRenderer(this);
2502
+ //Needs to happen in the pageload, not after, or animations don't work in webkit
2503
+ // http://stackoverflow.com/questions/8455200/svg-animate-with-dynamically-added-elements
2504
+ //Disable on touch browsers as the animation messes up on a touch zoom and isn't very noticable
2505
+ }
2506
+ },
2507
+
2508
+ _spiderfierOnRemove: function () {
2509
+ this._map.off('click', this._unspiderfyWrapper, this);
2510
+ this._map.off('zoomstart', this._unspiderfyZoomStart, this);
2511
+ this._map.off('zoomanim', this._unspiderfyZoomAnim, this);
2512
+ this._map.off('zoomend', this._noanimationUnspiderfy, this);
2513
+
2514
+ //Ensure that markers are back where they should be
2515
+ // Use no animation to avoid a sticky leaflet-cluster-anim class on mapPane
2516
+ this._noanimationUnspiderfy();
2517
+ },
2518
+
2519
+ //On zoom start we add a zoomanim handler so that we are guaranteed to be last (after markers are animated)
2520
+ //This means we can define the animation they do rather than Markers doing an animation to their actual location
2521
+ _unspiderfyZoomStart: function () {
2522
+ if (!this._map) { //May have been removed from the map by a zoomEnd handler
2523
+ return;
2524
+ }
2525
+
2526
+ this._map.on('zoomanim', this._unspiderfyZoomAnim, this);
2527
+ },
2528
+
2529
+ _unspiderfyZoomAnim: function (zoomDetails) {
2530
+ //Wait until the first zoomanim after the user has finished touch-zooming before running the animation
2531
+ if (L.DomUtil.hasClass(this._map._mapPane, 'leaflet-touching')) {
2532
+ return;
2533
+ }
2534
+
2535
+ this._map.off('zoomanim', this._unspiderfyZoomAnim, this);
2536
+ this._unspiderfy(zoomDetails);
2537
+ },
2538
+
2539
+ _unspiderfyWrapper: function () {
2540
+ /// <summary>_unspiderfy but passes no arguments</summary>
2541
+ this._unspiderfy();
2542
+ },
2543
+
2544
+ _unspiderfy: function (zoomDetails) {
2545
+ if (this._spiderfied) {
2546
+ this._spiderfied.unspiderfy(zoomDetails);
2547
+ }
2548
+ },
2549
+
2550
+ _noanimationUnspiderfy: function () {
2551
+ if (this._spiderfied) {
2552
+ this._spiderfied._noanimationUnspiderfy();
2553
+ }
2554
+ },
2555
+
2556
+ //If the given layer is currently being spiderfied then we unspiderfy it so it isn't on the map anymore etc
2557
+ _unspiderfyLayer: function (layer) {
2558
+ if (layer._spiderLeg) {
2559
+ this._featureGroup.removeLayer(layer);
2560
+
2561
+ if (layer.clusterShow) {
2562
+ layer.clusterShow();
2563
+ }
2564
+ //Position will be fixed up immediately in _animationUnspiderfy
2565
+ if (layer.setZIndexOffset) {
2566
+ layer.setZIndexOffset(0);
2567
+ }
2568
+
2569
+ this._map.removeLayer(layer._spiderLeg);
2570
+ delete layer._spiderLeg;
2571
+ }
2572
+ }
2573
+ });
2574
+
2575
+ /**
2576
+ * Adds 1 public method to MCG and 1 to L.Marker to facilitate changing
2577
+ * markers' icon options and refreshing their icon and their parent clusters
2578
+ * accordingly (case where their iconCreateFunction uses data of childMarkers
2579
+ * to make up the cluster icon).
2580
+ */
2581
+
2582
+
2583
+ L.MarkerClusterGroup.include({
2584
+ /**
2585
+ * Updates the icon of all clusters which are parents of the given marker(s).
2586
+ * In singleMarkerMode, also updates the given marker(s) icon.
2587
+ * @param layers L.MarkerClusterGroup|L.LayerGroup|Array(L.Marker)|Map(L.Marker)|
2588
+ * L.MarkerCluster|L.Marker (optional) list of markers (or single marker) whose parent
2589
+ * clusters need to be updated. If not provided, retrieves all child markers of this.
2590
+ * @returns {L.MarkerClusterGroup}
2591
+ */
2592
+ refreshClusters: function (layers) {
2593
+ if (!layers) {
2594
+ layers = this._topClusterLevel.getAllChildMarkers();
2595
+ } else if (layers instanceof L.MarkerClusterGroup) {
2596
+ layers = layers._topClusterLevel.getAllChildMarkers();
2597
+ } else if (layers instanceof L.LayerGroup) {
2598
+ layers = layers._layers;
2599
+ } else if (layers instanceof L.MarkerCluster) {
2600
+ layers = layers.getAllChildMarkers();
2601
+ } else if (layers instanceof L.Marker) {
2602
+ layers = [layers];
2603
+ } // else: must be an Array(L.Marker)|Map(L.Marker)
2604
+ this._flagParentsIconsNeedUpdate(layers);
2605
+ this._refreshClustersIcons();
2606
+
2607
+ // In case of singleMarkerMode, also re-draw the markers.
2608
+ if (this.options.singleMarkerMode) {
2609
+ this._refreshSingleMarkerModeMarkers(layers);
2610
+ }
2611
+
2612
+ return this;
2613
+ },
2614
+
2615
+ /**
2616
+ * Simply flags all parent clusters of the given markers as having a "dirty" icon.
2617
+ * @param layers Array(L.Marker)|Map(L.Marker) list of markers.
2618
+ * @private
2619
+ */
2620
+ _flagParentsIconsNeedUpdate: function (layers) {
2621
+ var id, parent;
2622
+
2623
+ // Assumes layers is an Array or an Object whose prototype is non-enumerable.
2624
+ for (id in layers) {
2625
+ // Flag parent clusters' icon as "dirty", all the way up.
2626
+ // Dumb process that flags multiple times upper parents, but still
2627
+ // much more efficient than trying to be smart and make short lists,
2628
+ // at least in the case of a hierarchy following a power law:
2629
+ // http://jsperf.com/flag-nodes-in-power-hierarchy/2
2630
+ parent = layers[id].__parent;
2631
+ while (parent) {
2632
+ parent._iconNeedsUpdate = true;
2633
+ parent = parent.__parent;
2634
+ }
2635
+ }
2636
+ },
2637
+
2638
+ /**
2639
+ * Re-draws the icon of the supplied markers.
2640
+ * To be used in singleMarkerMode only.
2641
+ * @param layers Array(L.Marker)|Map(L.Marker) list of markers.
2642
+ * @private
2643
+ */
2644
+ _refreshSingleMarkerModeMarkers: function (layers) {
2645
+ var id, layer;
2646
+
2647
+ for (id in layers) {
2648
+ layer = layers[id];
2649
+
2650
+ // Make sure we do not override markers that do not belong to THIS group.
2651
+ if (this.hasLayer(layer)) {
2652
+ // Need to re-create the icon first, then re-draw the marker.
2653
+ layer.setIcon(this._overrideMarkerIcon(layer));
2654
+ }
2655
+ }
2656
+ }
2657
+ });
2658
+
2659
+ L.Marker.include({
2660
+ /**
2661
+ * Updates the given options in the marker's icon and refreshes the marker.
2662
+ * @param options map object of icon options.
2663
+ * @param directlyRefreshClusters boolean (optional) true to trigger
2664
+ * MCG.refreshClustersOf() right away with this single marker.
2665
+ * @returns {L.Marker}
2666
+ */
2667
+ refreshIconOptions: function (options, directlyRefreshClusters) {
2668
+ var icon = this.options.icon;
2669
+
2670
+ L.setOptions(icon, options);
2671
+
2672
+ this.setIcon(icon);
2673
+
2674
+ // Shortcut to refresh the associated MCG clusters right away.
2675
+ // To be used when refreshing a single marker.
2676
+ // Otherwise, better use MCG.refreshClusters() once at the end with
2677
+ // the list of modified markers.
2678
+ if (directlyRefreshClusters && this.__parent) {
2679
+ this.__parent._group.refreshClusters(this);
2680
+ }
2681
+
2682
+ return this;
2683
+ }
2684
+ });
2685
+
2686
+ exports.MarkerClusterGroup = MarkerClusterGroup;
2687
+ exports.MarkerCluster = MarkerCluster;
2688
+
2689
+ })));
2690
+ //# sourceMappingURL=leaflet.markercluster-src.js.map