leaflet-with-dashoffset-canvas-fix 1.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/CHANGELOG.md +2191 -0
  2. package/LICENSE +26 -0
  3. package/README.md +3 -0
  4. package/package.json +149 -0
  5. package/src/Leaflet.js +24 -0
  6. package/src/control/Control.Attribution.js +148 -0
  7. package/src/control/Control.Layers.js +443 -0
  8. package/src/control/Control.Scale.js +132 -0
  9. package/src/control/Control.Zoom.js +146 -0
  10. package/src/control/Control.js +174 -0
  11. package/src/control/index.js +17 -0
  12. package/src/core/Browser.js +220 -0
  13. package/src/core/Class.js +135 -0
  14. package/src/core/Events.js +344 -0
  15. package/src/core/Handler.js +57 -0
  16. package/src/core/Util.js +241 -0
  17. package/src/core/index.js +15 -0
  18. package/src/dom/DomEvent.DoubleTap.js +91 -0
  19. package/src/dom/DomEvent.Pointer.js +97 -0
  20. package/src/dom/DomEvent.js +315 -0
  21. package/src/dom/DomUtil.js +349 -0
  22. package/src/dom/Draggable.js +220 -0
  23. package/src/dom/PosAnimation.js +113 -0
  24. package/src/dom/index.js +9 -0
  25. package/src/geo/LatLng.js +137 -0
  26. package/src/geo/LatLngBounds.js +251 -0
  27. package/src/geo/crs/CRS.EPSG3395.js +20 -0
  28. package/src/geo/crs/CRS.EPSG3857.js +27 -0
  29. package/src/geo/crs/CRS.EPSG4326.js +23 -0
  30. package/src/geo/crs/CRS.Earth.js +33 -0
  31. package/src/geo/crs/CRS.Simple.js +36 -0
  32. package/src/geo/crs/CRS.js +139 -0
  33. package/src/geo/crs/index.js +15 -0
  34. package/src/geo/index.js +7 -0
  35. package/src/geo/projection/Projection.LonLat.js +28 -0
  36. package/src/geo/projection/Projection.Mercator.js +49 -0
  37. package/src/geo/projection/Projection.SphericalMercator.js +44 -0
  38. package/src/geo/projection/index.js +26 -0
  39. package/src/geometry/Bounds.js +219 -0
  40. package/src/geometry/LineUtil.js +306 -0
  41. package/src/geometry/Point.js +222 -0
  42. package/src/geometry/PolyUtil.js +129 -0
  43. package/src/geometry/Transformation.js +79 -0
  44. package/src/geometry/index.js +8 -0
  45. package/src/images/layers.svg +1 -0
  46. package/src/images/logo.svg +1 -0
  47. package/src/images/marker.svg +1 -0
  48. package/src/layer/DivOverlay.js +348 -0
  49. package/src/layer/FeatureGroup.js +94 -0
  50. package/src/layer/GeoJSON.js +452 -0
  51. package/src/layer/ImageOverlay.js +270 -0
  52. package/src/layer/Layer.js +275 -0
  53. package/src/layer/LayerGroup.js +159 -0
  54. package/src/layer/Popup.js +506 -0
  55. package/src/layer/SVGOverlay.js +50 -0
  56. package/src/layer/Tooltip.js +444 -0
  57. package/src/layer/VideoOverlay.js +106 -0
  58. package/src/layer/index.js +24 -0
  59. package/src/layer/marker/DivIcon.js +74 -0
  60. package/src/layer/marker/Icon.Default.js +66 -0
  61. package/src/layer/marker/Icon.js +165 -0
  62. package/src/layer/marker/Marker.Drag.js +161 -0
  63. package/src/layer/marker/Marker.js +419 -0
  64. package/src/layer/marker/index.js +8 -0
  65. package/src/layer/tile/GridLayer.js +923 -0
  66. package/src/layer/tile/TileLayer.WMS.js +137 -0
  67. package/src/layer/tile/TileLayer.js +289 -0
  68. package/src/layer/tile/index.js +6 -0
  69. package/src/layer/vector/Canvas.js +493 -0
  70. package/src/layer/vector/Circle.js +113 -0
  71. package/src/layer/vector/CircleMarker.js +109 -0
  72. package/src/layer/vector/Path.js +148 -0
  73. package/src/layer/vector/Polygon.js +159 -0
  74. package/src/layer/vector/Polyline.js +307 -0
  75. package/src/layer/vector/Rectangle.js +57 -0
  76. package/src/layer/vector/Renderer.getRenderer.js +45 -0
  77. package/src/layer/vector/Renderer.js +133 -0
  78. package/src/layer/vector/SVG.Util.js +39 -0
  79. package/src/layer/vector/SVG.VML.js +144 -0
  80. package/src/layer/vector/SVG.js +207 -0
  81. package/src/layer/vector/index.js +14 -0
  82. package/src/map/Map.js +1751 -0
  83. package/src/map/handler/Map.BoxZoom.js +152 -0
  84. package/src/map/handler/Map.DoubleClickZoom.js +55 -0
  85. package/src/map/handler/Map.Drag.js +235 -0
  86. package/src/map/handler/Map.Keyboard.js +183 -0
  87. package/src/map/handler/Map.ScrollWheelZoom.js +91 -0
  88. package/src/map/handler/Map.TapHold.js +102 -0
  89. package/src/map/handler/Map.TouchZoom.js +130 -0
  90. package/src/map/index.js +17 -0
package/src/map/Map.js ADDED
@@ -0,0 +1,1751 @@
1
+ import * as Util from '../core/Util';
2
+ import {Evented} from '../core/Events';
3
+ import {EPSG3857} from '../geo/crs/CRS.EPSG3857';
4
+ import {Point, toPoint} from '../geometry/Point';
5
+ import {Bounds, toBounds} from '../geometry/Bounds';
6
+ import {LatLng, toLatLng} from '../geo/LatLng';
7
+ import {LatLngBounds, toLatLngBounds} from '../geo/LatLngBounds';
8
+ import Browser from '../core/Browser';
9
+ import * as DomEvent from '../dom/DomEvent';
10
+ import * as DomUtil from '../dom/DomUtil';
11
+ import {PosAnimation} from '../dom/PosAnimation';
12
+
13
+ /*
14
+ * @class Map
15
+ * @aka L.Map
16
+ * @inherits Evented
17
+ *
18
+ * The central class of the API — it is used to create a map on a page and manipulate it.
19
+ *
20
+ * @example
21
+ *
22
+ * ```js
23
+ * // initialize the map on the "map" div with a given center and zoom
24
+ * var map = L.map('map', {
25
+ * center: [51.505, -0.09],
26
+ * zoom: 13
27
+ * });
28
+ * ```
29
+ *
30
+ */
31
+
32
+ export var Map = Evented.extend({
33
+
34
+ options: {
35
+ // @section Map State Options
36
+ // @option crs: CRS = L.CRS.EPSG3857
37
+ // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
38
+ // sure what it means.
39
+ crs: EPSG3857,
40
+
41
+ // @option center: LatLng = undefined
42
+ // Initial geographic center of the map
43
+ center: undefined,
44
+
45
+ // @option zoom: Number = undefined
46
+ // Initial map zoom level
47
+ zoom: undefined,
48
+
49
+ // @option minZoom: Number = *
50
+ // Minimum zoom level of the map.
51
+ // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
52
+ // the lowest of their `minZoom` options will be used instead.
53
+ minZoom: undefined,
54
+
55
+ // @option maxZoom: Number = *
56
+ // Maximum zoom level of the map.
57
+ // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
58
+ // the highest of their `maxZoom` options will be used instead.
59
+ maxZoom: undefined,
60
+
61
+ // @option layers: Layer[] = []
62
+ // Array of layers that will be added to the map initially
63
+ layers: [],
64
+
65
+ // @option maxBounds: LatLngBounds = null
66
+ // When this option is set, the map restricts the view to the given
67
+ // geographical bounds, bouncing the user back if the user tries to pan
68
+ // outside the view. To set the restriction dynamically, use
69
+ // [`setMaxBounds`](#map-setmaxbounds) method.
70
+ maxBounds: undefined,
71
+
72
+ // @option renderer: Renderer = *
73
+ // The default method for drawing vector layers on the map. `L.SVG`
74
+ // or `L.Canvas` by default depending on browser support.
75
+ renderer: undefined,
76
+
77
+
78
+ // @section Animation Options
79
+ // @option zoomAnimation: Boolean = true
80
+ // Whether the map zoom animation is enabled. By default it's enabled
81
+ // in all browsers that support CSS3 Transitions except Android.
82
+ zoomAnimation: true,
83
+
84
+ // @option zoomAnimationThreshold: Number = 4
85
+ // Won't animate zoom if the zoom difference exceeds this value.
86
+ zoomAnimationThreshold: 4,
87
+
88
+ // @option fadeAnimation: Boolean = true
89
+ // Whether the tile fade animation is enabled. By default it's enabled
90
+ // in all browsers that support CSS3 Transitions except Android.
91
+ fadeAnimation: true,
92
+
93
+ // @option markerZoomAnimation: Boolean = true
94
+ // Whether markers animate their zoom with the zoom animation, if disabled
95
+ // they will disappear for the length of the animation. By default it's
96
+ // enabled in all browsers that support CSS3 Transitions except Android.
97
+ markerZoomAnimation: true,
98
+
99
+ // @option transform3DLimit: Number = 2^23
100
+ // Defines the maximum size of a CSS translation transform. The default
101
+ // value should not be changed unless a web browser positions layers in
102
+ // the wrong place after doing a large `panBy`.
103
+ transform3DLimit: 8388608, // Precision limit of a 32-bit float
104
+
105
+ // @section Interaction Options
106
+ // @option zoomSnap: Number = 1
107
+ // Forces the map's zoom level to always be a multiple of this, particularly
108
+ // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
109
+ // By default, the zoom level snaps to the nearest integer; lower values
110
+ // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
111
+ // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
112
+ zoomSnap: 1,
113
+
114
+ // @option zoomDelta: Number = 1
115
+ // Controls how much the map's zoom level will change after a
116
+ // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
117
+ // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
118
+ // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
119
+ zoomDelta: 1,
120
+
121
+ // @option trackResize: Boolean = true
122
+ // Whether the map automatically handles browser window resize to update itself.
123
+ trackResize: true
124
+ },
125
+
126
+ initialize: function (id, options) { // (HTMLElement or String, Object)
127
+ options = Util.setOptions(this, options);
128
+
129
+ // Make sure to assign internal flags at the beginning,
130
+ // to avoid inconsistent state in some edge cases.
131
+ this._handlers = [];
132
+ this._layers = {};
133
+ this._zoomBoundLayers = {};
134
+ this._sizeChanged = true;
135
+
136
+ this._initContainer(id);
137
+ this._initLayout();
138
+
139
+ // hack for https://github.com/Leaflet/Leaflet/issues/1980
140
+ this._onResize = Util.bind(this._onResize, this);
141
+
142
+ this._initEvents();
143
+
144
+ if (options.maxBounds) {
145
+ this.setMaxBounds(options.maxBounds);
146
+ }
147
+
148
+ if (options.zoom !== undefined) {
149
+ this._zoom = this._limitZoom(options.zoom);
150
+ }
151
+
152
+ if (options.center && options.zoom !== undefined) {
153
+ this.setView(toLatLng(options.center), options.zoom, {reset: true});
154
+ }
155
+
156
+ this.callInitHooks();
157
+
158
+ // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
159
+ this._zoomAnimated = DomUtil.TRANSITION && Browser.any3d && !Browser.mobileOpera &&
160
+ this.options.zoomAnimation;
161
+
162
+ // zoom transitions run with the same duration for all layers, so if one of transitionend events
163
+ // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
164
+ if (this._zoomAnimated) {
165
+ this._createAnimProxy();
166
+ DomEvent.on(this._proxy, DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
167
+ }
168
+
169
+ this._addLayers(this.options.layers);
170
+ },
171
+
172
+
173
+ // @section Methods for modifying map state
174
+
175
+ // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
176
+ // Sets the view of the map (geographical center and zoom) with the given
177
+ // animation options.
178
+ setView: function (center, zoom, options) {
179
+
180
+ zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
181
+ center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
182
+ options = options || {};
183
+
184
+ this._stop();
185
+
186
+ if (this._loaded && !options.reset && options !== true) {
187
+
188
+ if (options.animate !== undefined) {
189
+ options.zoom = Util.extend({animate: options.animate}, options.zoom);
190
+ options.pan = Util.extend({animate: options.animate, duration: options.duration}, options.pan);
191
+ }
192
+
193
+ // try animating pan or zoom
194
+ var moved = (this._zoom !== zoom) ?
195
+ this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
196
+ this._tryAnimatedPan(center, options.pan);
197
+
198
+ if (moved) {
199
+ // prevent resize handler call, the view will refresh after animation anyway
200
+ clearTimeout(this._sizeTimer);
201
+ return this;
202
+ }
203
+ }
204
+
205
+ // animation didn't start, just reset the map view
206
+ this._resetView(center, zoom, options.pan && options.pan.noMoveStart);
207
+
208
+ return this;
209
+ },
210
+
211
+ // @method setZoom(zoom: Number, options?: Zoom/pan options): this
212
+ // Sets the zoom of the map.
213
+ setZoom: function (zoom, options) {
214
+ if (!this._loaded) {
215
+ this._zoom = zoom;
216
+ return this;
217
+ }
218
+ return this.setView(this.getCenter(), zoom, {zoom: options});
219
+ },
220
+
221
+ // @method zoomIn(delta?: Number, options?: Zoom options): this
222
+ // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
223
+ zoomIn: function (delta, options) {
224
+ delta = delta || (Browser.any3d ? this.options.zoomDelta : 1);
225
+ return this.setZoom(this._zoom + delta, options);
226
+ },
227
+
228
+ // @method zoomOut(delta?: Number, options?: Zoom options): this
229
+ // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
230
+ zoomOut: function (delta, options) {
231
+ delta = delta || (Browser.any3d ? this.options.zoomDelta : 1);
232
+ return this.setZoom(this._zoom - delta, options);
233
+ },
234
+
235
+ // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
236
+ // Zooms the map while keeping a specified geographical point on the map
237
+ // stationary (e.g. used internally for scroll zoom and double-click zoom).
238
+ // @alternative
239
+ // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
240
+ // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
241
+ setZoomAround: function (latlng, zoom, options) {
242
+ var scale = this.getZoomScale(zoom),
243
+ viewHalf = this.getSize().divideBy(2),
244
+ containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
245
+
246
+ centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
247
+ newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
248
+
249
+ return this.setView(newCenter, zoom, {zoom: options});
250
+ },
251
+
252
+ _getBoundsCenterZoom: function (bounds, options) {
253
+
254
+ options = options || {};
255
+ bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
256
+
257
+ var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
258
+ paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
259
+
260
+ zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
261
+
262
+ zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
263
+
264
+ if (zoom === Infinity) {
265
+ return {
266
+ center: bounds.getCenter(),
267
+ zoom: zoom
268
+ };
269
+ }
270
+
271
+ var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
272
+
273
+ swPoint = this.project(bounds.getSouthWest(), zoom),
274
+ nePoint = this.project(bounds.getNorthEast(), zoom),
275
+ center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
276
+
277
+ return {
278
+ center: center,
279
+ zoom: zoom
280
+ };
281
+ },
282
+
283
+ // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
284
+ // Sets a map view that contains the given geographical bounds with the
285
+ // maximum zoom level possible.
286
+ fitBounds: function (bounds, options) {
287
+
288
+ bounds = toLatLngBounds(bounds);
289
+
290
+ if (!bounds.isValid()) {
291
+ throw new Error('Bounds are not valid.');
292
+ }
293
+
294
+ var target = this._getBoundsCenterZoom(bounds, options);
295
+ return this.setView(target.center, target.zoom, options);
296
+ },
297
+
298
+ // @method fitWorld(options?: fitBounds options): this
299
+ // Sets a map view that mostly contains the whole world with the maximum
300
+ // zoom level possible.
301
+ fitWorld: function (options) {
302
+ return this.fitBounds([[-90, -180], [90, 180]], options);
303
+ },
304
+
305
+ // @method panTo(latlng: LatLng, options?: Pan options): this
306
+ // Pans the map to a given center.
307
+ panTo: function (center, options) { // (LatLng)
308
+ return this.setView(center, this._zoom, {pan: options});
309
+ },
310
+
311
+ // @method panBy(offset: Point, options?: Pan options): this
312
+ // Pans the map by a given number of pixels (animated).
313
+ panBy: function (offset, options) {
314
+ offset = toPoint(offset).round();
315
+ options = options || {};
316
+
317
+ if (!offset.x && !offset.y) {
318
+ return this.fire('moveend');
319
+ }
320
+ // If we pan too far, Chrome gets issues with tiles
321
+ // and makes them disappear or appear in the wrong place (slightly offset) #2602
322
+ if (options.animate !== true && !this.getSize().contains(offset)) {
323
+ this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
324
+ return this;
325
+ }
326
+
327
+ if (!this._panAnim) {
328
+ this._panAnim = new PosAnimation();
329
+
330
+ this._panAnim.on({
331
+ 'step': this._onPanTransitionStep,
332
+ 'end': this._onPanTransitionEnd
333
+ }, this);
334
+ }
335
+
336
+ // don't fire movestart if animating inertia
337
+ if (!options.noMoveStart) {
338
+ this.fire('movestart');
339
+ }
340
+
341
+ // animate pan unless animate: false specified
342
+ if (options.animate !== false) {
343
+ DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
344
+
345
+ var newPos = this._getMapPanePos().subtract(offset).round();
346
+ this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
347
+ } else {
348
+ this._rawPanBy(offset);
349
+ this.fire('move').fire('moveend');
350
+ }
351
+
352
+ return this;
353
+ },
354
+
355
+ // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
356
+ // Sets the view of the map (geographical center and zoom) performing a smooth
357
+ // pan-zoom animation.
358
+ flyTo: function (targetCenter, targetZoom, options) {
359
+
360
+ options = options || {};
361
+ if (options.animate === false || !Browser.any3d) {
362
+ return this.setView(targetCenter, targetZoom, options);
363
+ }
364
+
365
+ this._stop();
366
+
367
+ var from = this.project(this.getCenter()),
368
+ to = this.project(targetCenter),
369
+ size = this.getSize(),
370
+ startZoom = this._zoom;
371
+
372
+ targetCenter = toLatLng(targetCenter);
373
+ targetZoom = targetZoom === undefined ? startZoom : targetZoom;
374
+
375
+ var w0 = Math.max(size.x, size.y),
376
+ w1 = w0 * this.getZoomScale(startZoom, targetZoom),
377
+ u1 = (to.distanceTo(from)) || 1,
378
+ rho = 1.42,
379
+ rho2 = rho * rho;
380
+
381
+ function r(i) {
382
+ var s1 = i ? -1 : 1,
383
+ s2 = i ? w1 : w0,
384
+ t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
385
+ b1 = 2 * s2 * rho2 * u1,
386
+ b = t1 / b1,
387
+ sq = Math.sqrt(b * b + 1) - b;
388
+
389
+ // workaround for floating point precision bug when sq = 0, log = -Infinite,
390
+ // thus triggering an infinite loop in flyTo
391
+ var log = sq < 0.000000001 ? -18 : Math.log(sq);
392
+
393
+ return log;
394
+ }
395
+
396
+ function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
397
+ function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
398
+ function tanh(n) { return sinh(n) / cosh(n); }
399
+
400
+ var r0 = r(0);
401
+
402
+ function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
403
+ function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
404
+
405
+ function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
406
+
407
+ var start = Date.now(),
408
+ S = (r(1) - r0) / rho,
409
+ duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
410
+
411
+ function frame() {
412
+ var t = (Date.now() - start) / duration,
413
+ s = easeOut(t) * S;
414
+
415
+ if (t <= 1) {
416
+ this._flyToFrame = Util.requestAnimFrame(frame, this);
417
+
418
+ this._move(
419
+ this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
420
+ this.getScaleZoom(w0 / w(s), startZoom),
421
+ {flyTo: true});
422
+
423
+ } else {
424
+ this
425
+ ._move(targetCenter, targetZoom)
426
+ ._moveEnd(true);
427
+ }
428
+ }
429
+
430
+ this._moveStart(true, options.noMoveStart);
431
+
432
+ frame.call(this);
433
+ return this;
434
+ },
435
+
436
+ // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
437
+ // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
438
+ // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
439
+ flyToBounds: function (bounds, options) {
440
+ var target = this._getBoundsCenterZoom(bounds, options);
441
+ return this.flyTo(target.center, target.zoom, options);
442
+ },
443
+
444
+ // @method setMaxBounds(bounds: LatLngBounds): this
445
+ // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
446
+ setMaxBounds: function (bounds) {
447
+ bounds = toLatLngBounds(bounds);
448
+
449
+ if (this.listens('moveend', this._panInsideMaxBounds)) {
450
+ this.off('moveend', this._panInsideMaxBounds);
451
+ }
452
+
453
+ if (!bounds.isValid()) {
454
+ this.options.maxBounds = null;
455
+ return this;
456
+ }
457
+
458
+ this.options.maxBounds = bounds;
459
+
460
+ if (this._loaded) {
461
+ this._panInsideMaxBounds();
462
+ }
463
+
464
+ return this.on('moveend', this._panInsideMaxBounds);
465
+ },
466
+
467
+ // @method setMinZoom(zoom: Number): this
468
+ // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
469
+ setMinZoom: function (zoom) {
470
+ var oldZoom = this.options.minZoom;
471
+ this.options.minZoom = zoom;
472
+
473
+ if (this._loaded && oldZoom !== zoom) {
474
+ this.fire('zoomlevelschange');
475
+
476
+ if (this.getZoom() < this.options.minZoom) {
477
+ return this.setZoom(zoom);
478
+ }
479
+ }
480
+
481
+ return this;
482
+ },
483
+
484
+ // @method setMaxZoom(zoom: Number): this
485
+ // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
486
+ setMaxZoom: function (zoom) {
487
+ var oldZoom = this.options.maxZoom;
488
+ this.options.maxZoom = zoom;
489
+
490
+ if (this._loaded && oldZoom !== zoom) {
491
+ this.fire('zoomlevelschange');
492
+
493
+ if (this.getZoom() > this.options.maxZoom) {
494
+ return this.setZoom(zoom);
495
+ }
496
+ }
497
+
498
+ return this;
499
+ },
500
+
501
+ // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
502
+ // Pans the map to the closest view that would lie inside the given bounds (if it's not already), controlling the animation using the options specific, if any.
503
+ panInsideBounds: function (bounds, options) {
504
+ this._enforcingBounds = true;
505
+ var center = this.getCenter(),
506
+ newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
507
+
508
+ if (!center.equals(newCenter)) {
509
+ this.panTo(newCenter, options);
510
+ }
511
+
512
+ this._enforcingBounds = false;
513
+ return this;
514
+ },
515
+
516
+ // @method panInside(latlng: LatLng, options?: padding options): this
517
+ // Pans the map the minimum amount to make the `latlng` visible. Use
518
+ // padding options to fit the display to more restricted bounds.
519
+ // If `latlng` is already within the (optionally padded) display bounds,
520
+ // the map will not be panned.
521
+ panInside: function (latlng, options) {
522
+ options = options || {};
523
+
524
+ var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
525
+ paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
526
+ pixelCenter = this.project(this.getCenter()),
527
+ pixelPoint = this.project(latlng),
528
+ pixelBounds = this.getPixelBounds(),
529
+ paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]),
530
+ paddedSize = paddedBounds.getSize();
531
+
532
+ if (!paddedBounds.contains(pixelPoint)) {
533
+ this._enforcingBounds = true;
534
+ var centerOffset = pixelPoint.subtract(paddedBounds.getCenter());
535
+ var offset = paddedBounds.extend(pixelPoint).getSize().subtract(paddedSize);
536
+ pixelCenter.x += centerOffset.x < 0 ? -offset.x : offset.x;
537
+ pixelCenter.y += centerOffset.y < 0 ? -offset.y : offset.y;
538
+ this.panTo(this.unproject(pixelCenter), options);
539
+ this._enforcingBounds = false;
540
+ }
541
+ return this;
542
+ },
543
+
544
+ // @method invalidateSize(options: Zoom/pan options): this
545
+ // Checks if the map container size changed and updates the map if so —
546
+ // call it after you've changed the map size dynamically, also animating
547
+ // pan by default. If `options.pan` is `false`, panning will not occur.
548
+ // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
549
+ // that it doesn't happen often even if the method is called many
550
+ // times in a row.
551
+
552
+ // @alternative
553
+ // @method invalidateSize(animate: Boolean): this
554
+ // Checks if the map container size changed and updates the map if so —
555
+ // call it after you've changed the map size dynamically, also animating
556
+ // pan by default.
557
+ invalidateSize: function (options) {
558
+ if (!this._loaded) { return this; }
559
+
560
+ options = Util.extend({
561
+ animate: false,
562
+ pan: true
563
+ }, options === true ? {animate: true} : options);
564
+
565
+ var oldSize = this.getSize();
566
+ this._sizeChanged = true;
567
+ this._lastCenter = null;
568
+
569
+ var newSize = this.getSize(),
570
+ oldCenter = oldSize.divideBy(2).round(),
571
+ newCenter = newSize.divideBy(2).round(),
572
+ offset = oldCenter.subtract(newCenter);
573
+
574
+ if (!offset.x && !offset.y) { return this; }
575
+
576
+ if (options.animate && options.pan) {
577
+ this.panBy(offset);
578
+
579
+ } else {
580
+ if (options.pan) {
581
+ this._rawPanBy(offset);
582
+ }
583
+
584
+ this.fire('move');
585
+
586
+ if (options.debounceMoveend) {
587
+ clearTimeout(this._sizeTimer);
588
+ this._sizeTimer = setTimeout(Util.bind(this.fire, this, 'moveend'), 200);
589
+ } else {
590
+ this.fire('moveend');
591
+ }
592
+ }
593
+
594
+ // @section Map state change events
595
+ // @event resize: ResizeEvent
596
+ // Fired when the map is resized.
597
+ return this.fire('resize', {
598
+ oldSize: oldSize,
599
+ newSize: newSize
600
+ });
601
+ },
602
+
603
+ // @section Methods for modifying map state
604
+ // @method stop(): this
605
+ // Stops the currently running `panTo` or `flyTo` animation, if any.
606
+ stop: function () {
607
+ this.setZoom(this._limitZoom(this._zoom));
608
+ if (!this.options.zoomSnap) {
609
+ this.fire('viewreset');
610
+ }
611
+ return this._stop();
612
+ },
613
+
614
+ // @section Geolocation methods
615
+ // @method locate(options?: Locate options): this
616
+ // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
617
+ // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
618
+ // and optionally sets the map view to the user's location with respect to
619
+ // detection accuracy (or to the world view if geolocation failed).
620
+ // Note that, if your page doesn't use HTTPS, this method will fail in
621
+ // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
622
+ // See `Locate options` for more details.
623
+ locate: function (options) {
624
+
625
+ options = this._locateOptions = Util.extend({
626
+ timeout: 10000,
627
+ watch: false
628
+ // setView: false
629
+ // maxZoom: <Number>
630
+ // maximumAge: 0
631
+ // enableHighAccuracy: false
632
+ }, options);
633
+
634
+ if (!('geolocation' in navigator)) {
635
+ this._handleGeolocationError({
636
+ code: 0,
637
+ message: 'Geolocation not supported.'
638
+ });
639
+ return this;
640
+ }
641
+
642
+ var onResponse = Util.bind(this._handleGeolocationResponse, this),
643
+ onError = Util.bind(this._handleGeolocationError, this);
644
+
645
+ if (options.watch) {
646
+ this._locationWatchId =
647
+ navigator.geolocation.watchPosition(onResponse, onError, options);
648
+ } else {
649
+ navigator.geolocation.getCurrentPosition(onResponse, onError, options);
650
+ }
651
+ return this;
652
+ },
653
+
654
+ // @method stopLocate(): this
655
+ // Stops watching location previously initiated by `map.locate({watch: true})`
656
+ // and aborts resetting the map view if map.locate was called with
657
+ // `{setView: true}`.
658
+ stopLocate: function () {
659
+ if (navigator.geolocation && navigator.geolocation.clearWatch) {
660
+ navigator.geolocation.clearWatch(this._locationWatchId);
661
+ }
662
+ if (this._locateOptions) {
663
+ this._locateOptions.setView = false;
664
+ }
665
+ return this;
666
+ },
667
+
668
+ _handleGeolocationError: function (error) {
669
+ if (!this._container._leaflet_id) { return; }
670
+
671
+ var c = error.code,
672
+ message = error.message ||
673
+ (c === 1 ? 'permission denied' :
674
+ (c === 2 ? 'position unavailable' : 'timeout'));
675
+
676
+ if (this._locateOptions.setView && !this._loaded) {
677
+ this.fitWorld();
678
+ }
679
+
680
+ // @section Location events
681
+ // @event locationerror: ErrorEvent
682
+ // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
683
+ this.fire('locationerror', {
684
+ code: c,
685
+ message: 'Geolocation error: ' + message + '.'
686
+ });
687
+ },
688
+
689
+ _handleGeolocationResponse: function (pos) {
690
+ if (!this._container._leaflet_id) { return; }
691
+
692
+ var lat = pos.coords.latitude,
693
+ lng = pos.coords.longitude,
694
+ latlng = new LatLng(lat, lng),
695
+ bounds = latlng.toBounds(pos.coords.accuracy * 2),
696
+ options = this._locateOptions;
697
+
698
+ if (options.setView) {
699
+ var zoom = this.getBoundsZoom(bounds);
700
+ this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
701
+ }
702
+
703
+ var data = {
704
+ latlng: latlng,
705
+ bounds: bounds,
706
+ timestamp: pos.timestamp
707
+ };
708
+
709
+ for (var i in pos.coords) {
710
+ if (typeof pos.coords[i] === 'number') {
711
+ data[i] = pos.coords[i];
712
+ }
713
+ }
714
+
715
+ // @event locationfound: LocationEvent
716
+ // Fired when geolocation (using the [`locate`](#map-locate) method)
717
+ // went successfully.
718
+ this.fire('locationfound', data);
719
+ },
720
+
721
+ // TODO Appropriate docs section?
722
+ // @section Other Methods
723
+ // @method addHandler(name: String, HandlerClass: Function): this
724
+ // Adds a new `Handler` to the map, given its name and constructor function.
725
+ addHandler: function (name, HandlerClass) {
726
+ if (!HandlerClass) { return this; }
727
+
728
+ var handler = this[name] = new HandlerClass(this);
729
+
730
+ this._handlers.push(handler);
731
+
732
+ if (this.options[name]) {
733
+ handler.enable();
734
+ }
735
+
736
+ return this;
737
+ },
738
+
739
+ // @method remove(): this
740
+ // Destroys the map and clears all related event listeners.
741
+ remove: function () {
742
+
743
+ this._initEvents(true);
744
+ if (this.options.maxBounds) { this.off('moveend', this._panInsideMaxBounds); }
745
+
746
+ if (this._containerId !== this._container._leaflet_id) {
747
+ throw new Error('Map container is being reused by another instance');
748
+ }
749
+
750
+ try {
751
+ // throws error in IE6-8
752
+ delete this._container._leaflet_id;
753
+ delete this._containerId;
754
+ } catch (e) {
755
+ /*eslint-disable */
756
+ this._container._leaflet_id = undefined;
757
+ /* eslint-enable */
758
+ this._containerId = undefined;
759
+ }
760
+
761
+ if (this._locationWatchId !== undefined) {
762
+ this.stopLocate();
763
+ }
764
+
765
+ this._stop();
766
+
767
+ DomUtil.remove(this._mapPane);
768
+
769
+ if (this._clearControlPos) {
770
+ this._clearControlPos();
771
+ }
772
+ if (this._resizeRequest) {
773
+ Util.cancelAnimFrame(this._resizeRequest);
774
+ this._resizeRequest = null;
775
+ }
776
+
777
+ this._clearHandlers();
778
+
779
+ if (this._loaded) {
780
+ // @section Map state change events
781
+ // @event unload: Event
782
+ // Fired when the map is destroyed with [remove](#map-remove) method.
783
+ this.fire('unload');
784
+ }
785
+
786
+ var i;
787
+ for (i in this._layers) {
788
+ this._layers[i].remove();
789
+ }
790
+ for (i in this._panes) {
791
+ DomUtil.remove(this._panes[i]);
792
+ }
793
+
794
+ this._layers = [];
795
+ this._panes = [];
796
+ delete this._mapPane;
797
+ delete this._renderer;
798
+
799
+ return this;
800
+ },
801
+
802
+ // @section Other Methods
803
+ // @method createPane(name: String, container?: HTMLElement): HTMLElement
804
+ // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
805
+ // then returns it. The pane is created as a child of `container`, or
806
+ // as a child of the main map pane if not set.
807
+ createPane: function (name, container) {
808
+ var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
809
+ pane = DomUtil.create('div', className, container || this._mapPane);
810
+
811
+ if (name) {
812
+ this._panes[name] = pane;
813
+ }
814
+ return pane;
815
+ },
816
+
817
+ // @section Methods for Getting Map State
818
+
819
+ // @method getCenter(): LatLng
820
+ // Returns the geographical center of the map view
821
+ getCenter: function () {
822
+ this._checkIfLoaded();
823
+
824
+ if (this._lastCenter && !this._moved()) {
825
+ return this._lastCenter.clone();
826
+ }
827
+ return this.layerPointToLatLng(this._getCenterLayerPoint());
828
+ },
829
+
830
+ // @method getZoom(): Number
831
+ // Returns the current zoom level of the map view
832
+ getZoom: function () {
833
+ return this._zoom;
834
+ },
835
+
836
+ // @method getBounds(): LatLngBounds
837
+ // Returns the geographical bounds visible in the current map view
838
+ getBounds: function () {
839
+ var bounds = this.getPixelBounds(),
840
+ sw = this.unproject(bounds.getBottomLeft()),
841
+ ne = this.unproject(bounds.getTopRight());
842
+
843
+ return new LatLngBounds(sw, ne);
844
+ },
845
+
846
+ // @method getMinZoom(): Number
847
+ // Returns the minimum zoom level of the map (if set in the `minZoom` option of the map or of any layers), or `0` by default.
848
+ getMinZoom: function () {
849
+ return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
850
+ },
851
+
852
+ // @method getMaxZoom(): Number
853
+ // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
854
+ getMaxZoom: function () {
855
+ return this.options.maxZoom === undefined ?
856
+ (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
857
+ this.options.maxZoom;
858
+ },
859
+
860
+ // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
861
+ // Returns the maximum zoom level on which the given bounds fit to the map
862
+ // view in its entirety. If `inside` (optional) is set to `true`, the method
863
+ // instead returns the minimum zoom level on which the map view fits into
864
+ // the given bounds in its entirety.
865
+ getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
866
+ bounds = toLatLngBounds(bounds);
867
+ padding = toPoint(padding || [0, 0]);
868
+
869
+ var zoom = this.getZoom() || 0,
870
+ min = this.getMinZoom(),
871
+ max = this.getMaxZoom(),
872
+ nw = bounds.getNorthWest(),
873
+ se = bounds.getSouthEast(),
874
+ size = this.getSize().subtract(padding),
875
+ boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
876
+ snap = Browser.any3d ? this.options.zoomSnap : 1,
877
+ scalex = size.x / boundsSize.x,
878
+ scaley = size.y / boundsSize.y,
879
+ scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
880
+
881
+ zoom = this.getScaleZoom(scale, zoom);
882
+
883
+ if (snap) {
884
+ zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
885
+ zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
886
+ }
887
+
888
+ return Math.max(min, Math.min(max, zoom));
889
+ },
890
+
891
+ // @method getSize(): Point
892
+ // Returns the current size of the map container (in pixels).
893
+ getSize: function () {
894
+ if (!this._size || this._sizeChanged) {
895
+ this._size = new Point(
896
+ this._container.clientWidth || 0,
897
+ this._container.clientHeight || 0);
898
+
899
+ this._sizeChanged = false;
900
+ }
901
+ return this._size.clone();
902
+ },
903
+
904
+ // @method getPixelBounds(): Bounds
905
+ // Returns the bounds of the current map view in projected pixel
906
+ // coordinates (sometimes useful in layer and overlay implementations).
907
+ getPixelBounds: function (center, zoom) {
908
+ var topLeftPoint = this._getTopLeftPoint(center, zoom);
909
+ return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
910
+ },
911
+
912
+ // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
913
+ // the map pane? "left point of the map layer" can be confusing, specially
914
+ // since there can be negative offsets.
915
+ // @method getPixelOrigin(): Point
916
+ // Returns the projected pixel coordinates of the top left point of
917
+ // the map layer (useful in custom layer and overlay implementations).
918
+ getPixelOrigin: function () {
919
+ this._checkIfLoaded();
920
+ return this._pixelOrigin;
921
+ },
922
+
923
+ // @method getPixelWorldBounds(zoom?: Number): Bounds
924
+ // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
925
+ // If `zoom` is omitted, the map's current zoom level is used.
926
+ getPixelWorldBounds: function (zoom) {
927
+ return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
928
+ },
929
+
930
+ // @section Other Methods
931
+
932
+ // @method getPane(pane: String|HTMLElement): HTMLElement
933
+ // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
934
+ getPane: function (pane) {
935
+ return typeof pane === 'string' ? this._panes[pane] : pane;
936
+ },
937
+
938
+ // @method getPanes(): Object
939
+ // Returns a plain object containing the names of all [panes](#map-pane) as keys and
940
+ // the panes as values.
941
+ getPanes: function () {
942
+ return this._panes;
943
+ },
944
+
945
+ // @method getContainer: HTMLElement
946
+ // Returns the HTML element that contains the map.
947
+ getContainer: function () {
948
+ return this._container;
949
+ },
950
+
951
+
952
+ // @section Conversion Methods
953
+
954
+ // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
955
+ // Returns the scale factor to be applied to a map transition from zoom level
956
+ // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
957
+ getZoomScale: function (toZoom, fromZoom) {
958
+ // TODO replace with universal implementation after refactoring projections
959
+ var crs = this.options.crs;
960
+ fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
961
+ return crs.scale(toZoom) / crs.scale(fromZoom);
962
+ },
963
+
964
+ // @method getScaleZoom(scale: Number, fromZoom: Number): Number
965
+ // Returns the zoom level that the map would end up at, if it is at `fromZoom`
966
+ // level and everything is scaled by a factor of `scale`. Inverse of
967
+ // [`getZoomScale`](#map-getZoomScale).
968
+ getScaleZoom: function (scale, fromZoom) {
969
+ var crs = this.options.crs;
970
+ fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
971
+ var zoom = crs.zoom(scale * crs.scale(fromZoom));
972
+ return isNaN(zoom) ? Infinity : zoom;
973
+ },
974
+
975
+ // @method project(latlng: LatLng, zoom: Number): Point
976
+ // Projects a geographical coordinate `LatLng` according to the projection
977
+ // of the map's CRS, then scales it according to `zoom` and the CRS's
978
+ // `Transformation`. The result is pixel coordinate relative to
979
+ // the CRS origin.
980
+ project: function (latlng, zoom) {
981
+ zoom = zoom === undefined ? this._zoom : zoom;
982
+ return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
983
+ },
984
+
985
+ // @method unproject(point: Point, zoom: Number): LatLng
986
+ // Inverse of [`project`](#map-project).
987
+ unproject: function (point, zoom) {
988
+ zoom = zoom === undefined ? this._zoom : zoom;
989
+ return this.options.crs.pointToLatLng(toPoint(point), zoom);
990
+ },
991
+
992
+ // @method layerPointToLatLng(point: Point): LatLng
993
+ // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
994
+ // returns the corresponding geographical coordinate (for the current zoom level).
995
+ layerPointToLatLng: function (point) {
996
+ var projectedPoint = toPoint(point).add(this.getPixelOrigin());
997
+ return this.unproject(projectedPoint);
998
+ },
999
+
1000
+ // @method latLngToLayerPoint(latlng: LatLng): Point
1001
+ // Given a geographical coordinate, returns the corresponding pixel coordinate
1002
+ // relative to the [origin pixel](#map-getpixelorigin).
1003
+ latLngToLayerPoint: function (latlng) {
1004
+ var projectedPoint = this.project(toLatLng(latlng))._round();
1005
+ return projectedPoint._subtract(this.getPixelOrigin());
1006
+ },
1007
+
1008
+ // @method wrapLatLng(latlng: LatLng): LatLng
1009
+ // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
1010
+ // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
1011
+ // CRS's bounds.
1012
+ // By default this means longitude is wrapped around the dateline so its
1013
+ // value is between -180 and +180 degrees.
1014
+ wrapLatLng: function (latlng) {
1015
+ return this.options.crs.wrapLatLng(toLatLng(latlng));
1016
+ },
1017
+
1018
+ // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
1019
+ // Returns a `LatLngBounds` with the same size as the given one, ensuring that
1020
+ // its center is within the CRS's bounds.
1021
+ // By default this means the center longitude is wrapped around the dateline so its
1022
+ // value is between -180 and +180 degrees, and the majority of the bounds
1023
+ // overlaps the CRS's bounds.
1024
+ wrapLatLngBounds: function (latlng) {
1025
+ return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
1026
+ },
1027
+
1028
+ // @method distance(latlng1: LatLng, latlng2: LatLng): Number
1029
+ // Returns the distance between two geographical coordinates according to
1030
+ // the map's CRS. By default this measures distance in meters.
1031
+ distance: function (latlng1, latlng2) {
1032
+ return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
1033
+ },
1034
+
1035
+ // @method containerPointToLayerPoint(point: Point): Point
1036
+ // Given a pixel coordinate relative to the map container, returns the corresponding
1037
+ // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
1038
+ containerPointToLayerPoint: function (point) { // (Point)
1039
+ return toPoint(point).subtract(this._getMapPanePos());
1040
+ },
1041
+
1042
+ // @method layerPointToContainerPoint(point: Point): Point
1043
+ // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
1044
+ // returns the corresponding pixel coordinate relative to the map container.
1045
+ layerPointToContainerPoint: function (point) { // (Point)
1046
+ return toPoint(point).add(this._getMapPanePos());
1047
+ },
1048
+
1049
+ // @method containerPointToLatLng(point: Point): LatLng
1050
+ // Given a pixel coordinate relative to the map container, returns
1051
+ // the corresponding geographical coordinate (for the current zoom level).
1052
+ containerPointToLatLng: function (point) {
1053
+ var layerPoint = this.containerPointToLayerPoint(toPoint(point));
1054
+ return this.layerPointToLatLng(layerPoint);
1055
+ },
1056
+
1057
+ // @method latLngToContainerPoint(latlng: LatLng): Point
1058
+ // Given a geographical coordinate, returns the corresponding pixel coordinate
1059
+ // relative to the map container.
1060
+ latLngToContainerPoint: function (latlng) {
1061
+ return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
1062
+ },
1063
+
1064
+ // @method mouseEventToContainerPoint(ev: MouseEvent): Point
1065
+ // Given a MouseEvent object, returns the pixel coordinate relative to the
1066
+ // map container where the event took place.
1067
+ mouseEventToContainerPoint: function (e) {
1068
+ return DomEvent.getMousePosition(e, this._container);
1069
+ },
1070
+
1071
+ // @method mouseEventToLayerPoint(ev: MouseEvent): Point
1072
+ // Given a MouseEvent object, returns the pixel coordinate relative to
1073
+ // the [origin pixel](#map-getpixelorigin) where the event took place.
1074
+ mouseEventToLayerPoint: function (e) {
1075
+ return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
1076
+ },
1077
+
1078
+ // @method mouseEventToLatLng(ev: MouseEvent): LatLng
1079
+ // Given a MouseEvent object, returns geographical coordinate where the
1080
+ // event took place.
1081
+ mouseEventToLatLng: function (e) { // (MouseEvent)
1082
+ return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
1083
+ },
1084
+
1085
+
1086
+ // map initialization methods
1087
+
1088
+ _initContainer: function (id) {
1089
+ var container = this._container = DomUtil.get(id);
1090
+
1091
+ if (!container) {
1092
+ throw new Error('Map container not found.');
1093
+ } else if (container._leaflet_id) {
1094
+ throw new Error('Map container is already initialized.');
1095
+ }
1096
+
1097
+ DomEvent.on(container, 'scroll', this._onScroll, this);
1098
+ this._containerId = Util.stamp(container);
1099
+ },
1100
+
1101
+ _initLayout: function () {
1102
+ var container = this._container;
1103
+
1104
+ this._fadeAnimated = this.options.fadeAnimation && Browser.any3d;
1105
+
1106
+ DomUtil.addClass(container, 'leaflet-container' +
1107
+ (Browser.touch ? ' leaflet-touch' : '') +
1108
+ (Browser.retina ? ' leaflet-retina' : '') +
1109
+ (Browser.ielt9 ? ' leaflet-oldie' : '') +
1110
+ (Browser.safari ? ' leaflet-safari' : '') +
1111
+ (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
1112
+
1113
+ var position = DomUtil.getStyle(container, 'position');
1114
+
1115
+ if (position !== 'absolute' && position !== 'relative' && position !== 'fixed' && position !== 'sticky') {
1116
+ container.style.position = 'relative';
1117
+ }
1118
+
1119
+ this._initPanes();
1120
+
1121
+ if (this._initControlPos) {
1122
+ this._initControlPos();
1123
+ }
1124
+ },
1125
+
1126
+ _initPanes: function () {
1127
+ var panes = this._panes = {};
1128
+ this._paneRenderers = {};
1129
+
1130
+ // @section
1131
+ //
1132
+ // Panes are DOM elements used to control the ordering of layers on the map. You
1133
+ // can access panes with [`map.getPane`](#map-getpane) or
1134
+ // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
1135
+ // [`map.createPane`](#map-createpane) method.
1136
+ //
1137
+ // Every map has the following default panes that differ only in zIndex.
1138
+ //
1139
+ // @pane mapPane: HTMLElement = 'auto'
1140
+ // Pane that contains all other map panes
1141
+
1142
+ this._mapPane = this.createPane('mapPane', this._container);
1143
+ DomUtil.setPosition(this._mapPane, new Point(0, 0));
1144
+
1145
+ // @pane tilePane: HTMLElement = 200
1146
+ // Pane for `GridLayer`s and `TileLayer`s
1147
+ this.createPane('tilePane');
1148
+ // @pane overlayPane: HTMLElement = 400
1149
+ // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
1150
+ this.createPane('overlayPane');
1151
+ // @pane shadowPane: HTMLElement = 500
1152
+ // Pane for overlay shadows (e.g. `Marker` shadows)
1153
+ this.createPane('shadowPane');
1154
+ // @pane markerPane: HTMLElement = 600
1155
+ // Pane for `Icon`s of `Marker`s
1156
+ this.createPane('markerPane');
1157
+ // @pane tooltipPane: HTMLElement = 650
1158
+ // Pane for `Tooltip`s.
1159
+ this.createPane('tooltipPane');
1160
+ // @pane popupPane: HTMLElement = 700
1161
+ // Pane for `Popup`s.
1162
+ this.createPane('popupPane');
1163
+
1164
+ if (!this.options.markerZoomAnimation) {
1165
+ DomUtil.addClass(panes.markerPane, 'leaflet-zoom-hide');
1166
+ DomUtil.addClass(panes.shadowPane, 'leaflet-zoom-hide');
1167
+ }
1168
+ },
1169
+
1170
+
1171
+ // private methods that modify map state
1172
+
1173
+ // @section Map state change events
1174
+ _resetView: function (center, zoom, noMoveStart) {
1175
+ DomUtil.setPosition(this._mapPane, new Point(0, 0));
1176
+
1177
+ var loading = !this._loaded;
1178
+ this._loaded = true;
1179
+ zoom = this._limitZoom(zoom);
1180
+
1181
+ this.fire('viewprereset');
1182
+
1183
+ var zoomChanged = this._zoom !== zoom;
1184
+ this
1185
+ ._moveStart(zoomChanged, noMoveStart)
1186
+ ._move(center, zoom)
1187
+ ._moveEnd(zoomChanged);
1188
+
1189
+ // @event viewreset: Event
1190
+ // Fired when the map needs to redraw its content (this usually happens
1191
+ // on map zoom or load). Very useful for creating custom overlays.
1192
+ this.fire('viewreset');
1193
+
1194
+ // @event load: Event
1195
+ // Fired when the map is initialized (when its center and zoom are set
1196
+ // for the first time).
1197
+ if (loading) {
1198
+ this.fire('load');
1199
+ }
1200
+ },
1201
+
1202
+ _moveStart: function (zoomChanged, noMoveStart) {
1203
+ // @event zoomstart: Event
1204
+ // Fired when the map zoom is about to change (e.g. before zoom animation).
1205
+ // @event movestart: Event
1206
+ // Fired when the view of the map starts changing (e.g. user starts dragging the map).
1207
+ if (zoomChanged) {
1208
+ this.fire('zoomstart');
1209
+ }
1210
+ if (!noMoveStart) {
1211
+ this.fire('movestart');
1212
+ }
1213
+ return this;
1214
+ },
1215
+
1216
+ _move: function (center, zoom, data, supressEvent) {
1217
+ if (zoom === undefined) {
1218
+ zoom = this._zoom;
1219
+ }
1220
+ var zoomChanged = this._zoom !== zoom;
1221
+
1222
+ this._zoom = zoom;
1223
+ this._lastCenter = center;
1224
+ this._pixelOrigin = this._getNewPixelOrigin(center);
1225
+
1226
+ if (!supressEvent) {
1227
+ // @event zoom: Event
1228
+ // Fired repeatedly during any change in zoom level,
1229
+ // including zoom and fly animations.
1230
+ if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530
1231
+ this.fire('zoom', data);
1232
+ }
1233
+
1234
+ // @event move: Event
1235
+ // Fired repeatedly during any movement of the map,
1236
+ // including pan and fly animations.
1237
+ this.fire('move', data);
1238
+ } else if (data && data.pinch) { // Always fire 'zoom' if pinching because #3530
1239
+ this.fire('zoom', data);
1240
+ }
1241
+ return this;
1242
+ },
1243
+
1244
+ _moveEnd: function (zoomChanged) {
1245
+ // @event zoomend: Event
1246
+ // Fired when the map zoom changed, after any animations.
1247
+ if (zoomChanged) {
1248
+ this.fire('zoomend');
1249
+ }
1250
+
1251
+ // @event moveend: Event
1252
+ // Fired when the center of the map stops changing
1253
+ // (e.g. user stopped dragging the map or after non-centered zoom).
1254
+ return this.fire('moveend');
1255
+ },
1256
+
1257
+ _stop: function () {
1258
+ Util.cancelAnimFrame(this._flyToFrame);
1259
+ if (this._panAnim) {
1260
+ this._panAnim.stop();
1261
+ }
1262
+ return this;
1263
+ },
1264
+
1265
+ _rawPanBy: function (offset) {
1266
+ DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
1267
+ },
1268
+
1269
+ _getZoomSpan: function () {
1270
+ return this.getMaxZoom() - this.getMinZoom();
1271
+ },
1272
+
1273
+ _panInsideMaxBounds: function () {
1274
+ if (!this._enforcingBounds) {
1275
+ this.panInsideBounds(this.options.maxBounds);
1276
+ }
1277
+ },
1278
+
1279
+ _checkIfLoaded: function () {
1280
+ if (!this._loaded) {
1281
+ throw new Error('Set map center and zoom first.');
1282
+ }
1283
+ },
1284
+
1285
+ // DOM event handling
1286
+
1287
+ // @section Interaction events
1288
+ _initEvents: function (remove) {
1289
+ this._targets = {};
1290
+ this._targets[Util.stamp(this._container)] = this;
1291
+
1292
+ var onOff = remove ? DomEvent.off : DomEvent.on;
1293
+
1294
+ // @event click: MouseEvent
1295
+ // Fired when the user clicks (or taps) the map.
1296
+ // @event dblclick: MouseEvent
1297
+ // Fired when the user double-clicks (or double-taps) the map.
1298
+ // @event mousedown: MouseEvent
1299
+ // Fired when the user pushes the mouse button on the map.
1300
+ // @event mouseup: MouseEvent
1301
+ // Fired when the user releases the mouse button on the map.
1302
+ // @event mouseover: MouseEvent
1303
+ // Fired when the mouse enters the map.
1304
+ // @event mouseout: MouseEvent
1305
+ // Fired when the mouse leaves the map.
1306
+ // @event mousemove: MouseEvent
1307
+ // Fired while the mouse moves over the map.
1308
+ // @event contextmenu: MouseEvent
1309
+ // Fired when the user pushes the right mouse button on the map, prevents
1310
+ // default browser context menu from showing if there are listeners on
1311
+ // this event. Also fired on mobile when the user holds a single touch
1312
+ // for a second (also called long press).
1313
+ // @event keypress: KeyboardEvent
1314
+ // Fired when the user presses a key from the keyboard that produces a character value while the map is focused.
1315
+ // @event keydown: KeyboardEvent
1316
+ // Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event,
1317
+ // the `keydown` event is fired for keys that produce a character value and for keys
1318
+ // that do not produce a character value.
1319
+ // @event keyup: KeyboardEvent
1320
+ // Fired when the user releases a key from the keyboard while the map is focused.
1321
+ onOff(this._container, 'click dblclick mousedown mouseup ' +
1322
+ 'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this);
1323
+
1324
+ if (this.options.trackResize) {
1325
+ onOff(window, 'resize', this._onResize, this);
1326
+ }
1327
+
1328
+ if (Browser.any3d && this.options.transform3DLimit) {
1329
+ (remove ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
1330
+ }
1331
+ },
1332
+
1333
+ _onResize: function () {
1334
+ Util.cancelAnimFrame(this._resizeRequest);
1335
+ this._resizeRequest = Util.requestAnimFrame(
1336
+ function () { this.invalidateSize({debounceMoveend: true}); }, this);
1337
+ },
1338
+
1339
+ _onScroll: function () {
1340
+ this._container.scrollTop = 0;
1341
+ this._container.scrollLeft = 0;
1342
+ },
1343
+
1344
+ _onMoveEnd: function () {
1345
+ var pos = this._getMapPanePos();
1346
+ if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
1347
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
1348
+ // a pixel offset on very high values, see: https://jsfiddle.net/dg6r5hhb/
1349
+ this._resetView(this.getCenter(), this.getZoom());
1350
+ }
1351
+ },
1352
+
1353
+ _findEventTargets: function (e, type) {
1354
+ var targets = [],
1355
+ target,
1356
+ isHover = type === 'mouseout' || type === 'mouseover',
1357
+ src = e.target || e.srcElement,
1358
+ dragging = false;
1359
+
1360
+ while (src) {
1361
+ target = this._targets[Util.stamp(src)];
1362
+ if (target && (type === 'click' || type === 'preclick') && this._draggableMoved(target)) {
1363
+ // Prevent firing click after you just dragged an object.
1364
+ dragging = true;
1365
+ break;
1366
+ }
1367
+ if (target && target.listens(type, true)) {
1368
+ if (isHover && !DomEvent.isExternalTarget(src, e)) { break; }
1369
+ targets.push(target);
1370
+ if (isHover) { break; }
1371
+ }
1372
+ if (src === this._container) { break; }
1373
+ src = src.parentNode;
1374
+ }
1375
+ if (!targets.length && !dragging && !isHover && this.listens(type, true)) {
1376
+ targets = [this];
1377
+ }
1378
+ return targets;
1379
+ },
1380
+
1381
+ _isClickDisabled: function (el) {
1382
+ while (el && el !== this._container) {
1383
+ if (el['_leaflet_disable_click']) { return true; }
1384
+ el = el.parentNode;
1385
+ }
1386
+ },
1387
+
1388
+ _handleDOMEvent: function (e) {
1389
+ var el = (e.target || e.srcElement);
1390
+ if (!this._loaded || el['_leaflet_disable_events'] || e.type === 'click' && this._isClickDisabled(el)) {
1391
+ return;
1392
+ }
1393
+
1394
+ var type = e.type;
1395
+
1396
+ if (type === 'mousedown') {
1397
+ // prevents outline when clicking on keyboard-focusable element
1398
+ DomUtil.preventOutline(el);
1399
+ }
1400
+
1401
+ this._fireDOMEvent(e, type);
1402
+ },
1403
+
1404
+ _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
1405
+
1406
+ _fireDOMEvent: function (e, type, canvasTargets) {
1407
+
1408
+ if (e.type === 'click') {
1409
+ // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
1410
+ // @event preclick: MouseEvent
1411
+ // Fired before mouse click on the map (sometimes useful when you
1412
+ // want something to happen on click before any existing click
1413
+ // handlers start running).
1414
+ var synth = Util.extend({}, e);
1415
+ synth.type = 'preclick';
1416
+ this._fireDOMEvent(synth, synth.type, canvasTargets);
1417
+ }
1418
+
1419
+ // Find the layer the event is propagating from and its parents.
1420
+ var targets = this._findEventTargets(e, type);
1421
+
1422
+ if (canvasTargets) {
1423
+ var filtered = []; // pick only targets with listeners
1424
+ for (var i = 0; i < canvasTargets.length; i++) {
1425
+ if (canvasTargets[i].listens(type, true)) {
1426
+ filtered.push(canvasTargets[i]);
1427
+ }
1428
+ }
1429
+ targets = filtered.concat(targets);
1430
+ }
1431
+
1432
+ if (!targets.length) { return; }
1433
+
1434
+ if (type === 'contextmenu') {
1435
+ DomEvent.preventDefault(e);
1436
+ }
1437
+
1438
+ var target = targets[0];
1439
+ var data = {
1440
+ originalEvent: e
1441
+ };
1442
+
1443
+ if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') {
1444
+ var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
1445
+ data.containerPoint = isMarker ?
1446
+ this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
1447
+ data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
1448
+ data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
1449
+ }
1450
+
1451
+ for (i = 0; i < targets.length; i++) {
1452
+ targets[i].fire(type, data, true);
1453
+ if (data.originalEvent._stopped ||
1454
+ (targets[i].options.bubblingMouseEvents === false && Util.indexOf(this._mouseEvents, type) !== -1)) { return; }
1455
+ }
1456
+ },
1457
+
1458
+ _draggableMoved: function (obj) {
1459
+ obj = obj.dragging && obj.dragging.enabled() ? obj : this;
1460
+ return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
1461
+ },
1462
+
1463
+ _clearHandlers: function () {
1464
+ for (var i = 0, len = this._handlers.length; i < len; i++) {
1465
+ this._handlers[i].disable();
1466
+ }
1467
+ },
1468
+
1469
+ // @section Other Methods
1470
+
1471
+ // @method whenReady(fn: Function, context?: Object): this
1472
+ // Runs the given function `fn` when the map gets initialized with
1473
+ // a view (center and zoom) and at least one layer, or immediately
1474
+ // if it's already initialized, optionally passing a function context.
1475
+ whenReady: function (callback, context) {
1476
+ if (this._loaded) {
1477
+ callback.call(context || this, {target: this});
1478
+ } else {
1479
+ this.on('load', callback, context);
1480
+ }
1481
+ return this;
1482
+ },
1483
+
1484
+
1485
+ // private methods for getting map state
1486
+
1487
+ _getMapPanePos: function () {
1488
+ return DomUtil.getPosition(this._mapPane) || new Point(0, 0);
1489
+ },
1490
+
1491
+ _moved: function () {
1492
+ var pos = this._getMapPanePos();
1493
+ return pos && !pos.equals([0, 0]);
1494
+ },
1495
+
1496
+ _getTopLeftPoint: function (center, zoom) {
1497
+ var pixelOrigin = center && zoom !== undefined ?
1498
+ this._getNewPixelOrigin(center, zoom) :
1499
+ this.getPixelOrigin();
1500
+ return pixelOrigin.subtract(this._getMapPanePos());
1501
+ },
1502
+
1503
+ _getNewPixelOrigin: function (center, zoom) {
1504
+ var viewHalf = this.getSize()._divideBy(2);
1505
+ return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
1506
+ },
1507
+
1508
+ _latLngToNewLayerPoint: function (latlng, zoom, center) {
1509
+ var topLeft = this._getNewPixelOrigin(center, zoom);
1510
+ return this.project(latlng, zoom)._subtract(topLeft);
1511
+ },
1512
+
1513
+ _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
1514
+ var topLeft = this._getNewPixelOrigin(center, zoom);
1515
+ return toBounds([
1516
+ this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
1517
+ this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
1518
+ this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
1519
+ this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
1520
+ ]);
1521
+ },
1522
+
1523
+ // layer point of the current center
1524
+ _getCenterLayerPoint: function () {
1525
+ return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
1526
+ },
1527
+
1528
+ // offset of the specified place to the current center in pixels
1529
+ _getCenterOffset: function (latlng) {
1530
+ return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
1531
+ },
1532
+
1533
+ // adjust center for view to get inside bounds
1534
+ _limitCenter: function (center, zoom, bounds) {
1535
+
1536
+ if (!bounds) { return center; }
1537
+
1538
+ var centerPoint = this.project(center, zoom),
1539
+ viewHalf = this.getSize().divideBy(2),
1540
+ viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
1541
+ offset = this._getBoundsOffset(viewBounds, bounds, zoom);
1542
+
1543
+ // If offset is less than a pixel, ignore.
1544
+ // This prevents unstable projections from getting into
1545
+ // an infinite loop of tiny offsets.
1546
+ if (Math.abs(offset.x) <= 1 && Math.abs(offset.y) <= 1) {
1547
+ return center;
1548
+ }
1549
+
1550
+ return this.unproject(centerPoint.add(offset), zoom);
1551
+ },
1552
+
1553
+ // adjust offset for view to get inside bounds
1554
+ _limitOffset: function (offset, bounds) {
1555
+ if (!bounds) { return offset; }
1556
+
1557
+ var viewBounds = this.getPixelBounds(),
1558
+ newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
1559
+
1560
+ return offset.add(this._getBoundsOffset(newBounds, bounds));
1561
+ },
1562
+
1563
+ // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
1564
+ _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
1565
+ var projectedMaxBounds = toBounds(
1566
+ this.project(maxBounds.getNorthEast(), zoom),
1567
+ this.project(maxBounds.getSouthWest(), zoom)
1568
+ ),
1569
+ minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
1570
+ maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
1571
+
1572
+ dx = this._rebound(minOffset.x, -maxOffset.x),
1573
+ dy = this._rebound(minOffset.y, -maxOffset.y);
1574
+
1575
+ return new Point(dx, dy);
1576
+ },
1577
+
1578
+ _rebound: function (left, right) {
1579
+ return left + right > 0 ?
1580
+ Math.round(left - right) / 2 :
1581
+ Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
1582
+ },
1583
+
1584
+ _limitZoom: function (zoom) {
1585
+ var min = this.getMinZoom(),
1586
+ max = this.getMaxZoom(),
1587
+ snap = Browser.any3d ? this.options.zoomSnap : 1;
1588
+ if (snap) {
1589
+ zoom = Math.round(zoom / snap) * snap;
1590
+ }
1591
+ return Math.max(min, Math.min(max, zoom));
1592
+ },
1593
+
1594
+ _onPanTransitionStep: function () {
1595
+ this.fire('move');
1596
+ },
1597
+
1598
+ _onPanTransitionEnd: function () {
1599
+ DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
1600
+ this.fire('moveend');
1601
+ },
1602
+
1603
+ _tryAnimatedPan: function (center, options) {
1604
+ // difference between the new and current centers in pixels
1605
+ var offset = this._getCenterOffset(center)._trunc();
1606
+
1607
+ // don't animate too far unless animate: true specified in options
1608
+ if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
1609
+
1610
+ this.panBy(offset, options);
1611
+
1612
+ return true;
1613
+ },
1614
+
1615
+ _createAnimProxy: function () {
1616
+
1617
+ var proxy = this._proxy = DomUtil.create('div', 'leaflet-proxy leaflet-zoom-animated');
1618
+ this._panes.mapPane.appendChild(proxy);
1619
+
1620
+ this.on('zoomanim', function (e) {
1621
+ var prop = DomUtil.TRANSFORM,
1622
+ transform = this._proxy.style[prop];
1623
+
1624
+ DomUtil.setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
1625
+
1626
+ // workaround for case when transform is the same and so transitionend event is not fired
1627
+ if (transform === this._proxy.style[prop] && this._animatingZoom) {
1628
+ this._onZoomTransitionEnd();
1629
+ }
1630
+ }, this);
1631
+
1632
+ this.on('load moveend', this._animMoveEnd, this);
1633
+
1634
+ this._on('unload', this._destroyAnimProxy, this);
1635
+ },
1636
+
1637
+ _destroyAnimProxy: function () {
1638
+ DomUtil.remove(this._proxy);
1639
+ this.off('load moveend', this._animMoveEnd, this);
1640
+ delete this._proxy;
1641
+ },
1642
+
1643
+ _animMoveEnd: function () {
1644
+ var c = this.getCenter(),
1645
+ z = this.getZoom();
1646
+ DomUtil.setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
1647
+ },
1648
+
1649
+ _catchTransitionEnd: function (e) {
1650
+ if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
1651
+ this._onZoomTransitionEnd();
1652
+ }
1653
+ },
1654
+
1655
+ _nothingToAnimate: function () {
1656
+ return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
1657
+ },
1658
+
1659
+ _tryAnimatedZoom: function (center, zoom, options) {
1660
+
1661
+ if (this._animatingZoom) { return true; }
1662
+
1663
+ options = options || {};
1664
+
1665
+ // don't animate if disabled, not supported or zoom difference is too large
1666
+ if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
1667
+ Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
1668
+
1669
+ // offset is the pixel coords of the zoom origin relative to the current center
1670
+ var scale = this.getZoomScale(zoom),
1671
+ offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
1672
+
1673
+ // don't animate if the zoom origin isn't within one screen from the current center, unless forced
1674
+ if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
1675
+
1676
+ Util.requestAnimFrame(function () {
1677
+ this
1678
+ ._moveStart(true, options.noMoveStart || false)
1679
+ ._animateZoom(center, zoom, true);
1680
+ }, this);
1681
+
1682
+ return true;
1683
+ },
1684
+
1685
+ _animateZoom: function (center, zoom, startAnim, noUpdate) {
1686
+ if (!this._mapPane) { return; }
1687
+
1688
+ if (startAnim) {
1689
+ this._animatingZoom = true;
1690
+
1691
+ // remember what center/zoom to set after animation
1692
+ this._animateToCenter = center;
1693
+ this._animateToZoom = zoom;
1694
+
1695
+ DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
1696
+ }
1697
+
1698
+ // @section Other Events
1699
+ // @event zoomanim: ZoomAnimEvent
1700
+ // Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom.
1701
+ this.fire('zoomanim', {
1702
+ center: center,
1703
+ zoom: zoom,
1704
+ noUpdate: noUpdate
1705
+ });
1706
+
1707
+ if (!this._tempFireZoomEvent) {
1708
+ this._tempFireZoomEvent = this._zoom !== this._animateToZoom;
1709
+ }
1710
+
1711
+ this._move(this._animateToCenter, this._animateToZoom, undefined, true);
1712
+
1713
+ // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
1714
+ setTimeout(Util.bind(this._onZoomTransitionEnd, this), 250);
1715
+ },
1716
+
1717
+ _onZoomTransitionEnd: function () {
1718
+ if (!this._animatingZoom) { return; }
1719
+
1720
+ if (this._mapPane) {
1721
+ DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
1722
+ }
1723
+
1724
+ this._animatingZoom = false;
1725
+
1726
+ this._move(this._animateToCenter, this._animateToZoom, undefined, true);
1727
+
1728
+ if (this._tempFireZoomEvent) {
1729
+ this.fire('zoom');
1730
+ }
1731
+ delete this._tempFireZoomEvent;
1732
+
1733
+ this.fire('move');
1734
+
1735
+ this._moveEnd(true);
1736
+ }
1737
+ });
1738
+
1739
+ // @section
1740
+
1741
+ // @factory L.map(id: String, options?: Map options)
1742
+ // Instantiates a map object given the DOM ID of a `<div>` element
1743
+ // and optionally an object literal with `Map options`.
1744
+ //
1745
+ // @alternative
1746
+ // @factory L.map(el: HTMLElement, options?: Map options)
1747
+ // Instantiates a map object given an instance of a `<div>` HTML element
1748
+ // and optionally an object literal with `Map options`.
1749
+ export function createMap(id, options) {
1750
+ return new Map(id, options);
1751
+ }