PluginProbe
Property Hive / 2.2.6
Property Hive v2.2.6
2.3.1 2.3.0 2.2.6 2.2.5 2.2.4 2.2.3 2.2.2 1.4.46 1.4.47 1.4.48 1.4.49 1.4.5 1.4.50 1.4.51 1.4.52 1.4.53 1.4.54 1.4.55 1.4.56 1.4.57 1.4.58 1.4.59 1.4.6 1.4.60 1.4.61 All 261 releases
propertyhive / assets / js / leaflet / leaflet-src.js

leaflet-src.js in Property Hive 2.2.6, at assets/js/leaflet/leaflet-src.js

14,513 lines 439.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* @preserve
2 * Leaflet 1.9.4, a JS library for interactive maps. https://leafletjs.com
3 * (c) 2010-2023 Vladimir Agafonkin, (c) 2010-2011 CloudMade
4 */
5
6 (function (global, factory) {
7 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
8 typeof define === 'function' && define.amd ? define(['exports'], factory) :
9 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.leaflet = {}));
10 })(this, (function (exports) { 'use strict';
11
12 var version = "1.9.4";
13
14 /*
15 * @namespace Util
16 *
17 * Various utility functions, used by Leaflet internally.
18 */
19
20 // @function extend(dest: Object, src?: Object): Object
21 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
22 function extend(dest) {
23 var i, j, len, src;
24
25 for (j = 1, len = arguments.length; j < len; j++) {
26 src = arguments[j];
27 for (i in src) {
28 dest[i] = src[i];
29 }
30 }
31 return dest;
32 }
33
34 // @function create(proto: Object, properties?: Object): Object
35 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
36 var create$2 = Object.create || (function () {
37 function F() {}
38 return function (proto) {
39 F.prototype = proto;
40 return new F();
41 };
42 })();
43
44 // @function bind(fn: Function, …): Function
45 // Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
46 // Has a `L.bind()` shortcut.
47 function bind(fn, obj) {
48 var slice = Array.prototype.slice;
49
50 if (fn.bind) {
51 return fn.bind.apply(fn, slice.call(arguments, 1));
52 }
53
54 var args = slice.call(arguments, 2);
55
56 return function () {
57 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
58 };
59 }
60
61 // @property lastId: Number
62 // Last unique ID used by [`stamp()`](#util-stamp)
63 var lastId = 0;
64
65 // @function stamp(obj: Object): Number
66 // Returns the unique ID of an object, assigning it one if it doesn't have it.
67 function stamp(obj) {
68 if (!('_leaflet_id' in obj)) {
69 obj['_leaflet_id'] = ++lastId;
70 }
71 return obj._leaflet_id;
72 }
73
74 // @function throttle(fn: Function, time: Number, context: Object): Function
75 // Returns a function which executes function `fn` with the given scope `context`
76 // (so that the `this` keyword refers to `context` inside `fn`'s code). The function
77 // `fn` will be called no more than one time per given amount of `time`. The arguments
78 // received by the bound function will be any arguments passed when binding the
79 // function, followed by any arguments passed when invoking the bound function.
80 // Has an `L.throttle` shortcut.
81 function throttle(fn, time, context) {
82 var lock, args, wrapperFn, later;
83
84 later = function () {
85 // reset lock and call if queued
86 lock = false;
87 if (args) {
88 wrapperFn.apply(context, args);
89 args = false;
90 }
91 };
92
93 wrapperFn = function () {
94 if (lock) {
95 // called too soon, queue to call later
96 args = arguments;
97
98 } else {
99 // call and lock until later
100 fn.apply(context, arguments);
101 setTimeout(later, time);
102 lock = true;
103 }
104 };
105
106 return wrapperFn;
107 }
108
109 // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
110 // Returns the number `num` modulo `range` in such a way so it lies within
111 // `range[0]` and `range[1]`. The returned value will be always smaller than
112 // `range[1]` unless `includeMax` is set to `true`.
113 function wrapNum(x, range, includeMax) {
114 var max = range[1],
115 min = range[0],
116 d = max - min;
117 return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
118 }
119
120 // @function falseFn(): Function
121 // Returns a function which always returns `false`.
122 function falseFn() { return false; }
123
124 // @function formatNum(num: Number, precision?: Number|false): Number
125 // Returns the number `num` rounded with specified `precision`.
126 // The default `precision` value is 6 decimal places.
127 // `false` can be passed to skip any processing (can be useful to avoid round-off errors).
128 function formatNum(num, precision) {
129 if (precision === false) { return num; }
130 var pow = Math.pow(10, precision === undefined ? 6 : precision);
131 return Math.round(num * pow) / pow;
132 }
133
134 // @function trim(str: String): String
135 // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
136 function trim(str) {
137 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
138 }
139
140 // @function splitWords(str: String): String[]
141 // Trims and splits the string on whitespace and returns the array of parts.
142 function splitWords(str) {
143 return trim(str).split(/\s+/);
144 }
145
146 // @function setOptions(obj: Object, options: Object): Object
147 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
148 function setOptions(obj, options) {
149 if (!Object.prototype.hasOwnProperty.call(obj, 'options')) {
150 obj.options = obj.options ? create$2(obj.options) : {};
151 }
152 for (var i in options) {
153 obj.options[i] = options[i];
154 }
155 return obj.options;
156 }
157
158 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
159 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
160 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
161 // be appended at the end. If `uppercase` is `true`, the parameter names will
162 // be uppercased (e.g. `'?A=foo&B=bar'`)
163 function getParamString(obj, existingUrl, uppercase) {
164 var params = [];
165 for (var i in obj) {
166 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
167 }
168 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
169 }
170
171 var templateRe = /\{ *([\w_ -]+) *\}/g;
172
173 // @function template(str: String, data: Object): String
174 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
175 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
176 // `('Hello foo, bar')`. You can also specify functions instead of strings for
177 // data values — they will be evaluated passing `data` as an argument.
178 function template(str, data) {
179 return str.replace(templateRe, function (str, key) {
180 var value = data[key];
181
182 if (value === undefined) {
183 throw new Error('No value provided for variable ' + str);
184
185 } else if (typeof value === 'function') {
186 value = value(data);
187 }
188 return value;
189 });
190 }
191
192 // @function isArray(obj): Boolean
193 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
194 var isArray = Array.isArray || function (obj) {
195 return (Object.prototype.toString.call(obj) === '[object Array]');
196 };
197
198 // @function indexOf(array: Array, el: Object): Number
199 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
200 function indexOf(array, el) {
201 for (var i = 0; i < array.length; i++) {
202 if (array[i] === el) { return i; }
203 }
204 return -1;
205 }
206
207 // @property emptyImageUrl: String
208 // Data URI string containing a base64-encoded empty GIF image.
209 // Used as a hack to free memory from unused images on WebKit-powered
210 // mobile devices (by setting image `src` to this string).
211 var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
212
213 // inspired by https://paulirish.com/2011/requestanimationframe-for-smart-animating/
214
215 function getPrefixed(name) {
216 return window['webkit' + name] || window['moz' + name] || window['ms' + name];
217 }
218
219 var lastTime = 0;
220
221 // fallback for IE 7-8
222 function timeoutDefer(fn) {
223 var time = +new Date(),
224 timeToCall = Math.max(0, 16 - (time - lastTime));
225
226 lastTime = time + timeToCall;
227 return window.setTimeout(fn, timeToCall);
228 }
229
230 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
231 var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
232 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
233
234 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
235 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
236 // `context` if given. When `immediate` is set, `fn` is called immediately if
237 // the browser doesn't have native support for
238 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
239 // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
240 function requestAnimFrame(fn, context, immediate) {
241 if (immediate && requestFn === timeoutDefer) {
242 fn.call(context);
243 } else {
244 return requestFn.call(window, bind(fn, context));
245 }
246 }
247
248 // @function cancelAnimFrame(id: Number): undefined
249 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
250 function cancelAnimFrame(id) {
251 if (id) {
252 cancelFn.call(window, id);
253 }
254 }
255
256 var Util = {
257 __proto__: null,
258 extend: extend,
259 create: create$2,
260 bind: bind,
261 get lastId () { return lastId; },
262 stamp: stamp,
263 throttle: throttle,
264 wrapNum: wrapNum,
265 falseFn: falseFn,
266 formatNum: formatNum,
267 trim: trim,
268 splitWords: splitWords,
269 setOptions: setOptions,
270 getParamString: getParamString,
271 template: template,
272 isArray: isArray,
273 indexOf: indexOf,
274 emptyImageUrl: emptyImageUrl,
275 requestFn: requestFn,
276 cancelFn: cancelFn,
277 requestAnimFrame: requestAnimFrame,
278 cancelAnimFrame: cancelAnimFrame
279 };
280
281 // @class Class
282 // @aka L.Class
283
284 // @section
285 // @uninheritable
286
287 // Thanks to John Resig and Dean Edwards for inspiration!
288
289 function Class() {}
290
291 Class.extend = function (props) {
292
293 // @function extend(props: Object): Function
294 // [Extends the current class](#class-inheritance) given the properties to be included.
295 // Returns a Javascript function that is a class constructor (to be called with `new`).
296 var NewClass = function () {
297
298 setOptions(this);
299
300 // call the constructor
301 if (this.initialize) {
302 this.initialize.apply(this, arguments);
303 }
304
305 // call all constructor hooks
306 this.callInitHooks();
307 };
308
309 var parentProto = NewClass.__super__ = this.prototype;
310
311 var proto = create$2(parentProto);
312 proto.constructor = NewClass;
313
314 NewClass.prototype = proto;
315
316 // inherit parent's statics
317 for (var i in this) {
318 if (Object.prototype.hasOwnProperty.call(this, i) && i !== 'prototype' && i !== '__super__') {
319 NewClass[i] = this[i];
320 }
321 }
322
323 // mix static properties into the class
324 if (props.statics) {
325 extend(NewClass, props.statics);
326 }
327
328 // mix includes into the prototype
329 if (props.includes) {
330 checkDeprecatedMixinEvents(props.includes);
331 extend.apply(null, [proto].concat(props.includes));
332 }
333
334 // mix given properties into the prototype
335 extend(proto, props);
336 delete proto.statics;
337 delete proto.includes;
338
339 // merge options
340 if (proto.options) {
341 proto.options = parentProto.options ? create$2(parentProto.options) : {};
342 extend(proto.options, props.options);
343 }
344
345 proto._initHooks = [];
346
347 // add method for calling all hooks
348 proto.callInitHooks = function () {
349
350 if (this._initHooksCalled) { return; }
351
352 if (parentProto.callInitHooks) {
353 parentProto.callInitHooks.call(this);
354 }
355
356 this._initHooksCalled = true;
357
358 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
359 proto._initHooks[i].call(this);
360 }
361 };
362
363 return NewClass;
364 };
365
366
367 // @function include(properties: Object): this
368 // [Includes a mixin](#class-includes) into the current class.
369 Class.include = function (props) {
370 var parentOptions = this.prototype.options;
371 extend(this.prototype, props);
372 if (props.options) {
373 this.prototype.options = parentOptions;
374 this.mergeOptions(props.options);
375 }
376 return this;
377 };
378
379 // @function mergeOptions(options: Object): this
380 // [Merges `options`](#class-options) into the defaults of the class.
381 Class.mergeOptions = function (options) {
382 extend(this.prototype.options, options);
383 return this;
384 };
385
386 // @function addInitHook(fn: Function): this
387 // Adds a [constructor hook](#class-constructor-hooks) to the class.
388 Class.addInitHook = function (fn) { // (Function) || (String, args...)
389 var args = Array.prototype.slice.call(arguments, 1);
390
391 var init = typeof fn === 'function' ? fn : function () {
392 this[fn].apply(this, args);
393 };
394
395 this.prototype._initHooks = this.prototype._initHooks || [];
396 this.prototype._initHooks.push(init);
397 return this;
398 };
399
400 function checkDeprecatedMixinEvents(includes) {
401 /* global L: true */
402 if (typeof L === 'undefined' || !L || !L.Mixin) { return; }
403
404 includes = isArray(includes) ? includes : [includes];
405
406 for (var i = 0; i < includes.length; i++) {
407 if (includes[i] === L.Mixin.Events) {
408 console.warn('Deprecated include of L.Mixin.Events: ' +
409 'this property will be removed in future releases, ' +
410 'please inherit from L.Evented instead.', new Error().stack);
411 }
412 }
413 }
414
415 /*
416 * @class Evented
417 * @aka L.Evented
418 * @inherits Class
419 *
420 * A set of methods shared between event-powered classes (like `Map` and `Marker`). Generally, events allow you to execute some function when something happens with an object (e.g. the user clicks on the map, causing the map to fire `'click'` event).
421 *
422 * @example
423 *
424 * ```js
425 * map.on('click', function(e) {
426 * alert(e.latlng);
427 * } );
428 * ```
429 *
430 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
431 *
432 * ```js
433 * function onClick(e) { ... }
434 *
435 * map.on('click', onClick);
436 * map.off('click', onClick);
437 * ```
438 */
439
440 var Events = {
441 /* @method on(type: String, fn: Function, context?: Object): this
442 * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`).
443 *
444 * @alternative
445 * @method on(eventMap: Object): this
446 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
447 */
448 on: function (types, fn, context) {
449
450 // types can be a map of types/handlers
451 if (typeof types === 'object') {
452 for (var type in types) {
453 // we don't process space-separated events here for performance;
454 // it's a hot path since Layer uses the on(obj) syntax
455 this._on(type, types[type], fn);
456 }
457
458 } else {
459 // types can be a string of space-separated words
460 types = splitWords(types);
461
462 for (var i = 0, len = types.length; i < len; i++) {
463 this._on(types[i], fn, context);
464 }
465 }
466
467 return this;
468 },
469
470 /* @method off(type: String, fn?: Function, context?: Object): this
471 * Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener.
472 *
473 * @alternative
474 * @method off(eventMap: Object): this
475 * Removes a set of type/listener pairs.
476 *
477 * @alternative
478 * @method off: this
479 * Removes all listeners to all events on the object. This includes implicitly attached events.
480 */
481 off: function (types, fn, context) {
482
483 if (!arguments.length) {
484 // clear all listeners if called without arguments
485 delete this._events;
486
487 } else if (typeof types === 'object') {
488 for (var type in types) {
489 this._off(type, types[type], fn);
490 }
491
492 } else {
493 types = splitWords(types);
494
495 var removeAll = arguments.length === 1;
496 for (var i = 0, len = types.length; i < len; i++) {
497 if (removeAll) {
498 this._off(types[i]);
499 } else {
500 this._off(types[i], fn, context);
501 }
502 }
503 }
504
505 return this;
506 },
507
508 // attach listener (without syntactic sugar now)
509 _on: function (type, fn, context, _once) {
510 if (typeof fn !== 'function') {
511 console.warn('wrong listener type: ' + typeof fn);
512 return;
513 }
514
515 // check if fn already there
516 if (this._listens(type, fn, context) !== false) {
517 return;
518 }
519
520 if (context === this) {
521 // Less memory footprint.
522 context = undefined;
523 }
524
525 var newListener = {fn: fn, ctx: context};
526 if (_once) {
527 newListener.once = true;
528 }
529
530 this._events = this._events || {};
531 this._events[type] = this._events[type] || [];
532 this._events[type].push(newListener);
533 },
534
535 _off: function (type, fn, context) {
536 var listeners,
537 i,
538 len;
539
540 if (!this._events) {
541 return;
542 }
543
544 listeners = this._events[type];
545 if (!listeners) {
546 return;
547 }
548
549 if (arguments.length === 1) { // remove all
550 if (this._firingCount) {
551 // Set all removed listeners to noop
552 // so they are not called if remove happens in fire
553 for (i = 0, len = listeners.length; i < len; i++) {
554 listeners[i].fn = falseFn;
555 }
556 }
557 // clear all listeners for a type if function isn't specified
558 delete this._events[type];
559 return;
560 }
561
562 if (typeof fn !== 'function') {
563 console.warn('wrong listener type: ' + typeof fn);
564 return;
565 }
566
567 // find fn and remove it
568 var index = this._listens(type, fn, context);
569 if (index !== false) {
570 var listener = listeners[index];
571 if (this._firingCount) {
572 // set the removed listener to noop so that's not called if remove happens in fire
573 listener.fn = falseFn;
574
575 /* copy array in case events are being fired */
576 this._events[type] = listeners = listeners.slice();
577 }
578 listeners.splice(index, 1);
579 }
580 },
581
582 // @method fire(type: String, data?: Object, propagate?: Boolean): this
583 // Fires an event of the specified type. You can optionally provide a data
584 // object — the first argument of the listener function will contain its
585 // properties. The event can optionally be propagated to event parents.
586 fire: function (type, data, propagate) {
587 if (!this.listens(type, propagate)) { return this; }
588
589 var event = extend({}, data, {
590 type: type,
591 target: this,
592 sourceTarget: data && data.sourceTarget || this
593 });
594
595 if (this._events) {
596 var listeners = this._events[type];
597 if (listeners) {
598 this._firingCount = (this._firingCount + 1) || 1;
599 for (var i = 0, len = listeners.length; i < len; i++) {
600 var l = listeners[i];
601 // off overwrites l.fn, so we need to copy fn to a var
602 var fn = l.fn;
603 if (l.once) {
604 this.off(type, fn, l.ctx);
605 }
606 fn.call(l.ctx || this, event);
607 }
608
609 this._firingCount--;
610 }
611 }
612
613 if (propagate) {
614 // propagate the event to parents (set with addEventParent)
615 this._propagateEvent(event);
616 }
617
618 return this;
619 },
620
621 // @method listens(type: String, propagate?: Boolean): Boolean
622 // @method listens(type: String, fn: Function, context?: Object, propagate?: Boolean): Boolean
623 // Returns `true` if a particular event type has any listeners attached to it.
624 // The verification can optionally be propagated, it will return `true` if parents have the listener attached to it.
625 listens: function (type, fn, context, propagate) {
626 if (typeof type !== 'string') {
627 console.warn('"string" type argument expected');
628 }
629
630 // we don't overwrite the input `fn` value, because we need to use it for propagation
631 var _fn = fn;
632 if (typeof fn !== 'function') {
633 propagate = !!fn;
634 _fn = undefined;
635 context = undefined;
636 }
637
638 var listeners = this._events && this._events[type];
639 if (listeners && listeners.length) {
640 if (this._listens(type, _fn, context) !== false) {
641 return true;
642 }
643 }
644
645 if (propagate) {
646 // also check parents for listeners if event propagates
647 for (var id in this._eventParents) {
648 if (this._eventParents[id].listens(type, fn, context, propagate)) { return true; }
649 }
650 }
651 return false;
652 },
653
654 // returns the index (number) or false
655 _listens: function (type, fn, context) {
656 if (!this._events) {
657 return false;
658 }
659
660 var listeners = this._events[type] || [];
661 if (!fn) {
662 return !!listeners.length;
663 }
664
665 if (context === this) {
666 // Less memory footprint.
667 context = undefined;
668 }
669
670 for (var i = 0, len = listeners.length; i < len; i++) {
671 if (listeners[i].fn === fn && listeners[i].ctx === context) {
672 return i;
673 }
674 }
675 return false;
676
677 },
678
679 // @method once(…): this
680 // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
681 once: function (types, fn, context) {
682
683 // types can be a map of types/handlers
684 if (typeof types === 'object') {
685 for (var type in types) {
686 // we don't process space-separated events here for performance;
687 // it's a hot path since Layer uses the on(obj) syntax
688 this._on(type, types[type], fn, true);
689 }
690
691 } else {
692 // types can be a string of space-separated words
693 types = splitWords(types);
694
695 for (var i = 0, len = types.length; i < len; i++) {
696 this._on(types[i], fn, context, true);
697 }
698 }
699
700 return this;
701 },
702
703 // @method addEventParent(obj: Evented): this
704 // Adds an event parent - an `Evented` that will receive propagated events
705 addEventParent: function (obj) {
706 this._eventParents = this._eventParents || {};
707 this._eventParents[stamp(obj)] = obj;
708 return this;
709 },
710
711 // @method removeEventParent(obj: Evented): this
712 // Removes an event parent, so it will stop receiving propagated events
713 removeEventParent: function (obj) {
714 if (this._eventParents) {
715 delete this._eventParents[stamp(obj)];
716 }
717 return this;
718 },
719
720 _propagateEvent: function (e) {
721 for (var id in this._eventParents) {
722 this._eventParents[id].fire(e.type, extend({
723 layer: e.target,
724 propagatedFrom: e.target
725 }, e), true);
726 }
727 }
728 };
729
730 // aliases; we should ditch those eventually
731
732 // @method addEventListener(…): this
733 // Alias to [`on(…)`](#evented-on)
734 Events.addEventListener = Events.on;
735
736 // @method removeEventListener(…): this
737 // Alias to [`off(…)`](#evented-off)
738
739 // @method clearAllEventListeners(…): this
740 // Alias to [`off()`](#evented-off)
741 Events.removeEventListener = Events.clearAllEventListeners = Events.off;
742
743 // @method addOneTimeEventListener(…): this
744 // Alias to [`once(…)`](#evented-once)
745 Events.addOneTimeEventListener = Events.once;
746
747 // @method fireEvent(…): this
748 // Alias to [`fire(…)`](#evented-fire)
749 Events.fireEvent = Events.fire;
750
751 // @method hasEventListeners(…): Boolean
752 // Alias to [`listens(…)`](#evented-listens)
753 Events.hasEventListeners = Events.listens;
754
755 var Evented = Class.extend(Events);
756
757 /*
758 * @class Point
759 * @aka L.Point
760 *
761 * Represents a point with `x` and `y` coordinates in pixels.
762 *
763 * @example
764 *
765 * ```js
766 * var point = L.point(200, 300);
767 * ```
768 *
769 * All Leaflet methods and options that accept `Point` objects also accept them in a simple Array form (unless noted otherwise), so these lines are equivalent:
770 *
771 * ```js
772 * map.panBy([200, 300]);
773 * map.panBy(L.point(200, 300));
774 * ```
775 *
776 * Note that `Point` does not inherit from Leaflet's `Class` object,
777 * which means new classes can't inherit from it, and new methods
778 * can't be added to it with the `include` function.
779 */
780
781 function Point(x, y, round) {
782 // @property x: Number; The `x` coordinate of the point
783 this.x = (round ? Math.round(x) : x);
784 // @property y: Number; The `y` coordinate of the point
785 this.y = (round ? Math.round(y) : y);
786 }
787
788 var trunc = Math.trunc || function (v) {
789 return v > 0 ? Math.floor(v) : Math.ceil(v);
790 };
791
792 Point.prototype = {
793
794 // @method clone(): Point
795 // Returns a copy of the current point.
796 clone: function () {
797 return new Point(this.x, this.y);
798 },
799
800 // @method add(otherPoint: Point): Point
801 // Returns the result of addition of the current and the given points.
802 add: function (point) {
803 // non-destructive, returns a new point
804 return this.clone()._add(toPoint(point));
805 },
806
807 _add: function (point) {
808 // destructive, used directly for performance in situations where it's safe to modify existing point
809 this.x += point.x;
810 this.y += point.y;
811 return this;
812 },
813
814 // @method subtract(otherPoint: Point): Point
815 // Returns the result of subtraction of the given point from the current.
816 subtract: function (point) {
817 return this.clone()._subtract(toPoint(point));
818 },
819
820 _subtract: function (point) {
821 this.x -= point.x;
822 this.y -= point.y;
823 return this;
824 },
825
826 // @method divideBy(num: Number): Point
827 // Returns the result of division of the current point by the given number.
828 divideBy: function (num) {
829 return this.clone()._divideBy(num);
830 },
831
832 _divideBy: function (num) {
833 this.x /= num;
834 this.y /= num;
835 return this;
836 },
837
838 // @method multiplyBy(num: Number): Point
839 // Returns the result of multiplication of the current point by the given number.
840 multiplyBy: function (num) {
841 return this.clone()._multiplyBy(num);
842 },
843
844 _multiplyBy: function (num) {
845 this.x *= num;
846 this.y *= num;
847 return this;
848 },
849
850 // @method scaleBy(scale: Point): Point
851 // Multiply each coordinate of the current point by each coordinate of
852 // `scale`. In linear algebra terms, multiply the point by the
853 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
854 // defined by `scale`.
855 scaleBy: function (point) {
856 return new Point(this.x * point.x, this.y * point.y);
857 },
858
859 // @method unscaleBy(scale: Point): Point
860 // Inverse of `scaleBy`. Divide each coordinate of the current point by
861 // each coordinate of `scale`.
862 unscaleBy: function (point) {
863 return new Point(this.x / point.x, this.y / point.y);
864 },
865
866 // @method round(): Point
867 // Returns a copy of the current point with rounded coordinates.
868 round: function () {
869 return this.clone()._round();
870 },
871
872 _round: function () {
873 this.x = Math.round(this.x);
874 this.y = Math.round(this.y);
875 return this;
876 },
877
878 // @method floor(): Point
879 // Returns a copy of the current point with floored coordinates (rounded down).
880 floor: function () {
881 return this.clone()._floor();
882 },
883
884 _floor: function () {
885 this.x = Math.floor(this.x);
886 this.y = Math.floor(this.y);
887 return this;
888 },
889
890 // @method ceil(): Point
891 // Returns a copy of the current point with ceiled coordinates (rounded up).
892 ceil: function () {
893 return this.clone()._ceil();
894 },
895
896 _ceil: function () {
897 this.x = Math.ceil(this.x);
898 this.y = Math.ceil(this.y);
899 return this;
900 },
901
902 // @method trunc(): Point
903 // Returns a copy of the current point with truncated coordinates (rounded towards zero).
904 trunc: function () {
905 return this.clone()._trunc();
906 },
907
908 _trunc: function () {
909 this.x = trunc(this.x);
910 this.y = trunc(this.y);
911 return this;
912 },
913
914 // @method distanceTo(otherPoint: Point): Number
915 // Returns the cartesian distance between the current and the given points.
916 distanceTo: function (point) {
917 point = toPoint(point);
918
919 var x = point.x - this.x,
920 y = point.y - this.y;
921
922 return Math.sqrt(x * x + y * y);
923 },
924
925 // @method equals(otherPoint: Point): Boolean
926 // Returns `true` if the given point has the same coordinates.
927 equals: function (point) {
928 point = toPoint(point);
929
930 return point.x === this.x &&
931 point.y === this.y;
932 },
933
934 // @method contains(otherPoint: Point): Boolean
935 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
936 contains: function (point) {
937 point = toPoint(point);
938
939 return Math.abs(point.x) <= Math.abs(this.x) &&
940 Math.abs(point.y) <= Math.abs(this.y);
941 },
942
943 // @method toString(): String
944 // Returns a string representation of the point for debugging purposes.
945 toString: function () {
946 return 'Point(' +
947 formatNum(this.x) + ', ' +
948 formatNum(this.y) + ')';
949 }
950 };
951
952 // @factory L.point(x: Number, y: Number, round?: Boolean)
953 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
954
955 // @alternative
956 // @factory L.point(coords: Number[])
957 // Expects an array of the form `[x, y]` instead.
958
959 // @alternative
960 // @factory L.point(coords: Object)
961 // Expects a plain object of the form `{x: Number, y: Number}` instead.
962 function toPoint(x, y, round) {
963 if (x instanceof Point) {
964 return x;
965 }
966 if (isArray(x)) {
967 return new Point(x[0], x[1]);
968 }
969 if (x === undefined || x === null) {
970 return x;
971 }
972 if (typeof x === 'object' && 'x' in x && 'y' in x) {
973 return new Point(x.x, x.y);
974 }
975 return new Point(x, y, round);
976 }
977
978 /*
979 * @class Bounds
980 * @aka L.Bounds
981 *
982 * Represents a rectangular area in pixel coordinates.
983 *
984 * @example
985 *
986 * ```js
987 * var p1 = L.point(10, 10),
988 * p2 = L.point(40, 60),
989 * bounds = L.bounds(p1, p2);
990 * ```
991 *
992 * All Leaflet methods that accept `Bounds` objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
993 *
994 * ```js
995 * otherBounds.intersects([[10, 10], [40, 60]]);
996 * ```
997 *
998 * Note that `Bounds` does not inherit from Leaflet's `Class` object,
999 * which means new classes can't inherit from it, and new methods
1000 * can't be added to it with the `include` function.
1001 */
1002
1003 function Bounds(a, b) {
1004 if (!a) { return; }
1005
1006 var points = b ? [a, b] : a;
1007
1008 for (var i = 0, len = points.length; i < len; i++) {
1009 this.extend(points[i]);
1010 }
1011 }
1012
1013 Bounds.prototype = {
1014 // @method extend(point: Point): this
1015 // Extends the bounds to contain the given point.
1016
1017 // @alternative
1018 // @method extend(otherBounds: Bounds): this
1019 // Extend the bounds to contain the given bounds
1020 extend: function (obj) {
1021 var min2, max2;
1022 if (!obj) { return this; }
1023
1024 if (obj instanceof Point || typeof obj[0] === 'number' || 'x' in obj) {
1025 min2 = max2 = toPoint(obj);
1026 } else {
1027 obj = toBounds(obj);
1028 min2 = obj.min;
1029 max2 = obj.max;
1030
1031 if (!min2 || !max2) { return this; }
1032 }
1033
1034 // @property min: Point
1035 // The top left corner of the rectangle.
1036 // @property max: Point
1037 // The bottom right corner of the rectangle.
1038 if (!this.min && !this.max) {
1039 this.min = min2.clone();
1040 this.max = max2.clone();
1041 } else {
1042 this.min.x = Math.min(min2.x, this.min.x);
1043 this.max.x = Math.max(max2.x, this.max.x);
1044 this.min.y = Math.min(min2.y, this.min.y);
1045 this.max.y = Math.max(max2.y, this.max.y);
1046 }
1047 return this;
1048 },
1049
1050 // @method getCenter(round?: Boolean): Point
1051 // Returns the center point of the bounds.
1052 getCenter: function (round) {
1053 return toPoint(
1054 (this.min.x + this.max.x) / 2,
1055 (this.min.y + this.max.y) / 2, round);
1056 },
1057
1058 // @method getBottomLeft(): Point
1059 // Returns the bottom-left point of the bounds.
1060 getBottomLeft: function () {
1061 return toPoint(this.min.x, this.max.y);
1062 },
1063
1064 // @method getTopRight(): Point
1065 // Returns the top-right point of the bounds.
1066 getTopRight: function () { // -> Point
1067 return toPoint(this.max.x, this.min.y);
1068 },
1069
1070 // @method getTopLeft(): Point
1071 // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).
1072 getTopLeft: function () {
1073 return this.min; // left, top
1074 },
1075
1076 // @method getBottomRight(): Point
1077 // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).
1078 getBottomRight: function () {
1079 return this.max; // right, bottom
1080 },
1081
1082 // @method getSize(): Point
1083 // Returns the size of the given bounds
1084 getSize: function () {
1085 return this.max.subtract(this.min);
1086 },
1087
1088 // @method contains(otherBounds: Bounds): Boolean
1089 // Returns `true` if the rectangle contains the given one.
1090 // @alternative
1091 // @method contains(point: Point): Boolean
1092 // Returns `true` if the rectangle contains the given point.
1093 contains: function (obj) {
1094 var min, max;
1095
1096 if (typeof obj[0] === 'number' || obj instanceof Point) {
1097 obj = toPoint(obj);
1098 } else {
1099 obj = toBounds(obj);
1100 }
1101
1102 if (obj instanceof Bounds) {
1103 min = obj.min;
1104 max = obj.max;
1105 } else {
1106 min = max = obj;
1107 }
1108
1109 return (min.x >= this.min.x) &&
1110 (max.x <= this.max.x) &&
1111 (min.y >= this.min.y) &&
1112 (max.y <= this.max.y);
1113 },
1114
1115 // @method intersects(otherBounds: Bounds): Boolean
1116 // Returns `true` if the rectangle intersects the given bounds. Two bounds
1117 // intersect if they have at least one point in common.
1118 intersects: function (bounds) { // (Bounds) -> Boolean
1119 bounds = toBounds(bounds);
1120
1121 var min = this.min,
1122 max = this.max,
1123 min2 = bounds.min,
1124 max2 = bounds.max,
1125 xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1126 yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1127
1128 return xIntersects && yIntersects;
1129 },
1130
1131 // @method overlaps(otherBounds: Bounds): Boolean
1132 // Returns `true` if the rectangle overlaps the given bounds. Two bounds
1133 // overlap if their intersection is an area.
1134 overlaps: function (bounds) { // (Bounds) -> Boolean
1135 bounds = toBounds(bounds);
1136
1137 var min = this.min,
1138 max = this.max,
1139 min2 = bounds.min,
1140 max2 = bounds.max,
1141 xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1142 yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1143
1144 return xOverlaps && yOverlaps;
1145 },
1146
1147 // @method isValid(): Boolean
1148 // Returns `true` if the bounds are properly initialized.
1149 isValid: function () {
1150 return !!(this.min && this.max);
1151 },
1152
1153
1154 // @method pad(bufferRatio: Number): Bounds
1155 // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
1156 // For example, a ratio of 0.5 extends the bounds by 50% in each direction.
1157 // Negative values will retract the bounds.
1158 pad: function (bufferRatio) {
1159 var min = this.min,
1160 max = this.max,
1161 heightBuffer = Math.abs(min.x - max.x) * bufferRatio,
1162 widthBuffer = Math.abs(min.y - max.y) * bufferRatio;
1163
1164
1165 return toBounds(
1166 toPoint(min.x - heightBuffer, min.y - widthBuffer),
1167 toPoint(max.x + heightBuffer, max.y + widthBuffer));
1168 },
1169
1170
1171 // @method equals(otherBounds: Bounds): Boolean
1172 // Returns `true` if the rectangle is equivalent to the given bounds.
1173 equals: function (bounds) {
1174 if (!bounds) { return false; }
1175
1176 bounds = toBounds(bounds);
1177
1178 return this.min.equals(bounds.getTopLeft()) &&
1179 this.max.equals(bounds.getBottomRight());
1180 },
1181 };
1182
1183
1184 // @factory L.bounds(corner1: Point, corner2: Point)
1185 // Creates a Bounds object from two corners coordinate pairs.
1186 // @alternative
1187 // @factory L.bounds(points: Point[])
1188 // Creates a Bounds object from the given array of points.
1189 function toBounds(a, b) {
1190 if (!a || a instanceof Bounds) {
1191 return a;
1192 }
1193 return new Bounds(a, b);
1194 }
1195
1196 /*
1197 * @class LatLngBounds
1198 * @aka L.LatLngBounds
1199 *
1200 * Represents a rectangular geographical area on a map.
1201 *
1202 * @example
1203 *
1204 * ```js
1205 * var corner1 = L.latLng(40.712, -74.227),
1206 * corner2 = L.latLng(40.774, -74.125),
1207 * bounds = L.latLngBounds(corner1, corner2);
1208 * ```
1209 *
1210 * All Leaflet methods that accept LatLngBounds objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
1211 *
1212 * ```js
1213 * map.fitBounds([
1214 * [40.712, -74.227],
1215 * [40.774, -74.125]
1216 * ]);
1217 * ```
1218 *
1219 * Caution: if the area crosses the antimeridian (often confused with the International Date Line), you must specify corners _outside_ the [-180, 180] degrees longitude range.
1220 *
1221 * Note that `LatLngBounds` does not inherit from Leaflet's `Class` object,
1222 * which means new classes can't inherit from it, and new methods
1223 * can't be added to it with the `include` function.
1224 */
1225
1226 function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
1227 if (!corner1) { return; }
1228
1229 var latlngs = corner2 ? [corner1, corner2] : corner1;
1230
1231 for (var i = 0, len = latlngs.length; i < len; i++) {
1232 this.extend(latlngs[i]);
1233 }
1234 }
1235
1236 LatLngBounds.prototype = {
1237
1238 // @method extend(latlng: LatLng): this
1239 // Extend the bounds to contain the given point
1240
1241 // @alternative
1242 // @method extend(otherBounds: LatLngBounds): this
1243 // Extend the bounds to contain the given bounds
1244 extend: function (obj) {
1245 var sw = this._southWest,
1246 ne = this._northEast,
1247 sw2, ne2;
1248
1249 if (obj instanceof LatLng) {
1250 sw2 = obj;
1251 ne2 = obj;
1252
1253 } else if (obj instanceof LatLngBounds) {
1254 sw2 = obj._southWest;
1255 ne2 = obj._northEast;
1256
1257 if (!sw2 || !ne2) { return this; }
1258
1259 } else {
1260 return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
1261 }
1262
1263 if (!sw && !ne) {
1264 this._southWest = new LatLng(sw2.lat, sw2.lng);
1265 this._northEast = new LatLng(ne2.lat, ne2.lng);
1266 } else {
1267 sw.lat = Math.min(sw2.lat, sw.lat);
1268 sw.lng = Math.min(sw2.lng, sw.lng);
1269 ne.lat = Math.max(ne2.lat, ne.lat);
1270 ne.lng = Math.max(ne2.lng, ne.lng);
1271 }
1272
1273 return this;
1274 },
1275
1276 // @method pad(bufferRatio: Number): LatLngBounds
1277 // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
1278 // For example, a ratio of 0.5 extends the bounds by 50% in each direction.
1279 // Negative values will retract the bounds.
1280 pad: function (bufferRatio) {
1281 var sw = this._southWest,
1282 ne = this._northEast,
1283 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1284 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1285
1286 return new LatLngBounds(
1287 new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1288 new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1289 },
1290
1291 // @method getCenter(): LatLng
1292 // Returns the center point of the bounds.
1293 getCenter: function () {
1294 return new LatLng(
1295 (this._southWest.lat + this._northEast.lat) / 2,
1296 (this._southWest.lng + this._northEast.lng) / 2);
1297 },
1298
1299 // @method getSouthWest(): LatLng
1300 // Returns the south-west point of the bounds.
1301 getSouthWest: function () {
1302 return this._southWest;
1303 },
1304
1305 // @method getNorthEast(): LatLng
1306 // Returns the north-east point of the bounds.
1307 getNorthEast: function () {
1308 return this._northEast;
1309 },
1310
1311 // @method getNorthWest(): LatLng
1312 // Returns the north-west point of the bounds.
1313 getNorthWest: function () {
1314 return new LatLng(this.getNorth(), this.getWest());
1315 },
1316
1317 // @method getSouthEast(): LatLng
1318 // Returns the south-east point of the bounds.
1319 getSouthEast: function () {
1320 return new LatLng(this.getSouth(), this.getEast());
1321 },
1322
1323 // @method getWest(): Number
1324 // Returns the west longitude of the bounds
1325 getWest: function () {
1326 return this._southWest.lng;
1327 },
1328
1329 // @method getSouth(): Number
1330 // Returns the south latitude of the bounds
1331 getSouth: function () {
1332 return this._southWest.lat;
1333 },
1334
1335 // @method getEast(): Number
1336 // Returns the east longitude of the bounds
1337 getEast: function () {
1338 return this._northEast.lng;
1339 },
1340
1341 // @method getNorth(): Number
1342 // Returns the north latitude of the bounds
1343 getNorth: function () {
1344 return this._northEast.lat;
1345 },
1346
1347 // @method contains(otherBounds: LatLngBounds): Boolean
1348 // Returns `true` if the rectangle contains the given one.
1349
1350 // @alternative
1351 // @method contains (latlng: LatLng): Boolean
1352 // Returns `true` if the rectangle contains the given point.
1353 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1354 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
1355 obj = toLatLng(obj);
1356 } else {
1357 obj = toLatLngBounds(obj);
1358 }
1359
1360 var sw = this._southWest,
1361 ne = this._northEast,
1362 sw2, ne2;
1363
1364 if (obj instanceof LatLngBounds) {
1365 sw2 = obj.getSouthWest();
1366 ne2 = obj.getNorthEast();
1367 } else {
1368 sw2 = ne2 = obj;
1369 }
1370
1371 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1372 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1373 },
1374
1375 // @method intersects(otherBounds: LatLngBounds): Boolean
1376 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1377 intersects: function (bounds) {
1378 bounds = toLatLngBounds(bounds);
1379
1380 var sw = this._southWest,
1381 ne = this._northEast,
1382 sw2 = bounds.getSouthWest(),
1383 ne2 = bounds.getNorthEast(),
1384
1385 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1386 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1387
1388 return latIntersects && lngIntersects;
1389 },
1390
1391 // @method overlaps(otherBounds: LatLngBounds): Boolean
1392 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1393 overlaps: function (bounds) {
1394 bounds = toLatLngBounds(bounds);
1395
1396 var sw = this._southWest,
1397 ne = this._northEast,
1398 sw2 = bounds.getSouthWest(),
1399 ne2 = bounds.getNorthEast(),
1400
1401 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1402 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1403
1404 return latOverlaps && lngOverlaps;
1405 },
1406
1407 // @method toBBoxString(): String
1408 // Returns a string with bounding box coordinates in a 'southwest_lng,southwest_lat,northeast_lng,northeast_lat' format. Useful for sending requests to web services that return geo data.
1409 toBBoxString: function () {
1410 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1411 },
1412
1413 // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
1414 // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overridden by setting `maxMargin` to a small number.
1415 equals: function (bounds, maxMargin) {
1416 if (!bounds) { return false; }
1417
1418 bounds = toLatLngBounds(bounds);
1419
1420 return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
1421 this._northEast.equals(bounds.getNorthEast(), maxMargin);
1422 },
1423
1424 // @method isValid(): Boolean
1425 // Returns `true` if the bounds are properly initialized.
1426 isValid: function () {
1427 return !!(this._southWest && this._northEast);
1428 }
1429 };
1430
1431 // TODO International date line?
1432
1433 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
1434 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
1435
1436 // @alternative
1437 // @factory L.latLngBounds(latlngs: LatLng[])
1438 // Creates a `LatLngBounds` object defined by the geographical points it contains. Very useful for zooming the map to fit a particular set of locations with [`fitBounds`](#map-fitbounds).
1439 function toLatLngBounds(a, b) {
1440 if (a instanceof LatLngBounds) {
1441 return a;
1442 }
1443 return new LatLngBounds(a, b);
1444 }
1445
1446 /* @class LatLng
1447 * @aka L.LatLng
1448 *
1449 * Represents a geographical point with a certain latitude and longitude.
1450 *
1451 * @example
1452 *
1453 * ```
1454 * var latlng = L.latLng(50.5, 30.5);
1455 * ```
1456 *
1457 * All Leaflet methods that accept LatLng objects also accept them in a simple Array form and simple object form (unless noted otherwise), so these lines are equivalent:
1458 *
1459 * ```
1460 * map.panTo([50, 30]);
1461 * map.panTo({lon: 30, lat: 50});
1462 * map.panTo({lat: 50, lng: 30});
1463 * map.panTo(L.latLng(50, 30));
1464 * ```
1465 *
1466 * Note that `LatLng` does not inherit from Leaflet's `Class` object,
1467 * which means new classes can't inherit from it, and new methods
1468 * can't be added to it with the `include` function.
1469 */
1470
1471 function LatLng(lat, lng, alt) {
1472 if (isNaN(lat) || isNaN(lng)) {
1473 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1474 }
1475
1476 // @property lat: Number
1477 // Latitude in degrees
1478 this.lat = +lat;
1479
1480 // @property lng: Number
1481 // Longitude in degrees
1482 this.lng = +lng;
1483
1484 // @property alt: Number
1485 // Altitude in meters (optional)
1486 if (alt !== undefined) {
1487 this.alt = +alt;
1488 }
1489 }
1490
1491 LatLng.prototype = {
1492 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1493 // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overridden by setting `maxMargin` to a small number.
1494 equals: function (obj, maxMargin) {
1495 if (!obj) { return false; }
1496
1497 obj = toLatLng(obj);
1498
1499 var margin = Math.max(
1500 Math.abs(this.lat - obj.lat),
1501 Math.abs(this.lng - obj.lng));
1502
1503 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1504 },
1505
1506 // @method toString(): String
1507 // Returns a string representation of the point (for debugging purposes).
1508 toString: function (precision) {
1509 return 'LatLng(' +
1510 formatNum(this.lat, precision) + ', ' +
1511 formatNum(this.lng, precision) + ')';
1512 },
1513
1514 // @method distanceTo(otherLatLng: LatLng): Number
1515 // Returns the distance (in meters) to the given `LatLng` calculated using the [Spherical Law of Cosines](https://en.wikipedia.org/wiki/Spherical_law_of_cosines).
1516 distanceTo: function (other) {
1517 return Earth.distance(this, toLatLng(other));
1518 },
1519
1520 // @method wrap(): LatLng
1521 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1522 wrap: function () {
1523 return Earth.wrapLatLng(this);
1524 },
1525
1526 // @method toBounds(sizeInMeters: Number): LatLngBounds
1527 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
1528 toBounds: function (sizeInMeters) {
1529 var latAccuracy = 180 * sizeInMeters / 40075017,
1530 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1531
1532 return toLatLngBounds(
1533 [this.lat - latAccuracy, this.lng - lngAccuracy],
1534 [this.lat + latAccuracy, this.lng + lngAccuracy]);
1535 },
1536
1537 clone: function () {
1538 return new LatLng(this.lat, this.lng, this.alt);
1539 }
1540 };
1541
1542
1543
1544 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1545 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1546
1547 // @alternative
1548 // @factory L.latLng(coords: Array): LatLng
1549 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1550
1551 // @alternative
1552 // @factory L.latLng(coords: Object): LatLng
1553 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1554
1555 function toLatLng(a, b, c) {
1556 if (a instanceof LatLng) {
1557 return a;
1558 }
1559 if (isArray(a) && typeof a[0] !== 'object') {
1560 if (a.length === 3) {
1561 return new LatLng(a[0], a[1], a[2]);
1562 }
1563 if (a.length === 2) {
1564 return new LatLng(a[0], a[1]);
1565 }
1566 return null;
1567 }
1568 if (a === undefined || a === null) {
1569 return a;
1570 }
1571 if (typeof a === 'object' && 'lat' in a) {
1572 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1573 }
1574 if (b === undefined) {
1575 return null;
1576 }
1577 return new LatLng(a, b, c);
1578 }
1579
1580 /*
1581 * @namespace CRS
1582 * @crs L.CRS.Base
1583 * Object that defines coordinate reference systems for projecting
1584 * geographical points into pixel (screen) coordinates and back (and to
1585 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
1586 * [spatial reference system](https://en.wikipedia.org/wiki/Spatial_reference_system).
1587 *
1588 * Leaflet defines the most usual CRSs by default. If you want to use a
1589 * CRS not defined by default, take a look at the
1590 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
1591 *
1592 * Note that the CRS instances do not inherit from Leaflet's `Class` object,
1593 * and can't be instantiated. Also, new classes can't inherit from them,
1594 * and methods can't be added to them with the `include` function.
1595 */
1596
1597 var CRS = {
1598 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
1599 // Projects geographical coordinates into pixel coordinates for a given zoom.
1600 latLngToPoint: function (latlng, zoom) {
1601 var projectedPoint = this.projection.project(latlng),
1602 scale = this.scale(zoom);
1603
1604 return this.transformation._transform(projectedPoint, scale);
1605 },
1606
1607 // @method pointToLatLng(point: Point, zoom: Number): LatLng
1608 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
1609 // zoom into geographical coordinates.
1610 pointToLatLng: function (point, zoom) {
1611 var scale = this.scale(zoom),
1612 untransformedPoint = this.transformation.untransform(point, scale);
1613
1614 return this.projection.unproject(untransformedPoint);
1615 },
1616
1617 // @method project(latlng: LatLng): Point
1618 // Projects geographical coordinates into coordinates in units accepted for
1619 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
1620 project: function (latlng) {
1621 return this.projection.project(latlng);
1622 },
1623
1624 // @method unproject(point: Point): LatLng
1625 // Given a projected coordinate returns the corresponding LatLng.
1626 // The inverse of `project`.
1627 unproject: function (point) {
1628 return this.projection.unproject(point);
1629 },
1630
1631 // @method scale(zoom: Number): Number
1632 // Returns the scale used when transforming projected coordinates into
1633 // pixel coordinates for a particular zoom. For example, it returns
1634 // `256 * 2^zoom` for Mercator-based CRS.
1635 scale: function (zoom) {
1636 return 256 * Math.pow(2, zoom);
1637 },
1638
1639 // @method zoom(scale: Number): Number
1640 // Inverse of `scale()`, returns the zoom level corresponding to a scale
1641 // factor of `scale`.
1642 zoom: function (scale) {
1643 return Math.log(scale / 256) / Math.LN2;
1644 },
1645
1646 // @method getProjectedBounds(zoom: Number): Bounds
1647 // Returns the projection's bounds scaled and transformed for the provided `zoom`.
1648 getProjectedBounds: function (zoom) {
1649 if (this.infinite) { return null; }
1650
1651 var b = this.projection.bounds,
1652 s = this.scale(zoom),
1653 min = this.transformation.transform(b.min, s),
1654 max = this.transformation.transform(b.max, s);
1655
1656 return new Bounds(min, max);
1657 },
1658
1659 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
1660 // Returns the distance between two geographical coordinates.
1661
1662 // @property code: String
1663 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
1664 //
1665 // @property wrapLng: Number[]
1666 // An array of two numbers defining whether the longitude (horizontal) coordinate
1667 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
1668 // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
1669 //
1670 // @property wrapLat: Number[]
1671 // Like `wrapLng`, but for the latitude (vertical) axis.
1672
1673 // wrapLng: [min, max],
1674 // wrapLat: [min, max],
1675
1676 // @property infinite: Boolean
1677 // If true, the coordinate space will be unbounded (infinite in both axes)
1678 infinite: false,
1679
1680 // @method wrapLatLng(latlng: LatLng): LatLng
1681 // Returns a `LatLng` where lat and lng has been wrapped according to the
1682 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
1683 wrapLatLng: function (latlng) {
1684 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
1685 lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
1686 alt = latlng.alt;
1687
1688 return new LatLng(lat, lng, alt);
1689 },
1690
1691 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
1692 // Returns a `LatLngBounds` with the same size as the given one, ensuring
1693 // that its center is within the CRS's bounds.
1694 // Only accepts actual `L.LatLngBounds` instances, not arrays.
1695 wrapLatLngBounds: function (bounds) {
1696 var center = bounds.getCenter(),
1697 newCenter = this.wrapLatLng(center),
1698 latShift = center.lat - newCenter.lat,
1699 lngShift = center.lng - newCenter.lng;
1700
1701 if (latShift === 0 && lngShift === 0) {
1702 return bounds;
1703 }
1704
1705 var sw = bounds.getSouthWest(),
1706 ne = bounds.getNorthEast(),
1707 newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),
1708 newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);
1709
1710 return new LatLngBounds(newSw, newNe);
1711 }
1712 };
1713
1714 /*
1715 * @namespace CRS
1716 * @crs L.CRS.Earth
1717 *
1718 * Serves as the base for CRS that are global such that they cover the earth.
1719 * Can only be used as the base for other CRS and cannot be used directly,
1720 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
1721 * meters.
1722 */
1723
1724 var Earth = extend({}, CRS, {
1725 wrapLng: [-180, 180],
1726
1727 // Mean Earth Radius, as recommended for use by
1728 // the International Union of Geodesy and Geophysics,
1729 // see https://rosettacode.org/wiki/Haversine_formula
1730 R: 6371000,
1731
1732 // distance between two geographical points using spherical law of cosines approximation
1733 distance: function (latlng1, latlng2) {
1734 var rad = Math.PI / 180,
1735 lat1 = latlng1.lat * rad,
1736 lat2 = latlng2.lat * rad,
1737 sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2),
1738 sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2),
1739 a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon,
1740 c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1741 return this.R * c;
1742 }
1743 });
1744
1745 /*
1746 * @namespace Projection
1747 * @projection L.Projection.SphericalMercator
1748 *
1749 * Spherical Mercator projection — the most common projection for online maps,
1750 * used by almost all free and commercial tile providers. Assumes that Earth is
1751 * a sphere. Used by the `EPSG:3857` CRS.
1752 */
1753
1754 var earthRadius = 6378137;
1755
1756 var SphericalMercator = {
1757
1758 R: earthRadius,
1759 MAX_LATITUDE: 85.0511287798,
1760
1761 project: function (latlng) {
1762 var d = Math.PI / 180,
1763 max = this.MAX_LATITUDE,
1764 lat = Math.max(Math.min(max, latlng.lat), -max),
1765 sin = Math.sin(lat * d);
1766
1767 return new Point(
1768 this.R * latlng.lng * d,
1769 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
1770 },
1771
1772 unproject: function (point) {
1773 var d = 180 / Math.PI;
1774
1775 return new LatLng(
1776 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
1777 point.x * d / this.R);
1778 },
1779
1780 bounds: (function () {
1781 var d = earthRadius * Math.PI;
1782 return new Bounds([-d, -d], [d, d]);
1783 })()
1784 };
1785
1786 /*
1787 * @class Transformation
1788 * @aka L.Transformation
1789 *
1790 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1791 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1792 * the reverse. Used by Leaflet in its projections code.
1793 *
1794 * @example
1795 *
1796 * ```js
1797 * var transformation = L.transformation(2, 5, -1, 10),
1798 * p = L.point(1, 2),
1799 * p2 = transformation.transform(p), // L.point(7, 8)
1800 * p3 = transformation.untransform(p2); // L.point(1, 2)
1801 * ```
1802 */
1803
1804
1805 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1806 // Creates a `Transformation` object with the given coefficients.
1807 function Transformation(a, b, c, d) {
1808 if (isArray(a)) {
1809 // use array properties
1810 this._a = a[0];
1811 this._b = a[1];
1812 this._c = a[2];
1813 this._d = a[3];
1814 return;
1815 }
1816 this._a = a;
1817 this._b = b;
1818 this._c = c;
1819 this._d = d;
1820 }
1821
1822 Transformation.prototype = {
1823 // @method transform(point: Point, scale?: Number): Point
1824 // Returns a transformed point, optionally multiplied by the given scale.
1825 // Only accepts actual `L.Point` instances, not arrays.
1826 transform: function (point, scale) { // (Point, Number) -> Point
1827 return this._transform(point.clone(), scale);
1828 },
1829
1830 // destructive transform (faster)
1831 _transform: function (point, scale) {
1832 scale = scale || 1;
1833 point.x = scale * (this._a * point.x + this._b);
1834 point.y = scale * (this._c * point.y + this._d);
1835 return point;
1836 },
1837
1838 // @method untransform(point: Point, scale?: Number): Point
1839 // Returns the reverse transformation of the given point, optionally divided
1840 // by the given scale. Only accepts actual `L.Point` instances, not arrays.
1841 untransform: function (point, scale) {
1842 scale = scale || 1;
1843 return new Point(
1844 (point.x / scale - this._b) / this._a,
1845 (point.y / scale - this._d) / this._c);
1846 }
1847 };
1848
1849 // factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1850
1851 // @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1852 // Instantiates a Transformation object with the given coefficients.
1853
1854 // @alternative
1855 // @factory L.transformation(coefficients: Array): Transformation
1856 // Expects an coefficients array of the form
1857 // `[a: Number, b: Number, c: Number, d: Number]`.
1858
1859 function toTransformation(a, b, c, d) {
1860 return new Transformation(a, b, c, d);
1861 }
1862
1863 /*
1864 * @namespace CRS
1865 * @crs L.CRS.EPSG3857
1866 *
1867 * The most common CRS for online maps, used by almost all free and commercial
1868 * tile providers. Uses Spherical Mercator projection. Set in by default in
1869 * Map's `crs` option.
1870 */
1871
1872 var EPSG3857 = extend({}, Earth, {
1873 code: 'EPSG:3857',
1874 projection: SphericalMercator,
1875
1876 transformation: (function () {
1877 var scale = 0.5 / (Math.PI * SphericalMercator.R);
1878 return toTransformation(scale, 0.5, -scale, 0.5);
1879 }())
1880 });
1881
1882 var EPSG900913 = extend({}, EPSG3857, {
1883 code: 'EPSG:900913'
1884 });
1885
1886 // @namespace SVG; @section
1887 // There are several static functions which can be called without instantiating L.SVG:
1888
1889 // @function create(name: String): SVGElement
1890 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
1891 // corresponding to the class name passed. For example, using 'line' will return
1892 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
1893 function svgCreate(name) {
1894 return document.createElementNS('http://www.w3.org/2000/svg', name);
1895 }
1896
1897 // @function pointsToPath(rings: Point[], closed: Boolean): String
1898 // Generates a SVG path string for multiple rings, with each ring turning
1899 // into "M..L..L.." instructions
1900 function pointsToPath(rings, closed) {
1901 var str = '',
1902 i, j, len, len2, points, p;
1903
1904 for (i = 0, len = rings.length; i < len; i++) {
1905 points = rings[i];
1906
1907 for (j = 0, len2 = points.length; j < len2; j++) {
1908 p = points[j];
1909 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
1910 }
1911
1912 // closes the ring for polygons; "x" is VML syntax
1913 str += closed ? (Browser.svg ? 'z' : 'x') : '';
1914 }
1915
1916 // SVG complains about empty path strings
1917 return str || 'M0 0';
1918 }
1919
1920 /*
1921 * @namespace Browser
1922 * @aka L.Browser
1923 *
1924 * A namespace with static properties for browser/feature detection used by Leaflet internally.
1925 *
1926 * @example
1927 *
1928 * ```js
1929 * if (L.Browser.ielt9) {
1930 * alert('Upgrade your browser, dude!');
1931 * }
1932 * ```
1933 */
1934
1935 var style = document.documentElement.style;
1936
1937 // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
1938 var ie = 'ActiveXObject' in window;
1939
1940 // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
1941 var ielt9 = ie && !document.addEventListener;
1942
1943 // @property edge: Boolean; `true` for the Edge web browser.
1944 var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
1945
1946 // @property webkit: Boolean;
1947 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
1948 var webkit = userAgentContains('webkit');
1949
1950 // @property android: Boolean
1951 // **Deprecated.** `true` for any browser running on an Android platform.
1952 var android = userAgentContains('android');
1953
1954 // @property android23: Boolean; **Deprecated.** `true` for browsers running on Android 2 or Android 3.
1955 var android23 = userAgentContains('android 2') || userAgentContains('android 3');
1956
1957 /* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */
1958 var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit
1959 // @property androidStock: Boolean; **Deprecated.** `true` for the Android stock browser (i.e. not Chrome)
1960 var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);
1961
1962 // @property opera: Boolean; `true` for the Opera browser
1963 var opera = !!window.opera;
1964
1965 // @property chrome: Boolean; `true` for the Chrome browser.
1966 var chrome = !edge && userAgentContains('chrome');
1967
1968 // @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
1969 var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
1970
1971 // @property safari: Boolean; `true` for the Safari browser.
1972 var safari = !chrome && userAgentContains('safari');
1973
1974 var phantom = userAgentContains('phantom');
1975
1976 // @property opera12: Boolean
1977 // `true` for the Opera browser supporting CSS transforms (version 12 or later).
1978 var opera12 = 'OTransition' in style;
1979
1980 // @property win: Boolean; `true` when the browser is running in a Windows platform
1981 var win = navigator.platform.indexOf('Win') === 0;
1982
1983 // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
1984 var ie3d = ie && ('transition' in style);
1985
1986 // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
1987 var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;
1988
1989 // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
1990 var gecko3d = 'MozPerspective' in style;
1991
1992 // @property any3d: Boolean
1993 // `true` for all browsers supporting CSS transforms.
1994 var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
1995
1996 // @property mobile: Boolean; `true` for all browsers running in a mobile device.
1997 var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
1998
1999 // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
2000 var mobileWebkit = mobile && webkit;
2001
2002 // @property mobileWebkit3d: Boolean
2003 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
2004 var mobileWebkit3d = mobile && webkit3d;
2005
2006 // @property msPointer: Boolean
2007 // `true` for browsers implementing the Microsoft touch events model (notably IE10).
2008 var msPointer = !window.PointerEvent && window.MSPointerEvent;
2009
2010 // @property pointer: Boolean
2011 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
2012 var pointer = !!(window.PointerEvent || msPointer);
2013
2014 // @property touchNative: Boolean
2015 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
2016 // **This does not necessarily mean** that the browser is running in a computer with
2017 // a touchscreen, it only means that the browser is capable of understanding
2018 // touch events.
2019 var touchNative = 'ontouchstart' in window || !!window.TouchEvent;
2020
2021 // @property touch: Boolean
2022 // `true` for all browsers supporting either [touch](#browser-touch) or [pointer](#browser-pointer) events.
2023 // Note: pointer events will be preferred (if available), and processed for all `touch*` listeners.
2024 var touch = !window.L_NO_TOUCH && (touchNative || pointer);
2025
2026 // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
2027 var mobileOpera = mobile && opera;
2028
2029 // @property mobileGecko: Boolean
2030 // `true` for gecko-based browsers running in a mobile device.
2031 var mobileGecko = mobile && gecko;
2032
2033 // @property retina: Boolean
2034 // `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%.
2035 var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
2036
2037 // @property passiveEvents: Boolean
2038 // `true` for browsers that support passive events.
2039 var passiveEvents = (function () {
2040 var supportsPassiveOption = false;
2041 try {
2042 var opts = Object.defineProperty({}, 'passive', {
2043 get: function () { // eslint-disable-line getter-return
2044 supportsPassiveOption = true;
2045 }
2046 });
2047 window.addEventListener('testPassiveEventSupport', falseFn, opts);
2048 window.removeEventListener('testPassiveEventSupport', falseFn, opts);
2049 } catch (e) {
2050 // Errors can safely be ignored since this is only a browser support test.
2051 }
2052 return supportsPassiveOption;
2053 }());
2054
2055 // @property canvas: Boolean
2056 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
2057 var canvas$1 = (function () {
2058 return !!document.createElement('canvas').getContext;
2059 }());
2060
2061 // @property svg: Boolean
2062 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
2063 var svg$1 = !!(document.createElementNS && svgCreate('svg').createSVGRect);
2064
2065 var inlineSvg = !!svg$1 && (function () {
2066 var div = document.createElement('div');
2067 div.innerHTML = '<svg/>';
2068 return (div.firstChild && div.firstChild.namespaceURI) === 'http://www.w3.org/2000/svg';
2069 })();
2070
2071 // @property vml: Boolean
2072 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
2073 var vml = !svg$1 && (function () {
2074 try {
2075 var div = document.createElement('div');
2076 div.innerHTML = '<v:shape adj="1"/>';
2077
2078 var shape = div.firstChild;
2079 shape.style.behavior = 'url(#default#VML)';
2080
2081 return shape && (typeof shape.adj === 'object');
2082
2083 } catch (e) {
2084 return false;
2085 }
2086 }());
2087
2088
2089 // @property mac: Boolean; `true` when the browser is running in a Mac platform
2090 var mac = navigator.platform.indexOf('Mac') === 0;
2091
2092 // @property mac: Boolean; `true` when the browser is running in a Linux platform
2093 var linux = navigator.platform.indexOf('Linux') === 0;
2094
2095 function userAgentContains(str) {
2096 return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
2097 }
2098
2099
2100 var Browser = {
2101 ie: ie,
2102 ielt9: ielt9,
2103 edge: edge,
2104 webkit: webkit,
2105 android: android,
2106 android23: android23,
2107 androidStock: androidStock,
2108 opera: opera,
2109 chrome: chrome,
2110 gecko: gecko,
2111 safari: safari,
2112 phantom: phantom,
2113 opera12: opera12,
2114 win: win,
2115 ie3d: ie3d,
2116 webkit3d: webkit3d,
2117 gecko3d: gecko3d,
2118 any3d: any3d,
2119 mobile: mobile,
2120 mobileWebkit: mobileWebkit,
2121 mobileWebkit3d: mobileWebkit3d,
2122 msPointer: msPointer,
2123 pointer: pointer,
2124 touch: touch,
2125 touchNative: touchNative,
2126 mobileOpera: mobileOpera,
2127 mobileGecko: mobileGecko,
2128 retina: retina,
2129 passiveEvents: passiveEvents,
2130 canvas: canvas$1,
2131 svg: svg$1,
2132 vml: vml,
2133 inlineSvg: inlineSvg,
2134 mac: mac,
2135 linux: linux
2136 };
2137
2138 /*
2139 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
2140 */
2141
2142 var POINTER_DOWN = Browser.msPointer ? 'MSPointerDown' : 'pointerdown';
2143 var POINTER_MOVE = Browser.msPointer ? 'MSPointerMove' : 'pointermove';
2144 var POINTER_UP = Browser.msPointer ? 'MSPointerUp' : 'pointerup';
2145 var POINTER_CANCEL = Browser.msPointer ? 'MSPointerCancel' : 'pointercancel';
2146 var pEvent = {
2147 touchstart : POINTER_DOWN,
2148 touchmove : POINTER_MOVE,
2149 touchend : POINTER_UP,
2150 touchcancel : POINTER_CANCEL
2151 };
2152 var handle = {
2153 touchstart : _onPointerStart,
2154 touchmove : _handlePointer,
2155 touchend : _handlePointer,
2156 touchcancel : _handlePointer
2157 };
2158 var _pointers = {};
2159 var _pointerDocListener = false;
2160
2161 // Provides a touch events wrapper for (ms)pointer events.
2162 // ref https://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
2163
2164 function addPointerListener(obj, type, handler) {
2165 if (type === 'touchstart') {
2166 _addPointerDocListener();
2167 }
2168 if (!handle[type]) {
2169 console.warn('wrong event specified:', type);
2170 return falseFn;
2171 }
2172 handler = handle[type].bind(this, handler);
2173 obj.addEventListener(pEvent[type], handler, false);
2174 return handler;
2175 }
2176
2177 function removePointerListener(obj, type, handler) {
2178 if (!pEvent[type]) {
2179 console.warn('wrong event specified:', type);
2180 return;
2181 }
2182 obj.removeEventListener(pEvent[type], handler, false);
2183 }
2184
2185 function _globalPointerDown(e) {
2186 _pointers[e.pointerId] = e;
2187 }
2188
2189 function _globalPointerMove(e) {
2190 if (_pointers[e.pointerId]) {
2191 _pointers[e.pointerId] = e;
2192 }
2193 }
2194
2195 function _globalPointerUp(e) {
2196 delete _pointers[e.pointerId];
2197 }
2198
2199 function _addPointerDocListener() {
2200 // need to keep track of what pointers and how many are active to provide e.touches emulation
2201 if (!_pointerDocListener) {
2202 // we listen document as any drags that end by moving the touch off the screen get fired there
2203 document.addEventListener(POINTER_DOWN, _globalPointerDown, true);
2204 document.addEventListener(POINTER_MOVE, _globalPointerMove, true);
2205 document.addEventListener(POINTER_UP, _globalPointerUp, true);
2206 document.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
2207
2208 _pointerDocListener = true;
2209 }
2210 }
2211
2212 function _handlePointer(handler, e) {
2213 if (e.pointerType === (e.MSPOINTER_TYPE_MOUSE || 'mouse')) { return; }
2214
2215 e.touches = [];
2216 for (var i in _pointers) {
2217 e.touches.push(_pointers[i]);
2218 }
2219 e.changedTouches = [e];
2220
2221 handler(e);
2222 }
2223
2224 function _onPointerStart(handler, e) {
2225 // IE10 specific: MsTouch needs preventDefault. See #2000
2226 if (e.MSPOINTER_TYPE_TOUCH && e.pointerType === e.MSPOINTER_TYPE_TOUCH) {
2227 preventDefault(e);
2228 }
2229 _handlePointer(handler, e);
2230 }
2231
2232 /*
2233 * Extends the event handling code with double tap support for mobile browsers.
2234 *
2235 * Note: currently most browsers fire native dblclick, with only a few exceptions
2236 * (see https://github.com/Leaflet/Leaflet/issues/7012#issuecomment-595087386)
2237 */
2238
2239 function makeDblclick(event) {
2240 // in modern browsers `type` cannot be just overridden:
2241 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only
2242 var newEvent = {},
2243 prop, i;
2244 for (i in event) {
2245 prop = event[i];
2246 newEvent[i] = prop && prop.bind ? prop.bind(event) : prop;
2247 }
2248 event = newEvent;
2249 newEvent.type = 'dblclick';
2250 newEvent.detail = 2;
2251 newEvent.isTrusted = false;
2252 newEvent._simulated = true; // for debug purposes
2253 return newEvent;
2254 }
2255
2256 var delay = 200;
2257 function addDoubleTapListener(obj, handler) {
2258 // Most browsers handle double tap natively
2259 obj.addEventListener('dblclick', handler);
2260
2261 // On some platforms the browser doesn't fire native dblclicks for touch events.
2262 // It seems that in all such cases `detail` property of `click` event is always `1`.
2263 // So here we rely on that fact to avoid excessive 'dblclick' simulation when not needed.
2264 var last = 0,
2265 detail;
2266 function simDblclick(e) {
2267 if (e.detail !== 1) {
2268 detail = e.detail; // keep in sync to avoid false dblclick in some cases
2269 return;
2270 }
2271
2272 if (e.pointerType === 'mouse' ||
2273 (e.sourceCapabilities && !e.sourceCapabilities.firesTouchEvents)) {
2274
2275 return;
2276 }
2277
2278 // When clicking on an <input>, the browser generates a click on its
2279 // <label> (and vice versa) triggering two clicks in quick succession.
2280 // This ignores clicks on elements which are a label with a 'for'
2281 // attribute (or children of such a label), but not children of
2282 // a <input>.
2283 var path = getPropagationPath(e);
2284 if (path.some(function (el) {
2285 return el instanceof HTMLLabelElement && el.attributes.for;
2286 }) &&
2287 !path.some(function (el) {
2288 return (
2289 el instanceof HTMLInputElement ||
2290 el instanceof HTMLSelectElement
2291 );
2292 })
2293 ) {
2294 return;
2295 }
2296
2297 var now = Date.now();
2298 if (now - last <= delay) {
2299 detail++;
2300 if (detail === 2) {
2301 handler(makeDblclick(e));
2302 }
2303 } else {
2304 detail = 1;
2305 }
2306 last = now;
2307 }
2308
2309 obj.addEventListener('click', simDblclick);
2310
2311 return {
2312 dblclick: handler,
2313 simDblclick: simDblclick
2314 };
2315 }
2316
2317 function removeDoubleTapListener(obj, handlers) {
2318 obj.removeEventListener('dblclick', handlers.dblclick);
2319 obj.removeEventListener('click', handlers.simDblclick);
2320 }
2321
2322 /*
2323 * @namespace DomUtil
2324 *
2325 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
2326 * tree, used by Leaflet internally.
2327 *
2328 * Most functions expecting or returning a `HTMLElement` also work for
2329 * SVG elements. The only difference is that classes refer to CSS classes
2330 * in HTML and SVG classes in SVG.
2331 */
2332
2333
2334 // @property TRANSFORM: String
2335 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
2336 var TRANSFORM = testProp(
2337 ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
2338
2339 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
2340 // the same for the transitionend event, in particular the Android 4.1 stock browser
2341
2342 // @property TRANSITION: String
2343 // Vendor-prefixed transition style name.
2344 var TRANSITION = testProp(
2345 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
2346
2347 // @property TRANSITION_END: String
2348 // Vendor-prefixed transitionend event name.
2349 var TRANSITION_END =
2350 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
2351
2352
2353 // @function get(id: String|HTMLElement): HTMLElement
2354 // Returns an element given its DOM id, or returns the element itself
2355 // if it was passed directly.
2356 function get(id) {
2357 return typeof id === 'string' ? document.getElementById(id) : id;
2358 }
2359
2360 // @function getStyle(el: HTMLElement, styleAttrib: String): String
2361 // Returns the value for a certain style attribute on an element,
2362 // including computed values or values set through CSS.
2363 function getStyle(el, style) {
2364 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
2365
2366 if ((!value || value === 'auto') && document.defaultView) {
2367 var css = document.defaultView.getComputedStyle(el, null);
2368 value = css ? css[style] : null;
2369 }
2370 return value === 'auto' ? null : value;
2371 }
2372
2373 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
2374 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
2375 function create$1(tagName, className, container) {
2376 var el = document.createElement(tagName);
2377 el.className = className || '';
2378
2379 if (container) {
2380 container.appendChild(el);
2381 }
2382 return el;
2383 }
2384
2385 // @function remove(el: HTMLElement)
2386 // Removes `el` from its parent element
2387 function remove(el) {
2388 var parent = el.parentNode;
2389 if (parent) {
2390 parent.removeChild(el);
2391 }
2392 }
2393
2394 // @function empty(el: HTMLElement)
2395 // Removes all of `el`'s children elements from `el`
2396 function empty(el) {
2397 while (el.firstChild) {
2398 el.removeChild(el.firstChild);
2399 }
2400 }
2401
2402 // @function toFront(el: HTMLElement)
2403 // Makes `el` the last child of its parent, so it renders in front of the other children.
2404 function toFront(el) {
2405 var parent = el.parentNode;
2406 if (parent && parent.lastChild !== el) {
2407 parent.appendChild(el);
2408 }
2409 }
2410
2411 // @function toBack(el: HTMLElement)
2412 // Makes `el` the first child of its parent, so it renders behind the other children.
2413 function toBack(el) {
2414 var parent = el.parentNode;
2415 if (parent && parent.firstChild !== el) {
2416 parent.insertBefore(el, parent.firstChild);
2417 }
2418 }
2419
2420 // @function hasClass(el: HTMLElement, name: String): Boolean
2421 // Returns `true` if the element's class attribute contains `name`.
2422 function hasClass(el, name) {
2423 if (el.classList !== undefined) {
2424 return el.classList.contains(name);
2425 }
2426 var className = getClass(el);
2427 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
2428 }
2429
2430 // @function addClass(el: HTMLElement, name: String)
2431 // Adds `name` to the element's class attribute.
2432 function addClass(el, name) {
2433 if (el.classList !== undefined) {
2434 var classes = splitWords(name);
2435 for (var i = 0, len = classes.length; i < len; i++) {
2436 el.classList.add(classes[i]);
2437 }
2438 } else if (!hasClass(el, name)) {
2439 var className = getClass(el);
2440 setClass(el, (className ? className + ' ' : '') + name);
2441 }
2442 }
2443
2444 // @function removeClass(el: HTMLElement, name: String)
2445 // Removes `name` from the element's class attribute.
2446 function removeClass(el, name) {
2447 if (el.classList !== undefined) {
2448 el.classList.remove(name);
2449 } else {
2450 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
2451 }
2452 }
2453
2454 // @function setClass(el: HTMLElement, name: String)
2455 // Sets the element's class.
2456 function setClass(el, name) {
2457 if (el.className.baseVal === undefined) {
2458 el.className = name;
2459 } else {
2460 // in case of SVG element
2461 el.className.baseVal = name;
2462 }
2463 }
2464
2465 // @function getClass(el: HTMLElement): String
2466 // Returns the element's class.
2467 function getClass(el) {
2468 // Check if the element is an SVGElementInstance and use the correspondingElement instead
2469 // (Required for linked SVG elements in IE11.)
2470 if (el.correspondingElement) {
2471 el = el.correspondingElement;
2472 }
2473 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
2474 }
2475
2476 // @function setOpacity(el: HTMLElement, opacity: Number)
2477 // Set the opacity of an element (including old IE support).
2478 // `opacity` must be a number from `0` to `1`.
2479 function setOpacity(el, value) {
2480 if ('opacity' in el.style) {
2481 el.style.opacity = value;
2482 } else if ('filter' in el.style) {
2483 _setOpacityIE(el, value);
2484 }
2485 }
2486
2487 function _setOpacityIE(el, value) {
2488 var filter = false,
2489 filterName = 'DXImageTransform.Microsoft.Alpha';
2490
2491 // filters collection throws an error if we try to retrieve a filter that doesn't exist
2492 try {
2493 filter = el.filters.item(filterName);
2494 } catch (e) {
2495 // don't set opacity to 1 if we haven't already set an opacity,
2496 // it isn't needed and breaks transparent pngs.
2497 if (value === 1) { return; }
2498 }
2499
2500 value = Math.round(value * 100);
2501
2502 if (filter) {
2503 filter.Enabled = (value !== 100);
2504 filter.Opacity = value;
2505 } else {
2506 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
2507 }
2508 }
2509
2510 // @function testProp(props: String[]): String|false
2511 // Goes through the array of style names and returns the first name
2512 // that is a valid style name for an element. If no such name is found,
2513 // it returns false. Useful for vendor-prefixed styles like `transform`.
2514 function testProp(props) {
2515 var style = document.documentElement.style;
2516
2517 for (var i = 0; i < props.length; i++) {
2518 if (props[i] in style) {
2519 return props[i];
2520 }
2521 }
2522 return false;
2523 }
2524
2525 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
2526 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
2527 // and optionally scaled by `scale`. Does not have an effect if the
2528 // browser doesn't support 3D CSS transforms.
2529 function setTransform(el, offset, scale) {
2530 var pos = offset || new Point(0, 0);
2531
2532 el.style[TRANSFORM] =
2533 (Browser.ie3d ?
2534 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
2535 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
2536 (scale ? ' scale(' + scale + ')' : '');
2537 }
2538
2539 // @function setPosition(el: HTMLElement, position: Point)
2540 // Sets the position of `el` to coordinates specified by `position`,
2541 // using CSS translate or top/left positioning depending on the browser
2542 // (used by Leaflet internally to position its layers).
2543 function setPosition(el, point) {
2544
2545 /*eslint-disable */
2546 el._leaflet_pos = point;
2547 /* eslint-enable */
2548
2549 if (Browser.any3d) {
2550 setTransform(el, point);
2551 } else {
2552 el.style.left = point.x + 'px';
2553 el.style.top = point.y + 'px';
2554 }
2555 }
2556
2557 // @function getPosition(el: HTMLElement): Point
2558 // Returns the coordinates of an element previously positioned with setPosition.
2559 function getPosition(el) {
2560 // this method is only used for elements previously positioned using setPosition,
2561 // so it's safe to cache the position for performance
2562
2563 return el._leaflet_pos || new Point(0, 0);
2564 }
2565
2566 // @function disableTextSelection()
2567 // Prevents the user from generating `selectstart` DOM events, usually generated
2568 // when the user drags the mouse through a page with text. Used internally
2569 // by Leaflet to override the behaviour of any click-and-drag interaction on
2570 // the map. Affects drag interactions on the whole document.
2571
2572 // @function enableTextSelection()
2573 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
2574 var disableTextSelection;
2575 var enableTextSelection;
2576 var _userSelect;
2577 if ('onselectstart' in document) {
2578 disableTextSelection = function () {
2579 on(window, 'selectstart', preventDefault);
2580 };
2581 enableTextSelection = function () {
2582 off(window, 'selectstart', preventDefault);
2583 };
2584 } else {
2585 var userSelectProperty = testProp(
2586 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
2587
2588 disableTextSelection = function () {
2589 if (userSelectProperty) {
2590 var style = document.documentElement.style;
2591 _userSelect = style[userSelectProperty];
2592 style[userSelectProperty] = 'none';
2593 }
2594 };
2595 enableTextSelection = function () {
2596 if (userSelectProperty) {
2597 document.documentElement.style[userSelectProperty] = _userSelect;
2598 _userSelect = undefined;
2599 }
2600 };
2601 }
2602
2603 // @function disableImageDrag()
2604 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
2605 // for `dragstart` DOM events, usually generated when the user drags an image.
2606 function disableImageDrag() {
2607 on(window, 'dragstart', preventDefault);
2608 }
2609
2610 // @function enableImageDrag()
2611 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
2612 function enableImageDrag() {
2613 off(window, 'dragstart', preventDefault);
2614 }
2615
2616 var _outlineElement, _outlineStyle;
2617 // @function preventOutline(el: HTMLElement)
2618 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
2619 // of the element `el` invisible. Used internally by Leaflet to prevent
2620 // focusable elements from displaying an outline when the user performs a
2621 // drag interaction on them.
2622 function preventOutline(element) {
2623 while (element.tabIndex === -1) {
2624 element = element.parentNode;
2625 }
2626 if (!element.style) { return; }
2627 restoreOutline();
2628 _outlineElement = element;
2629 _outlineStyle = element.style.outlineStyle;
2630 element.style.outlineStyle = 'none';
2631 on(window, 'keydown', restoreOutline);
2632 }
2633
2634 // @function restoreOutline()
2635 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
2636 function restoreOutline() {
2637 if (!_outlineElement) { return; }
2638 _outlineElement.style.outlineStyle = _outlineStyle;
2639 _outlineElement = undefined;
2640 _outlineStyle = undefined;
2641 off(window, 'keydown', restoreOutline);
2642 }
2643
2644 // @function getSizedParentNode(el: HTMLElement): HTMLElement
2645 // Finds the closest parent node which size (width and height) is not null.
2646 function getSizedParentNode(element) {
2647 do {
2648 element = element.parentNode;
2649 } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
2650 return element;
2651 }
2652
2653 // @function getScale(el: HTMLElement): Object
2654 // Computes the CSS scale currently applied on the element.
2655 // Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
2656 // and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
2657 function getScale(element) {
2658 var rect = element.getBoundingClientRect(); // Read-only in old browsers.
2659
2660 return {
2661 x: rect.width / element.offsetWidth || 1,
2662 y: rect.height / element.offsetHeight || 1,
2663 boundingClientRect: rect
2664 };
2665 }
2666
2667 var DomUtil = {
2668 __proto__: null,
2669 TRANSFORM: TRANSFORM,
2670 TRANSITION: TRANSITION,
2671 TRANSITION_END: TRANSITION_END,
2672 get: get,
2673 getStyle: getStyle,
2674 create: create$1,
2675 remove: remove,
2676 empty: empty,
2677 toFront: toFront,
2678 toBack: toBack,
2679 hasClass: hasClass,
2680 addClass: addClass,
2681 removeClass: removeClass,
2682 setClass: setClass,
2683 getClass: getClass,
2684 setOpacity: setOpacity,
2685 testProp: testProp,
2686 setTransform: setTransform,
2687 setPosition: setPosition,
2688 getPosition: getPosition,
2689 get disableTextSelection () { return disableTextSelection; },
2690 get enableTextSelection () { return enableTextSelection; },
2691 disableImageDrag: disableImageDrag,
2692 enableImageDrag: enableImageDrag,
2693 preventOutline: preventOutline,
2694 restoreOutline: restoreOutline,
2695 getSizedParentNode: getSizedParentNode,
2696 getScale: getScale
2697 };
2698
2699 /*
2700 * @namespace DomEvent
2701 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
2702 */
2703
2704 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
2705
2706 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
2707 // Adds a listener function (`fn`) to a particular DOM event type of the
2708 // element `el`. You can optionally specify the context of the listener
2709 // (object the `this` keyword will point to). You can also pass several
2710 // space-separated types (e.g. `'click dblclick'`).
2711
2712 // @alternative
2713 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
2714 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2715 function on(obj, types, fn, context) {
2716
2717 if (types && typeof types === 'object') {
2718 for (var type in types) {
2719 addOne(obj, type, types[type], fn);
2720 }
2721 } else {
2722 types = splitWords(types);
2723
2724 for (var i = 0, len = types.length; i < len; i++) {
2725 addOne(obj, types[i], fn, context);
2726 }
2727 }
2728
2729 return this;
2730 }
2731
2732 var eventsKey = '_leaflet_events';
2733
2734 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
2735 // Removes a previously added listener function.
2736 // Note that if you passed a custom context to on, you must pass the same
2737 // context to `off` in order to remove the listener.
2738
2739 // @alternative
2740 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
2741 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2742
2743 // @alternative
2744 // @function off(el: HTMLElement, types: String): this
2745 // Removes all previously added listeners of given types.
2746
2747 // @alternative
2748 // @function off(el: HTMLElement): this
2749 // Removes all previously added listeners from given HTMLElement
2750 function off(obj, types, fn, context) {
2751
2752 if (arguments.length === 1) {
2753 batchRemove(obj);
2754 delete obj[eventsKey];
2755
2756 } else if (types && typeof types === 'object') {
2757 for (var type in types) {
2758 removeOne(obj, type, types[type], fn);
2759 }
2760
2761 } else {
2762 types = splitWords(types);
2763
2764 if (arguments.length === 2) {
2765 batchRemove(obj, function (type) {
2766 return indexOf(types, type) !== -1;
2767 });
2768 } else {
2769 for (var i = 0, len = types.length; i < len; i++) {
2770 removeOne(obj, types[i], fn, context);
2771 }
2772 }
2773 }
2774
2775 return this;
2776 }
2777
2778 function batchRemove(obj, filterFn) {
2779 for (var id in obj[eventsKey]) {
2780 var type = id.split(/\d/)[0];
2781 if (!filterFn || filterFn(type)) {
2782 removeOne(obj, type, null, null, id);
2783 }
2784 }
2785 }
2786
2787 var mouseSubst = {
2788 mouseenter: 'mouseover',
2789 mouseleave: 'mouseout',
2790 wheel: !('onwheel' in window) && 'mousewheel'
2791 };
2792
2793 function addOne(obj, type, fn, context) {
2794 var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
2795
2796 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
2797
2798 var handler = function (e) {
2799 return fn.call(context || obj, e || window.event);
2800 };
2801
2802 var originalHandler = handler;
2803
2804 if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) {
2805 // Needs DomEvent.Pointer.js
2806 handler = addPointerListener(obj, type, handler);
2807
2808 } else if (Browser.touch && (type === 'dblclick')) {
2809 handler = addDoubleTapListener(obj, handler);
2810
2811 } else if ('addEventListener' in obj) {
2812
2813 if (type === 'touchstart' || type === 'touchmove' || type === 'wheel' || type === 'mousewheel') {
2814 obj.addEventListener(mouseSubst[type] || type, handler, Browser.passiveEvents ? {passive: false} : false);
2815
2816 } else if (type === 'mouseenter' || type === 'mouseleave') {
2817 handler = function (e) {
2818 e = e || window.event;
2819 if (isExternalTarget(obj, e)) {
2820 originalHandler(e);
2821 }
2822 };
2823 obj.addEventListener(mouseSubst[type], handler, false);
2824
2825 } else {
2826 obj.addEventListener(type, originalHandler, false);
2827 }
2828
2829 } else {
2830 obj.attachEvent('on' + type, handler);
2831 }
2832
2833 obj[eventsKey] = obj[eventsKey] || {};
2834 obj[eventsKey][id] = handler;
2835 }
2836
2837 function removeOne(obj, type, fn, context, id) {
2838 id = id || type + stamp(fn) + (context ? '_' + stamp(context) : '');
2839 var handler = obj[eventsKey] && obj[eventsKey][id];
2840
2841 if (!handler) { return this; }
2842
2843 if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) {
2844 removePointerListener(obj, type, handler);
2845
2846 } else if (Browser.touch && (type === 'dblclick')) {
2847 removeDoubleTapListener(obj, handler);
2848
2849 } else if ('removeEventListener' in obj) {
2850
2851 obj.removeEventListener(mouseSubst[type] || type, handler, false);
2852
2853 } else {
2854 obj.detachEvent('on' + type, handler);
2855 }
2856
2857 obj[eventsKey][id] = null;
2858 }
2859
2860 // @function stopPropagation(ev: DOMEvent): this
2861 // Stop the given event from propagation to parent elements. Used inside the listener functions:
2862 // ```js
2863 // L.DomEvent.on(div, 'click', function (ev) {
2864 // L.DomEvent.stopPropagation(ev);
2865 // });
2866 // ```
2867 function stopPropagation(e) {
2868
2869 if (e.stopPropagation) {
2870 e.stopPropagation();
2871 } else if (e.originalEvent) { // In case of Leaflet event.
2872 e.originalEvent._stopped = true;
2873 } else {
2874 e.cancelBubble = true;
2875 }
2876
2877 return this;
2878 }
2879
2880 // @function disableScrollPropagation(el: HTMLElement): this
2881 // Adds `stopPropagation` to the element's `'wheel'` events (plus browser variants).
2882 function disableScrollPropagation(el) {
2883 addOne(el, 'wheel', stopPropagation);
2884 return this;
2885 }
2886
2887 // @function disableClickPropagation(el: HTMLElement): this
2888 // Adds `stopPropagation` to the element's `'click'`, `'dblclick'`, `'contextmenu'`,
2889 // `'mousedown'` and `'touchstart'` events (plus browser variants).
2890 function disableClickPropagation(el) {
2891 on(el, 'mousedown touchstart dblclick contextmenu', stopPropagation);
2892 el['_leaflet_disable_click'] = true;
2893 return this;
2894 }
2895
2896 // @function preventDefault(ev: DOMEvent): this
2897 // Prevents the default action of the DOM Event `ev` from happening (such as
2898 // following a link in the href of the a element, or doing a POST request
2899 // with page reload when a `<form>` is submitted).
2900 // Use it inside listener functions.
2901 function preventDefault(e) {
2902 if (e.preventDefault) {
2903 e.preventDefault();
2904 } else {
2905 e.returnValue = false;
2906 }
2907 return this;
2908 }
2909
2910 // @function stop(ev: DOMEvent): this
2911 // Does `stopPropagation` and `preventDefault` at the same time.
2912 function stop(e) {
2913 preventDefault(e);
2914 stopPropagation(e);
2915 return this;
2916 }
2917
2918 // @function getPropagationPath(ev: DOMEvent): Array
2919 // Compatibility polyfill for [`Event.composedPath()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath).
2920 // Returns an array containing the `HTMLElement`s that the given DOM event
2921 // should propagate to (if not stopped).
2922 function getPropagationPath(ev) {
2923 if (ev.composedPath) {
2924 return ev.composedPath();
2925 }
2926
2927 var path = [];
2928 var el = ev.target;
2929
2930 while (el) {
2931 path.push(el);
2932 el = el.parentNode;
2933 }
2934 return path;
2935 }
2936
2937
2938 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
2939 // Gets normalized mouse position from a DOM event relative to the
2940 // `container` (border excluded) or to the whole page if not specified.
2941 function getMousePosition(e, container) {
2942 if (!container) {
2943 return new Point(e.clientX, e.clientY);
2944 }
2945
2946 var scale = getScale(container),
2947 offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y)
2948
2949 return new Point(
2950 // offset.left/top values are in page scale (like clientX/Y),
2951 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
2952 (e.clientX - offset.left) / scale.x - container.clientLeft,
2953 (e.clientY - offset.top) / scale.y - container.clientTop
2954 );
2955 }
2956
2957
2958 // except , Safari and
2959 // We need double the scroll pixels (see #7403 and #4538) for all Browsers
2960 // except OSX (Mac) -> 3x, Chrome running on Linux 1x
2961
2962 var wheelPxFactor =
2963 (Browser.linux && Browser.chrome) ? window.devicePixelRatio :
2964 Browser.mac ? window.devicePixelRatio * 3 :
2965 window.devicePixelRatio > 0 ? 2 * window.devicePixelRatio : 1;
2966 // @function getWheelDelta(ev: DOMEvent): Number
2967 // Gets normalized wheel delta from a wheel DOM event, in vertical
2968 // pixels scrolled (negative if scrolling down).
2969 // Events from pointing devices without precise scrolling are mapped to
2970 // a best guess of 60 pixels.
2971 function getWheelDelta(e) {
2972 return (Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
2973 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
2974 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
2975 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
2976 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
2977 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
2978 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
2979 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
2980 0;
2981 }
2982
2983 // check if element really left/entered the event target (for mouseenter/mouseleave)
2984 function isExternalTarget(el, e) {
2985
2986 var related = e.relatedTarget;
2987
2988 if (!related) { return true; }
2989
2990 try {
2991 while (related && (related !== el)) {
2992 related = related.parentNode;
2993 }
2994 } catch (err) {
2995 return false;
2996 }
2997 return (related !== el);
2998 }
2999
3000 var DomEvent = {
3001 __proto__: null,
3002 on: on,
3003 off: off,
3004 stopPropagation: stopPropagation,
3005 disableScrollPropagation: disableScrollPropagation,
3006 disableClickPropagation: disableClickPropagation,
3007 preventDefault: preventDefault,
3008 stop: stop,
3009 getPropagationPath: getPropagationPath,
3010 getMousePosition: getMousePosition,
3011 getWheelDelta: getWheelDelta,
3012 isExternalTarget: isExternalTarget,
3013 addListener: on,
3014 removeListener: off
3015 };
3016
3017 /*
3018 * @class PosAnimation
3019 * @aka L.PosAnimation
3020 * @inherits Evented
3021 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
3022 *
3023 * @example
3024 * ```js
3025 * var myPositionMarker = L.marker([48.864716, 2.294694]).addTo(map);
3026 *
3027 * myPositionMarker.on("click", function() {
3028 * var pos = map.latLngToLayerPoint(myPositionMarker.getLatLng());
3029 * pos.y -= 25;
3030 * var fx = new L.PosAnimation();
3031 *
3032 * fx.once('end',function() {
3033 * pos.y += 25;
3034 * fx.run(myPositionMarker._icon, pos, 0.8);
3035 * });
3036 *
3037 * fx.run(myPositionMarker._icon, pos, 0.3);
3038 * });
3039 *
3040 * ```
3041 *
3042 * @constructor L.PosAnimation()
3043 * Creates a `PosAnimation` object.
3044 *
3045 */
3046
3047 var PosAnimation = Evented.extend({
3048
3049 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
3050 // Run an animation of a given element to a new position, optionally setting
3051 // duration in seconds (`0.25` by default) and easing linearity factor (3rd
3052 // argument of the [cubic bezier curve](https://cubic-bezier.com/#0,0,.5,1),
3053 // `0.5` by default).
3054 run: function (el, newPos, duration, easeLinearity) {
3055 this.stop();
3056
3057 this._el = el;
3058 this._inProgress = true;
3059 this._duration = duration || 0.25;
3060 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
3061
3062 this._startPos = getPosition(el);
3063 this._offset = newPos.subtract(this._startPos);
3064 this._startTime = +new Date();
3065
3066 // @event start: Event
3067 // Fired when the animation starts
3068 this.fire('start');
3069
3070 this._animate();
3071 },
3072
3073 // @method stop()
3074 // Stops the animation (if currently running).
3075 stop: function () {
3076 if (!this._inProgress) { return; }
3077
3078 this._step(true);
3079 this._complete();
3080 },
3081
3082 _animate: function () {
3083 // animation loop
3084 this._animId = requestAnimFrame(this._animate, this);
3085 this._step();
3086 },
3087
3088 _step: function (round) {
3089 var elapsed = (+new Date()) - this._startTime,
3090 duration = this._duration * 1000;
3091
3092 if (elapsed < duration) {
3093 this._runFrame(this._easeOut(elapsed / duration), round);
3094 } else {
3095 this._runFrame(1);
3096 this._complete();
3097 }
3098 },
3099
3100 _runFrame: function (progress, round) {
3101 var pos = this._startPos.add(this._offset.multiplyBy(progress));
3102 if (round) {
3103 pos._round();
3104 }
3105 setPosition(this._el, pos);
3106
3107 // @event step: Event
3108 // Fired continuously during the animation.
3109 this.fire('step');
3110 },
3111
3112 _complete: function () {
3113 cancelAnimFrame(this._animId);
3114
3115 this._inProgress = false;
3116 // @event end: Event
3117 // Fired when the animation ends.
3118 this.fire('end');
3119 },
3120
3121 _easeOut: function (t) {
3122 return 1 - Math.pow(1 - t, this._easeOutPower);
3123 }
3124 });
3125
3126 /*
3127 * @class Map
3128 * @aka L.Map
3129 * @inherits Evented
3130 *
3131 * The central class of the API — it is used to create a map on a page and manipulate it.
3132 *
3133 * @example
3134 *
3135 * ```js
3136 * // initialize the map on the "map" div with a given center and zoom
3137 * var map = L.map('map', {
3138 * center: [51.505, -0.09],
3139 * zoom: 13
3140 * });
3141 * ```
3142 *
3143 */
3144
3145 var Map = Evented.extend({
3146
3147 options: {
3148 // @section Map State Options
3149 // @option crs: CRS = L.CRS.EPSG3857
3150 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
3151 // sure what it means.
3152 crs: EPSG3857,
3153
3154 // @option center: LatLng = undefined
3155 // Initial geographic center of the map
3156 center: undefined,
3157
3158 // @option zoom: Number = undefined
3159 // Initial map zoom level
3160 zoom: undefined,
3161
3162 // @option minZoom: Number = *
3163 // Minimum zoom level of the map.
3164 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3165 // the lowest of their `minZoom` options will be used instead.
3166 minZoom: undefined,
3167
3168 // @option maxZoom: Number = *
3169 // Maximum zoom level of the map.
3170 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3171 // the highest of their `maxZoom` options will be used instead.
3172 maxZoom: undefined,
3173
3174 // @option layers: Layer[] = []
3175 // Array of layers that will be added to the map initially
3176 layers: [],
3177
3178 // @option maxBounds: LatLngBounds = null
3179 // When this option is set, the map restricts the view to the given
3180 // geographical bounds, bouncing the user back if the user tries to pan
3181 // outside the view. To set the restriction dynamically, use
3182 // [`setMaxBounds`](#map-setmaxbounds) method.
3183 maxBounds: undefined,
3184
3185 // @option renderer: Renderer = *
3186 // The default method for drawing vector layers on the map. `L.SVG`
3187 // or `L.Canvas` by default depending on browser support.
3188 renderer: undefined,
3189
3190
3191 // @section Animation Options
3192 // @option zoomAnimation: Boolean = true
3193 // Whether the map zoom animation is enabled. By default it's enabled
3194 // in all browsers that support CSS3 Transitions except Android.
3195 zoomAnimation: true,
3196
3197 // @option zoomAnimationThreshold: Number = 4
3198 // Won't animate zoom if the zoom difference exceeds this value.
3199 zoomAnimationThreshold: 4,
3200
3201 // @option fadeAnimation: Boolean = true
3202 // Whether the tile fade animation is enabled. By default it's enabled
3203 // in all browsers that support CSS3 Transitions except Android.
3204 fadeAnimation: true,
3205
3206 // @option markerZoomAnimation: Boolean = true
3207 // Whether markers animate their zoom with the zoom animation, if disabled
3208 // they will disappear for the length of the animation. By default it's
3209 // enabled in all browsers that support CSS3 Transitions except Android.
3210 markerZoomAnimation: true,
3211
3212 // @option transform3DLimit: Number = 2^23
3213 // Defines the maximum size of a CSS translation transform. The default
3214 // value should not be changed unless a web browser positions layers in
3215 // the wrong place after doing a large `panBy`.
3216 transform3DLimit: 8388608, // Precision limit of a 32-bit float
3217
3218 // @section Interaction Options
3219 // @option zoomSnap: Number = 1
3220 // Forces the map's zoom level to always be a multiple of this, particularly
3221 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
3222 // By default, the zoom level snaps to the nearest integer; lower values
3223 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
3224 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
3225 zoomSnap: 1,
3226
3227 // @option zoomDelta: Number = 1
3228 // Controls how much the map's zoom level will change after a
3229 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
3230 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
3231 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
3232 zoomDelta: 1,
3233
3234 // @option trackResize: Boolean = true
3235 // Whether the map automatically handles browser window resize to update itself.
3236 trackResize: true
3237 },
3238
3239 initialize: function (id, options) { // (HTMLElement or String, Object)
3240 options = setOptions(this, options);
3241
3242 // Make sure to assign internal flags at the beginning,
3243 // to avoid inconsistent state in some edge cases.
3244 this._handlers = [];
3245 this._layers = {};
3246 this._zoomBoundLayers = {};
3247 this._sizeChanged = true;
3248
3249 this._initContainer(id);
3250 this._initLayout();
3251
3252 // hack for https://github.com/Leaflet/Leaflet/issues/1980
3253 this._onResize = bind(this._onResize, this);
3254
3255 this._initEvents();
3256
3257 if (options.maxBounds) {
3258 this.setMaxBounds(options.maxBounds);
3259 }
3260
3261 if (options.zoom !== undefined) {
3262 this._zoom = this._limitZoom(options.zoom);
3263 }
3264
3265 if (options.center && options.zoom !== undefined) {
3266 this.setView(toLatLng(options.center), options.zoom, {reset: true});
3267 }
3268
3269 this.callInitHooks();
3270
3271 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
3272 this._zoomAnimated = TRANSITION && Browser.any3d && !Browser.mobileOpera &&
3273 this.options.zoomAnimation;
3274
3275 // zoom transitions run with the same duration for all layers, so if one of transitionend events
3276 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
3277 if (this._zoomAnimated) {
3278 this._createAnimProxy();
3279 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
3280 }
3281
3282 this._addLayers(this.options.layers);
3283 },
3284
3285
3286 // @section Methods for modifying map state
3287
3288 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
3289 // Sets the view of the map (geographical center and zoom) with the given
3290 // animation options.
3291 setView: function (center, zoom, options) {
3292
3293 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
3294 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
3295 options = options || {};
3296
3297 this._stop();
3298
3299 if (this._loaded && !options.reset && options !== true) {
3300
3301 if (options.animate !== undefined) {
3302 options.zoom = extend({animate: options.animate}, options.zoom);
3303 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
3304 }
3305
3306 // try animating pan or zoom
3307 var moved = (this._zoom !== zoom) ?
3308 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
3309 this._tryAnimatedPan(center, options.pan);
3310
3311 if (moved) {
3312 // prevent resize handler call, the view will refresh after animation anyway
3313 clearTimeout(this._sizeTimer);
3314 return this;
3315 }
3316 }
3317
3318 // animation didn't start, just reset the map view
3319 this._resetView(center, zoom, options.pan && options.pan.noMoveStart);
3320
3321 return this;
3322 },
3323
3324 // @method setZoom(zoom: Number, options?: Zoom/pan options): this
3325 // Sets the zoom of the map.
3326 setZoom: function (zoom, options) {
3327 if (!this._loaded) {
3328 this._zoom = zoom;
3329 return this;
3330 }
3331 return this.setView(this.getCenter(), zoom, {zoom: options});
3332 },
3333
3334 // @method zoomIn(delta?: Number, options?: Zoom options): this
3335 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3336 zoomIn: function (delta, options) {
3337 delta = delta || (Browser.any3d ? this.options.zoomDelta : 1);
3338 return this.setZoom(this._zoom + delta, options);
3339 },
3340
3341 // @method zoomOut(delta?: Number, options?: Zoom options): this
3342 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3343 zoomOut: function (delta, options) {
3344 delta = delta || (Browser.any3d ? this.options.zoomDelta : 1);
3345 return this.setZoom(this._zoom - delta, options);
3346 },
3347
3348 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
3349 // Zooms the map while keeping a specified geographical point on the map
3350 // stationary (e.g. used internally for scroll zoom and double-click zoom).
3351 // @alternative
3352 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
3353 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
3354 setZoomAround: function (latlng, zoom, options) {
3355 var scale = this.getZoomScale(zoom),
3356 viewHalf = this.getSize().divideBy(2),
3357 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
3358
3359 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
3360 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
3361
3362 return this.setView(newCenter, zoom, {zoom: options});
3363 },
3364
3365 _getBoundsCenterZoom: function (bounds, options) {
3366
3367 options = options || {};
3368 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
3369
3370 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3371 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3372
3373 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
3374
3375 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
3376
3377 if (zoom === Infinity) {
3378 return {
3379 center: bounds.getCenter(),
3380 zoom: zoom
3381 };
3382 }
3383
3384 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
3385
3386 swPoint = this.project(bounds.getSouthWest(), zoom),
3387 nePoint = this.project(bounds.getNorthEast(), zoom),
3388 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
3389
3390 return {
3391 center: center,
3392 zoom: zoom
3393 };
3394 },
3395
3396 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
3397 // Sets a map view that contains the given geographical bounds with the
3398 // maximum zoom level possible.
3399 fitBounds: function (bounds, options) {
3400
3401 bounds = toLatLngBounds(bounds);
3402
3403 if (!bounds.isValid()) {
3404 throw new Error('Bounds are not valid.');
3405 }
3406
3407 var target = this._getBoundsCenterZoom(bounds, options);
3408 return this.setView(target.center, target.zoom, options);
3409 },
3410
3411 // @method fitWorld(options?: fitBounds options): this
3412 // Sets a map view that mostly contains the whole world with the maximum
3413 // zoom level possible.
3414 fitWorld: function (options) {
3415 return this.fitBounds([[-90, -180], [90, 180]], options);
3416 },
3417
3418 // @method panTo(latlng: LatLng, options?: Pan options): this
3419 // Pans the map to a given center.
3420 panTo: function (center, options) { // (LatLng)
3421 return this.setView(center, this._zoom, {pan: options});
3422 },
3423
3424 // @method panBy(offset: Point, options?: Pan options): this
3425 // Pans the map by a given number of pixels (animated).
3426 panBy: function (offset, options) {
3427 offset = toPoint(offset).round();
3428 options = options || {};
3429
3430 if (!offset.x && !offset.y) {
3431 return this.fire('moveend');
3432 }
3433 // If we pan too far, Chrome gets issues with tiles
3434 // and makes them disappear or appear in the wrong place (slightly offset) #2602
3435 if (options.animate !== true && !this.getSize().contains(offset)) {
3436 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
3437 return this;
3438 }
3439
3440 if (!this._panAnim) {
3441 this._panAnim = new PosAnimation();
3442
3443 this._panAnim.on({
3444 'step': this._onPanTransitionStep,
3445 'end': this._onPanTransitionEnd
3446 }, this);
3447 }
3448
3449 // don't fire movestart if animating inertia
3450 if (!options.noMoveStart) {
3451 this.fire('movestart');
3452 }
3453
3454 // animate pan unless animate: false specified
3455 if (options.animate !== false) {
3456 addClass(this._mapPane, 'leaflet-pan-anim');
3457
3458 var newPos = this._getMapPanePos().subtract(offset).round();
3459 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
3460 } else {
3461 this._rawPanBy(offset);
3462 this.fire('move').fire('moveend');
3463 }
3464
3465 return this;
3466 },
3467
3468 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
3469 // Sets the view of the map (geographical center and zoom) performing a smooth
3470 // pan-zoom animation.
3471 flyTo: function (targetCenter, targetZoom, options) {
3472
3473 options = options || {};
3474 if (options.animate === false || !Browser.any3d) {
3475 return this.setView(targetCenter, targetZoom, options);
3476 }
3477
3478 this._stop();
3479
3480 var from = this.project(this.getCenter()),
3481 to = this.project(targetCenter),
3482 size = this.getSize(),
3483 startZoom = this._zoom;
3484
3485 targetCenter = toLatLng(targetCenter);
3486 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
3487
3488 var w0 = Math.max(size.x, size.y),
3489 w1 = w0 * this.getZoomScale(startZoom, targetZoom),
3490 u1 = (to.distanceTo(from)) || 1,
3491 rho = 1.42,
3492 rho2 = rho * rho;
3493
3494 function r(i) {
3495 var s1 = i ? -1 : 1,
3496 s2 = i ? w1 : w0,
3497 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
3498 b1 = 2 * s2 * rho2 * u1,
3499 b = t1 / b1,
3500 sq = Math.sqrt(b * b + 1) - b;
3501
3502 // workaround for floating point precision bug when sq = 0, log = -Infinite,
3503 // thus triggering an infinite loop in flyTo
3504 var log = sq < 0.000000001 ? -18 : Math.log(sq);
3505
3506 return log;
3507 }
3508
3509 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
3510 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
3511 function tanh(n) { return sinh(n) / cosh(n); }
3512
3513 var r0 = r(0);
3514
3515 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
3516 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
3517
3518 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
3519
3520 var start = Date.now(),
3521 S = (r(1) - r0) / rho,
3522 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
3523
3524 function frame() {
3525 var t = (Date.now() - start) / duration,
3526 s = easeOut(t) * S;
3527
3528 if (t <= 1) {
3529 this._flyToFrame = requestAnimFrame(frame, this);
3530
3531 this._move(
3532 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
3533 this.getScaleZoom(w0 / w(s), startZoom),
3534 {flyTo: true});
3535
3536 } else {
3537 this
3538 ._move(targetCenter, targetZoom)
3539 ._moveEnd(true);
3540 }
3541 }
3542
3543 this._moveStart(true, options.noMoveStart);
3544
3545 frame.call(this);
3546 return this;
3547 },
3548
3549 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
3550 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
3551 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
3552 flyToBounds: function (bounds, options) {
3553 var target = this._getBoundsCenterZoom(bounds, options);
3554 return this.flyTo(target.center, target.zoom, options);
3555 },
3556
3557 // @method setMaxBounds(bounds: LatLngBounds): this
3558 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
3559 setMaxBounds: function (bounds) {
3560 bounds = toLatLngBounds(bounds);
3561
3562 if (this.listens('moveend', this._panInsideMaxBounds)) {
3563 this.off('moveend', this._panInsideMaxBounds);
3564 }
3565
3566 if (!bounds.isValid()) {
3567 this.options.maxBounds = null;
3568 return this;
3569 }
3570
3571 this.options.maxBounds = bounds;
3572
3573 if (this._loaded) {
3574 this._panInsideMaxBounds();
3575 }
3576
3577 return this.on('moveend', this._panInsideMaxBounds);
3578 },
3579
3580 // @method setMinZoom(zoom: Number): this
3581 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
3582 setMinZoom: function (zoom) {
3583 var oldZoom = this.options.minZoom;
3584 this.options.minZoom = zoom;
3585
3586 if (this._loaded && oldZoom !== zoom) {
3587 this.fire('zoomlevelschange');
3588
3589 if (this.getZoom() < this.options.minZoom) {
3590 return this.setZoom(zoom);
3591 }
3592 }
3593
3594 return this;
3595 },
3596
3597 // @method setMaxZoom(zoom: Number): this
3598 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
3599 setMaxZoom: function (zoom) {
3600 var oldZoom = this.options.maxZoom;
3601 this.options.maxZoom = zoom;
3602
3603 if (this._loaded && oldZoom !== zoom) {
3604 this.fire('zoomlevelschange');
3605
3606 if (this.getZoom() > this.options.maxZoom) {
3607 return this.setZoom(zoom);
3608 }
3609 }
3610
3611 return this;
3612 },
3613
3614 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
3615 // 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.
3616 panInsideBounds: function (bounds, options) {
3617 this._enforcingBounds = true;
3618 var center = this.getCenter(),
3619 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
3620
3621 if (!center.equals(newCenter)) {
3622 this.panTo(newCenter, options);
3623 }
3624
3625 this._enforcingBounds = false;
3626 return this;
3627 },
3628
3629 // @method panInside(latlng: LatLng, options?: padding options): this
3630 // Pans the map the minimum amount to make the `latlng` visible. Use
3631 // padding options to fit the display to more restricted bounds.
3632 // If `latlng` is already within the (optionally padded) display bounds,
3633 // the map will not be panned.
3634 panInside: function (latlng, options) {
3635 options = options || {};
3636
3637 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3638 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3639 pixelCenter = this.project(this.getCenter()),
3640 pixelPoint = this.project(latlng),
3641 pixelBounds = this.getPixelBounds(),
3642 paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]),
3643 paddedSize = paddedBounds.getSize();
3644
3645 if (!paddedBounds.contains(pixelPoint)) {
3646 this._enforcingBounds = true;
3647 var centerOffset = pixelPoint.subtract(paddedBounds.getCenter());
3648 var offset = paddedBounds.extend(pixelPoint).getSize().subtract(paddedSize);
3649 pixelCenter.x += centerOffset.x < 0 ? -offset.x : offset.x;
3650 pixelCenter.y += centerOffset.y < 0 ? -offset.y : offset.y;
3651 this.panTo(this.unproject(pixelCenter), options);
3652 this._enforcingBounds = false;
3653 }
3654 return this;
3655 },
3656
3657 // @method invalidateSize(options: Zoom/pan options): this
3658 // Checks if the map container size changed and updates the map if so —
3659 // call it after you've changed the map size dynamically, also animating
3660 // pan by default. If `options.pan` is `false`, panning will not occur.
3661 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
3662 // that it doesn't happen often even if the method is called many
3663 // times in a row.
3664
3665 // @alternative
3666 // @method invalidateSize(animate: Boolean): this
3667 // Checks if the map container size changed and updates the map if so —
3668 // call it after you've changed the map size dynamically, also animating
3669 // pan by default.
3670 invalidateSize: function (options) {
3671 if (!this._loaded) { return this; }
3672
3673 options = extend({
3674 animate: false,
3675 pan: true
3676 }, options === true ? {animate: true} : options);
3677
3678 var oldSize = this.getSize();
3679 this._sizeChanged = true;
3680 this._lastCenter = null;
3681
3682 var newSize = this.getSize(),
3683 oldCenter = oldSize.divideBy(2).round(),
3684 newCenter = newSize.divideBy(2).round(),
3685 offset = oldCenter.subtract(newCenter);
3686
3687 if (!offset.x && !offset.y) { return this; }
3688
3689 if (options.animate && options.pan) {
3690 this.panBy(offset);
3691
3692 } else {
3693 if (options.pan) {
3694 this._rawPanBy(offset);
3695 }
3696
3697 this.fire('move');
3698
3699 if (options.debounceMoveend) {
3700 clearTimeout(this._sizeTimer);
3701 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
3702 } else {
3703 this.fire('moveend');
3704 }
3705 }
3706
3707 // @section Map state change events
3708 // @event resize: ResizeEvent
3709 // Fired when the map is resized.
3710 return this.fire('resize', {
3711 oldSize: oldSize,
3712 newSize: newSize
3713 });
3714 },
3715
3716 // @section Methods for modifying map state
3717 // @method stop(): this
3718 // Stops the currently running `panTo` or `flyTo` animation, if any.
3719 stop: function () {
3720 this.setZoom(this._limitZoom(this._zoom));
3721 if (!this.options.zoomSnap) {
3722 this.fire('viewreset');
3723 }
3724 return this._stop();
3725 },
3726
3727 // @section Geolocation methods
3728 // @method locate(options?: Locate options): this
3729 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
3730 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
3731 // and optionally sets the map view to the user's location with respect to
3732 // detection accuracy (or to the world view if geolocation failed).
3733 // Note that, if your page doesn't use HTTPS, this method will fail in
3734 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
3735 // See `Locate options` for more details.
3736 locate: function (options) {
3737
3738 options = this._locateOptions = extend({
3739 timeout: 10000,
3740 watch: false
3741 // setView: false
3742 // maxZoom: <Number>
3743 // maximumAge: 0
3744 // enableHighAccuracy: false
3745 }, options);
3746
3747 if (!('geolocation' in navigator)) {
3748 this._handleGeolocationError({
3749 code: 0,
3750 message: 'Geolocation not supported.'
3751 });
3752 return this;
3753 }
3754
3755 var onResponse = bind(this._handleGeolocationResponse, this),
3756 onError = bind(this._handleGeolocationError, this);
3757
3758 if (options.watch) {
3759 this._locationWatchId =
3760 navigator.geolocation.watchPosition(onResponse, onError, options);
3761 } else {
3762 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
3763 }
3764 return this;
3765 },
3766
3767 // @method stopLocate(): this
3768 // Stops watching location previously initiated by `map.locate({watch: true})`
3769 // and aborts resetting the map view if map.locate was called with
3770 // `{setView: true}`.
3771 stopLocate: function () {
3772 if (navigator.geolocation && navigator.geolocation.clearWatch) {
3773 navigator.geolocation.clearWatch(this._locationWatchId);
3774 }
3775 if (this._locateOptions) {
3776 this._locateOptions.setView = false;
3777 }
3778 return this;
3779 },
3780
3781 _handleGeolocationError: function (error) {
3782 if (!this._container._leaflet_id) { return; }
3783
3784 var c = error.code,
3785 message = error.message ||
3786 (c === 1 ? 'permission denied' :
3787 (c === 2 ? 'position unavailable' : 'timeout'));
3788
3789 if (this._locateOptions.setView && !this._loaded) {
3790 this.fitWorld();
3791 }
3792
3793 // @section Location events
3794 // @event locationerror: ErrorEvent
3795 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
3796 this.fire('locationerror', {
3797 code: c,
3798 message: 'Geolocation error: ' + message + '.'
3799 });
3800 },
3801
3802 _handleGeolocationResponse: function (pos) {
3803 if (!this._container._leaflet_id) { return; }
3804
3805 var lat = pos.coords.latitude,
3806 lng = pos.coords.longitude,
3807 latlng = new LatLng(lat, lng),
3808 bounds = latlng.toBounds(pos.coords.accuracy * 2),
3809 options = this._locateOptions;
3810
3811 if (options.setView) {
3812 var zoom = this.getBoundsZoom(bounds);
3813 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
3814 }
3815
3816 var data = {
3817 latlng: latlng,
3818 bounds: bounds,
3819 timestamp: pos.timestamp
3820 };
3821
3822 for (var i in pos.coords) {
3823 if (typeof pos.coords[i] === 'number') {
3824 data[i] = pos.coords[i];
3825 }
3826 }
3827
3828 // @event locationfound: LocationEvent
3829 // Fired when geolocation (using the [`locate`](#map-locate) method)
3830 // went successfully.
3831 this.fire('locationfound', data);
3832 },
3833
3834 // TODO Appropriate docs section?
3835 // @section Other Methods
3836 // @method addHandler(name: String, HandlerClass: Function): this
3837 // Adds a new `Handler` to the map, given its name and constructor function.
3838 addHandler: function (name, HandlerClass) {
3839 if (!HandlerClass) { return this; }
3840
3841 var handler = this[name] = new HandlerClass(this);
3842
3843 this._handlers.push(handler);
3844
3845 if (this.options[name]) {
3846 handler.enable();
3847 }
3848
3849 return this;
3850 },
3851
3852 // @method remove(): this
3853 // Destroys the map and clears all related event listeners.
3854 remove: function () {
3855
3856 this._initEvents(true);
3857 if (this.options.maxBounds) { this.off('moveend', this._panInsideMaxBounds); }
3858
3859 if (this._containerId !== this._container._leaflet_id) {
3860 throw new Error('Map container is being reused by another instance');
3861 }
3862
3863 try {
3864 // throws error in IE6-8
3865 delete this._container._leaflet_id;
3866 delete this._containerId;
3867 } catch (e) {
3868 /*eslint-disable */
3869 this._container._leaflet_id = undefined;
3870 /* eslint-enable */
3871 this._containerId = undefined;
3872 }
3873
3874 if (this._locationWatchId !== undefined) {
3875 this.stopLocate();
3876 }
3877
3878 this._stop();
3879
3880 remove(this._mapPane);
3881
3882 if (this._clearControlPos) {
3883 this._clearControlPos();
3884 }
3885 if (this._resizeRequest) {
3886 cancelAnimFrame(this._resizeRequest);
3887 this._resizeRequest = null;
3888 }
3889
3890 this._clearHandlers();
3891
3892 if (this._loaded) {
3893 // @section Map state change events
3894 // @event unload: Event
3895 // Fired when the map is destroyed with [remove](#map-remove) method.
3896 this.fire('unload');
3897 }
3898
3899 var i;
3900 for (i in this._layers) {
3901 this._layers[i].remove();
3902 }
3903 for (i in this._panes) {
3904 remove(this._panes[i]);
3905 }
3906
3907 this._layers = [];
3908 this._panes = [];
3909 delete this._mapPane;
3910 delete this._renderer;
3911
3912 return this;
3913 },
3914
3915 // @section Other Methods
3916 // @method createPane(name: String, container?: HTMLElement): HTMLElement
3917 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
3918 // then returns it. The pane is created as a child of `container`, or
3919 // as a child of the main map pane if not set.
3920 createPane: function (name, container) {
3921 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3922 pane = create$1('div', className, container || this._mapPane);
3923
3924 if (name) {
3925 this._panes[name] = pane;
3926 }
3927 return pane;
3928 },
3929
3930 // @section Methods for Getting Map State
3931
3932 // @method getCenter(): LatLng
3933 // Returns the geographical center of the map view
3934 getCenter: function () {
3935 this._checkIfLoaded();
3936
3937 if (this._lastCenter && !this._moved()) {
3938 return this._lastCenter.clone();
3939 }
3940 return this.layerPointToLatLng(this._getCenterLayerPoint());
3941 },
3942
3943 // @method getZoom(): Number
3944 // Returns the current zoom level of the map view
3945 getZoom: function () {
3946 return this._zoom;
3947 },
3948
3949 // @method getBounds(): LatLngBounds
3950 // Returns the geographical bounds visible in the current map view
3951 getBounds: function () {
3952 var bounds = this.getPixelBounds(),
3953 sw = this.unproject(bounds.getBottomLeft()),
3954 ne = this.unproject(bounds.getTopRight());
3955
3956 return new LatLngBounds(sw, ne);
3957 },
3958
3959 // @method getMinZoom(): Number
3960 // 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.
3961 getMinZoom: function () {
3962 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3963 },
3964
3965 // @method getMaxZoom(): Number
3966 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3967 getMaxZoom: function () {
3968 return this.options.maxZoom === undefined ?
3969 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3970 this.options.maxZoom;
3971 },
3972
3973 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
3974 // Returns the maximum zoom level on which the given bounds fit to the map
3975 // view in its entirety. If `inside` (optional) is set to `true`, the method
3976 // instead returns the minimum zoom level on which the map view fits into
3977 // the given bounds in its entirety.
3978 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3979 bounds = toLatLngBounds(bounds);
3980 padding = toPoint(padding || [0, 0]);
3981
3982 var zoom = this.getZoom() || 0,
3983 min = this.getMinZoom(),
3984 max = this.getMaxZoom(),
3985 nw = bounds.getNorthWest(),
3986 se = bounds.getSouthEast(),
3987 size = this.getSize().subtract(padding),
3988 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3989 snap = Browser.any3d ? this.options.zoomSnap : 1,
3990 scalex = size.x / boundsSize.x,
3991 scaley = size.y / boundsSize.y,
3992 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
3993
3994 zoom = this.getScaleZoom(scale, zoom);
3995
3996 if (snap) {
3997 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3998 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3999 }
4000
4001 return Math.max(min, Math.min(max, zoom));
4002 },
4003
4004 // @method getSize(): Point
4005 // Returns the current size of the map container (in pixels).
4006 getSize: function () {
4007 if (!this._size || this._sizeChanged) {
4008 this._size = new Point(
4009 this._container.clientWidth || 0,
4010 this._container.clientHeight || 0);
4011
4012 this._sizeChanged = false;
4013 }
4014 return this._size.clone();
4015 },
4016
4017 // @method getPixelBounds(): Bounds
4018 // Returns the bounds of the current map view in projected pixel
4019 // coordinates (sometimes useful in layer and overlay implementations).
4020 getPixelBounds: function (center, zoom) {
4021 var topLeftPoint = this._getTopLeftPoint(center, zoom);
4022 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
4023 },
4024
4025 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
4026 // the map pane? "left point of the map layer" can be confusing, specially
4027 // since there can be negative offsets.
4028 // @method getPixelOrigin(): Point
4029 // Returns the projected pixel coordinates of the top left point of
4030 // the map layer (useful in custom layer and overlay implementations).
4031 getPixelOrigin: function () {
4032 this._checkIfLoaded();
4033 return this._pixelOrigin;
4034 },
4035
4036 // @method getPixelWorldBounds(zoom?: Number): Bounds
4037 // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
4038 // If `zoom` is omitted, the map's current zoom level is used.
4039 getPixelWorldBounds: function (zoom) {
4040 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
4041 },
4042
4043 // @section Other Methods
4044
4045 // @method getPane(pane: String|HTMLElement): HTMLElement
4046 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
4047 getPane: function (pane) {
4048 return typeof pane === 'string' ? this._panes[pane] : pane;
4049 },
4050
4051 // @method getPanes(): Object
4052 // Returns a plain object containing the names of all [panes](#map-pane) as keys and
4053 // the panes as values.
4054 getPanes: function () {
4055 return this._panes;
4056 },
4057
4058 // @method getContainer: HTMLElement
4059 // Returns the HTML element that contains the map.
4060 getContainer: function () {
4061 return this._container;
4062 },
4063
4064
4065 // @section Conversion Methods
4066
4067 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
4068 // Returns the scale factor to be applied to a map transition from zoom level
4069 // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
4070 getZoomScale: function (toZoom, fromZoom) {
4071 // TODO replace with universal implementation after refactoring projections
4072 var crs = this.options.crs;
4073 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
4074 return crs.scale(toZoom) / crs.scale(fromZoom);
4075 },
4076
4077 // @method getScaleZoom(scale: Number, fromZoom: Number): Number
4078 // Returns the zoom level that the map would end up at, if it is at `fromZoom`
4079 // level and everything is scaled by a factor of `scale`. Inverse of
4080 // [`getZoomScale`](#map-getZoomScale).
4081 getScaleZoom: function (scale, fromZoom) {
4082 var crs = this.options.crs;
4083 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
4084 var zoom = crs.zoom(scale * crs.scale(fromZoom));
4085 return isNaN(zoom) ? Infinity : zoom;
4086 },
4087
4088 // @method project(latlng: LatLng, zoom: Number): Point
4089 // Projects a geographical coordinate `LatLng` according to the projection
4090 // of the map's CRS, then scales it according to `zoom` and the CRS's
4091 // `Transformation`. The result is pixel coordinate relative to
4092 // the CRS origin.
4093 project: function (latlng, zoom) {
4094 zoom = zoom === undefined ? this._zoom : zoom;
4095 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
4096 },
4097
4098 // @method unproject(point: Point, zoom: Number): LatLng
4099 // Inverse of [`project`](#map-project).
4100 unproject: function (point, zoom) {
4101 zoom = zoom === undefined ? this._zoom : zoom;
4102 return this.options.crs.pointToLatLng(toPoint(point), zoom);
4103 },
4104
4105 // @method layerPointToLatLng(point: Point): LatLng
4106 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
4107 // returns the corresponding geographical coordinate (for the current zoom level).
4108 layerPointToLatLng: function (point) {
4109 var projectedPoint = toPoint(point).add(this.getPixelOrigin());
4110 return this.unproject(projectedPoint);
4111 },
4112
4113 // @method latLngToLayerPoint(latlng: LatLng): Point
4114 // Given a geographical coordinate, returns the corresponding pixel coordinate
4115 // relative to the [origin pixel](#map-getpixelorigin).
4116 latLngToLayerPoint: function (latlng) {
4117 var projectedPoint = this.project(toLatLng(latlng))._round();
4118 return projectedPoint._subtract(this.getPixelOrigin());
4119 },
4120
4121 // @method wrapLatLng(latlng: LatLng): LatLng
4122 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
4123 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
4124 // CRS's bounds.
4125 // By default this means longitude is wrapped around the dateline so its
4126 // value is between -180 and +180 degrees.
4127 wrapLatLng: function (latlng) {
4128 return this.options.crs.wrapLatLng(toLatLng(latlng));
4129 },
4130
4131 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
4132 // Returns a `LatLngBounds` with the same size as the given one, ensuring that
4133 // its center is within the CRS's bounds.
4134 // By default this means the center longitude is wrapped around the dateline so its
4135 // value is between -180 and +180 degrees, and the majority of the bounds
4136 // overlaps the CRS's bounds.
4137 wrapLatLngBounds: function (latlng) {
4138 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
4139 },
4140
4141 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
4142 // Returns the distance between two geographical coordinates according to
4143 // the map's CRS. By default this measures distance in meters.
4144 distance: function (latlng1, latlng2) {
4145 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
4146 },
4147
4148 // @method containerPointToLayerPoint(point: Point): Point
4149 // Given a pixel coordinate relative to the map container, returns the corresponding
4150 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
4151 containerPointToLayerPoint: function (point) { // (Point)
4152 return toPoint(point).subtract(this._getMapPanePos());
4153 },
4154
4155 // @method layerPointToContainerPoint(point: Point): Point
4156 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
4157 // returns the corresponding pixel coordinate relative to the map container.
4158 layerPointToContainerPoint: function (point) { // (Point)
4159 return toPoint(point).add(this._getMapPanePos());
4160 },
4161
4162 // @method containerPointToLatLng(point: Point): LatLng
4163 // Given a pixel coordinate relative to the map container, returns
4164 // the corresponding geographical coordinate (for the current zoom level).
4165 containerPointToLatLng: function (point) {
4166 var layerPoint = this.containerPointToLayerPoint(toPoint(point));
4167 return this.layerPointToLatLng(layerPoint);
4168 },
4169
4170 // @method latLngToContainerPoint(latlng: LatLng): Point
4171 // Given a geographical coordinate, returns the corresponding pixel coordinate
4172 // relative to the map container.
4173 latLngToContainerPoint: function (latlng) {
4174 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
4175 },
4176
4177 // @method mouseEventToContainerPoint(ev: MouseEvent): Point
4178 // Given a MouseEvent object, returns the pixel coordinate relative to the
4179 // map container where the event took place.
4180 mouseEventToContainerPoint: function (e) {
4181 return getMousePosition(e, this._container);
4182 },
4183
4184 // @method mouseEventToLayerPoint(ev: MouseEvent): Point
4185 // Given a MouseEvent object, returns the pixel coordinate relative to
4186 // the [origin pixel](#map-getpixelorigin) where the event took place.
4187 mouseEventToLayerPoint: function (e) {
4188 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
4189 },
4190
4191 // @method mouseEventToLatLng(ev: MouseEvent): LatLng
4192 // Given a MouseEvent object, returns geographical coordinate where the
4193 // event took place.
4194 mouseEventToLatLng: function (e) { // (MouseEvent)
4195 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
4196 },
4197
4198
4199 // map initialization methods
4200
4201 _initContainer: function (id) {
4202 var container = this._container = get(id);
4203
4204 if (!container) {
4205 throw new Error('Map container not found.');
4206 } else if (container._leaflet_id) {
4207 throw new Error('Map container is already initialized.');
4208 }
4209
4210 on(container, 'scroll', this._onScroll, this);
4211 this._containerId = stamp(container);
4212 },
4213
4214 _initLayout: function () {
4215 var container = this._container;
4216
4217 this._fadeAnimated = this.options.fadeAnimation && Browser.any3d;
4218
4219 addClass(container, 'leaflet-container' +
4220 (Browser.touch ? ' leaflet-touch' : '') +
4221 (Browser.retina ? ' leaflet-retina' : '') +
4222 (Browser.ielt9 ? ' leaflet-oldie' : '') +
4223 (Browser.safari ? ' leaflet-safari' : '') +
4224 (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
4225
4226 var position = getStyle(container, 'position');
4227
4228 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed' && position !== 'sticky') {
4229 container.style.position = 'relative';
4230 }
4231
4232 this._initPanes();
4233
4234 if (this._initControlPos) {
4235 this._initControlPos();
4236 }
4237 },
4238
4239 _initPanes: function () {
4240 var panes = this._panes = {};
4241 this._paneRenderers = {};
4242
4243 // @section
4244 //
4245 // Panes are DOM elements used to control the ordering of layers on the map. You
4246 // can access panes with [`map.getPane`](#map-getpane) or
4247 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
4248 // [`map.createPane`](#map-createpane) method.
4249 //
4250 // Every map has the following default panes that differ only in zIndex.
4251 //
4252 // @pane mapPane: HTMLElement = 'auto'
4253 // Pane that contains all other map panes
4254
4255 this._mapPane = this.createPane('mapPane', this._container);
4256 setPosition(this._mapPane, new Point(0, 0));
4257
4258 // @pane tilePane: HTMLElement = 200
4259 // Pane for `GridLayer`s and `TileLayer`s
4260 this.createPane('tilePane');
4261 // @pane overlayPane: HTMLElement = 400
4262 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
4263 this.createPane('overlayPane');
4264 // @pane shadowPane: HTMLElement = 500
4265 // Pane for overlay shadows (e.g. `Marker` shadows)
4266 this.createPane('shadowPane');
4267 // @pane markerPane: HTMLElement = 600
4268 // Pane for `Icon`s of `Marker`s
4269 this.createPane('markerPane');
4270 // @pane tooltipPane: HTMLElement = 650
4271 // Pane for `Tooltip`s.
4272 this.createPane('tooltipPane');
4273 // @pane popupPane: HTMLElement = 700
4274 // Pane for `Popup`s.
4275 this.createPane('popupPane');
4276
4277 if (!this.options.markerZoomAnimation) {
4278 addClass(panes.markerPane, 'leaflet-zoom-hide');
4279 addClass(panes.shadowPane, 'leaflet-zoom-hide');
4280 }
4281 },
4282
4283
4284 // private methods that modify map state
4285
4286 // @section Map state change events
4287 _resetView: function (center, zoom, noMoveStart) {
4288 setPosition(this._mapPane, new Point(0, 0));
4289
4290 var loading = !this._loaded;
4291 this._loaded = true;
4292 zoom = this._limitZoom(zoom);
4293
4294 this.fire('viewprereset');
4295
4296 var zoomChanged = this._zoom !== zoom;
4297 this
4298 ._moveStart(zoomChanged, noMoveStart)
4299 ._move(center, zoom)
4300 ._moveEnd(zoomChanged);
4301
4302 // @event viewreset: Event
4303 // Fired when the map needs to redraw its content (this usually happens
4304 // on map zoom or load). Very useful for creating custom overlays.
4305 this.fire('viewreset');
4306
4307 // @event load: Event
4308 // Fired when the map is initialized (when its center and zoom are set
4309 // for the first time).
4310 if (loading) {
4311 this.fire('load');
4312 }
4313 },
4314
4315 _moveStart: function (zoomChanged, noMoveStart) {
4316 // @event zoomstart: Event
4317 // Fired when the map zoom is about to change (e.g. before zoom animation).
4318 // @event movestart: Event
4319 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
4320 if (zoomChanged) {
4321 this.fire('zoomstart');
4322 }
4323 if (!noMoveStart) {
4324 this.fire('movestart');
4325 }
4326 return this;
4327 },
4328
4329 _move: function (center, zoom, data, supressEvent) {
4330 if (zoom === undefined) {
4331 zoom = this._zoom;
4332 }
4333 var zoomChanged = this._zoom !== zoom;
4334
4335 this._zoom = zoom;
4336 this._lastCenter = center;
4337 this._pixelOrigin = this._getNewPixelOrigin(center);
4338
4339 if (!supressEvent) {
4340 // @event zoom: Event
4341 // Fired repeatedly during any change in zoom level,
4342 // including zoom and fly animations.
4343 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530
4344 this.fire('zoom', data);
4345 }
4346
4347 // @event move: Event
4348 // Fired repeatedly during any movement of the map,
4349 // including pan and fly animations.
4350 this.fire('move', data);
4351 } else if (data && data.pinch) { // Always fire 'zoom' if pinching because #3530
4352 this.fire('zoom', data);
4353 }
4354 return this;
4355 },
4356
4357 _moveEnd: function (zoomChanged) {
4358 // @event zoomend: Event
4359 // Fired when the map zoom changed, after any animations.
4360 if (zoomChanged) {
4361 this.fire('zoomend');
4362 }
4363
4364 // @event moveend: Event
4365 // Fired when the center of the map stops changing
4366 // (e.g. user stopped dragging the map or after non-centered zoom).
4367 return this.fire('moveend');
4368 },
4369
4370 _stop: function () {
4371 cancelAnimFrame(this._flyToFrame);
4372 if (this._panAnim) {
4373 this._panAnim.stop();
4374 }
4375 return this;
4376 },
4377
4378 _rawPanBy: function (offset) {
4379 setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
4380 },
4381
4382 _getZoomSpan: function () {
4383 return this.getMaxZoom() - this.getMinZoom();
4384 },
4385
4386 _panInsideMaxBounds: function () {
4387 if (!this._enforcingBounds) {
4388 this.panInsideBounds(this.options.maxBounds);
4389 }
4390 },
4391
4392 _checkIfLoaded: function () {
4393 if (!this._loaded) {
4394 throw new Error('Set map center and zoom first.');
4395 }
4396 },
4397
4398 // DOM event handling
4399
4400 // @section Interaction events
4401 _initEvents: function (remove) {
4402 this._targets = {};
4403 this._targets[stamp(this._container)] = this;
4404
4405 var onOff = remove ? off : on;
4406
4407 // @event click: MouseEvent
4408 // Fired when the user clicks (or taps) the map.
4409 // @event dblclick: MouseEvent
4410 // Fired when the user double-clicks (or double-taps) the map.
4411 // @event mousedown: MouseEvent
4412 // Fired when the user pushes the mouse button on the map.
4413 // @event mouseup: MouseEvent
4414 // Fired when the user releases the mouse button on the map.
4415 // @event mouseover: MouseEvent
4416 // Fired when the mouse enters the map.
4417 // @event mouseout: MouseEvent
4418 // Fired when the mouse leaves the map.
4419 // @event mousemove: MouseEvent
4420 // Fired while the mouse moves over the map.
4421 // @event contextmenu: MouseEvent
4422 // Fired when the user pushes the right mouse button on the map, prevents
4423 // default browser context menu from showing if there are listeners on
4424 // this event. Also fired on mobile when the user holds a single touch
4425 // for a second (also called long press).
4426 // @event keypress: KeyboardEvent
4427 // Fired when the user presses a key from the keyboard that produces a character value while the map is focused.
4428 // @event keydown: KeyboardEvent
4429 // Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event,
4430 // the `keydown` event is fired for keys that produce a character value and for keys
4431 // that do not produce a character value.
4432 // @event keyup: KeyboardEvent
4433 // Fired when the user releases a key from the keyboard while the map is focused.
4434 onOff(this._container, 'click dblclick mousedown mouseup ' +
4435 'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this);
4436
4437 if (this.options.trackResize) {
4438 onOff(window, 'resize', this._onResize, this);
4439 }
4440
4441 if (Browser.any3d && this.options.transform3DLimit) {
4442 (remove ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
4443 }
4444 },
4445
4446 _onResize: function () {
4447 cancelAnimFrame(this._resizeRequest);
4448 this._resizeRequest = requestAnimFrame(
4449 function () { this.invalidateSize({debounceMoveend: true}); }, this);
4450 },
4451
4452 _onScroll: function () {
4453 this._container.scrollTop = 0;
4454 this._container.scrollLeft = 0;
4455 },
4456
4457 _onMoveEnd: function () {
4458 var pos = this._getMapPanePos();
4459 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
4460 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
4461 // a pixel offset on very high values, see: https://jsfiddle.net/dg6r5hhb/
4462 this._resetView(this.getCenter(), this.getZoom());
4463 }
4464 },
4465
4466 _findEventTargets: function (e, type) {
4467 var targets = [],
4468 target,
4469 isHover = type === 'mouseout' || type === 'mouseover',
4470 src = e.target || e.srcElement,
4471 dragging = false;
4472
4473 while (src) {
4474 target = this._targets[stamp(src)];
4475 if (target && (type === 'click' || type === 'preclick') && this._draggableMoved(target)) {
4476 // Prevent firing click after you just dragged an object.
4477 dragging = true;
4478 break;
4479 }
4480 if (target && target.listens(type, true)) {
4481 if (isHover && !isExternalTarget(src, e)) { break; }
4482 targets.push(target);
4483 if (isHover) { break; }
4484 }
4485 if (src === this._container) { break; }
4486 src = src.parentNode;
4487 }
4488 if (!targets.length && !dragging && !isHover && this.listens(type, true)) {
4489 targets = [this];
4490 }
4491 return targets;
4492 },
4493
4494 _isClickDisabled: function (el) {
4495 while (el && el !== this._container) {
4496 if (el['_leaflet_disable_click']) { return true; }
4497 el = el.parentNode;
4498 }
4499 },
4500
4501 _handleDOMEvent: function (e) {
4502 var el = (e.target || e.srcElement);
4503 if (!this._loaded || el['_leaflet_disable_events'] || e.type === 'click' && this._isClickDisabled(el)) {
4504 return;
4505 }
4506
4507 var type = e.type;
4508
4509 if (type === 'mousedown') {
4510 // prevents outline when clicking on keyboard-focusable element
4511 preventOutline(el);
4512 }
4513
4514 this._fireDOMEvent(e, type);
4515 },
4516
4517 _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
4518
4519 _fireDOMEvent: function (e, type, canvasTargets) {
4520
4521 if (e.type === 'click') {
4522 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
4523 // @event preclick: MouseEvent
4524 // Fired before mouse click on the map (sometimes useful when you
4525 // want something to happen on click before any existing click
4526 // handlers start running).
4527 var synth = extend({}, e);
4528 synth.type = 'preclick';
4529 this._fireDOMEvent(synth, synth.type, canvasTargets);
4530 }
4531
4532 // Find the layer the event is propagating from and its parents.
4533 var targets = this._findEventTargets(e, type);
4534
4535 if (canvasTargets) {
4536 var filtered = []; // pick only targets with listeners
4537 for (var i = 0; i < canvasTargets.length; i++) {
4538 if (canvasTargets[i].listens(type, true)) {
4539 filtered.push(canvasTargets[i]);
4540 }
4541 }
4542 targets = filtered.concat(targets);
4543 }
4544
4545 if (!targets.length) { return; }
4546
4547 if (type === 'contextmenu') {
4548 preventDefault(e);
4549 }
4550
4551 var target = targets[0];
4552 var data = {
4553 originalEvent: e
4554 };
4555
4556 if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') {
4557 var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
4558 data.containerPoint = isMarker ?
4559 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
4560 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
4561 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
4562 }
4563
4564 for (i = 0; i < targets.length; i++) {
4565 targets[i].fire(type, data, true);
4566 if (data.originalEvent._stopped ||
4567 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
4568 }
4569 },
4570
4571 _draggableMoved: function (obj) {
4572 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
4573 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
4574 },
4575
4576 _clearHandlers: function () {
4577 for (var i = 0, len = this._handlers.length; i < len; i++) {
4578 this._handlers[i].disable();
4579 }
4580 },
4581
4582 // @section Other Methods
4583
4584 // @method whenReady(fn: Function, context?: Object): this
4585 // Runs the given function `fn` when the map gets initialized with
4586 // a view (center and zoom) and at least one layer, or immediately
4587 // if it's already initialized, optionally passing a function context.
4588 whenReady: function (callback, context) {
4589 if (this._loaded) {
4590 callback.call(context || this, {target: this});
4591 } else {
4592 this.on('load', callback, context);
4593 }
4594 return this;
4595 },
4596
4597
4598 // private methods for getting map state
4599
4600 _getMapPanePos: function () {
4601 return getPosition(this._mapPane) || new Point(0, 0);
4602 },
4603
4604 _moved: function () {
4605 var pos = this._getMapPanePos();
4606 return pos && !pos.equals([0, 0]);
4607 },
4608
4609 _getTopLeftPoint: function (center, zoom) {
4610 var pixelOrigin = center && zoom !== undefined ?
4611 this._getNewPixelOrigin(center, zoom) :
4612 this.getPixelOrigin();
4613 return pixelOrigin.subtract(this._getMapPanePos());
4614 },
4615
4616 _getNewPixelOrigin: function (center, zoom) {
4617 var viewHalf = this.getSize()._divideBy(2);
4618 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
4619 },
4620
4621 _latLngToNewLayerPoint: function (latlng, zoom, center) {
4622 var topLeft = this._getNewPixelOrigin(center, zoom);
4623 return this.project(latlng, zoom)._subtract(topLeft);
4624 },
4625
4626 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
4627 var topLeft = this._getNewPixelOrigin(center, zoom);
4628 return toBounds([
4629 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
4630 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
4631 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
4632 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
4633 ]);
4634 },
4635
4636 // layer point of the current center
4637 _getCenterLayerPoint: function () {
4638 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
4639 },
4640
4641 // offset of the specified place to the current center in pixels
4642 _getCenterOffset: function (latlng) {
4643 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
4644 },
4645
4646 // adjust center for view to get inside bounds
4647 _limitCenter: function (center, zoom, bounds) {
4648
4649 if (!bounds) { return center; }
4650
4651 var centerPoint = this.project(center, zoom),
4652 viewHalf = this.getSize().divideBy(2),
4653 viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
4654 offset = this._getBoundsOffset(viewBounds, bounds, zoom);
4655
4656 // If offset is less than a pixel, ignore.
4657 // This prevents unstable projections from getting into
4658 // an infinite loop of tiny offsets.
4659 if (Math.abs(offset.x) <= 1 && Math.abs(offset.y) <= 1) {
4660 return center;
4661 }
4662
4663 return this.unproject(centerPoint.add(offset), zoom);
4664 },
4665
4666 // adjust offset for view to get inside bounds
4667 _limitOffset: function (offset, bounds) {
4668 if (!bounds) { return offset; }
4669
4670 var viewBounds = this.getPixelBounds(),
4671 newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
4672
4673 return offset.add(this._getBoundsOffset(newBounds, bounds));
4674 },
4675
4676 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
4677 _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
4678 var projectedMaxBounds = toBounds(
4679 this.project(maxBounds.getNorthEast(), zoom),
4680 this.project(maxBounds.getSouthWest(), zoom)
4681 ),
4682 minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
4683 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
4684
4685 dx = this._rebound(minOffset.x, -maxOffset.x),
4686 dy = this._rebound(minOffset.y, -maxOffset.y);
4687
4688 return new Point(dx, dy);
4689 },
4690
4691 _rebound: function (left, right) {
4692 return left + right > 0 ?
4693 Math.round(left - right) / 2 :
4694 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
4695 },
4696
4697 _limitZoom: function (zoom) {
4698 var min = this.getMinZoom(),
4699 max = this.getMaxZoom(),
4700 snap = Browser.any3d ? this.options.zoomSnap : 1;
4701 if (snap) {
4702 zoom = Math.round(zoom / snap) * snap;
4703 }
4704 return Math.max(min, Math.min(max, zoom));
4705 },
4706
4707 _onPanTransitionStep: function () {
4708 this.fire('move');
4709 },
4710
4711 _onPanTransitionEnd: function () {
4712 removeClass(this._mapPane, 'leaflet-pan-anim');
4713 this.fire('moveend');
4714 },
4715
4716 _tryAnimatedPan: function (center, options) {
4717 // difference between the new and current centers in pixels
4718 var offset = this._getCenterOffset(center)._trunc();
4719
4720 // don't animate too far unless animate: true specified in options
4721 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
4722
4723 this.panBy(offset, options);
4724
4725 return true;
4726 },
4727
4728 _createAnimProxy: function () {
4729
4730 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
4731 this._panes.mapPane.appendChild(proxy);
4732
4733 this.on('zoomanim', function (e) {
4734 var prop = TRANSFORM,
4735 transform = this._proxy.style[prop];
4736
4737 setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
4738
4739 // workaround for case when transform is the same and so transitionend event is not fired
4740 if (transform === this._proxy.style[prop] && this._animatingZoom) {
4741 this._onZoomTransitionEnd();
4742 }
4743 }, this);
4744
4745 this.on('load moveend', this._animMoveEnd, this);
4746
4747 this._on('unload', this._destroyAnimProxy, this);
4748 },
4749
4750 _destroyAnimProxy: function () {
4751 remove(this._proxy);
4752 this.off('load moveend', this._animMoveEnd, this);
4753 delete this._proxy;
4754 },
4755
4756 _animMoveEnd: function () {
4757 var c = this.getCenter(),
4758 z = this.getZoom();
4759 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
4760 },
4761
4762 _catchTransitionEnd: function (e) {
4763 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
4764 this._onZoomTransitionEnd();
4765 }
4766 },
4767
4768 _nothingToAnimate: function () {
4769 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
4770 },
4771
4772 _tryAnimatedZoom: function (center, zoom, options) {
4773
4774 if (this._animatingZoom) { return true; }
4775
4776 options = options || {};
4777
4778 // don't animate if disabled, not supported or zoom difference is too large
4779 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
4780 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
4781
4782 // offset is the pixel coords of the zoom origin relative to the current center
4783 var scale = this.getZoomScale(zoom),
4784 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
4785
4786 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
4787 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
4788
4789 requestAnimFrame(function () {
4790 this
4791 ._moveStart(true, options.noMoveStart || false)
4792 ._animateZoom(center, zoom, true);
4793 }, this);
4794
4795 return true;
4796 },
4797
4798 _animateZoom: function (center, zoom, startAnim, noUpdate) {
4799 if (!this._mapPane) { return; }
4800
4801 if (startAnim) {
4802 this._animatingZoom = true;
4803
4804 // remember what center/zoom to set after animation
4805 this._animateToCenter = center;
4806 this._animateToZoom = zoom;
4807
4808 addClass(this._mapPane, 'leaflet-zoom-anim');
4809 }
4810
4811 // @section Other Events
4812 // @event zoomanim: ZoomAnimEvent
4813 // Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom.
4814 this.fire('zoomanim', {
4815 center: center,
4816 zoom: zoom,
4817 noUpdate: noUpdate
4818 });
4819
4820 if (!this._tempFireZoomEvent) {
4821 this._tempFireZoomEvent = this._zoom !== this._animateToZoom;
4822 }
4823
4824 this._move(this._animateToCenter, this._animateToZoom, undefined, true);
4825
4826 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
4827 setTimeout(bind(this._onZoomTransitionEnd, this), 250);
4828 },
4829
4830 _onZoomTransitionEnd: function () {
4831 if (!this._animatingZoom) { return; }
4832
4833 if (this._mapPane) {
4834 removeClass(this._mapPane, 'leaflet-zoom-anim');
4835 }
4836
4837 this._animatingZoom = false;
4838
4839 this._move(this._animateToCenter, this._animateToZoom, undefined, true);
4840
4841 if (this._tempFireZoomEvent) {
4842 this.fire('zoom');
4843 }
4844 delete this._tempFireZoomEvent;
4845
4846 this.fire('move');
4847
4848 this._moveEnd(true);
4849 }
4850 });
4851
4852 // @section
4853
4854 // @factory L.map(id: String, options?: Map options)
4855 // Instantiates a map object given the DOM ID of a `<div>` element
4856 // and optionally an object literal with `Map options`.
4857 //
4858 // @alternative
4859 // @factory L.map(el: HTMLElement, options?: Map options)
4860 // Instantiates a map object given an instance of a `<div>` HTML element
4861 // and optionally an object literal with `Map options`.
4862 function createMap(id, options) {
4863 return new Map(id, options);
4864 }
4865
4866 /*
4867 * @class Control
4868 * @aka L.Control
4869 * @inherits Class
4870 *
4871 * L.Control is a base class for implementing map controls. Handles positioning.
4872 * All other controls extend from this class.
4873 */
4874
4875 var Control = Class.extend({
4876 // @section
4877 // @aka Control Options
4878 options: {
4879 // @option position: String = 'topright'
4880 // The position of the control (one of the map corners). Possible values are `'topleft'`,
4881 // `'topright'`, `'bottomleft'` or `'bottomright'`
4882 position: 'topright'
4883 },
4884
4885 initialize: function (options) {
4886 setOptions(this, options);
4887 },
4888
4889 /* @section
4890 * Classes extending L.Control will inherit the following methods:
4891 *
4892 * @method getPosition: string
4893 * Returns the position of the control.
4894 */
4895 getPosition: function () {
4896 return this.options.position;
4897 },
4898
4899 // @method setPosition(position: string): this
4900 // Sets the position of the control.
4901 setPosition: function (position) {
4902 var map = this._map;
4903
4904 if (map) {
4905 map.removeControl(this);
4906 }
4907
4908 this.options.position = position;
4909
4910 if (map) {
4911 map.addControl(this);
4912 }
4913
4914 return this;
4915 },
4916
4917 // @method getContainer: HTMLElement
4918 // Returns the HTMLElement that contains the control.
4919 getContainer: function () {
4920 return this._container;
4921 },
4922
4923 // @method addTo(map: Map): this
4924 // Adds the control to the given map.
4925 addTo: function (map) {
4926 this.remove();
4927 this._map = map;
4928
4929 var container = this._container = this.onAdd(map),
4930 pos = this.getPosition(),
4931 corner = map._controlCorners[pos];
4932
4933 addClass(container, 'leaflet-control');
4934
4935 if (pos.indexOf('bottom') !== -1) {
4936 corner.insertBefore(container, corner.firstChild);
4937 } else {
4938 corner.appendChild(container);
4939 }
4940
4941 this._map.on('unload', this.remove, this);
4942
4943 return this;
4944 },
4945
4946 // @method remove: this
4947 // Removes the control from the map it is currently active on.
4948 remove: function () {
4949 if (!this._map) {
4950 return this;
4951 }
4952
4953 remove(this._container);
4954
4955 if (this.onRemove) {
4956 this.onRemove(this._map);
4957 }
4958
4959 this._map.off('unload', this.remove, this);
4960 this._map = null;
4961
4962 return this;
4963 },
4964
4965 _refocusOnMap: function (e) {
4966 // if map exists and event is not a keyboard event
4967 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
4968 this._map.getContainer().focus();
4969 }
4970 }
4971 });
4972
4973 var control = function (options) {
4974 return new Control(options);
4975 };
4976
4977 /* @section Extension methods
4978 * @uninheritable
4979 *
4980 * Every control should extend from `L.Control` and (re-)implement the following methods.
4981 *
4982 * @method onAdd(map: Map): HTMLElement
4983 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
4984 *
4985 * @method onRemove(map: Map)
4986 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
4987 */
4988
4989 /* @namespace Map
4990 * @section Methods for Layers and Controls
4991 */
4992 Map.include({
4993 // @method addControl(control: Control): this
4994 // Adds the given control to the map
4995 addControl: function (control) {
4996 control.addTo(this);
4997 return this;
4998 },
4999
5000 // @method removeControl(control: Control): this
5001 // Removes the given control from the map
5002 removeControl: function (control) {
5003 control.remove();
5004 return this;
5005 },
5006
5007 _initControlPos: function () {
5008 var corners = this._controlCorners = {},
5009 l = 'leaflet-',
5010 container = this._controlContainer =
5011 create$1('div', l + 'control-container', this._container);
5012
5013 function createCorner(vSide, hSide) {
5014 var className = l + vSide + ' ' + l + hSide;
5015
5016 corners[vSide + hSide] = create$1('div', className, container);
5017 }
5018
5019 createCorner('top', 'left');
5020 createCorner('top', 'right');
5021 createCorner('bottom', 'left');
5022 createCorner('bottom', 'right');
5023 },
5024
5025 _clearControlPos: function () {
5026 for (var i in this._controlCorners) {
5027 remove(this._controlCorners[i]);
5028 }
5029 remove(this._controlContainer);
5030 delete this._controlCorners;
5031 delete this._controlContainer;
5032 }
5033 });
5034
5035 /*
5036 * @class Control.Layers
5037 * @aka L.Control.Layers
5038 * @inherits Control
5039 *
5040 * The layers control gives users the ability to switch between different base layers and switch overlays on/off (check out the [detailed example](https://leafletjs.com/examples/layers-control/)). Extends `Control`.
5041 *
5042 * @example
5043 *
5044 * ```js
5045 * var baseLayers = {
5046 * "Mapbox": mapbox,
5047 * "OpenStreetMap": osm
5048 * };
5049 *
5050 * var overlays = {
5051 * "Marker": marker,
5052 * "Roads": roadsLayer
5053 * };
5054 *
5055 * L.control.layers(baseLayers, overlays).addTo(map);
5056 * ```
5057 *
5058 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
5059 *
5060 * ```js
5061 * {
5062 * "<someName1>": layer1,
5063 * "<someName2>": layer2
5064 * }
5065 * ```
5066 *
5067 * The layer names can contain HTML, which allows you to add additional styling to the items:
5068 *
5069 * ```js
5070 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
5071 * ```
5072 */
5073
5074 var Layers = Control.extend({
5075 // @section
5076 // @aka Control.Layers options
5077 options: {
5078 // @option collapsed: Boolean = true
5079 // If `true`, the control will be collapsed into an icon and expanded on mouse hover, touch, or keyboard activation.
5080 collapsed: true,
5081 position: 'topright',
5082
5083 // @option autoZIndex: Boolean = true
5084 // If `true`, the control will assign zIndexes in increasing order to all of its layers so that the order is preserved when switching them on/off.
5085 autoZIndex: true,
5086
5087 // @option hideSingleBase: Boolean = false
5088 // If `true`, the base layers in the control will be hidden when there is only one.
5089 hideSingleBase: false,
5090
5091 // @option sortLayers: Boolean = false
5092 // Whether to sort the layers. When `false`, layers will keep the order
5093 // in which they were added to the control.
5094 sortLayers: false,
5095
5096 // @option sortFunction: Function = *
5097 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
5098 // that will be used for sorting the layers, when `sortLayers` is `true`.
5099 // The function receives both the `L.Layer` instances and their names, as in
5100 // `sortFunction(layerA, layerB, nameA, nameB)`.
5101 // By default, it sorts layers alphabetically by their name.
5102 sortFunction: function (layerA, layerB, nameA, nameB) {
5103 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
5104 }
5105 },
5106
5107 initialize: function (baseLayers, overlays, options) {
5108 setOptions(this, options);
5109
5110 this._layerControlInputs = [];
5111 this._layers = [];
5112 this._lastZIndex = 0;
5113 this._handlingClick = false;
5114 this._preventClick = false;
5115
5116 for (var i in baseLayers) {
5117 this._addLayer(baseLayers[i], i);
5118 }
5119
5120 for (i in overlays) {
5121 this._addLayer(overlays[i], i, true);
5122 }
5123 },
5124
5125 onAdd: function (map) {
5126 this._initLayout();
5127 this._update();
5128
5129 this._map = map;
5130 map.on('zoomend', this._checkDisabledLayers, this);
5131
5132 for (var i = 0; i < this._layers.length; i++) {
5133 this._layers[i].layer.on('add remove', this._onLayerChange, this);
5134 }
5135
5136 return this._container;
5137 },
5138
5139 addTo: function (map) {
5140 Control.prototype.addTo.call(this, map);
5141 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
5142 return this._expandIfNotCollapsed();
5143 },
5144
5145 onRemove: function () {
5146 this._map.off('zoomend', this._checkDisabledLayers, this);
5147
5148 for (var i = 0; i < this._layers.length; i++) {
5149 this._layers[i].layer.off('add remove', this._onLayerChange, this);
5150 }
5151 },
5152
5153 // @method addBaseLayer(layer: Layer, name: String): this
5154 // Adds a base layer (radio button entry) with the given name to the control.
5155 addBaseLayer: function (layer, name) {
5156 this._addLayer(layer, name);
5157 return (this._map) ? this._update() : this;
5158 },
5159
5160 // @method addOverlay(layer: Layer, name: String): this
5161 // Adds an overlay (checkbox entry) with the given name to the control.
5162 addOverlay: function (layer, name) {
5163 this._addLayer(layer, name, true);
5164 return (this._map) ? this._update() : this;
5165 },
5166
5167 // @method removeLayer(layer: Layer): this
5168 // Remove the given layer from the control.
5169 removeLayer: function (layer) {
5170 layer.off('add remove', this._onLayerChange, this);
5171
5172 var obj = this._getLayer(stamp(layer));
5173 if (obj) {
5174 this._layers.splice(this._layers.indexOf(obj), 1);
5175 }
5176 return (this._map) ? this._update() : this;
5177 },
5178
5179 // @method expand(): this
5180 // Expand the control container if collapsed.
5181 expand: function () {
5182 addClass(this._container, 'leaflet-control-layers-expanded');
5183 this._section.style.height = null;
5184 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
5185 if (acceptableHeight < this._section.clientHeight) {
5186 addClass(this._section, 'leaflet-control-layers-scrollbar');
5187 this._section.style.height = acceptableHeight + 'px';
5188 } else {
5189 removeClass(this._section, 'leaflet-control-layers-scrollbar');
5190 }
5191 this._checkDisabledLayers();
5192 return this;
5193 },
5194
5195 // @method collapse(): this
5196 // Collapse the control container if expanded.
5197 collapse: function () {
5198 removeClass(this._container, 'leaflet-control-layers-expanded');
5199 return this;
5200 },
5201
5202 _initLayout: function () {
5203 var className = 'leaflet-control-layers',
5204 container = this._container = create$1('div', className),
5205 collapsed = this.options.collapsed;
5206
5207 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
5208 container.setAttribute('aria-haspopup', true);
5209
5210 disableClickPropagation(container);
5211 disableScrollPropagation(container);
5212
5213 var section = this._section = create$1('section', className + '-list');
5214
5215 if (collapsed) {
5216 this._map.on('click', this.collapse, this);
5217
5218 on(container, {
5219 mouseenter: this._expandSafely,
5220 mouseleave: this.collapse
5221 }, this);
5222 }
5223
5224 var link = this._layersLink = create$1('a', className + '-toggle', container);
5225 link.href = '#';
5226 link.title = 'Layers';
5227 link.setAttribute('role', 'button');
5228
5229 on(link, {
5230 keydown: function (e) {
5231 if (e.keyCode === 13) {
5232 this._expandSafely();
5233 }
5234 },
5235 // Certain screen readers intercept the key event and instead send a click event
5236 click: function (e) {
5237 preventDefault(e);
5238 this._expandSafely();
5239 }
5240 }, this);
5241
5242 if (!collapsed) {
5243 this.expand();
5244 }
5245
5246 this._baseLayersList = create$1('div', className + '-base', section);
5247 this._separator = create$1('div', className + '-separator', section);
5248 this._overlaysList = create$1('div', className + '-overlays', section);
5249
5250 container.appendChild(section);
5251 },
5252
5253 _getLayer: function (id) {
5254 for (var i = 0; i < this._layers.length; i++) {
5255
5256 if (this._layers[i] && stamp(this._layers[i].layer) === id) {
5257 return this._layers[i];
5258 }
5259 }
5260 },
5261
5262 _addLayer: function (layer, name, overlay) {
5263 if (this._map) {
5264 layer.on('add remove', this._onLayerChange, this);
5265 }
5266
5267 this._layers.push({
5268 layer: layer,
5269 name: name,
5270 overlay: overlay
5271 });
5272
5273 if (this.options.sortLayers) {
5274 this._layers.sort(bind(function (a, b) {
5275 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
5276 }, this));
5277 }
5278
5279 if (this.options.autoZIndex && layer.setZIndex) {
5280 this._lastZIndex++;
5281 layer.setZIndex(this._lastZIndex);
5282 }
5283
5284 this._expandIfNotCollapsed();
5285 },
5286
5287 _update: function () {
5288 if (!this._container) { return this; }
5289
5290 empty(this._baseLayersList);
5291 empty(this._overlaysList);
5292
5293 this._layerControlInputs = [];
5294 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
5295
5296 for (i = 0; i < this._layers.length; i++) {
5297 obj = this._layers[i];
5298 this._addItem(obj);
5299 overlaysPresent = overlaysPresent || obj.overlay;
5300 baseLayersPresent = baseLayersPresent || !obj.overlay;
5301 baseLayersCount += !obj.overlay ? 1 : 0;
5302 }
5303
5304 // Hide base layers section if there's only one layer.
5305 if (this.options.hideSingleBase) {
5306 baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
5307 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
5308 }
5309
5310 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
5311
5312 return this;
5313 },
5314
5315 _onLayerChange: function (e) {
5316 if (!this._handlingClick) {
5317 this._update();
5318 }
5319
5320 var obj = this._getLayer(stamp(e.target));
5321
5322 // @namespace Map
5323 // @section Layer events
5324 // @event baselayerchange: LayersControlEvent
5325 // Fired when the base layer is changed through the [layers control](#control-layers).
5326 // @event overlayadd: LayersControlEvent
5327 // Fired when an overlay is selected through the [layers control](#control-layers).
5328 // @event overlayremove: LayersControlEvent
5329 // Fired when an overlay is deselected through the [layers control](#control-layers).
5330 // @namespace Control.Layers
5331 var type = obj.overlay ?
5332 (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
5333 (e.type === 'add' ? 'baselayerchange' : null);
5334
5335 if (type) {
5336 this._map.fire(type, obj);
5337 }
5338 },
5339
5340 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see https://stackoverflow.com/a/119079)
5341 _createRadioElement: function (name, checked) {
5342
5343 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
5344 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
5345
5346 var radioFragment = document.createElement('div');
5347 radioFragment.innerHTML = radioHtml;
5348
5349 return radioFragment.firstChild;
5350 },
5351
5352 _addItem: function (obj) {
5353 var label = document.createElement('label'),
5354 checked = this._map.hasLayer(obj.layer),
5355 input;
5356
5357 if (obj.overlay) {
5358 input = document.createElement('input');
5359 input.type = 'checkbox';
5360 input.className = 'leaflet-control-layers-selector';
5361 input.defaultChecked = checked;
5362 } else {
5363 input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked);
5364 }
5365
5366 this._layerControlInputs.push(input);
5367 input.layerId = stamp(obj.layer);
5368
5369 on(input, 'click', this._onInputClick, this);
5370
5371 var name = document.createElement('span');
5372 name.innerHTML = ' ' + obj.name;
5373
5374 // Helps from preventing layer control flicker when checkboxes are disabled
5375 // https://github.com/Leaflet/Leaflet/issues/2771
5376 var holder = document.createElement('span');
5377
5378 label.appendChild(holder);
5379 holder.appendChild(input);
5380 holder.appendChild(name);
5381
5382 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
5383 container.appendChild(label);
5384
5385 this._checkDisabledLayers();
5386 return label;
5387 },
5388
5389 _onInputClick: function () {
5390 // expanding the control on mobile with a click can cause adding a layer - we don't want this
5391 if (this._preventClick) {
5392 return;
5393 }
5394
5395 var inputs = this._layerControlInputs,
5396 input, layer;
5397 var addedLayers = [],
5398 removedLayers = [];
5399
5400 this._handlingClick = true;
5401
5402 for (var i = inputs.length - 1; i >= 0; i--) {
5403 input = inputs[i];
5404 layer = this._getLayer(input.layerId).layer;
5405
5406 if (input.checked) {
5407 addedLayers.push(layer);
5408 } else if (!input.checked) {
5409 removedLayers.push(layer);
5410 }
5411 }
5412
5413 // Bugfix issue 2318: Should remove all old layers before readding new ones
5414 for (i = 0; i < removedLayers.length; i++) {
5415 if (this._map.hasLayer(removedLayers[i])) {
5416 this._map.removeLayer(removedLayers[i]);
5417 }
5418 }
5419 for (i = 0; i < addedLayers.length; i++) {
5420 if (!this._map.hasLayer(addedLayers[i])) {
5421 this._map.addLayer(addedLayers[i]);
5422 }
5423 }
5424
5425 this._handlingClick = false;
5426
5427 this._refocusOnMap();
5428 },
5429
5430 _checkDisabledLayers: function () {
5431 var inputs = this._layerControlInputs,
5432 input,
5433 layer,
5434 zoom = this._map.getZoom();
5435
5436 for (var i = inputs.length - 1; i >= 0; i--) {
5437 input = inputs[i];
5438 layer = this._getLayer(input.layerId).layer;
5439 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
5440 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
5441
5442 }
5443 },
5444
5445 _expandIfNotCollapsed: function () {
5446 if (this._map && !this.options.collapsed) {
5447 this.expand();
5448 }
5449 return this;
5450 },
5451
5452 _expandSafely: function () {
5453 var section = this._section;
5454 this._preventClick = true;
5455 on(section, 'click', preventDefault);
5456 this.expand();
5457 var that = this;
5458 setTimeout(function () {
5459 off(section, 'click', preventDefault);
5460 that._preventClick = false;
5461 });
5462 }
5463
5464 });
5465
5466
5467 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
5468 // Creates a layers control with the given layers. Base layers will be switched with radio buttons, while overlays will be switched with checkboxes. Note that all base layers should be passed in the base layers object, but only one should be added to the map during map instantiation.
5469 var layers = function (baseLayers, overlays, options) {
5470 return new Layers(baseLayers, overlays, options);
5471 };
5472
5473 /*
5474 * @class Control.Zoom
5475 * @aka L.Control.Zoom
5476 * @inherits Control
5477 *
5478 * A basic zoom control with two buttons (zoom in and zoom out). It is put on the map by default unless you set its [`zoomControl` option](#map-zoomcontrol) to `false`. Extends `Control`.
5479 */
5480
5481 var Zoom = Control.extend({
5482 // @section
5483 // @aka Control.Zoom options
5484 options: {
5485 position: 'topleft',
5486
5487 // @option zoomInText: String = '<span aria-hidden="true">+</span>'
5488 // The text set on the 'zoom in' button.
5489 zoomInText: '<span aria-hidden="true">+</span>',
5490
5491 // @option zoomInTitle: String = 'Zoom in'
5492 // The title set on the 'zoom in' button.
5493 zoomInTitle: 'Zoom in',
5494
5495 // @option zoomOutText: String = '<span aria-hidden="true">&#x2212;</span>'
5496 // The text set on the 'zoom out' button.
5497 zoomOutText: '<span aria-hidden="true">&#x2212;</span>',
5498
5499 // @option zoomOutTitle: String = 'Zoom out'
5500 // The title set on the 'zoom out' button.
5501 zoomOutTitle: 'Zoom out'
5502 },
5503
5504 onAdd: function (map) {
5505 var zoomName = 'leaflet-control-zoom',
5506 container = create$1('div', zoomName + ' leaflet-bar'),
5507 options = this.options;
5508
5509 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle,
5510 zoomName + '-in', container, this._zoomIn);
5511 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
5512 zoomName + '-out', container, this._zoomOut);
5513
5514 this._updateDisabled();
5515 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
5516
5517 return container;
5518 },
5519
5520 onRemove: function (map) {
5521 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
5522 },
5523
5524 disable: function () {
5525 this._disabled = true;
5526 this._updateDisabled();
5527 return this;
5528 },
5529
5530 enable: function () {
5531 this._disabled = false;
5532 this._updateDisabled();
5533 return this;
5534 },
5535
5536 _zoomIn: function (e) {
5537 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
5538 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5539 }
5540 },
5541
5542 _zoomOut: function (e) {
5543 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
5544 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5545 }
5546 },
5547
5548 _createButton: function (html, title, className, container, fn) {
5549 var link = create$1('a', className, container);
5550 link.innerHTML = html;
5551 link.href = '#';
5552 link.title = title;
5553
5554 /*
5555 * Will force screen readers like VoiceOver to read this as "Zoom in - button"
5556 */
5557 link.setAttribute('role', 'button');
5558 link.setAttribute('aria-label', title);
5559
5560 disableClickPropagation(link);
5561 on(link, 'click', stop);
5562 on(link, 'click', fn, this);
5563 on(link, 'click', this._refocusOnMap, this);
5564
5565 return link;
5566 },
5567
5568 _updateDisabled: function () {
5569 var map = this._map,
5570 className = 'leaflet-disabled';
5571
5572 removeClass(this._zoomInButton, className);
5573 removeClass(this._zoomOutButton, className);
5574 this._zoomInButton.setAttribute('aria-disabled', 'false');
5575 this._zoomOutButton.setAttribute('aria-disabled', 'false');
5576
5577 if (this._disabled || map._zoom === map.getMinZoom()) {
5578 addClass(this._zoomOutButton, className);
5579 this._zoomOutButton.setAttribute('aria-disabled', 'true');
5580 }
5581 if (this._disabled || map._zoom === map.getMaxZoom()) {
5582 addClass(this._zoomInButton, className);
5583 this._zoomInButton.setAttribute('aria-disabled', 'true');
5584 }
5585 }
5586 });
5587
5588 // @namespace Map
5589 // @section Control options
5590 // @option zoomControl: Boolean = true
5591 // Whether a [zoom control](#control-zoom) is added to the map by default.
5592 Map.mergeOptions({
5593 zoomControl: true
5594 });
5595
5596 Map.addInitHook(function () {
5597 if (this.options.zoomControl) {
5598 // @section Controls
5599 // @property zoomControl: Control.Zoom
5600 // The default zoom control (only available if the
5601 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
5602 this.zoomControl = new Zoom();
5603 this.addControl(this.zoomControl);
5604 }
5605 });
5606
5607 // @namespace Control.Zoom
5608 // @factory L.control.zoom(options: Control.Zoom options)
5609 // Creates a zoom control
5610 var zoom = function (options) {
5611 return new Zoom(options);
5612 };
5613
5614 /*
5615 * @class Control.Scale
5616 * @aka L.Control.Scale
5617 * @inherits Control
5618 *
5619 * A simple scale control that shows the scale of the current center of screen in metric (m/km) and imperial (mi/ft) systems. Extends `Control`.
5620 *
5621 * @example
5622 *
5623 * ```js
5624 * L.control.scale().addTo(map);
5625 * ```
5626 */
5627
5628 var Scale = Control.extend({
5629 // @section
5630 // @aka Control.Scale options
5631 options: {
5632 position: 'bottomleft',
5633
5634 // @option maxWidth: Number = 100
5635 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
5636 maxWidth: 100,
5637
5638 // @option metric: Boolean = True
5639 // Whether to show the metric scale line (m/km).
5640 metric: true,
5641
5642 // @option imperial: Boolean = True
5643 // Whether to show the imperial scale line (mi/ft).
5644 imperial: true
5645
5646 // @option updateWhenIdle: Boolean = false
5647 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
5648 },
5649
5650 onAdd: function (map) {
5651 var className = 'leaflet-control-scale',
5652 container = create$1('div', className),
5653 options = this.options;
5654
5655 this._addScales(options, className + '-line', container);
5656
5657 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5658 map.whenReady(this._update, this);
5659
5660 return container;
5661 },
5662
5663 onRemove: function (map) {
5664 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5665 },
5666
5667 _addScales: function (options, className, container) {
5668 if (options.metric) {
5669 this._mScale = create$1('div', className, container);
5670 }
5671 if (options.imperial) {
5672 this._iScale = create$1('div', className, container);
5673 }
5674 },
5675
5676 _update: function () {
5677 var map = this._map,
5678 y = map.getSize().y / 2;
5679
5680 var maxMeters = map.distance(
5681 map.containerPointToLatLng([0, y]),
5682 map.containerPointToLatLng([this.options.maxWidth, y]));
5683
5684 this._updateScales(maxMeters);
5685 },
5686
5687 _updateScales: function (maxMeters) {
5688 if (this.options.metric && maxMeters) {
5689 this._updateMetric(maxMeters);
5690 }
5691 if (this.options.imperial && maxMeters) {
5692 this._updateImperial(maxMeters);
5693 }
5694 },
5695
5696 _updateMetric: function (maxMeters) {
5697 var meters = this._getRoundNum(maxMeters),
5698 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
5699
5700 this._updateScale(this._mScale, label, meters / maxMeters);
5701 },
5702
5703 _updateImperial: function (maxMeters) {
5704 var maxFeet = maxMeters * 3.2808399,
5705 maxMiles, miles, feet;
5706
5707 if (maxFeet > 5280) {
5708 maxMiles = maxFeet / 5280;
5709 miles = this._getRoundNum(maxMiles);
5710 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
5711
5712 } else {
5713 feet = this._getRoundNum(maxFeet);
5714 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
5715 }
5716 },
5717
5718 _updateScale: function (scale, text, ratio) {
5719 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
5720 scale.innerHTML = text;
5721 },
5722
5723 _getRoundNum: function (num) {
5724 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5725 d = num / pow10;
5726
5727 d = d >= 10 ? 10 :
5728 d >= 5 ? 5 :
5729 d >= 3 ? 3 :
5730 d >= 2 ? 2 : 1;
5731
5732 return pow10 * d;
5733 }
5734 });
5735
5736
5737 // @factory L.control.scale(options?: Control.Scale options)
5738 // Creates an scale control with the given options.
5739 var scale = function (options) {
5740 return new Scale(options);
5741 };
5742
5743 var ukrainianFlag = '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="12" height="8" viewBox="0 0 12 8" class="leaflet-attribution-flag"><path fill="#4C7BE1" d="M0 0h12v4H0z"/><path fill="#FFD500" d="M0 4h12v3H0z"/><path fill="#E0BC00" d="M0 7h12v1H0z"/></svg>';
5744
5745
5746 /*
5747 * @class Control.Attribution
5748 * @aka L.Control.Attribution
5749 * @inherits Control
5750 *
5751 * The attribution control allows you to display attribution data in a small text box on a map. It is put on the map by default unless you set its [`attributionControl` option](#map-attributioncontrol) to `false`, and it fetches attribution texts from layers with the [`getAttribution` method](#layer-getattribution) automatically. Extends Control.
5752 */
5753
5754 var Attribution = Control.extend({
5755 // @section
5756 // @aka Control.Attribution options
5757 options: {
5758 position: 'bottomright',
5759
5760 // @option prefix: String|false = 'Leaflet'
5761 // The HTML text shown before the attributions. Pass `false` to disable.
5762 prefix: '<a href="https://leafletjs.com" title="A JavaScript library for interactive maps">' + (Browser.inlineSvg ? ukrainianFlag + ' ' : '') + 'Leaflet</a>'
5763 },
5764
5765 initialize: function (options) {
5766 setOptions(this, options);
5767
5768 this._attributions = {};
5769 },
5770
5771 onAdd: function (map) {
5772 map.attributionControl = this;
5773 this._container = create$1('div', 'leaflet-control-attribution');
5774 disableClickPropagation(this._container);
5775
5776 // TODO ugly, refactor
5777 for (var i in map._layers) {
5778 if (map._layers[i].getAttribution) {
5779 this.addAttribution(map._layers[i].getAttribution());
5780 }
5781 }
5782
5783 this._update();
5784
5785 map.on('layeradd', this._addAttribution, this);
5786
5787 return this._container;
5788 },
5789
5790 onRemove: function (map) {
5791 map.off('layeradd', this._addAttribution, this);
5792 },
5793
5794 _addAttribution: function (ev) {
5795 if (ev.layer.getAttribution) {
5796 this.addAttribution(ev.layer.getAttribution());
5797 ev.layer.once('remove', function () {
5798 this.removeAttribution(ev.layer.getAttribution());
5799 }, this);
5800 }
5801 },
5802
5803 // @method setPrefix(prefix: String|false): this
5804 // The HTML text shown before the attributions. Pass `false` to disable.
5805 setPrefix: function (prefix) {
5806 this.options.prefix = prefix;
5807 this._update();
5808 return this;
5809 },
5810
5811 // @method addAttribution(text: String): this
5812 // Adds an attribution text (e.g. `'&copy; OpenStreetMap contributors'`).
5813 addAttribution: function (text) {
5814 if (!text) { return this; }
5815
5816 if (!this._attributions[text]) {
5817 this._attributions[text] = 0;
5818 }
5819 this._attributions[text]++;
5820
5821 this._update();
5822
5823 return this;
5824 },
5825
5826 // @method removeAttribution(text: String): this
5827 // Removes an attribution text.
5828 removeAttribution: function (text) {
5829 if (!text) { return this; }
5830
5831 if (this._attributions[text]) {
5832 this._attributions[text]--;
5833 this._update();
5834 }
5835
5836 return this;
5837 },
5838
5839 _update: function () {
5840 if (!this._map) { return; }
5841
5842 var attribs = [];
5843
5844 for (var i in this._attributions) {
5845 if (this._attributions[i]) {
5846 attribs.push(i);
5847 }
5848 }
5849
5850 var prefixAndAttribs = [];
5851
5852 if (this.options.prefix) {
5853 prefixAndAttribs.push(this.options.prefix);
5854 }
5855 if (attribs.length) {
5856 prefixAndAttribs.push(attribs.join(', '));
5857 }
5858
5859 this._container.innerHTML = prefixAndAttribs.join(' <span aria-hidden="true">|</span> ');
5860 }
5861 });
5862
5863 // @namespace Map
5864 // @section Control options
5865 // @option attributionControl: Boolean = true
5866 // Whether a [attribution control](#control-attribution) is added to the map by default.
5867 Map.mergeOptions({
5868 attributionControl: true
5869 });
5870
5871 Map.addInitHook(function () {
5872 if (this.options.attributionControl) {
5873 new Attribution().addTo(this);
5874 }
5875 });
5876
5877 // @namespace Control.Attribution
5878 // @factory L.control.attribution(options: Control.Attribution options)
5879 // Creates an attribution control.
5880 var attribution = function (options) {
5881 return new Attribution(options);
5882 };
5883
5884 Control.Layers = Layers;
5885 Control.Zoom = Zoom;
5886 Control.Scale = Scale;
5887 Control.Attribution = Attribution;
5888
5889 control.layers = layers;
5890 control.zoom = zoom;
5891 control.scale = scale;
5892 control.attribution = attribution;
5893
5894 /*
5895 L.Handler is a base class for handler classes that are used internally to inject
5896 interaction features like dragging to classes like Map and Marker.
5897 */
5898
5899 // @class Handler
5900 // @aka L.Handler
5901 // Abstract class for map interaction handlers
5902
5903 var Handler = Class.extend({
5904 initialize: function (map) {
5905 this._map = map;
5906 },
5907
5908 // @method enable(): this
5909 // Enables the handler
5910 enable: function () {
5911 if (this._enabled) { return this; }
5912
5913 this._enabled = true;
5914 this.addHooks();
5915 return this;
5916 },
5917
5918 // @method disable(): this
5919 // Disables the handler
5920 disable: function () {
5921 if (!this._enabled) { return this; }
5922
5923 this._enabled = false;
5924 this.removeHooks();
5925 return this;
5926 },
5927
5928 // @method enabled(): Boolean
5929 // Returns `true` if the handler is enabled
5930 enabled: function () {
5931 return !!this._enabled;
5932 }
5933
5934 // @section Extension methods
5935 // Classes inheriting from `Handler` must implement the two following methods:
5936 // @method addHooks()
5937 // Called when the handler is enabled, should add event hooks.
5938 // @method removeHooks()
5939 // Called when the handler is disabled, should remove the event hooks added previously.
5940 });
5941
5942 // @section There is static function which can be called without instantiating L.Handler:
5943 // @function addTo(map: Map, name: String): this
5944 // Adds a new Handler to the given map with the given name.
5945 Handler.addTo = function (map, name) {
5946 map.addHandler(name, this);
5947 return this;
5948 };
5949
5950 var Mixin = {Events: Events};
5951
5952 /*
5953 * @class Draggable
5954 * @aka L.Draggable
5955 * @inherits Evented
5956 *
5957 * A class for making DOM elements draggable (including touch support).
5958 * Used internally for map and marker dragging. Only works for elements
5959 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
5960 *
5961 * @example
5962 * ```js
5963 * var draggable = new L.Draggable(elementToDrag);
5964 * draggable.enable();
5965 * ```
5966 */
5967
5968 var START = Browser.touch ? 'touchstart mousedown' : 'mousedown';
5969
5970 var Draggable = Evented.extend({
5971
5972 options: {
5973 // @section
5974 // @aka Draggable options
5975 // @option clickTolerance: Number = 3
5976 // The max number of pixels a user can shift the mouse pointer during a click
5977 // for it to be considered a valid click (as opposed to a mouse drag).
5978 clickTolerance: 3
5979 },
5980
5981 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
5982 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
5983 initialize: function (element, dragStartTarget, preventOutline, options) {
5984 setOptions(this, options);
5985
5986 this._element = element;
5987 this._dragStartTarget = dragStartTarget || element;
5988 this._preventOutline = preventOutline;
5989 },
5990
5991 // @method enable()
5992 // Enables the dragging ability
5993 enable: function () {
5994 if (this._enabled) { return; }
5995
5996 on(this._dragStartTarget, START, this._onDown, this);
5997
5998 this._enabled = true;
5999 },
6000
6001 // @method disable()
6002 // Disables the dragging ability
6003 disable: function () {
6004 if (!this._enabled) { return; }
6005
6006 // If we're currently dragging this draggable,
6007 // disabling it counts as first ending the drag.
6008 if (Draggable._dragging === this) {
6009 this.finishDrag(true);
6010 }
6011
6012 off(this._dragStartTarget, START, this._onDown, this);
6013
6014 this._enabled = false;
6015 this._moved = false;
6016 },
6017
6018 _onDown: function (e) {
6019 // Ignore the event if disabled; this happens in IE11
6020 // under some circumstances, see #3666.
6021 if (!this._enabled) { return; }
6022
6023 this._moved = false;
6024
6025 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
6026
6027 if (e.touches && e.touches.length !== 1) {
6028 // Finish dragging to avoid conflict with touchZoom
6029 if (Draggable._dragging === this) {
6030 this.finishDrag();
6031 }
6032 return;
6033 }
6034
6035 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
6036 Draggable._dragging = this; // Prevent dragging multiple objects at once.
6037
6038 if (this._preventOutline) {
6039 preventOutline(this._element);
6040 }
6041
6042 disableImageDrag();
6043 disableTextSelection();
6044
6045 if (this._moving) { return; }
6046
6047 // @event down: Event
6048 // Fired when a drag is about to start.
6049 this.fire('down');
6050
6051 var first = e.touches ? e.touches[0] : e,
6052 sizedParent = getSizedParentNode(this._element);
6053
6054 this._startPoint = new Point(first.clientX, first.clientY);
6055 this._startPos = getPosition(this._element);
6056
6057 // Cache the scale, so that we can continuously compensate for it during drag (_onMove).
6058 this._parentScale = getScale(sizedParent);
6059
6060 var mouseevent = e.type === 'mousedown';
6061 on(document, mouseevent ? 'mousemove' : 'touchmove', this._onMove, this);
6062 on(document, mouseevent ? 'mouseup' : 'touchend touchcancel', this._onUp, this);
6063 },
6064
6065 _onMove: function (e) {
6066 // Ignore the event if disabled; this happens in IE11
6067 // under some circumstances, see #3666.
6068 if (!this._enabled) { return; }
6069
6070 if (e.touches && e.touches.length > 1) {
6071 this._moved = true;
6072 return;
6073 }
6074
6075 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
6076 offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
6077
6078 if (!offset.x && !offset.y) { return; }
6079 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
6080
6081 // We assume that the parent container's position, border and scale do not change for the duration of the drag.
6082 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
6083 // and we can use the cached value for the scale.
6084 offset.x /= this._parentScale.x;
6085 offset.y /= this._parentScale.y;
6086
6087 preventDefault(e);
6088
6089 if (!this._moved) {
6090 // @event dragstart: Event
6091 // Fired when a drag starts
6092 this.fire('dragstart');
6093
6094 this._moved = true;
6095
6096 addClass(document.body, 'leaflet-dragging');
6097
6098 this._lastTarget = e.target || e.srcElement;
6099 // IE and Edge do not give the <use> element, so fetch it
6100 // if necessary
6101 if (window.SVGElementInstance && this._lastTarget instanceof window.SVGElementInstance) {
6102 this._lastTarget = this._lastTarget.correspondingUseElement;
6103 }
6104 addClass(this._lastTarget, 'leaflet-drag-target');
6105 }
6106
6107 this._newPos = this._startPos.add(offset);
6108 this._moving = true;
6109
6110 this._lastEvent = e;
6111 this._updatePosition();
6112 },
6113
6114 _updatePosition: function () {
6115 var e = {originalEvent: this._lastEvent};
6116
6117 // @event predrag: Event
6118 // Fired continuously during dragging *before* each corresponding
6119 // update of the element's position.
6120 this.fire('predrag', e);
6121 setPosition(this._element, this._newPos);
6122
6123 // @event drag: Event
6124 // Fired continuously during dragging.
6125 this.fire('drag', e);
6126 },
6127
6128 _onUp: function () {
6129 // Ignore the event if disabled; this happens in IE11
6130 // under some circumstances, see #3666.
6131 if (!this._enabled) { return; }
6132 this.finishDrag();
6133 },
6134
6135 finishDrag: function (noInertia) {
6136 removeClass(document.body, 'leaflet-dragging');
6137
6138 if (this._lastTarget) {
6139 removeClass(this._lastTarget, 'leaflet-drag-target');
6140 this._lastTarget = null;
6141 }
6142
6143 off(document, 'mousemove touchmove', this._onMove, this);
6144 off(document, 'mouseup touchend touchcancel', this._onUp, this);
6145
6146 enableImageDrag();
6147 enableTextSelection();
6148
6149 var fireDragend = this._moved && this._moving;
6150
6151 this._moving = false;
6152 Draggable._dragging = false;
6153
6154 if (fireDragend) {
6155 // @event dragend: DragEndEvent
6156 // Fired when the drag ends.
6157 this.fire('dragend', {
6158 noInertia: noInertia,
6159 distance: this._newPos.distanceTo(this._startPos)
6160 });
6161 }
6162 }
6163
6164 });
6165
6166 /*
6167 * @namespace PolyUtil
6168 * Various utility functions for polygon geometries.
6169 */
6170
6171 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
6172 * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)).
6173 * Used by Leaflet to only show polygon points that are on the screen or near, increasing
6174 * performance. Note that polygon points needs different algorithm for clipping
6175 * than polyline, so there's a separate method for it.
6176 */
6177 function clipPolygon(points, bounds, round) {
6178 var clippedPoints,
6179 edges = [1, 4, 2, 8],
6180 i, j, k,
6181 a, b,
6182 len, edge, p;
6183
6184 for (i = 0, len = points.length; i < len; i++) {
6185 points[i]._code = _getBitCode(points[i], bounds);
6186 }
6187
6188 // for each edge (left, bottom, right, top)
6189 for (k = 0; k < 4; k++) {
6190 edge = edges[k];
6191 clippedPoints = [];
6192
6193 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
6194 a = points[i];
6195 b = points[j];
6196
6197 // if a is inside the clip window
6198 if (!(a._code & edge)) {
6199 // if b is outside the clip window (a->b goes out of screen)
6200 if (b._code & edge) {
6201 p = _getEdgeIntersection(b, a, edge, bounds, round);
6202 p._code = _getBitCode(p, bounds);
6203 clippedPoints.push(p);
6204 }
6205 clippedPoints.push(a);
6206
6207 // else if b is inside the clip window (a->b enters the screen)
6208 } else if (!(b._code & edge)) {
6209 p = _getEdgeIntersection(b, a, edge, bounds, round);
6210 p._code = _getBitCode(p, bounds);
6211 clippedPoints.push(p);
6212 }
6213 }
6214 points = clippedPoints;
6215 }
6216
6217 return points;
6218 }
6219
6220 /* @function polygonCenter(latlngs: LatLng[], crs: CRS): LatLng
6221 * Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the passed LatLngs (first ring) from a polygon.
6222 */
6223 function polygonCenter(latlngs, crs) {
6224 var i, j, p1, p2, f, area, x, y, center;
6225
6226 if (!latlngs || latlngs.length === 0) {
6227 throw new Error('latlngs not passed');
6228 }
6229
6230 if (!isFlat(latlngs)) {
6231 console.warn('latlngs are not flat! Only the first ring will be used');
6232 latlngs = latlngs[0];
6233 }
6234
6235 var centroidLatLng = toLatLng([0, 0]);
6236
6237 var bounds = toLatLngBounds(latlngs);
6238 var areaBounds = bounds.getNorthWest().distanceTo(bounds.getSouthWest()) * bounds.getNorthEast().distanceTo(bounds.getNorthWest());
6239 // tests showed that below 1700 rounding errors are happening
6240 if (areaBounds < 1700) {
6241 // getting a inexact center, to move the latlngs near to [0, 0] to prevent rounding errors
6242 centroidLatLng = centroid(latlngs);
6243 }
6244
6245 var len = latlngs.length;
6246 var points = [];
6247 for (i = 0; i < len; i++) {
6248 var latlng = toLatLng(latlngs[i]);
6249 points.push(crs.project(toLatLng([latlng.lat - centroidLatLng.lat, latlng.lng - centroidLatLng.lng])));
6250 }
6251
6252 area = x = y = 0;
6253
6254 // polygon centroid algorithm;
6255 for (i = 0, j = len - 1; i < len; j = i++) {
6256 p1 = points[i];
6257 p2 = points[j];
6258
6259 f = p1.y * p2.x - p2.y * p1.x;
6260 x += (p1.x + p2.x) * f;
6261 y += (p1.y + p2.y) * f;
6262 area += f * 3;
6263 }
6264
6265 if (area === 0) {
6266 // Polygon is so small that all points are on same pixel.
6267 center = points[0];
6268 } else {
6269 center = [x / area, y / area];
6270 }
6271
6272 var latlngCenter = crs.unproject(toPoint(center));
6273 return toLatLng([latlngCenter.lat + centroidLatLng.lat, latlngCenter.lng + centroidLatLng.lng]);
6274 }
6275
6276 /* @function centroid(latlngs: LatLng[]): LatLng
6277 * Returns the 'center of mass' of the passed LatLngs.
6278 */
6279 function centroid(coords) {
6280 var latSum = 0;
6281 var lngSum = 0;
6282 var len = 0;
6283 for (var i = 0; i < coords.length; i++) {
6284 var latlng = toLatLng(coords[i]);
6285 latSum += latlng.lat;
6286 lngSum += latlng.lng;
6287 len++;
6288 }
6289 return toLatLng([latSum / len, lngSum / len]);
6290 }
6291
6292 var PolyUtil = {
6293 __proto__: null,
6294 clipPolygon: clipPolygon,
6295 polygonCenter: polygonCenter,
6296 centroid: centroid
6297 };
6298
6299 /*
6300 * @namespace LineUtil
6301 *
6302 * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
6303 */
6304
6305 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
6306 // Improves rendering performance dramatically by lessening the number of points to draw.
6307
6308 // @function simplify(points: Point[], tolerance: Number): Point[]
6309 // Dramatically reduces the number of points in a polyline while retaining
6310 // its shape and returns a new array of simplified points, using the
6311 // [Ramer-Douglas-Peucker algorithm](https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm).
6312 // Used for a huge performance boost when processing/displaying Leaflet polylines for
6313 // each zoom level and also reducing visual noise. tolerance affects the amount of
6314 // simplification (lesser value means higher quality but slower and with more points).
6315 // Also released as a separated micro-library [Simplify.js](https://mourner.github.io/simplify-js/).
6316 function simplify(points, tolerance) {
6317 if (!tolerance || !points.length) {
6318 return points.slice();
6319 }
6320
6321 var sqTolerance = tolerance * tolerance;
6322
6323 // stage 1: vertex reduction
6324 points = _reducePoints(points, sqTolerance);
6325
6326 // stage 2: Douglas-Peucker simplification
6327 points = _simplifyDP(points, sqTolerance);
6328
6329 return points;
6330 }
6331
6332 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
6333 // Returns the distance between point `p` and segment `p1` to `p2`.
6334 function pointToSegmentDistance(p, p1, p2) {
6335 return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
6336 }
6337
6338 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
6339 // Returns the closest point from a point `p` on a segment `p1` to `p2`.
6340 function closestPointOnSegment(p, p1, p2) {
6341 return _sqClosestPointOnSegment(p, p1, p2);
6342 }
6343
6344 // Ramer-Douglas-Peucker simplification, see https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
6345 function _simplifyDP(points, sqTolerance) {
6346
6347 var len = points.length,
6348 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
6349 markers = new ArrayConstructor(len);
6350
6351 markers[0] = markers[len - 1] = 1;
6352
6353 _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
6354
6355 var i,
6356 newPoints = [];
6357
6358 for (i = 0; i < len; i++) {
6359 if (markers[i]) {
6360 newPoints.push(points[i]);
6361 }
6362 }
6363
6364 return newPoints;
6365 }
6366
6367 function _simplifyDPStep(points, markers, sqTolerance, first, last) {
6368
6369 var maxSqDist = 0,
6370 index, i, sqDist;
6371
6372 for (i = first + 1; i <= last - 1; i++) {
6373 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
6374
6375 if (sqDist > maxSqDist) {
6376 index = i;
6377 maxSqDist = sqDist;
6378 }
6379 }
6380
6381 if (maxSqDist > sqTolerance) {
6382 markers[index] = 1;
6383
6384 _simplifyDPStep(points, markers, sqTolerance, first, index);
6385 _simplifyDPStep(points, markers, sqTolerance, index, last);
6386 }
6387 }
6388
6389 // reduce points that are too close to each other to a single point
6390 function _reducePoints(points, sqTolerance) {
6391 var reducedPoints = [points[0]];
6392
6393 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
6394 if (_sqDist(points[i], points[prev]) > sqTolerance) {
6395 reducedPoints.push(points[i]);
6396 prev = i;
6397 }
6398 }
6399 if (prev < len - 1) {
6400 reducedPoints.push(points[len - 1]);
6401 }
6402 return reducedPoints;
6403 }
6404
6405 var _lastCode;
6406
6407 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
6408 // Clips the segment a to b by rectangular bounds with the
6409 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
6410 // (modifying the segment points directly!). Used by Leaflet to only show polyline
6411 // points that are on the screen or near, increasing performance.
6412 function clipSegment(a, b, bounds, useLastCode, round) {
6413 var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
6414 codeB = _getBitCode(b, bounds),
6415
6416 codeOut, p, newCode;
6417
6418 // save 2nd code to avoid calculating it on the next segment
6419 _lastCode = codeB;
6420
6421 while (true) {
6422 // if a,b is inside the clip window (trivial accept)
6423 if (!(codeA | codeB)) {
6424 return [a, b];
6425 }
6426
6427 // if a,b is outside the clip window (trivial reject)
6428 if (codeA & codeB) {
6429 return false;
6430 }
6431
6432 // other cases
6433 codeOut = codeA || codeB;
6434 p = _getEdgeIntersection(a, b, codeOut, bounds, round);
6435 newCode = _getBitCode(p, bounds);
6436
6437 if (codeOut === codeA) {
6438 a = p;
6439 codeA = newCode;
6440 } else {
6441 b = p;
6442 codeB = newCode;
6443 }
6444 }
6445 }
6446
6447 function _getEdgeIntersection(a, b, code, bounds, round) {
6448 var dx = b.x - a.x,
6449 dy = b.y - a.y,
6450 min = bounds.min,
6451 max = bounds.max,
6452 x, y;
6453
6454 if (code & 8) { // top
6455 x = a.x + dx * (max.y - a.y) / dy;
6456 y = max.y;
6457
6458 } else if (code & 4) { // bottom
6459 x = a.x + dx * (min.y - a.y) / dy;
6460 y = min.y;
6461
6462 } else if (code & 2) { // right
6463 x = max.x;
6464 y = a.y + dy * (max.x - a.x) / dx;
6465
6466 } else if (code & 1) { // left
6467 x = min.x;
6468 y = a.y + dy * (min.x - a.x) / dx;
6469 }
6470
6471 return new Point(x, y, round);
6472 }
6473
6474 function _getBitCode(p, bounds) {
6475 var code = 0;
6476
6477 if (p.x < bounds.min.x) { // left
6478 code |= 1;
6479 } else if (p.x > bounds.max.x) { // right
6480 code |= 2;
6481 }
6482
6483 if (p.y < bounds.min.y) { // bottom
6484 code |= 4;
6485 } else if (p.y > bounds.max.y) { // top
6486 code |= 8;
6487 }
6488
6489 return code;
6490 }
6491
6492 // square distance (to avoid unnecessary Math.sqrt calls)
6493 function _sqDist(p1, p2) {
6494 var dx = p2.x - p1.x,
6495 dy = p2.y - p1.y;
6496 return dx * dx + dy * dy;
6497 }
6498
6499 // return closest point on segment or distance to that point
6500 function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
6501 var x = p1.x,
6502 y = p1.y,
6503 dx = p2.x - x,
6504 dy = p2.y - y,
6505 dot = dx * dx + dy * dy,
6506 t;
6507
6508 if (dot > 0) {
6509 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
6510
6511 if (t > 1) {
6512 x = p2.x;
6513 y = p2.y;
6514 } else if (t > 0) {
6515 x += dx * t;
6516 y += dy * t;
6517 }
6518 }
6519
6520 dx = p.x - x;
6521 dy = p.y - y;
6522
6523 return sqDist ? dx * dx + dy * dy : new Point(x, y);
6524 }
6525
6526
6527 // @function isFlat(latlngs: LatLng[]): Boolean
6528 // Returns true if `latlngs` is a flat array, false is nested.
6529 function isFlat(latlngs) {
6530 return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
6531 }
6532
6533 function _flat(latlngs) {
6534 console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
6535 return isFlat(latlngs);
6536 }
6537
6538 /* @function polylineCenter(latlngs: LatLng[], crs: CRS): LatLng
6539 * Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the passed LatLngs (first ring) from a polyline.
6540 */
6541 function polylineCenter(latlngs, crs) {
6542 var i, halfDist, segDist, dist, p1, p2, ratio, center;
6543
6544 if (!latlngs || latlngs.length === 0) {
6545 throw new Error('latlngs not passed');
6546 }
6547
6548 if (!isFlat(latlngs)) {
6549 console.warn('latlngs are not flat! Only the first ring will be used');
6550 latlngs = latlngs[0];
6551 }
6552
6553 var centroidLatLng = toLatLng([0, 0]);
6554
6555 var bounds = toLatLngBounds(latlngs);
6556 var areaBounds = bounds.getNorthWest().distanceTo(bounds.getSouthWest()) * bounds.getNorthEast().distanceTo(bounds.getNorthWest());
6557 // tests showed that below 1700 rounding errors are happening
6558 if (areaBounds < 1700) {
6559 // getting a inexact center, to move the latlngs near to [0, 0] to prevent rounding errors
6560 centroidLatLng = centroid(latlngs);
6561 }
6562
6563 var len = latlngs.length;
6564 var points = [];
6565 for (i = 0; i < len; i++) {
6566 var latlng = toLatLng(latlngs[i]);
6567 points.push(crs.project(toLatLng([latlng.lat - centroidLatLng.lat, latlng.lng - centroidLatLng.lng])));
6568 }
6569
6570 for (i = 0, halfDist = 0; i < len - 1; i++) {
6571 halfDist += points[i].distanceTo(points[i + 1]) / 2;
6572 }
6573
6574 // The line is so small in the current view that all points are on the same pixel.
6575 if (halfDist === 0) {
6576 center = points[0];
6577 } else {
6578 for (i = 0, dist = 0; i < len - 1; i++) {
6579 p1 = points[i];
6580 p2 = points[i + 1];
6581 segDist = p1.distanceTo(p2);
6582 dist += segDist;
6583
6584 if (dist > halfDist) {
6585 ratio = (dist - halfDist) / segDist;
6586 center = [
6587 p2.x - ratio * (p2.x - p1.x),
6588 p2.y - ratio * (p2.y - p1.y)
6589 ];
6590 break;
6591 }
6592 }
6593 }
6594
6595 var latlngCenter = crs.unproject(toPoint(center));
6596 return toLatLng([latlngCenter.lat + centroidLatLng.lat, latlngCenter.lng + centroidLatLng.lng]);
6597 }
6598
6599 var LineUtil = {
6600 __proto__: null,
6601 simplify: simplify,
6602 pointToSegmentDistance: pointToSegmentDistance,
6603 closestPointOnSegment: closestPointOnSegment,
6604 clipSegment: clipSegment,
6605 _getEdgeIntersection: _getEdgeIntersection,
6606 _getBitCode: _getBitCode,
6607 _sqClosestPointOnSegment: _sqClosestPointOnSegment,
6608 isFlat: isFlat,
6609 _flat: _flat,
6610 polylineCenter: polylineCenter
6611 };
6612
6613 /*
6614 * @namespace Projection
6615 * @section
6616 * Leaflet comes with a set of already defined Projections out of the box:
6617 *
6618 * @projection L.Projection.LonLat
6619 *
6620 * Equirectangular, or Plate Carree projection — the most simple projection,
6621 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
6622 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
6623 * `EPSG:4326` and `Simple` CRS.
6624 */
6625
6626 var LonLat = {
6627 project: function (latlng) {
6628 return new Point(latlng.lng, latlng.lat);
6629 },
6630
6631 unproject: function (point) {
6632 return new LatLng(point.y, point.x);
6633 },
6634
6635 bounds: new Bounds([-180, -90], [180, 90])
6636 };
6637
6638 /*
6639 * @namespace Projection
6640 * @projection L.Projection.Mercator
6641 *
6642 * Elliptical Mercator projection — more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS.
6643 */
6644
6645 var Mercator = {
6646 R: 6378137,
6647 R_MINOR: 6356752.314245179,
6648
6649 bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
6650
6651 project: function (latlng) {
6652 var d = Math.PI / 180,
6653 r = this.R,
6654 y = latlng.lat * d,
6655 tmp = this.R_MINOR / r,
6656 e = Math.sqrt(1 - tmp * tmp),
6657 con = e * Math.sin(y);
6658
6659 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
6660 y = -r * Math.log(Math.max(ts, 1E-10));
6661
6662 return new Point(latlng.lng * d * r, y);
6663 },
6664
6665 unproject: function (point) {
6666 var d = 180 / Math.PI,
6667 r = this.R,
6668 tmp = this.R_MINOR / r,
6669 e = Math.sqrt(1 - tmp * tmp),
6670 ts = Math.exp(-point.y / r),
6671 phi = Math.PI / 2 - 2 * Math.atan(ts);
6672
6673 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
6674 con = e * Math.sin(phi);
6675 con = Math.pow((1 - con) / (1 + con), e / 2);
6676 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
6677 phi += dphi;
6678 }
6679
6680 return new LatLng(phi * d, point.x * d / r);
6681 }
6682 };
6683
6684 /*
6685 * @class Projection
6686
6687 * An object with methods for projecting geographical coordinates of the world onto
6688 * a flat surface (and back). See [Map projection](https://en.wikipedia.org/wiki/Map_projection).
6689
6690 * @property bounds: Bounds
6691 * The bounds (specified in CRS units) where the projection is valid
6692
6693 * @method project(latlng: LatLng): Point
6694 * Projects geographical coordinates into a 2D point.
6695 * Only accepts actual `L.LatLng` instances, not arrays.
6696
6697 * @method unproject(point: Point): LatLng
6698 * The inverse of `project`. Projects a 2D point into a geographical location.
6699 * Only accepts actual `L.Point` instances, not arrays.
6700
6701 * Note that the projection instances do not inherit from Leaflet's `Class` object,
6702 * and can't be instantiated. Also, new classes can't inherit from them,
6703 * and methods can't be added to them with the `include` function.
6704
6705 */
6706
6707 var index = {
6708 __proto__: null,
6709 LonLat: LonLat,
6710 Mercator: Mercator,
6711 SphericalMercator: SphericalMercator
6712 };
6713
6714 /*
6715 * @namespace CRS
6716 * @crs L.CRS.EPSG3395
6717 *
6718 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
6719 */
6720 var EPSG3395 = extend({}, Earth, {
6721 code: 'EPSG:3395',
6722 projection: Mercator,
6723
6724 transformation: (function () {
6725 var scale = 0.5 / (Math.PI * Mercator.R);
6726 return toTransformation(scale, 0.5, -scale, 0.5);
6727 }())
6728 });
6729
6730 /*
6731 * @namespace CRS
6732 * @crs L.CRS.EPSG4326
6733 *
6734 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
6735 *
6736 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
6737 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer`
6738 * with this CRS, ensure that there are two 256x256 pixel tiles covering the
6739 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
6740 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
6741 */
6742
6743 var EPSG4326 = extend({}, Earth, {
6744 code: 'EPSG:4326',
6745 projection: LonLat,
6746 transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
6747 });
6748
6749 /*
6750 * @namespace CRS
6751 * @crs L.CRS.Simple
6752 *
6753 * A simple CRS that maps longitude and latitude into `x` and `y` directly.
6754 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
6755 * axis should still be inverted (going from bottom to top). `distance()` returns
6756 * simple euclidean distance.
6757 */
6758
6759 var Simple = extend({}, CRS, {
6760 projection: LonLat,
6761 transformation: toTransformation(1, 0, -1, 0),
6762
6763 scale: function (zoom) {
6764 return Math.pow(2, zoom);
6765 },
6766
6767 zoom: function (scale) {
6768 return Math.log(scale) / Math.LN2;
6769 },
6770
6771 distance: function (latlng1, latlng2) {
6772 var dx = latlng2.lng - latlng1.lng,
6773 dy = latlng2.lat - latlng1.lat;
6774
6775 return Math.sqrt(dx * dx + dy * dy);
6776 },
6777
6778 infinite: true
6779 });
6780
6781 CRS.Earth = Earth;
6782 CRS.EPSG3395 = EPSG3395;
6783 CRS.EPSG3857 = EPSG3857;
6784 CRS.EPSG900913 = EPSG900913;
6785 CRS.EPSG4326 = EPSG4326;
6786 CRS.Simple = Simple;
6787
6788 /*
6789 * @class Layer
6790 * @inherits Evented
6791 * @aka L.Layer
6792 * @aka ILayer
6793 *
6794 * A set of methods from the Layer base class that all Leaflet layers use.
6795 * Inherits all methods, options and events from `L.Evented`.
6796 *
6797 * @example
6798 *
6799 * ```js
6800 * var layer = L.marker(latlng).addTo(map);
6801 * layer.addTo(map);
6802 * layer.remove();
6803 * ```
6804 *
6805 * @event add: Event
6806 * Fired after the layer is added to a map
6807 *
6808 * @event remove: Event
6809 * Fired after the layer is removed from a map
6810 */
6811
6812
6813 var Layer = Evented.extend({
6814
6815 // Classes extending `L.Layer` will inherit the following options:
6816 options: {
6817 // @option pane: String = 'overlayPane'
6818 // By default the layer will be added to the map's [overlay pane](#map-overlaypane). Overriding this option will cause the layer to be placed on another pane by default.
6819 pane: 'overlayPane',
6820
6821 // @option attribution: String = null
6822 // String to be shown in the attribution control, e.g. "© OpenStreetMap contributors". It describes the layer data and is often a legal obligation towards copyright holders and tile providers.
6823 attribution: null,
6824
6825 bubblingMouseEvents: true
6826 },
6827
6828 /* @section
6829 * Classes extending `L.Layer` will inherit the following methods:
6830 *
6831 * @method addTo(map: Map|LayerGroup): this
6832 * Adds the layer to the given map or layer group.
6833 */
6834 addTo: function (map) {
6835 map.addLayer(this);
6836 return this;
6837 },
6838
6839 // @method remove: this
6840 // Removes the layer from the map it is currently active on.
6841 remove: function () {
6842 return this.removeFrom(this._map || this._mapToAdd);
6843 },
6844
6845 // @method removeFrom(map: Map): this
6846 // Removes the layer from the given map
6847 //
6848 // @alternative
6849 // @method removeFrom(group: LayerGroup): this
6850 // Removes the layer from the given `LayerGroup`
6851 removeFrom: function (obj) {
6852 if (obj) {
6853 obj.removeLayer(this);
6854 }
6855 return this;
6856 },
6857
6858 // @method getPane(name? : String): HTMLElement
6859 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
6860 getPane: function (name) {
6861 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
6862 },
6863
6864 addInteractiveTarget: function (targetEl) {
6865 this._map._targets[stamp(targetEl)] = this;
6866 return this;
6867 },
6868
6869 removeInteractiveTarget: function (targetEl) {
6870 delete this._map._targets[stamp(targetEl)];
6871 return this;
6872 },
6873
6874 // @method getAttribution: String
6875 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
6876 getAttribution: function () {
6877 return this.options.attribution;
6878 },
6879
6880 _layerAdd: function (e) {
6881 var map = e.target;
6882
6883 // check in case layer gets added and then removed before the map is ready
6884 if (!map.hasLayer(this)) { return; }
6885
6886 this._map = map;
6887 this._zoomAnimated = map._zoomAnimated;
6888
6889 if (this.getEvents) {
6890 var events = this.getEvents();
6891 map.on(events, this);
6892 this.once('remove', function () {
6893 map.off(events, this);
6894 }, this);
6895 }
6896
6897 this.onAdd(map);
6898
6899 this.fire('add');
6900 map.fire('layeradd', {layer: this});
6901 }
6902 });
6903
6904 /* @section Extension methods
6905 * @uninheritable
6906 *
6907 * Every layer should extend from `L.Layer` and (re-)implement the following methods.
6908 *
6909 * @method onAdd(map: Map): this
6910 * Should contain code that creates DOM elements for the layer, adds them to `map panes` where they should belong and puts listeners on relevant map events. Called on [`map.addLayer(layer)`](#map-addlayer).
6911 *
6912 * @method onRemove(map: Map): this
6913 * Should contain all clean up code that removes the layer's elements from the DOM and removes listeners previously added in [`onAdd`](#layer-onadd). Called on [`map.removeLayer(layer)`](#map-removelayer).
6914 *
6915 * @method getEvents(): Object
6916 * This optional method should return an object like `{ viewreset: this._reset }` for [`addEventListener`](#evented-addeventlistener). The event handlers in this object will be automatically added and removed from the map with your layer.
6917 *
6918 * @method getAttribution(): String
6919 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
6920 *
6921 * @method beforeAdd(map: Map): this
6922 * Optional method. Called on [`map.addLayer(layer)`](#map-addlayer), before the layer is added to the map, before events are initialized, without waiting until the map is in a usable state. Use for early initialization only.
6923 */
6924
6925
6926 /* @namespace Map
6927 * @section Layer events
6928 *
6929 * @event layeradd: LayerEvent
6930 * Fired when a new layer is added to the map.
6931 *
6932 * @event layerremove: LayerEvent
6933 * Fired when some layer is removed from the map
6934 *
6935 * @section Methods for Layers and Controls
6936 */
6937 Map.include({
6938 // @method addLayer(layer: Layer): this
6939 // Adds the given layer to the map
6940 addLayer: function (layer) {
6941 if (!layer._layerAdd) {
6942 throw new Error('The provided object is not a Layer.');
6943 }
6944
6945 var id = stamp(layer);
6946 if (this._layers[id]) { return this; }
6947 this._layers[id] = layer;
6948
6949 layer._mapToAdd = this;
6950
6951 if (layer.beforeAdd) {
6952 layer.beforeAdd(this);
6953 }
6954
6955 this.whenReady(layer._layerAdd, layer);
6956
6957 return this;
6958 },
6959
6960 // @method removeLayer(layer: Layer): this
6961 // Removes the given layer from the map.
6962 removeLayer: function (layer) {
6963 var id = stamp(layer);
6964
6965 if (!this._layers[id]) { return this; }
6966
6967 if (this._loaded) {
6968 layer.onRemove(this);
6969 }
6970
6971 delete this._layers[id];
6972
6973 if (this._loaded) {
6974 this.fire('layerremove', {layer: layer});
6975 layer.fire('remove');
6976 }
6977
6978 layer._map = layer._mapToAdd = null;
6979
6980 return this;
6981 },
6982
6983 // @method hasLayer(layer: Layer): Boolean
6984 // Returns `true` if the given layer is currently added to the map
6985 hasLayer: function (layer) {
6986 return stamp(layer) in this._layers;
6987 },
6988
6989 /* @method eachLayer(fn: Function, context?: Object): this
6990 * Iterates over the layers of the map, optionally specifying context of the iterator function.
6991 * ```
6992 * map.eachLayer(function(layer){
6993 * layer.bindPopup('Hello');
6994 * });
6995 * ```
6996 */
6997 eachLayer: function (method, context) {
6998 for (var i in this._layers) {
6999 method.call(context, this._layers[i]);
7000 }
7001 return this;
7002 },
7003
7004 _addLayers: function (layers) {
7005 layers = layers ? (isArray(layers) ? layers : [layers]) : [];
7006
7007 for (var i = 0, len = layers.length; i < len; i++) {
7008 this.addLayer(layers[i]);
7009 }
7010 },
7011
7012 _addZoomLimit: function (layer) {
7013 if (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
7014 this._zoomBoundLayers[stamp(layer)] = layer;
7015 this._updateZoomLevels();
7016 }
7017 },
7018
7019 _removeZoomLimit: function (layer) {
7020 var id = stamp(layer);
7021
7022 if (this._zoomBoundLayers[id]) {
7023 delete this._zoomBoundLayers[id];
7024 this._updateZoomLevels();
7025 }
7026 },
7027
7028 _updateZoomLevels: function () {
7029 var minZoom = Infinity,
7030 maxZoom = -Infinity,
7031 oldZoomSpan = this._getZoomSpan();
7032
7033 for (var i in this._zoomBoundLayers) {
7034 var options = this._zoomBoundLayers[i].options;
7035
7036 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
7037 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
7038 }
7039
7040 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
7041 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
7042
7043 // @section Map state change events
7044 // @event zoomlevelschange: Event
7045 // Fired when the number of zoomlevels on the map is changed due
7046 // to adding or removing a layer.
7047 if (oldZoomSpan !== this._getZoomSpan()) {
7048 this.fire('zoomlevelschange');
7049 }
7050
7051 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
7052 this.setZoom(this._layersMaxZoom);
7053 }
7054 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
7055 this.setZoom(this._layersMinZoom);
7056 }
7057 }
7058 });
7059
7060 /*
7061 * @class LayerGroup
7062 * @aka L.LayerGroup
7063 * @inherits Interactive layer
7064 *
7065 * Used to group several layers and handle them as one. If you add it to the map,
7066 * any layers added or removed from the group will be added/removed on the map as
7067 * well. Extends `Layer`.
7068 *
7069 * @example
7070 *
7071 * ```js
7072 * L.layerGroup([marker1, marker2])
7073 * .addLayer(polyline)
7074 * .addTo(map);
7075 * ```
7076 */
7077
7078 var LayerGroup = Layer.extend({
7079
7080 initialize: function (layers, options) {
7081 setOptions(this, options);
7082
7083 this._layers = {};
7084
7085 var i, len;
7086
7087 if (layers) {
7088 for (i = 0, len = layers.length; i < len; i++) {
7089 this.addLayer(layers[i]);
7090 }
7091 }
7092 },
7093
7094 // @method addLayer(layer: Layer): this
7095 // Adds the given layer to the group.
7096 addLayer: function (layer) {
7097 var id = this.getLayerId(layer);
7098
7099 this._layers[id] = layer;
7100
7101 if (this._map) {
7102 this._map.addLayer(layer);
7103 }
7104
7105 return this;
7106 },
7107
7108 // @method removeLayer(layer: Layer): this
7109 // Removes the given layer from the group.
7110 // @alternative
7111 // @method removeLayer(id: Number): this
7112 // Removes the layer with the given internal ID from the group.
7113 removeLayer: function (layer) {
7114 var id = layer in this._layers ? layer : this.getLayerId(layer);
7115
7116 if (this._map && this._layers[id]) {
7117 this._map.removeLayer(this._layers[id]);
7118 }
7119
7120 delete this._layers[id];
7121
7122 return this;
7123 },
7124
7125 // @method hasLayer(layer: Layer): Boolean
7126 // Returns `true` if the given layer is currently added to the group.
7127 // @alternative
7128 // @method hasLayer(id: Number): Boolean
7129 // Returns `true` if the given internal ID is currently added to the group.
7130 hasLayer: function (layer) {
7131 var layerId = typeof layer === 'number' ? layer : this.getLayerId(layer);
7132 return layerId in this._layers;
7133 },
7134
7135 // @method clearLayers(): this
7136 // Removes all the layers from the group.
7137 clearLayers: function () {
7138 return this.eachLayer(this.removeLayer, this);
7139 },
7140
7141 // @method invoke(methodName: String, …): this
7142 // Calls `methodName` on every layer contained in this group, passing any
7143 // additional parameters. Has no effect if the layers contained do not
7144 // implement `methodName`.
7145 invoke: function (methodName) {
7146 var args = Array.prototype.slice.call(arguments, 1),
7147 i, layer;
7148
7149 for (i in this._layers) {
7150 layer = this._layers[i];
7151
7152 if (layer[methodName]) {
7153 layer[methodName].apply(layer, args);
7154 }
7155 }
7156
7157 return this;
7158 },
7159
7160 onAdd: function (map) {
7161 this.eachLayer(map.addLayer, map);
7162 },
7163
7164 onRemove: function (map) {
7165 this.eachLayer(map.removeLayer, map);
7166 },
7167
7168 // @method eachLayer(fn: Function, context?: Object): this
7169 // Iterates over the layers of the group, optionally specifying context of the iterator function.
7170 // ```js
7171 // group.eachLayer(function (layer) {
7172 // layer.bindPopup('Hello');
7173 // });
7174 // ```
7175 eachLayer: function (method, context) {
7176 for (var i in this._layers) {
7177 method.call(context, this._layers[i]);
7178 }
7179 return this;
7180 },
7181
7182 // @method getLayer(id: Number): Layer
7183 // Returns the layer with the given internal ID.
7184 getLayer: function (id) {
7185 return this._layers[id];
7186 },
7187
7188 // @method getLayers(): Layer[]
7189 // Returns an array of all the layers added to the group.
7190 getLayers: function () {
7191 var layers = [];
7192 this.eachLayer(layers.push, layers);
7193 return layers;
7194 },
7195
7196 // @method setZIndex(zIndex: Number): this
7197 // Calls `setZIndex` on every layer contained in this group, passing the z-index.
7198 setZIndex: function (zIndex) {
7199 return this.invoke('setZIndex', zIndex);
7200 },
7201
7202 // @method getLayerId(layer: Layer): Number
7203 // Returns the internal ID for a layer
7204 getLayerId: function (layer) {
7205 return stamp(layer);
7206 }
7207 });
7208
7209
7210 // @factory L.layerGroup(layers?: Layer[], options?: Object)
7211 // Create a layer group, optionally given an initial set of layers and an `options` object.
7212 var layerGroup = function (layers, options) {
7213 return new LayerGroup(layers, options);
7214 };
7215
7216 /*
7217 * @class FeatureGroup
7218 * @aka L.FeatureGroup
7219 * @inherits LayerGroup
7220 *
7221 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
7222 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
7223 * * Events are propagated to the `FeatureGroup`, so if the group has an event
7224 * handler, it will handle events from any of the layers. This includes mouse events
7225 * and custom events.
7226 * * Has `layeradd` and `layerremove` events
7227 *
7228 * @example
7229 *
7230 * ```js
7231 * L.featureGroup([marker1, marker2, polyline])
7232 * .bindPopup('Hello world!')
7233 * .on('click', function() { alert('Clicked on a member of the group!'); })
7234 * .addTo(map);
7235 * ```
7236 */
7237
7238 var FeatureGroup = LayerGroup.extend({
7239
7240 addLayer: function (layer) {
7241 if (this.hasLayer(layer)) {
7242 return this;
7243 }
7244
7245 layer.addEventParent(this);
7246
7247 LayerGroup.prototype.addLayer.call(this, layer);
7248
7249 // @event layeradd: LayerEvent
7250 // Fired when a layer is added to this `FeatureGroup`
7251 return this.fire('layeradd', {layer: layer});
7252 },
7253
7254 removeLayer: function (layer) {
7255 if (!this.hasLayer(layer)) {
7256 return this;
7257 }
7258 if (layer in this._layers) {
7259 layer = this._layers[layer];
7260 }
7261
7262 layer.removeEventParent(this);
7263
7264 LayerGroup.prototype.removeLayer.call(this, layer);
7265
7266 // @event layerremove: LayerEvent
7267 // Fired when a layer is removed from this `FeatureGroup`
7268 return this.fire('layerremove', {layer: layer});
7269 },
7270
7271 // @method setStyle(style: Path options): this
7272 // Sets the given path options to each layer of the group that has a `setStyle` method.
7273 setStyle: function (style) {
7274 return this.invoke('setStyle', style);
7275 },
7276
7277 // @method bringToFront(): this
7278 // Brings the layer group to the top of all other layers
7279 bringToFront: function () {
7280 return this.invoke('bringToFront');
7281 },
7282
7283 // @method bringToBack(): this
7284 // Brings the layer group to the back of all other layers
7285 bringToBack: function () {
7286 return this.invoke('bringToBack');
7287 },
7288
7289 // @method getBounds(): LatLngBounds
7290 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
7291 getBounds: function () {
7292 var bounds = new LatLngBounds();
7293
7294 for (var id in this._layers) {
7295 var layer = this._layers[id];
7296 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
7297 }
7298 return bounds;
7299 }
7300 });
7301
7302 // @factory L.featureGroup(layers?: Layer[], options?: Object)
7303 // Create a feature group, optionally given an initial set of layers and an `options` object.
7304 var featureGroup = function (layers, options) {
7305 return new FeatureGroup(layers, options);
7306 };
7307
7308 /*
7309 * @class Icon
7310 * @aka L.Icon
7311 *
7312 * Represents an icon to provide when creating a marker.
7313 *
7314 * @example
7315 *
7316 * ```js
7317 * var myIcon = L.icon({
7318 * iconUrl: 'my-icon.png',
7319 * iconRetinaUrl: 'my-icon@2x.png',
7320 * iconSize: [38, 95],
7321 * iconAnchor: [22, 94],
7322 * popupAnchor: [-3, -76],
7323 * shadowUrl: 'my-icon-shadow.png',
7324 * shadowRetinaUrl: 'my-icon-shadow@2x.png',
7325 * shadowSize: [68, 95],
7326 * shadowAnchor: [22, 94]
7327 * });
7328 *
7329 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
7330 * ```
7331 *
7332 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
7333 *
7334 */
7335
7336 var Icon = Class.extend({
7337
7338 /* @section
7339 * @aka Icon options
7340 *
7341 * @option iconUrl: String = null
7342 * **(required)** The URL to the icon image (absolute or relative to your script path).
7343 *
7344 * @option iconRetinaUrl: String = null
7345 * The URL to a retina sized version of the icon image (absolute or relative to your
7346 * script path). Used for Retina screen devices.
7347 *
7348 * @option iconSize: Point = null
7349 * Size of the icon image in pixels.
7350 *
7351 * @option iconAnchor: Point = null
7352 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
7353 * will be aligned so that this point is at the marker's geographical location. Centered
7354 * by default if size is specified, also can be set in CSS with negative margins.
7355 *
7356 * @option popupAnchor: Point = [0, 0]
7357 * The coordinates of the point from which popups will "open", relative to the icon anchor.
7358 *
7359 * @option tooltipAnchor: Point = [0, 0]
7360 * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
7361 *
7362 * @option shadowUrl: String = null
7363 * The URL to the icon shadow image. If not specified, no shadow image will be created.
7364 *
7365 * @option shadowRetinaUrl: String = null
7366 *
7367 * @option shadowSize: Point = null
7368 * Size of the shadow image in pixels.
7369 *
7370 * @option shadowAnchor: Point = null
7371 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
7372 * as iconAnchor if not specified).
7373 *
7374 * @option className: String = ''
7375 * A custom class name to assign to both icon and shadow images. Empty by default.
7376 */
7377
7378 options: {
7379 popupAnchor: [0, 0],
7380 tooltipAnchor: [0, 0],
7381
7382 // @option crossOrigin: Boolean|String = false
7383 // Whether the crossOrigin attribute will be added to the tiles.
7384 // If a String is provided, all tiles will have their crossOrigin attribute set to the String provided. This is needed if you want to access tile pixel data.
7385 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
7386 crossOrigin: false
7387 },
7388
7389 initialize: function (options) {
7390 setOptions(this, options);
7391 },
7392
7393 // @method createIcon(oldIcon?: HTMLElement): HTMLElement
7394 // Called internally when the icon has to be shown, returns a `<img>` HTML element
7395 // styled according to the options.
7396 createIcon: function (oldIcon) {
7397 return this._createIcon('icon', oldIcon);
7398 },
7399
7400 // @method createShadow(oldIcon?: HTMLElement): HTMLElement
7401 // As `createIcon`, but for the shadow beneath it.
7402 createShadow: function (oldIcon) {
7403 return this._createIcon('shadow', oldIcon);
7404 },
7405
7406 _createIcon: function (name, oldIcon) {
7407 var src = this._getIconUrl(name);
7408
7409 if (!src) {
7410 if (name === 'icon') {
7411 throw new Error('iconUrl not set in Icon options (see the docs).');
7412 }
7413 return null;
7414 }
7415
7416 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
7417 this._setIconStyles(img, name);
7418
7419 if (this.options.crossOrigin || this.options.crossOrigin === '') {
7420 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
7421 }
7422
7423 return img;
7424 },
7425
7426 _setIconStyles: function (img, name) {
7427 var options = this.options;
7428 var sizeOption = options[name + 'Size'];
7429
7430 if (typeof sizeOption === 'number') {
7431 sizeOption = [sizeOption, sizeOption];
7432 }
7433
7434 var size = toPoint(sizeOption),
7435 anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
7436 size && size.divideBy(2, true));
7437
7438 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
7439
7440 if (anchor) {
7441 img.style.marginLeft = (-anchor.x) + 'px';
7442 img.style.marginTop = (-anchor.y) + 'px';
7443 }
7444
7445 if (size) {
7446 img.style.width = size.x + 'px';
7447 img.style.height = size.y + 'px';
7448 }
7449 },
7450
7451 _createImg: function (src, el) {
7452 el = el || document.createElement('img');
7453 el.src = src;
7454 return el;
7455 },
7456
7457 _getIconUrl: function (name) {
7458 return Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
7459 }
7460 });
7461
7462
7463 // @factory L.icon(options: Icon options)
7464 // Creates an icon instance with the given options.
7465 function icon(options) {
7466 return new Icon(options);
7467 }
7468
7469 /*
7470 * @miniclass Icon.Default (Icon)
7471 * @aka L.Icon.Default
7472 * @section
7473 *
7474 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
7475 * no icon is specified. Points to the blue marker image distributed with Leaflet
7476 * releases.
7477 *
7478 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
7479 * (which is a set of `Icon options`).
7480 *
7481 * If you want to _completely_ replace the default icon, override the
7482 * `L.Marker.prototype.options.icon` with your own icon instead.
7483 */
7484
7485 var IconDefault = Icon.extend({
7486
7487 options: {
7488 iconUrl: 'marker-icon.png',
7489 iconRetinaUrl: 'marker-icon-2x.png',
7490 shadowUrl: 'marker-shadow.png',
7491 iconSize: [25, 41],
7492 iconAnchor: [12, 41],
7493 popupAnchor: [1, -34],
7494 tooltipAnchor: [16, -28],
7495 shadowSize: [41, 41]
7496 },
7497
7498 _getIconUrl: function (name) {
7499 if (typeof IconDefault.imagePath !== 'string') { // Deprecated, backwards-compatibility only
7500 IconDefault.imagePath = this._detectIconPath();
7501 }
7502
7503 // @option imagePath: String
7504 // `Icon.Default` will try to auto-detect the location of the
7505 // blue icon images. If you are placing these images in a non-standard
7506 // way, set this option to point to the right path.
7507 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
7508 },
7509
7510 _stripUrl: function (path) { // separate function to use in tests
7511 var strip = function (str, re, idx) {
7512 var match = re.exec(str);
7513 return match && match[idx];
7514 };
7515 path = strip(path, /^url\((['"])?(.+)\1\)$/, 2);
7516 return path && strip(path, /^(.*)marker-icon\.png$/, 1);
7517 },
7518
7519 _detectIconPath: function () {
7520 var el = create$1('div', 'leaflet-default-icon-path', document.body);
7521 var path = getStyle(el, 'background-image') ||
7522 getStyle(el, 'backgroundImage'); // IE8
7523
7524 document.body.removeChild(el);
7525 path = this._stripUrl(path);
7526 if (path) { return path; }
7527 var link = document.querySelector('link[href$="leaflet.css"]');
7528 if (!link) { return ''; }
7529 return link.href.substring(0, link.href.length - 'leaflet.css'.length - 1);
7530 }
7531 });
7532
7533 /*
7534 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7535 */
7536
7537
7538 /* @namespace Marker
7539 * @section Interaction handlers
7540 *
7541 * Interaction handlers are properties of a marker instance that allow you to control interaction behavior in runtime, enabling or disabling certain features such as dragging (see `Handler` methods). Example:
7542 *
7543 * ```js
7544 * marker.dragging.disable();
7545 * ```
7546 *
7547 * @property dragging: Handler
7548 * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
7549 */
7550
7551 var MarkerDrag = Handler.extend({
7552 initialize: function (marker) {
7553 this._marker = marker;
7554 },
7555
7556 addHooks: function () {
7557 var icon = this._marker._icon;
7558
7559 if (!this._draggable) {
7560 this._draggable = new Draggable(icon, icon, true);
7561 }
7562
7563 this._draggable.on({
7564 dragstart: this._onDragStart,
7565 predrag: this._onPreDrag,
7566 drag: this._onDrag,
7567 dragend: this._onDragEnd
7568 }, this).enable();
7569
7570 addClass(icon, 'leaflet-marker-draggable');
7571 },
7572
7573 removeHooks: function () {
7574 this._draggable.off({
7575 dragstart: this._onDragStart,
7576 predrag: this._onPreDrag,
7577 drag: this._onDrag,
7578 dragend: this._onDragEnd
7579 }, this).disable();
7580
7581 if (this._marker._icon) {
7582 removeClass(this._marker._icon, 'leaflet-marker-draggable');
7583 }
7584 },
7585
7586 moved: function () {
7587 return this._draggable && this._draggable._moved;
7588 },
7589
7590 _adjustPan: function (e) {
7591 var marker = this._marker,
7592 map = marker._map,
7593 speed = this._marker.options.autoPanSpeed,
7594 padding = this._marker.options.autoPanPadding,
7595 iconPos = getPosition(marker._icon),
7596 bounds = map.getPixelBounds(),
7597 origin = map.getPixelOrigin();
7598
7599 var panBounds = toBounds(
7600 bounds.min._subtract(origin).add(padding),
7601 bounds.max._subtract(origin).subtract(padding)
7602 );
7603
7604 if (!panBounds.contains(iconPos)) {
7605 // Compute incremental movement
7606 var movement = toPoint(
7607 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
7608 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
7609
7610 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
7611 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
7612 ).multiplyBy(speed);
7613
7614 map.panBy(movement, {animate: false});
7615
7616 this._draggable._newPos._add(movement);
7617 this._draggable._startPos._add(movement);
7618
7619 setPosition(marker._icon, this._draggable._newPos);
7620 this._onDrag(e);
7621
7622 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7623 }
7624 },
7625
7626 _onDragStart: function () {
7627 // @section Dragging events
7628 // @event dragstart: Event
7629 // Fired when the user starts dragging the marker.
7630
7631 // @event movestart: Event
7632 // Fired when the marker starts moving (because of dragging).
7633
7634 this._oldLatLng = this._marker.getLatLng();
7635
7636 // When using ES6 imports it could not be set when `Popup` was not imported as well
7637 this._marker.closePopup && this._marker.closePopup();
7638
7639 this._marker
7640 .fire('movestart')
7641 .fire('dragstart');
7642 },
7643
7644 _onPreDrag: function (e) {
7645 if (this._marker.options.autoPan) {
7646 cancelAnimFrame(this._panRequest);
7647 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7648 }
7649 },
7650
7651 _onDrag: function (e) {
7652 var marker = this._marker,
7653 shadow = marker._shadow,
7654 iconPos = getPosition(marker._icon),
7655 latlng = marker._map.layerPointToLatLng(iconPos);
7656
7657 // update shadow position
7658 if (shadow) {
7659 setPosition(shadow, iconPos);
7660 }
7661
7662 marker._latlng = latlng;
7663 e.latlng = latlng;
7664 e.oldLatLng = this._oldLatLng;
7665
7666 // @event drag: Event
7667 // Fired repeatedly while the user drags the marker.
7668 marker
7669 .fire('move', e)
7670 .fire('drag', e);
7671 },
7672
7673 _onDragEnd: function (e) {
7674 // @event dragend: DragEndEvent
7675 // Fired when the user stops dragging the marker.
7676
7677 cancelAnimFrame(this._panRequest);
7678
7679 // @event moveend: Event
7680 // Fired when the marker stops moving (because of dragging).
7681 delete this._oldLatLng;
7682 this._marker
7683 .fire('moveend')
7684 .fire('dragend', e);
7685 }
7686 });
7687
7688 /*
7689 * @class Marker
7690 * @inherits Interactive layer
7691 * @aka L.Marker
7692 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
7693 *
7694 * @example
7695 *
7696 * ```js
7697 * L.marker([50.5, 30.5]).addTo(map);
7698 * ```
7699 */
7700
7701 var Marker = Layer.extend({
7702
7703 // @section
7704 // @aka Marker options
7705 options: {
7706 // @option icon: Icon = *
7707 // Icon instance to use for rendering the marker.
7708 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
7709 // If not specified, a common instance of `L.Icon.Default` is used.
7710 icon: new IconDefault(),
7711
7712 // Option inherited from "Interactive layer" abstract class
7713 interactive: true,
7714
7715 // @option keyboard: Boolean = true
7716 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
7717 keyboard: true,
7718
7719 // @option title: String = ''
7720 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
7721 // [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled).
7722 title: '',
7723
7724 // @option alt: String = 'Marker'
7725 // Text for the `alt` attribute of the icon image.
7726 // [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled).
7727 alt: 'Marker',
7728
7729 // @option zIndexOffset: Number = 0
7730 // By default, marker images zIndex is set automatically based on its latitude. Use this option if you want to put the marker on top of all others (or below), specifying a high value like `1000` (or high negative value, respectively).
7731 zIndexOffset: 0,
7732
7733 // @option opacity: Number = 1.0
7734 // The opacity of the marker.
7735 opacity: 1,
7736
7737 // @option riseOnHover: Boolean = false
7738 // If `true`, the marker will get on top of others when you hover the mouse over it.
7739 riseOnHover: false,
7740
7741 // @option riseOffset: Number = 250
7742 // The z-index offset used for the `riseOnHover` feature.
7743 riseOffset: 250,
7744
7745 // @option pane: String = 'markerPane'
7746 // `Map pane` where the markers icon will be added.
7747 pane: 'markerPane',
7748
7749 // @option shadowPane: String = 'shadowPane'
7750 // `Map pane` where the markers shadow will be added.
7751 shadowPane: 'shadowPane',
7752
7753 // @option bubblingMouseEvents: Boolean = false
7754 // When `true`, a mouse event on this marker will trigger the same event on the map
7755 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7756 bubblingMouseEvents: false,
7757
7758 // @option autoPanOnFocus: Boolean = true
7759 // When `true`, the map will pan whenever the marker is focused (via
7760 // e.g. pressing `tab` on the keyboard) to ensure the marker is
7761 // visible within the map's bounds
7762 autoPanOnFocus: true,
7763
7764 // @section Draggable marker options
7765 // @option draggable: Boolean = false
7766 // Whether the marker is draggable with mouse/touch or not.
7767 draggable: false,
7768
7769 // @option autoPan: Boolean = false
7770 // Whether to pan the map when dragging this marker near its edge or not.
7771 autoPan: false,
7772
7773 // @option autoPanPadding: Point = Point(50, 50)
7774 // Distance (in pixels to the left/right and to the top/bottom) of the
7775 // map edge to start panning the map.
7776 autoPanPadding: [50, 50],
7777
7778 // @option autoPanSpeed: Number = 10
7779 // Number of pixels the map should pan by.
7780 autoPanSpeed: 10
7781 },
7782
7783 /* @section
7784 *
7785 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
7786 */
7787
7788 initialize: function (latlng, options) {
7789 setOptions(this, options);
7790 this._latlng = toLatLng(latlng);
7791 },
7792
7793 onAdd: function (map) {
7794 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
7795
7796 if (this._zoomAnimated) {
7797 map.on('zoomanim', this._animateZoom, this);
7798 }
7799
7800 this._initIcon();
7801 this.update();
7802 },
7803
7804 onRemove: function (map) {
7805 if (this.dragging && this.dragging.enabled()) {
7806 this.options.draggable = true;
7807 this.dragging.removeHooks();
7808 }
7809 delete this.dragging;
7810
7811 if (this._zoomAnimated) {
7812 map.off('zoomanim', this._animateZoom, this);
7813 }
7814
7815 this._removeIcon();
7816 this._removeShadow();
7817 },
7818
7819 getEvents: function () {
7820 return {
7821 zoom: this.update,
7822 viewreset: this.update
7823 };
7824 },
7825
7826 // @method getLatLng: LatLng
7827 // Returns the current geographical position of the marker.
7828 getLatLng: function () {
7829 return this._latlng;
7830 },
7831
7832 // @method setLatLng(latlng: LatLng): this
7833 // Changes the marker position to the given point.
7834 setLatLng: function (latlng) {
7835 var oldLatLng = this._latlng;
7836 this._latlng = toLatLng(latlng);
7837 this.update();
7838
7839 // @event move: Event
7840 // Fired when the marker is moved via [`setLatLng`](#marker-setlatlng) or by [dragging](#marker-dragging). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
7841 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7842 },
7843
7844 // @method setZIndexOffset(offset: Number): this
7845 // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
7846 setZIndexOffset: function (offset) {
7847 this.options.zIndexOffset = offset;
7848 return this.update();
7849 },
7850
7851 // @method getIcon: Icon
7852 // Returns the current icon used by the marker
7853 getIcon: function () {
7854 return this.options.icon;
7855 },
7856
7857 // @method setIcon(icon: Icon): this
7858 // Changes the marker icon.
7859 setIcon: function (icon) {
7860
7861 this.options.icon = icon;
7862
7863 if (this._map) {
7864 this._initIcon();
7865 this.update();
7866 }
7867
7868 if (this._popup) {
7869 this.bindPopup(this._popup, this._popup.options);
7870 }
7871
7872 return this;
7873 },
7874
7875 getElement: function () {
7876 return this._icon;
7877 },
7878
7879 update: function () {
7880
7881 if (this._icon && this._map) {
7882 var pos = this._map.latLngToLayerPoint(this._latlng).round();
7883 this._setPos(pos);
7884 }
7885
7886 return this;
7887 },
7888
7889 _initIcon: function () {
7890 var options = this.options,
7891 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
7892
7893 var icon = options.icon.createIcon(this._icon),
7894 addIcon = false;
7895
7896 // if we're not reusing the icon, remove the old one and init new one
7897 if (icon !== this._icon) {
7898 if (this._icon) {
7899 this._removeIcon();
7900 }
7901 addIcon = true;
7902
7903 if (options.title) {
7904 icon.title = options.title;
7905 }
7906
7907 if (icon.tagName === 'IMG') {
7908 icon.alt = options.alt || '';
7909 }
7910 }
7911
7912 addClass(icon, classToAdd);
7913
7914 if (options.keyboard) {
7915 icon.tabIndex = '0';
7916 icon.setAttribute('role', 'button');
7917 }
7918
7919 this._icon = icon;
7920
7921 if (options.riseOnHover) {
7922 this.on({
7923 mouseover: this._bringToFront,
7924 mouseout: this._resetZIndex
7925 });
7926 }
7927
7928 if (this.options.autoPanOnFocus) {
7929 on(icon, 'focus', this._panOnFocus, this);
7930 }
7931
7932 var newShadow = options.icon.createShadow(this._shadow),
7933 addShadow = false;
7934
7935 if (newShadow !== this._shadow) {
7936 this._removeShadow();
7937 addShadow = true;
7938 }
7939
7940 if (newShadow) {
7941 addClass(newShadow, classToAdd);
7942 newShadow.alt = '';
7943 }
7944 this._shadow = newShadow;
7945
7946
7947 if (options.opacity < 1) {
7948 this._updateOpacity();
7949 }
7950
7951
7952 if (addIcon) {
7953 this.getPane().appendChild(this._icon);
7954 }
7955 this._initInteraction();
7956 if (newShadow && addShadow) {
7957 this.getPane(options.shadowPane).appendChild(this._shadow);
7958 }
7959 },
7960
7961 _removeIcon: function () {
7962 if (this.options.riseOnHover) {
7963 this.off({
7964 mouseover: this._bringToFront,
7965 mouseout: this._resetZIndex
7966 });
7967 }
7968
7969 if (this.options.autoPanOnFocus) {
7970 off(this._icon, 'focus', this._panOnFocus, this);
7971 }
7972
7973 remove(this._icon);
7974 this.removeInteractiveTarget(this._icon);
7975
7976 this._icon = null;
7977 },
7978
7979 _removeShadow: function () {
7980 if (this._shadow) {
7981 remove(this._shadow);
7982 }
7983 this._shadow = null;
7984 },
7985
7986 _setPos: function (pos) {
7987
7988 if (this._icon) {
7989 setPosition(this._icon, pos);
7990 }
7991
7992 if (this._shadow) {
7993 setPosition(this._shadow, pos);
7994 }
7995
7996 this._zIndex = pos.y + this.options.zIndexOffset;
7997
7998 this._resetZIndex();
7999 },
8000
8001 _updateZIndex: function (offset) {
8002 if (this._icon) {
8003 this._icon.style.zIndex = this._zIndex + offset;
8004 }
8005 },
8006
8007 _animateZoom: function (opt) {
8008 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
8009
8010 this._setPos(pos);
8011 },
8012
8013 _initInteraction: function () {
8014
8015 if (!this.options.interactive) { return; }
8016
8017 addClass(this._icon, 'leaflet-interactive');
8018
8019 this.addInteractiveTarget(this._icon);
8020
8021 if (MarkerDrag) {
8022 var draggable = this.options.draggable;
8023 if (this.dragging) {
8024 draggable = this.dragging.enabled();
8025 this.dragging.disable();
8026 }
8027
8028 this.dragging = new MarkerDrag(this);
8029
8030 if (draggable) {
8031 this.dragging.enable();
8032 }
8033 }
8034 },
8035
8036 // @method setOpacity(opacity: Number): this
8037 // Changes the opacity of the marker.
8038 setOpacity: function (opacity) {
8039 this.options.opacity = opacity;
8040 if (this._map) {
8041 this._updateOpacity();
8042 }
8043
8044 return this;
8045 },
8046
8047 _updateOpacity: function () {
8048 var opacity = this.options.opacity;
8049
8050 if (this._icon) {
8051 setOpacity(this._icon, opacity);
8052 }
8053
8054 if (this._shadow) {
8055 setOpacity(this._shadow, opacity);
8056 }
8057 },
8058
8059 _bringToFront: function () {
8060 this._updateZIndex(this.options.riseOffset);
8061 },
8062
8063 _resetZIndex: function () {
8064 this._updateZIndex(0);
8065 },
8066
8067 _panOnFocus: function () {
8068 var map = this._map;
8069 if (!map) { return; }
8070
8071 var iconOpts = this.options.icon.options;
8072 var size = iconOpts.iconSize ? toPoint(iconOpts.iconSize) : toPoint(0, 0);
8073 var anchor = iconOpts.iconAnchor ? toPoint(iconOpts.iconAnchor) : toPoint(0, 0);
8074
8075 map.panInside(this._latlng, {
8076 paddingTopLeft: anchor,
8077 paddingBottomRight: size.subtract(anchor)
8078 });
8079 },
8080
8081 _getPopupAnchor: function () {
8082 return this.options.icon.options.popupAnchor;
8083 },
8084
8085 _getTooltipAnchor: function () {
8086 return this.options.icon.options.tooltipAnchor;
8087 }
8088 });
8089
8090
8091 // factory L.marker(latlng: LatLng, options? : Marker options)
8092
8093 // @factory L.marker(latlng: LatLng, options? : Marker options)
8094 // Instantiates a Marker object given a geographical point and optionally an options object.
8095 function marker(latlng, options) {
8096 return new Marker(latlng, options);
8097 }
8098
8099 /*
8100 * @class Path
8101 * @aka L.Path
8102 * @inherits Interactive layer
8103 *
8104 * An abstract class that contains options and constants shared between vector
8105 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
8106 */
8107
8108 var Path = Layer.extend({
8109
8110 // @section
8111 // @aka Path options
8112 options: {
8113 // @option stroke: Boolean = true
8114 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
8115 stroke: true,
8116
8117 // @option color: String = '#3388ff'
8118 // Stroke color
8119 color: '#3388ff',
8120
8121 // @option weight: Number = 3
8122 // Stroke width in pixels
8123 weight: 3,
8124
8125 // @option opacity: Number = 1.0
8126 // Stroke opacity
8127 opacity: 1,
8128
8129 // @option lineCap: String= 'round'
8130 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
8131 lineCap: 'round',
8132
8133 // @option lineJoin: String = 'round'
8134 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
8135 lineJoin: 'round',
8136
8137 // @option dashArray: String = null
8138 // A string that defines the stroke [dash pattern](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dasharray). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility).
8139 dashArray: null,
8140
8141 // @option dashOffset: String = null
8142 // A string that defines the [distance into the dash pattern to start the dash](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dashoffset). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility).
8143 dashOffset: null,
8144
8145 // @option fill: Boolean = depends
8146 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
8147 fill: false,
8148
8149 // @option fillColor: String = *
8150 // Fill color. Defaults to the value of the [`color`](#path-color) option
8151 fillColor: null,
8152
8153 // @option fillOpacity: Number = 0.2
8154 // Fill opacity.
8155 fillOpacity: 0.2,
8156
8157 // @option fillRule: String = 'evenodd'
8158 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
8159 fillRule: 'evenodd',
8160
8161 // className: '',
8162
8163 // Option inherited from "Interactive layer" abstract class
8164 interactive: true,
8165
8166 // @option bubblingMouseEvents: Boolean = true
8167 // When `true`, a mouse event on this path will trigger the same event on the map
8168 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
8169 bubblingMouseEvents: true
8170 },
8171
8172 beforeAdd: function (map) {
8173 // Renderer is set here because we need to call renderer.getEvents
8174 // before this.getEvents.
8175 this._renderer = map.getRenderer(this);
8176 },
8177
8178 onAdd: function () {
8179 this._renderer._initPath(this);
8180 this._reset();
8181 this._renderer._addPath(this);
8182 },
8183
8184 onRemove: function () {
8185 this._renderer._removePath(this);
8186 },
8187
8188 // @method redraw(): this
8189 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
8190 redraw: function () {
8191 if (this._map) {
8192 this._renderer._updatePath(this);
8193 }
8194 return this;
8195 },
8196
8197 // @method setStyle(style: Path options): this
8198 // Changes the appearance of a Path based on the options in the `Path options` object.
8199 setStyle: function (style) {
8200 setOptions(this, style);
8201 if (this._renderer) {
8202 this._renderer._updateStyle(this);
8203 if (this.options.stroke && style && Object.prototype.hasOwnProperty.call(style, 'weight')) {
8204 this._updateBounds();
8205 }
8206 }
8207 return this;
8208 },
8209
8210 // @method bringToFront(): this
8211 // Brings the layer to the top of all path layers.
8212 bringToFront: function () {
8213 if (this._renderer) {
8214 this._renderer._bringToFront(this);
8215 }
8216 return this;
8217 },
8218
8219 // @method bringToBack(): this
8220 // Brings the layer to the bottom of all path layers.
8221 bringToBack: function () {
8222 if (this._renderer) {
8223 this._renderer._bringToBack(this);
8224 }
8225 return this;
8226 },
8227
8228 getElement: function () {
8229 return this._path;
8230 },
8231
8232 _reset: function () {
8233 // defined in child classes
8234 this._project();
8235 this._update();
8236 },
8237
8238 _clickTolerance: function () {
8239 // used when doing hit detection for Canvas layers
8240 return (this.options.stroke ? this.options.weight / 2 : 0) +
8241 (this._renderer.options.tolerance || 0);
8242 }
8243 });
8244
8245 /*
8246 * @class CircleMarker
8247 * @aka L.CircleMarker
8248 * @inherits Path
8249 *
8250 * A circle of a fixed size with radius specified in pixels. Extends `Path`.
8251 */
8252
8253 var CircleMarker = Path.extend({
8254
8255 // @section
8256 // @aka CircleMarker options
8257 options: {
8258 fill: true,
8259
8260 // @option radius: Number = 10
8261 // Radius of the circle marker, in pixels
8262 radius: 10
8263 },
8264
8265 initialize: function (latlng, options) {
8266 setOptions(this, options);
8267 this._latlng = toLatLng(latlng);
8268 this._radius = this.options.radius;
8269 },
8270
8271 // @method setLatLng(latLng: LatLng): this
8272 // Sets the position of a circle marker to a new location.
8273 setLatLng: function (latlng) {
8274 var oldLatLng = this._latlng;
8275 this._latlng = toLatLng(latlng);
8276 this.redraw();
8277
8278 // @event move: Event
8279 // Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
8280 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
8281 },
8282
8283 // @method getLatLng(): LatLng
8284 // Returns the current geographical position of the circle marker
8285 getLatLng: function () {
8286 return this._latlng;
8287 },
8288
8289 // @method setRadius(radius: Number): this
8290 // Sets the radius of a circle marker. Units are in pixels.
8291 setRadius: function (radius) {
8292 this.options.radius = this._radius = radius;
8293 return this.redraw();
8294 },
8295
8296 // @method getRadius(): Number
8297 // Returns the current radius of the circle
8298 getRadius: function () {
8299 return this._radius;
8300 },
8301
8302 setStyle : function (options) {
8303 var radius = options && options.radius || this._radius;
8304 Path.prototype.setStyle.call(this, options);
8305 this.setRadius(radius);
8306 return this;
8307 },
8308
8309 _project: function () {
8310 this._point = this._map.latLngToLayerPoint(this._latlng);
8311 this._updateBounds();
8312 },
8313
8314 _updateBounds: function () {
8315 var r = this._radius,
8316 r2 = this._radiusY || r,
8317 w = this._clickTolerance(),
8318 p = [r + w, r2 + w];
8319 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
8320 },
8321
8322 _update: function () {
8323 if (this._map) {
8324 this._updatePath();
8325 }
8326 },
8327
8328 _updatePath: function () {
8329 this._renderer._updateCircle(this);
8330 },
8331
8332 _empty: function () {
8333 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
8334 },
8335
8336 // Needed by the `Canvas` renderer for interactivity
8337 _containsPoint: function (p) {
8338 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
8339 }
8340 });
8341
8342
8343 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
8344 // Instantiates a circle marker object given a geographical point, and an optional options object.
8345 function circleMarker(latlng, options) {
8346 return new CircleMarker(latlng, options);
8347 }
8348
8349 /*
8350 * @class Circle
8351 * @aka L.Circle
8352 * @inherits CircleMarker
8353 *
8354 * A class for drawing circle overlays on a map. Extends `CircleMarker`.
8355 *
8356 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
8357 *
8358 * @example
8359 *
8360 * ```js
8361 * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
8362 * ```
8363 */
8364
8365 var Circle = CircleMarker.extend({
8366
8367 initialize: function (latlng, options, legacyOptions) {
8368 if (typeof options === 'number') {
8369 // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
8370 options = extend({}, legacyOptions, {radius: options});
8371 }
8372 setOptions(this, options);
8373 this._latlng = toLatLng(latlng);
8374
8375 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
8376
8377 // @section
8378 // @aka Circle options
8379 // @option radius: Number; Radius of the circle, in meters.
8380 this._mRadius = this.options.radius;
8381 },
8382
8383 // @method setRadius(radius: Number): this
8384 // Sets the radius of a circle. Units are in meters.
8385 setRadius: function (radius) {
8386 this._mRadius = radius;
8387 return this.redraw();
8388 },
8389
8390 // @method getRadius(): Number
8391 // Returns the current radius of a circle. Units are in meters.
8392 getRadius: function () {
8393 return this._mRadius;
8394 },
8395
8396 // @method getBounds(): LatLngBounds
8397 // Returns the `LatLngBounds` of the path.
8398 getBounds: function () {
8399 var half = [this._radius, this._radiusY || this._radius];
8400
8401 return new LatLngBounds(
8402 this._map.layerPointToLatLng(this._point.subtract(half)),
8403 this._map.layerPointToLatLng(this._point.add(half)));
8404 },
8405
8406 setStyle: Path.prototype.setStyle,
8407
8408 _project: function () {
8409
8410 var lng = this._latlng.lng,
8411 lat = this._latlng.lat,
8412 map = this._map,
8413 crs = map.options.crs;
8414
8415 if (crs.distance === Earth.distance) {
8416 var d = Math.PI / 180,
8417 latR = (this._mRadius / Earth.R) / d,
8418 top = map.project([lat + latR, lng]),
8419 bottom = map.project([lat - latR, lng]),
8420 p = top.add(bottom).divideBy(2),
8421 lat2 = map.unproject(p).lat,
8422 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
8423 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
8424
8425 if (isNaN(lngR) || lngR === 0) {
8426 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
8427 }
8428
8429 this._point = p.subtract(map.getPixelOrigin());
8430 this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
8431 this._radiusY = p.y - top.y;
8432
8433 } else {
8434 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
8435
8436 this._point = map.latLngToLayerPoint(this._latlng);
8437 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
8438 }
8439
8440 this._updateBounds();
8441 }
8442 });
8443
8444 // @factory L.circle(latlng: LatLng, options?: Circle options)
8445 // Instantiates a circle object given a geographical point, and an options object
8446 // which contains the circle radius.
8447 // @alternative
8448 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
8449 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
8450 // Do not use in new applications or plugins.
8451 function circle(latlng, options, legacyOptions) {
8452 return new Circle(latlng, options, legacyOptions);
8453 }
8454
8455 /*
8456 * @class Polyline
8457 * @aka L.Polyline
8458 * @inherits Path
8459 *
8460 * A class for drawing polyline overlays on a map. Extends `Path`.
8461 *
8462 * @example
8463 *
8464 * ```js
8465 * // create a red polyline from an array of LatLng points
8466 * var latlngs = [
8467 * [45.51, -122.68],
8468 * [37.77, -122.43],
8469 * [34.04, -118.2]
8470 * ];
8471 *
8472 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
8473 *
8474 * // zoom the map to the polyline
8475 * map.fitBounds(polyline.getBounds());
8476 * ```
8477 *
8478 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
8479 *
8480 * ```js
8481 * // create a red polyline from an array of arrays of LatLng points
8482 * var latlngs = [
8483 * [[45.51, -122.68],
8484 * [37.77, -122.43],
8485 * [34.04, -118.2]],
8486 * [[40.78, -73.91],
8487 * [41.83, -87.62],
8488 * [32.76, -96.72]]
8489 * ];
8490 * ```
8491 */
8492
8493
8494 var Polyline = Path.extend({
8495
8496 // @section
8497 // @aka Polyline options
8498 options: {
8499 // @option smoothFactor: Number = 1.0
8500 // How much to simplify the polyline on each zoom level. More means
8501 // better performance and smoother look, and less means more accurate representation.
8502 smoothFactor: 1.0,
8503
8504 // @option noClip: Boolean = false
8505 // Disable polyline clipping.
8506 noClip: false
8507 },
8508
8509 initialize: function (latlngs, options) {
8510 setOptions(this, options);
8511 this._setLatLngs(latlngs);
8512 },
8513
8514 // @method getLatLngs(): LatLng[]
8515 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8516 getLatLngs: function () {
8517 return this._latlngs;
8518 },
8519
8520 // @method setLatLngs(latlngs: LatLng[]): this
8521 // Replaces all the points in the polyline with the given array of geographical points.
8522 setLatLngs: function (latlngs) {
8523 this._setLatLngs(latlngs);
8524 return this.redraw();
8525 },
8526
8527 // @method isEmpty(): Boolean
8528 // Returns `true` if the Polyline has no LatLngs.
8529 isEmpty: function () {
8530 return !this._latlngs.length;
8531 },
8532
8533 // @method closestLayerPoint(p: Point): Point
8534 // Returns the point closest to `p` on the Polyline.
8535 closestLayerPoint: function (p) {
8536 var minDistance = Infinity,
8537 minPoint = null,
8538 closest = _sqClosestPointOnSegment,
8539 p1, p2;
8540
8541 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8542 var points = this._parts[j];
8543
8544 for (var i = 1, len = points.length; i < len; i++) {
8545 p1 = points[i - 1];
8546 p2 = points[i];
8547
8548 var sqDist = closest(p, p1, p2, true);
8549
8550 if (sqDist < minDistance) {
8551 minDistance = sqDist;
8552 minPoint = closest(p, p1, p2);
8553 }
8554 }
8555 }
8556 if (minPoint) {
8557 minPoint.distance = Math.sqrt(minDistance);
8558 }
8559 return minPoint;
8560 },
8561
8562 // @method getCenter(): LatLng
8563 // Returns the center ([centroid](https://en.wikipedia.org/wiki/Centroid)) of the polyline.
8564 getCenter: function () {
8565 // throws error when not yet added to map as this center calculation requires projected coordinates
8566 if (!this._map) {
8567 throw new Error('Must add layer to map before using getCenter()');
8568 }
8569 return polylineCenter(this._defaultShape(), this._map.options.crs);
8570 },
8571
8572 // @method getBounds(): LatLngBounds
8573 // Returns the `LatLngBounds` of the path.
8574 getBounds: function () {
8575 return this._bounds;
8576 },
8577
8578 // @method addLatLng(latlng: LatLng, latlngs?: LatLng[]): this
8579 // Adds a given point to the polyline. By default, adds to the first ring of
8580 // the polyline in case of a multi-polyline, but can be overridden by passing
8581 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8582 addLatLng: function (latlng, latlngs) {
8583 latlngs = latlngs || this._defaultShape();
8584 latlng = toLatLng(latlng);
8585 latlngs.push(latlng);
8586 this._bounds.extend(latlng);
8587 return this.redraw();
8588 },
8589
8590 _setLatLngs: function (latlngs) {
8591 this._bounds = new LatLngBounds();
8592 this._latlngs = this._convertLatLngs(latlngs);
8593 },
8594
8595 _defaultShape: function () {
8596 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
8597 },
8598
8599 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8600 _convertLatLngs: function (latlngs) {
8601 var result = [],
8602 flat = isFlat(latlngs);
8603
8604 for (var i = 0, len = latlngs.length; i < len; i++) {
8605 if (flat) {
8606 result[i] = toLatLng(latlngs[i]);
8607 this._bounds.extend(result[i]);
8608 } else {
8609 result[i] = this._convertLatLngs(latlngs[i]);
8610 }
8611 }
8612
8613 return result;
8614 },
8615
8616 _project: function () {
8617 var pxBounds = new Bounds();
8618 this._rings = [];
8619 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8620
8621 if (this._bounds.isValid() && pxBounds.isValid()) {
8622 this._rawPxBounds = pxBounds;
8623 this._updateBounds();
8624 }
8625 },
8626
8627 _updateBounds: function () {
8628 var w = this._clickTolerance(),
8629 p = new Point(w, w);
8630
8631 if (!this._rawPxBounds) {
8632 return;
8633 }
8634
8635 this._pxBounds = new Bounds([
8636 this._rawPxBounds.min.subtract(p),
8637 this._rawPxBounds.max.add(p)
8638 ]);
8639 },
8640
8641 // recursively turns latlngs into a set of rings with projected coordinates
8642 _projectLatlngs: function (latlngs, result, projectedBounds) {
8643 var flat = latlngs[0] instanceof LatLng,
8644 len = latlngs.length,
8645 i, ring;
8646
8647 if (flat) {
8648 ring = [];
8649 for (i = 0; i < len; i++) {
8650 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8651 projectedBounds.extend(ring[i]);
8652 }
8653 result.push(ring);
8654 } else {
8655 for (i = 0; i < len; i++) {
8656 this._projectLatlngs(latlngs[i], result, projectedBounds);
8657 }
8658 }
8659 },
8660
8661 // clip polyline by renderer bounds so that we have less to render for performance
8662 _clipPoints: function () {
8663 var bounds = this._renderer._bounds;
8664
8665 this._parts = [];
8666 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8667 return;
8668 }
8669
8670 if (this.options.noClip) {
8671 this._parts = this._rings;
8672 return;
8673 }
8674
8675 var parts = this._parts,
8676 i, j, k, len, len2, segment, points;
8677
8678 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8679 points = this._rings[i];
8680
8681 for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8682 segment = clipSegment(points[j], points[j + 1], bounds, j, true);
8683
8684 if (!segment) { continue; }
8685
8686 parts[k] = parts[k] || [];
8687 parts[k].push(segment[0]);
8688
8689 // if segment goes out of screen, or it's the last one, it's the end of the line part
8690 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8691 parts[k].push(segment[1]);
8692 k++;
8693 }
8694 }
8695 }
8696 },
8697
8698 // simplify each clipped part of the polyline for performance
8699 _simplifyPoints: function () {
8700 var parts = this._parts,
8701 tolerance = this.options.smoothFactor;
8702
8703 for (var i = 0, len = parts.length; i < len; i++) {
8704 parts[i] = simplify(parts[i], tolerance);
8705 }
8706 },
8707
8708 _update: function () {
8709 if (!this._map) { return; }
8710
8711 this._clipPoints();
8712 this._simplifyPoints();
8713 this._updatePath();
8714 },
8715
8716 _updatePath: function () {
8717 this._renderer._updatePoly(this);
8718 },
8719
8720 // Needed by the `Canvas` renderer for interactivity
8721 _containsPoint: function (p, closed) {
8722 var i, j, k, len, len2, part,
8723 w = this._clickTolerance();
8724
8725 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8726
8727 // hit detection for polylines
8728 for (i = 0, len = this._parts.length; i < len; i++) {
8729 part = this._parts[i];
8730
8731 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8732 if (!closed && (j === 0)) { continue; }
8733
8734 if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
8735 return true;
8736 }
8737 }
8738 }
8739 return false;
8740 }
8741 });
8742
8743 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8744 // Instantiates a polyline object given an array of geographical points and
8745 // optionally an options object. You can create a `Polyline` object with
8746 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8747 // of geographic points.
8748 function polyline(latlngs, options) {
8749 return new Polyline(latlngs, options);
8750 }
8751
8752 // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
8753 Polyline._flat = _flat;
8754
8755 /*
8756 * @class Polygon
8757 * @aka L.Polygon
8758 * @inherits Polyline
8759 *
8760 * A class for drawing polygon overlays on a map. Extends `Polyline`.
8761 *
8762 * Note that points you pass when creating a polygon shouldn't have an additional last point equal to the first one — it's better to filter out such points.
8763 *
8764 *
8765 * @example
8766 *
8767 * ```js
8768 * // create a red polygon from an array of LatLng points
8769 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
8770 *
8771 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8772 *
8773 * // zoom the map to the polygon
8774 * map.fitBounds(polygon.getBounds());
8775 * ```
8776 *
8777 * You can also pass an array of arrays of latlngs, with the first array representing the outer shape and the other arrays representing holes in the outer shape:
8778 *
8779 * ```js
8780 * var latlngs = [
8781 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8782 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8783 * ];
8784 * ```
8785 *
8786 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8787 *
8788 * ```js
8789 * var latlngs = [
8790 * [ // first polygon
8791 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8792 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8793 * ],
8794 * [ // second polygon
8795 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
8796 * ]
8797 * ];
8798 * ```
8799 */
8800
8801 var Polygon = Polyline.extend({
8802
8803 options: {
8804 fill: true
8805 },
8806
8807 isEmpty: function () {
8808 return !this._latlngs.length || !this._latlngs[0].length;
8809 },
8810
8811 // @method getCenter(): LatLng
8812 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the Polygon.
8813 getCenter: function () {
8814 // throws error when not yet added to map as this center calculation requires projected coordinates
8815 if (!this._map) {
8816 throw new Error('Must add layer to map before using getCenter()');
8817 }
8818 return polygonCenter(this._defaultShape(), this._map.options.crs);
8819 },
8820
8821 _convertLatLngs: function (latlngs) {
8822 var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
8823 len = result.length;
8824
8825 // remove last point if it equals first one
8826 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
8827 result.pop();
8828 }
8829 return result;
8830 },
8831
8832 _setLatLngs: function (latlngs) {
8833 Polyline.prototype._setLatLngs.call(this, latlngs);
8834 if (isFlat(this._latlngs)) {
8835 this._latlngs = [this._latlngs];
8836 }
8837 },
8838
8839 _defaultShape: function () {
8840 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8841 },
8842
8843 _clipPoints: function () {
8844 // polygons need a different clipping algorithm so we redefine that
8845
8846 var bounds = this._renderer._bounds,
8847 w = this.options.weight,
8848 p = new Point(w, w);
8849
8850 // increase clip padding by stroke width to avoid stroke on clip edges
8851 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
8852
8853 this._parts = [];
8854 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8855 return;
8856 }
8857
8858 if (this.options.noClip) {
8859 this._parts = this._rings;
8860 return;
8861 }
8862
8863 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8864 clipped = clipPolygon(this._rings[i], bounds, true);
8865 if (clipped.length) {
8866 this._parts.push(clipped);
8867 }
8868 }
8869 },
8870
8871 _updatePath: function () {
8872 this._renderer._updatePoly(this, true);
8873 },
8874
8875 // Needed by the `Canvas` renderer for interactivity
8876 _containsPoint: function (p) {
8877 var inside = false,
8878 part, p1, p2, i, j, k, len, len2;
8879
8880 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8881
8882 // ray casting algorithm for detecting if point is in polygon
8883 for (i = 0, len = this._parts.length; i < len; i++) {
8884 part = this._parts[i];
8885
8886 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8887 p1 = part[j];
8888 p2 = part[k];
8889
8890 if (((p1.y > p.y) !== (p2.y > p.y)) && (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
8891 inside = !inside;
8892 }
8893 }
8894 }
8895
8896 // also check if it's on polygon stroke
8897 return inside || Polyline.prototype._containsPoint.call(this, p, true);
8898 }
8899
8900 });
8901
8902
8903 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8904 function polygon(latlngs, options) {
8905 return new Polygon(latlngs, options);
8906 }
8907
8908 /*
8909 * @class GeoJSON
8910 * @aka L.GeoJSON
8911 * @inherits FeatureGroup
8912 *
8913 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
8914 * GeoJSON data and display it on the map. Extends `FeatureGroup`.
8915 *
8916 * @example
8917 *
8918 * ```js
8919 * L.geoJSON(data, {
8920 * style: function (feature) {
8921 * return {color: feature.properties.color};
8922 * }
8923 * }).bindPopup(function (layer) {
8924 * return layer.feature.properties.description;
8925 * }).addTo(map);
8926 * ```
8927 */
8928
8929 var GeoJSON = FeatureGroup.extend({
8930
8931 /* @section
8932 * @aka GeoJSON options
8933 *
8934 * @option pointToLayer: Function = *
8935 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
8936 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
8937 * The default is to spawn a default `Marker`:
8938 * ```js
8939 * function(geoJsonPoint, latlng) {
8940 * return L.marker(latlng);
8941 * }
8942 * ```
8943 *
8944 * @option style: Function = *
8945 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
8946 * called internally when data is added.
8947 * The default value is to not override any defaults:
8948 * ```js
8949 * function (geoJsonFeature) {
8950 * return {}
8951 * }
8952 * ```
8953 *
8954 * @option onEachFeature: Function = *
8955 * A `Function` that will be called once for each created `Feature`, after it has
8956 * been created and styled. Useful for attaching events and popups to features.
8957 * The default is to do nothing with the newly created layers:
8958 * ```js
8959 * function (feature, layer) {}
8960 * ```
8961 *
8962 * @option filter: Function = *
8963 * A `Function` that will be used to decide whether to include a feature or not.
8964 * The default is to include all features:
8965 * ```js
8966 * function (geoJsonFeature) {
8967 * return true;
8968 * }
8969 * ```
8970 * Note: dynamically changing the `filter` option will have effect only on newly
8971 * added data. It will _not_ re-evaluate already included features.
8972 *
8973 * @option coordsToLatLng: Function = *
8974 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
8975 * The default is the `coordsToLatLng` static method.
8976 *
8977 * @option markersInheritOptions: Boolean = false
8978 * Whether default Markers for "Point" type Features inherit from group options.
8979 */
8980
8981 initialize: function (geojson, options) {
8982 setOptions(this, options);
8983
8984 this._layers = {};
8985
8986 if (geojson) {
8987 this.addData(geojson);
8988 }
8989 },
8990
8991 // @method addData( <GeoJSON> data ): this
8992 // Adds a GeoJSON object to the layer.
8993 addData: function (geojson) {
8994 var features = isArray(geojson) ? geojson : geojson.features,
8995 i, len, feature;
8996
8997 if (features) {
8998 for (i = 0, len = features.length; i < len; i++) {
8999 // only add this if geometry or geometries are set and not null
9000 feature = features[i];
9001 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
9002 this.addData(feature);
9003 }
9004 }
9005 return this;
9006 }
9007
9008 var options = this.options;
9009
9010 if (options.filter && !options.filter(geojson)) { return this; }
9011
9012 var layer = geometryToLayer(geojson, options);
9013 if (!layer) {
9014 return this;
9015 }
9016 layer.feature = asFeature(geojson);
9017
9018 layer.defaultOptions = layer.options;
9019 this.resetStyle(layer);
9020
9021 if (options.onEachFeature) {
9022 options.onEachFeature(geojson, layer);
9023 }
9024
9025 return this.addLayer(layer);
9026 },
9027
9028 // @method resetStyle( <Path> layer? ): this
9029 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
9030 // If `layer` is omitted, the style of all features in the current layer is reset.
9031 resetStyle: function (layer) {
9032 if (layer === undefined) {
9033 return this.eachLayer(this.resetStyle, this);
9034 }
9035 // reset any custom styles
9036 layer.options = extend({}, layer.defaultOptions);
9037 this._setLayerStyle(layer, this.options.style);
9038 return this;
9039 },
9040
9041 // @method setStyle( <Function> style ): this
9042 // Changes styles of GeoJSON vector layers with the given style function.
9043 setStyle: function (style) {
9044 return this.eachLayer(function (layer) {
9045 this._setLayerStyle(layer, style);
9046 }, this);
9047 },
9048
9049 _setLayerStyle: function (layer, style) {
9050 if (layer.setStyle) {
9051 if (typeof style === 'function') {
9052 style = style(layer.feature);
9053 }
9054 layer.setStyle(style);
9055 }
9056 }
9057 });
9058
9059 // @section
9060 // There are several static functions which can be called without instantiating L.GeoJSON:
9061
9062 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
9063 // Creates a `Layer` from a given GeoJSON feature. Can use a custom
9064 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
9065 // functions if provided as options.
9066 function geometryToLayer(geojson, options) {
9067
9068 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
9069 coords = geometry ? geometry.coordinates : null,
9070 layers = [],
9071 pointToLayer = options && options.pointToLayer,
9072 _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
9073 latlng, latlngs, i, len;
9074
9075 if (!coords && !geometry) {
9076 return null;
9077 }
9078
9079 switch (geometry.type) {
9080 case 'Point':
9081 latlng = _coordsToLatLng(coords);
9082 return _pointToLayer(pointToLayer, geojson, latlng, options);
9083
9084 case 'MultiPoint':
9085 for (i = 0, len = coords.length; i < len; i++) {
9086 latlng = _coordsToLatLng(coords[i]);
9087 layers.push(_pointToLayer(pointToLayer, geojson, latlng, options));
9088 }
9089 return new FeatureGroup(layers);
9090
9091 case 'LineString':
9092 case 'MultiLineString':
9093 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
9094 return new Polyline(latlngs, options);
9095
9096 case 'Polygon':
9097 case 'MultiPolygon':
9098 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
9099 return new Polygon(latlngs, options);
9100
9101 case 'GeometryCollection':
9102 for (i = 0, len = geometry.geometries.length; i < len; i++) {
9103 var geoLayer = geometryToLayer({
9104 geometry: geometry.geometries[i],
9105 type: 'Feature',
9106 properties: geojson.properties
9107 }, options);
9108
9109 if (geoLayer) {
9110 layers.push(geoLayer);
9111 }
9112 }
9113 return new FeatureGroup(layers);
9114
9115 case 'FeatureCollection':
9116 for (i = 0, len = geometry.features.length; i < len; i++) {
9117 var featureLayer = geometryToLayer(geometry.features[i], options);
9118
9119 if (featureLayer) {
9120 layers.push(featureLayer);
9121 }
9122 }
9123 return new FeatureGroup(layers);
9124
9125 default:
9126 throw new Error('Invalid GeoJSON object.');
9127 }
9128 }
9129
9130 function _pointToLayer(pointToLayerFn, geojson, latlng, options) {
9131 return pointToLayerFn ?
9132 pointToLayerFn(geojson, latlng) :
9133 new Marker(latlng, options && options.markersInheritOptions && options);
9134 }
9135
9136 // @function coordsToLatLng(coords: Array): LatLng
9137 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
9138 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
9139 function coordsToLatLng(coords) {
9140 return new LatLng(coords[1], coords[0], coords[2]);
9141 }
9142
9143 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
9144 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
9145 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
9146 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
9147 function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
9148 var latlngs = [];
9149
9150 for (var i = 0, len = coords.length, latlng; i < len; i++) {
9151 latlng = levelsDeep ?
9152 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
9153 (_coordsToLatLng || coordsToLatLng)(coords[i]);
9154
9155 latlngs.push(latlng);
9156 }
9157
9158 return latlngs;
9159 }
9160
9161 // @function latLngToCoords(latlng: LatLng, precision?: Number|false): Array
9162 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
9163 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function.
9164 function latLngToCoords(latlng, precision) {
9165 latlng = toLatLng(latlng);
9166 return latlng.alt !== undefined ?
9167 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
9168 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
9169 }
9170
9171 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean, precision?: Number|false): Array
9172 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
9173 // `closed` determines whether the first point should be appended to the end of the array to close the feature, only used when `levelsDeep` is 0. False by default.
9174 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function.
9175 function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
9176 var coords = [];
9177
9178 for (var i = 0, len = latlngs.length; i < len; i++) {
9179 // Check for flat arrays required to ensure unbalanced arrays are correctly converted in recursion
9180 coords.push(levelsDeep ?
9181 latLngsToCoords(latlngs[i], isFlat(latlngs[i]) ? 0 : levelsDeep - 1, closed, precision) :
9182 latLngToCoords(latlngs[i], precision));
9183 }
9184
9185 if (!levelsDeep && closed && coords.length > 0) {
9186 coords.push(coords[0].slice());
9187 }
9188
9189 return coords;
9190 }
9191
9192 function getFeature(layer, newGeometry) {
9193 return layer.feature ?
9194 extend({}, layer.feature, {geometry: newGeometry}) :
9195 asFeature(newGeometry);
9196 }
9197
9198 // @function asFeature(geojson: Object): Object
9199 // Normalize GeoJSON geometries/features into GeoJSON features.
9200 function asFeature(geojson) {
9201 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
9202 return geojson;
9203 }
9204
9205 return {
9206 type: 'Feature',
9207 properties: {},
9208 geometry: geojson
9209 };
9210 }
9211
9212 var PointToGeoJSON = {
9213 toGeoJSON: function (precision) {
9214 return getFeature(this, {
9215 type: 'Point',
9216 coordinates: latLngToCoords(this.getLatLng(), precision)
9217 });
9218 }
9219 };
9220
9221 // @namespace Marker
9222 // @section Other methods
9223 // @method toGeoJSON(precision?: Number|false): Object
9224 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
9225 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
9226 Marker.include(PointToGeoJSON);
9227
9228 // @namespace CircleMarker
9229 // @method toGeoJSON(precision?: Number|false): Object
9230 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
9231 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
9232 Circle.include(PointToGeoJSON);
9233 CircleMarker.include(PointToGeoJSON);
9234
9235
9236 // @namespace Polyline
9237 // @method toGeoJSON(precision?: Number|false): Object
9238 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
9239 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
9240 Polyline.include({
9241 toGeoJSON: function (precision) {
9242 var multi = !isFlat(this._latlngs);
9243
9244 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
9245
9246 return getFeature(this, {
9247 type: (multi ? 'Multi' : '') + 'LineString',
9248 coordinates: coords
9249 });
9250 }
9251 });
9252
9253 // @namespace Polygon
9254 // @method toGeoJSON(precision?: Number|false): Object
9255 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
9256 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
9257 Polygon.include({
9258 toGeoJSON: function (precision) {
9259 var holes = !isFlat(this._latlngs),
9260 multi = holes && !isFlat(this._latlngs[0]);
9261
9262 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
9263
9264 if (!holes) {
9265 coords = [coords];
9266 }
9267
9268 return getFeature(this, {
9269 type: (multi ? 'Multi' : '') + 'Polygon',
9270 coordinates: coords
9271 });
9272 }
9273 });
9274
9275
9276 // @namespace LayerGroup
9277 LayerGroup.include({
9278 toMultiPoint: function (precision) {
9279 var coords = [];
9280
9281 this.eachLayer(function (layer) {
9282 coords.push(layer.toGeoJSON(precision).geometry.coordinates);
9283 });
9284
9285 return getFeature(this, {
9286 type: 'MultiPoint',
9287 coordinates: coords
9288 });
9289 },
9290
9291 // @method toGeoJSON(precision?: Number|false): Object
9292 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
9293 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
9294 toGeoJSON: function (precision) {
9295
9296 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
9297
9298 if (type === 'MultiPoint') {
9299 return this.toMultiPoint(precision);
9300 }
9301
9302 var isGeometryCollection = type === 'GeometryCollection',
9303 jsons = [];
9304
9305 this.eachLayer(function (layer) {
9306 if (layer.toGeoJSON) {
9307 var json = layer.toGeoJSON(precision);
9308 if (isGeometryCollection) {
9309 jsons.push(json.geometry);
9310 } else {
9311 var feature = asFeature(json);
9312 // Squash nested feature collections
9313 if (feature.type === 'FeatureCollection') {
9314 jsons.push.apply(jsons, feature.features);
9315 } else {
9316 jsons.push(feature);
9317 }
9318 }
9319 }
9320 });
9321
9322 if (isGeometryCollection) {
9323 return getFeature(this, {
9324 geometries: jsons,
9325 type: 'GeometryCollection'
9326 });
9327 }
9328
9329 return {
9330 type: 'FeatureCollection',
9331 features: jsons
9332 };
9333 }
9334 });
9335
9336 // @namespace GeoJSON
9337 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
9338 // Creates a GeoJSON layer. Optionally accepts an object in
9339 // [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
9340 // (you can alternatively add it later with `addData` method) and an `options` object.
9341 function geoJSON(geojson, options) {
9342 return new GeoJSON(geojson, options);
9343 }
9344
9345 // Backward compatibility.
9346 var geoJson = geoJSON;
9347
9348 /*
9349 * @class ImageOverlay
9350 * @aka L.ImageOverlay
9351 * @inherits Interactive layer
9352 *
9353 * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
9354 *
9355 * @example
9356 *
9357 * ```js
9358 * var imageUrl = 'https://maps.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
9359 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
9360 * L.imageOverlay(imageUrl, imageBounds).addTo(map);
9361 * ```
9362 */
9363
9364 var ImageOverlay = Layer.extend({
9365
9366 // @section
9367 // @aka ImageOverlay options
9368 options: {
9369 // @option opacity: Number = 1.0
9370 // The opacity of the image overlay.
9371 opacity: 1,
9372
9373 // @option alt: String = ''
9374 // Text for the `alt` attribute of the image (useful for accessibility).
9375 alt: '',
9376
9377 // @option interactive: Boolean = false
9378 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
9379 interactive: false,
9380
9381 // @option crossOrigin: Boolean|String = false
9382 // Whether the crossOrigin attribute will be added to the image.
9383 // If a String is provided, the image will have its crossOrigin attribute set to the String provided. This is needed if you want to access image pixel data.
9384 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
9385 crossOrigin: false,
9386
9387 // @option errorOverlayUrl: String = ''
9388 // URL to the overlay image to show in place of the overlay that failed to load.
9389 errorOverlayUrl: '',
9390
9391 // @option zIndex: Number = 1
9392 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
9393 zIndex: 1,
9394
9395 // @option className: String = ''
9396 // A custom class name to assign to the image. Empty by default.
9397 className: ''
9398 },
9399
9400 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
9401 this._url = url;
9402 this._bounds = toLatLngBounds(bounds);
9403
9404 setOptions(this, options);
9405 },
9406
9407 onAdd: function () {
9408 if (!this._image) {
9409 this._initImage();
9410
9411 if (this.options.opacity < 1) {
9412 this._updateOpacity();
9413 }
9414 }
9415
9416 if (this.options.interactive) {
9417 addClass(this._image, 'leaflet-interactive');
9418 this.addInteractiveTarget(this._image);
9419 }
9420
9421 this.getPane().appendChild(this._image);
9422 this._reset();
9423 },
9424
9425 onRemove: function () {
9426 remove(this._image);
9427 if (this.options.interactive) {
9428 this.removeInteractiveTarget(this._image);
9429 }
9430 },
9431
9432 // @method setOpacity(opacity: Number): this
9433 // Sets the opacity of the overlay.
9434 setOpacity: function (opacity) {
9435 this.options.opacity = opacity;
9436
9437 if (this._image) {
9438 this._updateOpacity();
9439 }
9440 return this;
9441 },
9442
9443 setStyle: function (styleOpts) {
9444 if (styleOpts.opacity) {
9445 this.setOpacity(styleOpts.opacity);
9446 }
9447 return this;
9448 },
9449
9450 // @method bringToFront(): this
9451 // Brings the layer to the top of all overlays.
9452 bringToFront: function () {
9453 if (this._map) {
9454 toFront(this._image);
9455 }
9456 return this;
9457 },
9458
9459 // @method bringToBack(): this
9460 // Brings the layer to the bottom of all overlays.
9461 bringToBack: function () {
9462 if (this._map) {
9463 toBack(this._image);
9464 }
9465 return this;
9466 },
9467
9468 // @method setUrl(url: String): this
9469 // Changes the URL of the image.
9470 setUrl: function (url) {
9471 this._url = url;
9472
9473 if (this._image) {
9474 this._image.src = url;
9475 }
9476 return this;
9477 },
9478
9479 // @method setBounds(bounds: LatLngBounds): this
9480 // Update the bounds that this ImageOverlay covers
9481 setBounds: function (bounds) {
9482 this._bounds = toLatLngBounds(bounds);
9483
9484 if (this._map) {
9485 this._reset();
9486 }
9487 return this;
9488 },
9489
9490 getEvents: function () {
9491 var events = {
9492 zoom: this._reset,
9493 viewreset: this._reset
9494 };
9495
9496 if (this._zoomAnimated) {
9497 events.zoomanim = this._animateZoom;
9498 }
9499
9500 return events;
9501 },
9502
9503 // @method setZIndex(value: Number): this
9504 // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
9505 setZIndex: function (value) {
9506 this.options.zIndex = value;
9507 this._updateZIndex();
9508 return this;
9509 },
9510
9511 // @method getBounds(): LatLngBounds
9512 // Get the bounds that this ImageOverlay covers
9513 getBounds: function () {
9514 return this._bounds;
9515 },
9516
9517 // @method getElement(): HTMLElement
9518 // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
9519 // used by this overlay.
9520 getElement: function () {
9521 return this._image;
9522 },
9523
9524 _initImage: function () {
9525 var wasElementSupplied = this._url.tagName === 'IMG';
9526 var img = this._image = wasElementSupplied ? this._url : create$1('img');
9527
9528 addClass(img, 'leaflet-image-layer');
9529 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
9530 if (this.options.className) { addClass(img, this.options.className); }
9531
9532 img.onselectstart = falseFn;
9533 img.onmousemove = falseFn;
9534
9535 // @event load: Event
9536 // Fired when the ImageOverlay layer has loaded its image
9537 img.onload = bind(this.fire, this, 'load');
9538 img.onerror = bind(this._overlayOnError, this, 'error');
9539
9540 if (this.options.crossOrigin || this.options.crossOrigin === '') {
9541 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
9542 }
9543
9544 if (this.options.zIndex) {
9545 this._updateZIndex();
9546 }
9547
9548 if (wasElementSupplied) {
9549 this._url = img.src;
9550 return;
9551 }
9552
9553 img.src = this._url;
9554 img.alt = this.options.alt;
9555 },
9556
9557 _animateZoom: function (e) {
9558 var scale = this._map.getZoomScale(e.zoom),
9559 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
9560
9561 setTransform(this._image, offset, scale);
9562 },
9563
9564 _reset: function () {
9565 var image = this._image,
9566 bounds = new Bounds(
9567 this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
9568 this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
9569 size = bounds.getSize();
9570
9571 setPosition(image, bounds.min);
9572
9573 image.style.width = size.x + 'px';
9574 image.style.height = size.y + 'px';
9575 },
9576
9577 _updateOpacity: function () {
9578 setOpacity(this._image, this.options.opacity);
9579 },
9580
9581 _updateZIndex: function () {
9582 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
9583 this._image.style.zIndex = this.options.zIndex;
9584 }
9585 },
9586
9587 _overlayOnError: function () {
9588 // @event error: Event
9589 // Fired when the ImageOverlay layer fails to load its image
9590 this.fire('error');
9591
9592 var errorUrl = this.options.errorOverlayUrl;
9593 if (errorUrl && this._url !== errorUrl) {
9594 this._url = errorUrl;
9595 this._image.src = errorUrl;
9596 }
9597 },
9598
9599 // @method getCenter(): LatLng
9600 // Returns the center of the ImageOverlay.
9601 getCenter: function () {
9602 return this._bounds.getCenter();
9603 }
9604 });
9605
9606 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
9607 // Instantiates an image overlay object given the URL of the image and the
9608 // geographical bounds it is tied to.
9609 var imageOverlay = function (url, bounds, options) {
9610 return new ImageOverlay(url, bounds, options);
9611 };
9612
9613 /*
9614 * @class VideoOverlay
9615 * @aka L.VideoOverlay
9616 * @inherits ImageOverlay
9617 *
9618 * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
9619 *
9620 * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
9621 * HTML5 element.
9622 *
9623 * @example
9624 *
9625 * ```js
9626 * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
9627 * videoBounds = [[ 32, -130], [ 13, -100]];
9628 * L.videoOverlay(videoUrl, videoBounds ).addTo(map);
9629 * ```
9630 */
9631
9632 var VideoOverlay = ImageOverlay.extend({
9633
9634 // @section
9635 // @aka VideoOverlay options
9636 options: {
9637 // @option autoplay: Boolean = true
9638 // Whether the video starts playing automatically when loaded.
9639 // On some browsers autoplay will only work with `muted: true`
9640 autoplay: true,
9641
9642 // @option loop: Boolean = true
9643 // Whether the video will loop back to the beginning when played.
9644 loop: true,
9645
9646 // @option keepAspectRatio: Boolean = true
9647 // Whether the video will save aspect ratio after the projection.
9648 // Relevant for supported browsers. See [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit)
9649 keepAspectRatio: true,
9650
9651 // @option muted: Boolean = false
9652 // Whether the video starts on mute when loaded.
9653 muted: false,
9654
9655 // @option playsInline: Boolean = true
9656 // Mobile browsers will play the video right where it is instead of open it up in fullscreen mode.
9657 playsInline: true
9658 },
9659
9660 _initImage: function () {
9661 var wasElementSupplied = this._url.tagName === 'VIDEO';
9662 var vid = this._image = wasElementSupplied ? this._url : create$1('video');
9663
9664 addClass(vid, 'leaflet-image-layer');
9665 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
9666 if (this.options.className) { addClass(vid, this.options.className); }
9667
9668 vid.onselectstart = falseFn;
9669 vid.onmousemove = falseFn;
9670
9671 // @event load: Event
9672 // Fired when the video has finished loading the first frame
9673 vid.onloadeddata = bind(this.fire, this, 'load');
9674
9675 if (wasElementSupplied) {
9676 var sourceElements = vid.getElementsByTagName('source');
9677 var sources = [];
9678 for (var j = 0; j < sourceElements.length; j++) {
9679 sources.push(sourceElements[j].src);
9680 }
9681
9682 this._url = (sourceElements.length > 0) ? sources : [vid.src];
9683 return;
9684 }
9685
9686 if (!isArray(this._url)) { this._url = [this._url]; }
9687
9688 if (!this.options.keepAspectRatio && Object.prototype.hasOwnProperty.call(vid.style, 'objectFit')) {
9689 vid.style['objectFit'] = 'fill';
9690 }
9691 vid.autoplay = !!this.options.autoplay;
9692 vid.loop = !!this.options.loop;
9693 vid.muted = !!this.options.muted;
9694 vid.playsInline = !!this.options.playsInline;
9695 for (var i = 0; i < this._url.length; i++) {
9696 var source = create$1('source');
9697 source.src = this._url[i];
9698 vid.appendChild(source);
9699 }
9700 }
9701
9702 // @method getElement(): HTMLVideoElement
9703 // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
9704 // used by this overlay.
9705 });
9706
9707
9708 // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
9709 // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
9710 // geographical bounds it is tied to.
9711
9712 function videoOverlay(video, bounds, options) {
9713 return new VideoOverlay(video, bounds, options);
9714 }
9715
9716 /*
9717 * @class SVGOverlay
9718 * @aka L.SVGOverlay
9719 * @inherits ImageOverlay
9720 *
9721 * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`.
9722 *
9723 * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element.
9724 *
9725 * @example
9726 *
9727 * ```js
9728 * var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
9729 * svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg");
9730 * svgElement.setAttribute('viewBox', "0 0 200 200");
9731 * svgElement.innerHTML = '<rect width="200" height="200"/><rect x="75" y="23" width="50" height="50" style="fill:red"/><rect x="75" y="123" width="50" height="50" style="fill:#0013ff"/>';
9732 * var svgElementBounds = [ [ 32, -130 ], [ 13, -100 ] ];
9733 * L.svgOverlay(svgElement, svgElementBounds).addTo(map);
9734 * ```
9735 */
9736
9737 var SVGOverlay = ImageOverlay.extend({
9738 _initImage: function () {
9739 var el = this._image = this._url;
9740
9741 addClass(el, 'leaflet-image-layer');
9742 if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); }
9743 if (this.options.className) { addClass(el, this.options.className); }
9744
9745 el.onselectstart = falseFn;
9746 el.onmousemove = falseFn;
9747 }
9748
9749 // @method getElement(): SVGElement
9750 // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement)
9751 // used by this overlay.
9752 });
9753
9754
9755 // @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options)
9756 // Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to.
9757 // A viewBox attribute is required on the SVG element to zoom in and out properly.
9758
9759 function svgOverlay(el, bounds, options) {
9760 return new SVGOverlay(el, bounds, options);
9761 }
9762
9763 /*
9764 * @class DivOverlay
9765 * @inherits Interactive layer
9766 * @aka L.DivOverlay
9767 * Base model for L.Popup and L.Tooltip. Inherit from it for custom overlays like plugins.
9768 */
9769
9770 // @namespace DivOverlay
9771 var DivOverlay = Layer.extend({
9772
9773 // @section
9774 // @aka DivOverlay options
9775 options: {
9776 // @option interactive: Boolean = false
9777 // If true, the popup/tooltip will listen to the mouse events.
9778 interactive: false,
9779
9780 // @option offset: Point = Point(0, 0)
9781 // The offset of the overlay position.
9782 offset: [0, 0],
9783
9784 // @option className: String = ''
9785 // A custom CSS class name to assign to the overlay.
9786 className: '',
9787
9788 // @option pane: String = undefined
9789 // `Map pane` where the overlay will be added.
9790 pane: undefined,
9791
9792 // @option content: String|HTMLElement|Function = ''
9793 // Sets the HTML content of the overlay while initializing. If a function is passed the source layer will be
9794 // passed to the function. The function should return a `String` or `HTMLElement` to be used in the overlay.
9795 content: ''
9796 },
9797
9798 initialize: function (options, source) {
9799 if (options && (options instanceof LatLng || isArray(options))) {
9800 this._latlng = toLatLng(options);
9801 setOptions(this, source);
9802 } else {
9803 setOptions(this, options);
9804 this._source = source;
9805 }
9806 if (this.options.content) {
9807 this._content = this.options.content;
9808 }
9809 },
9810
9811 // @method openOn(map: Map): this
9812 // Adds the overlay to the map.
9813 // Alternative to `map.openPopup(popup)`/`.openTooltip(tooltip)`.
9814 openOn: function (map) {
9815 map = arguments.length ? map : this._source._map; // experimental, not the part of public api
9816 if (!map.hasLayer(this)) {
9817 map.addLayer(this);
9818 }
9819 return this;
9820 },
9821
9822 // @method close(): this
9823 // Closes the overlay.
9824 // Alternative to `map.closePopup(popup)`/`.closeTooltip(tooltip)`
9825 // and `layer.closePopup()`/`.closeTooltip()`.
9826 close: function () {
9827 if (this._map) {
9828 this._map.removeLayer(this);
9829 }
9830 return this;
9831 },
9832
9833 // @method toggle(layer?: Layer): this
9834 // Opens or closes the overlay bound to layer depending on its current state.
9835 // Argument may be omitted only for overlay bound to layer.
9836 // Alternative to `layer.togglePopup()`/`.toggleTooltip()`.
9837 toggle: function (layer) {
9838 if (this._map) {
9839 this.close();
9840 } else {
9841 if (arguments.length) {
9842 this._source = layer;
9843 } else {
9844 layer = this._source;
9845 }
9846 this._prepareOpen();
9847
9848 // open the overlay on the map
9849 this.openOn(layer._map);
9850 }
9851 return this;
9852 },
9853
9854 onAdd: function (map) {
9855 this._zoomAnimated = map._zoomAnimated;
9856
9857 if (!this._container) {
9858 this._initLayout();
9859 }
9860
9861 if (map._fadeAnimated) {
9862 setOpacity(this._container, 0);
9863 }
9864
9865 clearTimeout(this._removeTimeout);
9866 this.getPane().appendChild(this._container);
9867 this.update();
9868
9869 if (map._fadeAnimated) {
9870 setOpacity(this._container, 1);
9871 }
9872
9873 this.bringToFront();
9874
9875 if (this.options.interactive) {
9876 addClass(this._container, 'leaflet-interactive');
9877 this.addInteractiveTarget(this._container);
9878 }
9879 },
9880
9881 onRemove: function (map) {
9882 if (map._fadeAnimated) {
9883 setOpacity(this._container, 0);
9884 this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
9885 } else {
9886 remove(this._container);
9887 }
9888
9889 if (this.options.interactive) {
9890 removeClass(this._container, 'leaflet-interactive');
9891 this.removeInteractiveTarget(this._container);
9892 }
9893 },
9894
9895 // @namespace DivOverlay
9896 // @method getLatLng: LatLng
9897 // Returns the geographical point of the overlay.
9898 getLatLng: function () {
9899 return this._latlng;
9900 },
9901
9902 // @method setLatLng(latlng: LatLng): this
9903 // Sets the geographical point where the overlay will open.
9904 setLatLng: function (latlng) {
9905 this._latlng = toLatLng(latlng);
9906 if (this._map) {
9907 this._updatePosition();
9908 this._adjustPan();
9909 }
9910 return this;
9911 },
9912
9913 // @method getContent: String|HTMLElement
9914 // Returns the content of the overlay.
9915 getContent: function () {
9916 return this._content;
9917 },
9918
9919 // @method setContent(htmlContent: String|HTMLElement|Function): this
9920 // Sets the HTML content of the overlay. If a function is passed the source layer will be passed to the function.
9921 // The function should return a `String` or `HTMLElement` to be used in the overlay.
9922 setContent: function (content) {
9923 this._content = content;
9924 this.update();
9925 return this;
9926 },
9927
9928 // @method getElement: String|HTMLElement
9929 // Returns the HTML container of the overlay.
9930 getElement: function () {
9931 return this._container;
9932 },
9933
9934 // @method update: null
9935 // Updates the overlay content, layout and position. Useful for updating the overlay after something inside changed, e.g. image loaded.
9936 update: function () {
9937 if (!this._map) { return; }
9938
9939 this._container.style.visibility = 'hidden';
9940
9941 this._updateContent();
9942 this._updateLayout();
9943 this._updatePosition();
9944
9945 this._container.style.visibility = '';
9946
9947 this._adjustPan();
9948 },
9949
9950 getEvents: function () {
9951 var events = {
9952 zoom: this._updatePosition,
9953 viewreset: this._updatePosition
9954 };
9955
9956 if (this._zoomAnimated) {
9957 events.zoomanim = this._animateZoom;
9958 }
9959 return events;
9960 },
9961
9962 // @method isOpen: Boolean
9963 // Returns `true` when the overlay is visible on the map.
9964 isOpen: function () {
9965 return !!this._map && this._map.hasLayer(this);
9966 },
9967
9968 // @method bringToFront: this
9969 // Brings this overlay in front of other overlays (in the same map pane).
9970 bringToFront: function () {
9971 if (this._map) {
9972 toFront(this._container);
9973 }
9974 return this;
9975 },
9976
9977 // @method bringToBack: this
9978 // Brings this overlay to the back of other overlays (in the same map pane).
9979 bringToBack: function () {
9980 if (this._map) {
9981 toBack(this._container);
9982 }
9983 return this;
9984 },
9985
9986 // prepare bound overlay to open: update latlng pos / content source (for FeatureGroup)
9987 _prepareOpen: function (latlng) {
9988 var source = this._source;
9989 if (!source._map) { return false; }
9990
9991 if (source instanceof FeatureGroup) {
9992 source = null;
9993 var layers = this._source._layers;
9994 for (var id in layers) {
9995 if (layers[id]._map) {
9996 source = layers[id];
9997 break;
9998 }
9999 }
10000 if (!source) { return false; } // Unable to get source layer.
10001
10002 // set overlay source to this layer
10003 this._source = source;
10004 }
10005
10006 if (!latlng) {
10007 if (source.getCenter) {
10008 latlng = source.getCenter();
10009 } else if (source.getLatLng) {
10010 latlng = source.getLatLng();
10011 } else if (source.getBounds) {
10012 latlng = source.getBounds().getCenter();
10013 } else {
10014 throw new Error('Unable to get source layer LatLng.');
10015 }
10016 }
10017 this.setLatLng(latlng);
10018
10019 if (this._map) {
10020 // update the overlay (content, layout, etc...)
10021 this.update();
10022 }
10023
10024 return true;
10025 },
10026
10027 _updateContent: function () {
10028 if (!this._content) { return; }
10029
10030 var node = this._contentNode;
10031 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
10032
10033 if (typeof content === 'string') {
10034 node.innerHTML = content;
10035 } else {
10036 while (node.hasChildNodes()) {
10037 node.removeChild(node.firstChild);
10038 }
10039 node.appendChild(content);
10040 }
10041
10042 // @namespace DivOverlay
10043 // @section DivOverlay events
10044 // @event contentupdate: Event
10045 // Fired when the content of the overlay is updated
10046 this.fire('contentupdate');
10047 },
10048
10049 _updatePosition: function () {
10050 if (!this._map) { return; }
10051
10052 var pos = this._map.latLngToLayerPoint(this._latlng),
10053 offset = toPoint(this.options.offset),
10054 anchor = this._getAnchor();
10055
10056 if (this._zoomAnimated) {
10057 setPosition(this._container, pos.add(anchor));
10058 } else {
10059 offset = offset.add(pos).add(anchor);
10060 }
10061
10062 var bottom = this._containerBottom = -offset.y,
10063 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
10064
10065 // bottom position the overlay in case the height of the overlay changes (images loading etc)
10066 this._container.style.bottom = bottom + 'px';
10067 this._container.style.left = left + 'px';
10068 },
10069
10070 _getAnchor: function () {
10071 return [0, 0];
10072 }
10073
10074 });
10075
10076 Map.include({
10077 _initOverlay: function (OverlayClass, content, latlng, options) {
10078 var overlay = content;
10079 if (!(overlay instanceof OverlayClass)) {
10080 overlay = new OverlayClass(options).setContent(content);
10081 }
10082 if (latlng) {
10083 overlay.setLatLng(latlng);
10084 }
10085 return overlay;
10086 }
10087 });
10088
10089
10090 Layer.include({
10091 _initOverlay: function (OverlayClass, old, content, options) {
10092 var overlay = content;
10093 if (overlay instanceof OverlayClass) {
10094 setOptions(overlay, options);
10095 overlay._source = this;
10096 } else {
10097 overlay = (old && !options) ? old : new OverlayClass(options, this);
10098 overlay.setContent(content);
10099 }
10100 return overlay;
10101 }
10102 });
10103
10104 /*
10105 * @class Popup
10106 * @inherits DivOverlay
10107 * @aka L.Popup
10108 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
10109 * open popups while making sure that only one popup is open at one time
10110 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
10111 *
10112 * @example
10113 *
10114 * If you want to just bind a popup to marker click and then open it, it's really easy:
10115 *
10116 * ```js
10117 * marker.bindPopup(popupContent).openPopup();
10118 * ```
10119 * Path overlays like polylines also have a `bindPopup` method.
10120 *
10121 * A popup can be also standalone:
10122 *
10123 * ```js
10124 * var popup = L.popup()
10125 * .setLatLng(latlng)
10126 * .setContent('<p>Hello world!<br />This is a nice popup.</p>')
10127 * .openOn(map);
10128 * ```
10129 * or
10130 * ```js
10131 * var popup = L.popup(latlng, {content: '<p>Hello world!<br />This is a nice popup.</p>')
10132 * .openOn(map);
10133 * ```
10134 */
10135
10136
10137 // @namespace Popup
10138 var Popup = DivOverlay.extend({
10139
10140 // @section
10141 // @aka Popup options
10142 options: {
10143 // @option pane: String = 'popupPane'
10144 // `Map pane` where the popup will be added.
10145 pane: 'popupPane',
10146
10147 // @option offset: Point = Point(0, 7)
10148 // The offset of the popup position.
10149 offset: [0, 7],
10150
10151 // @option maxWidth: Number = 300
10152 // Max width of the popup, in pixels.
10153 maxWidth: 300,
10154
10155 // @option minWidth: Number = 50
10156 // Min width of the popup, in pixels.
10157 minWidth: 50,
10158
10159 // @option maxHeight: Number = null
10160 // If set, creates a scrollable container of the given height
10161 // inside a popup if its content exceeds it.
10162 // The scrollable container can be styled using the
10163 // `leaflet-popup-scrolled` CSS class selector.
10164 maxHeight: null,
10165
10166 // @option autoPan: Boolean = true
10167 // Set it to `false` if you don't want the map to do panning animation
10168 // to fit the opened popup.
10169 autoPan: true,
10170
10171 // @option autoPanPaddingTopLeft: Point = null
10172 // The margin between the popup and the top left corner of the map
10173 // view after autopanning was performed.
10174 autoPanPaddingTopLeft: null,
10175
10176 // @option autoPanPaddingBottomRight: Point = null
10177 // The margin between the popup and the bottom right corner of the map
10178 // view after autopanning was performed.
10179 autoPanPaddingBottomRight: null,
10180
10181 // @option autoPanPadding: Point = Point(5, 5)
10182 // Equivalent of setting both top left and bottom right autopan padding to the same value.
10183 autoPanPadding: [5, 5],
10184
10185 // @option keepInView: Boolean = false
10186 // Set it to `true` if you want to prevent users from panning the popup
10187 // off of the screen while it is open.
10188 keepInView: false,
10189
10190 // @option closeButton: Boolean = true
10191 // Controls the presence of a close button in the popup.
10192 closeButton: true,
10193
10194 // @option autoClose: Boolean = true
10195 // Set it to `false` if you want to override the default behavior of
10196 // the popup closing when another popup is opened.
10197 autoClose: true,
10198
10199 // @option closeOnEscapeKey: Boolean = true
10200 // Set it to `false` if you want to override the default behavior of
10201 // the ESC key for closing of the popup.
10202 closeOnEscapeKey: true,
10203
10204 // @option closeOnClick: Boolean = *
10205 // Set it if you want to override the default behavior of the popup closing when user clicks
10206 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
10207
10208 // @option className: String = ''
10209 // A custom CSS class name to assign to the popup.
10210 className: ''
10211 },
10212
10213 // @namespace Popup
10214 // @method openOn(map: Map): this
10215 // Alternative to `map.openPopup(popup)`.
10216 // Adds the popup to the map and closes the previous one.
10217 openOn: function (map) {
10218 map = arguments.length ? map : this._source._map; // experimental, not the part of public api
10219
10220 if (!map.hasLayer(this) && map._popup && map._popup.options.autoClose) {
10221 map.removeLayer(map._popup);
10222 }
10223 map._popup = this;
10224
10225 return DivOverlay.prototype.openOn.call(this, map);
10226 },
10227
10228 onAdd: function (map) {
10229 DivOverlay.prototype.onAdd.call(this, map);
10230
10231 // @namespace Map
10232 // @section Popup events
10233 // @event popupopen: PopupEvent
10234 // Fired when a popup is opened in the map
10235 map.fire('popupopen', {popup: this});
10236
10237 if (this._source) {
10238 // @namespace Layer
10239 // @section Popup events
10240 // @event popupopen: PopupEvent
10241 // Fired when a popup bound to this layer is opened
10242 this._source.fire('popupopen', {popup: this}, true);
10243 // For non-path layers, we toggle the popup when clicking
10244 // again the layer, so prevent the map to reopen it.
10245 if (!(this._source instanceof Path)) {
10246 this._source.on('preclick', stopPropagation);
10247 }
10248 }
10249 },
10250
10251 onRemove: function (map) {
10252 DivOverlay.prototype.onRemove.call(this, map);
10253
10254 // @namespace Map
10255 // @section Popup events
10256 // @event popupclose: PopupEvent
10257 // Fired when a popup in the map is closed
10258 map.fire('popupclose', {popup: this});
10259
10260 if (this._source) {
10261 // @namespace Layer
10262 // @section Popup events
10263 // @event popupclose: PopupEvent
10264 // Fired when a popup bound to this layer is closed
10265 this._source.fire('popupclose', {popup: this}, true);
10266 if (!(this._source instanceof Path)) {
10267 this._source.off('preclick', stopPropagation);
10268 }
10269 }
10270 },
10271
10272 getEvents: function () {
10273 var events = DivOverlay.prototype.getEvents.call(this);
10274
10275 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
10276 events.preclick = this.close;
10277 }
10278
10279 if (this.options.keepInView) {
10280 events.moveend = this._adjustPan;
10281 }
10282
10283 return events;
10284 },
10285
10286 _initLayout: function () {
10287 var prefix = 'leaflet-popup',
10288 container = this._container = create$1('div',
10289 prefix + ' ' + (this.options.className || '') +
10290 ' leaflet-zoom-animated');
10291
10292 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
10293 this._contentNode = create$1('div', prefix + '-content', wrapper);
10294
10295 disableClickPropagation(container);
10296 disableScrollPropagation(this._contentNode);
10297 on(container, 'contextmenu', stopPropagation);
10298
10299 this._tipContainer = create$1('div', prefix + '-tip-container', container);
10300 this._tip = create$1('div', prefix + '-tip', this._tipContainer);
10301
10302 if (this.options.closeButton) {
10303 var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
10304 closeButton.setAttribute('role', 'button'); // overrides the implicit role=link of <a> elements #7399
10305 closeButton.setAttribute('aria-label', 'Close popup');
10306 closeButton.href = '#close';
10307 closeButton.innerHTML = '<span aria-hidden="true">&#215;</span>';
10308
10309 on(closeButton, 'click', function (ev) {
10310 preventDefault(ev);
10311 this.close();
10312 }, this);
10313 }
10314 },
10315
10316 _updateLayout: function () {
10317 var container = this._contentNode,
10318 style = container.style;
10319
10320 style.width = '';
10321 style.whiteSpace = 'nowrap';
10322
10323 var width = container.offsetWidth;
10324 width = Math.min(width, this.options.maxWidth);
10325 width = Math.max(width, this.options.minWidth);
10326
10327 style.width = (width + 1) + 'px';
10328 style.whiteSpace = '';
10329
10330 style.height = '';
10331
10332 var height = container.offsetHeight,
10333 maxHeight = this.options.maxHeight,
10334 scrolledClass = 'leaflet-popup-scrolled';
10335
10336 if (maxHeight && height > maxHeight) {
10337 style.height = maxHeight + 'px';
10338 addClass(container, scrolledClass);
10339 } else {
10340 removeClass(container, scrolledClass);
10341 }
10342
10343 this._containerWidth = this._container.offsetWidth;
10344 },
10345
10346 _animateZoom: function (e) {
10347 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
10348 anchor = this._getAnchor();
10349 setPosition(this._container, pos.add(anchor));
10350 },
10351
10352 _adjustPan: function () {
10353 if (!this.options.autoPan) { return; }
10354 if (this._map._panAnim) { this._map._panAnim.stop(); }
10355
10356 // We can endlessly recurse if keepInView is set and the view resets.
10357 // Let's guard against that by exiting early if we're responding to our own autopan.
10358 if (this._autopanning) {
10359 this._autopanning = false;
10360 return;
10361 }
10362
10363 var map = this._map,
10364 marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
10365 containerHeight = this._container.offsetHeight + marginBottom,
10366 containerWidth = this._containerWidth,
10367 layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
10368
10369 layerPos._add(getPosition(this._container));
10370
10371 var containerPos = map.layerPointToContainerPoint(layerPos),
10372 padding = toPoint(this.options.autoPanPadding),
10373 paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
10374 paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
10375 size = map.getSize(),
10376 dx = 0,
10377 dy = 0;
10378
10379 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
10380 dx = containerPos.x + containerWidth - size.x + paddingBR.x;
10381 }
10382 if (containerPos.x - dx - paddingTL.x < 0) { // left
10383 dx = containerPos.x - paddingTL.x;
10384 }
10385 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
10386 dy = containerPos.y + containerHeight - size.y + paddingBR.y;
10387 }
10388 if (containerPos.y - dy - paddingTL.y < 0) { // top
10389 dy = containerPos.y - paddingTL.y;
10390 }
10391
10392 // @namespace Map
10393 // @section Popup events
10394 // @event autopanstart: Event
10395 // Fired when the map starts autopanning when opening a popup.
10396 if (dx || dy) {
10397 // Track that we're autopanning, as this function will be re-ran on moveend
10398 if (this.options.keepInView) {
10399 this._autopanning = true;
10400 }
10401
10402 map
10403 .fire('autopanstart')
10404 .panBy([dx, dy]);
10405 }
10406 },
10407
10408 _getAnchor: function () {
10409 // Where should we anchor the popup on the source layer?
10410 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
10411 }
10412
10413 });
10414
10415 // @namespace Popup
10416 // @factory L.popup(options?: Popup options, source?: Layer)
10417 // Instantiates a `Popup` object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the popup with a reference to the Layer to which it refers.
10418 // @alternative
10419 // @factory L.popup(latlng: LatLng, options?: Popup options)
10420 // Instantiates a `Popup` object given `latlng` where the popup will open and an optional `options` object that describes its appearance and location.
10421 var popup = function (options, source) {
10422 return new Popup(options, source);
10423 };
10424
10425
10426 /* @namespace Map
10427 * @section Interaction Options
10428 * @option closePopupOnClick: Boolean = true
10429 * Set it to `false` if you don't want popups to close when user clicks the map.
10430 */
10431 Map.mergeOptions({
10432 closePopupOnClick: true
10433 });
10434
10435
10436 // @namespace Map
10437 // @section Methods for Layers and Controls
10438 Map.include({
10439 // @method openPopup(popup: Popup): this
10440 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
10441 // @alternative
10442 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
10443 // Creates a popup with the specified content and options and opens it in the given point on a map.
10444 openPopup: function (popup, latlng, options) {
10445 this._initOverlay(Popup, popup, latlng, options)
10446 .openOn(this);
10447
10448 return this;
10449 },
10450
10451 // @method closePopup(popup?: Popup): this
10452 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
10453 closePopup: function (popup) {
10454 popup = arguments.length ? popup : this._popup;
10455 if (popup) {
10456 popup.close();
10457 }
10458 return this;
10459 }
10460 });
10461
10462 /*
10463 * @namespace Layer
10464 * @section Popup methods example
10465 *
10466 * All layers share a set of methods convenient for binding popups to it.
10467 *
10468 * ```js
10469 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
10470 * layer.openPopup();
10471 * layer.closePopup();
10472 * ```
10473 *
10474 * Popups will also be automatically opened when the layer is clicked on and closed when the layer is removed from the map or another popup is opened.
10475 */
10476
10477 // @section Popup methods
10478 Layer.include({
10479
10480 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
10481 // Binds a popup to the layer with the passed `content` and sets up the
10482 // necessary event listeners. If a `Function` is passed it will receive
10483 // the layer as the first argument and should return a `String` or `HTMLElement`.
10484 bindPopup: function (content, options) {
10485 this._popup = this._initOverlay(Popup, this._popup, content, options);
10486 if (!this._popupHandlersAdded) {
10487 this.on({
10488 click: this._openPopup,
10489 keypress: this._onKeyPress,
10490 remove: this.closePopup,
10491 move: this._movePopup
10492 });
10493 this._popupHandlersAdded = true;
10494 }
10495
10496 return this;
10497 },
10498
10499 // @method unbindPopup(): this
10500 // Removes the popup previously bound with `bindPopup`.
10501 unbindPopup: function () {
10502 if (this._popup) {
10503 this.off({
10504 click: this._openPopup,
10505 keypress: this._onKeyPress,
10506 remove: this.closePopup,
10507 move: this._movePopup
10508 });
10509 this._popupHandlersAdded = false;
10510 this._popup = null;
10511 }
10512 return this;
10513 },
10514
10515 // @method openPopup(latlng?: LatLng): this
10516 // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
10517 openPopup: function (latlng) {
10518 if (this._popup) {
10519 if (!(this instanceof FeatureGroup)) {
10520 this._popup._source = this;
10521 }
10522 if (this._popup._prepareOpen(latlng || this._latlng)) {
10523 // open the popup on the map
10524 this._popup.openOn(this._map);
10525 }
10526 }
10527 return this;
10528 },
10529
10530 // @method closePopup(): this
10531 // Closes the popup bound to this layer if it is open.
10532 closePopup: function () {
10533 if (this._popup) {
10534 this._popup.close();
10535 }
10536 return this;
10537 },
10538
10539 // @method togglePopup(): this
10540 // Opens or closes the popup bound to this layer depending on its current state.
10541 togglePopup: function () {
10542 if (this._popup) {
10543 this._popup.toggle(this);
10544 }
10545 return this;
10546 },
10547
10548 // @method isPopupOpen(): boolean
10549 // Returns `true` if the popup bound to this layer is currently open.
10550 isPopupOpen: function () {
10551 return (this._popup ? this._popup.isOpen() : false);
10552 },
10553
10554 // @method setPopupContent(content: String|HTMLElement|Popup): this
10555 // Sets the content of the popup bound to this layer.
10556 setPopupContent: function (content) {
10557 if (this._popup) {
10558 this._popup.setContent(content);
10559 }
10560 return this;
10561 },
10562
10563 // @method getPopup(): Popup
10564 // Returns the popup bound to this layer.
10565 getPopup: function () {
10566 return this._popup;
10567 },
10568
10569 _openPopup: function (e) {
10570 if (!this._popup || !this._map) {
10571 return;
10572 }
10573 // prevent map click
10574 stop(e);
10575
10576 var target = e.layer || e.target;
10577 if (this._popup._source === target && !(target instanceof Path)) {
10578 // treat it like a marker and figure out
10579 // if we should toggle it open/closed
10580 if (this._map.hasLayer(this._popup)) {
10581 this.closePopup();
10582 } else {
10583 this.openPopup(e.latlng);
10584 }
10585 return;
10586 }
10587 this._popup._source = target;
10588 this.openPopup(e.latlng);
10589 },
10590
10591 _movePopup: function (e) {
10592 this._popup.setLatLng(e.latlng);
10593 },
10594
10595 _onKeyPress: function (e) {
10596 if (e.originalEvent.keyCode === 13) {
10597 this._openPopup(e);
10598 }
10599 }
10600 });
10601
10602 /*
10603 * @class Tooltip
10604 * @inherits DivOverlay
10605 * @aka L.Tooltip
10606 * Used to display small texts on top of map layers.
10607 *
10608 * @example
10609 * If you want to just bind a tooltip to marker:
10610 *
10611 * ```js
10612 * marker.bindTooltip("my tooltip text").openTooltip();
10613 * ```
10614 * Path overlays like polylines also have a `bindTooltip` method.
10615 *
10616 * A tooltip can be also standalone:
10617 *
10618 * ```js
10619 * var tooltip = L.tooltip()
10620 * .setLatLng(latlng)
10621 * .setContent('Hello world!<br />This is a nice tooltip.')
10622 * .addTo(map);
10623 * ```
10624 * or
10625 * ```js
10626 * var tooltip = L.tooltip(latlng, {content: 'Hello world!<br />This is a nice tooltip.'})
10627 * .addTo(map);
10628 * ```
10629 *
10630 *
10631 * Note about tooltip offset. Leaflet takes two options in consideration
10632 * for computing tooltip offsetting:
10633 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
10634 * Add a positive x offset to move the tooltip to the right, and a positive y offset to
10635 * move it to the bottom. Negatives will move to the left and top.
10636 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
10637 * should adapt this value if you use a custom icon.
10638 */
10639
10640
10641 // @namespace Tooltip
10642 var Tooltip = DivOverlay.extend({
10643
10644 // @section
10645 // @aka Tooltip options
10646 options: {
10647 // @option pane: String = 'tooltipPane'
10648 // `Map pane` where the tooltip will be added.
10649 pane: 'tooltipPane',
10650
10651 // @option offset: Point = Point(0, 0)
10652 // Optional offset of the tooltip position.
10653 offset: [0, 0],
10654
10655 // @option direction: String = 'auto'
10656 // Direction where to open the tooltip. Possible values are: `right`, `left`,
10657 // `top`, `bottom`, `center`, `auto`.
10658 // `auto` will dynamically switch between `right` and `left` according to the tooltip
10659 // position on the map.
10660 direction: 'auto',
10661
10662 // @option permanent: Boolean = false
10663 // Whether to open the tooltip permanently or only on mouseover.
10664 permanent: false,
10665
10666 // @option sticky: Boolean = false
10667 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
10668 sticky: false,
10669
10670 // @option opacity: Number = 0.9
10671 // Tooltip container opacity.
10672 opacity: 0.9
10673 },
10674
10675 onAdd: function (map) {
10676 DivOverlay.prototype.onAdd.call(this, map);
10677 this.setOpacity(this.options.opacity);
10678
10679 // @namespace Map
10680 // @section Tooltip events
10681 // @event tooltipopen: TooltipEvent
10682 // Fired when a tooltip is opened in the map.
10683 map.fire('tooltipopen', {tooltip: this});
10684
10685 if (this._source) {
10686 this.addEventParent(this._source);
10687
10688 // @namespace Layer
10689 // @section Tooltip events
10690 // @event tooltipopen: TooltipEvent
10691 // Fired when a tooltip bound to this layer is opened.
10692 this._source.fire('tooltipopen', {tooltip: this}, true);
10693 }
10694 },
10695
10696 onRemove: function (map) {
10697 DivOverlay.prototype.onRemove.call(this, map);
10698
10699 // @namespace Map
10700 // @section Tooltip events
10701 // @event tooltipclose: TooltipEvent
10702 // Fired when a tooltip in the map is closed.
10703 map.fire('tooltipclose', {tooltip: this});
10704
10705 if (this._source) {
10706 this.removeEventParent(this._source);
10707
10708 // @namespace Layer
10709 // @section Tooltip events
10710 // @event tooltipclose: TooltipEvent
10711 // Fired when a tooltip bound to this layer is closed.
10712 this._source.fire('tooltipclose', {tooltip: this}, true);
10713 }
10714 },
10715
10716 getEvents: function () {
10717 var events = DivOverlay.prototype.getEvents.call(this);
10718
10719 if (!this.options.permanent) {
10720 events.preclick = this.close;
10721 }
10722
10723 return events;
10724 },
10725
10726 _initLayout: function () {
10727 var prefix = 'leaflet-tooltip',
10728 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
10729
10730 this._contentNode = this._container = create$1('div', className);
10731
10732 this._container.setAttribute('role', 'tooltip');
10733 this._container.setAttribute('id', 'leaflet-tooltip-' + stamp(this));
10734 },
10735
10736 _updateLayout: function () {},
10737
10738 _adjustPan: function () {},
10739
10740 _setPosition: function (pos) {
10741 var subX, subY,
10742 map = this._map,
10743 container = this._container,
10744 centerPoint = map.latLngToContainerPoint(map.getCenter()),
10745 tooltipPoint = map.layerPointToContainerPoint(pos),
10746 direction = this.options.direction,
10747 tooltipWidth = container.offsetWidth,
10748 tooltipHeight = container.offsetHeight,
10749 offset = toPoint(this.options.offset),
10750 anchor = this._getAnchor();
10751
10752 if (direction === 'top') {
10753 subX = tooltipWidth / 2;
10754 subY = tooltipHeight;
10755 } else if (direction === 'bottom') {
10756 subX = tooltipWidth / 2;
10757 subY = 0;
10758 } else if (direction === 'center') {
10759 subX = tooltipWidth / 2;
10760 subY = tooltipHeight / 2;
10761 } else if (direction === 'right') {
10762 subX = 0;
10763 subY = tooltipHeight / 2;
10764 } else if (direction === 'left') {
10765 subX = tooltipWidth;
10766 subY = tooltipHeight / 2;
10767 } else if (tooltipPoint.x < centerPoint.x) {
10768 direction = 'right';
10769 subX = 0;
10770 subY = tooltipHeight / 2;
10771 } else {
10772 direction = 'left';
10773 subX = tooltipWidth + (offset.x + anchor.x) * 2;
10774 subY = tooltipHeight / 2;
10775 }
10776
10777 pos = pos.subtract(toPoint(subX, subY, true)).add(offset).add(anchor);
10778
10779 removeClass(container, 'leaflet-tooltip-right');
10780 removeClass(container, 'leaflet-tooltip-left');
10781 removeClass(container, 'leaflet-tooltip-top');
10782 removeClass(container, 'leaflet-tooltip-bottom');
10783 addClass(container, 'leaflet-tooltip-' + direction);
10784 setPosition(container, pos);
10785 },
10786
10787 _updatePosition: function () {
10788 var pos = this._map.latLngToLayerPoint(this._latlng);
10789 this._setPosition(pos);
10790 },
10791
10792 setOpacity: function (opacity) {
10793 this.options.opacity = opacity;
10794
10795 if (this._container) {
10796 setOpacity(this._container, opacity);
10797 }
10798 },
10799
10800 _animateZoom: function (e) {
10801 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
10802 this._setPosition(pos);
10803 },
10804
10805 _getAnchor: function () {
10806 // Where should we anchor the tooltip on the source layer?
10807 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
10808 }
10809
10810 });
10811
10812 // @namespace Tooltip
10813 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
10814 // Instantiates a `Tooltip` object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the tooltip with a reference to the Layer to which it refers.
10815 // @alternative
10816 // @factory L.tooltip(latlng: LatLng, options?: Tooltip options)
10817 // Instantiates a `Tooltip` object given `latlng` where the tooltip will open and an optional `options` object that describes its appearance and location.
10818 var tooltip = function (options, source) {
10819 return new Tooltip(options, source);
10820 };
10821
10822 // @namespace Map
10823 // @section Methods for Layers and Controls
10824 Map.include({
10825
10826 // @method openTooltip(tooltip: Tooltip): this
10827 // Opens the specified tooltip.
10828 // @alternative
10829 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
10830 // Creates a tooltip with the specified content and options and open it.
10831 openTooltip: function (tooltip, latlng, options) {
10832 this._initOverlay(Tooltip, tooltip, latlng, options)
10833 .openOn(this);
10834
10835 return this;
10836 },
10837
10838 // @method closeTooltip(tooltip: Tooltip): this
10839 // Closes the tooltip given as parameter.
10840 closeTooltip: function (tooltip) {
10841 tooltip.close();
10842 return this;
10843 }
10844
10845 });
10846
10847 /*
10848 * @namespace Layer
10849 * @section Tooltip methods example
10850 *
10851 * All layers share a set of methods convenient for binding tooltips to it.
10852 *
10853 * ```js
10854 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
10855 * layer.openTooltip();
10856 * layer.closeTooltip();
10857 * ```
10858 */
10859
10860 // @section Tooltip methods
10861 Layer.include({
10862
10863 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
10864 // Binds a tooltip to the layer with the passed `content` and sets up the
10865 // necessary event listeners. If a `Function` is passed it will receive
10866 // the layer as the first argument and should return a `String` or `HTMLElement`.
10867 bindTooltip: function (content, options) {
10868
10869 if (this._tooltip && this.isTooltipOpen()) {
10870 this.unbindTooltip();
10871 }
10872
10873 this._tooltip = this._initOverlay(Tooltip, this._tooltip, content, options);
10874 this._initTooltipInteractions();
10875
10876 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
10877 this.openTooltip();
10878 }
10879
10880 return this;
10881 },
10882
10883 // @method unbindTooltip(): this
10884 // Removes the tooltip previously bound with `bindTooltip`.
10885 unbindTooltip: function () {
10886 if (this._tooltip) {
10887 this._initTooltipInteractions(true);
10888 this.closeTooltip();
10889 this._tooltip = null;
10890 }
10891 return this;
10892 },
10893
10894 _initTooltipInteractions: function (remove) {
10895 if (!remove && this._tooltipHandlersAdded) { return; }
10896 var onOff = remove ? 'off' : 'on',
10897 events = {
10898 remove: this.closeTooltip,
10899 move: this._moveTooltip
10900 };
10901 if (!this._tooltip.options.permanent) {
10902 events.mouseover = this._openTooltip;
10903 events.mouseout = this.closeTooltip;
10904 events.click = this._openTooltip;
10905 if (this._map) {
10906 this._addFocusListeners();
10907 } else {
10908 events.add = this._addFocusListeners;
10909 }
10910 } else {
10911 events.add = this._openTooltip;
10912 }
10913 if (this._tooltip.options.sticky) {
10914 events.mousemove = this._moveTooltip;
10915 }
10916 this[onOff](events);
10917 this._tooltipHandlersAdded = !remove;
10918 },
10919
10920 // @method openTooltip(latlng?: LatLng): this
10921 // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
10922 openTooltip: function (latlng) {
10923 if (this._tooltip) {
10924 if (!(this instanceof FeatureGroup)) {
10925 this._tooltip._source = this;
10926 }
10927 if (this._tooltip._prepareOpen(latlng)) {
10928 // open the tooltip on the map
10929 this._tooltip.openOn(this._map);
10930
10931 if (this.getElement) {
10932 this._setAriaDescribedByOnLayer(this);
10933 } else if (this.eachLayer) {
10934 this.eachLayer(this._setAriaDescribedByOnLayer, this);
10935 }
10936 }
10937 }
10938 return this;
10939 },
10940
10941 // @method closeTooltip(): this
10942 // Closes the tooltip bound to this layer if it is open.
10943 closeTooltip: function () {
10944 if (this._tooltip) {
10945 return this._tooltip.close();
10946 }
10947 },
10948
10949 // @method toggleTooltip(): this
10950 // Opens or closes the tooltip bound to this layer depending on its current state.
10951 toggleTooltip: function () {
10952 if (this._tooltip) {
10953 this._tooltip.toggle(this);
10954 }
10955 return this;
10956 },
10957
10958 // @method isTooltipOpen(): boolean
10959 // Returns `true` if the tooltip bound to this layer is currently open.
10960 isTooltipOpen: function () {
10961 return this._tooltip.isOpen();
10962 },
10963
10964 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10965 // Sets the content of the tooltip bound to this layer.
10966 setTooltipContent: function (content) {
10967 if (this._tooltip) {
10968 this._tooltip.setContent(content);
10969 }
10970 return this;
10971 },
10972
10973 // @method getTooltip(): Tooltip
10974 // Returns the tooltip bound to this layer.
10975 getTooltip: function () {
10976 return this._tooltip;
10977 },
10978
10979 _addFocusListeners: function () {
10980 if (this.getElement) {
10981 this._addFocusListenersOnLayer(this);
10982 } else if (this.eachLayer) {
10983 this.eachLayer(this._addFocusListenersOnLayer, this);
10984 }
10985 },
10986
10987 _addFocusListenersOnLayer: function (layer) {
10988 var el = typeof layer.getElement === 'function' && layer.getElement();
10989 if (el) {
10990 on(el, 'focus', function () {
10991 this._tooltip._source = layer;
10992 this.openTooltip();
10993 }, this);
10994 on(el, 'blur', this.closeTooltip, this);
10995 }
10996 },
10997
10998 _setAriaDescribedByOnLayer: function (layer) {
10999 var el = typeof layer.getElement === 'function' && layer.getElement();
11000 if (el) {
11001 el.setAttribute('aria-describedby', this._tooltip._container.id);
11002 }
11003 },
11004
11005
11006 _openTooltip: function (e) {
11007 if (!this._tooltip || !this._map) {
11008 return;
11009 }
11010
11011 // If the map is moving, we will show the tooltip after it's done.
11012 if (this._map.dragging && this._map.dragging.moving() && !this._openOnceFlag) {
11013 this._openOnceFlag = true;
11014 var that = this;
11015 this._map.once('moveend', function () {
11016 that._openOnceFlag = false;
11017 that._openTooltip(e);
11018 });
11019 return;
11020 }
11021
11022 this._tooltip._source = e.layer || e.target;
11023
11024 this.openTooltip(this._tooltip.options.sticky ? e.latlng : undefined);
11025 },
11026
11027 _moveTooltip: function (e) {
11028 var latlng = e.latlng, containerPoint, layerPoint;
11029 if (this._tooltip.options.sticky && e.originalEvent) {
11030 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
11031 layerPoint = this._map.containerPointToLayerPoint(containerPoint);
11032 latlng = this._map.layerPointToLatLng(layerPoint);
11033 }
11034 this._tooltip.setLatLng(latlng);
11035 }
11036 });
11037
11038 /*
11039 * @class DivIcon
11040 * @aka L.DivIcon
11041 * @inherits Icon
11042 *
11043 * Represents a lightweight icon for markers that uses a simple `<div>`
11044 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
11045 *
11046 * @example
11047 * ```js
11048 * var myIcon = L.divIcon({className: 'my-div-icon'});
11049 * // you can set .my-div-icon styles in CSS
11050 *
11051 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
11052 * ```
11053 *
11054 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
11055 */
11056
11057 var DivIcon = Icon.extend({
11058 options: {
11059 // @section
11060 // @aka DivIcon options
11061 iconSize: [12, 12], // also can be set through CSS
11062
11063 // iconAnchor: (Point),
11064 // popupAnchor: (Point),
11065
11066 // @option html: String|HTMLElement = ''
11067 // Custom HTML code to put inside the div element, empty by default. Alternatively,
11068 // an instance of `HTMLElement`.
11069 html: false,
11070
11071 // @option bgPos: Point = [0, 0]
11072 // Optional relative position of the background, in pixels
11073 bgPos: null,
11074
11075 className: 'leaflet-div-icon'
11076 },
11077
11078 createIcon: function (oldIcon) {
11079 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
11080 options = this.options;
11081
11082 if (options.html instanceof Element) {
11083 empty(div);
11084 div.appendChild(options.html);
11085 } else {
11086 div.innerHTML = options.html !== false ? options.html : '';
11087 }
11088
11089 if (options.bgPos) {
11090 var bgPos = toPoint(options.bgPos);
11091 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
11092 }
11093 this._setIconStyles(div, 'icon');
11094
11095 return div;
11096 },
11097
11098 createShadow: function () {
11099 return null;
11100 }
11101 });
11102
11103 // @factory L.divIcon(options: DivIcon options)
11104 // Creates a `DivIcon` instance with the given options.
11105 function divIcon(options) {
11106 return new DivIcon(options);
11107 }
11108
11109 Icon.Default = IconDefault;
11110
11111 /*
11112 * @class GridLayer
11113 * @inherits Layer
11114 * @aka L.GridLayer
11115 *
11116 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
11117 * GridLayer can be extended to create a tiled grid of HTML elements like `<canvas>`, `<img>` or `<div>`. GridLayer will handle creating and animating these DOM elements for you.
11118 *
11119 *
11120 * @section Synchronous usage
11121 * @example
11122 *
11123 * To create a custom layer, extend GridLayer and implement the `createTile()` method, which will be passed a `Point` object with the `x`, `y`, and `z` (zoom level) coordinates to draw your tile.
11124 *
11125 * ```js
11126 * var CanvasLayer = L.GridLayer.extend({
11127 * createTile: function(coords){
11128 * // create a <canvas> element for drawing
11129 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
11130 *
11131 * // setup tile width and height according to the options
11132 * var size = this.getTileSize();
11133 * tile.width = size.x;
11134 * tile.height = size.y;
11135 *
11136 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z
11137 * var ctx = tile.getContext('2d');
11138 *
11139 * // return the tile so it can be rendered on screen
11140 * return tile;
11141 * }
11142 * });
11143 * ```
11144 *
11145 * @section Asynchronous usage
11146 * @example
11147 *
11148 * Tile creation can also be asynchronous, this is useful when using a third-party drawing library. Once the tile is finished drawing it can be passed to the `done()` callback.
11149 *
11150 * ```js
11151 * var CanvasLayer = L.GridLayer.extend({
11152 * createTile: function(coords, done){
11153 * var error;
11154 *
11155 * // create a <canvas> element for drawing
11156 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
11157 *
11158 * // setup tile width and height according to the options
11159 * var size = this.getTileSize();
11160 * tile.width = size.x;
11161 * tile.height = size.y;
11162 *
11163 * // draw something asynchronously and pass the tile to the done() callback
11164 * setTimeout(function() {
11165 * done(error, tile);
11166 * }, 1000);
11167 *
11168 * return tile;
11169 * }
11170 * });
11171 * ```
11172 *
11173 * @section
11174 */
11175
11176
11177 var GridLayer = Layer.extend({
11178
11179 // @section
11180 // @aka GridLayer options
11181 options: {
11182 // @option tileSize: Number|Point = 256
11183 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
11184 tileSize: 256,
11185
11186 // @option opacity: Number = 1.0
11187 // Opacity of the tiles. Can be used in the `createTile()` function.
11188 opacity: 1,
11189
11190 // @option updateWhenIdle: Boolean = (depends)
11191 // Load new tiles only when panning ends.
11192 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
11193 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
11194 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
11195 updateWhenIdle: Browser.mobile,
11196
11197 // @option updateWhenZooming: Boolean = true
11198 // By default, a smooth zoom animation (during a [touch zoom](#map-touchzoom) or a [`flyTo()`](#map-flyto)) will update grid layers every integer zoom level. Setting this option to `false` will update the grid layer only when the smooth animation ends.
11199 updateWhenZooming: true,
11200
11201 // @option updateInterval: Number = 200
11202 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
11203 updateInterval: 200,
11204
11205 // @option zIndex: Number = 1
11206 // The explicit zIndex of the tile layer.
11207 zIndex: 1,
11208
11209 // @option bounds: LatLngBounds = undefined
11210 // If set, tiles will only be loaded inside the set `LatLngBounds`.
11211 bounds: null,
11212
11213 // @option minZoom: Number = 0
11214 // The minimum zoom level down to which this layer will be displayed (inclusive).
11215 minZoom: 0,
11216
11217 // @option maxZoom: Number = undefined
11218 // The maximum zoom level up to which this layer will be displayed (inclusive).
11219 maxZoom: undefined,
11220
11221 // @option maxNativeZoom: Number = undefined
11222 // Maximum zoom number the tile source has available. If it is specified,
11223 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
11224 // from `maxNativeZoom` level and auto-scaled.
11225 maxNativeZoom: undefined,
11226
11227 // @option minNativeZoom: Number = undefined
11228 // Minimum zoom number the tile source has available. If it is specified,
11229 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
11230 // from `minNativeZoom` level and auto-scaled.
11231 minNativeZoom: undefined,
11232
11233 // @option noWrap: Boolean = false
11234 // Whether the layer is wrapped around the antimeridian. If `true`, the
11235 // GridLayer will only be displayed once at low zoom levels. Has no
11236 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
11237 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
11238 // tiles outside the CRS limits.
11239 noWrap: false,
11240
11241 // @option pane: String = 'tilePane'
11242 // `Map pane` where the grid layer will be added.
11243 pane: 'tilePane',
11244
11245 // @option className: String = ''
11246 // A custom class name to assign to the tile layer. Empty by default.
11247 className: '',
11248
11249 // @option keepBuffer: Number = 2
11250 // When panning the map, keep this many rows and columns of tiles before unloading them.
11251 keepBuffer: 2
11252 },
11253
11254 initialize: function (options) {
11255 setOptions(this, options);
11256 },
11257
11258 onAdd: function () {
11259 this._initContainer();
11260
11261 this._levels = {};
11262 this._tiles = {};
11263
11264 this._resetView(); // implicit _update() call
11265 },
11266
11267 beforeAdd: function (map) {
11268 map._addZoomLimit(this);
11269 },
11270
11271 onRemove: function (map) {
11272 this._removeAllTiles();
11273 remove(this._container);
11274 map._removeZoomLimit(this);
11275 this._container = null;
11276 this._tileZoom = undefined;
11277 },
11278
11279 // @method bringToFront: this
11280 // Brings the tile layer to the top of all tile layers.
11281 bringToFront: function () {
11282 if (this._map) {
11283 toFront(this._container);
11284 this._setAutoZIndex(Math.max);
11285 }
11286 return this;
11287 },
11288
11289 // @method bringToBack: this
11290 // Brings the tile layer to the bottom of all tile layers.
11291 bringToBack: function () {
11292 if (this._map) {
11293 toBack(this._container);
11294 this._setAutoZIndex(Math.min);
11295 }
11296 return this;
11297 },
11298
11299 // @method getContainer: HTMLElement
11300 // Returns the HTML element that contains the tiles for this layer.
11301 getContainer: function () {
11302 return this._container;
11303 },
11304
11305 // @method setOpacity(opacity: Number): this
11306 // Changes the [opacity](#gridlayer-opacity) of the grid layer.
11307 setOpacity: function (opacity) {
11308 this.options.opacity = opacity;
11309 this._updateOpacity();
11310 return this;
11311 },
11312
11313 // @method setZIndex(zIndex: Number): this
11314 // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
11315 setZIndex: function (zIndex) {
11316 this.options.zIndex = zIndex;
11317 this._updateZIndex();
11318
11319 return this;
11320 },
11321
11322 // @method isLoading: Boolean
11323 // Returns `true` if any tile in the grid layer has not finished loading.
11324 isLoading: function () {
11325 return this._loading;
11326 },
11327
11328 // @method redraw: this
11329 // Causes the layer to clear all the tiles and request them again.
11330 redraw: function () {
11331 if (this._map) {
11332 this._removeAllTiles();
11333 var tileZoom = this._clampZoom(this._map.getZoom());
11334 if (tileZoom !== this._tileZoom) {
11335 this._tileZoom = tileZoom;
11336 this._updateLevels();
11337 }
11338 this._update();
11339 }
11340 return this;
11341 },
11342
11343 getEvents: function () {
11344 var events = {
11345 viewprereset: this._invalidateAll,
11346 viewreset: this._resetView,
11347 zoom: this._resetView,
11348 moveend: this._onMoveEnd
11349 };
11350
11351 if (!this.options.updateWhenIdle) {
11352 // update tiles on move, but not more often than once per given interval
11353 if (!this._onMove) {
11354 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
11355 }
11356
11357 events.move = this._onMove;
11358 }
11359
11360 if (this._zoomAnimated) {
11361 events.zoomanim = this._animateZoom;
11362 }
11363
11364 return events;
11365 },
11366
11367 // @section Extension methods
11368 // Layers extending `GridLayer` shall reimplement the following method.
11369 // @method createTile(coords: Object, done?: Function): HTMLElement
11370 // Called only internally, must be overridden by classes extending `GridLayer`.
11371 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
11372 // is specified, it must be called when the tile has finished loading and drawing.
11373 createTile: function () {
11374 return document.createElement('div');
11375 },
11376
11377 // @section
11378 // @method getTileSize: Point
11379 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
11380 getTileSize: function () {
11381 var s = this.options.tileSize;
11382 return s instanceof Point ? s : new Point(s, s);
11383 },
11384
11385 _updateZIndex: function () {
11386 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
11387 this._container.style.zIndex = this.options.zIndex;
11388 }
11389 },
11390
11391 _setAutoZIndex: function (compare) {
11392 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
11393
11394 var layers = this.getPane().children,
11395 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
11396
11397 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
11398
11399 zIndex = layers[i].style.zIndex;
11400
11401 if (layers[i] !== this._container && zIndex) {
11402 edgeZIndex = compare(edgeZIndex, +zIndex);
11403 }
11404 }
11405
11406 if (isFinite(edgeZIndex)) {
11407 this.options.zIndex = edgeZIndex + compare(-1, 1);
11408 this._updateZIndex();
11409 }
11410 },
11411
11412 _updateOpacity: function () {
11413 if (!this._map) { return; }
11414
11415 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
11416 if (Browser.ielt9) { return; }
11417
11418 setOpacity(this._container, this.options.opacity);
11419
11420 var now = +new Date(),
11421 nextFrame = false,
11422 willPrune = false;
11423
11424 for (var key in this._tiles) {
11425 var tile = this._tiles[key];
11426 if (!tile.current || !tile.loaded) { continue; }
11427
11428 var fade = Math.min(1, (now - tile.loaded) / 200);
11429
11430 setOpacity(tile.el, fade);
11431 if (fade < 1) {
11432 nextFrame = true;
11433 } else {
11434 if (tile.active) {
11435 willPrune = true;
11436 } else {
11437 this._onOpaqueTile(tile);
11438 }
11439 tile.active = true;
11440 }
11441 }
11442
11443 if (willPrune && !this._noPrune) { this._pruneTiles(); }
11444
11445 if (nextFrame) {
11446 cancelAnimFrame(this._fadeFrame);
11447 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11448 }
11449 },
11450
11451 _onOpaqueTile: falseFn,
11452
11453 _initContainer: function () {
11454 if (this._container) { return; }
11455
11456 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
11457 this._updateZIndex();
11458
11459 if (this.options.opacity < 1) {
11460 this._updateOpacity();
11461 }
11462
11463 this.getPane().appendChild(this._container);
11464 },
11465
11466 _updateLevels: function () {
11467
11468 var zoom = this._tileZoom,
11469 maxZoom = this.options.maxZoom;
11470
11471 if (zoom === undefined) { return undefined; }
11472
11473 for (var z in this._levels) {
11474 z = Number(z);
11475 if (this._levels[z].el.children.length || z === zoom) {
11476 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
11477 this._onUpdateLevel(z);
11478 } else {
11479 remove(this._levels[z].el);
11480 this._removeTilesAtZoom(z);
11481 this._onRemoveLevel(z);
11482 delete this._levels[z];
11483 }
11484 }
11485
11486 var level = this._levels[zoom],
11487 map = this._map;
11488
11489 if (!level) {
11490 level = this._levels[zoom] = {};
11491
11492 level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
11493 level.el.style.zIndex = maxZoom;
11494
11495 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
11496 level.zoom = zoom;
11497
11498 this._setZoomTransform(level, map.getCenter(), map.getZoom());
11499
11500 // force the browser to consider the newly added element for transition
11501 falseFn(level.el.offsetWidth);
11502
11503 this._onCreateLevel(level);
11504 }
11505
11506 this._level = level;
11507
11508 return level;
11509 },
11510
11511 _onUpdateLevel: falseFn,
11512
11513 _onRemoveLevel: falseFn,
11514
11515 _onCreateLevel: falseFn,
11516
11517 _pruneTiles: function () {
11518 if (!this._map) {
11519 return;
11520 }
11521
11522 var key, tile;
11523
11524 var zoom = this._map.getZoom();
11525 if (zoom > this.options.maxZoom ||
11526 zoom < this.options.minZoom) {
11527 this._removeAllTiles();
11528 return;
11529 }
11530
11531 for (key in this._tiles) {
11532 tile = this._tiles[key];
11533 tile.retain = tile.current;
11534 }
11535
11536 for (key in this._tiles) {
11537 tile = this._tiles[key];
11538 if (tile.current && !tile.active) {
11539 var coords = tile.coords;
11540 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
11541 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
11542 }
11543 }
11544 }
11545
11546 for (key in this._tiles) {
11547 if (!this._tiles[key].retain) {
11548 this._removeTile(key);
11549 }
11550 }
11551 },
11552
11553 _removeTilesAtZoom: function (zoom) {
11554 for (var key in this._tiles) {
11555 if (this._tiles[key].coords.z !== zoom) {
11556 continue;
11557 }
11558 this._removeTile(key);
11559 }
11560 },
11561
11562 _removeAllTiles: function () {
11563 for (var key in this._tiles) {
11564 this._removeTile(key);
11565 }
11566 },
11567
11568 _invalidateAll: function () {
11569 for (var z in this._levels) {
11570 remove(this._levels[z].el);
11571 this._onRemoveLevel(Number(z));
11572 delete this._levels[z];
11573 }
11574 this._removeAllTiles();
11575
11576 this._tileZoom = undefined;
11577 },
11578
11579 _retainParent: function (x, y, z, minZoom) {
11580 var x2 = Math.floor(x / 2),
11581 y2 = Math.floor(y / 2),
11582 z2 = z - 1,
11583 coords2 = new Point(+x2, +y2);
11584 coords2.z = +z2;
11585
11586 var key = this._tileCoordsToKey(coords2),
11587 tile = this._tiles[key];
11588
11589 if (tile && tile.active) {
11590 tile.retain = true;
11591 return true;
11592
11593 } else if (tile && tile.loaded) {
11594 tile.retain = true;
11595 }
11596
11597 if (z2 > minZoom) {
11598 return this._retainParent(x2, y2, z2, minZoom);
11599 }
11600
11601 return false;
11602 },
11603
11604 _retainChildren: function (x, y, z, maxZoom) {
11605
11606 for (var i = 2 * x; i < 2 * x + 2; i++) {
11607 for (var j = 2 * y; j < 2 * y + 2; j++) {
11608
11609 var coords = new Point(i, j);
11610 coords.z = z + 1;
11611
11612 var key = this._tileCoordsToKey(coords),
11613 tile = this._tiles[key];
11614
11615 if (tile && tile.active) {
11616 tile.retain = true;
11617 continue;
11618
11619 } else if (tile && tile.loaded) {
11620 tile.retain = true;
11621 }
11622
11623 if (z + 1 < maxZoom) {
11624 this._retainChildren(i, j, z + 1, maxZoom);
11625 }
11626 }
11627 }
11628 },
11629
11630 _resetView: function (e) {
11631 var animating = e && (e.pinch || e.flyTo);
11632 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
11633 },
11634
11635 _animateZoom: function (e) {
11636 this._setView(e.center, e.zoom, true, e.noUpdate);
11637 },
11638
11639 _clampZoom: function (zoom) {
11640 var options = this.options;
11641
11642 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
11643 return options.minNativeZoom;
11644 }
11645
11646 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
11647 return options.maxNativeZoom;
11648 }
11649
11650 return zoom;
11651 },
11652
11653 _setView: function (center, zoom, noPrune, noUpdate) {
11654 var tileZoom = Math.round(zoom);
11655 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
11656 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
11657 tileZoom = undefined;
11658 } else {
11659 tileZoom = this._clampZoom(tileZoom);
11660 }
11661
11662 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
11663
11664 if (!noUpdate || tileZoomChanged) {
11665
11666 this._tileZoom = tileZoom;
11667
11668 if (this._abortLoading) {
11669 this._abortLoading();
11670 }
11671
11672 this._updateLevels();
11673 this._resetGrid();
11674
11675 if (tileZoom !== undefined) {
11676 this._update(center);
11677 }
11678
11679 if (!noPrune) {
11680 this._pruneTiles();
11681 }
11682
11683 // Flag to prevent _updateOpacity from pruning tiles during
11684 // a zoom anim or a pinch gesture
11685 this._noPrune = !!noPrune;
11686 }
11687
11688 this._setZoomTransforms(center, zoom);
11689 },
11690
11691 _setZoomTransforms: function (center, zoom) {
11692 for (var i in this._levels) {
11693 this._setZoomTransform(this._levels[i], center, zoom);
11694 }
11695 },
11696
11697 _setZoomTransform: function (level, center, zoom) {
11698 var scale = this._map.getZoomScale(zoom, level.zoom),
11699 translate = level.origin.multiplyBy(scale)
11700 .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
11701
11702 if (Browser.any3d) {
11703 setTransform(level.el, translate, scale);
11704 } else {
11705 setPosition(level.el, translate);
11706 }
11707 },
11708
11709 _resetGrid: function () {
11710 var map = this._map,
11711 crs = map.options.crs,
11712 tileSize = this._tileSize = this.getTileSize(),
11713 tileZoom = this._tileZoom;
11714
11715 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
11716 if (bounds) {
11717 this._globalTileRange = this._pxBoundsToTileRange(bounds);
11718 }
11719
11720 this._wrapX = crs.wrapLng && !this.options.noWrap && [
11721 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
11722 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
11723 ];
11724 this._wrapY = crs.wrapLat && !this.options.noWrap && [
11725 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
11726 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
11727 ];
11728 },
11729
11730 _onMoveEnd: function () {
11731 if (!this._map || this._map._animatingZoom) { return; }
11732
11733 this._update();
11734 },
11735
11736 _getTiledPixelBounds: function (center) {
11737 var map = this._map,
11738 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
11739 scale = map.getZoomScale(mapZoom, this._tileZoom),
11740 pixelCenter = map.project(center, this._tileZoom).floor(),
11741 halfSize = map.getSize().divideBy(scale * 2);
11742
11743 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
11744 },
11745
11746 // Private method to load tiles in the grid's active zoom level according to map bounds
11747 _update: function (center) {
11748 var map = this._map;
11749 if (!map) { return; }
11750 var zoom = this._clampZoom(map.getZoom());
11751
11752 if (center === undefined) { center = map.getCenter(); }
11753 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom
11754
11755 var pixelBounds = this._getTiledPixelBounds(center),
11756 tileRange = this._pxBoundsToTileRange(pixelBounds),
11757 tileCenter = tileRange.getCenter(),
11758 queue = [],
11759 margin = this.options.keepBuffer,
11760 noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
11761 tileRange.getTopRight().add([margin, -margin]));
11762
11763 // Sanity check: panic if the tile range contains Infinity somewhere.
11764 if (!(isFinite(tileRange.min.x) &&
11765 isFinite(tileRange.min.y) &&
11766 isFinite(tileRange.max.x) &&
11767 isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
11768
11769 for (var key in this._tiles) {
11770 var c = this._tiles[key].coords;
11771 if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
11772 this._tiles[key].current = false;
11773 }
11774 }
11775
11776 // _update just loads more tiles. If the tile zoom level differs too much
11777 // from the map's, let _setView reset levels and prune old tiles.
11778 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
11779
11780 // create a queue of coordinates to load tiles from
11781 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
11782 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
11783 var coords = new Point(i, j);
11784 coords.z = this._tileZoom;
11785
11786 if (!this._isValidTile(coords)) { continue; }
11787
11788 var tile = this._tiles[this._tileCoordsToKey(coords)];
11789 if (tile) {
11790 tile.current = true;
11791 } else {
11792 queue.push(coords);
11793 }
11794 }
11795 }
11796
11797 // sort tile queue to load tiles in order of their distance to center
11798 queue.sort(function (a, b) {
11799 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
11800 });
11801
11802 if (queue.length !== 0) {
11803 // if it's the first batch of tiles to load
11804 if (!this._loading) {
11805 this._loading = true;
11806 // @event loading: Event
11807 // Fired when the grid layer starts loading tiles.
11808 this.fire('loading');
11809 }
11810
11811 // create DOM fragment to append tiles in one batch
11812 var fragment = document.createDocumentFragment();
11813
11814 for (i = 0; i < queue.length; i++) {
11815 this._addTile(queue[i], fragment);
11816 }
11817
11818 this._level.el.appendChild(fragment);
11819 }
11820 },
11821
11822 _isValidTile: function (coords) {
11823 var crs = this._map.options.crs;
11824
11825 if (!crs.infinite) {
11826 // don't load tile if it's out of bounds and not wrapped
11827 var bounds = this._globalTileRange;
11828 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
11829 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
11830 }
11831
11832 if (!this.options.bounds) { return true; }
11833
11834 // don't load tile if it doesn't intersect the bounds in options
11835 var tileBounds = this._tileCoordsToBounds(coords);
11836 return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
11837 },
11838
11839 _keyToBounds: function (key) {
11840 return this._tileCoordsToBounds(this._keyToTileCoords(key));
11841 },
11842
11843 _tileCoordsToNwSe: function (coords) {
11844 var map = this._map,
11845 tileSize = this.getTileSize(),
11846 nwPoint = coords.scaleBy(tileSize),
11847 sePoint = nwPoint.add(tileSize),
11848 nw = map.unproject(nwPoint, coords.z),
11849 se = map.unproject(sePoint, coords.z);
11850 return [nw, se];
11851 },
11852
11853 // converts tile coordinates to its geographical bounds
11854 _tileCoordsToBounds: function (coords) {
11855 var bp = this._tileCoordsToNwSe(coords),
11856 bounds = new LatLngBounds(bp[0], bp[1]);
11857
11858 if (!this.options.noWrap) {
11859 bounds = this._map.wrapLatLngBounds(bounds);
11860 }
11861 return bounds;
11862 },
11863 // converts tile coordinates to key for the tile cache
11864 _tileCoordsToKey: function (coords) {
11865 return coords.x + ':' + coords.y + ':' + coords.z;
11866 },
11867
11868 // converts tile cache key to coordinates
11869 _keyToTileCoords: function (key) {
11870 var k = key.split(':'),
11871 coords = new Point(+k[0], +k[1]);
11872 coords.z = +k[2];
11873 return coords;
11874 },
11875
11876 _removeTile: function (key) {
11877 var tile = this._tiles[key];
11878 if (!tile) { return; }
11879
11880 remove(tile.el);
11881
11882 delete this._tiles[key];
11883
11884 // @event tileunload: TileEvent
11885 // Fired when a tile is removed (e.g. when a tile goes off the screen).
11886 this.fire('tileunload', {
11887 tile: tile.el,
11888 coords: this._keyToTileCoords(key)
11889 });
11890 },
11891
11892 _initTile: function (tile) {
11893 addClass(tile, 'leaflet-tile');
11894
11895 var tileSize = this.getTileSize();
11896 tile.style.width = tileSize.x + 'px';
11897 tile.style.height = tileSize.y + 'px';
11898
11899 tile.onselectstart = falseFn;
11900 tile.onmousemove = falseFn;
11901
11902 // update opacity on tiles in IE7-8 because of filter inheritance problems
11903 if (Browser.ielt9 && this.options.opacity < 1) {
11904 setOpacity(tile, this.options.opacity);
11905 }
11906 },
11907
11908 _addTile: function (coords, container) {
11909 var tilePos = this._getTilePos(coords),
11910 key = this._tileCoordsToKey(coords);
11911
11912 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
11913
11914 this._initTile(tile);
11915
11916 // if createTile is defined with a second argument ("done" callback),
11917 // we know that tile is async and will be ready later; otherwise
11918 if (this.createTile.length < 2) {
11919 // mark tile as ready, but delay one frame for opacity animation to happen
11920 requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
11921 }
11922
11923 setPosition(tile, tilePos);
11924
11925 // save tile in cache
11926 this._tiles[key] = {
11927 el: tile,
11928 coords: coords,
11929 current: true
11930 };
11931
11932 container.appendChild(tile);
11933 // @event tileloadstart: TileEvent
11934 // Fired when a tile is requested and starts loading.
11935 this.fire('tileloadstart', {
11936 tile: tile,
11937 coords: coords
11938 });
11939 },
11940
11941 _tileReady: function (coords, err, tile) {
11942 if (err) {
11943 // @event tileerror: TileErrorEvent
11944 // Fired when there is an error loading a tile.
11945 this.fire('tileerror', {
11946 error: err,
11947 tile: tile,
11948 coords: coords
11949 });
11950 }
11951
11952 var key = this._tileCoordsToKey(coords);
11953
11954 tile = this._tiles[key];
11955 if (!tile) { return; }
11956
11957 tile.loaded = +new Date();
11958 if (this._map._fadeAnimated) {
11959 setOpacity(tile.el, 0);
11960 cancelAnimFrame(this._fadeFrame);
11961 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11962 } else {
11963 tile.active = true;
11964 this._pruneTiles();
11965 }
11966
11967 if (!err) {
11968 addClass(tile.el, 'leaflet-tile-loaded');
11969
11970 // @event tileload: TileEvent
11971 // Fired when a tile loads.
11972 this.fire('tileload', {
11973 tile: tile.el,
11974 coords: coords
11975 });
11976 }
11977
11978 if (this._noTilesToLoad()) {
11979 this._loading = false;
11980 // @event load: Event
11981 // Fired when the grid layer loaded all visible tiles.
11982 this.fire('load');
11983
11984 if (Browser.ielt9 || !this._map._fadeAnimated) {
11985 requestAnimFrame(this._pruneTiles, this);
11986 } else {
11987 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
11988 // to trigger a pruning.
11989 setTimeout(bind(this._pruneTiles, this), 250);
11990 }
11991 }
11992 },
11993
11994 _getTilePos: function (coords) {
11995 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
11996 },
11997
11998 _wrapCoords: function (coords) {
11999 var newCoords = new Point(
12000 this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
12001 this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
12002 newCoords.z = coords.z;
12003 return newCoords;
12004 },
12005
12006 _pxBoundsToTileRange: function (bounds) {
12007 var tileSize = this.getTileSize();
12008 return new Bounds(
12009 bounds.min.unscaleBy(tileSize).floor(),
12010 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
12011 },
12012
12013 _noTilesToLoad: function () {
12014 for (var key in this._tiles) {
12015 if (!this._tiles[key].loaded) { return false; }
12016 }
12017 return true;
12018 }
12019 });
12020
12021 // @factory L.gridLayer(options?: GridLayer options)
12022 // Creates a new instance of GridLayer with the supplied options.
12023 function gridLayer(options) {
12024 return new GridLayer(options);
12025 }
12026
12027 /*
12028 * @class TileLayer
12029 * @inherits GridLayer
12030 * @aka L.TileLayer
12031 * Used to load and display tile layers on the map. Note that most tile servers require attribution, which you can set under `Layer`. Extends `GridLayer`.
12032 *
12033 * @example
12034 *
12035 * ```js
12036 * L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar', attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'}).addTo(map);
12037 * ```
12038 *
12039 * @section URL template
12040 * @example
12041 *
12042 * A string of the following form:
12043 *
12044 * ```
12045 * 'https://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
12046 * ```
12047 *
12048 * `{s}` means one of the available subdomains (used sequentially to help with browser parallel requests per domain limitation; subdomain values are specified in options; `a`, `b` or `c` by default, can be omitted), `{z}` — zoom level, `{x}` and `{y}` — tile coordinates. `{r}` can be used to add "&commat;2x" to the URL to load retina tiles.
12049 *
12050 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
12051 *
12052 * ```
12053 * L.tileLayer('https://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
12054 * ```
12055 */
12056
12057
12058 var TileLayer = GridLayer.extend({
12059
12060 // @section
12061 // @aka TileLayer options
12062 options: {
12063 // @option minZoom: Number = 0
12064 // The minimum zoom level down to which this layer will be displayed (inclusive).
12065 minZoom: 0,
12066
12067 // @option maxZoom: Number = 18
12068 // The maximum zoom level up to which this layer will be displayed (inclusive).
12069 maxZoom: 18,
12070
12071 // @option subdomains: String|String[] = 'abc'
12072 // Subdomains of the tile service. Can be passed in the form of one string (where each letter is a subdomain name) or an array of strings.
12073 subdomains: 'abc',
12074
12075 // @option errorTileUrl: String = ''
12076 // URL to the tile image to show in place of the tile that failed to load.
12077 errorTileUrl: '',
12078
12079 // @option zoomOffset: Number = 0
12080 // The zoom number used in tile URLs will be offset with this value.
12081 zoomOffset: 0,
12082
12083 // @option tms: Boolean = false
12084 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
12085 tms: false,
12086
12087 // @option zoomReverse: Boolean = false
12088 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
12089 zoomReverse: false,
12090
12091 // @option detectRetina: Boolean = false
12092 // If `true` and user is on a retina display, it will request four tiles of half the specified size and a bigger zoom level in place of one to utilize the high resolution.
12093 detectRetina: false,
12094
12095 // @option crossOrigin: Boolean|String = false
12096 // Whether the crossOrigin attribute will be added to the tiles.
12097 // If a String is provided, all tiles will have their crossOrigin attribute set to the String provided. This is needed if you want to access tile pixel data.
12098 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
12099 crossOrigin: false,
12100
12101 // @option referrerPolicy: Boolean|String = false
12102 // Whether the referrerPolicy attribute will be added to the tiles.
12103 // If a String is provided, all tiles will have their referrerPolicy attribute set to the String provided.
12104 // This may be needed if your map's rendering context has a strict default but your tile provider expects a valid referrer
12105 // (e.g. to validate an API token).
12106 // Refer to [HTMLImageElement.referrerPolicy](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/referrerPolicy) for valid String values.
12107 referrerPolicy: false
12108 },
12109
12110 initialize: function (url, options) {
12111
12112 this._url = url;
12113
12114 options = setOptions(this, options);
12115
12116 // detecting retina displays, adjusting tileSize and zoom levels
12117 if (options.detectRetina && Browser.retina && options.maxZoom > 0) {
12118
12119 options.tileSize = Math.floor(options.tileSize / 2);
12120
12121 if (!options.zoomReverse) {
12122 options.zoomOffset++;
12123 options.maxZoom = Math.max(options.minZoom, options.maxZoom - 1);
12124 } else {
12125 options.zoomOffset--;
12126 options.minZoom = Math.min(options.maxZoom, options.minZoom + 1);
12127 }
12128
12129 options.minZoom = Math.max(0, options.minZoom);
12130 } else if (!options.zoomReverse) {
12131 // make sure maxZoom is gte minZoom
12132 options.maxZoom = Math.max(options.minZoom, options.maxZoom);
12133 } else {
12134 // make sure minZoom is lte maxZoom
12135 options.minZoom = Math.min(options.maxZoom, options.minZoom);
12136 }
12137
12138 if (typeof options.subdomains === 'string') {
12139 options.subdomains = options.subdomains.split('');
12140 }
12141
12142 this.on('tileunload', this._onTileRemove);
12143 },
12144
12145 // @method setUrl(url: String, noRedraw?: Boolean): this
12146 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
12147 // If the URL does not change, the layer will not be redrawn unless
12148 // the noRedraw parameter is set to false.
12149 setUrl: function (url, noRedraw) {
12150 if (this._url === url && noRedraw === undefined) {
12151 noRedraw = true;
12152 }
12153
12154 this._url = url;
12155
12156 if (!noRedraw) {
12157 this.redraw();
12158 }
12159 return this;
12160 },
12161
12162 // @method createTile(coords: Object, done?: Function): HTMLElement
12163 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
12164 // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
12165 // callback is called when the tile has been loaded.
12166 createTile: function (coords, done) {
12167 var tile = document.createElement('img');
12168
12169 on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
12170 on(tile, 'error', bind(this._tileOnError, this, done, tile));
12171
12172 if (this.options.crossOrigin || this.options.crossOrigin === '') {
12173 tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
12174 }
12175
12176 // for this new option we follow the documented behavior
12177 // more closely by only setting the property when string
12178 if (typeof this.options.referrerPolicy === 'string') {
12179 tile.referrerPolicy = this.options.referrerPolicy;
12180 }
12181
12182 // The alt attribute is set to the empty string,
12183 // allowing screen readers to ignore the decorative image tiles.
12184 // https://www.w3.org/WAI/tutorials/images/decorative/
12185 // https://www.w3.org/TR/html-aria/#el-img-empty-alt
12186 tile.alt = '';
12187
12188 tile.src = this.getTileUrl(coords);
12189
12190 return tile;
12191 },
12192
12193 // @section Extension methods
12194 // @uninheritable
12195 // Layers extending `TileLayer` might reimplement the following method.
12196 // @method getTileUrl(coords: Object): String
12197 // Called only internally, returns the URL for a tile given its coordinates.
12198 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
12199 getTileUrl: function (coords) {
12200 var data = {
12201 r: Browser.retina ? '@2x' : '',
12202 s: this._getSubdomain(coords),
12203 x: coords.x,
12204 y: coords.y,
12205 z: this._getZoomForUrl()
12206 };
12207 if (this._map && !this._map.options.crs.infinite) {
12208 var invertedY = this._globalTileRange.max.y - coords.y;
12209 if (this.options.tms) {
12210 data['y'] = invertedY;
12211 }
12212 data['-y'] = invertedY;
12213 }
12214
12215 return template(this._url, extend(data, this.options));
12216 },
12217
12218 _tileOnLoad: function (done, tile) {
12219 // For https://github.com/Leaflet/Leaflet/issues/3332
12220 if (Browser.ielt9) {
12221 setTimeout(bind(done, this, null, tile), 0);
12222 } else {
12223 done(null, tile);
12224 }
12225 },
12226
12227 _tileOnError: function (done, tile, e) {
12228 var errorUrl = this.options.errorTileUrl;
12229 if (errorUrl && tile.getAttribute('src') !== errorUrl) {
12230 tile.src = errorUrl;
12231 }
12232 done(e, tile);
12233 },
12234
12235 _onTileRemove: function (e) {
12236 e.tile.onload = null;
12237 },
12238
12239 _getZoomForUrl: function () {
12240 var zoom = this._tileZoom,
12241 maxZoom = this.options.maxZoom,
12242 zoomReverse = this.options.zoomReverse,
12243 zoomOffset = this.options.zoomOffset;
12244
12245 if (zoomReverse) {
12246 zoom = maxZoom - zoom;
12247 }
12248
12249 return zoom + zoomOffset;
12250 },
12251
12252 _getSubdomain: function (tilePoint) {
12253 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
12254 return this.options.subdomains[index];
12255 },
12256
12257 // stops loading all tiles in the background layer
12258 _abortLoading: function () {
12259 var i, tile;
12260 for (i in this._tiles) {
12261 if (this._tiles[i].coords.z !== this._tileZoom) {
12262 tile = this._tiles[i].el;
12263
12264 tile.onload = falseFn;
12265 tile.onerror = falseFn;
12266
12267 if (!tile.complete) {
12268 tile.src = emptyImageUrl;
12269 var coords = this._tiles[i].coords;
12270 remove(tile);
12271 delete this._tiles[i];
12272 // @event tileabort: TileEvent
12273 // Fired when a tile was loading but is now not wanted.
12274 this.fire('tileabort', {
12275 tile: tile,
12276 coords: coords
12277 });
12278 }
12279 }
12280 }
12281 },
12282
12283 _removeTile: function (key) {
12284 var tile = this._tiles[key];
12285 if (!tile) { return; }
12286
12287 // Cancels any pending http requests associated with the tile
12288 tile.el.setAttribute('src', emptyImageUrl);
12289
12290 return GridLayer.prototype._removeTile.call(this, key);
12291 },
12292
12293 _tileReady: function (coords, err, tile) {
12294 if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) {
12295 return;
12296 }
12297
12298 return GridLayer.prototype._tileReady.call(this, coords, err, tile);
12299 }
12300 });
12301
12302
12303 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
12304 // Instantiates a tile layer object given a `URL template` and optionally an options object.
12305
12306 function tileLayer(url, options) {
12307 return new TileLayer(url, options);
12308 }
12309
12310 /*
12311 * @class TileLayer.WMS
12312 * @inherits TileLayer
12313 * @aka L.TileLayer.WMS
12314 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
12315 *
12316 * @example
12317 *
12318 * ```js
12319 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
12320 * layers: 'nexrad-n0r-900913',
12321 * format: 'image/png',
12322 * transparent: true,
12323 * attribution: "Weather data © 2012 IEM Nexrad"
12324 * });
12325 * ```
12326 */
12327
12328 var TileLayerWMS = TileLayer.extend({
12329
12330 // @section
12331 // @aka TileLayer.WMS options
12332 // If any custom options not documented here are used, they will be sent to the
12333 // WMS server as extra parameters in each request URL. This can be useful for
12334 // [non-standard vendor WMS parameters](https://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
12335 defaultWmsParams: {
12336 service: 'WMS',
12337 request: 'GetMap',
12338
12339 // @option layers: String = ''
12340 // **(required)** Comma-separated list of WMS layers to show.
12341 layers: '',
12342
12343 // @option styles: String = ''
12344 // Comma-separated list of WMS styles.
12345 styles: '',
12346
12347 // @option format: String = 'image/jpeg'
12348 // WMS image format (use `'image/png'` for layers with transparency).
12349 format: 'image/jpeg',
12350
12351 // @option transparent: Boolean = false
12352 // If `true`, the WMS service will return images with transparency.
12353 transparent: false,
12354
12355 // @option version: String = '1.1.1'
12356 // Version of the WMS service to use
12357 version: '1.1.1'
12358 },
12359
12360 options: {
12361 // @option crs: CRS = null
12362 // Coordinate Reference System to use for the WMS requests, defaults to
12363 // map CRS. Don't change this if you're not sure what it means.
12364 crs: null,
12365
12366 // @option uppercase: Boolean = false
12367 // If `true`, WMS request parameter keys will be uppercase.
12368 uppercase: false
12369 },
12370
12371 initialize: function (url, options) {
12372
12373 this._url = url;
12374
12375 var wmsParams = extend({}, this.defaultWmsParams);
12376
12377 // all keys that are not TileLayer options go to WMS params
12378 for (var i in options) {
12379 if (!(i in this.options)) {
12380 wmsParams[i] = options[i];
12381 }
12382 }
12383
12384 options = setOptions(this, options);
12385
12386 var realRetina = options.detectRetina && Browser.retina ? 2 : 1;
12387 var tileSize = this.getTileSize();
12388 wmsParams.width = tileSize.x * realRetina;
12389 wmsParams.height = tileSize.y * realRetina;
12390
12391 this.wmsParams = wmsParams;
12392 },
12393
12394 onAdd: function (map) {
12395
12396 this._crs = this.options.crs || map.options.crs;
12397 this._wmsVersion = parseFloat(this.wmsParams.version);
12398
12399 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
12400 this.wmsParams[projectionKey] = this._crs.code;
12401
12402 TileLayer.prototype.onAdd.call(this, map);
12403 },
12404
12405 getTileUrl: function (coords) {
12406
12407 var tileBounds = this._tileCoordsToNwSe(coords),
12408 crs = this._crs,
12409 bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
12410 min = bounds.min,
12411 max = bounds.max,
12412 bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
12413 [min.y, min.x, max.y, max.x] :
12414 [min.x, min.y, max.x, max.y]).join(','),
12415 url = TileLayer.prototype.getTileUrl.call(this, coords);
12416 return url +
12417 getParamString(this.wmsParams, url, this.options.uppercase) +
12418 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
12419 },
12420
12421 // @method setParams(params: Object, noRedraw?: Boolean): this
12422 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
12423 setParams: function (params, noRedraw) {
12424
12425 extend(this.wmsParams, params);
12426
12427 if (!noRedraw) {
12428 this.redraw();
12429 }
12430
12431 return this;
12432 }
12433 });
12434
12435
12436 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
12437 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
12438 function tileLayerWMS(url, options) {
12439 return new TileLayerWMS(url, options);
12440 }
12441
12442 TileLayer.WMS = TileLayerWMS;
12443 tileLayer.wms = tileLayerWMS;
12444
12445 /*
12446 * @class Renderer
12447 * @inherits Layer
12448 * @aka L.Renderer
12449 *
12450 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
12451 * DOM container of the renderer, its bounds, and its zoom animation.
12452 *
12453 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
12454 * itself can be added or removed to the map. All paths use a renderer, which can
12455 * be implicit (the map will decide the type of renderer and use it automatically)
12456 * or explicit (using the [`renderer`](#path-renderer) option of the path).
12457 *
12458 * Do not use this class directly, use `SVG` and `Canvas` instead.
12459 *
12460 * @event update: Event
12461 * Fired when the renderer updates its bounds, center and zoom, for example when
12462 * its map has moved
12463 */
12464
12465 var Renderer = Layer.extend({
12466
12467 // @section
12468 // @aka Renderer options
12469 options: {
12470 // @option padding: Number = 0.1
12471 // How much to extend the clip area around the map view (relative to its size)
12472 // e.g. 0.1 would be 10% of map view in each direction
12473 padding: 0.1
12474 },
12475
12476 initialize: function (options) {
12477 setOptions(this, options);
12478 stamp(this);
12479 this._layers = this._layers || {};
12480 },
12481
12482 onAdd: function () {
12483 if (!this._container) {
12484 this._initContainer(); // defined by renderer implementations
12485
12486 // always keep transform-origin as 0 0
12487 addClass(this._container, 'leaflet-zoom-animated');
12488 }
12489
12490 this.getPane().appendChild(this._container);
12491 this._update();
12492 this.on('update', this._updatePaths, this);
12493 },
12494
12495 onRemove: function () {
12496 this.off('update', this._updatePaths, this);
12497 this._destroyContainer();
12498 },
12499
12500 getEvents: function () {
12501 var events = {
12502 viewreset: this._reset,
12503 zoom: this._onZoom,
12504 moveend: this._update,
12505 zoomend: this._onZoomEnd
12506 };
12507 if (this._zoomAnimated) {
12508 events.zoomanim = this._onAnimZoom;
12509 }
12510 return events;
12511 },
12512
12513 _onAnimZoom: function (ev) {
12514 this._updateTransform(ev.center, ev.zoom);
12515 },
12516
12517 _onZoom: function () {
12518 this._updateTransform(this._map.getCenter(), this._map.getZoom());
12519 },
12520
12521 _updateTransform: function (center, zoom) {
12522 var scale = this._map.getZoomScale(zoom, this._zoom),
12523 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
12524 currentCenterPoint = this._map.project(this._center, zoom),
12525
12526 topLeftOffset = viewHalf.multiplyBy(-scale).add(currentCenterPoint)
12527 .subtract(this._map._getNewPixelOrigin(center, zoom));
12528
12529 if (Browser.any3d) {
12530 setTransform(this._container, topLeftOffset, scale);
12531 } else {
12532 setPosition(this._container, topLeftOffset);
12533 }
12534 },
12535
12536 _reset: function () {
12537 this._update();
12538 this._updateTransform(this._center, this._zoom);
12539
12540 for (var id in this._layers) {
12541 this._layers[id]._reset();
12542 }
12543 },
12544
12545 _onZoomEnd: function () {
12546 for (var id in this._layers) {
12547 this._layers[id]._project();
12548 }
12549 },
12550
12551 _updatePaths: function () {
12552 for (var id in this._layers) {
12553 this._layers[id]._update();
12554 }
12555 },
12556
12557 _update: function () {
12558 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
12559 // Subclasses are responsible of firing the 'update' event.
12560 var p = this.options.padding,
12561 size = this._map.getSize(),
12562 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
12563
12564 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
12565
12566 this._center = this._map.getCenter();
12567 this._zoom = this._map.getZoom();
12568 }
12569 });
12570
12571 /*
12572 * @class Canvas
12573 * @inherits Renderer
12574 * @aka L.Canvas
12575 *
12576 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
12577 * Inherits `Renderer`.
12578 *
12579 * Due to [technical limitations](https://caniuse.com/canvas), Canvas is not
12580 * available in all web browsers, notably IE8, and overlapping geometries might
12581 * not display properly in some edge cases.
12582 *
12583 * @example
12584 *
12585 * Use Canvas by default for all paths in the map:
12586 *
12587 * ```js
12588 * var map = L.map('map', {
12589 * renderer: L.canvas()
12590 * });
12591 * ```
12592 *
12593 * Use a Canvas renderer with extra padding for specific vector geometries:
12594 *
12595 * ```js
12596 * var map = L.map('map');
12597 * var myRenderer = L.canvas({ padding: 0.5 });
12598 * var line = L.polyline( coordinates, { renderer: myRenderer } );
12599 * var circle = L.circle( center, { renderer: myRenderer } );
12600 * ```
12601 */
12602
12603 var Canvas = Renderer.extend({
12604
12605 // @section
12606 // @aka Canvas options
12607 options: {
12608 // @option tolerance: Number = 0
12609 // How much to extend the click tolerance around a path/object on the map.
12610 tolerance: 0
12611 },
12612
12613 getEvents: function () {
12614 var events = Renderer.prototype.getEvents.call(this);
12615 events.viewprereset = this._onViewPreReset;
12616 return events;
12617 },
12618
12619 _onViewPreReset: function () {
12620 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
12621 this._postponeUpdatePaths = true;
12622 },
12623
12624 onAdd: function () {
12625 Renderer.prototype.onAdd.call(this);
12626
12627 // Redraw vectors since canvas is cleared upon removal,
12628 // in case of removing the renderer itself from the map.
12629 this._draw();
12630 },
12631
12632 _initContainer: function () {
12633 var container = this._container = document.createElement('canvas');
12634
12635 on(container, 'mousemove', this._onMouseMove, this);
12636 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
12637 on(container, 'mouseout', this._handleMouseOut, this);
12638 container['_leaflet_disable_events'] = true;
12639
12640 this._ctx = container.getContext('2d');
12641 },
12642
12643 _destroyContainer: function () {
12644 cancelAnimFrame(this._redrawRequest);
12645 delete this._ctx;
12646 remove(this._container);
12647 off(this._container);
12648 delete this._container;
12649 },
12650
12651 _updatePaths: function () {
12652 if (this._postponeUpdatePaths) { return; }
12653
12654 var layer;
12655 this._redrawBounds = null;
12656 for (var id in this._layers) {
12657 layer = this._layers[id];
12658 layer._update();
12659 }
12660 this._redraw();
12661 },
12662
12663 _update: function () {
12664 if (this._map._animatingZoom && this._bounds) { return; }
12665
12666 Renderer.prototype._update.call(this);
12667
12668 var b = this._bounds,
12669 container = this._container,
12670 size = b.getSize(),
12671 m = Browser.retina ? 2 : 1;
12672
12673 setPosition(container, b.min);
12674
12675 // set canvas size (also clearing it); use double size on retina
12676 container.width = m * size.x;
12677 container.height = m * size.y;
12678 container.style.width = size.x + 'px';
12679 container.style.height = size.y + 'px';
12680
12681 if (Browser.retina) {
12682 this._ctx.scale(2, 2);
12683 }
12684
12685 // translate so we use the same path coordinates after canvas element moves
12686 this._ctx.translate(-b.min.x, -b.min.y);
12687
12688 // Tell paths to redraw themselves
12689 this.fire('update');
12690 },
12691
12692 _reset: function () {
12693 Renderer.prototype._reset.call(this);
12694
12695 if (this._postponeUpdatePaths) {
12696 this._postponeUpdatePaths = false;
12697 this._updatePaths();
12698 }
12699 },
12700
12701 _initPath: function (layer) {
12702 this._updateDashArray(layer);
12703 this._layers[stamp(layer)] = layer;
12704
12705 var order = layer._order = {
12706 layer: layer,
12707 prev: this._drawLast,
12708 next: null
12709 };
12710 if (this._drawLast) { this._drawLast.next = order; }
12711 this._drawLast = order;
12712 this._drawFirst = this._drawFirst || this._drawLast;
12713 },
12714
12715 _addPath: function (layer) {
12716 this._requestRedraw(layer);
12717 },
12718
12719 _removePath: function (layer) {
12720 var order = layer._order;
12721 var next = order.next;
12722 var prev = order.prev;
12723
12724 if (next) {
12725 next.prev = prev;
12726 } else {
12727 this._drawLast = prev;
12728 }
12729 if (prev) {
12730 prev.next = next;
12731 } else {
12732 this._drawFirst = next;
12733 }
12734
12735 delete layer._order;
12736
12737 delete this._layers[stamp(layer)];
12738
12739 this._requestRedraw(layer);
12740 },
12741
12742 _updatePath: function (layer) {
12743 // Redraw the union of the layer's old pixel
12744 // bounds and the new pixel bounds.
12745 this._extendRedrawBounds(layer);
12746 layer._project();
12747 layer._update();
12748 // The redraw will extend the redraw bounds
12749 // with the new pixel bounds.
12750 this._requestRedraw(layer);
12751 },
12752
12753 _updateStyle: function (layer) {
12754 this._updateDashArray(layer);
12755 this._requestRedraw(layer);
12756 },
12757
12758 _updateDashArray: function (layer) {
12759 if (typeof layer.options.dashArray === 'string') {
12760 var parts = layer.options.dashArray.split(/[, ]+/),
12761 dashArray = [],
12762 dashValue,
12763 i;
12764 for (i = 0; i < parts.length; i++) {
12765 dashValue = Number(parts[i]);
12766 // Ignore dash array containing invalid lengths
12767 if (isNaN(dashValue)) { return; }
12768 dashArray.push(dashValue);
12769 }
12770 layer.options._dashArray = dashArray;
12771 } else {
12772 layer.options._dashArray = layer.options.dashArray;
12773 }
12774 },
12775
12776 _requestRedraw: function (layer) {
12777 if (!this._map) { return; }
12778
12779 this._extendRedrawBounds(layer);
12780 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
12781 },
12782
12783 _extendRedrawBounds: function (layer) {
12784 if (layer._pxBounds) {
12785 var padding = (layer.options.weight || 0) + 1;
12786 this._redrawBounds = this._redrawBounds || new Bounds();
12787 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
12788 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
12789 }
12790 },
12791
12792 _redraw: function () {
12793 this._redrawRequest = null;
12794
12795 if (this._redrawBounds) {
12796 this._redrawBounds.min._floor();
12797 this._redrawBounds.max._ceil();
12798 }
12799
12800 this._clear(); // clear layers in redraw bounds
12801 this._draw(); // draw layers
12802
12803 this._redrawBounds = null;
12804 },
12805
12806 _clear: function () {
12807 var bounds = this._redrawBounds;
12808 if (bounds) {
12809 var size = bounds.getSize();
12810 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
12811 } else {
12812 this._ctx.save();
12813 this._ctx.setTransform(1, 0, 0, 1, 0, 0);
12814 this._ctx.clearRect(0, 0, this._container.width, this._container.height);
12815 this._ctx.restore();
12816 }
12817 },
12818
12819 _draw: function () {
12820 var layer, bounds = this._redrawBounds;
12821 this._ctx.save();
12822 if (bounds) {
12823 var size = bounds.getSize();
12824 this._ctx.beginPath();
12825 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
12826 this._ctx.clip();
12827 }
12828
12829 this._drawing = true;
12830
12831 for (var order = this._drawFirst; order; order = order.next) {
12832 layer = order.layer;
12833 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
12834 layer._updatePath();
12835 }
12836 }
12837
12838 this._drawing = false;
12839
12840 this._ctx.restore(); // Restore state before clipping.
12841 },
12842
12843 _updatePoly: function (layer, closed) {
12844 if (!this._drawing) { return; }
12845
12846 var i, j, len2, p,
12847 parts = layer._parts,
12848 len = parts.length,
12849 ctx = this._ctx;
12850
12851 if (!len) { return; }
12852
12853 ctx.beginPath();
12854
12855 for (i = 0; i < len; i++) {
12856 for (j = 0, len2 = parts[i].length; j < len2; j++) {
12857 p = parts[i][j];
12858 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
12859 }
12860 if (closed) {
12861 ctx.closePath();
12862 }
12863 }
12864
12865 this._fillStroke(ctx, layer);
12866
12867 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
12868 },
12869
12870 _updateCircle: function (layer) {
12871
12872 if (!this._drawing || layer._empty()) { return; }
12873
12874 var p = layer._point,
12875 ctx = this._ctx,
12876 r = Math.max(Math.round(layer._radius), 1),
12877 s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
12878
12879 if (s !== 1) {
12880 ctx.save();
12881 ctx.scale(1, s);
12882 }
12883
12884 ctx.beginPath();
12885 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
12886
12887 if (s !== 1) {
12888 ctx.restore();
12889 }
12890
12891 this._fillStroke(ctx, layer);
12892 },
12893
12894 _fillStroke: function (ctx, layer) {
12895 var options = layer.options;
12896
12897 if (options.fill) {
12898 ctx.globalAlpha = options.fillOpacity;
12899 ctx.fillStyle = options.fillColor || options.color;
12900 ctx.fill(options.fillRule || 'evenodd');
12901 }
12902
12903 if (options.stroke && options.weight !== 0) {
12904 if (ctx.setLineDash) {
12905 ctx.setLineDash(layer.options && layer.options._dashArray || []);
12906 }
12907 ctx.globalAlpha = options.opacity;
12908 ctx.lineWidth = options.weight;
12909 ctx.strokeStyle = options.color;
12910 ctx.lineCap = options.lineCap;
12911 ctx.lineJoin = options.lineJoin;
12912 ctx.stroke();
12913 }
12914 },
12915
12916 // Canvas obviously doesn't have mouse events for individual drawn objects,
12917 // so we emulate that by calculating what's under the mouse on mousemove/click manually
12918
12919 _onClick: function (e) {
12920 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
12921
12922 for (var order = this._drawFirst; order; order = order.next) {
12923 layer = order.layer;
12924 if (layer.options.interactive && layer._containsPoint(point)) {
12925 if (!(e.type === 'click' || e.type === 'preclick') || !this._map._draggableMoved(layer)) {
12926 clickedLayer = layer;
12927 }
12928 }
12929 }
12930 this._fireEvent(clickedLayer ? [clickedLayer] : false, e);
12931 },
12932
12933 _onMouseMove: function (e) {
12934 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
12935
12936 var point = this._map.mouseEventToLayerPoint(e);
12937 this._handleMouseHover(e, point);
12938 },
12939
12940
12941 _handleMouseOut: function (e) {
12942 var layer = this._hoveredLayer;
12943 if (layer) {
12944 // if we're leaving the layer, fire mouseout
12945 removeClass(this._container, 'leaflet-interactive');
12946 this._fireEvent([layer], e, 'mouseout');
12947 this._hoveredLayer = null;
12948 this._mouseHoverThrottled = false;
12949 }
12950 },
12951
12952 _handleMouseHover: function (e, point) {
12953 if (this._mouseHoverThrottled) {
12954 return;
12955 }
12956
12957 var layer, candidateHoveredLayer;
12958
12959 for (var order = this._drawFirst; order; order = order.next) {
12960 layer = order.layer;
12961 if (layer.options.interactive && layer._containsPoint(point)) {
12962 candidateHoveredLayer = layer;
12963 }
12964 }
12965
12966 if (candidateHoveredLayer !== this._hoveredLayer) {
12967 this._handleMouseOut(e);
12968
12969 if (candidateHoveredLayer) {
12970 addClass(this._container, 'leaflet-interactive'); // change cursor
12971 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
12972 this._hoveredLayer = candidateHoveredLayer;
12973 }
12974 }
12975
12976 this._fireEvent(this._hoveredLayer ? [this._hoveredLayer] : false, e);
12977
12978 this._mouseHoverThrottled = true;
12979 setTimeout(bind(function () {
12980 this._mouseHoverThrottled = false;
12981 }, this), 32);
12982 },
12983
12984 _fireEvent: function (layers, e, type) {
12985 this._map._fireDOMEvent(e, type || e.type, layers);
12986 },
12987
12988 _bringToFront: function (layer) {
12989 var order = layer._order;
12990
12991 if (!order) { return; }
12992
12993 var next = order.next;
12994 var prev = order.prev;
12995
12996 if (next) {
12997 next.prev = prev;
12998 } else {
12999 // Already last
13000 return;
13001 }
13002 if (prev) {
13003 prev.next = next;
13004 } else if (next) {
13005 // Update first entry unless this is the
13006 // single entry
13007 this._drawFirst = next;
13008 }
13009
13010 order.prev = this._drawLast;
13011 this._drawLast.next = order;
13012
13013 order.next = null;
13014 this._drawLast = order;
13015
13016 this._requestRedraw(layer);
13017 },
13018
13019 _bringToBack: function (layer) {
13020 var order = layer._order;
13021
13022 if (!order) { return; }
13023
13024 var next = order.next;
13025 var prev = order.prev;
13026
13027 if (prev) {
13028 prev.next = next;
13029 } else {
13030 // Already first
13031 return;
13032 }
13033 if (next) {
13034 next.prev = prev;
13035 } else if (prev) {
13036 // Update last entry unless this is the
13037 // single entry
13038 this._drawLast = prev;
13039 }
13040
13041 order.prev = null;
13042
13043 order.next = this._drawFirst;
13044 this._drawFirst.prev = order;
13045 this._drawFirst = order;
13046
13047 this._requestRedraw(layer);
13048 }
13049 });
13050
13051 // @factory L.canvas(options?: Renderer options)
13052 // Creates a Canvas renderer with the given options.
13053 function canvas(options) {
13054 return Browser.canvas ? new Canvas(options) : null;
13055 }
13056
13057 /*
13058 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
13059 */
13060
13061
13062 var vmlCreate = (function () {
13063 try {
13064 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
13065 return function (name) {
13066 return document.createElement('<lvml:' + name + ' class="lvml">');
13067 };
13068 } catch (e) {
13069 // Do not return fn from catch block so `e` can be garbage collected
13070 // See https://github.com/Leaflet/Leaflet/pull/7279
13071 }
13072 return function (name) {
13073 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
13074 };
13075 })();
13076
13077
13078 /*
13079 * @class SVG
13080 *
13081 *
13082 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
13083 * with old versions of Internet Explorer.
13084 */
13085
13086 // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
13087 var vmlMixin = {
13088
13089 _initContainer: function () {
13090 this._container = create$1('div', 'leaflet-vml-container');
13091 },
13092
13093 _update: function () {
13094 if (this._map._animatingZoom) { return; }
13095 Renderer.prototype._update.call(this);
13096 this.fire('update');
13097 },
13098
13099 _initPath: function (layer) {
13100 var container = layer._container = vmlCreate('shape');
13101
13102 addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
13103
13104 container.coordsize = '1 1';
13105
13106 layer._path = vmlCreate('path');
13107 container.appendChild(layer._path);
13108
13109 this._updateStyle(layer);
13110 this._layers[stamp(layer)] = layer;
13111 },
13112
13113 _addPath: function (layer) {
13114 var container = layer._container;
13115 this._container.appendChild(container);
13116
13117 if (layer.options.interactive) {
13118 layer.addInteractiveTarget(container);
13119 }
13120 },
13121
13122 _removePath: function (layer) {
13123 var container = layer._container;
13124 remove(container);
13125 layer.removeInteractiveTarget(container);
13126 delete this._layers[stamp(layer)];
13127 },
13128
13129 _updateStyle: function (layer) {
13130 var stroke = layer._stroke,
13131 fill = layer._fill,
13132 options = layer.options,
13133 container = layer._container;
13134
13135 container.stroked = !!options.stroke;
13136 container.filled = !!options.fill;
13137
13138 if (options.stroke) {
13139 if (!stroke) {
13140 stroke = layer._stroke = vmlCreate('stroke');
13141 }
13142 container.appendChild(stroke);
13143 stroke.weight = options.weight + 'px';
13144 stroke.color = options.color;
13145 stroke.opacity = options.opacity;
13146
13147 if (options.dashArray) {
13148 stroke.dashStyle = isArray(options.dashArray) ?
13149 options.dashArray.join(' ') :
13150 options.dashArray.replace(/( *, *)/g, ' ');
13151 } else {
13152 stroke.dashStyle = '';
13153 }
13154 stroke.endcap = options.lineCap.replace('butt', 'flat');
13155 stroke.joinstyle = options.lineJoin;
13156
13157 } else if (stroke) {
13158 container.removeChild(stroke);
13159 layer._stroke = null;
13160 }
13161
13162 if (options.fill) {
13163 if (!fill) {
13164 fill = layer._fill = vmlCreate('fill');
13165 }
13166 container.appendChild(fill);
13167 fill.color = options.fillColor || options.color;
13168 fill.opacity = options.fillOpacity;
13169
13170 } else if (fill) {
13171 container.removeChild(fill);
13172 layer._fill = null;
13173 }
13174 },
13175
13176 _updateCircle: function (layer) {
13177 var p = layer._point.round(),
13178 r = Math.round(layer._radius),
13179 r2 = Math.round(layer._radiusY || r);
13180
13181 this._setPath(layer, layer._empty() ? 'M0 0' :
13182 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
13183 },
13184
13185 _setPath: function (layer, path) {
13186 layer._path.v = path;
13187 },
13188
13189 _bringToFront: function (layer) {
13190 toFront(layer._container);
13191 },
13192
13193 _bringToBack: function (layer) {
13194 toBack(layer._container);
13195 }
13196 };
13197
13198 var create = Browser.vml ? vmlCreate : svgCreate;
13199
13200 /*
13201 * @class SVG
13202 * @inherits Renderer
13203 * @aka L.SVG
13204 *
13205 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
13206 * Inherits `Renderer`.
13207 *
13208 * Due to [technical limitations](https://caniuse.com/svg), SVG is not
13209 * available in all web browsers, notably Android 2.x and 3.x.
13210 *
13211 * Although SVG is not available on IE7 and IE8, these browsers support
13212 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
13213 * (a now deprecated technology), and the SVG renderer will fall back to VML in
13214 * this case.
13215 *
13216 * @example
13217 *
13218 * Use SVG by default for all paths in the map:
13219 *
13220 * ```js
13221 * var map = L.map('map', {
13222 * renderer: L.svg()
13223 * });
13224 * ```
13225 *
13226 * Use a SVG renderer with extra padding for specific vector geometries:
13227 *
13228 * ```js
13229 * var map = L.map('map');
13230 * var myRenderer = L.svg({ padding: 0.5 });
13231 * var line = L.polyline( coordinates, { renderer: myRenderer } );
13232 * var circle = L.circle( center, { renderer: myRenderer } );
13233 * ```
13234 */
13235
13236 var SVG = Renderer.extend({
13237
13238 _initContainer: function () {
13239 this._container = create('svg');
13240
13241 // makes it possible to click through svg root; we'll reset it back in individual paths
13242 this._container.setAttribute('pointer-events', 'none');
13243
13244 this._rootGroup = create('g');
13245 this._container.appendChild(this._rootGroup);
13246 },
13247
13248 _destroyContainer: function () {
13249 remove(this._container);
13250 off(this._container);
13251 delete this._container;
13252 delete this._rootGroup;
13253 delete this._svgSize;
13254 },
13255
13256 _update: function () {
13257 if (this._map._animatingZoom && this._bounds) { return; }
13258
13259 Renderer.prototype._update.call(this);
13260
13261 var b = this._bounds,
13262 size = b.getSize(),
13263 container = this._container;
13264
13265 // set size of svg-container if changed
13266 if (!this._svgSize || !this._svgSize.equals(size)) {
13267 this._svgSize = size;
13268 container.setAttribute('width', size.x);
13269 container.setAttribute('height', size.y);
13270 }
13271
13272 // movement: update container viewBox so that we don't have to change coordinates of individual layers
13273 setPosition(container, b.min);
13274 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
13275
13276 this.fire('update');
13277 },
13278
13279 // methods below are called by vector layers implementations
13280
13281 _initPath: function (layer) {
13282 var path = layer._path = create('path');
13283
13284 // @namespace Path
13285 // @option className: String = null
13286 // Custom class name set on an element. Only for SVG renderer.
13287 if (layer.options.className) {
13288 addClass(path, layer.options.className);
13289 }
13290
13291 if (layer.options.interactive) {
13292 addClass(path, 'leaflet-interactive');
13293 }
13294
13295 this._updateStyle(layer);
13296 this._layers[stamp(layer)] = layer;
13297 },
13298
13299 _addPath: function (layer) {
13300 if (!this._rootGroup) { this._initContainer(); }
13301 this._rootGroup.appendChild(layer._path);
13302 layer.addInteractiveTarget(layer._path);
13303 },
13304
13305 _removePath: function (layer) {
13306 remove(layer._path);
13307 layer.removeInteractiveTarget(layer._path);
13308 delete this._layers[stamp(layer)];
13309 },
13310
13311 _updatePath: function (layer) {
13312 layer._project();
13313 layer._update();
13314 },
13315
13316 _updateStyle: function (layer) {
13317 var path = layer._path,
13318 options = layer.options;
13319
13320 if (!path) { return; }
13321
13322 if (options.stroke) {
13323 path.setAttribute('stroke', options.color);
13324 path.setAttribute('stroke-opacity', options.opacity);
13325 path.setAttribute('stroke-width', options.weight);
13326 path.setAttribute('stroke-linecap', options.lineCap);
13327 path.setAttribute('stroke-linejoin', options.lineJoin);
13328
13329 if (options.dashArray) {
13330 path.setAttribute('stroke-dasharray', options.dashArray);
13331 } else {
13332 path.removeAttribute('stroke-dasharray');
13333 }
13334
13335 if (options.dashOffset) {
13336 path.setAttribute('stroke-dashoffset', options.dashOffset);
13337 } else {
13338 path.removeAttribute('stroke-dashoffset');
13339 }
13340 } else {
13341 path.setAttribute('stroke', 'none');
13342 }
13343
13344 if (options.fill) {
13345 path.setAttribute('fill', options.fillColor || options.color);
13346 path.setAttribute('fill-opacity', options.fillOpacity);
13347 path.setAttribute('fill-rule', options.fillRule || 'evenodd');
13348 } else {
13349 path.setAttribute('fill', 'none');
13350 }
13351 },
13352
13353 _updatePoly: function (layer, closed) {
13354 this._setPath(layer, pointsToPath(layer._parts, closed));
13355 },
13356
13357 _updateCircle: function (layer) {
13358 var p = layer._point,
13359 r = Math.max(Math.round(layer._radius), 1),
13360 r2 = Math.max(Math.round(layer._radiusY), 1) || r,
13361 arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
13362
13363 // drawing a circle with two half-arcs
13364 var d = layer._empty() ? 'M0 0' :
13365 'M' + (p.x - r) + ',' + p.y +
13366 arc + (r * 2) + ',0 ' +
13367 arc + (-r * 2) + ',0 ';
13368
13369 this._setPath(layer, d);
13370 },
13371
13372 _setPath: function (layer, path) {
13373 layer._path.setAttribute('d', path);
13374 },
13375
13376 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
13377 _bringToFront: function (layer) {
13378 toFront(layer._path);
13379 },
13380
13381 _bringToBack: function (layer) {
13382 toBack(layer._path);
13383 }
13384 });
13385
13386 if (Browser.vml) {
13387 SVG.include(vmlMixin);
13388 }
13389
13390 // @namespace SVG
13391 // @factory L.svg(options?: Renderer options)
13392 // Creates a SVG renderer with the given options.
13393 function svg(options) {
13394 return Browser.svg || Browser.vml ? new SVG(options) : null;
13395 }
13396
13397 Map.include({
13398 // @namespace Map; @method getRenderer(layer: Path): Renderer
13399 // Returns the instance of `Renderer` that should be used to render the given
13400 // `Path`. It will ensure that the `renderer` options of the map and paths
13401 // are respected, and that the renderers do exist on the map.
13402 getRenderer: function (layer) {
13403 // @namespace Path; @option renderer: Renderer
13404 // Use this specific instance of `Renderer` for this path. Takes
13405 // precedence over the map's [default renderer](#map-renderer).
13406 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
13407
13408 if (!renderer) {
13409 renderer = this._renderer = this._createRenderer();
13410 }
13411
13412 if (!this.hasLayer(renderer)) {
13413 this.addLayer(renderer);
13414 }
13415 return renderer;
13416 },
13417
13418 _getPaneRenderer: function (name) {
13419 if (name === 'overlayPane' || name === undefined) {
13420 return false;
13421 }
13422
13423 var renderer = this._paneRenderers[name];
13424 if (renderer === undefined) {
13425 renderer = this._createRenderer({pane: name});
13426 this._paneRenderers[name] = renderer;
13427 }
13428 return renderer;
13429 },
13430
13431 _createRenderer: function (options) {
13432 // @namespace Map; @option preferCanvas: Boolean = false
13433 // Whether `Path`s should be rendered on a `Canvas` renderer.
13434 // By default, all `Path`s are rendered in a `SVG` renderer.
13435 return (this.options.preferCanvas && canvas(options)) || svg(options);
13436 }
13437 });
13438
13439 /*
13440 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
13441 */
13442
13443 /*
13444 * @class Rectangle
13445 * @aka L.Rectangle
13446 * @inherits Polygon
13447 *
13448 * A class for drawing rectangle overlays on a map. Extends `Polygon`.
13449 *
13450 * @example
13451 *
13452 * ```js
13453 * // define rectangle geographical bounds
13454 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
13455 *
13456 * // create an orange rectangle
13457 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
13458 *
13459 * // zoom the map to the rectangle bounds
13460 * map.fitBounds(bounds);
13461 * ```
13462 *
13463 */
13464
13465
13466 var Rectangle = Polygon.extend({
13467 initialize: function (latLngBounds, options) {
13468 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
13469 },
13470
13471 // @method setBounds(latLngBounds: LatLngBounds): this
13472 // Redraws the rectangle with the passed bounds.
13473 setBounds: function (latLngBounds) {
13474 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
13475 },
13476
13477 _boundsToLatLngs: function (latLngBounds) {
13478 latLngBounds = toLatLngBounds(latLngBounds);
13479 return [
13480 latLngBounds.getSouthWest(),
13481 latLngBounds.getNorthWest(),
13482 latLngBounds.getNorthEast(),
13483 latLngBounds.getSouthEast()
13484 ];
13485 }
13486 });
13487
13488
13489 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
13490 function rectangle(latLngBounds, options) {
13491 return new Rectangle(latLngBounds, options);
13492 }
13493
13494 SVG.create = create;
13495 SVG.pointsToPath = pointsToPath;
13496
13497 GeoJSON.geometryToLayer = geometryToLayer;
13498 GeoJSON.coordsToLatLng = coordsToLatLng;
13499 GeoJSON.coordsToLatLngs = coordsToLatLngs;
13500 GeoJSON.latLngToCoords = latLngToCoords;
13501 GeoJSON.latLngsToCoords = latLngsToCoords;
13502 GeoJSON.getFeature = getFeature;
13503 GeoJSON.asFeature = asFeature;
13504
13505 /*
13506 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
13507 * (zoom to a selected bounding box), enabled by default.
13508 */
13509
13510 // @namespace Map
13511 // @section Interaction Options
13512 Map.mergeOptions({
13513 // @option boxZoom: Boolean = true
13514 // Whether the map can be zoomed to a rectangular area specified by
13515 // dragging the mouse while pressing the shift key.
13516 boxZoom: true
13517 });
13518
13519 var BoxZoom = Handler.extend({
13520 initialize: function (map) {
13521 this._map = map;
13522 this._container = map._container;
13523 this._pane = map._panes.overlayPane;
13524 this._resetStateTimeout = 0;
13525 map.on('unload', this._destroy, this);
13526 },
13527
13528 addHooks: function () {
13529 on(this._container, 'mousedown', this._onMouseDown, this);
13530 },
13531
13532 removeHooks: function () {
13533 off(this._container, 'mousedown', this._onMouseDown, this);
13534 },
13535
13536 moved: function () {
13537 return this._moved;
13538 },
13539
13540 _destroy: function () {
13541 remove(this._pane);
13542 delete this._pane;
13543 },
13544
13545 _resetState: function () {
13546 this._resetStateTimeout = 0;
13547 this._moved = false;
13548 },
13549
13550 _clearDeferredResetState: function () {
13551 if (this._resetStateTimeout !== 0) {
13552 clearTimeout(this._resetStateTimeout);
13553 this._resetStateTimeout = 0;
13554 }
13555 },
13556
13557 _onMouseDown: function (e) {
13558 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
13559
13560 // Clear the deferred resetState if it hasn't executed yet, otherwise it
13561 // will interrupt the interaction and orphan a box element in the container.
13562 this._clearDeferredResetState();
13563 this._resetState();
13564
13565 disableTextSelection();
13566 disableImageDrag();
13567
13568 this._startPoint = this._map.mouseEventToContainerPoint(e);
13569
13570 on(document, {
13571 contextmenu: stop,
13572 mousemove: this._onMouseMove,
13573 mouseup: this._onMouseUp,
13574 keydown: this._onKeyDown
13575 }, this);
13576 },
13577
13578 _onMouseMove: function (e) {
13579 if (!this._moved) {
13580 this._moved = true;
13581
13582 this._box = create$1('div', 'leaflet-zoom-box', this._container);
13583 addClass(this._container, 'leaflet-crosshair');
13584
13585 this._map.fire('boxzoomstart');
13586 }
13587
13588 this._point = this._map.mouseEventToContainerPoint(e);
13589
13590 var bounds = new Bounds(this._point, this._startPoint),
13591 size = bounds.getSize();
13592
13593 setPosition(this._box, bounds.min);
13594
13595 this._box.style.width = size.x + 'px';
13596 this._box.style.height = size.y + 'px';
13597 },
13598
13599 _finish: function () {
13600 if (this._moved) {
13601 remove(this._box);
13602 removeClass(this._container, 'leaflet-crosshair');
13603 }
13604
13605 enableTextSelection();
13606 enableImageDrag();
13607
13608 off(document, {
13609 contextmenu: stop,
13610 mousemove: this._onMouseMove,
13611 mouseup: this._onMouseUp,
13612 keydown: this._onKeyDown
13613 }, this);
13614 },
13615
13616 _onMouseUp: function (e) {
13617 if ((e.which !== 1) && (e.button !== 1)) { return; }
13618
13619 this._finish();
13620
13621 if (!this._moved) { return; }
13622 // Postpone to next JS tick so internal click event handling
13623 // still see it as "moved".
13624 this._clearDeferredResetState();
13625 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
13626
13627 var bounds = new LatLngBounds(
13628 this._map.containerPointToLatLng(this._startPoint),
13629 this._map.containerPointToLatLng(this._point));
13630
13631 this._map
13632 .fitBounds(bounds)
13633 .fire('boxzoomend', {boxZoomBounds: bounds});
13634 },
13635
13636 _onKeyDown: function (e) {
13637 if (e.keyCode === 27) {
13638 this._finish();
13639 this._clearDeferredResetState();
13640 this._resetState();
13641 }
13642 }
13643 });
13644
13645 // @section Handlers
13646 // @property boxZoom: Handler
13647 // Box (shift-drag with mouse) zoom handler.
13648 Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
13649
13650 /*
13651 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
13652 */
13653
13654 // @namespace Map
13655 // @section Interaction Options
13656
13657 Map.mergeOptions({
13658 // @option doubleClickZoom: Boolean|String = true
13659 // Whether the map can be zoomed in by double clicking on it and
13660 // zoomed out by double clicking while holding shift. If passed
13661 // `'center'`, double-click zoom will zoom to the center of the
13662 // view regardless of where the mouse was.
13663 doubleClickZoom: true
13664 });
13665
13666 var DoubleClickZoom = Handler.extend({
13667 addHooks: function () {
13668 this._map.on('dblclick', this._onDoubleClick, this);
13669 },
13670
13671 removeHooks: function () {
13672 this._map.off('dblclick', this._onDoubleClick, this);
13673 },
13674
13675 _onDoubleClick: function (e) {
13676 var map = this._map,
13677 oldZoom = map.getZoom(),
13678 delta = map.options.zoomDelta,
13679 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
13680
13681 if (map.options.doubleClickZoom === 'center') {
13682 map.setZoom(zoom);
13683 } else {
13684 map.setZoomAround(e.containerPoint, zoom);
13685 }
13686 }
13687 });
13688
13689 // @section Handlers
13690 //
13691 // Map properties include interaction handlers that allow you to control
13692 // interaction behavior in runtime, enabling or disabling certain features such
13693 // as dragging or touch zoom (see `Handler` methods). For example:
13694 //
13695 // ```js
13696 // map.doubleClickZoom.disable();
13697 // ```
13698 //
13699 // @property doubleClickZoom: Handler
13700 // Double click zoom handler.
13701 Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
13702
13703 /*
13704 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
13705 */
13706
13707 // @namespace Map
13708 // @section Interaction Options
13709 Map.mergeOptions({
13710 // @option dragging: Boolean = true
13711 // Whether the map is draggable with mouse/touch or not.
13712 dragging: true,
13713
13714 // @section Panning Inertia Options
13715 // @option inertia: Boolean = *
13716 // If enabled, panning of the map will have an inertia effect where
13717 // the map builds momentum while dragging and continues moving in
13718 // the same direction for some time. Feels especially nice on touch
13719 // devices. Enabled by default.
13720 inertia: true,
13721
13722 // @option inertiaDeceleration: Number = 3000
13723 // The rate with which the inertial movement slows down, in pixels/second².
13724 inertiaDeceleration: 3400, // px/s^2
13725
13726 // @option inertiaMaxSpeed: Number = Infinity
13727 // Max speed of the inertial movement, in pixels/second.
13728 inertiaMaxSpeed: Infinity, // px/s
13729
13730 // @option easeLinearity: Number = 0.2
13731 easeLinearity: 0.2,
13732
13733 // TODO refactor, move to CRS
13734 // @option worldCopyJump: Boolean = false
13735 // With this option enabled, the map tracks when you pan to another "copy"
13736 // of the world and seamlessly jumps to the original one so that all overlays
13737 // like markers and vector layers are still visible.
13738 worldCopyJump: false,
13739
13740 // @option maxBoundsViscosity: Number = 0.0
13741 // If `maxBounds` is set, this option will control how solid the bounds
13742 // are when dragging the map around. The default value of `0.0` allows the
13743 // user to drag outside the bounds at normal speed, higher values will
13744 // slow down map dragging outside bounds, and `1.0` makes the bounds fully
13745 // solid, preventing the user from dragging outside the bounds.
13746 maxBoundsViscosity: 0.0
13747 });
13748
13749 var Drag = Handler.extend({
13750 addHooks: function () {
13751 if (!this._draggable) {
13752 var map = this._map;
13753
13754 this._draggable = new Draggable(map._mapPane, map._container);
13755
13756 this._draggable.on({
13757 dragstart: this._onDragStart,
13758 drag: this._onDrag,
13759 dragend: this._onDragEnd
13760 }, this);
13761
13762 this._draggable.on('predrag', this._onPreDragLimit, this);
13763 if (map.options.worldCopyJump) {
13764 this._draggable.on('predrag', this._onPreDragWrap, this);
13765 map.on('zoomend', this._onZoomEnd, this);
13766
13767 map.whenReady(this._onZoomEnd, this);
13768 }
13769 }
13770 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
13771 this._draggable.enable();
13772 this._positions = [];
13773 this._times = [];
13774 },
13775
13776 removeHooks: function () {
13777 removeClass(this._map._container, 'leaflet-grab');
13778 removeClass(this._map._container, 'leaflet-touch-drag');
13779 this._draggable.disable();
13780 },
13781
13782 moved: function () {
13783 return this._draggable && this._draggable._moved;
13784 },
13785
13786 moving: function () {
13787 return this._draggable && this._draggable._moving;
13788 },
13789
13790 _onDragStart: function () {
13791 var map = this._map;
13792
13793 map._stop();
13794 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
13795 var bounds = toLatLngBounds(this._map.options.maxBounds);
13796
13797 this._offsetLimit = toBounds(
13798 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
13799 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
13800 .add(this._map.getSize()));
13801
13802 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
13803 } else {
13804 this._offsetLimit = null;
13805 }
13806
13807 map
13808 .fire('movestart')
13809 .fire('dragstart');
13810
13811 if (map.options.inertia) {
13812 this._positions = [];
13813 this._times = [];
13814 }
13815 },
13816
13817 _onDrag: function (e) {
13818 if (this._map.options.inertia) {
13819 var time = this._lastTime = +new Date(),
13820 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
13821
13822 this._positions.push(pos);
13823 this._times.push(time);
13824
13825 this._prunePositions(time);
13826 }
13827
13828 this._map
13829 .fire('move', e)
13830 .fire('drag', e);
13831 },
13832
13833 _prunePositions: function (time) {
13834 while (this._positions.length > 1 && time - this._times[0] > 50) {
13835 this._positions.shift();
13836 this._times.shift();
13837 }
13838 },
13839
13840 _onZoomEnd: function () {
13841 var pxCenter = this._map.getSize().divideBy(2),
13842 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
13843
13844 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
13845 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
13846 },
13847
13848 _viscousLimit: function (value, threshold) {
13849 return value - (value - threshold) * this._viscosity;
13850 },
13851
13852 _onPreDragLimit: function () {
13853 if (!this._viscosity || !this._offsetLimit) { return; }
13854
13855 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
13856
13857 var limit = this._offsetLimit;
13858 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
13859 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
13860 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
13861 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
13862
13863 this._draggable._newPos = this._draggable._startPos.add(offset);
13864 },
13865
13866 _onPreDragWrap: function () {
13867 // TODO refactor to be able to adjust map pane position after zoom
13868 var worldWidth = this._worldWidth,
13869 halfWidth = Math.round(worldWidth / 2),
13870 dx = this._initialWorldOffset,
13871 x = this._draggable._newPos.x,
13872 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
13873 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
13874 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
13875
13876 this._draggable._absPos = this._draggable._newPos.clone();
13877 this._draggable._newPos.x = newX;
13878 },
13879
13880 _onDragEnd: function (e) {
13881 var map = this._map,
13882 options = map.options,
13883
13884 noInertia = !options.inertia || e.noInertia || this._times.length < 2;
13885
13886 map.fire('dragend', e);
13887
13888 if (noInertia) {
13889 map.fire('moveend');
13890
13891 } else {
13892 this._prunePositions(+new Date());
13893
13894 var direction = this._lastPos.subtract(this._positions[0]),
13895 duration = (this._lastTime - this._times[0]) / 1000,
13896 ease = options.easeLinearity,
13897
13898 speedVector = direction.multiplyBy(ease / duration),
13899 speed = speedVector.distanceTo([0, 0]),
13900
13901 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
13902 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
13903
13904 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
13905 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
13906
13907 if (!offset.x && !offset.y) {
13908 map.fire('moveend');
13909
13910 } else {
13911 offset = map._limitOffset(offset, map.options.maxBounds);
13912
13913 requestAnimFrame(function () {
13914 map.panBy(offset, {
13915 duration: decelerationDuration,
13916 easeLinearity: ease,
13917 noMoveStart: true,
13918 animate: true
13919 });
13920 });
13921 }
13922 }
13923 }
13924 });
13925
13926 // @section Handlers
13927 // @property dragging: Handler
13928 // Map dragging handler (by both mouse and touch).
13929 Map.addInitHook('addHandler', 'dragging', Drag);
13930
13931 /*
13932 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
13933 */
13934
13935 // @namespace Map
13936 // @section Keyboard Navigation Options
13937 Map.mergeOptions({
13938 // @option keyboard: Boolean = true
13939 // Makes the map focusable and allows users to navigate the map with keyboard
13940 // arrows and `+`/`-` keys.
13941 keyboard: true,
13942
13943 // @option keyboardPanDelta: Number = 80
13944 // Amount of pixels to pan when pressing an arrow key.
13945 keyboardPanDelta: 80
13946 });
13947
13948 var Keyboard = Handler.extend({
13949
13950 keyCodes: {
13951 left: [37],
13952 right: [39],
13953 down: [40],
13954 up: [38],
13955 zoomIn: [187, 107, 61, 171],
13956 zoomOut: [189, 109, 54, 173]
13957 },
13958
13959 initialize: function (map) {
13960 this._map = map;
13961
13962 this._setPanDelta(map.options.keyboardPanDelta);
13963 this._setZoomDelta(map.options.zoomDelta);
13964 },
13965
13966 addHooks: function () {
13967 var container = this._map._container;
13968
13969 // make the container focusable by tabbing
13970 if (container.tabIndex <= 0) {
13971 container.tabIndex = '0';
13972 }
13973
13974 on(container, {
13975 focus: this._onFocus,
13976 blur: this._onBlur,
13977 mousedown: this._onMouseDown
13978 }, this);
13979
13980 this._map.on({
13981 focus: this._addHooks,
13982 blur: this._removeHooks
13983 }, this);
13984 },
13985
13986 removeHooks: function () {
13987 this._removeHooks();
13988
13989 off(this._map._container, {
13990 focus: this._onFocus,
13991 blur: this._onBlur,
13992 mousedown: this._onMouseDown
13993 }, this);
13994
13995 this._map.off({
13996 focus: this._addHooks,
13997 blur: this._removeHooks
13998 }, this);
13999 },
14000
14001 _onMouseDown: function () {
14002 if (this._focused) { return; }
14003
14004 var body = document.body,
14005 docEl = document.documentElement,
14006 top = body.scrollTop || docEl.scrollTop,
14007 left = body.scrollLeft || docEl.scrollLeft;
14008
14009 this._map._container.focus();
14010
14011 window.scrollTo(left, top);
14012 },
14013
14014 _onFocus: function () {
14015 this._focused = true;
14016 this._map.fire('focus');
14017 },
14018
14019 _onBlur: function () {
14020 this._focused = false;
14021 this._map.fire('blur');
14022 },
14023
14024 _setPanDelta: function (panDelta) {
14025 var keys = this._panKeys = {},
14026 codes = this.keyCodes,
14027 i, len;
14028
14029 for (i = 0, len = codes.left.length; i < len; i++) {
14030 keys[codes.left[i]] = [-1 * panDelta, 0];
14031 }
14032 for (i = 0, len = codes.right.length; i < len; i++) {
14033 keys[codes.right[i]] = [panDelta, 0];
14034 }
14035 for (i = 0, len = codes.down.length; i < len; i++) {
14036 keys[codes.down[i]] = [0, panDelta];
14037 }
14038 for (i = 0, len = codes.up.length; i < len; i++) {
14039 keys[codes.up[i]] = [0, -1 * panDelta];
14040 }
14041 },
14042
14043 _setZoomDelta: function (zoomDelta) {
14044 var keys = this._zoomKeys = {},
14045 codes = this.keyCodes,
14046 i, len;
14047
14048 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
14049 keys[codes.zoomIn[i]] = zoomDelta;
14050 }
14051 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
14052 keys[codes.zoomOut[i]] = -zoomDelta;
14053 }
14054 },
14055
14056 _addHooks: function () {
14057 on(document, 'keydown', this._onKeyDown, this);
14058 },
14059
14060 _removeHooks: function () {
14061 off(document, 'keydown', this._onKeyDown, this);
14062 },
14063
14064 _onKeyDown: function (e) {
14065 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
14066
14067 var key = e.keyCode,
14068 map = this._map,
14069 offset;
14070
14071 if (key in this._panKeys) {
14072 if (!map._panAnim || !map._panAnim._inProgress) {
14073 offset = this._panKeys[key];
14074 if (e.shiftKey) {
14075 offset = toPoint(offset).multiplyBy(3);
14076 }
14077
14078 if (map.options.maxBounds) {
14079 offset = map._limitOffset(toPoint(offset), map.options.maxBounds);
14080 }
14081
14082 if (map.options.worldCopyJump) {
14083 var newLatLng = map.wrapLatLng(map.unproject(map.project(map.getCenter()).add(offset)));
14084 map.panTo(newLatLng);
14085 } else {
14086 map.panBy(offset);
14087 }
14088 }
14089 } else if (key in this._zoomKeys) {
14090 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
14091
14092 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
14093 map.closePopup();
14094
14095 } else {
14096 return;
14097 }
14098
14099 stop(e);
14100 }
14101 });
14102
14103 // @section Handlers
14104 // @section Handlers
14105 // @property keyboard: Handler
14106 // Keyboard navigation handler.
14107 Map.addInitHook('addHandler', 'keyboard', Keyboard);
14108
14109 /*
14110 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
14111 */
14112
14113 // @namespace Map
14114 // @section Interaction Options
14115 Map.mergeOptions({
14116 // @section Mouse wheel options
14117 // @option scrollWheelZoom: Boolean|String = true
14118 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
14119 // it will zoom to the center of the view regardless of where the mouse was.
14120 scrollWheelZoom: true,
14121
14122 // @option wheelDebounceTime: Number = 40
14123 // Limits the rate at which a wheel can fire (in milliseconds). By default
14124 // user can't zoom via wheel more often than once per 40 ms.
14125 wheelDebounceTime: 40,
14126
14127 // @option wheelPxPerZoomLevel: Number = 60
14128 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
14129 // mean a change of one full zoom level. Smaller values will make wheel-zooming
14130 // faster (and vice versa).
14131 wheelPxPerZoomLevel: 60
14132 });
14133
14134 var ScrollWheelZoom = Handler.extend({
14135 addHooks: function () {
14136 on(this._map._container, 'wheel', this._onWheelScroll, this);
14137
14138 this._delta = 0;
14139 },
14140
14141 removeHooks: function () {
14142 off(this._map._container, 'wheel', this._onWheelScroll, this);
14143 },
14144
14145 _onWheelScroll: function (e) {
14146 var delta = getWheelDelta(e);
14147
14148 var debounce = this._map.options.wheelDebounceTime;
14149
14150 this._delta += delta;
14151 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
14152
14153 if (!this._startTime) {
14154 this._startTime = +new Date();
14155 }
14156
14157 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
14158
14159 clearTimeout(this._timer);
14160 this._timer = setTimeout(bind(this._performZoom, this), left);
14161
14162 stop(e);
14163 },
14164
14165 _performZoom: function () {
14166 var map = this._map,
14167 zoom = map.getZoom(),
14168 snap = this._map.options.zoomSnap || 0;
14169
14170 map._stop(); // stop panning and fly animations if any
14171
14172 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
14173 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
14174 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
14175 d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
14176 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
14177
14178 this._delta = 0;
14179 this._startTime = null;
14180
14181 if (!delta) { return; }
14182
14183 if (map.options.scrollWheelZoom === 'center') {
14184 map.setZoom(zoom + delta);
14185 } else {
14186 map.setZoomAround(this._lastMousePos, zoom + delta);
14187 }
14188 }
14189 });
14190
14191 // @section Handlers
14192 // @property scrollWheelZoom: Handler
14193 // Scroll wheel zoom handler.
14194 Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
14195
14196 /*
14197 * L.Map.TapHold is used to simulate `contextmenu` event on long hold,
14198 * which otherwise is not fired by mobile Safari.
14199 */
14200
14201 var tapHoldDelay = 600;
14202
14203 // @namespace Map
14204 // @section Interaction Options
14205 Map.mergeOptions({
14206 // @section Touch interaction options
14207 // @option tapHold: Boolean
14208 // Enables simulation of `contextmenu` event, default is `true` for mobile Safari.
14209 tapHold: Browser.touchNative && Browser.safari && Browser.mobile,
14210
14211 // @option tapTolerance: Number = 15
14212 // The max number of pixels a user can shift his finger during touch
14213 // for it to be considered a valid tap.
14214 tapTolerance: 15
14215 });
14216
14217 var TapHold = Handler.extend({
14218 addHooks: function () {
14219 on(this._map._container, 'touchstart', this._onDown, this);
14220 },
14221
14222 removeHooks: function () {
14223 off(this._map._container, 'touchstart', this._onDown, this);
14224 },
14225
14226 _onDown: function (e) {
14227 clearTimeout(this._holdTimeout);
14228 if (e.touches.length !== 1) { return; }
14229
14230 var first = e.touches[0];
14231 this._startPos = this._newPos = new Point(first.clientX, first.clientY);
14232
14233 this._holdTimeout = setTimeout(bind(function () {
14234 this._cancel();
14235 if (!this._isTapValid()) { return; }
14236
14237 // prevent simulated mouse events https://w3c.github.io/touch-events/#mouse-events
14238 on(document, 'touchend', preventDefault);
14239 on(document, 'touchend touchcancel', this._cancelClickPrevent);
14240 this._simulateEvent('contextmenu', first);
14241 }, this), tapHoldDelay);
14242
14243 on(document, 'touchend touchcancel contextmenu', this._cancel, this);
14244 on(document, 'touchmove', this._onMove, this);
14245 },
14246
14247 _cancelClickPrevent: function cancelClickPrevent() {
14248 off(document, 'touchend', preventDefault);
14249 off(document, 'touchend touchcancel', cancelClickPrevent);
14250 },
14251
14252 _cancel: function () {
14253 clearTimeout(this._holdTimeout);
14254 off(document, 'touchend touchcancel contextmenu', this._cancel, this);
14255 off(document, 'touchmove', this._onMove, this);
14256 },
14257
14258 _onMove: function (e) {
14259 var first = e.touches[0];
14260 this._newPos = new Point(first.clientX, first.clientY);
14261 },
14262
14263 _isTapValid: function () {
14264 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
14265 },
14266
14267 _simulateEvent: function (type, e) {
14268 var simulatedEvent = new MouseEvent(type, {
14269 bubbles: true,
14270 cancelable: true,
14271 view: window,
14272 // detail: 1,
14273 screenX: e.screenX,
14274 screenY: e.screenY,
14275 clientX: e.clientX,
14276 clientY: e.clientY,
14277 // button: 2,
14278 // buttons: 2
14279 });
14280
14281 simulatedEvent._simulated = true;
14282
14283 e.target.dispatchEvent(simulatedEvent);
14284 }
14285 });
14286
14287 // @section Handlers
14288 // @property tapHold: Handler
14289 // Long tap handler to simulate `contextmenu` event (useful in mobile Safari).
14290 Map.addInitHook('addHandler', 'tapHold', TapHold);
14291
14292 /*
14293 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
14294 */
14295
14296 // @namespace Map
14297 // @section Interaction Options
14298 Map.mergeOptions({
14299 // @section Touch interaction options
14300 // @option touchZoom: Boolean|String = *
14301 // Whether the map can be zoomed by touch-dragging with two fingers. If
14302 // passed `'center'`, it will zoom to the center of the view regardless of
14303 // where the touch events (fingers) were. Enabled for touch-capable web
14304 // browsers.
14305 touchZoom: Browser.touch,
14306
14307 // @option bounceAtZoomLimits: Boolean = true
14308 // Set it to false if you don't want the map to zoom beyond min/max zoom
14309 // and then bounce back when pinch-zooming.
14310 bounceAtZoomLimits: true
14311 });
14312
14313 var TouchZoom = Handler.extend({
14314 addHooks: function () {
14315 addClass(this._map._container, 'leaflet-touch-zoom');
14316 on(this._map._container, 'touchstart', this._onTouchStart, this);
14317 },
14318
14319 removeHooks: function () {
14320 removeClass(this._map._container, 'leaflet-touch-zoom');
14321 off(this._map._container, 'touchstart', this._onTouchStart, this);
14322 },
14323
14324 _onTouchStart: function (e) {
14325 var map = this._map;
14326 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
14327
14328 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
14329 p2 = map.mouseEventToContainerPoint(e.touches[1]);
14330
14331 this._centerPoint = map.getSize()._divideBy(2);
14332 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
14333 if (map.options.touchZoom !== 'center') {
14334 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
14335 }
14336
14337 this._startDist = p1.distanceTo(p2);
14338 this._startZoom = map.getZoom();
14339
14340 this._moved = false;
14341 this._zooming = true;
14342
14343 map._stop();
14344
14345 on(document, 'touchmove', this._onTouchMove, this);
14346 on(document, 'touchend touchcancel', this._onTouchEnd, this);
14347
14348 preventDefault(e);
14349 },
14350
14351 _onTouchMove: function (e) {
14352 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
14353
14354 var map = this._map,
14355 p1 = map.mouseEventToContainerPoint(e.touches[0]),
14356 p2 = map.mouseEventToContainerPoint(e.touches[1]),
14357 scale = p1.distanceTo(p2) / this._startDist;
14358
14359 this._zoom = map.getScaleZoom(scale, this._startZoom);
14360
14361 if (!map.options.bounceAtZoomLimits && (
14362 (this._zoom < map.getMinZoom() && scale < 1) ||
14363 (this._zoom > map.getMaxZoom() && scale > 1))) {
14364 this._zoom = map._limitZoom(this._zoom);
14365 }
14366
14367 if (map.options.touchZoom === 'center') {
14368 this._center = this._startLatLng;
14369 if (scale === 1) { return; }
14370 } else {
14371 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
14372 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
14373 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
14374 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
14375 }
14376
14377 if (!this._moved) {
14378 map._moveStart(true, false);
14379 this._moved = true;
14380 }
14381
14382 cancelAnimFrame(this._animRequest);
14383
14384 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false}, undefined);
14385 this._animRequest = requestAnimFrame(moveFn, this, true);
14386
14387 preventDefault(e);
14388 },
14389
14390 _onTouchEnd: function () {
14391 if (!this._moved || !this._zooming) {
14392 this._zooming = false;
14393 return;
14394 }
14395
14396 this._zooming = false;
14397 cancelAnimFrame(this._animRequest);
14398
14399 off(document, 'touchmove', this._onTouchMove, this);
14400 off(document, 'touchend touchcancel', this._onTouchEnd, this);
14401
14402 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
14403 if (this._map.options.zoomAnimation) {
14404 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
14405 } else {
14406 this._map._resetView(this._center, this._map._limitZoom(this._zoom));
14407 }
14408 }
14409 });
14410
14411 // @section Handlers
14412 // @property touchZoom: Handler
14413 // Touch zoom handler.
14414 Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
14415
14416 Map.BoxZoom = BoxZoom;
14417 Map.DoubleClickZoom = DoubleClickZoom;
14418 Map.Drag = Drag;
14419 Map.Keyboard = Keyboard;
14420 Map.ScrollWheelZoom = ScrollWheelZoom;
14421 Map.TapHold = TapHold;
14422 Map.TouchZoom = TouchZoom;
14423
14424 exports.Bounds = Bounds;
14425 exports.Browser = Browser;
14426 exports.CRS = CRS;
14427 exports.Canvas = Canvas;
14428 exports.Circle = Circle;
14429 exports.CircleMarker = CircleMarker;
14430 exports.Class = Class;
14431 exports.Control = Control;
14432 exports.DivIcon = DivIcon;
14433 exports.DivOverlay = DivOverlay;
14434 exports.DomEvent = DomEvent;
14435 exports.DomUtil = DomUtil;
14436 exports.Draggable = Draggable;
14437 exports.Evented = Evented;
14438 exports.FeatureGroup = FeatureGroup;
14439 exports.GeoJSON = GeoJSON;
14440 exports.GridLayer = GridLayer;
14441 exports.Handler = Handler;
14442 exports.Icon = Icon;
14443 exports.ImageOverlay = ImageOverlay;
14444 exports.LatLng = LatLng;
14445 exports.LatLngBounds = LatLngBounds;
14446 exports.Layer = Layer;
14447 exports.LayerGroup = LayerGroup;
14448 exports.LineUtil = LineUtil;
14449 exports.Map = Map;
14450 exports.Marker = Marker;
14451 exports.Mixin = Mixin;
14452 exports.Path = Path;
14453 exports.Point = Point;
14454 exports.PolyUtil = PolyUtil;
14455 exports.Polygon = Polygon;
14456 exports.Polyline = Polyline;
14457 exports.Popup = Popup;
14458 exports.PosAnimation = PosAnimation;
14459 exports.Projection = index;
14460 exports.Rectangle = Rectangle;
14461 exports.Renderer = Renderer;
14462 exports.SVG = SVG;
14463 exports.SVGOverlay = SVGOverlay;
14464 exports.TileLayer = TileLayer;
14465 exports.Tooltip = Tooltip;
14466 exports.Transformation = Transformation;
14467 exports.Util = Util;
14468 exports.VideoOverlay = VideoOverlay;
14469 exports.bind = bind;
14470 exports.bounds = toBounds;
14471 exports.canvas = canvas;
14472 exports.circle = circle;
14473 exports.circleMarker = circleMarker;
14474 exports.control = control;
14475 exports.divIcon = divIcon;
14476 exports.extend = extend;
14477 exports.featureGroup = featureGroup;
14478 exports.geoJSON = geoJSON;
14479 exports.geoJson = geoJson;
14480 exports.gridLayer = gridLayer;
14481 exports.icon = icon;
14482 exports.imageOverlay = imageOverlay;
14483 exports.latLng = toLatLng;
14484 exports.latLngBounds = toLatLngBounds;
14485 exports.layerGroup = layerGroup;
14486 exports.map = createMap;
14487 exports.marker = marker;
14488 exports.point = toPoint;
14489 exports.polygon = polygon;
14490 exports.polyline = polyline;
14491 exports.popup = popup;
14492 exports.rectangle = rectangle;
14493 exports.setOptions = setOptions;
14494 exports.stamp = stamp;
14495 exports.svg = svg;
14496 exports.svgOverlay = svgOverlay;
14497 exports.tileLayer = tileLayer;
14498 exports.tooltip = tooltip;
14499 exports.transformation = toTransformation;
14500 exports.version = version;
14501 exports.videoOverlay = videoOverlay;
14502
14503 var oldL = window.L;
14504 exports.noConflict = function() {
14505 window.L = oldL;
14506 return this;
14507 }
14508 // Always export us to window global (see #2364)
14509 window.L = exports;
14510
14511 }));
14512 //# sourceMappingURL=leaflet-src.js.map
14513