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.esm.js

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

14,420 lines 414.6 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 var version = "1.9.4";
7
8 /*
9 * @namespace Util
10 *
11 * Various utility functions, used by Leaflet internally.
12 */
13
14 // @function extend(dest: Object, src?: Object): Object
15 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
16 function extend(dest) {
17 var i, j, len, src;
18
19 for (j = 1, len = arguments.length; j < len; j++) {
20 src = arguments[j];
21 for (i in src) {
22 dest[i] = src[i];
23 }
24 }
25 return dest;
26 }
27
28 // @function create(proto: Object, properties?: Object): Object
29 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
30 var create$2 = Object.create || (function () {
31 function F() {}
32 return function (proto) {
33 F.prototype = proto;
34 return new F();
35 };
36 })();
37
38 // @function bind(fn: Function, …): Function
39 // 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).
40 // Has a `L.bind()` shortcut.
41 function bind(fn, obj) {
42 var slice = Array.prototype.slice;
43
44 if (fn.bind) {
45 return fn.bind.apply(fn, slice.call(arguments, 1));
46 }
47
48 var args = slice.call(arguments, 2);
49
50 return function () {
51 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
52 };
53 }
54
55 // @property lastId: Number
56 // Last unique ID used by [`stamp()`](#util-stamp)
57 var lastId = 0;
58
59 // @function stamp(obj: Object): Number
60 // Returns the unique ID of an object, assigning it one if it doesn't have it.
61 function stamp(obj) {
62 if (!('_leaflet_id' in obj)) {
63 obj['_leaflet_id'] = ++lastId;
64 }
65 return obj._leaflet_id;
66 }
67
68 // @function throttle(fn: Function, time: Number, context: Object): Function
69 // Returns a function which executes function `fn` with the given scope `context`
70 // (so that the `this` keyword refers to `context` inside `fn`'s code). The function
71 // `fn` will be called no more than one time per given amount of `time`. The arguments
72 // received by the bound function will be any arguments passed when binding the
73 // function, followed by any arguments passed when invoking the bound function.
74 // Has an `L.throttle` shortcut.
75 function throttle(fn, time, context) {
76 var lock, args, wrapperFn, later;
77
78 later = function () {
79 // reset lock and call if queued
80 lock = false;
81 if (args) {
82 wrapperFn.apply(context, args);
83 args = false;
84 }
85 };
86
87 wrapperFn = function () {
88 if (lock) {
89 // called too soon, queue to call later
90 args = arguments;
91
92 } else {
93 // call and lock until later
94 fn.apply(context, arguments);
95 setTimeout(later, time);
96 lock = true;
97 }
98 };
99
100 return wrapperFn;
101 }
102
103 // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
104 // Returns the number `num` modulo `range` in such a way so it lies within
105 // `range[0]` and `range[1]`. The returned value will be always smaller than
106 // `range[1]` unless `includeMax` is set to `true`.
107 function wrapNum(x, range, includeMax) {
108 var max = range[1],
109 min = range[0],
110 d = max - min;
111 return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
112 }
113
114 // @function falseFn(): Function
115 // Returns a function which always returns `false`.
116 function falseFn() { return false; }
117
118 // @function formatNum(num: Number, precision?: Number|false): Number
119 // Returns the number `num` rounded with specified `precision`.
120 // The default `precision` value is 6 decimal places.
121 // `false` can be passed to skip any processing (can be useful to avoid round-off errors).
122 function formatNum(num, precision) {
123 if (precision === false) { return num; }
124 var pow = Math.pow(10, precision === undefined ? 6 : precision);
125 return Math.round(num * pow) / pow;
126 }
127
128 // @function trim(str: String): String
129 // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
130 function trim(str) {
131 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
132 }
133
134 // @function splitWords(str: String): String[]
135 // Trims and splits the string on whitespace and returns the array of parts.
136 function splitWords(str) {
137 return trim(str).split(/\s+/);
138 }
139
140 // @function setOptions(obj: Object, options: Object): Object
141 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
142 function setOptions(obj, options) {
143 if (!Object.prototype.hasOwnProperty.call(obj, 'options')) {
144 obj.options = obj.options ? create$2(obj.options) : {};
145 }
146 for (var i in options) {
147 obj.options[i] = options[i];
148 }
149 return obj.options;
150 }
151
152 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
153 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
154 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
155 // be appended at the end. If `uppercase` is `true`, the parameter names will
156 // be uppercased (e.g. `'?A=foo&B=bar'`)
157 function getParamString(obj, existingUrl, uppercase) {
158 var params = [];
159 for (var i in obj) {
160 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
161 }
162 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
163 }
164
165 var templateRe = /\{ *([\w_ -]+) *\}/g;
166
167 // @function template(str: String, data: Object): String
168 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
169 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
170 // `('Hello foo, bar')`. You can also specify functions instead of strings for
171 // data values — they will be evaluated passing `data` as an argument.
172 function template(str, data) {
173 return str.replace(templateRe, function (str, key) {
174 var value = data[key];
175
176 if (value === undefined) {
177 throw new Error('No value provided for variable ' + str);
178
179 } else if (typeof value === 'function') {
180 value = value(data);
181 }
182 return value;
183 });
184 }
185
186 // @function isArray(obj): Boolean
187 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
188 var isArray = Array.isArray || function (obj) {
189 return (Object.prototype.toString.call(obj) === '[object Array]');
190 };
191
192 // @function indexOf(array: Array, el: Object): Number
193 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
194 function indexOf(array, el) {
195 for (var i = 0; i < array.length; i++) {
196 if (array[i] === el) { return i; }
197 }
198 return -1;
199 }
200
201 // @property emptyImageUrl: String
202 // Data URI string containing a base64-encoded empty GIF image.
203 // Used as a hack to free memory from unused images on WebKit-powered
204 // mobile devices (by setting image `src` to this string).
205 var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
206
207 // inspired by https://paulirish.com/2011/requestanimationframe-for-smart-animating/
208
209 function getPrefixed(name) {
210 return window['webkit' + name] || window['moz' + name] || window['ms' + name];
211 }
212
213 var lastTime = 0;
214
215 // fallback for IE 7-8
216 function timeoutDefer(fn) {
217 var time = +new Date(),
218 timeToCall = Math.max(0, 16 - (time - lastTime));
219
220 lastTime = time + timeToCall;
221 return window.setTimeout(fn, timeToCall);
222 }
223
224 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
225 var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
226 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
227
228 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
229 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
230 // `context` if given. When `immediate` is set, `fn` is called immediately if
231 // the browser doesn't have native support for
232 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
233 // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
234 function requestAnimFrame(fn, context, immediate) {
235 if (immediate && requestFn === timeoutDefer) {
236 fn.call(context);
237 } else {
238 return requestFn.call(window, bind(fn, context));
239 }
240 }
241
242 // @function cancelAnimFrame(id: Number): undefined
243 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
244 function cancelAnimFrame(id) {
245 if (id) {
246 cancelFn.call(window, id);
247 }
248 }
249
250 var Util = {
251 __proto__: null,
252 extend: extend,
253 create: create$2,
254 bind: bind,
255 get lastId () { return lastId; },
256 stamp: stamp,
257 throttle: throttle,
258 wrapNum: wrapNum,
259 falseFn: falseFn,
260 formatNum: formatNum,
261 trim: trim,
262 splitWords: splitWords,
263 setOptions: setOptions,
264 getParamString: getParamString,
265 template: template,
266 isArray: isArray,
267 indexOf: indexOf,
268 emptyImageUrl: emptyImageUrl,
269 requestFn: requestFn,
270 cancelFn: cancelFn,
271 requestAnimFrame: requestAnimFrame,
272 cancelAnimFrame: cancelAnimFrame
273 };
274
275 // @class Class
276 // @aka L.Class
277
278 // @section
279 // @uninheritable
280
281 // Thanks to John Resig and Dean Edwards for inspiration!
282
283 function Class() {}
284
285 Class.extend = function (props) {
286
287 // @function extend(props: Object): Function
288 // [Extends the current class](#class-inheritance) given the properties to be included.
289 // Returns a Javascript function that is a class constructor (to be called with `new`).
290 var NewClass = function () {
291
292 setOptions(this);
293
294 // call the constructor
295 if (this.initialize) {
296 this.initialize.apply(this, arguments);
297 }
298
299 // call all constructor hooks
300 this.callInitHooks();
301 };
302
303 var parentProto = NewClass.__super__ = this.prototype;
304
305 var proto = create$2(parentProto);
306 proto.constructor = NewClass;
307
308 NewClass.prototype = proto;
309
310 // inherit parent's statics
311 for (var i in this) {
312 if (Object.prototype.hasOwnProperty.call(this, i) && i !== 'prototype' && i !== '__super__') {
313 NewClass[i] = this[i];
314 }
315 }
316
317 // mix static properties into the class
318 if (props.statics) {
319 extend(NewClass, props.statics);
320 }
321
322 // mix includes into the prototype
323 if (props.includes) {
324 checkDeprecatedMixinEvents(props.includes);
325 extend.apply(null, [proto].concat(props.includes));
326 }
327
328 // mix given properties into the prototype
329 extend(proto, props);
330 delete proto.statics;
331 delete proto.includes;
332
333 // merge options
334 if (proto.options) {
335 proto.options = parentProto.options ? create$2(parentProto.options) : {};
336 extend(proto.options, props.options);
337 }
338
339 proto._initHooks = [];
340
341 // add method for calling all hooks
342 proto.callInitHooks = function () {
343
344 if (this._initHooksCalled) { return; }
345
346 if (parentProto.callInitHooks) {
347 parentProto.callInitHooks.call(this);
348 }
349
350 this._initHooksCalled = true;
351
352 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
353 proto._initHooks[i].call(this);
354 }
355 };
356
357 return NewClass;
358 };
359
360
361 // @function include(properties: Object): this
362 // [Includes a mixin](#class-includes) into the current class.
363 Class.include = function (props) {
364 var parentOptions = this.prototype.options;
365 extend(this.prototype, props);
366 if (props.options) {
367 this.prototype.options = parentOptions;
368 this.mergeOptions(props.options);
369 }
370 return this;
371 };
372
373 // @function mergeOptions(options: Object): this
374 // [Merges `options`](#class-options) into the defaults of the class.
375 Class.mergeOptions = function (options) {
376 extend(this.prototype.options, options);
377 return this;
378 };
379
380 // @function addInitHook(fn: Function): this
381 // Adds a [constructor hook](#class-constructor-hooks) to the class.
382 Class.addInitHook = function (fn) { // (Function) || (String, args...)
383 var args = Array.prototype.slice.call(arguments, 1);
384
385 var init = typeof fn === 'function' ? fn : function () {
386 this[fn].apply(this, args);
387 };
388
389 this.prototype._initHooks = this.prototype._initHooks || [];
390 this.prototype._initHooks.push(init);
391 return this;
392 };
393
394 function checkDeprecatedMixinEvents(includes) {
395 /* global L: true */
396 if (typeof L === 'undefined' || !L || !L.Mixin) { return; }
397
398 includes = isArray(includes) ? includes : [includes];
399
400 for (var i = 0; i < includes.length; i++) {
401 if (includes[i] === L.Mixin.Events) {
402 console.warn('Deprecated include of L.Mixin.Events: ' +
403 'this property will be removed in future releases, ' +
404 'please inherit from L.Evented instead.', new Error().stack);
405 }
406 }
407 }
408
409 /*
410 * @class Evented
411 * @aka L.Evented
412 * @inherits Class
413 *
414 * 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).
415 *
416 * @example
417 *
418 * ```js
419 * map.on('click', function(e) {
420 * alert(e.latlng);
421 * } );
422 * ```
423 *
424 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
425 *
426 * ```js
427 * function onClick(e) { ... }
428 *
429 * map.on('click', onClick);
430 * map.off('click', onClick);
431 * ```
432 */
433
434 var Events = {
435 /* @method on(type: String, fn: Function, context?: Object): this
436 * 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'`).
437 *
438 * @alternative
439 * @method on(eventMap: Object): this
440 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
441 */
442 on: function (types, fn, context) {
443
444 // types can be a map of types/handlers
445 if (typeof types === 'object') {
446 for (var type in types) {
447 // we don't process space-separated events here for performance;
448 // it's a hot path since Layer uses the on(obj) syntax
449 this._on(type, types[type], fn);
450 }
451
452 } else {
453 // types can be a string of space-separated words
454 types = splitWords(types);
455
456 for (var i = 0, len = types.length; i < len; i++) {
457 this._on(types[i], fn, context);
458 }
459 }
460
461 return this;
462 },
463
464 /* @method off(type: String, fn?: Function, context?: Object): this
465 * 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.
466 *
467 * @alternative
468 * @method off(eventMap: Object): this
469 * Removes a set of type/listener pairs.
470 *
471 * @alternative
472 * @method off: this
473 * Removes all listeners to all events on the object. This includes implicitly attached events.
474 */
475 off: function (types, fn, context) {
476
477 if (!arguments.length) {
478 // clear all listeners if called without arguments
479 delete this._events;
480
481 } else if (typeof types === 'object') {
482 for (var type in types) {
483 this._off(type, types[type], fn);
484 }
485
486 } else {
487 types = splitWords(types);
488
489 var removeAll = arguments.length === 1;
490 for (var i = 0, len = types.length; i < len; i++) {
491 if (removeAll) {
492 this._off(types[i]);
493 } else {
494 this._off(types[i], fn, context);
495 }
496 }
497 }
498
499 return this;
500 },
501
502 // attach listener (without syntactic sugar now)
503 _on: function (type, fn, context, _once) {
504 if (typeof fn !== 'function') {
505 console.warn('wrong listener type: ' + typeof fn);
506 return;
507 }
508
509 // check if fn already there
510 if (this._listens(type, fn, context) !== false) {
511 return;
512 }
513
514 if (context === this) {
515 // Less memory footprint.
516 context = undefined;
517 }
518
519 var newListener = {fn: fn, ctx: context};
520 if (_once) {
521 newListener.once = true;
522 }
523
524 this._events = this._events || {};
525 this._events[type] = this._events[type] || [];
526 this._events[type].push(newListener);
527 },
528
529 _off: function (type, fn, context) {
530 var listeners,
531 i,
532 len;
533
534 if (!this._events) {
535 return;
536 }
537
538 listeners = this._events[type];
539 if (!listeners) {
540 return;
541 }
542
543 if (arguments.length === 1) { // remove all
544 if (this._firingCount) {
545 // Set all removed listeners to noop
546 // so they are not called if remove happens in fire
547 for (i = 0, len = listeners.length; i < len; i++) {
548 listeners[i].fn = falseFn;
549 }
550 }
551 // clear all listeners for a type if function isn't specified
552 delete this._events[type];
553 return;
554 }
555
556 if (typeof fn !== 'function') {
557 console.warn('wrong listener type: ' + typeof fn);
558 return;
559 }
560
561 // find fn and remove it
562 var index = this._listens(type, fn, context);
563 if (index !== false) {
564 var listener = listeners[index];
565 if (this._firingCount) {
566 // set the removed listener to noop so that's not called if remove happens in fire
567 listener.fn = falseFn;
568
569 /* copy array in case events are being fired */
570 this._events[type] = listeners = listeners.slice();
571 }
572 listeners.splice(index, 1);
573 }
574 },
575
576 // @method fire(type: String, data?: Object, propagate?: Boolean): this
577 // Fires an event of the specified type. You can optionally provide a data
578 // object — the first argument of the listener function will contain its
579 // properties. The event can optionally be propagated to event parents.
580 fire: function (type, data, propagate) {
581 if (!this.listens(type, propagate)) { return this; }
582
583 var event = extend({}, data, {
584 type: type,
585 target: this,
586 sourceTarget: data && data.sourceTarget || this
587 });
588
589 if (this._events) {
590 var listeners = this._events[type];
591 if (listeners) {
592 this._firingCount = (this._firingCount + 1) || 1;
593 for (var i = 0, len = listeners.length; i < len; i++) {
594 var l = listeners[i];
595 // off overwrites l.fn, so we need to copy fn to a var
596 var fn = l.fn;
597 if (l.once) {
598 this.off(type, fn, l.ctx);
599 }
600 fn.call(l.ctx || this, event);
601 }
602
603 this._firingCount--;
604 }
605 }
606
607 if (propagate) {
608 // propagate the event to parents (set with addEventParent)
609 this._propagateEvent(event);
610 }
611
612 return this;
613 },
614
615 // @method listens(type: String, propagate?: Boolean): Boolean
616 // @method listens(type: String, fn: Function, context?: Object, propagate?: Boolean): Boolean
617 // Returns `true` if a particular event type has any listeners attached to it.
618 // The verification can optionally be propagated, it will return `true` if parents have the listener attached to it.
619 listens: function (type, fn, context, propagate) {
620 if (typeof type !== 'string') {
621 console.warn('"string" type argument expected');
622 }
623
624 // we don't overwrite the input `fn` value, because we need to use it for propagation
625 var _fn = fn;
626 if (typeof fn !== 'function') {
627 propagate = !!fn;
628 _fn = undefined;
629 context = undefined;
630 }
631
632 var listeners = this._events && this._events[type];
633 if (listeners && listeners.length) {
634 if (this._listens(type, _fn, context) !== false) {
635 return true;
636 }
637 }
638
639 if (propagate) {
640 // also check parents for listeners if event propagates
641 for (var id in this._eventParents) {
642 if (this._eventParents[id].listens(type, fn, context, propagate)) { return true; }
643 }
644 }
645 return false;
646 },
647
648 // returns the index (number) or false
649 _listens: function (type, fn, context) {
650 if (!this._events) {
651 return false;
652 }
653
654 var listeners = this._events[type] || [];
655 if (!fn) {
656 return !!listeners.length;
657 }
658
659 if (context === this) {
660 // Less memory footprint.
661 context = undefined;
662 }
663
664 for (var i = 0, len = listeners.length; i < len; i++) {
665 if (listeners[i].fn === fn && listeners[i].ctx === context) {
666 return i;
667 }
668 }
669 return false;
670
671 },
672
673 // @method once(…): this
674 // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
675 once: function (types, fn, context) {
676
677 // types can be a map of types/handlers
678 if (typeof types === 'object') {
679 for (var type in types) {
680 // we don't process space-separated events here for performance;
681 // it's a hot path since Layer uses the on(obj) syntax
682 this._on(type, types[type], fn, true);
683 }
684
685 } else {
686 // types can be a string of space-separated words
687 types = splitWords(types);
688
689 for (var i = 0, len = types.length; i < len; i++) {
690 this._on(types[i], fn, context, true);
691 }
692 }
693
694 return this;
695 },
696
697 // @method addEventParent(obj: Evented): this
698 // Adds an event parent - an `Evented` that will receive propagated events
699 addEventParent: function (obj) {
700 this._eventParents = this._eventParents || {};
701 this._eventParents[stamp(obj)] = obj;
702 return this;
703 },
704
705 // @method removeEventParent(obj: Evented): this
706 // Removes an event parent, so it will stop receiving propagated events
707 removeEventParent: function (obj) {
708 if (this._eventParents) {
709 delete this._eventParents[stamp(obj)];
710 }
711 return this;
712 },
713
714 _propagateEvent: function (e) {
715 for (var id in this._eventParents) {
716 this._eventParents[id].fire(e.type, extend({
717 layer: e.target,
718 propagatedFrom: e.target
719 }, e), true);
720 }
721 }
722 };
723
724 // aliases; we should ditch those eventually
725
726 // @method addEventListener(…): this
727 // Alias to [`on(…)`](#evented-on)
728 Events.addEventListener = Events.on;
729
730 // @method removeEventListener(…): this
731 // Alias to [`off(…)`](#evented-off)
732
733 // @method clearAllEventListeners(…): this
734 // Alias to [`off()`](#evented-off)
735 Events.removeEventListener = Events.clearAllEventListeners = Events.off;
736
737 // @method addOneTimeEventListener(…): this
738 // Alias to [`once(…)`](#evented-once)
739 Events.addOneTimeEventListener = Events.once;
740
741 // @method fireEvent(…): this
742 // Alias to [`fire(…)`](#evented-fire)
743 Events.fireEvent = Events.fire;
744
745 // @method hasEventListeners(…): Boolean
746 // Alias to [`listens(…)`](#evented-listens)
747 Events.hasEventListeners = Events.listens;
748
749 var Evented = Class.extend(Events);
750
751 /*
752 * @class Point
753 * @aka L.Point
754 *
755 * Represents a point with `x` and `y` coordinates in pixels.
756 *
757 * @example
758 *
759 * ```js
760 * var point = L.point(200, 300);
761 * ```
762 *
763 * 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:
764 *
765 * ```js
766 * map.panBy([200, 300]);
767 * map.panBy(L.point(200, 300));
768 * ```
769 *
770 * Note that `Point` does not inherit from Leaflet's `Class` object,
771 * which means new classes can't inherit from it, and new methods
772 * can't be added to it with the `include` function.
773 */
774
775 function Point(x, y, round) {
776 // @property x: Number; The `x` coordinate of the point
777 this.x = (round ? Math.round(x) : x);
778 // @property y: Number; The `y` coordinate of the point
779 this.y = (round ? Math.round(y) : y);
780 }
781
782 var trunc = Math.trunc || function (v) {
783 return v > 0 ? Math.floor(v) : Math.ceil(v);
784 };
785
786 Point.prototype = {
787
788 // @method clone(): Point
789 // Returns a copy of the current point.
790 clone: function () {
791 return new Point(this.x, this.y);
792 },
793
794 // @method add(otherPoint: Point): Point
795 // Returns the result of addition of the current and the given points.
796 add: function (point) {
797 // non-destructive, returns a new point
798 return this.clone()._add(toPoint(point));
799 },
800
801 _add: function (point) {
802 // destructive, used directly for performance in situations where it's safe to modify existing point
803 this.x += point.x;
804 this.y += point.y;
805 return this;
806 },
807
808 // @method subtract(otherPoint: Point): Point
809 // Returns the result of subtraction of the given point from the current.
810 subtract: function (point) {
811 return this.clone()._subtract(toPoint(point));
812 },
813
814 _subtract: function (point) {
815 this.x -= point.x;
816 this.y -= point.y;
817 return this;
818 },
819
820 // @method divideBy(num: Number): Point
821 // Returns the result of division of the current point by the given number.
822 divideBy: function (num) {
823 return this.clone()._divideBy(num);
824 },
825
826 _divideBy: function (num) {
827 this.x /= num;
828 this.y /= num;
829 return this;
830 },
831
832 // @method multiplyBy(num: Number): Point
833 // Returns the result of multiplication of the current point by the given number.
834 multiplyBy: function (num) {
835 return this.clone()._multiplyBy(num);
836 },
837
838 _multiplyBy: function (num) {
839 this.x *= num;
840 this.y *= num;
841 return this;
842 },
843
844 // @method scaleBy(scale: Point): Point
845 // Multiply each coordinate of the current point by each coordinate of
846 // `scale`. In linear algebra terms, multiply the point by the
847 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
848 // defined by `scale`.
849 scaleBy: function (point) {
850 return new Point(this.x * point.x, this.y * point.y);
851 },
852
853 // @method unscaleBy(scale: Point): Point
854 // Inverse of `scaleBy`. Divide each coordinate of the current point by
855 // each coordinate of `scale`.
856 unscaleBy: function (point) {
857 return new Point(this.x / point.x, this.y / point.y);
858 },
859
860 // @method round(): Point
861 // Returns a copy of the current point with rounded coordinates.
862 round: function () {
863 return this.clone()._round();
864 },
865
866 _round: function () {
867 this.x = Math.round(this.x);
868 this.y = Math.round(this.y);
869 return this;
870 },
871
872 // @method floor(): Point
873 // Returns a copy of the current point with floored coordinates (rounded down).
874 floor: function () {
875 return this.clone()._floor();
876 },
877
878 _floor: function () {
879 this.x = Math.floor(this.x);
880 this.y = Math.floor(this.y);
881 return this;
882 },
883
884 // @method ceil(): Point
885 // Returns a copy of the current point with ceiled coordinates (rounded up).
886 ceil: function () {
887 return this.clone()._ceil();
888 },
889
890 _ceil: function () {
891 this.x = Math.ceil(this.x);
892 this.y = Math.ceil(this.y);
893 return this;
894 },
895
896 // @method trunc(): Point
897 // Returns a copy of the current point with truncated coordinates (rounded towards zero).
898 trunc: function () {
899 return this.clone()._trunc();
900 },
901
902 _trunc: function () {
903 this.x = trunc(this.x);
904 this.y = trunc(this.y);
905 return this;
906 },
907
908 // @method distanceTo(otherPoint: Point): Number
909 // Returns the cartesian distance between the current and the given points.
910 distanceTo: function (point) {
911 point = toPoint(point);
912
913 var x = point.x - this.x,
914 y = point.y - this.y;
915
916 return Math.sqrt(x * x + y * y);
917 },
918
919 // @method equals(otherPoint: Point): Boolean
920 // Returns `true` if the given point has the same coordinates.
921 equals: function (point) {
922 point = toPoint(point);
923
924 return point.x === this.x &&
925 point.y === this.y;
926 },
927
928 // @method contains(otherPoint: Point): Boolean
929 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
930 contains: function (point) {
931 point = toPoint(point);
932
933 return Math.abs(point.x) <= Math.abs(this.x) &&
934 Math.abs(point.y) <= Math.abs(this.y);
935 },
936
937 // @method toString(): String
938 // Returns a string representation of the point for debugging purposes.
939 toString: function () {
940 return 'Point(' +
941 formatNum(this.x) + ', ' +
942 formatNum(this.y) + ')';
943 }
944 };
945
946 // @factory L.point(x: Number, y: Number, round?: Boolean)
947 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
948
949 // @alternative
950 // @factory L.point(coords: Number[])
951 // Expects an array of the form `[x, y]` instead.
952
953 // @alternative
954 // @factory L.point(coords: Object)
955 // Expects a plain object of the form `{x: Number, y: Number}` instead.
956 function toPoint(x, y, round) {
957 if (x instanceof Point) {
958 return x;
959 }
960 if (isArray(x)) {
961 return new Point(x[0], x[1]);
962 }
963 if (x === undefined || x === null) {
964 return x;
965 }
966 if (typeof x === 'object' && 'x' in x && 'y' in x) {
967 return new Point(x.x, x.y);
968 }
969 return new Point(x, y, round);
970 }
971
972 /*
973 * @class Bounds
974 * @aka L.Bounds
975 *
976 * Represents a rectangular area in pixel coordinates.
977 *
978 * @example
979 *
980 * ```js
981 * var p1 = L.point(10, 10),
982 * p2 = L.point(40, 60),
983 * bounds = L.bounds(p1, p2);
984 * ```
985 *
986 * 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:
987 *
988 * ```js
989 * otherBounds.intersects([[10, 10], [40, 60]]);
990 * ```
991 *
992 * Note that `Bounds` does not inherit from Leaflet's `Class` object,
993 * which means new classes can't inherit from it, and new methods
994 * can't be added to it with the `include` function.
995 */
996
997 function Bounds(a, b) {
998 if (!a) { return; }
999
1000 var points = b ? [a, b] : a;
1001
1002 for (var i = 0, len = points.length; i < len; i++) {
1003 this.extend(points[i]);
1004 }
1005 }
1006
1007 Bounds.prototype = {
1008 // @method extend(point: Point): this
1009 // Extends the bounds to contain the given point.
1010
1011 // @alternative
1012 // @method extend(otherBounds: Bounds): this
1013 // Extend the bounds to contain the given bounds
1014 extend: function (obj) {
1015 var min2, max2;
1016 if (!obj) { return this; }
1017
1018 if (obj instanceof Point || typeof obj[0] === 'number' || 'x' in obj) {
1019 min2 = max2 = toPoint(obj);
1020 } else {
1021 obj = toBounds(obj);
1022 min2 = obj.min;
1023 max2 = obj.max;
1024
1025 if (!min2 || !max2) { return this; }
1026 }
1027
1028 // @property min: Point
1029 // The top left corner of the rectangle.
1030 // @property max: Point
1031 // The bottom right corner of the rectangle.
1032 if (!this.min && !this.max) {
1033 this.min = min2.clone();
1034 this.max = max2.clone();
1035 } else {
1036 this.min.x = Math.min(min2.x, this.min.x);
1037 this.max.x = Math.max(max2.x, this.max.x);
1038 this.min.y = Math.min(min2.y, this.min.y);
1039 this.max.y = Math.max(max2.y, this.max.y);
1040 }
1041 return this;
1042 },
1043
1044 // @method getCenter(round?: Boolean): Point
1045 // Returns the center point of the bounds.
1046 getCenter: function (round) {
1047 return toPoint(
1048 (this.min.x + this.max.x) / 2,
1049 (this.min.y + this.max.y) / 2, round);
1050 },
1051
1052 // @method getBottomLeft(): Point
1053 // Returns the bottom-left point of the bounds.
1054 getBottomLeft: function () {
1055 return toPoint(this.min.x, this.max.y);
1056 },
1057
1058 // @method getTopRight(): Point
1059 // Returns the top-right point of the bounds.
1060 getTopRight: function () { // -> Point
1061 return toPoint(this.max.x, this.min.y);
1062 },
1063
1064 // @method getTopLeft(): Point
1065 // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).
1066 getTopLeft: function () {
1067 return this.min; // left, top
1068 },
1069
1070 // @method getBottomRight(): Point
1071 // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).
1072 getBottomRight: function () {
1073 return this.max; // right, bottom
1074 },
1075
1076 // @method getSize(): Point
1077 // Returns the size of the given bounds
1078 getSize: function () {
1079 return this.max.subtract(this.min);
1080 },
1081
1082 // @method contains(otherBounds: Bounds): Boolean
1083 // Returns `true` if the rectangle contains the given one.
1084 // @alternative
1085 // @method contains(point: Point): Boolean
1086 // Returns `true` if the rectangle contains the given point.
1087 contains: function (obj) {
1088 var min, max;
1089
1090 if (typeof obj[0] === 'number' || obj instanceof Point) {
1091 obj = toPoint(obj);
1092 } else {
1093 obj = toBounds(obj);
1094 }
1095
1096 if (obj instanceof Bounds) {
1097 min = obj.min;
1098 max = obj.max;
1099 } else {
1100 min = max = obj;
1101 }
1102
1103 return (min.x >= this.min.x) &&
1104 (max.x <= this.max.x) &&
1105 (min.y >= this.min.y) &&
1106 (max.y <= this.max.y);
1107 },
1108
1109 // @method intersects(otherBounds: Bounds): Boolean
1110 // Returns `true` if the rectangle intersects the given bounds. Two bounds
1111 // intersect if they have at least one point in common.
1112 intersects: function (bounds) { // (Bounds) -> Boolean
1113 bounds = toBounds(bounds);
1114
1115 var min = this.min,
1116 max = this.max,
1117 min2 = bounds.min,
1118 max2 = bounds.max,
1119 xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1120 yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1121
1122 return xIntersects && yIntersects;
1123 },
1124
1125 // @method overlaps(otherBounds: Bounds): Boolean
1126 // Returns `true` if the rectangle overlaps the given bounds. Two bounds
1127 // overlap if their intersection is an area.
1128 overlaps: function (bounds) { // (Bounds) -> Boolean
1129 bounds = toBounds(bounds);
1130
1131 var min = this.min,
1132 max = this.max,
1133 min2 = bounds.min,
1134 max2 = bounds.max,
1135 xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1136 yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1137
1138 return xOverlaps && yOverlaps;
1139 },
1140
1141 // @method isValid(): Boolean
1142 // Returns `true` if the bounds are properly initialized.
1143 isValid: function () {
1144 return !!(this.min && this.max);
1145 },
1146
1147
1148 // @method pad(bufferRatio: Number): Bounds
1149 // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
1150 // For example, a ratio of 0.5 extends the bounds by 50% in each direction.
1151 // Negative values will retract the bounds.
1152 pad: function (bufferRatio) {
1153 var min = this.min,
1154 max = this.max,
1155 heightBuffer = Math.abs(min.x - max.x) * bufferRatio,
1156 widthBuffer = Math.abs(min.y - max.y) * bufferRatio;
1157
1158
1159 return toBounds(
1160 toPoint(min.x - heightBuffer, min.y - widthBuffer),
1161 toPoint(max.x + heightBuffer, max.y + widthBuffer));
1162 },
1163
1164
1165 // @method equals(otherBounds: Bounds): Boolean
1166 // Returns `true` if the rectangle is equivalent to the given bounds.
1167 equals: function (bounds) {
1168 if (!bounds) { return false; }
1169
1170 bounds = toBounds(bounds);
1171
1172 return this.min.equals(bounds.getTopLeft()) &&
1173 this.max.equals(bounds.getBottomRight());
1174 },
1175 };
1176
1177
1178 // @factory L.bounds(corner1: Point, corner2: Point)
1179 // Creates a Bounds object from two corners coordinate pairs.
1180 // @alternative
1181 // @factory L.bounds(points: Point[])
1182 // Creates a Bounds object from the given array of points.
1183 function toBounds(a, b) {
1184 if (!a || a instanceof Bounds) {
1185 return a;
1186 }
1187 return new Bounds(a, b);
1188 }
1189
1190 /*
1191 * @class LatLngBounds
1192 * @aka L.LatLngBounds
1193 *
1194 * Represents a rectangular geographical area on a map.
1195 *
1196 * @example
1197 *
1198 * ```js
1199 * var corner1 = L.latLng(40.712, -74.227),
1200 * corner2 = L.latLng(40.774, -74.125),
1201 * bounds = L.latLngBounds(corner1, corner2);
1202 * ```
1203 *
1204 * 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:
1205 *
1206 * ```js
1207 * map.fitBounds([
1208 * [40.712, -74.227],
1209 * [40.774, -74.125]
1210 * ]);
1211 * ```
1212 *
1213 * 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.
1214 *
1215 * Note that `LatLngBounds` does not inherit from Leaflet's `Class` object,
1216 * which means new classes can't inherit from it, and new methods
1217 * can't be added to it with the `include` function.
1218 */
1219
1220 function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
1221 if (!corner1) { return; }
1222
1223 var latlngs = corner2 ? [corner1, corner2] : corner1;
1224
1225 for (var i = 0, len = latlngs.length; i < len; i++) {
1226 this.extend(latlngs[i]);
1227 }
1228 }
1229
1230 LatLngBounds.prototype = {
1231
1232 // @method extend(latlng: LatLng): this
1233 // Extend the bounds to contain the given point
1234
1235 // @alternative
1236 // @method extend(otherBounds: LatLngBounds): this
1237 // Extend the bounds to contain the given bounds
1238 extend: function (obj) {
1239 var sw = this._southWest,
1240 ne = this._northEast,
1241 sw2, ne2;
1242
1243 if (obj instanceof LatLng) {
1244 sw2 = obj;
1245 ne2 = obj;
1246
1247 } else if (obj instanceof LatLngBounds) {
1248 sw2 = obj._southWest;
1249 ne2 = obj._northEast;
1250
1251 if (!sw2 || !ne2) { return this; }
1252
1253 } else {
1254 return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
1255 }
1256
1257 if (!sw && !ne) {
1258 this._southWest = new LatLng(sw2.lat, sw2.lng);
1259 this._northEast = new LatLng(ne2.lat, ne2.lng);
1260 } else {
1261 sw.lat = Math.min(sw2.lat, sw.lat);
1262 sw.lng = Math.min(sw2.lng, sw.lng);
1263 ne.lat = Math.max(ne2.lat, ne.lat);
1264 ne.lng = Math.max(ne2.lng, ne.lng);
1265 }
1266
1267 return this;
1268 },
1269
1270 // @method pad(bufferRatio: Number): LatLngBounds
1271 // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
1272 // For example, a ratio of 0.5 extends the bounds by 50% in each direction.
1273 // Negative values will retract the bounds.
1274 pad: function (bufferRatio) {
1275 var sw = this._southWest,
1276 ne = this._northEast,
1277 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1278 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1279
1280 return new LatLngBounds(
1281 new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1282 new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1283 },
1284
1285 // @method getCenter(): LatLng
1286 // Returns the center point of the bounds.
1287 getCenter: function () {
1288 return new LatLng(
1289 (this._southWest.lat + this._northEast.lat) / 2,
1290 (this._southWest.lng + this._northEast.lng) / 2);
1291 },
1292
1293 // @method getSouthWest(): LatLng
1294 // Returns the south-west point of the bounds.
1295 getSouthWest: function () {
1296 return this._southWest;
1297 },
1298
1299 // @method getNorthEast(): LatLng
1300 // Returns the north-east point of the bounds.
1301 getNorthEast: function () {
1302 return this._northEast;
1303 },
1304
1305 // @method getNorthWest(): LatLng
1306 // Returns the north-west point of the bounds.
1307 getNorthWest: function () {
1308 return new LatLng(this.getNorth(), this.getWest());
1309 },
1310
1311 // @method getSouthEast(): LatLng
1312 // Returns the south-east point of the bounds.
1313 getSouthEast: function () {
1314 return new LatLng(this.getSouth(), this.getEast());
1315 },
1316
1317 // @method getWest(): Number
1318 // Returns the west longitude of the bounds
1319 getWest: function () {
1320 return this._southWest.lng;
1321 },
1322
1323 // @method getSouth(): Number
1324 // Returns the south latitude of the bounds
1325 getSouth: function () {
1326 return this._southWest.lat;
1327 },
1328
1329 // @method getEast(): Number
1330 // Returns the east longitude of the bounds
1331 getEast: function () {
1332 return this._northEast.lng;
1333 },
1334
1335 // @method getNorth(): Number
1336 // Returns the north latitude of the bounds
1337 getNorth: function () {
1338 return this._northEast.lat;
1339 },
1340
1341 // @method contains(otherBounds: LatLngBounds): Boolean
1342 // Returns `true` if the rectangle contains the given one.
1343
1344 // @alternative
1345 // @method contains (latlng: LatLng): Boolean
1346 // Returns `true` if the rectangle contains the given point.
1347 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1348 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
1349 obj = toLatLng(obj);
1350 } else {
1351 obj = toLatLngBounds(obj);
1352 }
1353
1354 var sw = this._southWest,
1355 ne = this._northEast,
1356 sw2, ne2;
1357
1358 if (obj instanceof LatLngBounds) {
1359 sw2 = obj.getSouthWest();
1360 ne2 = obj.getNorthEast();
1361 } else {
1362 sw2 = ne2 = obj;
1363 }
1364
1365 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1366 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1367 },
1368
1369 // @method intersects(otherBounds: LatLngBounds): Boolean
1370 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1371 intersects: function (bounds) {
1372 bounds = toLatLngBounds(bounds);
1373
1374 var sw = this._southWest,
1375 ne = this._northEast,
1376 sw2 = bounds.getSouthWest(),
1377 ne2 = bounds.getNorthEast(),
1378
1379 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1380 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1381
1382 return latIntersects && lngIntersects;
1383 },
1384
1385 // @method overlaps(otherBounds: LatLngBounds): Boolean
1386 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1387 overlaps: function (bounds) {
1388 bounds = toLatLngBounds(bounds);
1389
1390 var sw = this._southWest,
1391 ne = this._northEast,
1392 sw2 = bounds.getSouthWest(),
1393 ne2 = bounds.getNorthEast(),
1394
1395 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1396 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1397
1398 return latOverlaps && lngOverlaps;
1399 },
1400
1401 // @method toBBoxString(): String
1402 // 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.
1403 toBBoxString: function () {
1404 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1405 },
1406
1407 // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
1408 // 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.
1409 equals: function (bounds, maxMargin) {
1410 if (!bounds) { return false; }
1411
1412 bounds = toLatLngBounds(bounds);
1413
1414 return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
1415 this._northEast.equals(bounds.getNorthEast(), maxMargin);
1416 },
1417
1418 // @method isValid(): Boolean
1419 // Returns `true` if the bounds are properly initialized.
1420 isValid: function () {
1421 return !!(this._southWest && this._northEast);
1422 }
1423 };
1424
1425 // TODO International date line?
1426
1427 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
1428 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
1429
1430 // @alternative
1431 // @factory L.latLngBounds(latlngs: LatLng[])
1432 // 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).
1433 function toLatLngBounds(a, b) {
1434 if (a instanceof LatLngBounds) {
1435 return a;
1436 }
1437 return new LatLngBounds(a, b);
1438 }
1439
1440 /* @class LatLng
1441 * @aka L.LatLng
1442 *
1443 * Represents a geographical point with a certain latitude and longitude.
1444 *
1445 * @example
1446 *
1447 * ```
1448 * var latlng = L.latLng(50.5, 30.5);
1449 * ```
1450 *
1451 * 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:
1452 *
1453 * ```
1454 * map.panTo([50, 30]);
1455 * map.panTo({lon: 30, lat: 50});
1456 * map.panTo({lat: 50, lng: 30});
1457 * map.panTo(L.latLng(50, 30));
1458 * ```
1459 *
1460 * Note that `LatLng` does not inherit from Leaflet's `Class` object,
1461 * which means new classes can't inherit from it, and new methods
1462 * can't be added to it with the `include` function.
1463 */
1464
1465 function LatLng(lat, lng, alt) {
1466 if (isNaN(lat) || isNaN(lng)) {
1467 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1468 }
1469
1470 // @property lat: Number
1471 // Latitude in degrees
1472 this.lat = +lat;
1473
1474 // @property lng: Number
1475 // Longitude in degrees
1476 this.lng = +lng;
1477
1478 // @property alt: Number
1479 // Altitude in meters (optional)
1480 if (alt !== undefined) {
1481 this.alt = +alt;
1482 }
1483 }
1484
1485 LatLng.prototype = {
1486 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1487 // 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.
1488 equals: function (obj, maxMargin) {
1489 if (!obj) { return false; }
1490
1491 obj = toLatLng(obj);
1492
1493 var margin = Math.max(
1494 Math.abs(this.lat - obj.lat),
1495 Math.abs(this.lng - obj.lng));
1496
1497 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1498 },
1499
1500 // @method toString(): String
1501 // Returns a string representation of the point (for debugging purposes).
1502 toString: function (precision) {
1503 return 'LatLng(' +
1504 formatNum(this.lat, precision) + ', ' +
1505 formatNum(this.lng, precision) + ')';
1506 },
1507
1508 // @method distanceTo(otherLatLng: LatLng): Number
1509 // 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).
1510 distanceTo: function (other) {
1511 return Earth.distance(this, toLatLng(other));
1512 },
1513
1514 // @method wrap(): LatLng
1515 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1516 wrap: function () {
1517 return Earth.wrapLatLng(this);
1518 },
1519
1520 // @method toBounds(sizeInMeters: Number): LatLngBounds
1521 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
1522 toBounds: function (sizeInMeters) {
1523 var latAccuracy = 180 * sizeInMeters / 40075017,
1524 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1525
1526 return toLatLngBounds(
1527 [this.lat - latAccuracy, this.lng - lngAccuracy],
1528 [this.lat + latAccuracy, this.lng + lngAccuracy]);
1529 },
1530
1531 clone: function () {
1532 return new LatLng(this.lat, this.lng, this.alt);
1533 }
1534 };
1535
1536
1537
1538 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1539 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1540
1541 // @alternative
1542 // @factory L.latLng(coords: Array): LatLng
1543 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1544
1545 // @alternative
1546 // @factory L.latLng(coords: Object): LatLng
1547 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1548
1549 function toLatLng(a, b, c) {
1550 if (a instanceof LatLng) {
1551 return a;
1552 }
1553 if (isArray(a) && typeof a[0] !== 'object') {
1554 if (a.length === 3) {
1555 return new LatLng(a[0], a[1], a[2]);
1556 }
1557 if (a.length === 2) {
1558 return new LatLng(a[0], a[1]);
1559 }
1560 return null;
1561 }
1562 if (a === undefined || a === null) {
1563 return a;
1564 }
1565 if (typeof a === 'object' && 'lat' in a) {
1566 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1567 }
1568 if (b === undefined) {
1569 return null;
1570 }
1571 return new LatLng(a, b, c);
1572 }
1573
1574 /*
1575 * @namespace CRS
1576 * @crs L.CRS.Base
1577 * Object that defines coordinate reference systems for projecting
1578 * geographical points into pixel (screen) coordinates and back (and to
1579 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
1580 * [spatial reference system](https://en.wikipedia.org/wiki/Spatial_reference_system).
1581 *
1582 * Leaflet defines the most usual CRSs by default. If you want to use a
1583 * CRS not defined by default, take a look at the
1584 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
1585 *
1586 * Note that the CRS instances do not inherit from Leaflet's `Class` object,
1587 * and can't be instantiated. Also, new classes can't inherit from them,
1588 * and methods can't be added to them with the `include` function.
1589 */
1590
1591 var CRS = {
1592 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
1593 // Projects geographical coordinates into pixel coordinates for a given zoom.
1594 latLngToPoint: function (latlng, zoom) {
1595 var projectedPoint = this.projection.project(latlng),
1596 scale = this.scale(zoom);
1597
1598 return this.transformation._transform(projectedPoint, scale);
1599 },
1600
1601 // @method pointToLatLng(point: Point, zoom: Number): LatLng
1602 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
1603 // zoom into geographical coordinates.
1604 pointToLatLng: function (point, zoom) {
1605 var scale = this.scale(zoom),
1606 untransformedPoint = this.transformation.untransform(point, scale);
1607
1608 return this.projection.unproject(untransformedPoint);
1609 },
1610
1611 // @method project(latlng: LatLng): Point
1612 // Projects geographical coordinates into coordinates in units accepted for
1613 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
1614 project: function (latlng) {
1615 return this.projection.project(latlng);
1616 },
1617
1618 // @method unproject(point: Point): LatLng
1619 // Given a projected coordinate returns the corresponding LatLng.
1620 // The inverse of `project`.
1621 unproject: function (point) {
1622 return this.projection.unproject(point);
1623 },
1624
1625 // @method scale(zoom: Number): Number
1626 // Returns the scale used when transforming projected coordinates into
1627 // pixel coordinates for a particular zoom. For example, it returns
1628 // `256 * 2^zoom` for Mercator-based CRS.
1629 scale: function (zoom) {
1630 return 256 * Math.pow(2, zoom);
1631 },
1632
1633 // @method zoom(scale: Number): Number
1634 // Inverse of `scale()`, returns the zoom level corresponding to a scale
1635 // factor of `scale`.
1636 zoom: function (scale) {
1637 return Math.log(scale / 256) / Math.LN2;
1638 },
1639
1640 // @method getProjectedBounds(zoom: Number): Bounds
1641 // Returns the projection's bounds scaled and transformed for the provided `zoom`.
1642 getProjectedBounds: function (zoom) {
1643 if (this.infinite) { return null; }
1644
1645 var b = this.projection.bounds,
1646 s = this.scale(zoom),
1647 min = this.transformation.transform(b.min, s),
1648 max = this.transformation.transform(b.max, s);
1649
1650 return new Bounds(min, max);
1651 },
1652
1653 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
1654 // Returns the distance between two geographical coordinates.
1655
1656 // @property code: String
1657 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
1658 //
1659 // @property wrapLng: Number[]
1660 // An array of two numbers defining whether the longitude (horizontal) coordinate
1661 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
1662 // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
1663 //
1664 // @property wrapLat: Number[]
1665 // Like `wrapLng`, but for the latitude (vertical) axis.
1666
1667 // wrapLng: [min, max],
1668 // wrapLat: [min, max],
1669
1670 // @property infinite: Boolean
1671 // If true, the coordinate space will be unbounded (infinite in both axes)
1672 infinite: false,
1673
1674 // @method wrapLatLng(latlng: LatLng): LatLng
1675 // Returns a `LatLng` where lat and lng has been wrapped according to the
1676 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
1677 wrapLatLng: function (latlng) {
1678 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
1679 lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
1680 alt = latlng.alt;
1681
1682 return new LatLng(lat, lng, alt);
1683 },
1684
1685 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
1686 // Returns a `LatLngBounds` with the same size as the given one, ensuring
1687 // that its center is within the CRS's bounds.
1688 // Only accepts actual `L.LatLngBounds` instances, not arrays.
1689 wrapLatLngBounds: function (bounds) {
1690 var center = bounds.getCenter(),
1691 newCenter = this.wrapLatLng(center),
1692 latShift = center.lat - newCenter.lat,
1693 lngShift = center.lng - newCenter.lng;
1694
1695 if (latShift === 0 && lngShift === 0) {
1696 return bounds;
1697 }
1698
1699 var sw = bounds.getSouthWest(),
1700 ne = bounds.getNorthEast(),
1701 newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),
1702 newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);
1703
1704 return new LatLngBounds(newSw, newNe);
1705 }
1706 };
1707
1708 /*
1709 * @namespace CRS
1710 * @crs L.CRS.Earth
1711 *
1712 * Serves as the base for CRS that are global such that they cover the earth.
1713 * Can only be used as the base for other CRS and cannot be used directly,
1714 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
1715 * meters.
1716 */
1717
1718 var Earth = extend({}, CRS, {
1719 wrapLng: [-180, 180],
1720
1721 // Mean Earth Radius, as recommended for use by
1722 // the International Union of Geodesy and Geophysics,
1723 // see https://rosettacode.org/wiki/Haversine_formula
1724 R: 6371000,
1725
1726 // distance between two geographical points using spherical law of cosines approximation
1727 distance: function (latlng1, latlng2) {
1728 var rad = Math.PI / 180,
1729 lat1 = latlng1.lat * rad,
1730 lat2 = latlng2.lat * rad,
1731 sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2),
1732 sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2),
1733 a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon,
1734 c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1735 return this.R * c;
1736 }
1737 });
1738
1739 /*
1740 * @namespace Projection
1741 * @projection L.Projection.SphericalMercator
1742 *
1743 * Spherical Mercator projection — the most common projection for online maps,
1744 * used by almost all free and commercial tile providers. Assumes that Earth is
1745 * a sphere. Used by the `EPSG:3857` CRS.
1746 */
1747
1748 var earthRadius = 6378137;
1749
1750 var SphericalMercator = {
1751
1752 R: earthRadius,
1753 MAX_LATITUDE: 85.0511287798,
1754
1755 project: function (latlng) {
1756 var d = Math.PI / 180,
1757 max = this.MAX_LATITUDE,
1758 lat = Math.max(Math.min(max, latlng.lat), -max),
1759 sin = Math.sin(lat * d);
1760
1761 return new Point(
1762 this.R * latlng.lng * d,
1763 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
1764 },
1765
1766 unproject: function (point) {
1767 var d = 180 / Math.PI;
1768
1769 return new LatLng(
1770 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
1771 point.x * d / this.R);
1772 },
1773
1774 bounds: (function () {
1775 var d = earthRadius * Math.PI;
1776 return new Bounds([-d, -d], [d, d]);
1777 })()
1778 };
1779
1780 /*
1781 * @class Transformation
1782 * @aka L.Transformation
1783 *
1784 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1785 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1786 * the reverse. Used by Leaflet in its projections code.
1787 *
1788 * @example
1789 *
1790 * ```js
1791 * var transformation = L.transformation(2, 5, -1, 10),
1792 * p = L.point(1, 2),
1793 * p2 = transformation.transform(p), // L.point(7, 8)
1794 * p3 = transformation.untransform(p2); // L.point(1, 2)
1795 * ```
1796 */
1797
1798
1799 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1800 // Creates a `Transformation` object with the given coefficients.
1801 function Transformation(a, b, c, d) {
1802 if (isArray(a)) {
1803 // use array properties
1804 this._a = a[0];
1805 this._b = a[1];
1806 this._c = a[2];
1807 this._d = a[3];
1808 return;
1809 }
1810 this._a = a;
1811 this._b = b;
1812 this._c = c;
1813 this._d = d;
1814 }
1815
1816 Transformation.prototype = {
1817 // @method transform(point: Point, scale?: Number): Point
1818 // Returns a transformed point, optionally multiplied by the given scale.
1819 // Only accepts actual `L.Point` instances, not arrays.
1820 transform: function (point, scale) { // (Point, Number) -> Point
1821 return this._transform(point.clone(), scale);
1822 },
1823
1824 // destructive transform (faster)
1825 _transform: function (point, scale) {
1826 scale = scale || 1;
1827 point.x = scale * (this._a * point.x + this._b);
1828 point.y = scale * (this._c * point.y + this._d);
1829 return point;
1830 },
1831
1832 // @method untransform(point: Point, scale?: Number): Point
1833 // Returns the reverse transformation of the given point, optionally divided
1834 // by the given scale. Only accepts actual `L.Point` instances, not arrays.
1835 untransform: function (point, scale) {
1836 scale = scale || 1;
1837 return new Point(
1838 (point.x / scale - this._b) / this._a,
1839 (point.y / scale - this._d) / this._c);
1840 }
1841 };
1842
1843 // factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1844
1845 // @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1846 // Instantiates a Transformation object with the given coefficients.
1847
1848 // @alternative
1849 // @factory L.transformation(coefficients: Array): Transformation
1850 // Expects an coefficients array of the form
1851 // `[a: Number, b: Number, c: Number, d: Number]`.
1852
1853 function toTransformation(a, b, c, d) {
1854 return new Transformation(a, b, c, d);
1855 }
1856
1857 /*
1858 * @namespace CRS
1859 * @crs L.CRS.EPSG3857
1860 *
1861 * The most common CRS for online maps, used by almost all free and commercial
1862 * tile providers. Uses Spherical Mercator projection. Set in by default in
1863 * Map's `crs` option.
1864 */
1865
1866 var EPSG3857 = extend({}, Earth, {
1867 code: 'EPSG:3857',
1868 projection: SphericalMercator,
1869
1870 transformation: (function () {
1871 var scale = 0.5 / (Math.PI * SphericalMercator.R);
1872 return toTransformation(scale, 0.5, -scale, 0.5);
1873 }())
1874 });
1875
1876 var EPSG900913 = extend({}, EPSG3857, {
1877 code: 'EPSG:900913'
1878 });
1879
1880 // @namespace SVG; @section
1881 // There are several static functions which can be called without instantiating L.SVG:
1882
1883 // @function create(name: String): SVGElement
1884 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
1885 // corresponding to the class name passed. For example, using 'line' will return
1886 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
1887 function svgCreate(name) {
1888 return document.createElementNS('http://www.w3.org/2000/svg', name);
1889 }
1890
1891 // @function pointsToPath(rings: Point[], closed: Boolean): String
1892 // Generates a SVG path string for multiple rings, with each ring turning
1893 // into "M..L..L.." instructions
1894 function pointsToPath(rings, closed) {
1895 var str = '',
1896 i, j, len, len2, points, p;
1897
1898 for (i = 0, len = rings.length; i < len; i++) {
1899 points = rings[i];
1900
1901 for (j = 0, len2 = points.length; j < len2; j++) {
1902 p = points[j];
1903 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
1904 }
1905
1906 // closes the ring for polygons; "x" is VML syntax
1907 str += closed ? (Browser.svg ? 'z' : 'x') : '';
1908 }
1909
1910 // SVG complains about empty path strings
1911 return str || 'M0 0';
1912 }
1913
1914 /*
1915 * @namespace Browser
1916 * @aka L.Browser
1917 *
1918 * A namespace with static properties for browser/feature detection used by Leaflet internally.
1919 *
1920 * @example
1921 *
1922 * ```js
1923 * if (L.Browser.ielt9) {
1924 * alert('Upgrade your browser, dude!');
1925 * }
1926 * ```
1927 */
1928
1929 var style = document.documentElement.style;
1930
1931 // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
1932 var ie = 'ActiveXObject' in window;
1933
1934 // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
1935 var ielt9 = ie && !document.addEventListener;
1936
1937 // @property edge: Boolean; `true` for the Edge web browser.
1938 var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
1939
1940 // @property webkit: Boolean;
1941 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
1942 var webkit = userAgentContains('webkit');
1943
1944 // @property android: Boolean
1945 // **Deprecated.** `true` for any browser running on an Android platform.
1946 var android = userAgentContains('android');
1947
1948 // @property android23: Boolean; **Deprecated.** `true` for browsers running on Android 2 or Android 3.
1949 var android23 = userAgentContains('android 2') || userAgentContains('android 3');
1950
1951 /* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */
1952 var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit
1953 // @property androidStock: Boolean; **Deprecated.** `true` for the Android stock browser (i.e. not Chrome)
1954 var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);
1955
1956 // @property opera: Boolean; `true` for the Opera browser
1957 var opera = !!window.opera;
1958
1959 // @property chrome: Boolean; `true` for the Chrome browser.
1960 var chrome = !edge && userAgentContains('chrome');
1961
1962 // @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
1963 var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
1964
1965 // @property safari: Boolean; `true` for the Safari browser.
1966 var safari = !chrome && userAgentContains('safari');
1967
1968 var phantom = userAgentContains('phantom');
1969
1970 // @property opera12: Boolean
1971 // `true` for the Opera browser supporting CSS transforms (version 12 or later).
1972 var opera12 = 'OTransition' in style;
1973
1974 // @property win: Boolean; `true` when the browser is running in a Windows platform
1975 var win = navigator.platform.indexOf('Win') === 0;
1976
1977 // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
1978 var ie3d = ie && ('transition' in style);
1979
1980 // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
1981 var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;
1982
1983 // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
1984 var gecko3d = 'MozPerspective' in style;
1985
1986 // @property any3d: Boolean
1987 // `true` for all browsers supporting CSS transforms.
1988 var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
1989
1990 // @property mobile: Boolean; `true` for all browsers running in a mobile device.
1991 var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
1992
1993 // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
1994 var mobileWebkit = mobile && webkit;
1995
1996 // @property mobileWebkit3d: Boolean
1997 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
1998 var mobileWebkit3d = mobile && webkit3d;
1999
2000 // @property msPointer: Boolean
2001 // `true` for browsers implementing the Microsoft touch events model (notably IE10).
2002 var msPointer = !window.PointerEvent && window.MSPointerEvent;
2003
2004 // @property pointer: Boolean
2005 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
2006 var pointer = !!(window.PointerEvent || msPointer);
2007
2008 // @property touchNative: Boolean
2009 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
2010 // **This does not necessarily mean** that the browser is running in a computer with
2011 // a touchscreen, it only means that the browser is capable of understanding
2012 // touch events.
2013 var touchNative = 'ontouchstart' in window || !!window.TouchEvent;
2014
2015 // @property touch: Boolean
2016 // `true` for all browsers supporting either [touch](#browser-touch) or [pointer](#browser-pointer) events.
2017 // Note: pointer events will be preferred (if available), and processed for all `touch*` listeners.
2018 var touch = !window.L_NO_TOUCH && (touchNative || pointer);
2019
2020 // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
2021 var mobileOpera = mobile && opera;
2022
2023 // @property mobileGecko: Boolean
2024 // `true` for gecko-based browsers running in a mobile device.
2025 var mobileGecko = mobile && gecko;
2026
2027 // @property retina: Boolean
2028 // `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%.
2029 var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
2030
2031 // @property passiveEvents: Boolean
2032 // `true` for browsers that support passive events.
2033 var passiveEvents = (function () {
2034 var supportsPassiveOption = false;
2035 try {
2036 var opts = Object.defineProperty({}, 'passive', {
2037 get: function () { // eslint-disable-line getter-return
2038 supportsPassiveOption = true;
2039 }
2040 });
2041 window.addEventListener('testPassiveEventSupport', falseFn, opts);
2042 window.removeEventListener('testPassiveEventSupport', falseFn, opts);
2043 } catch (e) {
2044 // Errors can safely be ignored since this is only a browser support test.
2045 }
2046 return supportsPassiveOption;
2047 }());
2048
2049 // @property canvas: Boolean
2050 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
2051 var canvas$1 = (function () {
2052 return !!document.createElement('canvas').getContext;
2053 }());
2054
2055 // @property svg: Boolean
2056 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
2057 var svg$1 = !!(document.createElementNS && svgCreate('svg').createSVGRect);
2058
2059 var inlineSvg = !!svg$1 && (function () {
2060 var div = document.createElement('div');
2061 div.innerHTML = '<svg/>';
2062 return (div.firstChild && div.firstChild.namespaceURI) === 'http://www.w3.org/2000/svg';
2063 })();
2064
2065 // @property vml: Boolean
2066 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
2067 var vml = !svg$1 && (function () {
2068 try {
2069 var div = document.createElement('div');
2070 div.innerHTML = '<v:shape adj="1"/>';
2071
2072 var shape = div.firstChild;
2073 shape.style.behavior = 'url(#default#VML)';
2074
2075 return shape && (typeof shape.adj === 'object');
2076
2077 } catch (e) {
2078 return false;
2079 }
2080 }());
2081
2082
2083 // @property mac: Boolean; `true` when the browser is running in a Mac platform
2084 var mac = navigator.platform.indexOf('Mac') === 0;
2085
2086 // @property mac: Boolean; `true` when the browser is running in a Linux platform
2087 var linux = navigator.platform.indexOf('Linux') === 0;
2088
2089 function userAgentContains(str) {
2090 return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
2091 }
2092
2093
2094 var Browser = {
2095 ie: ie,
2096 ielt9: ielt9,
2097 edge: edge,
2098 webkit: webkit,
2099 android: android,
2100 android23: android23,
2101 androidStock: androidStock,
2102 opera: opera,
2103 chrome: chrome,
2104 gecko: gecko,
2105 safari: safari,
2106 phantom: phantom,
2107 opera12: opera12,
2108 win: win,
2109 ie3d: ie3d,
2110 webkit3d: webkit3d,
2111 gecko3d: gecko3d,
2112 any3d: any3d,
2113 mobile: mobile,
2114 mobileWebkit: mobileWebkit,
2115 mobileWebkit3d: mobileWebkit3d,
2116 msPointer: msPointer,
2117 pointer: pointer,
2118 touch: touch,
2119 touchNative: touchNative,
2120 mobileOpera: mobileOpera,
2121 mobileGecko: mobileGecko,
2122 retina: retina,
2123 passiveEvents: passiveEvents,
2124 canvas: canvas$1,
2125 svg: svg$1,
2126 vml: vml,
2127 inlineSvg: inlineSvg,
2128 mac: mac,
2129 linux: linux
2130 };
2131
2132 /*
2133 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
2134 */
2135
2136 var POINTER_DOWN = Browser.msPointer ? 'MSPointerDown' : 'pointerdown';
2137 var POINTER_MOVE = Browser.msPointer ? 'MSPointerMove' : 'pointermove';
2138 var POINTER_UP = Browser.msPointer ? 'MSPointerUp' : 'pointerup';
2139 var POINTER_CANCEL = Browser.msPointer ? 'MSPointerCancel' : 'pointercancel';
2140 var pEvent = {
2141 touchstart : POINTER_DOWN,
2142 touchmove : POINTER_MOVE,
2143 touchend : POINTER_UP,
2144 touchcancel : POINTER_CANCEL
2145 };
2146 var handle = {
2147 touchstart : _onPointerStart,
2148 touchmove : _handlePointer,
2149 touchend : _handlePointer,
2150 touchcancel : _handlePointer
2151 };
2152 var _pointers = {};
2153 var _pointerDocListener = false;
2154
2155 // Provides a touch events wrapper for (ms)pointer events.
2156 // ref https://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
2157
2158 function addPointerListener(obj, type, handler) {
2159 if (type === 'touchstart') {
2160 _addPointerDocListener();
2161 }
2162 if (!handle[type]) {
2163 console.warn('wrong event specified:', type);
2164 return falseFn;
2165 }
2166 handler = handle[type].bind(this, handler);
2167 obj.addEventListener(pEvent[type], handler, false);
2168 return handler;
2169 }
2170
2171 function removePointerListener(obj, type, handler) {
2172 if (!pEvent[type]) {
2173 console.warn('wrong event specified:', type);
2174 return;
2175 }
2176 obj.removeEventListener(pEvent[type], handler, false);
2177 }
2178
2179 function _globalPointerDown(e) {
2180 _pointers[e.pointerId] = e;
2181 }
2182
2183 function _globalPointerMove(e) {
2184 if (_pointers[e.pointerId]) {
2185 _pointers[e.pointerId] = e;
2186 }
2187 }
2188
2189 function _globalPointerUp(e) {
2190 delete _pointers[e.pointerId];
2191 }
2192
2193 function _addPointerDocListener() {
2194 // need to keep track of what pointers and how many are active to provide e.touches emulation
2195 if (!_pointerDocListener) {
2196 // we listen document as any drags that end by moving the touch off the screen get fired there
2197 document.addEventListener(POINTER_DOWN, _globalPointerDown, true);
2198 document.addEventListener(POINTER_MOVE, _globalPointerMove, true);
2199 document.addEventListener(POINTER_UP, _globalPointerUp, true);
2200 document.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
2201
2202 _pointerDocListener = true;
2203 }
2204 }
2205
2206 function _handlePointer(handler, e) {
2207 if (e.pointerType === (e.MSPOINTER_TYPE_MOUSE || 'mouse')) { return; }
2208
2209 e.touches = [];
2210 for (var i in _pointers) {
2211 e.touches.push(_pointers[i]);
2212 }
2213 e.changedTouches = [e];
2214
2215 handler(e);
2216 }
2217
2218 function _onPointerStart(handler, e) {
2219 // IE10 specific: MsTouch needs preventDefault. See #2000
2220 if (e.MSPOINTER_TYPE_TOUCH && e.pointerType === e.MSPOINTER_TYPE_TOUCH) {
2221 preventDefault(e);
2222 }
2223 _handlePointer(handler, e);
2224 }
2225
2226 /*
2227 * Extends the event handling code with double tap support for mobile browsers.
2228 *
2229 * Note: currently most browsers fire native dblclick, with only a few exceptions
2230 * (see https://github.com/Leaflet/Leaflet/issues/7012#issuecomment-595087386)
2231 */
2232
2233 function makeDblclick(event) {
2234 // in modern browsers `type` cannot be just overridden:
2235 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only
2236 var newEvent = {},
2237 prop, i;
2238 for (i in event) {
2239 prop = event[i];
2240 newEvent[i] = prop && prop.bind ? prop.bind(event) : prop;
2241 }
2242 event = newEvent;
2243 newEvent.type = 'dblclick';
2244 newEvent.detail = 2;
2245 newEvent.isTrusted = false;
2246 newEvent._simulated = true; // for debug purposes
2247 return newEvent;
2248 }
2249
2250 var delay = 200;
2251 function addDoubleTapListener(obj, handler) {
2252 // Most browsers handle double tap natively
2253 obj.addEventListener('dblclick', handler);
2254
2255 // On some platforms the browser doesn't fire native dblclicks for touch events.
2256 // It seems that in all such cases `detail` property of `click` event is always `1`.
2257 // So here we rely on that fact to avoid excessive 'dblclick' simulation when not needed.
2258 var last = 0,
2259 detail;
2260 function simDblclick(e) {
2261 if (e.detail !== 1) {
2262 detail = e.detail; // keep in sync to avoid false dblclick in some cases
2263 return;
2264 }
2265
2266 if (e.pointerType === 'mouse' ||
2267 (e.sourceCapabilities && !e.sourceCapabilities.firesTouchEvents)) {
2268
2269 return;
2270 }
2271
2272 // When clicking on an <input>, the browser generates a click on its
2273 // <label> (and vice versa) triggering two clicks in quick succession.
2274 // This ignores clicks on elements which are a label with a 'for'
2275 // attribute (or children of such a label), but not children of
2276 // a <input>.
2277 var path = getPropagationPath(e);
2278 if (path.some(function (el) {
2279 return el instanceof HTMLLabelElement && el.attributes.for;
2280 }) &&
2281 !path.some(function (el) {
2282 return (
2283 el instanceof HTMLInputElement ||
2284 el instanceof HTMLSelectElement
2285 );
2286 })
2287 ) {
2288 return;
2289 }
2290
2291 var now = Date.now();
2292 if (now - last <= delay) {
2293 detail++;
2294 if (detail === 2) {
2295 handler(makeDblclick(e));
2296 }
2297 } else {
2298 detail = 1;
2299 }
2300 last = now;
2301 }
2302
2303 obj.addEventListener('click', simDblclick);
2304
2305 return {
2306 dblclick: handler,
2307 simDblclick: simDblclick
2308 };
2309 }
2310
2311 function removeDoubleTapListener(obj, handlers) {
2312 obj.removeEventListener('dblclick', handlers.dblclick);
2313 obj.removeEventListener('click', handlers.simDblclick);
2314 }
2315
2316 /*
2317 * @namespace DomUtil
2318 *
2319 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
2320 * tree, used by Leaflet internally.
2321 *
2322 * Most functions expecting or returning a `HTMLElement` also work for
2323 * SVG elements. The only difference is that classes refer to CSS classes
2324 * in HTML and SVG classes in SVG.
2325 */
2326
2327
2328 // @property TRANSFORM: String
2329 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
2330 var TRANSFORM = testProp(
2331 ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
2332
2333 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
2334 // the same for the transitionend event, in particular the Android 4.1 stock browser
2335
2336 // @property TRANSITION: String
2337 // Vendor-prefixed transition style name.
2338 var TRANSITION = testProp(
2339 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
2340
2341 // @property TRANSITION_END: String
2342 // Vendor-prefixed transitionend event name.
2343 var TRANSITION_END =
2344 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
2345
2346
2347 // @function get(id: String|HTMLElement): HTMLElement
2348 // Returns an element given its DOM id, or returns the element itself
2349 // if it was passed directly.
2350 function get(id) {
2351 return typeof id === 'string' ? document.getElementById(id) : id;
2352 }
2353
2354 // @function getStyle(el: HTMLElement, styleAttrib: String): String
2355 // Returns the value for a certain style attribute on an element,
2356 // including computed values or values set through CSS.
2357 function getStyle(el, style) {
2358 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
2359
2360 if ((!value || value === 'auto') && document.defaultView) {
2361 var css = document.defaultView.getComputedStyle(el, null);
2362 value = css ? css[style] : null;
2363 }
2364 return value === 'auto' ? null : value;
2365 }
2366
2367 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
2368 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
2369 function create$1(tagName, className, container) {
2370 var el = document.createElement(tagName);
2371 el.className = className || '';
2372
2373 if (container) {
2374 container.appendChild(el);
2375 }
2376 return el;
2377 }
2378
2379 // @function remove(el: HTMLElement)
2380 // Removes `el` from its parent element
2381 function remove(el) {
2382 var parent = el.parentNode;
2383 if (parent) {
2384 parent.removeChild(el);
2385 }
2386 }
2387
2388 // @function empty(el: HTMLElement)
2389 // Removes all of `el`'s children elements from `el`
2390 function empty(el) {
2391 while (el.firstChild) {
2392 el.removeChild(el.firstChild);
2393 }
2394 }
2395
2396 // @function toFront(el: HTMLElement)
2397 // Makes `el` the last child of its parent, so it renders in front of the other children.
2398 function toFront(el) {
2399 var parent = el.parentNode;
2400 if (parent && parent.lastChild !== el) {
2401 parent.appendChild(el);
2402 }
2403 }
2404
2405 // @function toBack(el: HTMLElement)
2406 // Makes `el` the first child of its parent, so it renders behind the other children.
2407 function toBack(el) {
2408 var parent = el.parentNode;
2409 if (parent && parent.firstChild !== el) {
2410 parent.insertBefore(el, parent.firstChild);
2411 }
2412 }
2413
2414 // @function hasClass(el: HTMLElement, name: String): Boolean
2415 // Returns `true` if the element's class attribute contains `name`.
2416 function hasClass(el, name) {
2417 if (el.classList !== undefined) {
2418 return el.classList.contains(name);
2419 }
2420 var className = getClass(el);
2421 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
2422 }
2423
2424 // @function addClass(el: HTMLElement, name: String)
2425 // Adds `name` to the element's class attribute.
2426 function addClass(el, name) {
2427 if (el.classList !== undefined) {
2428 var classes = splitWords(name);
2429 for (var i = 0, len = classes.length; i < len; i++) {
2430 el.classList.add(classes[i]);
2431 }
2432 } else if (!hasClass(el, name)) {
2433 var className = getClass(el);
2434 setClass(el, (className ? className + ' ' : '') + name);
2435 }
2436 }
2437
2438 // @function removeClass(el: HTMLElement, name: String)
2439 // Removes `name` from the element's class attribute.
2440 function removeClass(el, name) {
2441 if (el.classList !== undefined) {
2442 el.classList.remove(name);
2443 } else {
2444 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
2445 }
2446 }
2447
2448 // @function setClass(el: HTMLElement, name: String)
2449 // Sets the element's class.
2450 function setClass(el, name) {
2451 if (el.className.baseVal === undefined) {
2452 el.className = name;
2453 } else {
2454 // in case of SVG element
2455 el.className.baseVal = name;
2456 }
2457 }
2458
2459 // @function getClass(el: HTMLElement): String
2460 // Returns the element's class.
2461 function getClass(el) {
2462 // Check if the element is an SVGElementInstance and use the correspondingElement instead
2463 // (Required for linked SVG elements in IE11.)
2464 if (el.correspondingElement) {
2465 el = el.correspondingElement;
2466 }
2467 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
2468 }
2469
2470 // @function setOpacity(el: HTMLElement, opacity: Number)
2471 // Set the opacity of an element (including old IE support).
2472 // `opacity` must be a number from `0` to `1`.
2473 function setOpacity(el, value) {
2474 if ('opacity' in el.style) {
2475 el.style.opacity = value;
2476 } else if ('filter' in el.style) {
2477 _setOpacityIE(el, value);
2478 }
2479 }
2480
2481 function _setOpacityIE(el, value) {
2482 var filter = false,
2483 filterName = 'DXImageTransform.Microsoft.Alpha';
2484
2485 // filters collection throws an error if we try to retrieve a filter that doesn't exist
2486 try {
2487 filter = el.filters.item(filterName);
2488 } catch (e) {
2489 // don't set opacity to 1 if we haven't already set an opacity,
2490 // it isn't needed and breaks transparent pngs.
2491 if (value === 1) { return; }
2492 }
2493
2494 value = Math.round(value * 100);
2495
2496 if (filter) {
2497 filter.Enabled = (value !== 100);
2498 filter.Opacity = value;
2499 } else {
2500 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
2501 }
2502 }
2503
2504 // @function testProp(props: String[]): String|false
2505 // Goes through the array of style names and returns the first name
2506 // that is a valid style name for an element. If no such name is found,
2507 // it returns false. Useful for vendor-prefixed styles like `transform`.
2508 function testProp(props) {
2509 var style = document.documentElement.style;
2510
2511 for (var i = 0; i < props.length; i++) {
2512 if (props[i] in style) {
2513 return props[i];
2514 }
2515 }
2516 return false;
2517 }
2518
2519 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
2520 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
2521 // and optionally scaled by `scale`. Does not have an effect if the
2522 // browser doesn't support 3D CSS transforms.
2523 function setTransform(el, offset, scale) {
2524 var pos = offset || new Point(0, 0);
2525
2526 el.style[TRANSFORM] =
2527 (Browser.ie3d ?
2528 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
2529 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
2530 (scale ? ' scale(' + scale + ')' : '');
2531 }
2532
2533 // @function setPosition(el: HTMLElement, position: Point)
2534 // Sets the position of `el` to coordinates specified by `position`,
2535 // using CSS translate or top/left positioning depending on the browser
2536 // (used by Leaflet internally to position its layers).
2537 function setPosition(el, point) {
2538
2539 /*eslint-disable */
2540 el._leaflet_pos = point;
2541 /* eslint-enable */
2542
2543 if (Browser.any3d) {
2544 setTransform(el, point);
2545 } else {
2546 el.style.left = point.x + 'px';
2547 el.style.top = point.y + 'px';
2548 }
2549 }
2550
2551 // @function getPosition(el: HTMLElement): Point
2552 // Returns the coordinates of an element previously positioned with setPosition.
2553 function getPosition(el) {
2554 // this method is only used for elements previously positioned using setPosition,
2555 // so it's safe to cache the position for performance
2556
2557 return el._leaflet_pos || new Point(0, 0);
2558 }
2559
2560 // @function disableTextSelection()
2561 // Prevents the user from generating `selectstart` DOM events, usually generated
2562 // when the user drags the mouse through a page with text. Used internally
2563 // by Leaflet to override the behaviour of any click-and-drag interaction on
2564 // the map. Affects drag interactions on the whole document.
2565
2566 // @function enableTextSelection()
2567 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
2568 var disableTextSelection;
2569 var enableTextSelection;
2570 var _userSelect;
2571 if ('onselectstart' in document) {
2572 disableTextSelection = function () {
2573 on(window, 'selectstart', preventDefault);
2574 };
2575 enableTextSelection = function () {
2576 off(window, 'selectstart', preventDefault);
2577 };
2578 } else {
2579 var userSelectProperty = testProp(
2580 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
2581
2582 disableTextSelection = function () {
2583 if (userSelectProperty) {
2584 var style = document.documentElement.style;
2585 _userSelect = style[userSelectProperty];
2586 style[userSelectProperty] = 'none';
2587 }
2588 };
2589 enableTextSelection = function () {
2590 if (userSelectProperty) {
2591 document.documentElement.style[userSelectProperty] = _userSelect;
2592 _userSelect = undefined;
2593 }
2594 };
2595 }
2596
2597 // @function disableImageDrag()
2598 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
2599 // for `dragstart` DOM events, usually generated when the user drags an image.
2600 function disableImageDrag() {
2601 on(window, 'dragstart', preventDefault);
2602 }
2603
2604 // @function enableImageDrag()
2605 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
2606 function enableImageDrag() {
2607 off(window, 'dragstart', preventDefault);
2608 }
2609
2610 var _outlineElement, _outlineStyle;
2611 // @function preventOutline(el: HTMLElement)
2612 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
2613 // of the element `el` invisible. Used internally by Leaflet to prevent
2614 // focusable elements from displaying an outline when the user performs a
2615 // drag interaction on them.
2616 function preventOutline(element) {
2617 while (element.tabIndex === -1) {
2618 element = element.parentNode;
2619 }
2620 if (!element.style) { return; }
2621 restoreOutline();
2622 _outlineElement = element;
2623 _outlineStyle = element.style.outlineStyle;
2624 element.style.outlineStyle = 'none';
2625 on(window, 'keydown', restoreOutline);
2626 }
2627
2628 // @function restoreOutline()
2629 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
2630 function restoreOutline() {
2631 if (!_outlineElement) { return; }
2632 _outlineElement.style.outlineStyle = _outlineStyle;
2633 _outlineElement = undefined;
2634 _outlineStyle = undefined;
2635 off(window, 'keydown', restoreOutline);
2636 }
2637
2638 // @function getSizedParentNode(el: HTMLElement): HTMLElement
2639 // Finds the closest parent node which size (width and height) is not null.
2640 function getSizedParentNode(element) {
2641 do {
2642 element = element.parentNode;
2643 } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
2644 return element;
2645 }
2646
2647 // @function getScale(el: HTMLElement): Object
2648 // Computes the CSS scale currently applied on the element.
2649 // Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
2650 // and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
2651 function getScale(element) {
2652 var rect = element.getBoundingClientRect(); // Read-only in old browsers.
2653
2654 return {
2655 x: rect.width / element.offsetWidth || 1,
2656 y: rect.height / element.offsetHeight || 1,
2657 boundingClientRect: rect
2658 };
2659 }
2660
2661 var DomUtil = {
2662 __proto__: null,
2663 TRANSFORM: TRANSFORM,
2664 TRANSITION: TRANSITION,
2665 TRANSITION_END: TRANSITION_END,
2666 get: get,
2667 getStyle: getStyle,
2668 create: create$1,
2669 remove: remove,
2670 empty: empty,
2671 toFront: toFront,
2672 toBack: toBack,
2673 hasClass: hasClass,
2674 addClass: addClass,
2675 removeClass: removeClass,
2676 setClass: setClass,
2677 getClass: getClass,
2678 setOpacity: setOpacity,
2679 testProp: testProp,
2680 setTransform: setTransform,
2681 setPosition: setPosition,
2682 getPosition: getPosition,
2683 get disableTextSelection () { return disableTextSelection; },
2684 get enableTextSelection () { return enableTextSelection; },
2685 disableImageDrag: disableImageDrag,
2686 enableImageDrag: enableImageDrag,
2687 preventOutline: preventOutline,
2688 restoreOutline: restoreOutline,
2689 getSizedParentNode: getSizedParentNode,
2690 getScale: getScale
2691 };
2692
2693 /*
2694 * @namespace DomEvent
2695 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
2696 */
2697
2698 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
2699
2700 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
2701 // Adds a listener function (`fn`) to a particular DOM event type of the
2702 // element `el`. You can optionally specify the context of the listener
2703 // (object the `this` keyword will point to). You can also pass several
2704 // space-separated types (e.g. `'click dblclick'`).
2705
2706 // @alternative
2707 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
2708 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2709 function on(obj, types, fn, context) {
2710
2711 if (types && typeof types === 'object') {
2712 for (var type in types) {
2713 addOne(obj, type, types[type], fn);
2714 }
2715 } else {
2716 types = splitWords(types);
2717
2718 for (var i = 0, len = types.length; i < len; i++) {
2719 addOne(obj, types[i], fn, context);
2720 }
2721 }
2722
2723 return this;
2724 }
2725
2726 var eventsKey = '_leaflet_events';
2727
2728 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
2729 // Removes a previously added listener function.
2730 // Note that if you passed a custom context to on, you must pass the same
2731 // context to `off` in order to remove the listener.
2732
2733 // @alternative
2734 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
2735 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2736
2737 // @alternative
2738 // @function off(el: HTMLElement, types: String): this
2739 // Removes all previously added listeners of given types.
2740
2741 // @alternative
2742 // @function off(el: HTMLElement): this
2743 // Removes all previously added listeners from given HTMLElement
2744 function off(obj, types, fn, context) {
2745
2746 if (arguments.length === 1) {
2747 batchRemove(obj);
2748 delete obj[eventsKey];
2749
2750 } else if (types && typeof types === 'object') {
2751 for (var type in types) {
2752 removeOne(obj, type, types[type], fn);
2753 }
2754
2755 } else {
2756 types = splitWords(types);
2757
2758 if (arguments.length === 2) {
2759 batchRemove(obj, function (type) {
2760 return indexOf(types, type) !== -1;
2761 });
2762 } else {
2763 for (var i = 0, len = types.length; i < len; i++) {
2764 removeOne(obj, types[i], fn, context);
2765 }
2766 }
2767 }
2768
2769 return this;
2770 }
2771
2772 function batchRemove(obj, filterFn) {
2773 for (var id in obj[eventsKey]) {
2774 var type = id.split(/\d/)[0];
2775 if (!filterFn || filterFn(type)) {
2776 removeOne(obj, type, null, null, id);
2777 }
2778 }
2779 }
2780
2781 var mouseSubst = {
2782 mouseenter: 'mouseover',
2783 mouseleave: 'mouseout',
2784 wheel: !('onwheel' in window) && 'mousewheel'
2785 };
2786
2787 function addOne(obj, type, fn, context) {
2788 var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
2789
2790 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
2791
2792 var handler = function (e) {
2793 return fn.call(context || obj, e || window.event);
2794 };
2795
2796 var originalHandler = handler;
2797
2798 if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) {
2799 // Needs DomEvent.Pointer.js
2800 handler = addPointerListener(obj, type, handler);
2801
2802 } else if (Browser.touch && (type === 'dblclick')) {
2803 handler = addDoubleTapListener(obj, handler);
2804
2805 } else if ('addEventListener' in obj) {
2806
2807 if (type === 'touchstart' || type === 'touchmove' || type === 'wheel' || type === 'mousewheel') {
2808 obj.addEventListener(mouseSubst[type] || type, handler, Browser.passiveEvents ? {passive: false} : false);
2809
2810 } else if (type === 'mouseenter' || type === 'mouseleave') {
2811 handler = function (e) {
2812 e = e || window.event;
2813 if (isExternalTarget(obj, e)) {
2814 originalHandler(e);
2815 }
2816 };
2817 obj.addEventListener(mouseSubst[type], handler, false);
2818
2819 } else {
2820 obj.addEventListener(type, originalHandler, false);
2821 }
2822
2823 } else {
2824 obj.attachEvent('on' + type, handler);
2825 }
2826
2827 obj[eventsKey] = obj[eventsKey] || {};
2828 obj[eventsKey][id] = handler;
2829 }
2830
2831 function removeOne(obj, type, fn, context, id) {
2832 id = id || type + stamp(fn) + (context ? '_' + stamp(context) : '');
2833 var handler = obj[eventsKey] && obj[eventsKey][id];
2834
2835 if (!handler) { return this; }
2836
2837 if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) {
2838 removePointerListener(obj, type, handler);
2839
2840 } else if (Browser.touch && (type === 'dblclick')) {
2841 removeDoubleTapListener(obj, handler);
2842
2843 } else if ('removeEventListener' in obj) {
2844
2845 obj.removeEventListener(mouseSubst[type] || type, handler, false);
2846
2847 } else {
2848 obj.detachEvent('on' + type, handler);
2849 }
2850
2851 obj[eventsKey][id] = null;
2852 }
2853
2854 // @function stopPropagation(ev: DOMEvent): this
2855 // Stop the given event from propagation to parent elements. Used inside the listener functions:
2856 // ```js
2857 // L.DomEvent.on(div, 'click', function (ev) {
2858 // L.DomEvent.stopPropagation(ev);
2859 // });
2860 // ```
2861 function stopPropagation(e) {
2862
2863 if (e.stopPropagation) {
2864 e.stopPropagation();
2865 } else if (e.originalEvent) { // In case of Leaflet event.
2866 e.originalEvent._stopped = true;
2867 } else {
2868 e.cancelBubble = true;
2869 }
2870
2871 return this;
2872 }
2873
2874 // @function disableScrollPropagation(el: HTMLElement): this
2875 // Adds `stopPropagation` to the element's `'wheel'` events (plus browser variants).
2876 function disableScrollPropagation(el) {
2877 addOne(el, 'wheel', stopPropagation);
2878 return this;
2879 }
2880
2881 // @function disableClickPropagation(el: HTMLElement): this
2882 // Adds `stopPropagation` to the element's `'click'`, `'dblclick'`, `'contextmenu'`,
2883 // `'mousedown'` and `'touchstart'` events (plus browser variants).
2884 function disableClickPropagation(el) {
2885 on(el, 'mousedown touchstart dblclick contextmenu', stopPropagation);
2886 el['_leaflet_disable_click'] = true;
2887 return this;
2888 }
2889
2890 // @function preventDefault(ev: DOMEvent): this
2891 // Prevents the default action of the DOM Event `ev` from happening (such as
2892 // following a link in the href of the a element, or doing a POST request
2893 // with page reload when a `<form>` is submitted).
2894 // Use it inside listener functions.
2895 function preventDefault(e) {
2896 if (e.preventDefault) {
2897 e.preventDefault();
2898 } else {
2899 e.returnValue = false;
2900 }
2901 return this;
2902 }
2903
2904 // @function stop(ev: DOMEvent): this
2905 // Does `stopPropagation` and `preventDefault` at the same time.
2906 function stop(e) {
2907 preventDefault(e);
2908 stopPropagation(e);
2909 return this;
2910 }
2911
2912 // @function getPropagationPath(ev: DOMEvent): Array
2913 // Compatibility polyfill for [`Event.composedPath()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath).
2914 // Returns an array containing the `HTMLElement`s that the given DOM event
2915 // should propagate to (if not stopped).
2916 function getPropagationPath(ev) {
2917 if (ev.composedPath) {
2918 return ev.composedPath();
2919 }
2920
2921 var path = [];
2922 var el = ev.target;
2923
2924 while (el) {
2925 path.push(el);
2926 el = el.parentNode;
2927 }
2928 return path;
2929 }
2930
2931
2932 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
2933 // Gets normalized mouse position from a DOM event relative to the
2934 // `container` (border excluded) or to the whole page if not specified.
2935 function getMousePosition(e, container) {
2936 if (!container) {
2937 return new Point(e.clientX, e.clientY);
2938 }
2939
2940 var scale = getScale(container),
2941 offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y)
2942
2943 return new Point(
2944 // offset.left/top values are in page scale (like clientX/Y),
2945 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
2946 (e.clientX - offset.left) / scale.x - container.clientLeft,
2947 (e.clientY - offset.top) / scale.y - container.clientTop
2948 );
2949 }
2950
2951
2952 // except , Safari and
2953 // We need double the scroll pixels (see #7403 and #4538) for all Browsers
2954 // except OSX (Mac) -> 3x, Chrome running on Linux 1x
2955
2956 var wheelPxFactor =
2957 (Browser.linux && Browser.chrome) ? window.devicePixelRatio :
2958 Browser.mac ? window.devicePixelRatio * 3 :
2959 window.devicePixelRatio > 0 ? 2 * window.devicePixelRatio : 1;
2960 // @function getWheelDelta(ev: DOMEvent): Number
2961 // Gets normalized wheel delta from a wheel DOM event, in vertical
2962 // pixels scrolled (negative if scrolling down).
2963 // Events from pointing devices without precise scrolling are mapped to
2964 // a best guess of 60 pixels.
2965 function getWheelDelta(e) {
2966 return (Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
2967 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
2968 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
2969 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
2970 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
2971 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
2972 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
2973 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
2974 0;
2975 }
2976
2977 // check if element really left/entered the event target (for mouseenter/mouseleave)
2978 function isExternalTarget(el, e) {
2979
2980 var related = e.relatedTarget;
2981
2982 if (!related) { return true; }
2983
2984 try {
2985 while (related && (related !== el)) {
2986 related = related.parentNode;
2987 }
2988 } catch (err) {
2989 return false;
2990 }
2991 return (related !== el);
2992 }
2993
2994 var DomEvent = {
2995 __proto__: null,
2996 on: on,
2997 off: off,
2998 stopPropagation: stopPropagation,
2999 disableScrollPropagation: disableScrollPropagation,
3000 disableClickPropagation: disableClickPropagation,
3001 preventDefault: preventDefault,
3002 stop: stop,
3003 getPropagationPath: getPropagationPath,
3004 getMousePosition: getMousePosition,
3005 getWheelDelta: getWheelDelta,
3006 isExternalTarget: isExternalTarget,
3007 addListener: on,
3008 removeListener: off
3009 };
3010
3011 /*
3012 * @class PosAnimation
3013 * @aka L.PosAnimation
3014 * @inherits Evented
3015 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
3016 *
3017 * @example
3018 * ```js
3019 * var myPositionMarker = L.marker([48.864716, 2.294694]).addTo(map);
3020 *
3021 * myPositionMarker.on("click", function() {
3022 * var pos = map.latLngToLayerPoint(myPositionMarker.getLatLng());
3023 * pos.y -= 25;
3024 * var fx = new L.PosAnimation();
3025 *
3026 * fx.once('end',function() {
3027 * pos.y += 25;
3028 * fx.run(myPositionMarker._icon, pos, 0.8);
3029 * });
3030 *
3031 * fx.run(myPositionMarker._icon, pos, 0.3);
3032 * });
3033 *
3034 * ```
3035 *
3036 * @constructor L.PosAnimation()
3037 * Creates a `PosAnimation` object.
3038 *
3039 */
3040
3041 var PosAnimation = Evented.extend({
3042
3043 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
3044 // Run an animation of a given element to a new position, optionally setting
3045 // duration in seconds (`0.25` by default) and easing linearity factor (3rd
3046 // argument of the [cubic bezier curve](https://cubic-bezier.com/#0,0,.5,1),
3047 // `0.5` by default).
3048 run: function (el, newPos, duration, easeLinearity) {
3049 this.stop();
3050
3051 this._el = el;
3052 this._inProgress = true;
3053 this._duration = duration || 0.25;
3054 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
3055
3056 this._startPos = getPosition(el);
3057 this._offset = newPos.subtract(this._startPos);
3058 this._startTime = +new Date();
3059
3060 // @event start: Event
3061 // Fired when the animation starts
3062 this.fire('start');
3063
3064 this._animate();
3065 },
3066
3067 // @method stop()
3068 // Stops the animation (if currently running).
3069 stop: function () {
3070 if (!this._inProgress) { return; }
3071
3072 this._step(true);
3073 this._complete();
3074 },
3075
3076 _animate: function () {
3077 // animation loop
3078 this._animId = requestAnimFrame(this._animate, this);
3079 this._step();
3080 },
3081
3082 _step: function (round) {
3083 var elapsed = (+new Date()) - this._startTime,
3084 duration = this._duration * 1000;
3085
3086 if (elapsed < duration) {
3087 this._runFrame(this._easeOut(elapsed / duration), round);
3088 } else {
3089 this._runFrame(1);
3090 this._complete();
3091 }
3092 },
3093
3094 _runFrame: function (progress, round) {
3095 var pos = this._startPos.add(this._offset.multiplyBy(progress));
3096 if (round) {
3097 pos._round();
3098 }
3099 setPosition(this._el, pos);
3100
3101 // @event step: Event
3102 // Fired continuously during the animation.
3103 this.fire('step');
3104 },
3105
3106 _complete: function () {
3107 cancelAnimFrame(this._animId);
3108
3109 this._inProgress = false;
3110 // @event end: Event
3111 // Fired when the animation ends.
3112 this.fire('end');
3113 },
3114
3115 _easeOut: function (t) {
3116 return 1 - Math.pow(1 - t, this._easeOutPower);
3117 }
3118 });
3119
3120 /*
3121 * @class Map
3122 * @aka L.Map
3123 * @inherits Evented
3124 *
3125 * The central class of the API — it is used to create a map on a page and manipulate it.
3126 *
3127 * @example
3128 *
3129 * ```js
3130 * // initialize the map on the "map" div with a given center and zoom
3131 * var map = L.map('map', {
3132 * center: [51.505, -0.09],
3133 * zoom: 13
3134 * });
3135 * ```
3136 *
3137 */
3138
3139 var Map = Evented.extend({
3140
3141 options: {
3142 // @section Map State Options
3143 // @option crs: CRS = L.CRS.EPSG3857
3144 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
3145 // sure what it means.
3146 crs: EPSG3857,
3147
3148 // @option center: LatLng = undefined
3149 // Initial geographic center of the map
3150 center: undefined,
3151
3152 // @option zoom: Number = undefined
3153 // Initial map zoom level
3154 zoom: undefined,
3155
3156 // @option minZoom: Number = *
3157 // Minimum zoom level of the map.
3158 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3159 // the lowest of their `minZoom` options will be used instead.
3160 minZoom: undefined,
3161
3162 // @option maxZoom: Number = *
3163 // Maximum zoom level of the map.
3164 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3165 // the highest of their `maxZoom` options will be used instead.
3166 maxZoom: undefined,
3167
3168 // @option layers: Layer[] = []
3169 // Array of layers that will be added to the map initially
3170 layers: [],
3171
3172 // @option maxBounds: LatLngBounds = null
3173 // When this option is set, the map restricts the view to the given
3174 // geographical bounds, bouncing the user back if the user tries to pan
3175 // outside the view. To set the restriction dynamically, use
3176 // [`setMaxBounds`](#map-setmaxbounds) method.
3177 maxBounds: undefined,
3178
3179 // @option renderer: Renderer = *
3180 // The default method for drawing vector layers on the map. `L.SVG`
3181 // or `L.Canvas` by default depending on browser support.
3182 renderer: undefined,
3183
3184
3185 // @section Animation Options
3186 // @option zoomAnimation: Boolean = true
3187 // Whether the map zoom animation is enabled. By default it's enabled
3188 // in all browsers that support CSS3 Transitions except Android.
3189 zoomAnimation: true,
3190
3191 // @option zoomAnimationThreshold: Number = 4
3192 // Won't animate zoom if the zoom difference exceeds this value.
3193 zoomAnimationThreshold: 4,
3194
3195 // @option fadeAnimation: Boolean = true
3196 // Whether the tile fade animation is enabled. By default it's enabled
3197 // in all browsers that support CSS3 Transitions except Android.
3198 fadeAnimation: true,
3199
3200 // @option markerZoomAnimation: Boolean = true
3201 // Whether markers animate their zoom with the zoom animation, if disabled
3202 // they will disappear for the length of the animation. By default it's
3203 // enabled in all browsers that support CSS3 Transitions except Android.
3204 markerZoomAnimation: true,
3205
3206 // @option transform3DLimit: Number = 2^23
3207 // Defines the maximum size of a CSS translation transform. The default
3208 // value should not be changed unless a web browser positions layers in
3209 // the wrong place after doing a large `panBy`.
3210 transform3DLimit: 8388608, // Precision limit of a 32-bit float
3211
3212 // @section Interaction Options
3213 // @option zoomSnap: Number = 1
3214 // Forces the map's zoom level to always be a multiple of this, particularly
3215 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
3216 // By default, the zoom level snaps to the nearest integer; lower values
3217 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
3218 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
3219 zoomSnap: 1,
3220
3221 // @option zoomDelta: Number = 1
3222 // Controls how much the map's zoom level will change after a
3223 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
3224 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
3225 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
3226 zoomDelta: 1,
3227
3228 // @option trackResize: Boolean = true
3229 // Whether the map automatically handles browser window resize to update itself.
3230 trackResize: true
3231 },
3232
3233 initialize: function (id, options) { // (HTMLElement or String, Object)
3234 options = setOptions(this, options);
3235
3236 // Make sure to assign internal flags at the beginning,
3237 // to avoid inconsistent state in some edge cases.
3238 this._handlers = [];
3239 this._layers = {};
3240 this._zoomBoundLayers = {};
3241 this._sizeChanged = true;
3242
3243 this._initContainer(id);
3244 this._initLayout();
3245
3246 // hack for https://github.com/Leaflet/Leaflet/issues/1980
3247 this._onResize = bind(this._onResize, this);
3248
3249 this._initEvents();
3250
3251 if (options.maxBounds) {
3252 this.setMaxBounds(options.maxBounds);
3253 }
3254
3255 if (options.zoom !== undefined) {
3256 this._zoom = this._limitZoom(options.zoom);
3257 }
3258
3259 if (options.center && options.zoom !== undefined) {
3260 this.setView(toLatLng(options.center), options.zoom, {reset: true});
3261 }
3262
3263 this.callInitHooks();
3264
3265 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
3266 this._zoomAnimated = TRANSITION && Browser.any3d && !Browser.mobileOpera &&
3267 this.options.zoomAnimation;
3268
3269 // zoom transitions run with the same duration for all layers, so if one of transitionend events
3270 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
3271 if (this._zoomAnimated) {
3272 this._createAnimProxy();
3273 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
3274 }
3275
3276 this._addLayers(this.options.layers);
3277 },
3278
3279
3280 // @section Methods for modifying map state
3281
3282 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
3283 // Sets the view of the map (geographical center and zoom) with the given
3284 // animation options.
3285 setView: function (center, zoom, options) {
3286
3287 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
3288 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
3289 options = options || {};
3290
3291 this._stop();
3292
3293 if (this._loaded && !options.reset && options !== true) {
3294
3295 if (options.animate !== undefined) {
3296 options.zoom = extend({animate: options.animate}, options.zoom);
3297 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
3298 }
3299
3300 // try animating pan or zoom
3301 var moved = (this._zoom !== zoom) ?
3302 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
3303 this._tryAnimatedPan(center, options.pan);
3304
3305 if (moved) {
3306 // prevent resize handler call, the view will refresh after animation anyway
3307 clearTimeout(this._sizeTimer);
3308 return this;
3309 }
3310 }
3311
3312 // animation didn't start, just reset the map view
3313 this._resetView(center, zoom, options.pan && options.pan.noMoveStart);
3314
3315 return this;
3316 },
3317
3318 // @method setZoom(zoom: Number, options?: Zoom/pan options): this
3319 // Sets the zoom of the map.
3320 setZoom: function (zoom, options) {
3321 if (!this._loaded) {
3322 this._zoom = zoom;
3323 return this;
3324 }
3325 return this.setView(this.getCenter(), zoom, {zoom: options});
3326 },
3327
3328 // @method zoomIn(delta?: Number, options?: Zoom options): this
3329 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3330 zoomIn: function (delta, options) {
3331 delta = delta || (Browser.any3d ? this.options.zoomDelta : 1);
3332 return this.setZoom(this._zoom + delta, options);
3333 },
3334
3335 // @method zoomOut(delta?: Number, options?: Zoom options): this
3336 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3337 zoomOut: function (delta, options) {
3338 delta = delta || (Browser.any3d ? this.options.zoomDelta : 1);
3339 return this.setZoom(this._zoom - delta, options);
3340 },
3341
3342 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
3343 // Zooms the map while keeping a specified geographical point on the map
3344 // stationary (e.g. used internally for scroll zoom and double-click zoom).
3345 // @alternative
3346 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
3347 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
3348 setZoomAround: function (latlng, zoom, options) {
3349 var scale = this.getZoomScale(zoom),
3350 viewHalf = this.getSize().divideBy(2),
3351 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
3352
3353 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
3354 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
3355
3356 return this.setView(newCenter, zoom, {zoom: options});
3357 },
3358
3359 _getBoundsCenterZoom: function (bounds, options) {
3360
3361 options = options || {};
3362 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
3363
3364 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3365 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3366
3367 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
3368
3369 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
3370
3371 if (zoom === Infinity) {
3372 return {
3373 center: bounds.getCenter(),
3374 zoom: zoom
3375 };
3376 }
3377
3378 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
3379
3380 swPoint = this.project(bounds.getSouthWest(), zoom),
3381 nePoint = this.project(bounds.getNorthEast(), zoom),
3382 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
3383
3384 return {
3385 center: center,
3386 zoom: zoom
3387 };
3388 },
3389
3390 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
3391 // Sets a map view that contains the given geographical bounds with the
3392 // maximum zoom level possible.
3393 fitBounds: function (bounds, options) {
3394
3395 bounds = toLatLngBounds(bounds);
3396
3397 if (!bounds.isValid()) {
3398 throw new Error('Bounds are not valid.');
3399 }
3400
3401 var target = this._getBoundsCenterZoom(bounds, options);
3402 return this.setView(target.center, target.zoom, options);
3403 },
3404
3405 // @method fitWorld(options?: fitBounds options): this
3406 // Sets a map view that mostly contains the whole world with the maximum
3407 // zoom level possible.
3408 fitWorld: function (options) {
3409 return this.fitBounds([[-90, -180], [90, 180]], options);
3410 },
3411
3412 // @method panTo(latlng: LatLng, options?: Pan options): this
3413 // Pans the map to a given center.
3414 panTo: function (center, options) { // (LatLng)
3415 return this.setView(center, this._zoom, {pan: options});
3416 },
3417
3418 // @method panBy(offset: Point, options?: Pan options): this
3419 // Pans the map by a given number of pixels (animated).
3420 panBy: function (offset, options) {
3421 offset = toPoint(offset).round();
3422 options = options || {};
3423
3424 if (!offset.x && !offset.y) {
3425 return this.fire('moveend');
3426 }
3427 // If we pan too far, Chrome gets issues with tiles
3428 // and makes them disappear or appear in the wrong place (slightly offset) #2602
3429 if (options.animate !== true && !this.getSize().contains(offset)) {
3430 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
3431 return this;
3432 }
3433
3434 if (!this._panAnim) {
3435 this._panAnim = new PosAnimation();
3436
3437 this._panAnim.on({
3438 'step': this._onPanTransitionStep,
3439 'end': this._onPanTransitionEnd
3440 }, this);
3441 }
3442
3443 // don't fire movestart if animating inertia
3444 if (!options.noMoveStart) {
3445 this.fire('movestart');
3446 }
3447
3448 // animate pan unless animate: false specified
3449 if (options.animate !== false) {
3450 addClass(this._mapPane, 'leaflet-pan-anim');
3451
3452 var newPos = this._getMapPanePos().subtract(offset).round();
3453 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
3454 } else {
3455 this._rawPanBy(offset);
3456 this.fire('move').fire('moveend');
3457 }
3458
3459 return this;
3460 },
3461
3462 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
3463 // Sets the view of the map (geographical center and zoom) performing a smooth
3464 // pan-zoom animation.
3465 flyTo: function (targetCenter, targetZoom, options) {
3466
3467 options = options || {};
3468 if (options.animate === false || !Browser.any3d) {
3469 return this.setView(targetCenter, targetZoom, options);
3470 }
3471
3472 this._stop();
3473
3474 var from = this.project(this.getCenter()),
3475 to = this.project(targetCenter),
3476 size = this.getSize(),
3477 startZoom = this._zoom;
3478
3479 targetCenter = toLatLng(targetCenter);
3480 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
3481
3482 var w0 = Math.max(size.x, size.y),
3483 w1 = w0 * this.getZoomScale(startZoom, targetZoom),
3484 u1 = (to.distanceTo(from)) || 1,
3485 rho = 1.42,
3486 rho2 = rho * rho;
3487
3488 function r(i) {
3489 var s1 = i ? -1 : 1,
3490 s2 = i ? w1 : w0,
3491 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
3492 b1 = 2 * s2 * rho2 * u1,
3493 b = t1 / b1,
3494 sq = Math.sqrt(b * b + 1) - b;
3495
3496 // workaround for floating point precision bug when sq = 0, log = -Infinite,
3497 // thus triggering an infinite loop in flyTo
3498 var log = sq < 0.000000001 ? -18 : Math.log(sq);
3499
3500 return log;
3501 }
3502
3503 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
3504 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
3505 function tanh(n) { return sinh(n) / cosh(n); }
3506
3507 var r0 = r(0);
3508
3509 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
3510 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
3511
3512 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
3513
3514 var start = Date.now(),
3515 S = (r(1) - r0) / rho,
3516 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
3517
3518 function frame() {
3519 var t = (Date.now() - start) / duration,
3520 s = easeOut(t) * S;
3521
3522 if (t <= 1) {
3523 this._flyToFrame = requestAnimFrame(frame, this);
3524
3525 this._move(
3526 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
3527 this.getScaleZoom(w0 / w(s), startZoom),
3528 {flyTo: true});
3529
3530 } else {
3531 this
3532 ._move(targetCenter, targetZoom)
3533 ._moveEnd(true);
3534 }
3535 }
3536
3537 this._moveStart(true, options.noMoveStart);
3538
3539 frame.call(this);
3540 return this;
3541 },
3542
3543 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
3544 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
3545 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
3546 flyToBounds: function (bounds, options) {
3547 var target = this._getBoundsCenterZoom(bounds, options);
3548 return this.flyTo(target.center, target.zoom, options);
3549 },
3550
3551 // @method setMaxBounds(bounds: LatLngBounds): this
3552 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
3553 setMaxBounds: function (bounds) {
3554 bounds = toLatLngBounds(bounds);
3555
3556 if (this.listens('moveend', this._panInsideMaxBounds)) {
3557 this.off('moveend', this._panInsideMaxBounds);
3558 }
3559
3560 if (!bounds.isValid()) {
3561 this.options.maxBounds = null;
3562 return this;
3563 }
3564
3565 this.options.maxBounds = bounds;
3566
3567 if (this._loaded) {
3568 this._panInsideMaxBounds();
3569 }
3570
3571 return this.on('moveend', this._panInsideMaxBounds);
3572 },
3573
3574 // @method setMinZoom(zoom: Number): this
3575 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
3576 setMinZoom: function (zoom) {
3577 var oldZoom = this.options.minZoom;
3578 this.options.minZoom = zoom;
3579
3580 if (this._loaded && oldZoom !== zoom) {
3581 this.fire('zoomlevelschange');
3582
3583 if (this.getZoom() < this.options.minZoom) {
3584 return this.setZoom(zoom);
3585 }
3586 }
3587
3588 return this;
3589 },
3590
3591 // @method setMaxZoom(zoom: Number): this
3592 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
3593 setMaxZoom: function (zoom) {
3594 var oldZoom = this.options.maxZoom;
3595 this.options.maxZoom = zoom;
3596
3597 if (this._loaded && oldZoom !== zoom) {
3598 this.fire('zoomlevelschange');
3599
3600 if (this.getZoom() > this.options.maxZoom) {
3601 return this.setZoom(zoom);
3602 }
3603 }
3604
3605 return this;
3606 },
3607
3608 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
3609 // 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.
3610 panInsideBounds: function (bounds, options) {
3611 this._enforcingBounds = true;
3612 var center = this.getCenter(),
3613 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
3614
3615 if (!center.equals(newCenter)) {
3616 this.panTo(newCenter, options);
3617 }
3618
3619 this._enforcingBounds = false;
3620 return this;
3621 },
3622
3623 // @method panInside(latlng: LatLng, options?: padding options): this
3624 // Pans the map the minimum amount to make the `latlng` visible. Use
3625 // padding options to fit the display to more restricted bounds.
3626 // If `latlng` is already within the (optionally padded) display bounds,
3627 // the map will not be panned.
3628 panInside: function (latlng, options) {
3629 options = options || {};
3630
3631 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3632 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3633 pixelCenter = this.project(this.getCenter()),
3634 pixelPoint = this.project(latlng),
3635 pixelBounds = this.getPixelBounds(),
3636 paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]),
3637 paddedSize = paddedBounds.getSize();
3638
3639 if (!paddedBounds.contains(pixelPoint)) {
3640 this._enforcingBounds = true;
3641 var centerOffset = pixelPoint.subtract(paddedBounds.getCenter());
3642 var offset = paddedBounds.extend(pixelPoint).getSize().subtract(paddedSize);
3643 pixelCenter.x += centerOffset.x < 0 ? -offset.x : offset.x;
3644 pixelCenter.y += centerOffset.y < 0 ? -offset.y : offset.y;
3645 this.panTo(this.unproject(pixelCenter), options);
3646 this._enforcingBounds = false;
3647 }
3648 return this;
3649 },
3650
3651 // @method invalidateSize(options: Zoom/pan options): this
3652 // Checks if the map container size changed and updates the map if so —
3653 // call it after you've changed the map size dynamically, also animating
3654 // pan by default. If `options.pan` is `false`, panning will not occur.
3655 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
3656 // that it doesn't happen often even if the method is called many
3657 // times in a row.
3658
3659 // @alternative
3660 // @method invalidateSize(animate: Boolean): this
3661 // Checks if the map container size changed and updates the map if so —
3662 // call it after you've changed the map size dynamically, also animating
3663 // pan by default.
3664 invalidateSize: function (options) {
3665 if (!this._loaded) { return this; }
3666
3667 options = extend({
3668 animate: false,
3669 pan: true
3670 }, options === true ? {animate: true} : options);
3671
3672 var oldSize = this.getSize();
3673 this._sizeChanged = true;
3674 this._lastCenter = null;
3675
3676 var newSize = this.getSize(),
3677 oldCenter = oldSize.divideBy(2).round(),
3678 newCenter = newSize.divideBy(2).round(),
3679 offset = oldCenter.subtract(newCenter);
3680
3681 if (!offset.x && !offset.y) { return this; }
3682
3683 if (options.animate && options.pan) {
3684 this.panBy(offset);
3685
3686 } else {
3687 if (options.pan) {
3688 this._rawPanBy(offset);
3689 }
3690
3691 this.fire('move');
3692
3693 if (options.debounceMoveend) {
3694 clearTimeout(this._sizeTimer);
3695 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
3696 } else {
3697 this.fire('moveend');
3698 }
3699 }
3700
3701 // @section Map state change events
3702 // @event resize: ResizeEvent
3703 // Fired when the map is resized.
3704 return this.fire('resize', {
3705 oldSize: oldSize,
3706 newSize: newSize
3707 });
3708 },
3709
3710 // @section Methods for modifying map state
3711 // @method stop(): this
3712 // Stops the currently running `panTo` or `flyTo` animation, if any.
3713 stop: function () {
3714 this.setZoom(this._limitZoom(this._zoom));
3715 if (!this.options.zoomSnap) {
3716 this.fire('viewreset');
3717 }
3718 return this._stop();
3719 },
3720
3721 // @section Geolocation methods
3722 // @method locate(options?: Locate options): this
3723 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
3724 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
3725 // and optionally sets the map view to the user's location with respect to
3726 // detection accuracy (or to the world view if geolocation failed).
3727 // Note that, if your page doesn't use HTTPS, this method will fail in
3728 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
3729 // See `Locate options` for more details.
3730 locate: function (options) {
3731
3732 options = this._locateOptions = extend({
3733 timeout: 10000,
3734 watch: false
3735 // setView: false
3736 // maxZoom: <Number>
3737 // maximumAge: 0
3738 // enableHighAccuracy: false
3739 }, options);
3740
3741 if (!('geolocation' in navigator)) {
3742 this._handleGeolocationError({
3743 code: 0,
3744 message: 'Geolocation not supported.'
3745 });
3746 return this;
3747 }
3748
3749 var onResponse = bind(this._handleGeolocationResponse, this),
3750 onError = bind(this._handleGeolocationError, this);
3751
3752 if (options.watch) {
3753 this._locationWatchId =
3754 navigator.geolocation.watchPosition(onResponse, onError, options);
3755 } else {
3756 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
3757 }
3758 return this;
3759 },
3760
3761 // @method stopLocate(): this
3762 // Stops watching location previously initiated by `map.locate({watch: true})`
3763 // and aborts resetting the map view if map.locate was called with
3764 // `{setView: true}`.
3765 stopLocate: function () {
3766 if (navigator.geolocation && navigator.geolocation.clearWatch) {
3767 navigator.geolocation.clearWatch(this._locationWatchId);
3768 }
3769 if (this._locateOptions) {
3770 this._locateOptions.setView = false;
3771 }
3772 return this;
3773 },
3774
3775 _handleGeolocationError: function (error) {
3776 if (!this._container._leaflet_id) { return; }
3777
3778 var c = error.code,
3779 message = error.message ||
3780 (c === 1 ? 'permission denied' :
3781 (c === 2 ? 'position unavailable' : 'timeout'));
3782
3783 if (this._locateOptions.setView && !this._loaded) {
3784 this.fitWorld();
3785 }
3786
3787 // @section Location events
3788 // @event locationerror: ErrorEvent
3789 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
3790 this.fire('locationerror', {
3791 code: c,
3792 message: 'Geolocation error: ' + message + '.'
3793 });
3794 },
3795
3796 _handleGeolocationResponse: function (pos) {
3797 if (!this._container._leaflet_id) { return; }
3798
3799 var lat = pos.coords.latitude,
3800 lng = pos.coords.longitude,
3801 latlng = new LatLng(lat, lng),
3802 bounds = latlng.toBounds(pos.coords.accuracy * 2),
3803 options = this._locateOptions;
3804
3805 if (options.setView) {
3806 var zoom = this.getBoundsZoom(bounds);
3807 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
3808 }
3809
3810 var data = {
3811 latlng: latlng,
3812 bounds: bounds,
3813 timestamp: pos.timestamp
3814 };
3815
3816 for (var i in pos.coords) {
3817 if (typeof pos.coords[i] === 'number') {
3818 data[i] = pos.coords[i];
3819 }
3820 }
3821
3822 // @event locationfound: LocationEvent
3823 // Fired when geolocation (using the [`locate`](#map-locate) method)
3824 // went successfully.
3825 this.fire('locationfound', data);
3826 },
3827
3828 // TODO Appropriate docs section?
3829 // @section Other Methods
3830 // @method addHandler(name: String, HandlerClass: Function): this
3831 // Adds a new `Handler` to the map, given its name and constructor function.
3832 addHandler: function (name, HandlerClass) {
3833 if (!HandlerClass) { return this; }
3834
3835 var handler = this[name] = new HandlerClass(this);
3836
3837 this._handlers.push(handler);
3838
3839 if (this.options[name]) {
3840 handler.enable();
3841 }
3842
3843 return this;
3844 },
3845
3846 // @method remove(): this
3847 // Destroys the map and clears all related event listeners.
3848 remove: function () {
3849
3850 this._initEvents(true);
3851 if (this.options.maxBounds) { this.off('moveend', this._panInsideMaxBounds); }
3852
3853 if (this._containerId !== this._container._leaflet_id) {
3854 throw new Error('Map container is being reused by another instance');
3855 }
3856
3857 try {
3858 // throws error in IE6-8
3859 delete this._container._leaflet_id;
3860 delete this._containerId;
3861 } catch (e) {
3862 /*eslint-disable */
3863 this._container._leaflet_id = undefined;
3864 /* eslint-enable */
3865 this._containerId = undefined;
3866 }
3867
3868 if (this._locationWatchId !== undefined) {
3869 this.stopLocate();
3870 }
3871
3872 this._stop();
3873
3874 remove(this._mapPane);
3875
3876 if (this._clearControlPos) {
3877 this._clearControlPos();
3878 }
3879 if (this._resizeRequest) {
3880 cancelAnimFrame(this._resizeRequest);
3881 this._resizeRequest = null;
3882 }
3883
3884 this._clearHandlers();
3885
3886 if (this._loaded) {
3887 // @section Map state change events
3888 // @event unload: Event
3889 // Fired when the map is destroyed with [remove](#map-remove) method.
3890 this.fire('unload');
3891 }
3892
3893 var i;
3894 for (i in this._layers) {
3895 this._layers[i].remove();
3896 }
3897 for (i in this._panes) {
3898 remove(this._panes[i]);
3899 }
3900
3901 this._layers = [];
3902 this._panes = [];
3903 delete this._mapPane;
3904 delete this._renderer;
3905
3906 return this;
3907 },
3908
3909 // @section Other Methods
3910 // @method createPane(name: String, container?: HTMLElement): HTMLElement
3911 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
3912 // then returns it. The pane is created as a child of `container`, or
3913 // as a child of the main map pane if not set.
3914 createPane: function (name, container) {
3915 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3916 pane = create$1('div', className, container || this._mapPane);
3917
3918 if (name) {
3919 this._panes[name] = pane;
3920 }
3921 return pane;
3922 },
3923
3924 // @section Methods for Getting Map State
3925
3926 // @method getCenter(): LatLng
3927 // Returns the geographical center of the map view
3928 getCenter: function () {
3929 this._checkIfLoaded();
3930
3931 if (this._lastCenter && !this._moved()) {
3932 return this._lastCenter.clone();
3933 }
3934 return this.layerPointToLatLng(this._getCenterLayerPoint());
3935 },
3936
3937 // @method getZoom(): Number
3938 // Returns the current zoom level of the map view
3939 getZoom: function () {
3940 return this._zoom;
3941 },
3942
3943 // @method getBounds(): LatLngBounds
3944 // Returns the geographical bounds visible in the current map view
3945 getBounds: function () {
3946 var bounds = this.getPixelBounds(),
3947 sw = this.unproject(bounds.getBottomLeft()),
3948 ne = this.unproject(bounds.getTopRight());
3949
3950 return new LatLngBounds(sw, ne);
3951 },
3952
3953 // @method getMinZoom(): Number
3954 // 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.
3955 getMinZoom: function () {
3956 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3957 },
3958
3959 // @method getMaxZoom(): Number
3960 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3961 getMaxZoom: function () {
3962 return this.options.maxZoom === undefined ?
3963 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3964 this.options.maxZoom;
3965 },
3966
3967 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
3968 // Returns the maximum zoom level on which the given bounds fit to the map
3969 // view in its entirety. If `inside` (optional) is set to `true`, the method
3970 // instead returns the minimum zoom level on which the map view fits into
3971 // the given bounds in its entirety.
3972 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3973 bounds = toLatLngBounds(bounds);
3974 padding = toPoint(padding || [0, 0]);
3975
3976 var zoom = this.getZoom() || 0,
3977 min = this.getMinZoom(),
3978 max = this.getMaxZoom(),
3979 nw = bounds.getNorthWest(),
3980 se = bounds.getSouthEast(),
3981 size = this.getSize().subtract(padding),
3982 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3983 snap = Browser.any3d ? this.options.zoomSnap : 1,
3984 scalex = size.x / boundsSize.x,
3985 scaley = size.y / boundsSize.y,
3986 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
3987
3988 zoom = this.getScaleZoom(scale, zoom);
3989
3990 if (snap) {
3991 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3992 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3993 }
3994
3995 return Math.max(min, Math.min(max, zoom));
3996 },
3997
3998 // @method getSize(): Point
3999 // Returns the current size of the map container (in pixels).
4000 getSize: function () {
4001 if (!this._size || this._sizeChanged) {
4002 this._size = new Point(
4003 this._container.clientWidth || 0,
4004 this._container.clientHeight || 0);
4005
4006 this._sizeChanged = false;
4007 }
4008 return this._size.clone();
4009 },
4010
4011 // @method getPixelBounds(): Bounds
4012 // Returns the bounds of the current map view in projected pixel
4013 // coordinates (sometimes useful in layer and overlay implementations).
4014 getPixelBounds: function (center, zoom) {
4015 var topLeftPoint = this._getTopLeftPoint(center, zoom);
4016 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
4017 },
4018
4019 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
4020 // the map pane? "left point of the map layer" can be confusing, specially
4021 // since there can be negative offsets.
4022 // @method getPixelOrigin(): Point
4023 // Returns the projected pixel coordinates of the top left point of
4024 // the map layer (useful in custom layer and overlay implementations).
4025 getPixelOrigin: function () {
4026 this._checkIfLoaded();
4027 return this._pixelOrigin;
4028 },
4029
4030 // @method getPixelWorldBounds(zoom?: Number): Bounds
4031 // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
4032 // If `zoom` is omitted, the map's current zoom level is used.
4033 getPixelWorldBounds: function (zoom) {
4034 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
4035 },
4036
4037 // @section Other Methods
4038
4039 // @method getPane(pane: String|HTMLElement): HTMLElement
4040 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
4041 getPane: function (pane) {
4042 return typeof pane === 'string' ? this._panes[pane] : pane;
4043 },
4044
4045 // @method getPanes(): Object
4046 // Returns a plain object containing the names of all [panes](#map-pane) as keys and
4047 // the panes as values.
4048 getPanes: function () {
4049 return this._panes;
4050 },
4051
4052 // @method getContainer: HTMLElement
4053 // Returns the HTML element that contains the map.
4054 getContainer: function () {
4055 return this._container;
4056 },
4057
4058
4059 // @section Conversion Methods
4060
4061 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
4062 // Returns the scale factor to be applied to a map transition from zoom level
4063 // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
4064 getZoomScale: function (toZoom, fromZoom) {
4065 // TODO replace with universal implementation after refactoring projections
4066 var crs = this.options.crs;
4067 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
4068 return crs.scale(toZoom) / crs.scale(fromZoom);
4069 },
4070
4071 // @method getScaleZoom(scale: Number, fromZoom: Number): Number
4072 // Returns the zoom level that the map would end up at, if it is at `fromZoom`
4073 // level and everything is scaled by a factor of `scale`. Inverse of
4074 // [`getZoomScale`](#map-getZoomScale).
4075 getScaleZoom: function (scale, fromZoom) {
4076 var crs = this.options.crs;
4077 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
4078 var zoom = crs.zoom(scale * crs.scale(fromZoom));
4079 return isNaN(zoom) ? Infinity : zoom;
4080 },
4081
4082 // @method project(latlng: LatLng, zoom: Number): Point
4083 // Projects a geographical coordinate `LatLng` according to the projection
4084 // of the map's CRS, then scales it according to `zoom` and the CRS's
4085 // `Transformation`. The result is pixel coordinate relative to
4086 // the CRS origin.
4087 project: function (latlng, zoom) {
4088 zoom = zoom === undefined ? this._zoom : zoom;
4089 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
4090 },
4091
4092 // @method unproject(point: Point, zoom: Number): LatLng
4093 // Inverse of [`project`](#map-project).
4094 unproject: function (point, zoom) {
4095 zoom = zoom === undefined ? this._zoom : zoom;
4096 return this.options.crs.pointToLatLng(toPoint(point), zoom);
4097 },
4098
4099 // @method layerPointToLatLng(point: Point): LatLng
4100 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
4101 // returns the corresponding geographical coordinate (for the current zoom level).
4102 layerPointToLatLng: function (point) {
4103 var projectedPoint = toPoint(point).add(this.getPixelOrigin());
4104 return this.unproject(projectedPoint);
4105 },
4106
4107 // @method latLngToLayerPoint(latlng: LatLng): Point
4108 // Given a geographical coordinate, returns the corresponding pixel coordinate
4109 // relative to the [origin pixel](#map-getpixelorigin).
4110 latLngToLayerPoint: function (latlng) {
4111 var projectedPoint = this.project(toLatLng(latlng))._round();
4112 return projectedPoint._subtract(this.getPixelOrigin());
4113 },
4114
4115 // @method wrapLatLng(latlng: LatLng): LatLng
4116 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
4117 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
4118 // CRS's bounds.
4119 // By default this means longitude is wrapped around the dateline so its
4120 // value is between -180 and +180 degrees.
4121 wrapLatLng: function (latlng) {
4122 return this.options.crs.wrapLatLng(toLatLng(latlng));
4123 },
4124
4125 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
4126 // Returns a `LatLngBounds` with the same size as the given one, ensuring that
4127 // its center is within the CRS's bounds.
4128 // By default this means the center longitude is wrapped around the dateline so its
4129 // value is between -180 and +180 degrees, and the majority of the bounds
4130 // overlaps the CRS's bounds.
4131 wrapLatLngBounds: function (latlng) {
4132 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
4133 },
4134
4135 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
4136 // Returns the distance between two geographical coordinates according to
4137 // the map's CRS. By default this measures distance in meters.
4138 distance: function (latlng1, latlng2) {
4139 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
4140 },
4141
4142 // @method containerPointToLayerPoint(point: Point): Point
4143 // Given a pixel coordinate relative to the map container, returns the corresponding
4144 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
4145 containerPointToLayerPoint: function (point) { // (Point)
4146 return toPoint(point).subtract(this._getMapPanePos());
4147 },
4148
4149 // @method layerPointToContainerPoint(point: Point): Point
4150 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
4151 // returns the corresponding pixel coordinate relative to the map container.
4152 layerPointToContainerPoint: function (point) { // (Point)
4153 return toPoint(point).add(this._getMapPanePos());
4154 },
4155
4156 // @method containerPointToLatLng(point: Point): LatLng
4157 // Given a pixel coordinate relative to the map container, returns
4158 // the corresponding geographical coordinate (for the current zoom level).
4159 containerPointToLatLng: function (point) {
4160 var layerPoint = this.containerPointToLayerPoint(toPoint(point));
4161 return this.layerPointToLatLng(layerPoint);
4162 },
4163
4164 // @method latLngToContainerPoint(latlng: LatLng): Point
4165 // Given a geographical coordinate, returns the corresponding pixel coordinate
4166 // relative to the map container.
4167 latLngToContainerPoint: function (latlng) {
4168 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
4169 },
4170
4171 // @method mouseEventToContainerPoint(ev: MouseEvent): Point
4172 // Given a MouseEvent object, returns the pixel coordinate relative to the
4173 // map container where the event took place.
4174 mouseEventToContainerPoint: function (e) {
4175 return getMousePosition(e, this._container);
4176 },
4177
4178 // @method mouseEventToLayerPoint(ev: MouseEvent): Point
4179 // Given a MouseEvent object, returns the pixel coordinate relative to
4180 // the [origin pixel](#map-getpixelorigin) where the event took place.
4181 mouseEventToLayerPoint: function (e) {
4182 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
4183 },
4184
4185 // @method mouseEventToLatLng(ev: MouseEvent): LatLng
4186 // Given a MouseEvent object, returns geographical coordinate where the
4187 // event took place.
4188 mouseEventToLatLng: function (e) { // (MouseEvent)
4189 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
4190 },
4191
4192
4193 // map initialization methods
4194
4195 _initContainer: function (id) {
4196 var container = this._container = get(id);
4197
4198 if (!container) {
4199 throw new Error('Map container not found.');
4200 } else if (container._leaflet_id) {
4201 throw new Error('Map container is already initialized.');
4202 }
4203
4204 on(container, 'scroll', this._onScroll, this);
4205 this._containerId = stamp(container);
4206 },
4207
4208 _initLayout: function () {
4209 var container = this._container;
4210
4211 this._fadeAnimated = this.options.fadeAnimation && Browser.any3d;
4212
4213 addClass(container, 'leaflet-container' +
4214 (Browser.touch ? ' leaflet-touch' : '') +
4215 (Browser.retina ? ' leaflet-retina' : '') +
4216 (Browser.ielt9 ? ' leaflet-oldie' : '') +
4217 (Browser.safari ? ' leaflet-safari' : '') +
4218 (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
4219
4220 var position = getStyle(container, 'position');
4221
4222 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed' && position !== 'sticky') {
4223 container.style.position = 'relative';
4224 }
4225
4226 this._initPanes();
4227
4228 if (this._initControlPos) {
4229 this._initControlPos();
4230 }
4231 },
4232
4233 _initPanes: function () {
4234 var panes = this._panes = {};
4235 this._paneRenderers = {};
4236
4237 // @section
4238 //
4239 // Panes are DOM elements used to control the ordering of layers on the map. You
4240 // can access panes with [`map.getPane`](#map-getpane) or
4241 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
4242 // [`map.createPane`](#map-createpane) method.
4243 //
4244 // Every map has the following default panes that differ only in zIndex.
4245 //
4246 // @pane mapPane: HTMLElement = 'auto'
4247 // Pane that contains all other map panes
4248
4249 this._mapPane = this.createPane('mapPane', this._container);
4250 setPosition(this._mapPane, new Point(0, 0));
4251
4252 // @pane tilePane: HTMLElement = 200
4253 // Pane for `GridLayer`s and `TileLayer`s
4254 this.createPane('tilePane');
4255 // @pane overlayPane: HTMLElement = 400
4256 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
4257 this.createPane('overlayPane');
4258 // @pane shadowPane: HTMLElement = 500
4259 // Pane for overlay shadows (e.g. `Marker` shadows)
4260 this.createPane('shadowPane');
4261 // @pane markerPane: HTMLElement = 600
4262 // Pane for `Icon`s of `Marker`s
4263 this.createPane('markerPane');
4264 // @pane tooltipPane: HTMLElement = 650
4265 // Pane for `Tooltip`s.
4266 this.createPane('tooltipPane');
4267 // @pane popupPane: HTMLElement = 700
4268 // Pane for `Popup`s.
4269 this.createPane('popupPane');
4270
4271 if (!this.options.markerZoomAnimation) {
4272 addClass(panes.markerPane, 'leaflet-zoom-hide');
4273 addClass(panes.shadowPane, 'leaflet-zoom-hide');
4274 }
4275 },
4276
4277
4278 // private methods that modify map state
4279
4280 // @section Map state change events
4281 _resetView: function (center, zoom, noMoveStart) {
4282 setPosition(this._mapPane, new Point(0, 0));
4283
4284 var loading = !this._loaded;
4285 this._loaded = true;
4286 zoom = this._limitZoom(zoom);
4287
4288 this.fire('viewprereset');
4289
4290 var zoomChanged = this._zoom !== zoom;
4291 this
4292 ._moveStart(zoomChanged, noMoveStart)
4293 ._move(center, zoom)
4294 ._moveEnd(zoomChanged);
4295
4296 // @event viewreset: Event
4297 // Fired when the map needs to redraw its content (this usually happens
4298 // on map zoom or load). Very useful for creating custom overlays.
4299 this.fire('viewreset');
4300
4301 // @event load: Event
4302 // Fired when the map is initialized (when its center and zoom are set
4303 // for the first time).
4304 if (loading) {
4305 this.fire('load');
4306 }
4307 },
4308
4309 _moveStart: function (zoomChanged, noMoveStart) {
4310 // @event zoomstart: Event
4311 // Fired when the map zoom is about to change (e.g. before zoom animation).
4312 // @event movestart: Event
4313 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
4314 if (zoomChanged) {
4315 this.fire('zoomstart');
4316 }
4317 if (!noMoveStart) {
4318 this.fire('movestart');
4319 }
4320 return this;
4321 },
4322
4323 _move: function (center, zoom, data, supressEvent) {
4324 if (zoom === undefined) {
4325 zoom = this._zoom;
4326 }
4327 var zoomChanged = this._zoom !== zoom;
4328
4329 this._zoom = zoom;
4330 this._lastCenter = center;
4331 this._pixelOrigin = this._getNewPixelOrigin(center);
4332
4333 if (!supressEvent) {
4334 // @event zoom: Event
4335 // Fired repeatedly during any change in zoom level,
4336 // including zoom and fly animations.
4337 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530
4338 this.fire('zoom', data);
4339 }
4340
4341 // @event move: Event
4342 // Fired repeatedly during any movement of the map,
4343 // including pan and fly animations.
4344 this.fire('move', data);
4345 } else if (data && data.pinch) { // Always fire 'zoom' if pinching because #3530
4346 this.fire('zoom', data);
4347 }
4348 return this;
4349 },
4350
4351 _moveEnd: function (zoomChanged) {
4352 // @event zoomend: Event
4353 // Fired when the map zoom changed, after any animations.
4354 if (zoomChanged) {
4355 this.fire('zoomend');
4356 }
4357
4358 // @event moveend: Event
4359 // Fired when the center of the map stops changing
4360 // (e.g. user stopped dragging the map or after non-centered zoom).
4361 return this.fire('moveend');
4362 },
4363
4364 _stop: function () {
4365 cancelAnimFrame(this._flyToFrame);
4366 if (this._panAnim) {
4367 this._panAnim.stop();
4368 }
4369 return this;
4370 },
4371
4372 _rawPanBy: function (offset) {
4373 setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
4374 },
4375
4376 _getZoomSpan: function () {
4377 return this.getMaxZoom() - this.getMinZoom();
4378 },
4379
4380 _panInsideMaxBounds: function () {
4381 if (!this._enforcingBounds) {
4382 this.panInsideBounds(this.options.maxBounds);
4383 }
4384 },
4385
4386 _checkIfLoaded: function () {
4387 if (!this._loaded) {
4388 throw new Error('Set map center and zoom first.');
4389 }
4390 },
4391
4392 // DOM event handling
4393
4394 // @section Interaction events
4395 _initEvents: function (remove) {
4396 this._targets = {};
4397 this._targets[stamp(this._container)] = this;
4398
4399 var onOff = remove ? off : on;
4400
4401 // @event click: MouseEvent
4402 // Fired when the user clicks (or taps) the map.
4403 // @event dblclick: MouseEvent
4404 // Fired when the user double-clicks (or double-taps) the map.
4405 // @event mousedown: MouseEvent
4406 // Fired when the user pushes the mouse button on the map.
4407 // @event mouseup: MouseEvent
4408 // Fired when the user releases the mouse button on the map.
4409 // @event mouseover: MouseEvent
4410 // Fired when the mouse enters the map.
4411 // @event mouseout: MouseEvent
4412 // Fired when the mouse leaves the map.
4413 // @event mousemove: MouseEvent
4414 // Fired while the mouse moves over the map.
4415 // @event contextmenu: MouseEvent
4416 // Fired when the user pushes the right mouse button on the map, prevents
4417 // default browser context menu from showing if there are listeners on
4418 // this event. Also fired on mobile when the user holds a single touch
4419 // for a second (also called long press).
4420 // @event keypress: KeyboardEvent
4421 // Fired when the user presses a key from the keyboard that produces a character value while the map is focused.
4422 // @event keydown: KeyboardEvent
4423 // Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event,
4424 // the `keydown` event is fired for keys that produce a character value and for keys
4425 // that do not produce a character value.
4426 // @event keyup: KeyboardEvent
4427 // Fired when the user releases a key from the keyboard while the map is focused.
4428 onOff(this._container, 'click dblclick mousedown mouseup ' +
4429 'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this);
4430
4431 if (this.options.trackResize) {
4432 onOff(window, 'resize', this._onResize, this);
4433 }
4434
4435 if (Browser.any3d && this.options.transform3DLimit) {
4436 (remove ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
4437 }
4438 },
4439
4440 _onResize: function () {
4441 cancelAnimFrame(this._resizeRequest);
4442 this._resizeRequest = requestAnimFrame(
4443 function () { this.invalidateSize({debounceMoveend: true}); }, this);
4444 },
4445
4446 _onScroll: function () {
4447 this._container.scrollTop = 0;
4448 this._container.scrollLeft = 0;
4449 },
4450
4451 _onMoveEnd: function () {
4452 var pos = this._getMapPanePos();
4453 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
4454 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
4455 // a pixel offset on very high values, see: https://jsfiddle.net/dg6r5hhb/
4456 this._resetView(this.getCenter(), this.getZoom());
4457 }
4458 },
4459
4460 _findEventTargets: function (e, type) {
4461 var targets = [],
4462 target,
4463 isHover = type === 'mouseout' || type === 'mouseover',
4464 src = e.target || e.srcElement,
4465 dragging = false;
4466
4467 while (src) {
4468 target = this._targets[stamp(src)];
4469 if (target && (type === 'click' || type === 'preclick') && this._draggableMoved(target)) {
4470 // Prevent firing click after you just dragged an object.
4471 dragging = true;
4472 break;
4473 }
4474 if (target && target.listens(type, true)) {
4475 if (isHover && !isExternalTarget(src, e)) { break; }
4476 targets.push(target);
4477 if (isHover) { break; }
4478 }
4479 if (src === this._container) { break; }
4480 src = src.parentNode;
4481 }
4482 if (!targets.length && !dragging && !isHover && this.listens(type, true)) {
4483 targets = [this];
4484 }
4485 return targets;
4486 },
4487
4488 _isClickDisabled: function (el) {
4489 while (el && el !== this._container) {
4490 if (el['_leaflet_disable_click']) { return true; }
4491 el = el.parentNode;
4492 }
4493 },
4494
4495 _handleDOMEvent: function (e) {
4496 var el = (e.target || e.srcElement);
4497 if (!this._loaded || el['_leaflet_disable_events'] || e.type === 'click' && this._isClickDisabled(el)) {
4498 return;
4499 }
4500
4501 var type = e.type;
4502
4503 if (type === 'mousedown') {
4504 // prevents outline when clicking on keyboard-focusable element
4505 preventOutline(el);
4506 }
4507
4508 this._fireDOMEvent(e, type);
4509 },
4510
4511 _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
4512
4513 _fireDOMEvent: function (e, type, canvasTargets) {
4514
4515 if (e.type === 'click') {
4516 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
4517 // @event preclick: MouseEvent
4518 // Fired before mouse click on the map (sometimes useful when you
4519 // want something to happen on click before any existing click
4520 // handlers start running).
4521 var synth = extend({}, e);
4522 synth.type = 'preclick';
4523 this._fireDOMEvent(synth, synth.type, canvasTargets);
4524 }
4525
4526 // Find the layer the event is propagating from and its parents.
4527 var targets = this._findEventTargets(e, type);
4528
4529 if (canvasTargets) {
4530 var filtered = []; // pick only targets with listeners
4531 for (var i = 0; i < canvasTargets.length; i++) {
4532 if (canvasTargets[i].listens(type, true)) {
4533 filtered.push(canvasTargets[i]);
4534 }
4535 }
4536 targets = filtered.concat(targets);
4537 }
4538
4539 if (!targets.length) { return; }
4540
4541 if (type === 'contextmenu') {
4542 preventDefault(e);
4543 }
4544
4545 var target = targets[0];
4546 var data = {
4547 originalEvent: e
4548 };
4549
4550 if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') {
4551 var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
4552 data.containerPoint = isMarker ?
4553 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
4554 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
4555 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
4556 }
4557
4558 for (i = 0; i < targets.length; i++) {
4559 targets[i].fire(type, data, true);
4560 if (data.originalEvent._stopped ||
4561 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
4562 }
4563 },
4564
4565 _draggableMoved: function (obj) {
4566 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
4567 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
4568 },
4569
4570 _clearHandlers: function () {
4571 for (var i = 0, len = this._handlers.length; i < len; i++) {
4572 this._handlers[i].disable();
4573 }
4574 },
4575
4576 // @section Other Methods
4577
4578 // @method whenReady(fn: Function, context?: Object): this
4579 // Runs the given function `fn` when the map gets initialized with
4580 // a view (center and zoom) and at least one layer, or immediately
4581 // if it's already initialized, optionally passing a function context.
4582 whenReady: function (callback, context) {
4583 if (this._loaded) {
4584 callback.call(context || this, {target: this});
4585 } else {
4586 this.on('load', callback, context);
4587 }
4588 return this;
4589 },
4590
4591
4592 // private methods for getting map state
4593
4594 _getMapPanePos: function () {
4595 return getPosition(this._mapPane) || new Point(0, 0);
4596 },
4597
4598 _moved: function () {
4599 var pos = this._getMapPanePos();
4600 return pos && !pos.equals([0, 0]);
4601 },
4602
4603 _getTopLeftPoint: function (center, zoom) {
4604 var pixelOrigin = center && zoom !== undefined ?
4605 this._getNewPixelOrigin(center, zoom) :
4606 this.getPixelOrigin();
4607 return pixelOrigin.subtract(this._getMapPanePos());
4608 },
4609
4610 _getNewPixelOrigin: function (center, zoom) {
4611 var viewHalf = this.getSize()._divideBy(2);
4612 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
4613 },
4614
4615 _latLngToNewLayerPoint: function (latlng, zoom, center) {
4616 var topLeft = this._getNewPixelOrigin(center, zoom);
4617 return this.project(latlng, zoom)._subtract(topLeft);
4618 },
4619
4620 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
4621 var topLeft = this._getNewPixelOrigin(center, zoom);
4622 return toBounds([
4623 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
4624 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
4625 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
4626 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
4627 ]);
4628 },
4629
4630 // layer point of the current center
4631 _getCenterLayerPoint: function () {
4632 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
4633 },
4634
4635 // offset of the specified place to the current center in pixels
4636 _getCenterOffset: function (latlng) {
4637 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
4638 },
4639
4640 // adjust center for view to get inside bounds
4641 _limitCenter: function (center, zoom, bounds) {
4642
4643 if (!bounds) { return center; }
4644
4645 var centerPoint = this.project(center, zoom),
4646 viewHalf = this.getSize().divideBy(2),
4647 viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
4648 offset = this._getBoundsOffset(viewBounds, bounds, zoom);
4649
4650 // If offset is less than a pixel, ignore.
4651 // This prevents unstable projections from getting into
4652 // an infinite loop of tiny offsets.
4653 if (Math.abs(offset.x) <= 1 && Math.abs(offset.y) <= 1) {
4654 return center;
4655 }
4656
4657 return this.unproject(centerPoint.add(offset), zoom);
4658 },
4659
4660 // adjust offset for view to get inside bounds
4661 _limitOffset: function (offset, bounds) {
4662 if (!bounds) { return offset; }
4663
4664 var viewBounds = this.getPixelBounds(),
4665 newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
4666
4667 return offset.add(this._getBoundsOffset(newBounds, bounds));
4668 },
4669
4670 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
4671 _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
4672 var projectedMaxBounds = toBounds(
4673 this.project(maxBounds.getNorthEast(), zoom),
4674 this.project(maxBounds.getSouthWest(), zoom)
4675 ),
4676 minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
4677 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
4678
4679 dx = this._rebound(minOffset.x, -maxOffset.x),
4680 dy = this._rebound(minOffset.y, -maxOffset.y);
4681
4682 return new Point(dx, dy);
4683 },
4684
4685 _rebound: function (left, right) {
4686 return left + right > 0 ?
4687 Math.round(left - right) / 2 :
4688 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
4689 },
4690
4691 _limitZoom: function (zoom) {
4692 var min = this.getMinZoom(),
4693 max = this.getMaxZoom(),
4694 snap = Browser.any3d ? this.options.zoomSnap : 1;
4695 if (snap) {
4696 zoom = Math.round(zoom / snap) * snap;
4697 }
4698 return Math.max(min, Math.min(max, zoom));
4699 },
4700
4701 _onPanTransitionStep: function () {
4702 this.fire('move');
4703 },
4704
4705 _onPanTransitionEnd: function () {
4706 removeClass(this._mapPane, 'leaflet-pan-anim');
4707 this.fire('moveend');
4708 },
4709
4710 _tryAnimatedPan: function (center, options) {
4711 // difference between the new and current centers in pixels
4712 var offset = this._getCenterOffset(center)._trunc();
4713
4714 // don't animate too far unless animate: true specified in options
4715 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
4716
4717 this.panBy(offset, options);
4718
4719 return true;
4720 },
4721
4722 _createAnimProxy: function () {
4723
4724 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
4725 this._panes.mapPane.appendChild(proxy);
4726
4727 this.on('zoomanim', function (e) {
4728 var prop = TRANSFORM,
4729 transform = this._proxy.style[prop];
4730
4731 setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
4732
4733 // workaround for case when transform is the same and so transitionend event is not fired
4734 if (transform === this._proxy.style[prop] && this._animatingZoom) {
4735 this._onZoomTransitionEnd();
4736 }
4737 }, this);
4738
4739 this.on('load moveend', this._animMoveEnd, this);
4740
4741 this._on('unload', this._destroyAnimProxy, this);
4742 },
4743
4744 _destroyAnimProxy: function () {
4745 remove(this._proxy);
4746 this.off('load moveend', this._animMoveEnd, this);
4747 delete this._proxy;
4748 },
4749
4750 _animMoveEnd: function () {
4751 var c = this.getCenter(),
4752 z = this.getZoom();
4753 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
4754 },
4755
4756 _catchTransitionEnd: function (e) {
4757 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
4758 this._onZoomTransitionEnd();
4759 }
4760 },
4761
4762 _nothingToAnimate: function () {
4763 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
4764 },
4765
4766 _tryAnimatedZoom: function (center, zoom, options) {
4767
4768 if (this._animatingZoom) { return true; }
4769
4770 options = options || {};
4771
4772 // don't animate if disabled, not supported or zoom difference is too large
4773 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
4774 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
4775
4776 // offset is the pixel coords of the zoom origin relative to the current center
4777 var scale = this.getZoomScale(zoom),
4778 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
4779
4780 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
4781 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
4782
4783 requestAnimFrame(function () {
4784 this
4785 ._moveStart(true, options.noMoveStart || false)
4786 ._animateZoom(center, zoom, true);
4787 }, this);
4788
4789 return true;
4790 },
4791
4792 _animateZoom: function (center, zoom, startAnim, noUpdate) {
4793 if (!this._mapPane) { return; }
4794
4795 if (startAnim) {
4796 this._animatingZoom = true;
4797
4798 // remember what center/zoom to set after animation
4799 this._animateToCenter = center;
4800 this._animateToZoom = zoom;
4801
4802 addClass(this._mapPane, 'leaflet-zoom-anim');
4803 }
4804
4805 // @section Other Events
4806 // @event zoomanim: ZoomAnimEvent
4807 // Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom.
4808 this.fire('zoomanim', {
4809 center: center,
4810 zoom: zoom,
4811 noUpdate: noUpdate
4812 });
4813
4814 if (!this._tempFireZoomEvent) {
4815 this._tempFireZoomEvent = this._zoom !== this._animateToZoom;
4816 }
4817
4818 this._move(this._animateToCenter, this._animateToZoom, undefined, true);
4819
4820 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
4821 setTimeout(bind(this._onZoomTransitionEnd, this), 250);
4822 },
4823
4824 _onZoomTransitionEnd: function () {
4825 if (!this._animatingZoom) { return; }
4826
4827 if (this._mapPane) {
4828 removeClass(this._mapPane, 'leaflet-zoom-anim');
4829 }
4830
4831 this._animatingZoom = false;
4832
4833 this._move(this._animateToCenter, this._animateToZoom, undefined, true);
4834
4835 if (this._tempFireZoomEvent) {
4836 this.fire('zoom');
4837 }
4838 delete this._tempFireZoomEvent;
4839
4840 this.fire('move');
4841
4842 this._moveEnd(true);
4843 }
4844 });
4845
4846 // @section
4847
4848 // @factory L.map(id: String, options?: Map options)
4849 // Instantiates a map object given the DOM ID of a `<div>` element
4850 // and optionally an object literal with `Map options`.
4851 //
4852 // @alternative
4853 // @factory L.map(el: HTMLElement, options?: Map options)
4854 // Instantiates a map object given an instance of a `<div>` HTML element
4855 // and optionally an object literal with `Map options`.
4856 function createMap(id, options) {
4857 return new Map(id, options);
4858 }
4859
4860 /*
4861 * @class Control
4862 * @aka L.Control
4863 * @inherits Class
4864 *
4865 * L.Control is a base class for implementing map controls. Handles positioning.
4866 * All other controls extend from this class.
4867 */
4868
4869 var Control = Class.extend({
4870 // @section
4871 // @aka Control Options
4872 options: {
4873 // @option position: String = 'topright'
4874 // The position of the control (one of the map corners). Possible values are `'topleft'`,
4875 // `'topright'`, `'bottomleft'` or `'bottomright'`
4876 position: 'topright'
4877 },
4878
4879 initialize: function (options) {
4880 setOptions(this, options);
4881 },
4882
4883 /* @section
4884 * Classes extending L.Control will inherit the following methods:
4885 *
4886 * @method getPosition: string
4887 * Returns the position of the control.
4888 */
4889 getPosition: function () {
4890 return this.options.position;
4891 },
4892
4893 // @method setPosition(position: string): this
4894 // Sets the position of the control.
4895 setPosition: function (position) {
4896 var map = this._map;
4897
4898 if (map) {
4899 map.removeControl(this);
4900 }
4901
4902 this.options.position = position;
4903
4904 if (map) {
4905 map.addControl(this);
4906 }
4907
4908 return this;
4909 },
4910
4911 // @method getContainer: HTMLElement
4912 // Returns the HTMLElement that contains the control.
4913 getContainer: function () {
4914 return this._container;
4915 },
4916
4917 // @method addTo(map: Map): this
4918 // Adds the control to the given map.
4919 addTo: function (map) {
4920 this.remove();
4921 this._map = map;
4922
4923 var container = this._container = this.onAdd(map),
4924 pos = this.getPosition(),
4925 corner = map._controlCorners[pos];
4926
4927 addClass(container, 'leaflet-control');
4928
4929 if (pos.indexOf('bottom') !== -1) {
4930 corner.insertBefore(container, corner.firstChild);
4931 } else {
4932 corner.appendChild(container);
4933 }
4934
4935 this._map.on('unload', this.remove, this);
4936
4937 return this;
4938 },
4939
4940 // @method remove: this
4941 // Removes the control from the map it is currently active on.
4942 remove: function () {
4943 if (!this._map) {
4944 return this;
4945 }
4946
4947 remove(this._container);
4948
4949 if (this.onRemove) {
4950 this.onRemove(this._map);
4951 }
4952
4953 this._map.off('unload', this.remove, this);
4954 this._map = null;
4955
4956 return this;
4957 },
4958
4959 _refocusOnMap: function (e) {
4960 // if map exists and event is not a keyboard event
4961 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
4962 this._map.getContainer().focus();
4963 }
4964 }
4965 });
4966
4967 var control = function (options) {
4968 return new Control(options);
4969 };
4970
4971 /* @section Extension methods
4972 * @uninheritable
4973 *
4974 * Every control should extend from `L.Control` and (re-)implement the following methods.
4975 *
4976 * @method onAdd(map: Map): HTMLElement
4977 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
4978 *
4979 * @method onRemove(map: Map)
4980 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
4981 */
4982
4983 /* @namespace Map
4984 * @section Methods for Layers and Controls
4985 */
4986 Map.include({
4987 // @method addControl(control: Control): this
4988 // Adds the given control to the map
4989 addControl: function (control) {
4990 control.addTo(this);
4991 return this;
4992 },
4993
4994 // @method removeControl(control: Control): this
4995 // Removes the given control from the map
4996 removeControl: function (control) {
4997 control.remove();
4998 return this;
4999 },
5000
5001 _initControlPos: function () {
5002 var corners = this._controlCorners = {},
5003 l = 'leaflet-',
5004 container = this._controlContainer =
5005 create$1('div', l + 'control-container', this._container);
5006
5007 function createCorner(vSide, hSide) {
5008 var className = l + vSide + ' ' + l + hSide;
5009
5010 corners[vSide + hSide] = create$1('div', className, container);
5011 }
5012
5013 createCorner('top', 'left');
5014 createCorner('top', 'right');
5015 createCorner('bottom', 'left');
5016 createCorner('bottom', 'right');
5017 },
5018
5019 _clearControlPos: function () {
5020 for (var i in this._controlCorners) {
5021 remove(this._controlCorners[i]);
5022 }
5023 remove(this._controlContainer);
5024 delete this._controlCorners;
5025 delete this._controlContainer;
5026 }
5027 });
5028
5029 /*
5030 * @class Control.Layers
5031 * @aka L.Control.Layers
5032 * @inherits Control
5033 *
5034 * 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`.
5035 *
5036 * @example
5037 *
5038 * ```js
5039 * var baseLayers = {
5040 * "Mapbox": mapbox,
5041 * "OpenStreetMap": osm
5042 * };
5043 *
5044 * var overlays = {
5045 * "Marker": marker,
5046 * "Roads": roadsLayer
5047 * };
5048 *
5049 * L.control.layers(baseLayers, overlays).addTo(map);
5050 * ```
5051 *
5052 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
5053 *
5054 * ```js
5055 * {
5056 * "<someName1>": layer1,
5057 * "<someName2>": layer2
5058 * }
5059 * ```
5060 *
5061 * The layer names can contain HTML, which allows you to add additional styling to the items:
5062 *
5063 * ```js
5064 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
5065 * ```
5066 */
5067
5068 var Layers = Control.extend({
5069 // @section
5070 // @aka Control.Layers options
5071 options: {
5072 // @option collapsed: Boolean = true
5073 // If `true`, the control will be collapsed into an icon and expanded on mouse hover, touch, or keyboard activation.
5074 collapsed: true,
5075 position: 'topright',
5076
5077 // @option autoZIndex: Boolean = true
5078 // 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.
5079 autoZIndex: true,
5080
5081 // @option hideSingleBase: Boolean = false
5082 // If `true`, the base layers in the control will be hidden when there is only one.
5083 hideSingleBase: false,
5084
5085 // @option sortLayers: Boolean = false
5086 // Whether to sort the layers. When `false`, layers will keep the order
5087 // in which they were added to the control.
5088 sortLayers: false,
5089
5090 // @option sortFunction: Function = *
5091 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
5092 // that will be used for sorting the layers, when `sortLayers` is `true`.
5093 // The function receives both the `L.Layer` instances and their names, as in
5094 // `sortFunction(layerA, layerB, nameA, nameB)`.
5095 // By default, it sorts layers alphabetically by their name.
5096 sortFunction: function (layerA, layerB, nameA, nameB) {
5097 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
5098 }
5099 },
5100
5101 initialize: function (baseLayers, overlays, options) {
5102 setOptions(this, options);
5103
5104 this._layerControlInputs = [];
5105 this._layers = [];
5106 this._lastZIndex = 0;
5107 this._handlingClick = false;
5108 this._preventClick = false;
5109
5110 for (var i in baseLayers) {
5111 this._addLayer(baseLayers[i], i);
5112 }
5113
5114 for (i in overlays) {
5115 this._addLayer(overlays[i], i, true);
5116 }
5117 },
5118
5119 onAdd: function (map) {
5120 this._initLayout();
5121 this._update();
5122
5123 this._map = map;
5124 map.on('zoomend', this._checkDisabledLayers, this);
5125
5126 for (var i = 0; i < this._layers.length; i++) {
5127 this._layers[i].layer.on('add remove', this._onLayerChange, this);
5128 }
5129
5130 return this._container;
5131 },
5132
5133 addTo: function (map) {
5134 Control.prototype.addTo.call(this, map);
5135 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
5136 return this._expandIfNotCollapsed();
5137 },
5138
5139 onRemove: function () {
5140 this._map.off('zoomend', this._checkDisabledLayers, this);
5141
5142 for (var i = 0; i < this._layers.length; i++) {
5143 this._layers[i].layer.off('add remove', this._onLayerChange, this);
5144 }
5145 },
5146
5147 // @method addBaseLayer(layer: Layer, name: String): this
5148 // Adds a base layer (radio button entry) with the given name to the control.
5149 addBaseLayer: function (layer, name) {
5150 this._addLayer(layer, name);
5151 return (this._map) ? this._update() : this;
5152 },
5153
5154 // @method addOverlay(layer: Layer, name: String): this
5155 // Adds an overlay (checkbox entry) with the given name to the control.
5156 addOverlay: function (layer, name) {
5157 this._addLayer(layer, name, true);
5158 return (this._map) ? this._update() : this;
5159 },
5160
5161 // @method removeLayer(layer: Layer): this
5162 // Remove the given layer from the control.
5163 removeLayer: function (layer) {
5164 layer.off('add remove', this._onLayerChange, this);
5165
5166 var obj = this._getLayer(stamp(layer));
5167 if (obj) {
5168 this._layers.splice(this._layers.indexOf(obj), 1);
5169 }
5170 return (this._map) ? this._update() : this;
5171 },
5172
5173 // @method expand(): this
5174 // Expand the control container if collapsed.
5175 expand: function () {
5176 addClass(this._container, 'leaflet-control-layers-expanded');
5177 this._section.style.height = null;
5178 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
5179 if (acceptableHeight < this._section.clientHeight) {
5180 addClass(this._section, 'leaflet-control-layers-scrollbar');
5181 this._section.style.height = acceptableHeight + 'px';
5182 } else {
5183 removeClass(this._section, 'leaflet-control-layers-scrollbar');
5184 }
5185 this._checkDisabledLayers();
5186 return this;
5187 },
5188
5189 // @method collapse(): this
5190 // Collapse the control container if expanded.
5191 collapse: function () {
5192 removeClass(this._container, 'leaflet-control-layers-expanded');
5193 return this;
5194 },
5195
5196 _initLayout: function () {
5197 var className = 'leaflet-control-layers',
5198 container = this._container = create$1('div', className),
5199 collapsed = this.options.collapsed;
5200
5201 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
5202 container.setAttribute('aria-haspopup', true);
5203
5204 disableClickPropagation(container);
5205 disableScrollPropagation(container);
5206
5207 var section = this._section = create$1('section', className + '-list');
5208
5209 if (collapsed) {
5210 this._map.on('click', this.collapse, this);
5211
5212 on(container, {
5213 mouseenter: this._expandSafely,
5214 mouseleave: this.collapse
5215 }, this);
5216 }
5217
5218 var link = this._layersLink = create$1('a', className + '-toggle', container);
5219 link.href = '#';
5220 link.title = 'Layers';
5221 link.setAttribute('role', 'button');
5222
5223 on(link, {
5224 keydown: function (e) {
5225 if (e.keyCode === 13) {
5226 this._expandSafely();
5227 }
5228 },
5229 // Certain screen readers intercept the key event and instead send a click event
5230 click: function (e) {
5231 preventDefault(e);
5232 this._expandSafely();
5233 }
5234 }, this);
5235
5236 if (!collapsed) {
5237 this.expand();
5238 }
5239
5240 this._baseLayersList = create$1('div', className + '-base', section);
5241 this._separator = create$1('div', className + '-separator', section);
5242 this._overlaysList = create$1('div', className + '-overlays', section);
5243
5244 container.appendChild(section);
5245 },
5246
5247 _getLayer: function (id) {
5248 for (var i = 0; i < this._layers.length; i++) {
5249
5250 if (this._layers[i] && stamp(this._layers[i].layer) === id) {
5251 return this._layers[i];
5252 }
5253 }
5254 },
5255
5256 _addLayer: function (layer, name, overlay) {
5257 if (this._map) {
5258 layer.on('add remove', this._onLayerChange, this);
5259 }
5260
5261 this._layers.push({
5262 layer: layer,
5263 name: name,
5264 overlay: overlay
5265 });
5266
5267 if (this.options.sortLayers) {
5268 this._layers.sort(bind(function (a, b) {
5269 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
5270 }, this));
5271 }
5272
5273 if (this.options.autoZIndex && layer.setZIndex) {
5274 this._lastZIndex++;
5275 layer.setZIndex(this._lastZIndex);
5276 }
5277
5278 this._expandIfNotCollapsed();
5279 },
5280
5281 _update: function () {
5282 if (!this._container) { return this; }
5283
5284 empty(this._baseLayersList);
5285 empty(this._overlaysList);
5286
5287 this._layerControlInputs = [];
5288 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
5289
5290 for (i = 0; i < this._layers.length; i++) {
5291 obj = this._layers[i];
5292 this._addItem(obj);
5293 overlaysPresent = overlaysPresent || obj.overlay;
5294 baseLayersPresent = baseLayersPresent || !obj.overlay;
5295 baseLayersCount += !obj.overlay ? 1 : 0;
5296 }
5297
5298 // Hide base layers section if there's only one layer.
5299 if (this.options.hideSingleBase) {
5300 baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
5301 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
5302 }
5303
5304 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
5305
5306 return this;
5307 },
5308
5309 _onLayerChange: function (e) {
5310 if (!this._handlingClick) {
5311 this._update();
5312 }
5313
5314 var obj = this._getLayer(stamp(e.target));
5315
5316 // @namespace Map
5317 // @section Layer events
5318 // @event baselayerchange: LayersControlEvent
5319 // Fired when the base layer is changed through the [layers control](#control-layers).
5320 // @event overlayadd: LayersControlEvent
5321 // Fired when an overlay is selected through the [layers control](#control-layers).
5322 // @event overlayremove: LayersControlEvent
5323 // Fired when an overlay is deselected through the [layers control](#control-layers).
5324 // @namespace Control.Layers
5325 var type = obj.overlay ?
5326 (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
5327 (e.type === 'add' ? 'baselayerchange' : null);
5328
5329 if (type) {
5330 this._map.fire(type, obj);
5331 }
5332 },
5333
5334 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see https://stackoverflow.com/a/119079)
5335 _createRadioElement: function (name, checked) {
5336
5337 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
5338 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
5339
5340 var radioFragment = document.createElement('div');
5341 radioFragment.innerHTML = radioHtml;
5342
5343 return radioFragment.firstChild;
5344 },
5345
5346 _addItem: function (obj) {
5347 var label = document.createElement('label'),
5348 checked = this._map.hasLayer(obj.layer),
5349 input;
5350
5351 if (obj.overlay) {
5352 input = document.createElement('input');
5353 input.type = 'checkbox';
5354 input.className = 'leaflet-control-layers-selector';
5355 input.defaultChecked = checked;
5356 } else {
5357 input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked);
5358 }
5359
5360 this._layerControlInputs.push(input);
5361 input.layerId = stamp(obj.layer);
5362
5363 on(input, 'click', this._onInputClick, this);
5364
5365 var name = document.createElement('span');
5366 name.innerHTML = ' ' + obj.name;
5367
5368 // Helps from preventing layer control flicker when checkboxes are disabled
5369 // https://github.com/Leaflet/Leaflet/issues/2771
5370 var holder = document.createElement('span');
5371
5372 label.appendChild(holder);
5373 holder.appendChild(input);
5374 holder.appendChild(name);
5375
5376 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
5377 container.appendChild(label);
5378
5379 this._checkDisabledLayers();
5380 return label;
5381 },
5382
5383 _onInputClick: function () {
5384 // expanding the control on mobile with a click can cause adding a layer - we don't want this
5385 if (this._preventClick) {
5386 return;
5387 }
5388
5389 var inputs = this._layerControlInputs,
5390 input, layer;
5391 var addedLayers = [],
5392 removedLayers = [];
5393
5394 this._handlingClick = true;
5395
5396 for (var i = inputs.length - 1; i >= 0; i--) {
5397 input = inputs[i];
5398 layer = this._getLayer(input.layerId).layer;
5399
5400 if (input.checked) {
5401 addedLayers.push(layer);
5402 } else if (!input.checked) {
5403 removedLayers.push(layer);
5404 }
5405 }
5406
5407 // Bugfix issue 2318: Should remove all old layers before readding new ones
5408 for (i = 0; i < removedLayers.length; i++) {
5409 if (this._map.hasLayer(removedLayers[i])) {
5410 this._map.removeLayer(removedLayers[i]);
5411 }
5412 }
5413 for (i = 0; i < addedLayers.length; i++) {
5414 if (!this._map.hasLayer(addedLayers[i])) {
5415 this._map.addLayer(addedLayers[i]);
5416 }
5417 }
5418
5419 this._handlingClick = false;
5420
5421 this._refocusOnMap();
5422 },
5423
5424 _checkDisabledLayers: function () {
5425 var inputs = this._layerControlInputs,
5426 input,
5427 layer,
5428 zoom = this._map.getZoom();
5429
5430 for (var i = inputs.length - 1; i >= 0; i--) {
5431 input = inputs[i];
5432 layer = this._getLayer(input.layerId).layer;
5433 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
5434 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
5435
5436 }
5437 },
5438
5439 _expandIfNotCollapsed: function () {
5440 if (this._map && !this.options.collapsed) {
5441 this.expand();
5442 }
5443 return this;
5444 },
5445
5446 _expandSafely: function () {
5447 var section = this._section;
5448 this._preventClick = true;
5449 on(section, 'click', preventDefault);
5450 this.expand();
5451 var that = this;
5452 setTimeout(function () {
5453 off(section, 'click', preventDefault);
5454 that._preventClick = false;
5455 });
5456 }
5457
5458 });
5459
5460
5461 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
5462 // 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.
5463 var layers = function (baseLayers, overlays, options) {
5464 return new Layers(baseLayers, overlays, options);
5465 };
5466
5467 /*
5468 * @class Control.Zoom
5469 * @aka L.Control.Zoom
5470 * @inherits Control
5471 *
5472 * 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`.
5473 */
5474
5475 var Zoom = Control.extend({
5476 // @section
5477 // @aka Control.Zoom options
5478 options: {
5479 position: 'topleft',
5480
5481 // @option zoomInText: String = '<span aria-hidden="true">+</span>'
5482 // The text set on the 'zoom in' button.
5483 zoomInText: '<span aria-hidden="true">+</span>',
5484
5485 // @option zoomInTitle: String = 'Zoom in'
5486 // The title set on the 'zoom in' button.
5487 zoomInTitle: 'Zoom in',
5488
5489 // @option zoomOutText: String = '<span aria-hidden="true">&#x2212;</span>'
5490 // The text set on the 'zoom out' button.
5491 zoomOutText: '<span aria-hidden="true">&#x2212;</span>',
5492
5493 // @option zoomOutTitle: String = 'Zoom out'
5494 // The title set on the 'zoom out' button.
5495 zoomOutTitle: 'Zoom out'
5496 },
5497
5498 onAdd: function (map) {
5499 var zoomName = 'leaflet-control-zoom',
5500 container = create$1('div', zoomName + ' leaflet-bar'),
5501 options = this.options;
5502
5503 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle,
5504 zoomName + '-in', container, this._zoomIn);
5505 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
5506 zoomName + '-out', container, this._zoomOut);
5507
5508 this._updateDisabled();
5509 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
5510
5511 return container;
5512 },
5513
5514 onRemove: function (map) {
5515 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
5516 },
5517
5518 disable: function () {
5519 this._disabled = true;
5520 this._updateDisabled();
5521 return this;
5522 },
5523
5524 enable: function () {
5525 this._disabled = false;
5526 this._updateDisabled();
5527 return this;
5528 },
5529
5530 _zoomIn: function (e) {
5531 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
5532 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5533 }
5534 },
5535
5536 _zoomOut: function (e) {
5537 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
5538 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5539 }
5540 },
5541
5542 _createButton: function (html, title, className, container, fn) {
5543 var link = create$1('a', className, container);
5544 link.innerHTML = html;
5545 link.href = '#';
5546 link.title = title;
5547
5548 /*
5549 * Will force screen readers like VoiceOver to read this as "Zoom in - button"
5550 */
5551 link.setAttribute('role', 'button');
5552 link.setAttribute('aria-label', title);
5553
5554 disableClickPropagation(link);
5555 on(link, 'click', stop);
5556 on(link, 'click', fn, this);
5557 on(link, 'click', this._refocusOnMap, this);
5558
5559 return link;
5560 },
5561
5562 _updateDisabled: function () {
5563 var map = this._map,
5564 className = 'leaflet-disabled';
5565
5566 removeClass(this._zoomInButton, className);
5567 removeClass(this._zoomOutButton, className);
5568 this._zoomInButton.setAttribute('aria-disabled', 'false');
5569 this._zoomOutButton.setAttribute('aria-disabled', 'false');
5570
5571 if (this._disabled || map._zoom === map.getMinZoom()) {
5572 addClass(this._zoomOutButton, className);
5573 this._zoomOutButton.setAttribute('aria-disabled', 'true');
5574 }
5575 if (this._disabled || map._zoom === map.getMaxZoom()) {
5576 addClass(this._zoomInButton, className);
5577 this._zoomInButton.setAttribute('aria-disabled', 'true');
5578 }
5579 }
5580 });
5581
5582 // @namespace Map
5583 // @section Control options
5584 // @option zoomControl: Boolean = true
5585 // Whether a [zoom control](#control-zoom) is added to the map by default.
5586 Map.mergeOptions({
5587 zoomControl: true
5588 });
5589
5590 Map.addInitHook(function () {
5591 if (this.options.zoomControl) {
5592 // @section Controls
5593 // @property zoomControl: Control.Zoom
5594 // The default zoom control (only available if the
5595 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
5596 this.zoomControl = new Zoom();
5597 this.addControl(this.zoomControl);
5598 }
5599 });
5600
5601 // @namespace Control.Zoom
5602 // @factory L.control.zoom(options: Control.Zoom options)
5603 // Creates a zoom control
5604 var zoom = function (options) {
5605 return new Zoom(options);
5606 };
5607
5608 /*
5609 * @class Control.Scale
5610 * @aka L.Control.Scale
5611 * @inherits Control
5612 *
5613 * 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`.
5614 *
5615 * @example
5616 *
5617 * ```js
5618 * L.control.scale().addTo(map);
5619 * ```
5620 */
5621
5622 var Scale = Control.extend({
5623 // @section
5624 // @aka Control.Scale options
5625 options: {
5626 position: 'bottomleft',
5627
5628 // @option maxWidth: Number = 100
5629 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
5630 maxWidth: 100,
5631
5632 // @option metric: Boolean = True
5633 // Whether to show the metric scale line (m/km).
5634 metric: true,
5635
5636 // @option imperial: Boolean = True
5637 // Whether to show the imperial scale line (mi/ft).
5638 imperial: true
5639
5640 // @option updateWhenIdle: Boolean = false
5641 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
5642 },
5643
5644 onAdd: function (map) {
5645 var className = 'leaflet-control-scale',
5646 container = create$1('div', className),
5647 options = this.options;
5648
5649 this._addScales(options, className + '-line', container);
5650
5651 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5652 map.whenReady(this._update, this);
5653
5654 return container;
5655 },
5656
5657 onRemove: function (map) {
5658 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5659 },
5660
5661 _addScales: function (options, className, container) {
5662 if (options.metric) {
5663 this._mScale = create$1('div', className, container);
5664 }
5665 if (options.imperial) {
5666 this._iScale = create$1('div', className, container);
5667 }
5668 },
5669
5670 _update: function () {
5671 var map = this._map,
5672 y = map.getSize().y / 2;
5673
5674 var maxMeters = map.distance(
5675 map.containerPointToLatLng([0, y]),
5676 map.containerPointToLatLng([this.options.maxWidth, y]));
5677
5678 this._updateScales(maxMeters);
5679 },
5680
5681 _updateScales: function (maxMeters) {
5682 if (this.options.metric && maxMeters) {
5683 this._updateMetric(maxMeters);
5684 }
5685 if (this.options.imperial && maxMeters) {
5686 this._updateImperial(maxMeters);
5687 }
5688 },
5689
5690 _updateMetric: function (maxMeters) {
5691 var meters = this._getRoundNum(maxMeters),
5692 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
5693
5694 this._updateScale(this._mScale, label, meters / maxMeters);
5695 },
5696
5697 _updateImperial: function (maxMeters) {
5698 var maxFeet = maxMeters * 3.2808399,
5699 maxMiles, miles, feet;
5700
5701 if (maxFeet > 5280) {
5702 maxMiles = maxFeet / 5280;
5703 miles = this._getRoundNum(maxMiles);
5704 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
5705
5706 } else {
5707 feet = this._getRoundNum(maxFeet);
5708 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
5709 }
5710 },
5711
5712 _updateScale: function (scale, text, ratio) {
5713 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
5714 scale.innerHTML = text;
5715 },
5716
5717 _getRoundNum: function (num) {
5718 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5719 d = num / pow10;
5720
5721 d = d >= 10 ? 10 :
5722 d >= 5 ? 5 :
5723 d >= 3 ? 3 :
5724 d >= 2 ? 2 : 1;
5725
5726 return pow10 * d;
5727 }
5728 });
5729
5730
5731 // @factory L.control.scale(options?: Control.Scale options)
5732 // Creates an scale control with the given options.
5733 var scale = function (options) {
5734 return new Scale(options);
5735 };
5736
5737 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>';
5738
5739
5740 /*
5741 * @class Control.Attribution
5742 * @aka L.Control.Attribution
5743 * @inherits Control
5744 *
5745 * 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.
5746 */
5747
5748 var Attribution = Control.extend({
5749 // @section
5750 // @aka Control.Attribution options
5751 options: {
5752 position: 'bottomright',
5753
5754 // @option prefix: String|false = 'Leaflet'
5755 // The HTML text shown before the attributions. Pass `false` to disable.
5756 prefix: '<a href="https://leafletjs.com" title="A JavaScript library for interactive maps">' + (Browser.inlineSvg ? ukrainianFlag + ' ' : '') + 'Leaflet</a>'
5757 },
5758
5759 initialize: function (options) {
5760 setOptions(this, options);
5761
5762 this._attributions = {};
5763 },
5764
5765 onAdd: function (map) {
5766 map.attributionControl = this;
5767 this._container = create$1('div', 'leaflet-control-attribution');
5768 disableClickPropagation(this._container);
5769
5770 // TODO ugly, refactor
5771 for (var i in map._layers) {
5772 if (map._layers[i].getAttribution) {
5773 this.addAttribution(map._layers[i].getAttribution());
5774 }
5775 }
5776
5777 this._update();
5778
5779 map.on('layeradd', this._addAttribution, this);
5780
5781 return this._container;
5782 },
5783
5784 onRemove: function (map) {
5785 map.off('layeradd', this._addAttribution, this);
5786 },
5787
5788 _addAttribution: function (ev) {
5789 if (ev.layer.getAttribution) {
5790 this.addAttribution(ev.layer.getAttribution());
5791 ev.layer.once('remove', function () {
5792 this.removeAttribution(ev.layer.getAttribution());
5793 }, this);
5794 }
5795 },
5796
5797 // @method setPrefix(prefix: String|false): this
5798 // The HTML text shown before the attributions. Pass `false` to disable.
5799 setPrefix: function (prefix) {
5800 this.options.prefix = prefix;
5801 this._update();
5802 return this;
5803 },
5804
5805 // @method addAttribution(text: String): this
5806 // Adds an attribution text (e.g. `'&copy; OpenStreetMap contributors'`).
5807 addAttribution: function (text) {
5808 if (!text) { return this; }
5809
5810 if (!this._attributions[text]) {
5811 this._attributions[text] = 0;
5812 }
5813 this._attributions[text]++;
5814
5815 this._update();
5816
5817 return this;
5818 },
5819
5820 // @method removeAttribution(text: String): this
5821 // Removes an attribution text.
5822 removeAttribution: function (text) {
5823 if (!text) { return this; }
5824
5825 if (this._attributions[text]) {
5826 this._attributions[text]--;
5827 this._update();
5828 }
5829
5830 return this;
5831 },
5832
5833 _update: function () {
5834 if (!this._map) { return; }
5835
5836 var attribs = [];
5837
5838 for (var i in this._attributions) {
5839 if (this._attributions[i]) {
5840 attribs.push(i);
5841 }
5842 }
5843
5844 var prefixAndAttribs = [];
5845
5846 if (this.options.prefix) {
5847 prefixAndAttribs.push(this.options.prefix);
5848 }
5849 if (attribs.length) {
5850 prefixAndAttribs.push(attribs.join(', '));
5851 }
5852
5853 this._container.innerHTML = prefixAndAttribs.join(' <span aria-hidden="true">|</span> ');
5854 }
5855 });
5856
5857 // @namespace Map
5858 // @section Control options
5859 // @option attributionControl: Boolean = true
5860 // Whether a [attribution control](#control-attribution) is added to the map by default.
5861 Map.mergeOptions({
5862 attributionControl: true
5863 });
5864
5865 Map.addInitHook(function () {
5866 if (this.options.attributionControl) {
5867 new Attribution().addTo(this);
5868 }
5869 });
5870
5871 // @namespace Control.Attribution
5872 // @factory L.control.attribution(options: Control.Attribution options)
5873 // Creates an attribution control.
5874 var attribution = function (options) {
5875 return new Attribution(options);
5876 };
5877
5878 Control.Layers = Layers;
5879 Control.Zoom = Zoom;
5880 Control.Scale = Scale;
5881 Control.Attribution = Attribution;
5882
5883 control.layers = layers;
5884 control.zoom = zoom;
5885 control.scale = scale;
5886 control.attribution = attribution;
5887
5888 /*
5889 L.Handler is a base class for handler classes that are used internally to inject
5890 interaction features like dragging to classes like Map and Marker.
5891 */
5892
5893 // @class Handler
5894 // @aka L.Handler
5895 // Abstract class for map interaction handlers
5896
5897 var Handler = Class.extend({
5898 initialize: function (map) {
5899 this._map = map;
5900 },
5901
5902 // @method enable(): this
5903 // Enables the handler
5904 enable: function () {
5905 if (this._enabled) { return this; }
5906
5907 this._enabled = true;
5908 this.addHooks();
5909 return this;
5910 },
5911
5912 // @method disable(): this
5913 // Disables the handler
5914 disable: function () {
5915 if (!this._enabled) { return this; }
5916
5917 this._enabled = false;
5918 this.removeHooks();
5919 return this;
5920 },
5921
5922 // @method enabled(): Boolean
5923 // Returns `true` if the handler is enabled
5924 enabled: function () {
5925 return !!this._enabled;
5926 }
5927
5928 // @section Extension methods
5929 // Classes inheriting from `Handler` must implement the two following methods:
5930 // @method addHooks()
5931 // Called when the handler is enabled, should add event hooks.
5932 // @method removeHooks()
5933 // Called when the handler is disabled, should remove the event hooks added previously.
5934 });
5935
5936 // @section There is static function which can be called without instantiating L.Handler:
5937 // @function addTo(map: Map, name: String): this
5938 // Adds a new Handler to the given map with the given name.
5939 Handler.addTo = function (map, name) {
5940 map.addHandler(name, this);
5941 return this;
5942 };
5943
5944 var Mixin = {Events: Events};
5945
5946 /*
5947 * @class Draggable
5948 * @aka L.Draggable
5949 * @inherits Evented
5950 *
5951 * A class for making DOM elements draggable (including touch support).
5952 * Used internally for map and marker dragging. Only works for elements
5953 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
5954 *
5955 * @example
5956 * ```js
5957 * var draggable = new L.Draggable(elementToDrag);
5958 * draggable.enable();
5959 * ```
5960 */
5961
5962 var START = Browser.touch ? 'touchstart mousedown' : 'mousedown';
5963
5964 var Draggable = Evented.extend({
5965
5966 options: {
5967 // @section
5968 // @aka Draggable options
5969 // @option clickTolerance: Number = 3
5970 // The max number of pixels a user can shift the mouse pointer during a click
5971 // for it to be considered a valid click (as opposed to a mouse drag).
5972 clickTolerance: 3
5973 },
5974
5975 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
5976 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
5977 initialize: function (element, dragStartTarget, preventOutline, options) {
5978 setOptions(this, options);
5979
5980 this._element = element;
5981 this._dragStartTarget = dragStartTarget || element;
5982 this._preventOutline = preventOutline;
5983 },
5984
5985 // @method enable()
5986 // Enables the dragging ability
5987 enable: function () {
5988 if (this._enabled) { return; }
5989
5990 on(this._dragStartTarget, START, this._onDown, this);
5991
5992 this._enabled = true;
5993 },
5994
5995 // @method disable()
5996 // Disables the dragging ability
5997 disable: function () {
5998 if (!this._enabled) { return; }
5999
6000 // If we're currently dragging this draggable,
6001 // disabling it counts as first ending the drag.
6002 if (Draggable._dragging === this) {
6003 this.finishDrag(true);
6004 }
6005
6006 off(this._dragStartTarget, START, this._onDown, this);
6007
6008 this._enabled = false;
6009 this._moved = false;
6010 },
6011
6012 _onDown: function (e) {
6013 // Ignore the event if disabled; this happens in IE11
6014 // under some circumstances, see #3666.
6015 if (!this._enabled) { return; }
6016
6017 this._moved = false;
6018
6019 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
6020
6021 if (e.touches && e.touches.length !== 1) {
6022 // Finish dragging to avoid conflict with touchZoom
6023 if (Draggable._dragging === this) {
6024 this.finishDrag();
6025 }
6026 return;
6027 }
6028
6029 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
6030 Draggable._dragging = this; // Prevent dragging multiple objects at once.
6031
6032 if (this._preventOutline) {
6033 preventOutline(this._element);
6034 }
6035
6036 disableImageDrag();
6037 disableTextSelection();
6038
6039 if (this._moving) { return; }
6040
6041 // @event down: Event
6042 // Fired when a drag is about to start.
6043 this.fire('down');
6044
6045 var first = e.touches ? e.touches[0] : e,
6046 sizedParent = getSizedParentNode(this._element);
6047
6048 this._startPoint = new Point(first.clientX, first.clientY);
6049 this._startPos = getPosition(this._element);
6050
6051 // Cache the scale, so that we can continuously compensate for it during drag (_onMove).
6052 this._parentScale = getScale(sizedParent);
6053
6054 var mouseevent = e.type === 'mousedown';
6055 on(document, mouseevent ? 'mousemove' : 'touchmove', this._onMove, this);
6056 on(document, mouseevent ? 'mouseup' : 'touchend touchcancel', this._onUp, this);
6057 },
6058
6059 _onMove: function (e) {
6060 // Ignore the event if disabled; this happens in IE11
6061 // under some circumstances, see #3666.
6062 if (!this._enabled) { return; }
6063
6064 if (e.touches && e.touches.length > 1) {
6065 this._moved = true;
6066 return;
6067 }
6068
6069 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
6070 offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
6071
6072 if (!offset.x && !offset.y) { return; }
6073 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
6074
6075 // We assume that the parent container's position, border and scale do not change for the duration of the drag.
6076 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
6077 // and we can use the cached value for the scale.
6078 offset.x /= this._parentScale.x;
6079 offset.y /= this._parentScale.y;
6080
6081 preventDefault(e);
6082
6083 if (!this._moved) {
6084 // @event dragstart: Event
6085 // Fired when a drag starts
6086 this.fire('dragstart');
6087
6088 this._moved = true;
6089
6090 addClass(document.body, 'leaflet-dragging');
6091
6092 this._lastTarget = e.target || e.srcElement;
6093 // IE and Edge do not give the <use> element, so fetch it
6094 // if necessary
6095 if (window.SVGElementInstance && this._lastTarget instanceof window.SVGElementInstance) {
6096 this._lastTarget = this._lastTarget.correspondingUseElement;
6097 }
6098 addClass(this._lastTarget, 'leaflet-drag-target');
6099 }
6100
6101 this._newPos = this._startPos.add(offset);
6102 this._moving = true;
6103
6104 this._lastEvent = e;
6105 this._updatePosition();
6106 },
6107
6108 _updatePosition: function () {
6109 var e = {originalEvent: this._lastEvent};
6110
6111 // @event predrag: Event
6112 // Fired continuously during dragging *before* each corresponding
6113 // update of the element's position.
6114 this.fire('predrag', e);
6115 setPosition(this._element, this._newPos);
6116
6117 // @event drag: Event
6118 // Fired continuously during dragging.
6119 this.fire('drag', e);
6120 },
6121
6122 _onUp: function () {
6123 // Ignore the event if disabled; this happens in IE11
6124 // under some circumstances, see #3666.
6125 if (!this._enabled) { return; }
6126 this.finishDrag();
6127 },
6128
6129 finishDrag: function (noInertia) {
6130 removeClass(document.body, 'leaflet-dragging');
6131
6132 if (this._lastTarget) {
6133 removeClass(this._lastTarget, 'leaflet-drag-target');
6134 this._lastTarget = null;
6135 }
6136
6137 off(document, 'mousemove touchmove', this._onMove, this);
6138 off(document, 'mouseup touchend touchcancel', this._onUp, this);
6139
6140 enableImageDrag();
6141 enableTextSelection();
6142
6143 var fireDragend = this._moved && this._moving;
6144
6145 this._moving = false;
6146 Draggable._dragging = false;
6147
6148 if (fireDragend) {
6149 // @event dragend: DragEndEvent
6150 // Fired when the drag ends.
6151 this.fire('dragend', {
6152 noInertia: noInertia,
6153 distance: this._newPos.distanceTo(this._startPos)
6154 });
6155 }
6156 }
6157
6158 });
6159
6160 /*
6161 * @namespace PolyUtil
6162 * Various utility functions for polygon geometries.
6163 */
6164
6165 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
6166 * 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)).
6167 * Used by Leaflet to only show polygon points that are on the screen or near, increasing
6168 * performance. Note that polygon points needs different algorithm for clipping
6169 * than polyline, so there's a separate method for it.
6170 */
6171 function clipPolygon(points, bounds, round) {
6172 var clippedPoints,
6173 edges = [1, 4, 2, 8],
6174 i, j, k,
6175 a, b,
6176 len, edge, p;
6177
6178 for (i = 0, len = points.length; i < len; i++) {
6179 points[i]._code = _getBitCode(points[i], bounds);
6180 }
6181
6182 // for each edge (left, bottom, right, top)
6183 for (k = 0; k < 4; k++) {
6184 edge = edges[k];
6185 clippedPoints = [];
6186
6187 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
6188 a = points[i];
6189 b = points[j];
6190
6191 // if a is inside the clip window
6192 if (!(a._code & edge)) {
6193 // if b is outside the clip window (a->b goes out of screen)
6194 if (b._code & edge) {
6195 p = _getEdgeIntersection(b, a, edge, bounds, round);
6196 p._code = _getBitCode(p, bounds);
6197 clippedPoints.push(p);
6198 }
6199 clippedPoints.push(a);
6200
6201 // else if b is inside the clip window (a->b enters the screen)
6202 } else if (!(b._code & edge)) {
6203 p = _getEdgeIntersection(b, a, edge, bounds, round);
6204 p._code = _getBitCode(p, bounds);
6205 clippedPoints.push(p);
6206 }
6207 }
6208 points = clippedPoints;
6209 }
6210
6211 return points;
6212 }
6213
6214 /* @function polygonCenter(latlngs: LatLng[], crs: CRS): LatLng
6215 * Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the passed LatLngs (first ring) from a polygon.
6216 */
6217 function polygonCenter(latlngs, crs) {
6218 var i, j, p1, p2, f, area, x, y, center;
6219
6220 if (!latlngs || latlngs.length === 0) {
6221 throw new Error('latlngs not passed');
6222 }
6223
6224 if (!isFlat(latlngs)) {
6225 console.warn('latlngs are not flat! Only the first ring will be used');
6226 latlngs = latlngs[0];
6227 }
6228
6229 var centroidLatLng = toLatLng([0, 0]);
6230
6231 var bounds = toLatLngBounds(latlngs);
6232 var areaBounds = bounds.getNorthWest().distanceTo(bounds.getSouthWest()) * bounds.getNorthEast().distanceTo(bounds.getNorthWest());
6233 // tests showed that below 1700 rounding errors are happening
6234 if (areaBounds < 1700) {
6235 // getting a inexact center, to move the latlngs near to [0, 0] to prevent rounding errors
6236 centroidLatLng = centroid(latlngs);
6237 }
6238
6239 var len = latlngs.length;
6240 var points = [];
6241 for (i = 0; i < len; i++) {
6242 var latlng = toLatLng(latlngs[i]);
6243 points.push(crs.project(toLatLng([latlng.lat - centroidLatLng.lat, latlng.lng - centroidLatLng.lng])));
6244 }
6245
6246 area = x = y = 0;
6247
6248 // polygon centroid algorithm;
6249 for (i = 0, j = len - 1; i < len; j = i++) {
6250 p1 = points[i];
6251 p2 = points[j];
6252
6253 f = p1.y * p2.x - p2.y * p1.x;
6254 x += (p1.x + p2.x) * f;
6255 y += (p1.y + p2.y) * f;
6256 area += f * 3;
6257 }
6258
6259 if (area === 0) {
6260 // Polygon is so small that all points are on same pixel.
6261 center = points[0];
6262 } else {
6263 center = [x / area, y / area];
6264 }
6265
6266 var latlngCenter = crs.unproject(toPoint(center));
6267 return toLatLng([latlngCenter.lat + centroidLatLng.lat, latlngCenter.lng + centroidLatLng.lng]);
6268 }
6269
6270 /* @function centroid(latlngs: LatLng[]): LatLng
6271 * Returns the 'center of mass' of the passed LatLngs.
6272 */
6273 function centroid(coords) {
6274 var latSum = 0;
6275 var lngSum = 0;
6276 var len = 0;
6277 for (var i = 0; i < coords.length; i++) {
6278 var latlng = toLatLng(coords[i]);
6279 latSum += latlng.lat;
6280 lngSum += latlng.lng;
6281 len++;
6282 }
6283 return toLatLng([latSum / len, lngSum / len]);
6284 }
6285
6286 var PolyUtil = {
6287 __proto__: null,
6288 clipPolygon: clipPolygon,
6289 polygonCenter: polygonCenter,
6290 centroid: centroid
6291 };
6292
6293 /*
6294 * @namespace LineUtil
6295 *
6296 * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
6297 */
6298
6299 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
6300 // Improves rendering performance dramatically by lessening the number of points to draw.
6301
6302 // @function simplify(points: Point[], tolerance: Number): Point[]
6303 // Dramatically reduces the number of points in a polyline while retaining
6304 // its shape and returns a new array of simplified points, using the
6305 // [Ramer-Douglas-Peucker algorithm](https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm).
6306 // Used for a huge performance boost when processing/displaying Leaflet polylines for
6307 // each zoom level and also reducing visual noise. tolerance affects the amount of
6308 // simplification (lesser value means higher quality but slower and with more points).
6309 // Also released as a separated micro-library [Simplify.js](https://mourner.github.io/simplify-js/).
6310 function simplify(points, tolerance) {
6311 if (!tolerance || !points.length) {
6312 return points.slice();
6313 }
6314
6315 var sqTolerance = tolerance * tolerance;
6316
6317 // stage 1: vertex reduction
6318 points = _reducePoints(points, sqTolerance);
6319
6320 // stage 2: Douglas-Peucker simplification
6321 points = _simplifyDP(points, sqTolerance);
6322
6323 return points;
6324 }
6325
6326 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
6327 // Returns the distance between point `p` and segment `p1` to `p2`.
6328 function pointToSegmentDistance(p, p1, p2) {
6329 return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
6330 }
6331
6332 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
6333 // Returns the closest point from a point `p` on a segment `p1` to `p2`.
6334 function closestPointOnSegment(p, p1, p2) {
6335 return _sqClosestPointOnSegment(p, p1, p2);
6336 }
6337
6338 // Ramer-Douglas-Peucker simplification, see https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
6339 function _simplifyDP(points, sqTolerance) {
6340
6341 var len = points.length,
6342 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
6343 markers = new ArrayConstructor(len);
6344
6345 markers[0] = markers[len - 1] = 1;
6346
6347 _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
6348
6349 var i,
6350 newPoints = [];
6351
6352 for (i = 0; i < len; i++) {
6353 if (markers[i]) {
6354 newPoints.push(points[i]);
6355 }
6356 }
6357
6358 return newPoints;
6359 }
6360
6361 function _simplifyDPStep(points, markers, sqTolerance, first, last) {
6362
6363 var maxSqDist = 0,
6364 index, i, sqDist;
6365
6366 for (i = first + 1; i <= last - 1; i++) {
6367 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
6368
6369 if (sqDist > maxSqDist) {
6370 index = i;
6371 maxSqDist = sqDist;
6372 }
6373 }
6374
6375 if (maxSqDist > sqTolerance) {
6376 markers[index] = 1;
6377
6378 _simplifyDPStep(points, markers, sqTolerance, first, index);
6379 _simplifyDPStep(points, markers, sqTolerance, index, last);
6380 }
6381 }
6382
6383 // reduce points that are too close to each other to a single point
6384 function _reducePoints(points, sqTolerance) {
6385 var reducedPoints = [points[0]];
6386
6387 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
6388 if (_sqDist(points[i], points[prev]) > sqTolerance) {
6389 reducedPoints.push(points[i]);
6390 prev = i;
6391 }
6392 }
6393 if (prev < len - 1) {
6394 reducedPoints.push(points[len - 1]);
6395 }
6396 return reducedPoints;
6397 }
6398
6399 var _lastCode;
6400
6401 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
6402 // Clips the segment a to b by rectangular bounds with the
6403 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
6404 // (modifying the segment points directly!). Used by Leaflet to only show polyline
6405 // points that are on the screen or near, increasing performance.
6406 function clipSegment(a, b, bounds, useLastCode, round) {
6407 var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
6408 codeB = _getBitCode(b, bounds),
6409
6410 codeOut, p, newCode;
6411
6412 // save 2nd code to avoid calculating it on the next segment
6413 _lastCode = codeB;
6414
6415 while (true) {
6416 // if a,b is inside the clip window (trivial accept)
6417 if (!(codeA | codeB)) {
6418 return [a, b];
6419 }
6420
6421 // if a,b is outside the clip window (trivial reject)
6422 if (codeA & codeB) {
6423 return false;
6424 }
6425
6426 // other cases
6427 codeOut = codeA || codeB;
6428 p = _getEdgeIntersection(a, b, codeOut, bounds, round);
6429 newCode = _getBitCode(p, bounds);
6430
6431 if (codeOut === codeA) {
6432 a = p;
6433 codeA = newCode;
6434 } else {
6435 b = p;
6436 codeB = newCode;
6437 }
6438 }
6439 }
6440
6441 function _getEdgeIntersection(a, b, code, bounds, round) {
6442 var dx = b.x - a.x,
6443 dy = b.y - a.y,
6444 min = bounds.min,
6445 max = bounds.max,
6446 x, y;
6447
6448 if (code & 8) { // top
6449 x = a.x + dx * (max.y - a.y) / dy;
6450 y = max.y;
6451
6452 } else if (code & 4) { // bottom
6453 x = a.x + dx * (min.y - a.y) / dy;
6454 y = min.y;
6455
6456 } else if (code & 2) { // right
6457 x = max.x;
6458 y = a.y + dy * (max.x - a.x) / dx;
6459
6460 } else if (code & 1) { // left
6461 x = min.x;
6462 y = a.y + dy * (min.x - a.x) / dx;
6463 }
6464
6465 return new Point(x, y, round);
6466 }
6467
6468 function _getBitCode(p, bounds) {
6469 var code = 0;
6470
6471 if (p.x < bounds.min.x) { // left
6472 code |= 1;
6473 } else if (p.x > bounds.max.x) { // right
6474 code |= 2;
6475 }
6476
6477 if (p.y < bounds.min.y) { // bottom
6478 code |= 4;
6479 } else if (p.y > bounds.max.y) { // top
6480 code |= 8;
6481 }
6482
6483 return code;
6484 }
6485
6486 // square distance (to avoid unnecessary Math.sqrt calls)
6487 function _sqDist(p1, p2) {
6488 var dx = p2.x - p1.x,
6489 dy = p2.y - p1.y;
6490 return dx * dx + dy * dy;
6491 }
6492
6493 // return closest point on segment or distance to that point
6494 function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
6495 var x = p1.x,
6496 y = p1.y,
6497 dx = p2.x - x,
6498 dy = p2.y - y,
6499 dot = dx * dx + dy * dy,
6500 t;
6501
6502 if (dot > 0) {
6503 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
6504
6505 if (t > 1) {
6506 x = p2.x;
6507 y = p2.y;
6508 } else if (t > 0) {
6509 x += dx * t;
6510 y += dy * t;
6511 }
6512 }
6513
6514 dx = p.x - x;
6515 dy = p.y - y;
6516
6517 return sqDist ? dx * dx + dy * dy : new Point(x, y);
6518 }
6519
6520
6521 // @function isFlat(latlngs: LatLng[]): Boolean
6522 // Returns true if `latlngs` is a flat array, false is nested.
6523 function isFlat(latlngs) {
6524 return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
6525 }
6526
6527 function _flat(latlngs) {
6528 console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
6529 return isFlat(latlngs);
6530 }
6531
6532 /* @function polylineCenter(latlngs: LatLng[], crs: CRS): LatLng
6533 * Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the passed LatLngs (first ring) from a polyline.
6534 */
6535 function polylineCenter(latlngs, crs) {
6536 var i, halfDist, segDist, dist, p1, p2, ratio, center;
6537
6538 if (!latlngs || latlngs.length === 0) {
6539 throw new Error('latlngs not passed');
6540 }
6541
6542 if (!isFlat(latlngs)) {
6543 console.warn('latlngs are not flat! Only the first ring will be used');
6544 latlngs = latlngs[0];
6545 }
6546
6547 var centroidLatLng = toLatLng([0, 0]);
6548
6549 var bounds = toLatLngBounds(latlngs);
6550 var areaBounds = bounds.getNorthWest().distanceTo(bounds.getSouthWest()) * bounds.getNorthEast().distanceTo(bounds.getNorthWest());
6551 // tests showed that below 1700 rounding errors are happening
6552 if (areaBounds < 1700) {
6553 // getting a inexact center, to move the latlngs near to [0, 0] to prevent rounding errors
6554 centroidLatLng = centroid(latlngs);
6555 }
6556
6557 var len = latlngs.length;
6558 var points = [];
6559 for (i = 0; i < len; i++) {
6560 var latlng = toLatLng(latlngs[i]);
6561 points.push(crs.project(toLatLng([latlng.lat - centroidLatLng.lat, latlng.lng - centroidLatLng.lng])));
6562 }
6563
6564 for (i = 0, halfDist = 0; i < len - 1; i++) {
6565 halfDist += points[i].distanceTo(points[i + 1]) / 2;
6566 }
6567
6568 // The line is so small in the current view that all points are on the same pixel.
6569 if (halfDist === 0) {
6570 center = points[0];
6571 } else {
6572 for (i = 0, dist = 0; i < len - 1; i++) {
6573 p1 = points[i];
6574 p2 = points[i + 1];
6575 segDist = p1.distanceTo(p2);
6576 dist += segDist;
6577
6578 if (dist > halfDist) {
6579 ratio = (dist - halfDist) / segDist;
6580 center = [
6581 p2.x - ratio * (p2.x - p1.x),
6582 p2.y - ratio * (p2.y - p1.y)
6583 ];
6584 break;
6585 }
6586 }
6587 }
6588
6589 var latlngCenter = crs.unproject(toPoint(center));
6590 return toLatLng([latlngCenter.lat + centroidLatLng.lat, latlngCenter.lng + centroidLatLng.lng]);
6591 }
6592
6593 var LineUtil = {
6594 __proto__: null,
6595 simplify: simplify,
6596 pointToSegmentDistance: pointToSegmentDistance,
6597 closestPointOnSegment: closestPointOnSegment,
6598 clipSegment: clipSegment,
6599 _getEdgeIntersection: _getEdgeIntersection,
6600 _getBitCode: _getBitCode,
6601 _sqClosestPointOnSegment: _sqClosestPointOnSegment,
6602 isFlat: isFlat,
6603 _flat: _flat,
6604 polylineCenter: polylineCenter
6605 };
6606
6607 /*
6608 * @namespace Projection
6609 * @section
6610 * Leaflet comes with a set of already defined Projections out of the box:
6611 *
6612 * @projection L.Projection.LonLat
6613 *
6614 * Equirectangular, or Plate Carree projection — the most simple projection,
6615 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
6616 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
6617 * `EPSG:4326` and `Simple` CRS.
6618 */
6619
6620 var LonLat = {
6621 project: function (latlng) {
6622 return new Point(latlng.lng, latlng.lat);
6623 },
6624
6625 unproject: function (point) {
6626 return new LatLng(point.y, point.x);
6627 },
6628
6629 bounds: new Bounds([-180, -90], [180, 90])
6630 };
6631
6632 /*
6633 * @namespace Projection
6634 * @projection L.Projection.Mercator
6635 *
6636 * Elliptical Mercator projection — more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS.
6637 */
6638
6639 var Mercator = {
6640 R: 6378137,
6641 R_MINOR: 6356752.314245179,
6642
6643 bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
6644
6645 project: function (latlng) {
6646 var d = Math.PI / 180,
6647 r = this.R,
6648 y = latlng.lat * d,
6649 tmp = this.R_MINOR / r,
6650 e = Math.sqrt(1 - tmp * tmp),
6651 con = e * Math.sin(y);
6652
6653 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
6654 y = -r * Math.log(Math.max(ts, 1E-10));
6655
6656 return new Point(latlng.lng * d * r, y);
6657 },
6658
6659 unproject: function (point) {
6660 var d = 180 / Math.PI,
6661 r = this.R,
6662 tmp = this.R_MINOR / r,
6663 e = Math.sqrt(1 - tmp * tmp),
6664 ts = Math.exp(-point.y / r),
6665 phi = Math.PI / 2 - 2 * Math.atan(ts);
6666
6667 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
6668 con = e * Math.sin(phi);
6669 con = Math.pow((1 - con) / (1 + con), e / 2);
6670 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
6671 phi += dphi;
6672 }
6673
6674 return new LatLng(phi * d, point.x * d / r);
6675 }
6676 };
6677
6678 /*
6679 * @class Projection
6680
6681 * An object with methods for projecting geographical coordinates of the world onto
6682 * a flat surface (and back). See [Map projection](https://en.wikipedia.org/wiki/Map_projection).
6683
6684 * @property bounds: Bounds
6685 * The bounds (specified in CRS units) where the projection is valid
6686
6687 * @method project(latlng: LatLng): Point
6688 * Projects geographical coordinates into a 2D point.
6689 * Only accepts actual `L.LatLng` instances, not arrays.
6690
6691 * @method unproject(point: Point): LatLng
6692 * The inverse of `project`. Projects a 2D point into a geographical location.
6693 * Only accepts actual `L.Point` instances, not arrays.
6694
6695 * Note that the projection instances do not inherit from Leaflet's `Class` object,
6696 * and can't be instantiated. Also, new classes can't inherit from them,
6697 * and methods can't be added to them with the `include` function.
6698
6699 */
6700
6701 var index = {
6702 __proto__: null,
6703 LonLat: LonLat,
6704 Mercator: Mercator,
6705 SphericalMercator: SphericalMercator
6706 };
6707
6708 /*
6709 * @namespace CRS
6710 * @crs L.CRS.EPSG3395
6711 *
6712 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
6713 */
6714 var EPSG3395 = extend({}, Earth, {
6715 code: 'EPSG:3395',
6716 projection: Mercator,
6717
6718 transformation: (function () {
6719 var scale = 0.5 / (Math.PI * Mercator.R);
6720 return toTransformation(scale, 0.5, -scale, 0.5);
6721 }())
6722 });
6723
6724 /*
6725 * @namespace CRS
6726 * @crs L.CRS.EPSG4326
6727 *
6728 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
6729 *
6730 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
6731 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer`
6732 * with this CRS, ensure that there are two 256x256 pixel tiles covering the
6733 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
6734 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
6735 */
6736
6737 var EPSG4326 = extend({}, Earth, {
6738 code: 'EPSG:4326',
6739 projection: LonLat,
6740 transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
6741 });
6742
6743 /*
6744 * @namespace CRS
6745 * @crs L.CRS.Simple
6746 *
6747 * A simple CRS that maps longitude and latitude into `x` and `y` directly.
6748 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
6749 * axis should still be inverted (going from bottom to top). `distance()` returns
6750 * simple euclidean distance.
6751 */
6752
6753 var Simple = extend({}, CRS, {
6754 projection: LonLat,
6755 transformation: toTransformation(1, 0, -1, 0),
6756
6757 scale: function (zoom) {
6758 return Math.pow(2, zoom);
6759 },
6760
6761 zoom: function (scale) {
6762 return Math.log(scale) / Math.LN2;
6763 },
6764
6765 distance: function (latlng1, latlng2) {
6766 var dx = latlng2.lng - latlng1.lng,
6767 dy = latlng2.lat - latlng1.lat;
6768
6769 return Math.sqrt(dx * dx + dy * dy);
6770 },
6771
6772 infinite: true
6773 });
6774
6775 CRS.Earth = Earth;
6776 CRS.EPSG3395 = EPSG3395;
6777 CRS.EPSG3857 = EPSG3857;
6778 CRS.EPSG900913 = EPSG900913;
6779 CRS.EPSG4326 = EPSG4326;
6780 CRS.Simple = Simple;
6781
6782 /*
6783 * @class Layer
6784 * @inherits Evented
6785 * @aka L.Layer
6786 * @aka ILayer
6787 *
6788 * A set of methods from the Layer base class that all Leaflet layers use.
6789 * Inherits all methods, options and events from `L.Evented`.
6790 *
6791 * @example
6792 *
6793 * ```js
6794 * var layer = L.marker(latlng).addTo(map);
6795 * layer.addTo(map);
6796 * layer.remove();
6797 * ```
6798 *
6799 * @event add: Event
6800 * Fired after the layer is added to a map
6801 *
6802 * @event remove: Event
6803 * Fired after the layer is removed from a map
6804 */
6805
6806
6807 var Layer = Evented.extend({
6808
6809 // Classes extending `L.Layer` will inherit the following options:
6810 options: {
6811 // @option pane: String = 'overlayPane'
6812 // 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.
6813 pane: 'overlayPane',
6814
6815 // @option attribution: String = null
6816 // 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.
6817 attribution: null,
6818
6819 bubblingMouseEvents: true
6820 },
6821
6822 /* @section
6823 * Classes extending `L.Layer` will inherit the following methods:
6824 *
6825 * @method addTo(map: Map|LayerGroup): this
6826 * Adds the layer to the given map or layer group.
6827 */
6828 addTo: function (map) {
6829 map.addLayer(this);
6830 return this;
6831 },
6832
6833 // @method remove: this
6834 // Removes the layer from the map it is currently active on.
6835 remove: function () {
6836 return this.removeFrom(this._map || this._mapToAdd);
6837 },
6838
6839 // @method removeFrom(map: Map): this
6840 // Removes the layer from the given map
6841 //
6842 // @alternative
6843 // @method removeFrom(group: LayerGroup): this
6844 // Removes the layer from the given `LayerGroup`
6845 removeFrom: function (obj) {
6846 if (obj) {
6847 obj.removeLayer(this);
6848 }
6849 return this;
6850 },
6851
6852 // @method getPane(name? : String): HTMLElement
6853 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
6854 getPane: function (name) {
6855 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
6856 },
6857
6858 addInteractiveTarget: function (targetEl) {
6859 this._map._targets[stamp(targetEl)] = this;
6860 return this;
6861 },
6862
6863 removeInteractiveTarget: function (targetEl) {
6864 delete this._map._targets[stamp(targetEl)];
6865 return this;
6866 },
6867
6868 // @method getAttribution: String
6869 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
6870 getAttribution: function () {
6871 return this.options.attribution;
6872 },
6873
6874 _layerAdd: function (e) {
6875 var map = e.target;
6876
6877 // check in case layer gets added and then removed before the map is ready
6878 if (!map.hasLayer(this)) { return; }
6879
6880 this._map = map;
6881 this._zoomAnimated = map._zoomAnimated;
6882
6883 if (this.getEvents) {
6884 var events = this.getEvents();
6885 map.on(events, this);
6886 this.once('remove', function () {
6887 map.off(events, this);
6888 }, this);
6889 }
6890
6891 this.onAdd(map);
6892
6893 this.fire('add');
6894 map.fire('layeradd', {layer: this});
6895 }
6896 });
6897
6898 /* @section Extension methods
6899 * @uninheritable
6900 *
6901 * Every layer should extend from `L.Layer` and (re-)implement the following methods.
6902 *
6903 * @method onAdd(map: Map): this
6904 * 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).
6905 *
6906 * @method onRemove(map: Map): this
6907 * 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).
6908 *
6909 * @method getEvents(): Object
6910 * 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.
6911 *
6912 * @method getAttribution(): String
6913 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
6914 *
6915 * @method beforeAdd(map: Map): this
6916 * 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.
6917 */
6918
6919
6920 /* @namespace Map
6921 * @section Layer events
6922 *
6923 * @event layeradd: LayerEvent
6924 * Fired when a new layer is added to the map.
6925 *
6926 * @event layerremove: LayerEvent
6927 * Fired when some layer is removed from the map
6928 *
6929 * @section Methods for Layers and Controls
6930 */
6931 Map.include({
6932 // @method addLayer(layer: Layer): this
6933 // Adds the given layer to the map
6934 addLayer: function (layer) {
6935 if (!layer._layerAdd) {
6936 throw new Error('The provided object is not a Layer.');
6937 }
6938
6939 var id = stamp(layer);
6940 if (this._layers[id]) { return this; }
6941 this._layers[id] = layer;
6942
6943 layer._mapToAdd = this;
6944
6945 if (layer.beforeAdd) {
6946 layer.beforeAdd(this);
6947 }
6948
6949 this.whenReady(layer._layerAdd, layer);
6950
6951 return this;
6952 },
6953
6954 // @method removeLayer(layer: Layer): this
6955 // Removes the given layer from the map.
6956 removeLayer: function (layer) {
6957 var id = stamp(layer);
6958
6959 if (!this._layers[id]) { return this; }
6960
6961 if (this._loaded) {
6962 layer.onRemove(this);
6963 }
6964
6965 delete this._layers[id];
6966
6967 if (this._loaded) {
6968 this.fire('layerremove', {layer: layer});
6969 layer.fire('remove');
6970 }
6971
6972 layer._map = layer._mapToAdd = null;
6973
6974 return this;
6975 },
6976
6977 // @method hasLayer(layer: Layer): Boolean
6978 // Returns `true` if the given layer is currently added to the map
6979 hasLayer: function (layer) {
6980 return stamp(layer) in this._layers;
6981 },
6982
6983 /* @method eachLayer(fn: Function, context?: Object): this
6984 * Iterates over the layers of the map, optionally specifying context of the iterator function.
6985 * ```
6986 * map.eachLayer(function(layer){
6987 * layer.bindPopup('Hello');
6988 * });
6989 * ```
6990 */
6991 eachLayer: function (method, context) {
6992 for (var i in this._layers) {
6993 method.call(context, this._layers[i]);
6994 }
6995 return this;
6996 },
6997
6998 _addLayers: function (layers) {
6999 layers = layers ? (isArray(layers) ? layers : [layers]) : [];
7000
7001 for (var i = 0, len = layers.length; i < len; i++) {
7002 this.addLayer(layers[i]);
7003 }
7004 },
7005
7006 _addZoomLimit: function (layer) {
7007 if (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
7008 this._zoomBoundLayers[stamp(layer)] = layer;
7009 this._updateZoomLevels();
7010 }
7011 },
7012
7013 _removeZoomLimit: function (layer) {
7014 var id = stamp(layer);
7015
7016 if (this._zoomBoundLayers[id]) {
7017 delete this._zoomBoundLayers[id];
7018 this._updateZoomLevels();
7019 }
7020 },
7021
7022 _updateZoomLevels: function () {
7023 var minZoom = Infinity,
7024 maxZoom = -Infinity,
7025 oldZoomSpan = this._getZoomSpan();
7026
7027 for (var i in this._zoomBoundLayers) {
7028 var options = this._zoomBoundLayers[i].options;
7029
7030 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
7031 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
7032 }
7033
7034 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
7035 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
7036
7037 // @section Map state change events
7038 // @event zoomlevelschange: Event
7039 // Fired when the number of zoomlevels on the map is changed due
7040 // to adding or removing a layer.
7041 if (oldZoomSpan !== this._getZoomSpan()) {
7042 this.fire('zoomlevelschange');
7043 }
7044
7045 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
7046 this.setZoom(this._layersMaxZoom);
7047 }
7048 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
7049 this.setZoom(this._layersMinZoom);
7050 }
7051 }
7052 });
7053
7054 /*
7055 * @class LayerGroup
7056 * @aka L.LayerGroup
7057 * @inherits Interactive layer
7058 *
7059 * Used to group several layers and handle them as one. If you add it to the map,
7060 * any layers added or removed from the group will be added/removed on the map as
7061 * well. Extends `Layer`.
7062 *
7063 * @example
7064 *
7065 * ```js
7066 * L.layerGroup([marker1, marker2])
7067 * .addLayer(polyline)
7068 * .addTo(map);
7069 * ```
7070 */
7071
7072 var LayerGroup = Layer.extend({
7073
7074 initialize: function (layers, options) {
7075 setOptions(this, options);
7076
7077 this._layers = {};
7078
7079 var i, len;
7080
7081 if (layers) {
7082 for (i = 0, len = layers.length; i < len; i++) {
7083 this.addLayer(layers[i]);
7084 }
7085 }
7086 },
7087
7088 // @method addLayer(layer: Layer): this
7089 // Adds the given layer to the group.
7090 addLayer: function (layer) {
7091 var id = this.getLayerId(layer);
7092
7093 this._layers[id] = layer;
7094
7095 if (this._map) {
7096 this._map.addLayer(layer);
7097 }
7098
7099 return this;
7100 },
7101
7102 // @method removeLayer(layer: Layer): this
7103 // Removes the given layer from the group.
7104 // @alternative
7105 // @method removeLayer(id: Number): this
7106 // Removes the layer with the given internal ID from the group.
7107 removeLayer: function (layer) {
7108 var id = layer in this._layers ? layer : this.getLayerId(layer);
7109
7110 if (this._map && this._layers[id]) {
7111 this._map.removeLayer(this._layers[id]);
7112 }
7113
7114 delete this._layers[id];
7115
7116 return this;
7117 },
7118
7119 // @method hasLayer(layer: Layer): Boolean
7120 // Returns `true` if the given layer is currently added to the group.
7121 // @alternative
7122 // @method hasLayer(id: Number): Boolean
7123 // Returns `true` if the given internal ID is currently added to the group.
7124 hasLayer: function (layer) {
7125 var layerId = typeof layer === 'number' ? layer : this.getLayerId(layer);
7126 return layerId in this._layers;
7127 },
7128
7129 // @method clearLayers(): this
7130 // Removes all the layers from the group.
7131 clearLayers: function () {
7132 return this.eachLayer(this.removeLayer, this);
7133 },
7134
7135 // @method invoke(methodName: String, …): this
7136 // Calls `methodName` on every layer contained in this group, passing any
7137 // additional parameters. Has no effect if the layers contained do not
7138 // implement `methodName`.
7139 invoke: function (methodName) {
7140 var args = Array.prototype.slice.call(arguments, 1),
7141 i, layer;
7142
7143 for (i in this._layers) {
7144 layer = this._layers[i];
7145
7146 if (layer[methodName]) {
7147 layer[methodName].apply(layer, args);
7148 }
7149 }
7150
7151 return this;
7152 },
7153
7154 onAdd: function (map) {
7155 this.eachLayer(map.addLayer, map);
7156 },
7157
7158 onRemove: function (map) {
7159 this.eachLayer(map.removeLayer, map);
7160 },
7161
7162 // @method eachLayer(fn: Function, context?: Object): this
7163 // Iterates over the layers of the group, optionally specifying context of the iterator function.
7164 // ```js
7165 // group.eachLayer(function (layer) {
7166 // layer.bindPopup('Hello');
7167 // });
7168 // ```
7169 eachLayer: function (method, context) {
7170 for (var i in this._layers) {
7171 method.call(context, this._layers[i]);
7172 }
7173 return this;
7174 },
7175
7176 // @method getLayer(id: Number): Layer
7177 // Returns the layer with the given internal ID.
7178 getLayer: function (id) {
7179 return this._layers[id];
7180 },
7181
7182 // @method getLayers(): Layer[]
7183 // Returns an array of all the layers added to the group.
7184 getLayers: function () {
7185 var layers = [];
7186 this.eachLayer(layers.push, layers);
7187 return layers;
7188 },
7189
7190 // @method setZIndex(zIndex: Number): this
7191 // Calls `setZIndex` on every layer contained in this group, passing the z-index.
7192 setZIndex: function (zIndex) {
7193 return this.invoke('setZIndex', zIndex);
7194 },
7195
7196 // @method getLayerId(layer: Layer): Number
7197 // Returns the internal ID for a layer
7198 getLayerId: function (layer) {
7199 return stamp(layer);
7200 }
7201 });
7202
7203
7204 // @factory L.layerGroup(layers?: Layer[], options?: Object)
7205 // Create a layer group, optionally given an initial set of layers and an `options` object.
7206 var layerGroup = function (layers, options) {
7207 return new LayerGroup(layers, options);
7208 };
7209
7210 /*
7211 * @class FeatureGroup
7212 * @aka L.FeatureGroup
7213 * @inherits LayerGroup
7214 *
7215 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
7216 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
7217 * * Events are propagated to the `FeatureGroup`, so if the group has an event
7218 * handler, it will handle events from any of the layers. This includes mouse events
7219 * and custom events.
7220 * * Has `layeradd` and `layerremove` events
7221 *
7222 * @example
7223 *
7224 * ```js
7225 * L.featureGroup([marker1, marker2, polyline])
7226 * .bindPopup('Hello world!')
7227 * .on('click', function() { alert('Clicked on a member of the group!'); })
7228 * .addTo(map);
7229 * ```
7230 */
7231
7232 var FeatureGroup = LayerGroup.extend({
7233
7234 addLayer: function (layer) {
7235 if (this.hasLayer(layer)) {
7236 return this;
7237 }
7238
7239 layer.addEventParent(this);
7240
7241 LayerGroup.prototype.addLayer.call(this, layer);
7242
7243 // @event layeradd: LayerEvent
7244 // Fired when a layer is added to this `FeatureGroup`
7245 return this.fire('layeradd', {layer: layer});
7246 },
7247
7248 removeLayer: function (layer) {
7249 if (!this.hasLayer(layer)) {
7250 return this;
7251 }
7252 if (layer in this._layers) {
7253 layer = this._layers[layer];
7254 }
7255
7256 layer.removeEventParent(this);
7257
7258 LayerGroup.prototype.removeLayer.call(this, layer);
7259
7260 // @event layerremove: LayerEvent
7261 // Fired when a layer is removed from this `FeatureGroup`
7262 return this.fire('layerremove', {layer: layer});
7263 },
7264
7265 // @method setStyle(style: Path options): this
7266 // Sets the given path options to each layer of the group that has a `setStyle` method.
7267 setStyle: function (style) {
7268 return this.invoke('setStyle', style);
7269 },
7270
7271 // @method bringToFront(): this
7272 // Brings the layer group to the top of all other layers
7273 bringToFront: function () {
7274 return this.invoke('bringToFront');
7275 },
7276
7277 // @method bringToBack(): this
7278 // Brings the layer group to the back of all other layers
7279 bringToBack: function () {
7280 return this.invoke('bringToBack');
7281 },
7282
7283 // @method getBounds(): LatLngBounds
7284 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
7285 getBounds: function () {
7286 var bounds = new LatLngBounds();
7287
7288 for (var id in this._layers) {
7289 var layer = this._layers[id];
7290 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
7291 }
7292 return bounds;
7293 }
7294 });
7295
7296 // @factory L.featureGroup(layers?: Layer[], options?: Object)
7297 // Create a feature group, optionally given an initial set of layers and an `options` object.
7298 var featureGroup = function (layers, options) {
7299 return new FeatureGroup(layers, options);
7300 };
7301
7302 /*
7303 * @class Icon
7304 * @aka L.Icon
7305 *
7306 * Represents an icon to provide when creating a marker.
7307 *
7308 * @example
7309 *
7310 * ```js
7311 * var myIcon = L.icon({
7312 * iconUrl: 'my-icon.png',
7313 * iconRetinaUrl: 'my-icon@2x.png',
7314 * iconSize: [38, 95],
7315 * iconAnchor: [22, 94],
7316 * popupAnchor: [-3, -76],
7317 * shadowUrl: 'my-icon-shadow.png',
7318 * shadowRetinaUrl: 'my-icon-shadow@2x.png',
7319 * shadowSize: [68, 95],
7320 * shadowAnchor: [22, 94]
7321 * });
7322 *
7323 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
7324 * ```
7325 *
7326 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
7327 *
7328 */
7329
7330 var Icon = Class.extend({
7331
7332 /* @section
7333 * @aka Icon options
7334 *
7335 * @option iconUrl: String = null
7336 * **(required)** The URL to the icon image (absolute or relative to your script path).
7337 *
7338 * @option iconRetinaUrl: String = null
7339 * The URL to a retina sized version of the icon image (absolute or relative to your
7340 * script path). Used for Retina screen devices.
7341 *
7342 * @option iconSize: Point = null
7343 * Size of the icon image in pixels.
7344 *
7345 * @option iconAnchor: Point = null
7346 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
7347 * will be aligned so that this point is at the marker's geographical location. Centered
7348 * by default if size is specified, also can be set in CSS with negative margins.
7349 *
7350 * @option popupAnchor: Point = [0, 0]
7351 * The coordinates of the point from which popups will "open", relative to the icon anchor.
7352 *
7353 * @option tooltipAnchor: Point = [0, 0]
7354 * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
7355 *
7356 * @option shadowUrl: String = null
7357 * The URL to the icon shadow image. If not specified, no shadow image will be created.
7358 *
7359 * @option shadowRetinaUrl: String = null
7360 *
7361 * @option shadowSize: Point = null
7362 * Size of the shadow image in pixels.
7363 *
7364 * @option shadowAnchor: Point = null
7365 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
7366 * as iconAnchor if not specified).
7367 *
7368 * @option className: String = ''
7369 * A custom class name to assign to both icon and shadow images. Empty by default.
7370 */
7371
7372 options: {
7373 popupAnchor: [0, 0],
7374 tooltipAnchor: [0, 0],
7375
7376 // @option crossOrigin: Boolean|String = false
7377 // Whether the crossOrigin attribute will be added to the tiles.
7378 // 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.
7379 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
7380 crossOrigin: false
7381 },
7382
7383 initialize: function (options) {
7384 setOptions(this, options);
7385 },
7386
7387 // @method createIcon(oldIcon?: HTMLElement): HTMLElement
7388 // Called internally when the icon has to be shown, returns a `<img>` HTML element
7389 // styled according to the options.
7390 createIcon: function (oldIcon) {
7391 return this._createIcon('icon', oldIcon);
7392 },
7393
7394 // @method createShadow(oldIcon?: HTMLElement): HTMLElement
7395 // As `createIcon`, but for the shadow beneath it.
7396 createShadow: function (oldIcon) {
7397 return this._createIcon('shadow', oldIcon);
7398 },
7399
7400 _createIcon: function (name, oldIcon) {
7401 var src = this._getIconUrl(name);
7402
7403 if (!src) {
7404 if (name === 'icon') {
7405 throw new Error('iconUrl not set in Icon options (see the docs).');
7406 }
7407 return null;
7408 }
7409
7410 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
7411 this._setIconStyles(img, name);
7412
7413 if (this.options.crossOrigin || this.options.crossOrigin === '') {
7414 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
7415 }
7416
7417 return img;
7418 },
7419
7420 _setIconStyles: function (img, name) {
7421 var options = this.options;
7422 var sizeOption = options[name + 'Size'];
7423
7424 if (typeof sizeOption === 'number') {
7425 sizeOption = [sizeOption, sizeOption];
7426 }
7427
7428 var size = toPoint(sizeOption),
7429 anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
7430 size && size.divideBy(2, true));
7431
7432 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
7433
7434 if (anchor) {
7435 img.style.marginLeft = (-anchor.x) + 'px';
7436 img.style.marginTop = (-anchor.y) + 'px';
7437 }
7438
7439 if (size) {
7440 img.style.width = size.x + 'px';
7441 img.style.height = size.y + 'px';
7442 }
7443 },
7444
7445 _createImg: function (src, el) {
7446 el = el || document.createElement('img');
7447 el.src = src;
7448 return el;
7449 },
7450
7451 _getIconUrl: function (name) {
7452 return Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
7453 }
7454 });
7455
7456
7457 // @factory L.icon(options: Icon options)
7458 // Creates an icon instance with the given options.
7459 function icon(options) {
7460 return new Icon(options);
7461 }
7462
7463 /*
7464 * @miniclass Icon.Default (Icon)
7465 * @aka L.Icon.Default
7466 * @section
7467 *
7468 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
7469 * no icon is specified. Points to the blue marker image distributed with Leaflet
7470 * releases.
7471 *
7472 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
7473 * (which is a set of `Icon options`).
7474 *
7475 * If you want to _completely_ replace the default icon, override the
7476 * `L.Marker.prototype.options.icon` with your own icon instead.
7477 */
7478
7479 var IconDefault = Icon.extend({
7480
7481 options: {
7482 iconUrl: 'marker-icon.png',
7483 iconRetinaUrl: 'marker-icon-2x.png',
7484 shadowUrl: 'marker-shadow.png',
7485 iconSize: [25, 41],
7486 iconAnchor: [12, 41],
7487 popupAnchor: [1, -34],
7488 tooltipAnchor: [16, -28],
7489 shadowSize: [41, 41]
7490 },
7491
7492 _getIconUrl: function (name) {
7493 if (typeof IconDefault.imagePath !== 'string') { // Deprecated, backwards-compatibility only
7494 IconDefault.imagePath = this._detectIconPath();
7495 }
7496
7497 // @option imagePath: String
7498 // `Icon.Default` will try to auto-detect the location of the
7499 // blue icon images. If you are placing these images in a non-standard
7500 // way, set this option to point to the right path.
7501 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
7502 },
7503
7504 _stripUrl: function (path) { // separate function to use in tests
7505 var strip = function (str, re, idx) {
7506 var match = re.exec(str);
7507 return match && match[idx];
7508 };
7509 path = strip(path, /^url\((['"])?(.+)\1\)$/, 2);
7510 return path && strip(path, /^(.*)marker-icon\.png$/, 1);
7511 },
7512
7513 _detectIconPath: function () {
7514 var el = create$1('div', 'leaflet-default-icon-path', document.body);
7515 var path = getStyle(el, 'background-image') ||
7516 getStyle(el, 'backgroundImage'); // IE8
7517
7518 document.body.removeChild(el);
7519 path = this._stripUrl(path);
7520 if (path) { return path; }
7521 var link = document.querySelector('link[href$="leaflet.css"]');
7522 if (!link) { return ''; }
7523 return link.href.substring(0, link.href.length - 'leaflet.css'.length - 1);
7524 }
7525 });
7526
7527 /*
7528 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7529 */
7530
7531
7532 /* @namespace Marker
7533 * @section Interaction handlers
7534 *
7535 * 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:
7536 *
7537 * ```js
7538 * marker.dragging.disable();
7539 * ```
7540 *
7541 * @property dragging: Handler
7542 * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
7543 */
7544
7545 var MarkerDrag = Handler.extend({
7546 initialize: function (marker) {
7547 this._marker = marker;
7548 },
7549
7550 addHooks: function () {
7551 var icon = this._marker._icon;
7552
7553 if (!this._draggable) {
7554 this._draggable = new Draggable(icon, icon, true);
7555 }
7556
7557 this._draggable.on({
7558 dragstart: this._onDragStart,
7559 predrag: this._onPreDrag,
7560 drag: this._onDrag,
7561 dragend: this._onDragEnd
7562 }, this).enable();
7563
7564 addClass(icon, 'leaflet-marker-draggable');
7565 },
7566
7567 removeHooks: function () {
7568 this._draggable.off({
7569 dragstart: this._onDragStart,
7570 predrag: this._onPreDrag,
7571 drag: this._onDrag,
7572 dragend: this._onDragEnd
7573 }, this).disable();
7574
7575 if (this._marker._icon) {
7576 removeClass(this._marker._icon, 'leaflet-marker-draggable');
7577 }
7578 },
7579
7580 moved: function () {
7581 return this._draggable && this._draggable._moved;
7582 },
7583
7584 _adjustPan: function (e) {
7585 var marker = this._marker,
7586 map = marker._map,
7587 speed = this._marker.options.autoPanSpeed,
7588 padding = this._marker.options.autoPanPadding,
7589 iconPos = getPosition(marker._icon),
7590 bounds = map.getPixelBounds(),
7591 origin = map.getPixelOrigin();
7592
7593 var panBounds = toBounds(
7594 bounds.min._subtract(origin).add(padding),
7595 bounds.max._subtract(origin).subtract(padding)
7596 );
7597
7598 if (!panBounds.contains(iconPos)) {
7599 // Compute incremental movement
7600 var movement = toPoint(
7601 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
7602 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
7603
7604 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
7605 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
7606 ).multiplyBy(speed);
7607
7608 map.panBy(movement, {animate: false});
7609
7610 this._draggable._newPos._add(movement);
7611 this._draggable._startPos._add(movement);
7612
7613 setPosition(marker._icon, this._draggable._newPos);
7614 this._onDrag(e);
7615
7616 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7617 }
7618 },
7619
7620 _onDragStart: function () {
7621 // @section Dragging events
7622 // @event dragstart: Event
7623 // Fired when the user starts dragging the marker.
7624
7625 // @event movestart: Event
7626 // Fired when the marker starts moving (because of dragging).
7627
7628 this._oldLatLng = this._marker.getLatLng();
7629
7630 // When using ES6 imports it could not be set when `Popup` was not imported as well
7631 this._marker.closePopup && this._marker.closePopup();
7632
7633 this._marker
7634 .fire('movestart')
7635 .fire('dragstart');
7636 },
7637
7638 _onPreDrag: function (e) {
7639 if (this._marker.options.autoPan) {
7640 cancelAnimFrame(this._panRequest);
7641 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7642 }
7643 },
7644
7645 _onDrag: function (e) {
7646 var marker = this._marker,
7647 shadow = marker._shadow,
7648 iconPos = getPosition(marker._icon),
7649 latlng = marker._map.layerPointToLatLng(iconPos);
7650
7651 // update shadow position
7652 if (shadow) {
7653 setPosition(shadow, iconPos);
7654 }
7655
7656 marker._latlng = latlng;
7657 e.latlng = latlng;
7658 e.oldLatLng = this._oldLatLng;
7659
7660 // @event drag: Event
7661 // Fired repeatedly while the user drags the marker.
7662 marker
7663 .fire('move', e)
7664 .fire('drag', e);
7665 },
7666
7667 _onDragEnd: function (e) {
7668 // @event dragend: DragEndEvent
7669 // Fired when the user stops dragging the marker.
7670
7671 cancelAnimFrame(this._panRequest);
7672
7673 // @event moveend: Event
7674 // Fired when the marker stops moving (because of dragging).
7675 delete this._oldLatLng;
7676 this._marker
7677 .fire('moveend')
7678 .fire('dragend', e);
7679 }
7680 });
7681
7682 /*
7683 * @class Marker
7684 * @inherits Interactive layer
7685 * @aka L.Marker
7686 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
7687 *
7688 * @example
7689 *
7690 * ```js
7691 * L.marker([50.5, 30.5]).addTo(map);
7692 * ```
7693 */
7694
7695 var Marker = Layer.extend({
7696
7697 // @section
7698 // @aka Marker options
7699 options: {
7700 // @option icon: Icon = *
7701 // Icon instance to use for rendering the marker.
7702 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
7703 // If not specified, a common instance of `L.Icon.Default` is used.
7704 icon: new IconDefault(),
7705
7706 // Option inherited from "Interactive layer" abstract class
7707 interactive: true,
7708
7709 // @option keyboard: Boolean = true
7710 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
7711 keyboard: true,
7712
7713 // @option title: String = ''
7714 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
7715 // [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled).
7716 title: '',
7717
7718 // @option alt: String = 'Marker'
7719 // Text for the `alt` attribute of the icon image.
7720 // [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled).
7721 alt: 'Marker',
7722
7723 // @option zIndexOffset: Number = 0
7724 // 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).
7725 zIndexOffset: 0,
7726
7727 // @option opacity: Number = 1.0
7728 // The opacity of the marker.
7729 opacity: 1,
7730
7731 // @option riseOnHover: Boolean = false
7732 // If `true`, the marker will get on top of others when you hover the mouse over it.
7733 riseOnHover: false,
7734
7735 // @option riseOffset: Number = 250
7736 // The z-index offset used for the `riseOnHover` feature.
7737 riseOffset: 250,
7738
7739 // @option pane: String = 'markerPane'
7740 // `Map pane` where the markers icon will be added.
7741 pane: 'markerPane',
7742
7743 // @option shadowPane: String = 'shadowPane'
7744 // `Map pane` where the markers shadow will be added.
7745 shadowPane: 'shadowPane',
7746
7747 // @option bubblingMouseEvents: Boolean = false
7748 // When `true`, a mouse event on this marker will trigger the same event on the map
7749 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7750 bubblingMouseEvents: false,
7751
7752 // @option autoPanOnFocus: Boolean = true
7753 // When `true`, the map will pan whenever the marker is focused (via
7754 // e.g. pressing `tab` on the keyboard) to ensure the marker is
7755 // visible within the map's bounds
7756 autoPanOnFocus: true,
7757
7758 // @section Draggable marker options
7759 // @option draggable: Boolean = false
7760 // Whether the marker is draggable with mouse/touch or not.
7761 draggable: false,
7762
7763 // @option autoPan: Boolean = false
7764 // Whether to pan the map when dragging this marker near its edge or not.
7765 autoPan: false,
7766
7767 // @option autoPanPadding: Point = Point(50, 50)
7768 // Distance (in pixels to the left/right and to the top/bottom) of the
7769 // map edge to start panning the map.
7770 autoPanPadding: [50, 50],
7771
7772 // @option autoPanSpeed: Number = 10
7773 // Number of pixels the map should pan by.
7774 autoPanSpeed: 10
7775 },
7776
7777 /* @section
7778 *
7779 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
7780 */
7781
7782 initialize: function (latlng, options) {
7783 setOptions(this, options);
7784 this._latlng = toLatLng(latlng);
7785 },
7786
7787 onAdd: function (map) {
7788 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
7789
7790 if (this._zoomAnimated) {
7791 map.on('zoomanim', this._animateZoom, this);
7792 }
7793
7794 this._initIcon();
7795 this.update();
7796 },
7797
7798 onRemove: function (map) {
7799 if (this.dragging && this.dragging.enabled()) {
7800 this.options.draggable = true;
7801 this.dragging.removeHooks();
7802 }
7803 delete this.dragging;
7804
7805 if (this._zoomAnimated) {
7806 map.off('zoomanim', this._animateZoom, this);
7807 }
7808
7809 this._removeIcon();
7810 this._removeShadow();
7811 },
7812
7813 getEvents: function () {
7814 return {
7815 zoom: this.update,
7816 viewreset: this.update
7817 };
7818 },
7819
7820 // @method getLatLng: LatLng
7821 // Returns the current geographical position of the marker.
7822 getLatLng: function () {
7823 return this._latlng;
7824 },
7825
7826 // @method setLatLng(latlng: LatLng): this
7827 // Changes the marker position to the given point.
7828 setLatLng: function (latlng) {
7829 var oldLatLng = this._latlng;
7830 this._latlng = toLatLng(latlng);
7831 this.update();
7832
7833 // @event move: Event
7834 // 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`.
7835 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7836 },
7837
7838 // @method setZIndexOffset(offset: Number): this
7839 // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
7840 setZIndexOffset: function (offset) {
7841 this.options.zIndexOffset = offset;
7842 return this.update();
7843 },
7844
7845 // @method getIcon: Icon
7846 // Returns the current icon used by the marker
7847 getIcon: function () {
7848 return this.options.icon;
7849 },
7850
7851 // @method setIcon(icon: Icon): this
7852 // Changes the marker icon.
7853 setIcon: function (icon) {
7854
7855 this.options.icon = icon;
7856
7857 if (this._map) {
7858 this._initIcon();
7859 this.update();
7860 }
7861
7862 if (this._popup) {
7863 this.bindPopup(this._popup, this._popup.options);
7864 }
7865
7866 return this;
7867 },
7868
7869 getElement: function () {
7870 return this._icon;
7871 },
7872
7873 update: function () {
7874
7875 if (this._icon && this._map) {
7876 var pos = this._map.latLngToLayerPoint(this._latlng).round();
7877 this._setPos(pos);
7878 }
7879
7880 return this;
7881 },
7882
7883 _initIcon: function () {
7884 var options = this.options,
7885 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
7886
7887 var icon = options.icon.createIcon(this._icon),
7888 addIcon = false;
7889
7890 // if we're not reusing the icon, remove the old one and init new one
7891 if (icon !== this._icon) {
7892 if (this._icon) {
7893 this._removeIcon();
7894 }
7895 addIcon = true;
7896
7897 if (options.title) {
7898 icon.title = options.title;
7899 }
7900
7901 if (icon.tagName === 'IMG') {
7902 icon.alt = options.alt || '';
7903 }
7904 }
7905
7906 addClass(icon, classToAdd);
7907
7908 if (options.keyboard) {
7909 icon.tabIndex = '0';
7910 icon.setAttribute('role', 'button');
7911 }
7912
7913 this._icon = icon;
7914
7915 if (options.riseOnHover) {
7916 this.on({
7917 mouseover: this._bringToFront,
7918 mouseout: this._resetZIndex
7919 });
7920 }
7921
7922 if (this.options.autoPanOnFocus) {
7923 on(icon, 'focus', this._panOnFocus, this);
7924 }
7925
7926 var newShadow = options.icon.createShadow(this._shadow),
7927 addShadow = false;
7928
7929 if (newShadow !== this._shadow) {
7930 this._removeShadow();
7931 addShadow = true;
7932 }
7933
7934 if (newShadow) {
7935 addClass(newShadow, classToAdd);
7936 newShadow.alt = '';
7937 }
7938 this._shadow = newShadow;
7939
7940
7941 if (options.opacity < 1) {
7942 this._updateOpacity();
7943 }
7944
7945
7946 if (addIcon) {
7947 this.getPane().appendChild(this._icon);
7948 }
7949 this._initInteraction();
7950 if (newShadow && addShadow) {
7951 this.getPane(options.shadowPane).appendChild(this._shadow);
7952 }
7953 },
7954
7955 _removeIcon: function () {
7956 if (this.options.riseOnHover) {
7957 this.off({
7958 mouseover: this._bringToFront,
7959 mouseout: this._resetZIndex
7960 });
7961 }
7962
7963 if (this.options.autoPanOnFocus) {
7964 off(this._icon, 'focus', this._panOnFocus, this);
7965 }
7966
7967 remove(this._icon);
7968 this.removeInteractiveTarget(this._icon);
7969
7970 this._icon = null;
7971 },
7972
7973 _removeShadow: function () {
7974 if (this._shadow) {
7975 remove(this._shadow);
7976 }
7977 this._shadow = null;
7978 },
7979
7980 _setPos: function (pos) {
7981
7982 if (this._icon) {
7983 setPosition(this._icon, pos);
7984 }
7985
7986 if (this._shadow) {
7987 setPosition(this._shadow, pos);
7988 }
7989
7990 this._zIndex = pos.y + this.options.zIndexOffset;
7991
7992 this._resetZIndex();
7993 },
7994
7995 _updateZIndex: function (offset) {
7996 if (this._icon) {
7997 this._icon.style.zIndex = this._zIndex + offset;
7998 }
7999 },
8000
8001 _animateZoom: function (opt) {
8002 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
8003
8004 this._setPos(pos);
8005 },
8006
8007 _initInteraction: function () {
8008
8009 if (!this.options.interactive) { return; }
8010
8011 addClass(this._icon, 'leaflet-interactive');
8012
8013 this.addInteractiveTarget(this._icon);
8014
8015 if (MarkerDrag) {
8016 var draggable = this.options.draggable;
8017 if (this.dragging) {
8018 draggable = this.dragging.enabled();
8019 this.dragging.disable();
8020 }
8021
8022 this.dragging = new MarkerDrag(this);
8023
8024 if (draggable) {
8025 this.dragging.enable();
8026 }
8027 }
8028 },
8029
8030 // @method setOpacity(opacity: Number): this
8031 // Changes the opacity of the marker.
8032 setOpacity: function (opacity) {
8033 this.options.opacity = opacity;
8034 if (this._map) {
8035 this._updateOpacity();
8036 }
8037
8038 return this;
8039 },
8040
8041 _updateOpacity: function () {
8042 var opacity = this.options.opacity;
8043
8044 if (this._icon) {
8045 setOpacity(this._icon, opacity);
8046 }
8047
8048 if (this._shadow) {
8049 setOpacity(this._shadow, opacity);
8050 }
8051 },
8052
8053 _bringToFront: function () {
8054 this._updateZIndex(this.options.riseOffset);
8055 },
8056
8057 _resetZIndex: function () {
8058 this._updateZIndex(0);
8059 },
8060
8061 _panOnFocus: function () {
8062 var map = this._map;
8063 if (!map) { return; }
8064
8065 var iconOpts = this.options.icon.options;
8066 var size = iconOpts.iconSize ? toPoint(iconOpts.iconSize) : toPoint(0, 0);
8067 var anchor = iconOpts.iconAnchor ? toPoint(iconOpts.iconAnchor) : toPoint(0, 0);
8068
8069 map.panInside(this._latlng, {
8070 paddingTopLeft: anchor,
8071 paddingBottomRight: size.subtract(anchor)
8072 });
8073 },
8074
8075 _getPopupAnchor: function () {
8076 return this.options.icon.options.popupAnchor;
8077 },
8078
8079 _getTooltipAnchor: function () {
8080 return this.options.icon.options.tooltipAnchor;
8081 }
8082 });
8083
8084
8085 // factory L.marker(latlng: LatLng, options? : Marker options)
8086
8087 // @factory L.marker(latlng: LatLng, options? : Marker options)
8088 // Instantiates a Marker object given a geographical point and optionally an options object.
8089 function marker(latlng, options) {
8090 return new Marker(latlng, options);
8091 }
8092
8093 /*
8094 * @class Path
8095 * @aka L.Path
8096 * @inherits Interactive layer
8097 *
8098 * An abstract class that contains options and constants shared between vector
8099 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
8100 */
8101
8102 var Path = Layer.extend({
8103
8104 // @section
8105 // @aka Path options
8106 options: {
8107 // @option stroke: Boolean = true
8108 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
8109 stroke: true,
8110
8111 // @option color: String = '#3388ff'
8112 // Stroke color
8113 color: '#3388ff',
8114
8115 // @option weight: Number = 3
8116 // Stroke width in pixels
8117 weight: 3,
8118
8119 // @option opacity: Number = 1.0
8120 // Stroke opacity
8121 opacity: 1,
8122
8123 // @option lineCap: String= 'round'
8124 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
8125 lineCap: 'round',
8126
8127 // @option lineJoin: String = 'round'
8128 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
8129 lineJoin: 'round',
8130
8131 // @option dashArray: String = null
8132 // 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).
8133 dashArray: null,
8134
8135 // @option dashOffset: String = null
8136 // 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).
8137 dashOffset: null,
8138
8139 // @option fill: Boolean = depends
8140 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
8141 fill: false,
8142
8143 // @option fillColor: String = *
8144 // Fill color. Defaults to the value of the [`color`](#path-color) option
8145 fillColor: null,
8146
8147 // @option fillOpacity: Number = 0.2
8148 // Fill opacity.
8149 fillOpacity: 0.2,
8150
8151 // @option fillRule: String = 'evenodd'
8152 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
8153 fillRule: 'evenodd',
8154
8155 // className: '',
8156
8157 // Option inherited from "Interactive layer" abstract class
8158 interactive: true,
8159
8160 // @option bubblingMouseEvents: Boolean = true
8161 // When `true`, a mouse event on this path will trigger the same event on the map
8162 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
8163 bubblingMouseEvents: true
8164 },
8165
8166 beforeAdd: function (map) {
8167 // Renderer is set here because we need to call renderer.getEvents
8168 // before this.getEvents.
8169 this._renderer = map.getRenderer(this);
8170 },
8171
8172 onAdd: function () {
8173 this._renderer._initPath(this);
8174 this._reset();
8175 this._renderer._addPath(this);
8176 },
8177
8178 onRemove: function () {
8179 this._renderer._removePath(this);
8180 },
8181
8182 // @method redraw(): this
8183 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
8184 redraw: function () {
8185 if (this._map) {
8186 this._renderer._updatePath(this);
8187 }
8188 return this;
8189 },
8190
8191 // @method setStyle(style: Path options): this
8192 // Changes the appearance of a Path based on the options in the `Path options` object.
8193 setStyle: function (style) {
8194 setOptions(this, style);
8195 if (this._renderer) {
8196 this._renderer._updateStyle(this);
8197 if (this.options.stroke && style && Object.prototype.hasOwnProperty.call(style, 'weight')) {
8198 this._updateBounds();
8199 }
8200 }
8201 return this;
8202 },
8203
8204 // @method bringToFront(): this
8205 // Brings the layer to the top of all path layers.
8206 bringToFront: function () {
8207 if (this._renderer) {
8208 this._renderer._bringToFront(this);
8209 }
8210 return this;
8211 },
8212
8213 // @method bringToBack(): this
8214 // Brings the layer to the bottom of all path layers.
8215 bringToBack: function () {
8216 if (this._renderer) {
8217 this._renderer._bringToBack(this);
8218 }
8219 return this;
8220 },
8221
8222 getElement: function () {
8223 return this._path;
8224 },
8225
8226 _reset: function () {
8227 // defined in child classes
8228 this._project();
8229 this._update();
8230 },
8231
8232 _clickTolerance: function () {
8233 // used when doing hit detection for Canvas layers
8234 return (this.options.stroke ? this.options.weight / 2 : 0) +
8235 (this._renderer.options.tolerance || 0);
8236 }
8237 });
8238
8239 /*
8240 * @class CircleMarker
8241 * @aka L.CircleMarker
8242 * @inherits Path
8243 *
8244 * A circle of a fixed size with radius specified in pixels. Extends `Path`.
8245 */
8246
8247 var CircleMarker = Path.extend({
8248
8249 // @section
8250 // @aka CircleMarker options
8251 options: {
8252 fill: true,
8253
8254 // @option radius: Number = 10
8255 // Radius of the circle marker, in pixels
8256 radius: 10
8257 },
8258
8259 initialize: function (latlng, options) {
8260 setOptions(this, options);
8261 this._latlng = toLatLng(latlng);
8262 this._radius = this.options.radius;
8263 },
8264
8265 // @method setLatLng(latLng: LatLng): this
8266 // Sets the position of a circle marker to a new location.
8267 setLatLng: function (latlng) {
8268 var oldLatLng = this._latlng;
8269 this._latlng = toLatLng(latlng);
8270 this.redraw();
8271
8272 // @event move: Event
8273 // Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
8274 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
8275 },
8276
8277 // @method getLatLng(): LatLng
8278 // Returns the current geographical position of the circle marker
8279 getLatLng: function () {
8280 return this._latlng;
8281 },
8282
8283 // @method setRadius(radius: Number): this
8284 // Sets the radius of a circle marker. Units are in pixels.
8285 setRadius: function (radius) {
8286 this.options.radius = this._radius = radius;
8287 return this.redraw();
8288 },
8289
8290 // @method getRadius(): Number
8291 // Returns the current radius of the circle
8292 getRadius: function () {
8293 return this._radius;
8294 },
8295
8296 setStyle : function (options) {
8297 var radius = options && options.radius || this._radius;
8298 Path.prototype.setStyle.call(this, options);
8299 this.setRadius(radius);
8300 return this;
8301 },
8302
8303 _project: function () {
8304 this._point = this._map.latLngToLayerPoint(this._latlng);
8305 this._updateBounds();
8306 },
8307
8308 _updateBounds: function () {
8309 var r = this._radius,
8310 r2 = this._radiusY || r,
8311 w = this._clickTolerance(),
8312 p = [r + w, r2 + w];
8313 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
8314 },
8315
8316 _update: function () {
8317 if (this._map) {
8318 this._updatePath();
8319 }
8320 },
8321
8322 _updatePath: function () {
8323 this._renderer._updateCircle(this);
8324 },
8325
8326 _empty: function () {
8327 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
8328 },
8329
8330 // Needed by the `Canvas` renderer for interactivity
8331 _containsPoint: function (p) {
8332 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
8333 }
8334 });
8335
8336
8337 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
8338 // Instantiates a circle marker object given a geographical point, and an optional options object.
8339 function circleMarker(latlng, options) {
8340 return new CircleMarker(latlng, options);
8341 }
8342
8343 /*
8344 * @class Circle
8345 * @aka L.Circle
8346 * @inherits CircleMarker
8347 *
8348 * A class for drawing circle overlays on a map. Extends `CircleMarker`.
8349 *
8350 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
8351 *
8352 * @example
8353 *
8354 * ```js
8355 * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
8356 * ```
8357 */
8358
8359 var Circle = CircleMarker.extend({
8360
8361 initialize: function (latlng, options, legacyOptions) {
8362 if (typeof options === 'number') {
8363 // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
8364 options = extend({}, legacyOptions, {radius: options});
8365 }
8366 setOptions(this, options);
8367 this._latlng = toLatLng(latlng);
8368
8369 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
8370
8371 // @section
8372 // @aka Circle options
8373 // @option radius: Number; Radius of the circle, in meters.
8374 this._mRadius = this.options.radius;
8375 },
8376
8377 // @method setRadius(radius: Number): this
8378 // Sets the radius of a circle. Units are in meters.
8379 setRadius: function (radius) {
8380 this._mRadius = radius;
8381 return this.redraw();
8382 },
8383
8384 // @method getRadius(): Number
8385 // Returns the current radius of a circle. Units are in meters.
8386 getRadius: function () {
8387 return this._mRadius;
8388 },
8389
8390 // @method getBounds(): LatLngBounds
8391 // Returns the `LatLngBounds` of the path.
8392 getBounds: function () {
8393 var half = [this._radius, this._radiusY || this._radius];
8394
8395 return new LatLngBounds(
8396 this._map.layerPointToLatLng(this._point.subtract(half)),
8397 this._map.layerPointToLatLng(this._point.add(half)));
8398 },
8399
8400 setStyle: Path.prototype.setStyle,
8401
8402 _project: function () {
8403
8404 var lng = this._latlng.lng,
8405 lat = this._latlng.lat,
8406 map = this._map,
8407 crs = map.options.crs;
8408
8409 if (crs.distance === Earth.distance) {
8410 var d = Math.PI / 180,
8411 latR = (this._mRadius / Earth.R) / d,
8412 top = map.project([lat + latR, lng]),
8413 bottom = map.project([lat - latR, lng]),
8414 p = top.add(bottom).divideBy(2),
8415 lat2 = map.unproject(p).lat,
8416 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
8417 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
8418
8419 if (isNaN(lngR) || lngR === 0) {
8420 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
8421 }
8422
8423 this._point = p.subtract(map.getPixelOrigin());
8424 this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
8425 this._radiusY = p.y - top.y;
8426
8427 } else {
8428 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
8429
8430 this._point = map.latLngToLayerPoint(this._latlng);
8431 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
8432 }
8433
8434 this._updateBounds();
8435 }
8436 });
8437
8438 // @factory L.circle(latlng: LatLng, options?: Circle options)
8439 // Instantiates a circle object given a geographical point, and an options object
8440 // which contains the circle radius.
8441 // @alternative
8442 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
8443 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
8444 // Do not use in new applications or plugins.
8445 function circle(latlng, options, legacyOptions) {
8446 return new Circle(latlng, options, legacyOptions);
8447 }
8448
8449 /*
8450 * @class Polyline
8451 * @aka L.Polyline
8452 * @inherits Path
8453 *
8454 * A class for drawing polyline overlays on a map. Extends `Path`.
8455 *
8456 * @example
8457 *
8458 * ```js
8459 * // create a red polyline from an array of LatLng points
8460 * var latlngs = [
8461 * [45.51, -122.68],
8462 * [37.77, -122.43],
8463 * [34.04, -118.2]
8464 * ];
8465 *
8466 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
8467 *
8468 * // zoom the map to the polyline
8469 * map.fitBounds(polyline.getBounds());
8470 * ```
8471 *
8472 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
8473 *
8474 * ```js
8475 * // create a red polyline from an array of arrays of LatLng points
8476 * var latlngs = [
8477 * [[45.51, -122.68],
8478 * [37.77, -122.43],
8479 * [34.04, -118.2]],
8480 * [[40.78, -73.91],
8481 * [41.83, -87.62],
8482 * [32.76, -96.72]]
8483 * ];
8484 * ```
8485 */
8486
8487
8488 var Polyline = Path.extend({
8489
8490 // @section
8491 // @aka Polyline options
8492 options: {
8493 // @option smoothFactor: Number = 1.0
8494 // How much to simplify the polyline on each zoom level. More means
8495 // better performance and smoother look, and less means more accurate representation.
8496 smoothFactor: 1.0,
8497
8498 // @option noClip: Boolean = false
8499 // Disable polyline clipping.
8500 noClip: false
8501 },
8502
8503 initialize: function (latlngs, options) {
8504 setOptions(this, options);
8505 this._setLatLngs(latlngs);
8506 },
8507
8508 // @method getLatLngs(): LatLng[]
8509 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8510 getLatLngs: function () {
8511 return this._latlngs;
8512 },
8513
8514 // @method setLatLngs(latlngs: LatLng[]): this
8515 // Replaces all the points in the polyline with the given array of geographical points.
8516 setLatLngs: function (latlngs) {
8517 this._setLatLngs(latlngs);
8518 return this.redraw();
8519 },
8520
8521 // @method isEmpty(): Boolean
8522 // Returns `true` if the Polyline has no LatLngs.
8523 isEmpty: function () {
8524 return !this._latlngs.length;
8525 },
8526
8527 // @method closestLayerPoint(p: Point): Point
8528 // Returns the point closest to `p` on the Polyline.
8529 closestLayerPoint: function (p) {
8530 var minDistance = Infinity,
8531 minPoint = null,
8532 closest = _sqClosestPointOnSegment,
8533 p1, p2;
8534
8535 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8536 var points = this._parts[j];
8537
8538 for (var i = 1, len = points.length; i < len; i++) {
8539 p1 = points[i - 1];
8540 p2 = points[i];
8541
8542 var sqDist = closest(p, p1, p2, true);
8543
8544 if (sqDist < minDistance) {
8545 minDistance = sqDist;
8546 minPoint = closest(p, p1, p2);
8547 }
8548 }
8549 }
8550 if (minPoint) {
8551 minPoint.distance = Math.sqrt(minDistance);
8552 }
8553 return minPoint;
8554 },
8555
8556 // @method getCenter(): LatLng
8557 // Returns the center ([centroid](https://en.wikipedia.org/wiki/Centroid)) of the polyline.
8558 getCenter: function () {
8559 // throws error when not yet added to map as this center calculation requires projected coordinates
8560 if (!this._map) {
8561 throw new Error('Must add layer to map before using getCenter()');
8562 }
8563 return polylineCenter(this._defaultShape(), this._map.options.crs);
8564 },
8565
8566 // @method getBounds(): LatLngBounds
8567 // Returns the `LatLngBounds` of the path.
8568 getBounds: function () {
8569 return this._bounds;
8570 },
8571
8572 // @method addLatLng(latlng: LatLng, latlngs?: LatLng[]): this
8573 // Adds a given point to the polyline. By default, adds to the first ring of
8574 // the polyline in case of a multi-polyline, but can be overridden by passing
8575 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8576 addLatLng: function (latlng, latlngs) {
8577 latlngs = latlngs || this._defaultShape();
8578 latlng = toLatLng(latlng);
8579 latlngs.push(latlng);
8580 this._bounds.extend(latlng);
8581 return this.redraw();
8582 },
8583
8584 _setLatLngs: function (latlngs) {
8585 this._bounds = new LatLngBounds();
8586 this._latlngs = this._convertLatLngs(latlngs);
8587 },
8588
8589 _defaultShape: function () {
8590 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
8591 },
8592
8593 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8594 _convertLatLngs: function (latlngs) {
8595 var result = [],
8596 flat = isFlat(latlngs);
8597
8598 for (var i = 0, len = latlngs.length; i < len; i++) {
8599 if (flat) {
8600 result[i] = toLatLng(latlngs[i]);
8601 this._bounds.extend(result[i]);
8602 } else {
8603 result[i] = this._convertLatLngs(latlngs[i]);
8604 }
8605 }
8606
8607 return result;
8608 },
8609
8610 _project: function () {
8611 var pxBounds = new Bounds();
8612 this._rings = [];
8613 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8614
8615 if (this._bounds.isValid() && pxBounds.isValid()) {
8616 this._rawPxBounds = pxBounds;
8617 this._updateBounds();
8618 }
8619 },
8620
8621 _updateBounds: function () {
8622 var w = this._clickTolerance(),
8623 p = new Point(w, w);
8624
8625 if (!this._rawPxBounds) {
8626 return;
8627 }
8628
8629 this._pxBounds = new Bounds([
8630 this._rawPxBounds.min.subtract(p),
8631 this._rawPxBounds.max.add(p)
8632 ]);
8633 },
8634
8635 // recursively turns latlngs into a set of rings with projected coordinates
8636 _projectLatlngs: function (latlngs, result, projectedBounds) {
8637 var flat = latlngs[0] instanceof LatLng,
8638 len = latlngs.length,
8639 i, ring;
8640
8641 if (flat) {
8642 ring = [];
8643 for (i = 0; i < len; i++) {
8644 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8645 projectedBounds.extend(ring[i]);
8646 }
8647 result.push(ring);
8648 } else {
8649 for (i = 0; i < len; i++) {
8650 this._projectLatlngs(latlngs[i], result, projectedBounds);
8651 }
8652 }
8653 },
8654
8655 // clip polyline by renderer bounds so that we have less to render for performance
8656 _clipPoints: function () {
8657 var bounds = this._renderer._bounds;
8658
8659 this._parts = [];
8660 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8661 return;
8662 }
8663
8664 if (this.options.noClip) {
8665 this._parts = this._rings;
8666 return;
8667 }
8668
8669 var parts = this._parts,
8670 i, j, k, len, len2, segment, points;
8671
8672 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8673 points = this._rings[i];
8674
8675 for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8676 segment = clipSegment(points[j], points[j + 1], bounds, j, true);
8677
8678 if (!segment) { continue; }
8679
8680 parts[k] = parts[k] || [];
8681 parts[k].push(segment[0]);
8682
8683 // if segment goes out of screen, or it's the last one, it's the end of the line part
8684 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8685 parts[k].push(segment[1]);
8686 k++;
8687 }
8688 }
8689 }
8690 },
8691
8692 // simplify each clipped part of the polyline for performance
8693 _simplifyPoints: function () {
8694 var parts = this._parts,
8695 tolerance = this.options.smoothFactor;
8696
8697 for (var i = 0, len = parts.length; i < len; i++) {
8698 parts[i] = simplify(parts[i], tolerance);
8699 }
8700 },
8701
8702 _update: function () {
8703 if (!this._map) { return; }
8704
8705 this._clipPoints();
8706 this._simplifyPoints();
8707 this._updatePath();
8708 },
8709
8710 _updatePath: function () {
8711 this._renderer._updatePoly(this);
8712 },
8713
8714 // Needed by the `Canvas` renderer for interactivity
8715 _containsPoint: function (p, closed) {
8716 var i, j, k, len, len2, part,
8717 w = this._clickTolerance();
8718
8719 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8720
8721 // hit detection for polylines
8722 for (i = 0, len = this._parts.length; i < len; i++) {
8723 part = this._parts[i];
8724
8725 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8726 if (!closed && (j === 0)) { continue; }
8727
8728 if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
8729 return true;
8730 }
8731 }
8732 }
8733 return false;
8734 }
8735 });
8736
8737 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8738 // Instantiates a polyline object given an array of geographical points and
8739 // optionally an options object. You can create a `Polyline` object with
8740 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8741 // of geographic points.
8742 function polyline(latlngs, options) {
8743 return new Polyline(latlngs, options);
8744 }
8745
8746 // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
8747 Polyline._flat = _flat;
8748
8749 /*
8750 * @class Polygon
8751 * @aka L.Polygon
8752 * @inherits Polyline
8753 *
8754 * A class for drawing polygon overlays on a map. Extends `Polyline`.
8755 *
8756 * 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.
8757 *
8758 *
8759 * @example
8760 *
8761 * ```js
8762 * // create a red polygon from an array of LatLng points
8763 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
8764 *
8765 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8766 *
8767 * // zoom the map to the polygon
8768 * map.fitBounds(polygon.getBounds());
8769 * ```
8770 *
8771 * 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:
8772 *
8773 * ```js
8774 * var latlngs = [
8775 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8776 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8777 * ];
8778 * ```
8779 *
8780 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8781 *
8782 * ```js
8783 * var latlngs = [
8784 * [ // first polygon
8785 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8786 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8787 * ],
8788 * [ // second polygon
8789 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
8790 * ]
8791 * ];
8792 * ```
8793 */
8794
8795 var Polygon = Polyline.extend({
8796
8797 options: {
8798 fill: true
8799 },
8800
8801 isEmpty: function () {
8802 return !this._latlngs.length || !this._latlngs[0].length;
8803 },
8804
8805 // @method getCenter(): LatLng
8806 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the Polygon.
8807 getCenter: function () {
8808 // throws error when not yet added to map as this center calculation requires projected coordinates
8809 if (!this._map) {
8810 throw new Error('Must add layer to map before using getCenter()');
8811 }
8812 return polygonCenter(this._defaultShape(), this._map.options.crs);
8813 },
8814
8815 _convertLatLngs: function (latlngs) {
8816 var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
8817 len = result.length;
8818
8819 // remove last point if it equals first one
8820 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
8821 result.pop();
8822 }
8823 return result;
8824 },
8825
8826 _setLatLngs: function (latlngs) {
8827 Polyline.prototype._setLatLngs.call(this, latlngs);
8828 if (isFlat(this._latlngs)) {
8829 this._latlngs = [this._latlngs];
8830 }
8831 },
8832
8833 _defaultShape: function () {
8834 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8835 },
8836
8837 _clipPoints: function () {
8838 // polygons need a different clipping algorithm so we redefine that
8839
8840 var bounds = this._renderer._bounds,
8841 w = this.options.weight,
8842 p = new Point(w, w);
8843
8844 // increase clip padding by stroke width to avoid stroke on clip edges
8845 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
8846
8847 this._parts = [];
8848 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8849 return;
8850 }
8851
8852 if (this.options.noClip) {
8853 this._parts = this._rings;
8854 return;
8855 }
8856
8857 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8858 clipped = clipPolygon(this._rings[i], bounds, true);
8859 if (clipped.length) {
8860 this._parts.push(clipped);
8861 }
8862 }
8863 },
8864
8865 _updatePath: function () {
8866 this._renderer._updatePoly(this, true);
8867 },
8868
8869 // Needed by the `Canvas` renderer for interactivity
8870 _containsPoint: function (p) {
8871 var inside = false,
8872 part, p1, p2, i, j, k, len, len2;
8873
8874 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8875
8876 // ray casting algorithm for detecting if point is in polygon
8877 for (i = 0, len = this._parts.length; i < len; i++) {
8878 part = this._parts[i];
8879
8880 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8881 p1 = part[j];
8882 p2 = part[k];
8883
8884 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)) {
8885 inside = !inside;
8886 }
8887 }
8888 }
8889
8890 // also check if it's on polygon stroke
8891 return inside || Polyline.prototype._containsPoint.call(this, p, true);
8892 }
8893
8894 });
8895
8896
8897 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8898 function polygon(latlngs, options) {
8899 return new Polygon(latlngs, options);
8900 }
8901
8902 /*
8903 * @class GeoJSON
8904 * @aka L.GeoJSON
8905 * @inherits FeatureGroup
8906 *
8907 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
8908 * GeoJSON data and display it on the map. Extends `FeatureGroup`.
8909 *
8910 * @example
8911 *
8912 * ```js
8913 * L.geoJSON(data, {
8914 * style: function (feature) {
8915 * return {color: feature.properties.color};
8916 * }
8917 * }).bindPopup(function (layer) {
8918 * return layer.feature.properties.description;
8919 * }).addTo(map);
8920 * ```
8921 */
8922
8923 var GeoJSON = FeatureGroup.extend({
8924
8925 /* @section
8926 * @aka GeoJSON options
8927 *
8928 * @option pointToLayer: Function = *
8929 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
8930 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
8931 * The default is to spawn a default `Marker`:
8932 * ```js
8933 * function(geoJsonPoint, latlng) {
8934 * return L.marker(latlng);
8935 * }
8936 * ```
8937 *
8938 * @option style: Function = *
8939 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
8940 * called internally when data is added.
8941 * The default value is to not override any defaults:
8942 * ```js
8943 * function (geoJsonFeature) {
8944 * return {}
8945 * }
8946 * ```
8947 *
8948 * @option onEachFeature: Function = *
8949 * A `Function` that will be called once for each created `Feature`, after it has
8950 * been created and styled. Useful for attaching events and popups to features.
8951 * The default is to do nothing with the newly created layers:
8952 * ```js
8953 * function (feature, layer) {}
8954 * ```
8955 *
8956 * @option filter: Function = *
8957 * A `Function` that will be used to decide whether to include a feature or not.
8958 * The default is to include all features:
8959 * ```js
8960 * function (geoJsonFeature) {
8961 * return true;
8962 * }
8963 * ```
8964 * Note: dynamically changing the `filter` option will have effect only on newly
8965 * added data. It will _not_ re-evaluate already included features.
8966 *
8967 * @option coordsToLatLng: Function = *
8968 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
8969 * The default is the `coordsToLatLng` static method.
8970 *
8971 * @option markersInheritOptions: Boolean = false
8972 * Whether default Markers for "Point" type Features inherit from group options.
8973 */
8974
8975 initialize: function (geojson, options) {
8976 setOptions(this, options);
8977
8978 this._layers = {};
8979
8980 if (geojson) {
8981 this.addData(geojson);
8982 }
8983 },
8984
8985 // @method addData( <GeoJSON> data ): this
8986 // Adds a GeoJSON object to the layer.
8987 addData: function (geojson) {
8988 var features = isArray(geojson) ? geojson : geojson.features,
8989 i, len, feature;
8990
8991 if (features) {
8992 for (i = 0, len = features.length; i < len; i++) {
8993 // only add this if geometry or geometries are set and not null
8994 feature = features[i];
8995 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
8996 this.addData(feature);
8997 }
8998 }
8999 return this;
9000 }
9001
9002 var options = this.options;
9003
9004 if (options.filter && !options.filter(geojson)) { return this; }
9005
9006 var layer = geometryToLayer(geojson, options);
9007 if (!layer) {
9008 return this;
9009 }
9010 layer.feature = asFeature(geojson);
9011
9012 layer.defaultOptions = layer.options;
9013 this.resetStyle(layer);
9014
9015 if (options.onEachFeature) {
9016 options.onEachFeature(geojson, layer);
9017 }
9018
9019 return this.addLayer(layer);
9020 },
9021
9022 // @method resetStyle( <Path> layer? ): this
9023 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
9024 // If `layer` is omitted, the style of all features in the current layer is reset.
9025 resetStyle: function (layer) {
9026 if (layer === undefined) {
9027 return this.eachLayer(this.resetStyle, this);
9028 }
9029 // reset any custom styles
9030 layer.options = extend({}, layer.defaultOptions);
9031 this._setLayerStyle(layer, this.options.style);
9032 return this;
9033 },
9034
9035 // @method setStyle( <Function> style ): this
9036 // Changes styles of GeoJSON vector layers with the given style function.
9037 setStyle: function (style) {
9038 return this.eachLayer(function (layer) {
9039 this._setLayerStyle(layer, style);
9040 }, this);
9041 },
9042
9043 _setLayerStyle: function (layer, style) {
9044 if (layer.setStyle) {
9045 if (typeof style === 'function') {
9046 style = style(layer.feature);
9047 }
9048 layer.setStyle(style);
9049 }
9050 }
9051 });
9052
9053 // @section
9054 // There are several static functions which can be called without instantiating L.GeoJSON:
9055
9056 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
9057 // Creates a `Layer` from a given GeoJSON feature. Can use a custom
9058 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
9059 // functions if provided as options.
9060 function geometryToLayer(geojson, options) {
9061
9062 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
9063 coords = geometry ? geometry.coordinates : null,
9064 layers = [],
9065 pointToLayer = options && options.pointToLayer,
9066 _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
9067 latlng, latlngs, i, len;
9068
9069 if (!coords && !geometry) {
9070 return null;
9071 }
9072
9073 switch (geometry.type) {
9074 case 'Point':
9075 latlng = _coordsToLatLng(coords);
9076 return _pointToLayer(pointToLayer, geojson, latlng, options);
9077
9078 case 'MultiPoint':
9079 for (i = 0, len = coords.length; i < len; i++) {
9080 latlng = _coordsToLatLng(coords[i]);
9081 layers.push(_pointToLayer(pointToLayer, geojson, latlng, options));
9082 }
9083 return new FeatureGroup(layers);
9084
9085 case 'LineString':
9086 case 'MultiLineString':
9087 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
9088 return new Polyline(latlngs, options);
9089
9090 case 'Polygon':
9091 case 'MultiPolygon':
9092 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
9093 return new Polygon(latlngs, options);
9094
9095 case 'GeometryCollection':
9096 for (i = 0, len = geometry.geometries.length; i < len; i++) {
9097 var geoLayer = geometryToLayer({
9098 geometry: geometry.geometries[i],
9099 type: 'Feature',
9100 properties: geojson.properties
9101 }, options);
9102
9103 if (geoLayer) {
9104 layers.push(geoLayer);
9105 }
9106 }
9107 return new FeatureGroup(layers);
9108
9109 case 'FeatureCollection':
9110 for (i = 0, len = geometry.features.length; i < len; i++) {
9111 var featureLayer = geometryToLayer(geometry.features[i], options);
9112
9113 if (featureLayer) {
9114 layers.push(featureLayer);
9115 }
9116 }
9117 return new FeatureGroup(layers);
9118
9119 default:
9120 throw new Error('Invalid GeoJSON object.');
9121 }
9122 }
9123
9124 function _pointToLayer(pointToLayerFn, geojson, latlng, options) {
9125 return pointToLayerFn ?
9126 pointToLayerFn(geojson, latlng) :
9127 new Marker(latlng, options && options.markersInheritOptions && options);
9128 }
9129
9130 // @function coordsToLatLng(coords: Array): LatLng
9131 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
9132 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
9133 function coordsToLatLng(coords) {
9134 return new LatLng(coords[1], coords[0], coords[2]);
9135 }
9136
9137 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
9138 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
9139 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
9140 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
9141 function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
9142 var latlngs = [];
9143
9144 for (var i = 0, len = coords.length, latlng; i < len; i++) {
9145 latlng = levelsDeep ?
9146 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
9147 (_coordsToLatLng || coordsToLatLng)(coords[i]);
9148
9149 latlngs.push(latlng);
9150 }
9151
9152 return latlngs;
9153 }
9154
9155 // @function latLngToCoords(latlng: LatLng, precision?: Number|false): Array
9156 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
9157 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function.
9158 function latLngToCoords(latlng, precision) {
9159 latlng = toLatLng(latlng);
9160 return latlng.alt !== undefined ?
9161 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
9162 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
9163 }
9164
9165 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean, precision?: Number|false): Array
9166 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
9167 // `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.
9168 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function.
9169 function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
9170 var coords = [];
9171
9172 for (var i = 0, len = latlngs.length; i < len; i++) {
9173 // Check for flat arrays required to ensure unbalanced arrays are correctly converted in recursion
9174 coords.push(levelsDeep ?
9175 latLngsToCoords(latlngs[i], isFlat(latlngs[i]) ? 0 : levelsDeep - 1, closed, precision) :
9176 latLngToCoords(latlngs[i], precision));
9177 }
9178
9179 if (!levelsDeep && closed && coords.length > 0) {
9180 coords.push(coords[0].slice());
9181 }
9182
9183 return coords;
9184 }
9185
9186 function getFeature(layer, newGeometry) {
9187 return layer.feature ?
9188 extend({}, layer.feature, {geometry: newGeometry}) :
9189 asFeature(newGeometry);
9190 }
9191
9192 // @function asFeature(geojson: Object): Object
9193 // Normalize GeoJSON geometries/features into GeoJSON features.
9194 function asFeature(geojson) {
9195 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
9196 return geojson;
9197 }
9198
9199 return {
9200 type: 'Feature',
9201 properties: {},
9202 geometry: geojson
9203 };
9204 }
9205
9206 var PointToGeoJSON = {
9207 toGeoJSON: function (precision) {
9208 return getFeature(this, {
9209 type: 'Point',
9210 coordinates: latLngToCoords(this.getLatLng(), precision)
9211 });
9212 }
9213 };
9214
9215 // @namespace Marker
9216 // @section Other methods
9217 // @method toGeoJSON(precision?: Number|false): Object
9218 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
9219 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
9220 Marker.include(PointToGeoJSON);
9221
9222 // @namespace CircleMarker
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 circle marker (as a GeoJSON `Point` Feature).
9226 Circle.include(PointToGeoJSON);
9227 CircleMarker.include(PointToGeoJSON);
9228
9229
9230 // @namespace Polyline
9231 // @method toGeoJSON(precision?: Number|false): Object
9232 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
9233 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
9234 Polyline.include({
9235 toGeoJSON: function (precision) {
9236 var multi = !isFlat(this._latlngs);
9237
9238 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
9239
9240 return getFeature(this, {
9241 type: (multi ? 'Multi' : '') + 'LineString',
9242 coordinates: coords
9243 });
9244 }
9245 });
9246
9247 // @namespace Polygon
9248 // @method toGeoJSON(precision?: Number|false): Object
9249 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
9250 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
9251 Polygon.include({
9252 toGeoJSON: function (precision) {
9253 var holes = !isFlat(this._latlngs),
9254 multi = holes && !isFlat(this._latlngs[0]);
9255
9256 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
9257
9258 if (!holes) {
9259 coords = [coords];
9260 }
9261
9262 return getFeature(this, {
9263 type: (multi ? 'Multi' : '') + 'Polygon',
9264 coordinates: coords
9265 });
9266 }
9267 });
9268
9269
9270 // @namespace LayerGroup
9271 LayerGroup.include({
9272 toMultiPoint: function (precision) {
9273 var coords = [];
9274
9275 this.eachLayer(function (layer) {
9276 coords.push(layer.toGeoJSON(precision).geometry.coordinates);
9277 });
9278
9279 return getFeature(this, {
9280 type: 'MultiPoint',
9281 coordinates: coords
9282 });
9283 },
9284
9285 // @method toGeoJSON(precision?: Number|false): Object
9286 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
9287 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
9288 toGeoJSON: function (precision) {
9289
9290 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
9291
9292 if (type === 'MultiPoint') {
9293 return this.toMultiPoint(precision);
9294 }
9295
9296 var isGeometryCollection = type === 'GeometryCollection',
9297 jsons = [];
9298
9299 this.eachLayer(function (layer) {
9300 if (layer.toGeoJSON) {
9301 var json = layer.toGeoJSON(precision);
9302 if (isGeometryCollection) {
9303 jsons.push(json.geometry);
9304 } else {
9305 var feature = asFeature(json);
9306 // Squash nested feature collections
9307 if (feature.type === 'FeatureCollection') {
9308 jsons.push.apply(jsons, feature.features);
9309 } else {
9310 jsons.push(feature);
9311 }
9312 }
9313 }
9314 });
9315
9316 if (isGeometryCollection) {
9317 return getFeature(this, {
9318 geometries: jsons,
9319 type: 'GeometryCollection'
9320 });
9321 }
9322
9323 return {
9324 type: 'FeatureCollection',
9325 features: jsons
9326 };
9327 }
9328 });
9329
9330 // @namespace GeoJSON
9331 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
9332 // Creates a GeoJSON layer. Optionally accepts an object in
9333 // [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
9334 // (you can alternatively add it later with `addData` method) and an `options` object.
9335 function geoJSON(geojson, options) {
9336 return new GeoJSON(geojson, options);
9337 }
9338
9339 // Backward compatibility.
9340 var geoJson = geoJSON;
9341
9342 /*
9343 * @class ImageOverlay
9344 * @aka L.ImageOverlay
9345 * @inherits Interactive layer
9346 *
9347 * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
9348 *
9349 * @example
9350 *
9351 * ```js
9352 * var imageUrl = 'https://maps.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
9353 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
9354 * L.imageOverlay(imageUrl, imageBounds).addTo(map);
9355 * ```
9356 */
9357
9358 var ImageOverlay = Layer.extend({
9359
9360 // @section
9361 // @aka ImageOverlay options
9362 options: {
9363 // @option opacity: Number = 1.0
9364 // The opacity of the image overlay.
9365 opacity: 1,
9366
9367 // @option alt: String = ''
9368 // Text for the `alt` attribute of the image (useful for accessibility).
9369 alt: '',
9370
9371 // @option interactive: Boolean = false
9372 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
9373 interactive: false,
9374
9375 // @option crossOrigin: Boolean|String = false
9376 // Whether the crossOrigin attribute will be added to the image.
9377 // 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.
9378 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
9379 crossOrigin: false,
9380
9381 // @option errorOverlayUrl: String = ''
9382 // URL to the overlay image to show in place of the overlay that failed to load.
9383 errorOverlayUrl: '',
9384
9385 // @option zIndex: Number = 1
9386 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
9387 zIndex: 1,
9388
9389 // @option className: String = ''
9390 // A custom class name to assign to the image. Empty by default.
9391 className: ''
9392 },
9393
9394 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
9395 this._url = url;
9396 this._bounds = toLatLngBounds(bounds);
9397
9398 setOptions(this, options);
9399 },
9400
9401 onAdd: function () {
9402 if (!this._image) {
9403 this._initImage();
9404
9405 if (this.options.opacity < 1) {
9406 this._updateOpacity();
9407 }
9408 }
9409
9410 if (this.options.interactive) {
9411 addClass(this._image, 'leaflet-interactive');
9412 this.addInteractiveTarget(this._image);
9413 }
9414
9415 this.getPane().appendChild(this._image);
9416 this._reset();
9417 },
9418
9419 onRemove: function () {
9420 remove(this._image);
9421 if (this.options.interactive) {
9422 this.removeInteractiveTarget(this._image);
9423 }
9424 },
9425
9426 // @method setOpacity(opacity: Number): this
9427 // Sets the opacity of the overlay.
9428 setOpacity: function (opacity) {
9429 this.options.opacity = opacity;
9430
9431 if (this._image) {
9432 this._updateOpacity();
9433 }
9434 return this;
9435 },
9436
9437 setStyle: function (styleOpts) {
9438 if (styleOpts.opacity) {
9439 this.setOpacity(styleOpts.opacity);
9440 }
9441 return this;
9442 },
9443
9444 // @method bringToFront(): this
9445 // Brings the layer to the top of all overlays.
9446 bringToFront: function () {
9447 if (this._map) {
9448 toFront(this._image);
9449 }
9450 return this;
9451 },
9452
9453 // @method bringToBack(): this
9454 // Brings the layer to the bottom of all overlays.
9455 bringToBack: function () {
9456 if (this._map) {
9457 toBack(this._image);
9458 }
9459 return this;
9460 },
9461
9462 // @method setUrl(url: String): this
9463 // Changes the URL of the image.
9464 setUrl: function (url) {
9465 this._url = url;
9466
9467 if (this._image) {
9468 this._image.src = url;
9469 }
9470 return this;
9471 },
9472
9473 // @method setBounds(bounds: LatLngBounds): this
9474 // Update the bounds that this ImageOverlay covers
9475 setBounds: function (bounds) {
9476 this._bounds = toLatLngBounds(bounds);
9477
9478 if (this._map) {
9479 this._reset();
9480 }
9481 return this;
9482 },
9483
9484 getEvents: function () {
9485 var events = {
9486 zoom: this._reset,
9487 viewreset: this._reset
9488 };
9489
9490 if (this._zoomAnimated) {
9491 events.zoomanim = this._animateZoom;
9492 }
9493
9494 return events;
9495 },
9496
9497 // @method setZIndex(value: Number): this
9498 // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
9499 setZIndex: function (value) {
9500 this.options.zIndex = value;
9501 this._updateZIndex();
9502 return this;
9503 },
9504
9505 // @method getBounds(): LatLngBounds
9506 // Get the bounds that this ImageOverlay covers
9507 getBounds: function () {
9508 return this._bounds;
9509 },
9510
9511 // @method getElement(): HTMLElement
9512 // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
9513 // used by this overlay.
9514 getElement: function () {
9515 return this._image;
9516 },
9517
9518 _initImage: function () {
9519 var wasElementSupplied = this._url.tagName === 'IMG';
9520 var img = this._image = wasElementSupplied ? this._url : create$1('img');
9521
9522 addClass(img, 'leaflet-image-layer');
9523 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
9524 if (this.options.className) { addClass(img, this.options.className); }
9525
9526 img.onselectstart = falseFn;
9527 img.onmousemove = falseFn;
9528
9529 // @event load: Event
9530 // Fired when the ImageOverlay layer has loaded its image
9531 img.onload = bind(this.fire, this, 'load');
9532 img.onerror = bind(this._overlayOnError, this, 'error');
9533
9534 if (this.options.crossOrigin || this.options.crossOrigin === '') {
9535 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
9536 }
9537
9538 if (this.options.zIndex) {
9539 this._updateZIndex();
9540 }
9541
9542 if (wasElementSupplied) {
9543 this._url = img.src;
9544 return;
9545 }
9546
9547 img.src = this._url;
9548 img.alt = this.options.alt;
9549 },
9550
9551 _animateZoom: function (e) {
9552 var scale = this._map.getZoomScale(e.zoom),
9553 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
9554
9555 setTransform(this._image, offset, scale);
9556 },
9557
9558 _reset: function () {
9559 var image = this._image,
9560 bounds = new Bounds(
9561 this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
9562 this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
9563 size = bounds.getSize();
9564
9565 setPosition(image, bounds.min);
9566
9567 image.style.width = size.x + 'px';
9568 image.style.height = size.y + 'px';
9569 },
9570
9571 _updateOpacity: function () {
9572 setOpacity(this._image, this.options.opacity);
9573 },
9574
9575 _updateZIndex: function () {
9576 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
9577 this._image.style.zIndex = this.options.zIndex;
9578 }
9579 },
9580
9581 _overlayOnError: function () {
9582 // @event error: Event
9583 // Fired when the ImageOverlay layer fails to load its image
9584 this.fire('error');
9585
9586 var errorUrl = this.options.errorOverlayUrl;
9587 if (errorUrl && this._url !== errorUrl) {
9588 this._url = errorUrl;
9589 this._image.src = errorUrl;
9590 }
9591 },
9592
9593 // @method getCenter(): LatLng
9594 // Returns the center of the ImageOverlay.
9595 getCenter: function () {
9596 return this._bounds.getCenter();
9597 }
9598 });
9599
9600 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
9601 // Instantiates an image overlay object given the URL of the image and the
9602 // geographical bounds it is tied to.
9603 var imageOverlay = function (url, bounds, options) {
9604 return new ImageOverlay(url, bounds, options);
9605 };
9606
9607 /*
9608 * @class VideoOverlay
9609 * @aka L.VideoOverlay
9610 * @inherits ImageOverlay
9611 *
9612 * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
9613 *
9614 * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
9615 * HTML5 element.
9616 *
9617 * @example
9618 *
9619 * ```js
9620 * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
9621 * videoBounds = [[ 32, -130], [ 13, -100]];
9622 * L.videoOverlay(videoUrl, videoBounds ).addTo(map);
9623 * ```
9624 */
9625
9626 var VideoOverlay = ImageOverlay.extend({
9627
9628 // @section
9629 // @aka VideoOverlay options
9630 options: {
9631 // @option autoplay: Boolean = true
9632 // Whether the video starts playing automatically when loaded.
9633 // On some browsers autoplay will only work with `muted: true`
9634 autoplay: true,
9635
9636 // @option loop: Boolean = true
9637 // Whether the video will loop back to the beginning when played.
9638 loop: true,
9639
9640 // @option keepAspectRatio: Boolean = true
9641 // Whether the video will save aspect ratio after the projection.
9642 // Relevant for supported browsers. See [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit)
9643 keepAspectRatio: true,
9644
9645 // @option muted: Boolean = false
9646 // Whether the video starts on mute when loaded.
9647 muted: false,
9648
9649 // @option playsInline: Boolean = true
9650 // Mobile browsers will play the video right where it is instead of open it up in fullscreen mode.
9651 playsInline: true
9652 },
9653
9654 _initImage: function () {
9655 var wasElementSupplied = this._url.tagName === 'VIDEO';
9656 var vid = this._image = wasElementSupplied ? this._url : create$1('video');
9657
9658 addClass(vid, 'leaflet-image-layer');
9659 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
9660 if (this.options.className) { addClass(vid, this.options.className); }
9661
9662 vid.onselectstart = falseFn;
9663 vid.onmousemove = falseFn;
9664
9665 // @event load: Event
9666 // Fired when the video has finished loading the first frame
9667 vid.onloadeddata = bind(this.fire, this, 'load');
9668
9669 if (wasElementSupplied) {
9670 var sourceElements = vid.getElementsByTagName('source');
9671 var sources = [];
9672 for (var j = 0; j < sourceElements.length; j++) {
9673 sources.push(sourceElements[j].src);
9674 }
9675
9676 this._url = (sourceElements.length > 0) ? sources : [vid.src];
9677 return;
9678 }
9679
9680 if (!isArray(this._url)) { this._url = [this._url]; }
9681
9682 if (!this.options.keepAspectRatio && Object.prototype.hasOwnProperty.call(vid.style, 'objectFit')) {
9683 vid.style['objectFit'] = 'fill';
9684 }
9685 vid.autoplay = !!this.options.autoplay;
9686 vid.loop = !!this.options.loop;
9687 vid.muted = !!this.options.muted;
9688 vid.playsInline = !!this.options.playsInline;
9689 for (var i = 0; i < this._url.length; i++) {
9690 var source = create$1('source');
9691 source.src = this._url[i];
9692 vid.appendChild(source);
9693 }
9694 }
9695
9696 // @method getElement(): HTMLVideoElement
9697 // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
9698 // used by this overlay.
9699 });
9700
9701
9702 // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
9703 // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
9704 // geographical bounds it is tied to.
9705
9706 function videoOverlay(video, bounds, options) {
9707 return new VideoOverlay(video, bounds, options);
9708 }
9709
9710 /*
9711 * @class SVGOverlay
9712 * @aka L.SVGOverlay
9713 * @inherits ImageOverlay
9714 *
9715 * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`.
9716 *
9717 * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element.
9718 *
9719 * @example
9720 *
9721 * ```js
9722 * var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
9723 * svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg");
9724 * svgElement.setAttribute('viewBox', "0 0 200 200");
9725 * 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"/>';
9726 * var svgElementBounds = [ [ 32, -130 ], [ 13, -100 ] ];
9727 * L.svgOverlay(svgElement, svgElementBounds).addTo(map);
9728 * ```
9729 */
9730
9731 var SVGOverlay = ImageOverlay.extend({
9732 _initImage: function () {
9733 var el = this._image = this._url;
9734
9735 addClass(el, 'leaflet-image-layer');
9736 if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); }
9737 if (this.options.className) { addClass(el, this.options.className); }
9738
9739 el.onselectstart = falseFn;
9740 el.onmousemove = falseFn;
9741 }
9742
9743 // @method getElement(): SVGElement
9744 // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement)
9745 // used by this overlay.
9746 });
9747
9748
9749 // @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options)
9750 // Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to.
9751 // A viewBox attribute is required on the SVG element to zoom in and out properly.
9752
9753 function svgOverlay(el, bounds, options) {
9754 return new SVGOverlay(el, bounds, options);
9755 }
9756
9757 /*
9758 * @class DivOverlay
9759 * @inherits Interactive layer
9760 * @aka L.DivOverlay
9761 * Base model for L.Popup and L.Tooltip. Inherit from it for custom overlays like plugins.
9762 */
9763
9764 // @namespace DivOverlay
9765 var DivOverlay = Layer.extend({
9766
9767 // @section
9768 // @aka DivOverlay options
9769 options: {
9770 // @option interactive: Boolean = false
9771 // If true, the popup/tooltip will listen to the mouse events.
9772 interactive: false,
9773
9774 // @option offset: Point = Point(0, 0)
9775 // The offset of the overlay position.
9776 offset: [0, 0],
9777
9778 // @option className: String = ''
9779 // A custom CSS class name to assign to the overlay.
9780 className: '',
9781
9782 // @option pane: String = undefined
9783 // `Map pane` where the overlay will be added.
9784 pane: undefined,
9785
9786 // @option content: String|HTMLElement|Function = ''
9787 // Sets the HTML content of the overlay while initializing. If a function is passed the source layer will be
9788 // passed to the function. The function should return a `String` or `HTMLElement` to be used in the overlay.
9789 content: ''
9790 },
9791
9792 initialize: function (options, source) {
9793 if (options && (options instanceof LatLng || isArray(options))) {
9794 this._latlng = toLatLng(options);
9795 setOptions(this, source);
9796 } else {
9797 setOptions(this, options);
9798 this._source = source;
9799 }
9800 if (this.options.content) {
9801 this._content = this.options.content;
9802 }
9803 },
9804
9805 // @method openOn(map: Map): this
9806 // Adds the overlay to the map.
9807 // Alternative to `map.openPopup(popup)`/`.openTooltip(tooltip)`.
9808 openOn: function (map) {
9809 map = arguments.length ? map : this._source._map; // experimental, not the part of public api
9810 if (!map.hasLayer(this)) {
9811 map.addLayer(this);
9812 }
9813 return this;
9814 },
9815
9816 // @method close(): this
9817 // Closes the overlay.
9818 // Alternative to `map.closePopup(popup)`/`.closeTooltip(tooltip)`
9819 // and `layer.closePopup()`/`.closeTooltip()`.
9820 close: function () {
9821 if (this._map) {
9822 this._map.removeLayer(this);
9823 }
9824 return this;
9825 },
9826
9827 // @method toggle(layer?: Layer): this
9828 // Opens or closes the overlay bound to layer depending on its current state.
9829 // Argument may be omitted only for overlay bound to layer.
9830 // Alternative to `layer.togglePopup()`/`.toggleTooltip()`.
9831 toggle: function (layer) {
9832 if (this._map) {
9833 this.close();
9834 } else {
9835 if (arguments.length) {
9836 this._source = layer;
9837 } else {
9838 layer = this._source;
9839 }
9840 this._prepareOpen();
9841
9842 // open the overlay on the map
9843 this.openOn(layer._map);
9844 }
9845 return this;
9846 },
9847
9848 onAdd: function (map) {
9849 this._zoomAnimated = map._zoomAnimated;
9850
9851 if (!this._container) {
9852 this._initLayout();
9853 }
9854
9855 if (map._fadeAnimated) {
9856 setOpacity(this._container, 0);
9857 }
9858
9859 clearTimeout(this._removeTimeout);
9860 this.getPane().appendChild(this._container);
9861 this.update();
9862
9863 if (map._fadeAnimated) {
9864 setOpacity(this._container, 1);
9865 }
9866
9867 this.bringToFront();
9868
9869 if (this.options.interactive) {
9870 addClass(this._container, 'leaflet-interactive');
9871 this.addInteractiveTarget(this._container);
9872 }
9873 },
9874
9875 onRemove: function (map) {
9876 if (map._fadeAnimated) {
9877 setOpacity(this._container, 0);
9878 this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
9879 } else {
9880 remove(this._container);
9881 }
9882
9883 if (this.options.interactive) {
9884 removeClass(this._container, 'leaflet-interactive');
9885 this.removeInteractiveTarget(this._container);
9886 }
9887 },
9888
9889 // @namespace DivOverlay
9890 // @method getLatLng: LatLng
9891 // Returns the geographical point of the overlay.
9892 getLatLng: function () {
9893 return this._latlng;
9894 },
9895
9896 // @method setLatLng(latlng: LatLng): this
9897 // Sets the geographical point where the overlay will open.
9898 setLatLng: function (latlng) {
9899 this._latlng = toLatLng(latlng);
9900 if (this._map) {
9901 this._updatePosition();
9902 this._adjustPan();
9903 }
9904 return this;
9905 },
9906
9907 // @method getContent: String|HTMLElement
9908 // Returns the content of the overlay.
9909 getContent: function () {
9910 return this._content;
9911 },
9912
9913 // @method setContent(htmlContent: String|HTMLElement|Function): this
9914 // Sets the HTML content of the overlay. If a function is passed the source layer will be passed to the function.
9915 // The function should return a `String` or `HTMLElement` to be used in the overlay.
9916 setContent: function (content) {
9917 this._content = content;
9918 this.update();
9919 return this;
9920 },
9921
9922 // @method getElement: String|HTMLElement
9923 // Returns the HTML container of the overlay.
9924 getElement: function () {
9925 return this._container;
9926 },
9927
9928 // @method update: null
9929 // Updates the overlay content, layout and position. Useful for updating the overlay after something inside changed, e.g. image loaded.
9930 update: function () {
9931 if (!this._map) { return; }
9932
9933 this._container.style.visibility = 'hidden';
9934
9935 this._updateContent();
9936 this._updateLayout();
9937 this._updatePosition();
9938
9939 this._container.style.visibility = '';
9940
9941 this._adjustPan();
9942 },
9943
9944 getEvents: function () {
9945 var events = {
9946 zoom: this._updatePosition,
9947 viewreset: this._updatePosition
9948 };
9949
9950 if (this._zoomAnimated) {
9951 events.zoomanim = this._animateZoom;
9952 }
9953 return events;
9954 },
9955
9956 // @method isOpen: Boolean
9957 // Returns `true` when the overlay is visible on the map.
9958 isOpen: function () {
9959 return !!this._map && this._map.hasLayer(this);
9960 },
9961
9962 // @method bringToFront: this
9963 // Brings this overlay in front of other overlays (in the same map pane).
9964 bringToFront: function () {
9965 if (this._map) {
9966 toFront(this._container);
9967 }
9968 return this;
9969 },
9970
9971 // @method bringToBack: this
9972 // Brings this overlay to the back of other overlays (in the same map pane).
9973 bringToBack: function () {
9974 if (this._map) {
9975 toBack(this._container);
9976 }
9977 return this;
9978 },
9979
9980 // prepare bound overlay to open: update latlng pos / content source (for FeatureGroup)
9981 _prepareOpen: function (latlng) {
9982 var source = this._source;
9983 if (!source._map) { return false; }
9984
9985 if (source instanceof FeatureGroup) {
9986 source = null;
9987 var layers = this._source._layers;
9988 for (var id in layers) {
9989 if (layers[id]._map) {
9990 source = layers[id];
9991 break;
9992 }
9993 }
9994 if (!source) { return false; } // Unable to get source layer.
9995
9996 // set overlay source to this layer
9997 this._source = source;
9998 }
9999
10000 if (!latlng) {
10001 if (source.getCenter) {
10002 latlng = source.getCenter();
10003 } else if (source.getLatLng) {
10004 latlng = source.getLatLng();
10005 } else if (source.getBounds) {
10006 latlng = source.getBounds().getCenter();
10007 } else {
10008 throw new Error('Unable to get source layer LatLng.');
10009 }
10010 }
10011 this.setLatLng(latlng);
10012
10013 if (this._map) {
10014 // update the overlay (content, layout, etc...)
10015 this.update();
10016 }
10017
10018 return true;
10019 },
10020
10021 _updateContent: function () {
10022 if (!this._content) { return; }
10023
10024 var node = this._contentNode;
10025 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
10026
10027 if (typeof content === 'string') {
10028 node.innerHTML = content;
10029 } else {
10030 while (node.hasChildNodes()) {
10031 node.removeChild(node.firstChild);
10032 }
10033 node.appendChild(content);
10034 }
10035
10036 // @namespace DivOverlay
10037 // @section DivOverlay events
10038 // @event contentupdate: Event
10039 // Fired when the content of the overlay is updated
10040 this.fire('contentupdate');
10041 },
10042
10043 _updatePosition: function () {
10044 if (!this._map) { return; }
10045
10046 var pos = this._map.latLngToLayerPoint(this._latlng),
10047 offset = toPoint(this.options.offset),
10048 anchor = this._getAnchor();
10049
10050 if (this._zoomAnimated) {
10051 setPosition(this._container, pos.add(anchor));
10052 } else {
10053 offset = offset.add(pos).add(anchor);
10054 }
10055
10056 var bottom = this._containerBottom = -offset.y,
10057 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
10058
10059 // bottom position the overlay in case the height of the overlay changes (images loading etc)
10060 this._container.style.bottom = bottom + 'px';
10061 this._container.style.left = left + 'px';
10062 },
10063
10064 _getAnchor: function () {
10065 return [0, 0];
10066 }
10067
10068 });
10069
10070 Map.include({
10071 _initOverlay: function (OverlayClass, content, latlng, options) {
10072 var overlay = content;
10073 if (!(overlay instanceof OverlayClass)) {
10074 overlay = new OverlayClass(options).setContent(content);
10075 }
10076 if (latlng) {
10077 overlay.setLatLng(latlng);
10078 }
10079 return overlay;
10080 }
10081 });
10082
10083
10084 Layer.include({
10085 _initOverlay: function (OverlayClass, old, content, options) {
10086 var overlay = content;
10087 if (overlay instanceof OverlayClass) {
10088 setOptions(overlay, options);
10089 overlay._source = this;
10090 } else {
10091 overlay = (old && !options) ? old : new OverlayClass(options, this);
10092 overlay.setContent(content);
10093 }
10094 return overlay;
10095 }
10096 });
10097
10098 /*
10099 * @class Popup
10100 * @inherits DivOverlay
10101 * @aka L.Popup
10102 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
10103 * open popups while making sure that only one popup is open at one time
10104 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
10105 *
10106 * @example
10107 *
10108 * If you want to just bind a popup to marker click and then open it, it's really easy:
10109 *
10110 * ```js
10111 * marker.bindPopup(popupContent).openPopup();
10112 * ```
10113 * Path overlays like polylines also have a `bindPopup` method.
10114 *
10115 * A popup can be also standalone:
10116 *
10117 * ```js
10118 * var popup = L.popup()
10119 * .setLatLng(latlng)
10120 * .setContent('<p>Hello world!<br />This is a nice popup.</p>')
10121 * .openOn(map);
10122 * ```
10123 * or
10124 * ```js
10125 * var popup = L.popup(latlng, {content: '<p>Hello world!<br />This is a nice popup.</p>')
10126 * .openOn(map);
10127 * ```
10128 */
10129
10130
10131 // @namespace Popup
10132 var Popup = DivOverlay.extend({
10133
10134 // @section
10135 // @aka Popup options
10136 options: {
10137 // @option pane: String = 'popupPane'
10138 // `Map pane` where the popup will be added.
10139 pane: 'popupPane',
10140
10141 // @option offset: Point = Point(0, 7)
10142 // The offset of the popup position.
10143 offset: [0, 7],
10144
10145 // @option maxWidth: Number = 300
10146 // Max width of the popup, in pixels.
10147 maxWidth: 300,
10148
10149 // @option minWidth: Number = 50
10150 // Min width of the popup, in pixels.
10151 minWidth: 50,
10152
10153 // @option maxHeight: Number = null
10154 // If set, creates a scrollable container of the given height
10155 // inside a popup if its content exceeds it.
10156 // The scrollable container can be styled using the
10157 // `leaflet-popup-scrolled` CSS class selector.
10158 maxHeight: null,
10159
10160 // @option autoPan: Boolean = true
10161 // Set it to `false` if you don't want the map to do panning animation
10162 // to fit the opened popup.
10163 autoPan: true,
10164
10165 // @option autoPanPaddingTopLeft: Point = null
10166 // The margin between the popup and the top left corner of the map
10167 // view after autopanning was performed.
10168 autoPanPaddingTopLeft: null,
10169
10170 // @option autoPanPaddingBottomRight: Point = null
10171 // The margin between the popup and the bottom right corner of the map
10172 // view after autopanning was performed.
10173 autoPanPaddingBottomRight: null,
10174
10175 // @option autoPanPadding: Point = Point(5, 5)
10176 // Equivalent of setting both top left and bottom right autopan padding to the same value.
10177 autoPanPadding: [5, 5],
10178
10179 // @option keepInView: Boolean = false
10180 // Set it to `true` if you want to prevent users from panning the popup
10181 // off of the screen while it is open.
10182 keepInView: false,
10183
10184 // @option closeButton: Boolean = true
10185 // Controls the presence of a close button in the popup.
10186 closeButton: true,
10187
10188 // @option autoClose: Boolean = true
10189 // Set it to `false` if you want to override the default behavior of
10190 // the popup closing when another popup is opened.
10191 autoClose: true,
10192
10193 // @option closeOnEscapeKey: Boolean = true
10194 // Set it to `false` if you want to override the default behavior of
10195 // the ESC key for closing of the popup.
10196 closeOnEscapeKey: true,
10197
10198 // @option closeOnClick: Boolean = *
10199 // Set it if you want to override the default behavior of the popup closing when user clicks
10200 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
10201
10202 // @option className: String = ''
10203 // A custom CSS class name to assign to the popup.
10204 className: ''
10205 },
10206
10207 // @namespace Popup
10208 // @method openOn(map: Map): this
10209 // Alternative to `map.openPopup(popup)`.
10210 // Adds the popup to the map and closes the previous one.
10211 openOn: function (map) {
10212 map = arguments.length ? map : this._source._map; // experimental, not the part of public api
10213
10214 if (!map.hasLayer(this) && map._popup && map._popup.options.autoClose) {
10215 map.removeLayer(map._popup);
10216 }
10217 map._popup = this;
10218
10219 return DivOverlay.prototype.openOn.call(this, map);
10220 },
10221
10222 onAdd: function (map) {
10223 DivOverlay.prototype.onAdd.call(this, map);
10224
10225 // @namespace Map
10226 // @section Popup events
10227 // @event popupopen: PopupEvent
10228 // Fired when a popup is opened in the map
10229 map.fire('popupopen', {popup: this});
10230
10231 if (this._source) {
10232 // @namespace Layer
10233 // @section Popup events
10234 // @event popupopen: PopupEvent
10235 // Fired when a popup bound to this layer is opened
10236 this._source.fire('popupopen', {popup: this}, true);
10237 // For non-path layers, we toggle the popup when clicking
10238 // again the layer, so prevent the map to reopen it.
10239 if (!(this._source instanceof Path)) {
10240 this._source.on('preclick', stopPropagation);
10241 }
10242 }
10243 },
10244
10245 onRemove: function (map) {
10246 DivOverlay.prototype.onRemove.call(this, map);
10247
10248 // @namespace Map
10249 // @section Popup events
10250 // @event popupclose: PopupEvent
10251 // Fired when a popup in the map is closed
10252 map.fire('popupclose', {popup: this});
10253
10254 if (this._source) {
10255 // @namespace Layer
10256 // @section Popup events
10257 // @event popupclose: PopupEvent
10258 // Fired when a popup bound to this layer is closed
10259 this._source.fire('popupclose', {popup: this}, true);
10260 if (!(this._source instanceof Path)) {
10261 this._source.off('preclick', stopPropagation);
10262 }
10263 }
10264 },
10265
10266 getEvents: function () {
10267 var events = DivOverlay.prototype.getEvents.call(this);
10268
10269 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
10270 events.preclick = this.close;
10271 }
10272
10273 if (this.options.keepInView) {
10274 events.moveend = this._adjustPan;
10275 }
10276
10277 return events;
10278 },
10279
10280 _initLayout: function () {
10281 var prefix = 'leaflet-popup',
10282 container = this._container = create$1('div',
10283 prefix + ' ' + (this.options.className || '') +
10284 ' leaflet-zoom-animated');
10285
10286 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
10287 this._contentNode = create$1('div', prefix + '-content', wrapper);
10288
10289 disableClickPropagation(container);
10290 disableScrollPropagation(this._contentNode);
10291 on(container, 'contextmenu', stopPropagation);
10292
10293 this._tipContainer = create$1('div', prefix + '-tip-container', container);
10294 this._tip = create$1('div', prefix + '-tip', this._tipContainer);
10295
10296 if (this.options.closeButton) {
10297 var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
10298 closeButton.setAttribute('role', 'button'); // overrides the implicit role=link of <a> elements #7399
10299 closeButton.setAttribute('aria-label', 'Close popup');
10300 closeButton.href = '#close';
10301 closeButton.innerHTML = '<span aria-hidden="true">&#215;</span>';
10302
10303 on(closeButton, 'click', function (ev) {
10304 preventDefault(ev);
10305 this.close();
10306 }, this);
10307 }
10308 },
10309
10310 _updateLayout: function () {
10311 var container = this._contentNode,
10312 style = container.style;
10313
10314 style.width = '';
10315 style.whiteSpace = 'nowrap';
10316
10317 var width = container.offsetWidth;
10318 width = Math.min(width, this.options.maxWidth);
10319 width = Math.max(width, this.options.minWidth);
10320
10321 style.width = (width + 1) + 'px';
10322 style.whiteSpace = '';
10323
10324 style.height = '';
10325
10326 var height = container.offsetHeight,
10327 maxHeight = this.options.maxHeight,
10328 scrolledClass = 'leaflet-popup-scrolled';
10329
10330 if (maxHeight && height > maxHeight) {
10331 style.height = maxHeight + 'px';
10332 addClass(container, scrolledClass);
10333 } else {
10334 removeClass(container, scrolledClass);
10335 }
10336
10337 this._containerWidth = this._container.offsetWidth;
10338 },
10339
10340 _animateZoom: function (e) {
10341 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
10342 anchor = this._getAnchor();
10343 setPosition(this._container, pos.add(anchor));
10344 },
10345
10346 _adjustPan: function () {
10347 if (!this.options.autoPan) { return; }
10348 if (this._map._panAnim) { this._map._panAnim.stop(); }
10349
10350 // We can endlessly recurse if keepInView is set and the view resets.
10351 // Let's guard against that by exiting early if we're responding to our own autopan.
10352 if (this._autopanning) {
10353 this._autopanning = false;
10354 return;
10355 }
10356
10357 var map = this._map,
10358 marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
10359 containerHeight = this._container.offsetHeight + marginBottom,
10360 containerWidth = this._containerWidth,
10361 layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
10362
10363 layerPos._add(getPosition(this._container));
10364
10365 var containerPos = map.layerPointToContainerPoint(layerPos),
10366 padding = toPoint(this.options.autoPanPadding),
10367 paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
10368 paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
10369 size = map.getSize(),
10370 dx = 0,
10371 dy = 0;
10372
10373 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
10374 dx = containerPos.x + containerWidth - size.x + paddingBR.x;
10375 }
10376 if (containerPos.x - dx - paddingTL.x < 0) { // left
10377 dx = containerPos.x - paddingTL.x;
10378 }
10379 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
10380 dy = containerPos.y + containerHeight - size.y + paddingBR.y;
10381 }
10382 if (containerPos.y - dy - paddingTL.y < 0) { // top
10383 dy = containerPos.y - paddingTL.y;
10384 }
10385
10386 // @namespace Map
10387 // @section Popup events
10388 // @event autopanstart: Event
10389 // Fired when the map starts autopanning when opening a popup.
10390 if (dx || dy) {
10391 // Track that we're autopanning, as this function will be re-ran on moveend
10392 if (this.options.keepInView) {
10393 this._autopanning = true;
10394 }
10395
10396 map
10397 .fire('autopanstart')
10398 .panBy([dx, dy]);
10399 }
10400 },
10401
10402 _getAnchor: function () {
10403 // Where should we anchor the popup on the source layer?
10404 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
10405 }
10406
10407 });
10408
10409 // @namespace Popup
10410 // @factory L.popup(options?: Popup options, source?: Layer)
10411 // 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.
10412 // @alternative
10413 // @factory L.popup(latlng: LatLng, options?: Popup options)
10414 // Instantiates a `Popup` object given `latlng` where the popup will open and an optional `options` object that describes its appearance and location.
10415 var popup = function (options, source) {
10416 return new Popup(options, source);
10417 };
10418
10419
10420 /* @namespace Map
10421 * @section Interaction Options
10422 * @option closePopupOnClick: Boolean = true
10423 * Set it to `false` if you don't want popups to close when user clicks the map.
10424 */
10425 Map.mergeOptions({
10426 closePopupOnClick: true
10427 });
10428
10429
10430 // @namespace Map
10431 // @section Methods for Layers and Controls
10432 Map.include({
10433 // @method openPopup(popup: Popup): this
10434 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
10435 // @alternative
10436 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
10437 // Creates a popup with the specified content and options and opens it in the given point on a map.
10438 openPopup: function (popup, latlng, options) {
10439 this._initOverlay(Popup, popup, latlng, options)
10440 .openOn(this);
10441
10442 return this;
10443 },
10444
10445 // @method closePopup(popup?: Popup): this
10446 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
10447 closePopup: function (popup) {
10448 popup = arguments.length ? popup : this._popup;
10449 if (popup) {
10450 popup.close();
10451 }
10452 return this;
10453 }
10454 });
10455
10456 /*
10457 * @namespace Layer
10458 * @section Popup methods example
10459 *
10460 * All layers share a set of methods convenient for binding popups to it.
10461 *
10462 * ```js
10463 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
10464 * layer.openPopup();
10465 * layer.closePopup();
10466 * ```
10467 *
10468 * 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.
10469 */
10470
10471 // @section Popup methods
10472 Layer.include({
10473
10474 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
10475 // Binds a popup to the layer with the passed `content` and sets up the
10476 // necessary event listeners. If a `Function` is passed it will receive
10477 // the layer as the first argument and should return a `String` or `HTMLElement`.
10478 bindPopup: function (content, options) {
10479 this._popup = this._initOverlay(Popup, this._popup, content, options);
10480 if (!this._popupHandlersAdded) {
10481 this.on({
10482 click: this._openPopup,
10483 keypress: this._onKeyPress,
10484 remove: this.closePopup,
10485 move: this._movePopup
10486 });
10487 this._popupHandlersAdded = true;
10488 }
10489
10490 return this;
10491 },
10492
10493 // @method unbindPopup(): this
10494 // Removes the popup previously bound with `bindPopup`.
10495 unbindPopup: function () {
10496 if (this._popup) {
10497 this.off({
10498 click: this._openPopup,
10499 keypress: this._onKeyPress,
10500 remove: this.closePopup,
10501 move: this._movePopup
10502 });
10503 this._popupHandlersAdded = false;
10504 this._popup = null;
10505 }
10506 return this;
10507 },
10508
10509 // @method openPopup(latlng?: LatLng): this
10510 // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
10511 openPopup: function (latlng) {
10512 if (this._popup) {
10513 if (!(this instanceof FeatureGroup)) {
10514 this._popup._source = this;
10515 }
10516 if (this._popup._prepareOpen(latlng || this._latlng)) {
10517 // open the popup on the map
10518 this._popup.openOn(this._map);
10519 }
10520 }
10521 return this;
10522 },
10523
10524 // @method closePopup(): this
10525 // Closes the popup bound to this layer if it is open.
10526 closePopup: function () {
10527 if (this._popup) {
10528 this._popup.close();
10529 }
10530 return this;
10531 },
10532
10533 // @method togglePopup(): this
10534 // Opens or closes the popup bound to this layer depending on its current state.
10535 togglePopup: function () {
10536 if (this._popup) {
10537 this._popup.toggle(this);
10538 }
10539 return this;
10540 },
10541
10542 // @method isPopupOpen(): boolean
10543 // Returns `true` if the popup bound to this layer is currently open.
10544 isPopupOpen: function () {
10545 return (this._popup ? this._popup.isOpen() : false);
10546 },
10547
10548 // @method setPopupContent(content: String|HTMLElement|Popup): this
10549 // Sets the content of the popup bound to this layer.
10550 setPopupContent: function (content) {
10551 if (this._popup) {
10552 this._popup.setContent(content);
10553 }
10554 return this;
10555 },
10556
10557 // @method getPopup(): Popup
10558 // Returns the popup bound to this layer.
10559 getPopup: function () {
10560 return this._popup;
10561 },
10562
10563 _openPopup: function (e) {
10564 if (!this._popup || !this._map) {
10565 return;
10566 }
10567 // prevent map click
10568 stop(e);
10569
10570 var target = e.layer || e.target;
10571 if (this._popup._source === target && !(target instanceof Path)) {
10572 // treat it like a marker and figure out
10573 // if we should toggle it open/closed
10574 if (this._map.hasLayer(this._popup)) {
10575 this.closePopup();
10576 } else {
10577 this.openPopup(e.latlng);
10578 }
10579 return;
10580 }
10581 this._popup._source = target;
10582 this.openPopup(e.latlng);
10583 },
10584
10585 _movePopup: function (e) {
10586 this._popup.setLatLng(e.latlng);
10587 },
10588
10589 _onKeyPress: function (e) {
10590 if (e.originalEvent.keyCode === 13) {
10591 this._openPopup(e);
10592 }
10593 }
10594 });
10595
10596 /*
10597 * @class Tooltip
10598 * @inherits DivOverlay
10599 * @aka L.Tooltip
10600 * Used to display small texts on top of map layers.
10601 *
10602 * @example
10603 * If you want to just bind a tooltip to marker:
10604 *
10605 * ```js
10606 * marker.bindTooltip("my tooltip text").openTooltip();
10607 * ```
10608 * Path overlays like polylines also have a `bindTooltip` method.
10609 *
10610 * A tooltip can be also standalone:
10611 *
10612 * ```js
10613 * var tooltip = L.tooltip()
10614 * .setLatLng(latlng)
10615 * .setContent('Hello world!<br />This is a nice tooltip.')
10616 * .addTo(map);
10617 * ```
10618 * or
10619 * ```js
10620 * var tooltip = L.tooltip(latlng, {content: 'Hello world!<br />This is a nice tooltip.'})
10621 * .addTo(map);
10622 * ```
10623 *
10624 *
10625 * Note about tooltip offset. Leaflet takes two options in consideration
10626 * for computing tooltip offsetting:
10627 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
10628 * Add a positive x offset to move the tooltip to the right, and a positive y offset to
10629 * move it to the bottom. Negatives will move to the left and top.
10630 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
10631 * should adapt this value if you use a custom icon.
10632 */
10633
10634
10635 // @namespace Tooltip
10636 var Tooltip = DivOverlay.extend({
10637
10638 // @section
10639 // @aka Tooltip options
10640 options: {
10641 // @option pane: String = 'tooltipPane'
10642 // `Map pane` where the tooltip will be added.
10643 pane: 'tooltipPane',
10644
10645 // @option offset: Point = Point(0, 0)
10646 // Optional offset of the tooltip position.
10647 offset: [0, 0],
10648
10649 // @option direction: String = 'auto'
10650 // Direction where to open the tooltip. Possible values are: `right`, `left`,
10651 // `top`, `bottom`, `center`, `auto`.
10652 // `auto` will dynamically switch between `right` and `left` according to the tooltip
10653 // position on the map.
10654 direction: 'auto',
10655
10656 // @option permanent: Boolean = false
10657 // Whether to open the tooltip permanently or only on mouseover.
10658 permanent: false,
10659
10660 // @option sticky: Boolean = false
10661 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
10662 sticky: false,
10663
10664 // @option opacity: Number = 0.9
10665 // Tooltip container opacity.
10666 opacity: 0.9
10667 },
10668
10669 onAdd: function (map) {
10670 DivOverlay.prototype.onAdd.call(this, map);
10671 this.setOpacity(this.options.opacity);
10672
10673 // @namespace Map
10674 // @section Tooltip events
10675 // @event tooltipopen: TooltipEvent
10676 // Fired when a tooltip is opened in the map.
10677 map.fire('tooltipopen', {tooltip: this});
10678
10679 if (this._source) {
10680 this.addEventParent(this._source);
10681
10682 // @namespace Layer
10683 // @section Tooltip events
10684 // @event tooltipopen: TooltipEvent
10685 // Fired when a tooltip bound to this layer is opened.
10686 this._source.fire('tooltipopen', {tooltip: this}, true);
10687 }
10688 },
10689
10690 onRemove: function (map) {
10691 DivOverlay.prototype.onRemove.call(this, map);
10692
10693 // @namespace Map
10694 // @section Tooltip events
10695 // @event tooltipclose: TooltipEvent
10696 // Fired when a tooltip in the map is closed.
10697 map.fire('tooltipclose', {tooltip: this});
10698
10699 if (this._source) {
10700 this.removeEventParent(this._source);
10701
10702 // @namespace Layer
10703 // @section Tooltip events
10704 // @event tooltipclose: TooltipEvent
10705 // Fired when a tooltip bound to this layer is closed.
10706 this._source.fire('tooltipclose', {tooltip: this}, true);
10707 }
10708 },
10709
10710 getEvents: function () {
10711 var events = DivOverlay.prototype.getEvents.call(this);
10712
10713 if (!this.options.permanent) {
10714 events.preclick = this.close;
10715 }
10716
10717 return events;
10718 },
10719
10720 _initLayout: function () {
10721 var prefix = 'leaflet-tooltip',
10722 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
10723
10724 this._contentNode = this._container = create$1('div', className);
10725
10726 this._container.setAttribute('role', 'tooltip');
10727 this._container.setAttribute('id', 'leaflet-tooltip-' + stamp(this));
10728 },
10729
10730 _updateLayout: function () {},
10731
10732 _adjustPan: function () {},
10733
10734 _setPosition: function (pos) {
10735 var subX, subY,
10736 map = this._map,
10737 container = this._container,
10738 centerPoint = map.latLngToContainerPoint(map.getCenter()),
10739 tooltipPoint = map.layerPointToContainerPoint(pos),
10740 direction = this.options.direction,
10741 tooltipWidth = container.offsetWidth,
10742 tooltipHeight = container.offsetHeight,
10743 offset = toPoint(this.options.offset),
10744 anchor = this._getAnchor();
10745
10746 if (direction === 'top') {
10747 subX = tooltipWidth / 2;
10748 subY = tooltipHeight;
10749 } else if (direction === 'bottom') {
10750 subX = tooltipWidth / 2;
10751 subY = 0;
10752 } else if (direction === 'center') {
10753 subX = tooltipWidth / 2;
10754 subY = tooltipHeight / 2;
10755 } else if (direction === 'right') {
10756 subX = 0;
10757 subY = tooltipHeight / 2;
10758 } else if (direction === 'left') {
10759 subX = tooltipWidth;
10760 subY = tooltipHeight / 2;
10761 } else if (tooltipPoint.x < centerPoint.x) {
10762 direction = 'right';
10763 subX = 0;
10764 subY = tooltipHeight / 2;
10765 } else {
10766 direction = 'left';
10767 subX = tooltipWidth + (offset.x + anchor.x) * 2;
10768 subY = tooltipHeight / 2;
10769 }
10770
10771 pos = pos.subtract(toPoint(subX, subY, true)).add(offset).add(anchor);
10772
10773 removeClass(container, 'leaflet-tooltip-right');
10774 removeClass(container, 'leaflet-tooltip-left');
10775 removeClass(container, 'leaflet-tooltip-top');
10776 removeClass(container, 'leaflet-tooltip-bottom');
10777 addClass(container, 'leaflet-tooltip-' + direction);
10778 setPosition(container, pos);
10779 },
10780
10781 _updatePosition: function () {
10782 var pos = this._map.latLngToLayerPoint(this._latlng);
10783 this._setPosition(pos);
10784 },
10785
10786 setOpacity: function (opacity) {
10787 this.options.opacity = opacity;
10788
10789 if (this._container) {
10790 setOpacity(this._container, opacity);
10791 }
10792 },
10793
10794 _animateZoom: function (e) {
10795 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
10796 this._setPosition(pos);
10797 },
10798
10799 _getAnchor: function () {
10800 // Where should we anchor the tooltip on the source layer?
10801 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
10802 }
10803
10804 });
10805
10806 // @namespace Tooltip
10807 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
10808 // 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.
10809 // @alternative
10810 // @factory L.tooltip(latlng: LatLng, options?: Tooltip options)
10811 // Instantiates a `Tooltip` object given `latlng` where the tooltip will open and an optional `options` object that describes its appearance and location.
10812 var tooltip = function (options, source) {
10813 return new Tooltip(options, source);
10814 };
10815
10816 // @namespace Map
10817 // @section Methods for Layers and Controls
10818 Map.include({
10819
10820 // @method openTooltip(tooltip: Tooltip): this
10821 // Opens the specified tooltip.
10822 // @alternative
10823 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
10824 // Creates a tooltip with the specified content and options and open it.
10825 openTooltip: function (tooltip, latlng, options) {
10826 this._initOverlay(Tooltip, tooltip, latlng, options)
10827 .openOn(this);
10828
10829 return this;
10830 },
10831
10832 // @method closeTooltip(tooltip: Tooltip): this
10833 // Closes the tooltip given as parameter.
10834 closeTooltip: function (tooltip) {
10835 tooltip.close();
10836 return this;
10837 }
10838
10839 });
10840
10841 /*
10842 * @namespace Layer
10843 * @section Tooltip methods example
10844 *
10845 * All layers share a set of methods convenient for binding tooltips to it.
10846 *
10847 * ```js
10848 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
10849 * layer.openTooltip();
10850 * layer.closeTooltip();
10851 * ```
10852 */
10853
10854 // @section Tooltip methods
10855 Layer.include({
10856
10857 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
10858 // Binds a tooltip to the layer with the passed `content` and sets up the
10859 // necessary event listeners. If a `Function` is passed it will receive
10860 // the layer as the first argument and should return a `String` or `HTMLElement`.
10861 bindTooltip: function (content, options) {
10862
10863 if (this._tooltip && this.isTooltipOpen()) {
10864 this.unbindTooltip();
10865 }
10866
10867 this._tooltip = this._initOverlay(Tooltip, this._tooltip, content, options);
10868 this._initTooltipInteractions();
10869
10870 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
10871 this.openTooltip();
10872 }
10873
10874 return this;
10875 },
10876
10877 // @method unbindTooltip(): this
10878 // Removes the tooltip previously bound with `bindTooltip`.
10879 unbindTooltip: function () {
10880 if (this._tooltip) {
10881 this._initTooltipInteractions(true);
10882 this.closeTooltip();
10883 this._tooltip = null;
10884 }
10885 return this;
10886 },
10887
10888 _initTooltipInteractions: function (remove) {
10889 if (!remove && this._tooltipHandlersAdded) { return; }
10890 var onOff = remove ? 'off' : 'on',
10891 events = {
10892 remove: this.closeTooltip,
10893 move: this._moveTooltip
10894 };
10895 if (!this._tooltip.options.permanent) {
10896 events.mouseover = this._openTooltip;
10897 events.mouseout = this.closeTooltip;
10898 events.click = this._openTooltip;
10899 if (this._map) {
10900 this._addFocusListeners();
10901 } else {
10902 events.add = this._addFocusListeners;
10903 }
10904 } else {
10905 events.add = this._openTooltip;
10906 }
10907 if (this._tooltip.options.sticky) {
10908 events.mousemove = this._moveTooltip;
10909 }
10910 this[onOff](events);
10911 this._tooltipHandlersAdded = !remove;
10912 },
10913
10914 // @method openTooltip(latlng?: LatLng): this
10915 // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
10916 openTooltip: function (latlng) {
10917 if (this._tooltip) {
10918 if (!(this instanceof FeatureGroup)) {
10919 this._tooltip._source = this;
10920 }
10921 if (this._tooltip._prepareOpen(latlng)) {
10922 // open the tooltip on the map
10923 this._tooltip.openOn(this._map);
10924
10925 if (this.getElement) {
10926 this._setAriaDescribedByOnLayer(this);
10927 } else if (this.eachLayer) {
10928 this.eachLayer(this._setAriaDescribedByOnLayer, this);
10929 }
10930 }
10931 }
10932 return this;
10933 },
10934
10935 // @method closeTooltip(): this
10936 // Closes the tooltip bound to this layer if it is open.
10937 closeTooltip: function () {
10938 if (this._tooltip) {
10939 return this._tooltip.close();
10940 }
10941 },
10942
10943 // @method toggleTooltip(): this
10944 // Opens or closes the tooltip bound to this layer depending on its current state.
10945 toggleTooltip: function () {
10946 if (this._tooltip) {
10947 this._tooltip.toggle(this);
10948 }
10949 return this;
10950 },
10951
10952 // @method isTooltipOpen(): boolean
10953 // Returns `true` if the tooltip bound to this layer is currently open.
10954 isTooltipOpen: function () {
10955 return this._tooltip.isOpen();
10956 },
10957
10958 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10959 // Sets the content of the tooltip bound to this layer.
10960 setTooltipContent: function (content) {
10961 if (this._tooltip) {
10962 this._tooltip.setContent(content);
10963 }
10964 return this;
10965 },
10966
10967 // @method getTooltip(): Tooltip
10968 // Returns the tooltip bound to this layer.
10969 getTooltip: function () {
10970 return this._tooltip;
10971 },
10972
10973 _addFocusListeners: function () {
10974 if (this.getElement) {
10975 this._addFocusListenersOnLayer(this);
10976 } else if (this.eachLayer) {
10977 this.eachLayer(this._addFocusListenersOnLayer, this);
10978 }
10979 },
10980
10981 _addFocusListenersOnLayer: function (layer) {
10982 var el = typeof layer.getElement === 'function' && layer.getElement();
10983 if (el) {
10984 on(el, 'focus', function () {
10985 this._tooltip._source = layer;
10986 this.openTooltip();
10987 }, this);
10988 on(el, 'blur', this.closeTooltip, this);
10989 }
10990 },
10991
10992 _setAriaDescribedByOnLayer: function (layer) {
10993 var el = typeof layer.getElement === 'function' && layer.getElement();
10994 if (el) {
10995 el.setAttribute('aria-describedby', this._tooltip._container.id);
10996 }
10997 },
10998
10999
11000 _openTooltip: function (e) {
11001 if (!this._tooltip || !this._map) {
11002 return;
11003 }
11004
11005 // If the map is moving, we will show the tooltip after it's done.
11006 if (this._map.dragging && this._map.dragging.moving() && !this._openOnceFlag) {
11007 this._openOnceFlag = true;
11008 var that = this;
11009 this._map.once('moveend', function () {
11010 that._openOnceFlag = false;
11011 that._openTooltip(e);
11012 });
11013 return;
11014 }
11015
11016 this._tooltip._source = e.layer || e.target;
11017
11018 this.openTooltip(this._tooltip.options.sticky ? e.latlng : undefined);
11019 },
11020
11021 _moveTooltip: function (e) {
11022 var latlng = e.latlng, containerPoint, layerPoint;
11023 if (this._tooltip.options.sticky && e.originalEvent) {
11024 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
11025 layerPoint = this._map.containerPointToLayerPoint(containerPoint);
11026 latlng = this._map.layerPointToLatLng(layerPoint);
11027 }
11028 this._tooltip.setLatLng(latlng);
11029 }
11030 });
11031
11032 /*
11033 * @class DivIcon
11034 * @aka L.DivIcon
11035 * @inherits Icon
11036 *
11037 * Represents a lightweight icon for markers that uses a simple `<div>`
11038 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
11039 *
11040 * @example
11041 * ```js
11042 * var myIcon = L.divIcon({className: 'my-div-icon'});
11043 * // you can set .my-div-icon styles in CSS
11044 *
11045 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
11046 * ```
11047 *
11048 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
11049 */
11050
11051 var DivIcon = Icon.extend({
11052 options: {
11053 // @section
11054 // @aka DivIcon options
11055 iconSize: [12, 12], // also can be set through CSS
11056
11057 // iconAnchor: (Point),
11058 // popupAnchor: (Point),
11059
11060 // @option html: String|HTMLElement = ''
11061 // Custom HTML code to put inside the div element, empty by default. Alternatively,
11062 // an instance of `HTMLElement`.
11063 html: false,
11064
11065 // @option bgPos: Point = [0, 0]
11066 // Optional relative position of the background, in pixels
11067 bgPos: null,
11068
11069 className: 'leaflet-div-icon'
11070 },
11071
11072 createIcon: function (oldIcon) {
11073 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
11074 options = this.options;
11075
11076 if (options.html instanceof Element) {
11077 empty(div);
11078 div.appendChild(options.html);
11079 } else {
11080 div.innerHTML = options.html !== false ? options.html : '';
11081 }
11082
11083 if (options.bgPos) {
11084 var bgPos = toPoint(options.bgPos);
11085 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
11086 }
11087 this._setIconStyles(div, 'icon');
11088
11089 return div;
11090 },
11091
11092 createShadow: function () {
11093 return null;
11094 }
11095 });
11096
11097 // @factory L.divIcon(options: DivIcon options)
11098 // Creates a `DivIcon` instance with the given options.
11099 function divIcon(options) {
11100 return new DivIcon(options);
11101 }
11102
11103 Icon.Default = IconDefault;
11104
11105 /*
11106 * @class GridLayer
11107 * @inherits Layer
11108 * @aka L.GridLayer
11109 *
11110 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
11111 * 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.
11112 *
11113 *
11114 * @section Synchronous usage
11115 * @example
11116 *
11117 * 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.
11118 *
11119 * ```js
11120 * var CanvasLayer = L.GridLayer.extend({
11121 * createTile: function(coords){
11122 * // create a <canvas> element for drawing
11123 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
11124 *
11125 * // setup tile width and height according to the options
11126 * var size = this.getTileSize();
11127 * tile.width = size.x;
11128 * tile.height = size.y;
11129 *
11130 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z
11131 * var ctx = tile.getContext('2d');
11132 *
11133 * // return the tile so it can be rendered on screen
11134 * return tile;
11135 * }
11136 * });
11137 * ```
11138 *
11139 * @section Asynchronous usage
11140 * @example
11141 *
11142 * 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.
11143 *
11144 * ```js
11145 * var CanvasLayer = L.GridLayer.extend({
11146 * createTile: function(coords, done){
11147 * var error;
11148 *
11149 * // create a <canvas> element for drawing
11150 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
11151 *
11152 * // setup tile width and height according to the options
11153 * var size = this.getTileSize();
11154 * tile.width = size.x;
11155 * tile.height = size.y;
11156 *
11157 * // draw something asynchronously and pass the tile to the done() callback
11158 * setTimeout(function() {
11159 * done(error, tile);
11160 * }, 1000);
11161 *
11162 * return tile;
11163 * }
11164 * });
11165 * ```
11166 *
11167 * @section
11168 */
11169
11170
11171 var GridLayer = Layer.extend({
11172
11173 // @section
11174 // @aka GridLayer options
11175 options: {
11176 // @option tileSize: Number|Point = 256
11177 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
11178 tileSize: 256,
11179
11180 // @option opacity: Number = 1.0
11181 // Opacity of the tiles. Can be used in the `createTile()` function.
11182 opacity: 1,
11183
11184 // @option updateWhenIdle: Boolean = (depends)
11185 // Load new tiles only when panning ends.
11186 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
11187 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
11188 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
11189 updateWhenIdle: Browser.mobile,
11190
11191 // @option updateWhenZooming: Boolean = true
11192 // 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.
11193 updateWhenZooming: true,
11194
11195 // @option updateInterval: Number = 200
11196 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
11197 updateInterval: 200,
11198
11199 // @option zIndex: Number = 1
11200 // The explicit zIndex of the tile layer.
11201 zIndex: 1,
11202
11203 // @option bounds: LatLngBounds = undefined
11204 // If set, tiles will only be loaded inside the set `LatLngBounds`.
11205 bounds: null,
11206
11207 // @option minZoom: Number = 0
11208 // The minimum zoom level down to which this layer will be displayed (inclusive).
11209 minZoom: 0,
11210
11211 // @option maxZoom: Number = undefined
11212 // The maximum zoom level up to which this layer will be displayed (inclusive).
11213 maxZoom: undefined,
11214
11215 // @option maxNativeZoom: Number = undefined
11216 // Maximum zoom number the tile source has available. If it is specified,
11217 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
11218 // from `maxNativeZoom` level and auto-scaled.
11219 maxNativeZoom: undefined,
11220
11221 // @option minNativeZoom: Number = undefined
11222 // Minimum zoom number the tile source has available. If it is specified,
11223 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
11224 // from `minNativeZoom` level and auto-scaled.
11225 minNativeZoom: undefined,
11226
11227 // @option noWrap: Boolean = false
11228 // Whether the layer is wrapped around the antimeridian. If `true`, the
11229 // GridLayer will only be displayed once at low zoom levels. Has no
11230 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
11231 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
11232 // tiles outside the CRS limits.
11233 noWrap: false,
11234
11235 // @option pane: String = 'tilePane'
11236 // `Map pane` where the grid layer will be added.
11237 pane: 'tilePane',
11238
11239 // @option className: String = ''
11240 // A custom class name to assign to the tile layer. Empty by default.
11241 className: '',
11242
11243 // @option keepBuffer: Number = 2
11244 // When panning the map, keep this many rows and columns of tiles before unloading them.
11245 keepBuffer: 2
11246 },
11247
11248 initialize: function (options) {
11249 setOptions(this, options);
11250 },
11251
11252 onAdd: function () {
11253 this._initContainer();
11254
11255 this._levels = {};
11256 this._tiles = {};
11257
11258 this._resetView(); // implicit _update() call
11259 },
11260
11261 beforeAdd: function (map) {
11262 map._addZoomLimit(this);
11263 },
11264
11265 onRemove: function (map) {
11266 this._removeAllTiles();
11267 remove(this._container);
11268 map._removeZoomLimit(this);
11269 this._container = null;
11270 this._tileZoom = undefined;
11271 },
11272
11273 // @method bringToFront: this
11274 // Brings the tile layer to the top of all tile layers.
11275 bringToFront: function () {
11276 if (this._map) {
11277 toFront(this._container);
11278 this._setAutoZIndex(Math.max);
11279 }
11280 return this;
11281 },
11282
11283 // @method bringToBack: this
11284 // Brings the tile layer to the bottom of all tile layers.
11285 bringToBack: function () {
11286 if (this._map) {
11287 toBack(this._container);
11288 this._setAutoZIndex(Math.min);
11289 }
11290 return this;
11291 },
11292
11293 // @method getContainer: HTMLElement
11294 // Returns the HTML element that contains the tiles for this layer.
11295 getContainer: function () {
11296 return this._container;
11297 },
11298
11299 // @method setOpacity(opacity: Number): this
11300 // Changes the [opacity](#gridlayer-opacity) of the grid layer.
11301 setOpacity: function (opacity) {
11302 this.options.opacity = opacity;
11303 this._updateOpacity();
11304 return this;
11305 },
11306
11307 // @method setZIndex(zIndex: Number): this
11308 // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
11309 setZIndex: function (zIndex) {
11310 this.options.zIndex = zIndex;
11311 this._updateZIndex();
11312
11313 return this;
11314 },
11315
11316 // @method isLoading: Boolean
11317 // Returns `true` if any tile in the grid layer has not finished loading.
11318 isLoading: function () {
11319 return this._loading;
11320 },
11321
11322 // @method redraw: this
11323 // Causes the layer to clear all the tiles and request them again.
11324 redraw: function () {
11325 if (this._map) {
11326 this._removeAllTiles();
11327 var tileZoom = this._clampZoom(this._map.getZoom());
11328 if (tileZoom !== this._tileZoom) {
11329 this._tileZoom = tileZoom;
11330 this._updateLevels();
11331 }
11332 this._update();
11333 }
11334 return this;
11335 },
11336
11337 getEvents: function () {
11338 var events = {
11339 viewprereset: this._invalidateAll,
11340 viewreset: this._resetView,
11341 zoom: this._resetView,
11342 moveend: this._onMoveEnd
11343 };
11344
11345 if (!this.options.updateWhenIdle) {
11346 // update tiles on move, but not more often than once per given interval
11347 if (!this._onMove) {
11348 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
11349 }
11350
11351 events.move = this._onMove;
11352 }
11353
11354 if (this._zoomAnimated) {
11355 events.zoomanim = this._animateZoom;
11356 }
11357
11358 return events;
11359 },
11360
11361 // @section Extension methods
11362 // Layers extending `GridLayer` shall reimplement the following method.
11363 // @method createTile(coords: Object, done?: Function): HTMLElement
11364 // Called only internally, must be overridden by classes extending `GridLayer`.
11365 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
11366 // is specified, it must be called when the tile has finished loading and drawing.
11367 createTile: function () {
11368 return document.createElement('div');
11369 },
11370
11371 // @section
11372 // @method getTileSize: Point
11373 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
11374 getTileSize: function () {
11375 var s = this.options.tileSize;
11376 return s instanceof Point ? s : new Point(s, s);
11377 },
11378
11379 _updateZIndex: function () {
11380 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
11381 this._container.style.zIndex = this.options.zIndex;
11382 }
11383 },
11384
11385 _setAutoZIndex: function (compare) {
11386 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
11387
11388 var layers = this.getPane().children,
11389 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
11390
11391 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
11392
11393 zIndex = layers[i].style.zIndex;
11394
11395 if (layers[i] !== this._container && zIndex) {
11396 edgeZIndex = compare(edgeZIndex, +zIndex);
11397 }
11398 }
11399
11400 if (isFinite(edgeZIndex)) {
11401 this.options.zIndex = edgeZIndex + compare(-1, 1);
11402 this._updateZIndex();
11403 }
11404 },
11405
11406 _updateOpacity: function () {
11407 if (!this._map) { return; }
11408
11409 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
11410 if (Browser.ielt9) { return; }
11411
11412 setOpacity(this._container, this.options.opacity);
11413
11414 var now = +new Date(),
11415 nextFrame = false,
11416 willPrune = false;
11417
11418 for (var key in this._tiles) {
11419 var tile = this._tiles[key];
11420 if (!tile.current || !tile.loaded) { continue; }
11421
11422 var fade = Math.min(1, (now - tile.loaded) / 200);
11423
11424 setOpacity(tile.el, fade);
11425 if (fade < 1) {
11426 nextFrame = true;
11427 } else {
11428 if (tile.active) {
11429 willPrune = true;
11430 } else {
11431 this._onOpaqueTile(tile);
11432 }
11433 tile.active = true;
11434 }
11435 }
11436
11437 if (willPrune && !this._noPrune) { this._pruneTiles(); }
11438
11439 if (nextFrame) {
11440 cancelAnimFrame(this._fadeFrame);
11441 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11442 }
11443 },
11444
11445 _onOpaqueTile: falseFn,
11446
11447 _initContainer: function () {
11448 if (this._container) { return; }
11449
11450 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
11451 this._updateZIndex();
11452
11453 if (this.options.opacity < 1) {
11454 this._updateOpacity();
11455 }
11456
11457 this.getPane().appendChild(this._container);
11458 },
11459
11460 _updateLevels: function () {
11461
11462 var zoom = this._tileZoom,
11463 maxZoom = this.options.maxZoom;
11464
11465 if (zoom === undefined) { return undefined; }
11466
11467 for (var z in this._levels) {
11468 z = Number(z);
11469 if (this._levels[z].el.children.length || z === zoom) {
11470 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
11471 this._onUpdateLevel(z);
11472 } else {
11473 remove(this._levels[z].el);
11474 this._removeTilesAtZoom(z);
11475 this._onRemoveLevel(z);
11476 delete this._levels[z];
11477 }
11478 }
11479
11480 var level = this._levels[zoom],
11481 map = this._map;
11482
11483 if (!level) {
11484 level = this._levels[zoom] = {};
11485
11486 level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
11487 level.el.style.zIndex = maxZoom;
11488
11489 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
11490 level.zoom = zoom;
11491
11492 this._setZoomTransform(level, map.getCenter(), map.getZoom());
11493
11494 // force the browser to consider the newly added element for transition
11495 falseFn(level.el.offsetWidth);
11496
11497 this._onCreateLevel(level);
11498 }
11499
11500 this._level = level;
11501
11502 return level;
11503 },
11504
11505 _onUpdateLevel: falseFn,
11506
11507 _onRemoveLevel: falseFn,
11508
11509 _onCreateLevel: falseFn,
11510
11511 _pruneTiles: function () {
11512 if (!this._map) {
11513 return;
11514 }
11515
11516 var key, tile;
11517
11518 var zoom = this._map.getZoom();
11519 if (zoom > this.options.maxZoom ||
11520 zoom < this.options.minZoom) {
11521 this._removeAllTiles();
11522 return;
11523 }
11524
11525 for (key in this._tiles) {
11526 tile = this._tiles[key];
11527 tile.retain = tile.current;
11528 }
11529
11530 for (key in this._tiles) {
11531 tile = this._tiles[key];
11532 if (tile.current && !tile.active) {
11533 var coords = tile.coords;
11534 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
11535 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
11536 }
11537 }
11538 }
11539
11540 for (key in this._tiles) {
11541 if (!this._tiles[key].retain) {
11542 this._removeTile(key);
11543 }
11544 }
11545 },
11546
11547 _removeTilesAtZoom: function (zoom) {
11548 for (var key in this._tiles) {
11549 if (this._tiles[key].coords.z !== zoom) {
11550 continue;
11551 }
11552 this._removeTile(key);
11553 }
11554 },
11555
11556 _removeAllTiles: function () {
11557 for (var key in this._tiles) {
11558 this._removeTile(key);
11559 }
11560 },
11561
11562 _invalidateAll: function () {
11563 for (var z in this._levels) {
11564 remove(this._levels[z].el);
11565 this._onRemoveLevel(Number(z));
11566 delete this._levels[z];
11567 }
11568 this._removeAllTiles();
11569
11570 this._tileZoom = undefined;
11571 },
11572
11573 _retainParent: function (x, y, z, minZoom) {
11574 var x2 = Math.floor(x / 2),
11575 y2 = Math.floor(y / 2),
11576 z2 = z - 1,
11577 coords2 = new Point(+x2, +y2);
11578 coords2.z = +z2;
11579
11580 var key = this._tileCoordsToKey(coords2),
11581 tile = this._tiles[key];
11582
11583 if (tile && tile.active) {
11584 tile.retain = true;
11585 return true;
11586
11587 } else if (tile && tile.loaded) {
11588 tile.retain = true;
11589 }
11590
11591 if (z2 > minZoom) {
11592 return this._retainParent(x2, y2, z2, minZoom);
11593 }
11594
11595 return false;
11596 },
11597
11598 _retainChildren: function (x, y, z, maxZoom) {
11599
11600 for (var i = 2 * x; i < 2 * x + 2; i++) {
11601 for (var j = 2 * y; j < 2 * y + 2; j++) {
11602
11603 var coords = new Point(i, j);
11604 coords.z = z + 1;
11605
11606 var key = this._tileCoordsToKey(coords),
11607 tile = this._tiles[key];
11608
11609 if (tile && tile.active) {
11610 tile.retain = true;
11611 continue;
11612
11613 } else if (tile && tile.loaded) {
11614 tile.retain = true;
11615 }
11616
11617 if (z + 1 < maxZoom) {
11618 this._retainChildren(i, j, z + 1, maxZoom);
11619 }
11620 }
11621 }
11622 },
11623
11624 _resetView: function (e) {
11625 var animating = e && (e.pinch || e.flyTo);
11626 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
11627 },
11628
11629 _animateZoom: function (e) {
11630 this._setView(e.center, e.zoom, true, e.noUpdate);
11631 },
11632
11633 _clampZoom: function (zoom) {
11634 var options = this.options;
11635
11636 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
11637 return options.minNativeZoom;
11638 }
11639
11640 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
11641 return options.maxNativeZoom;
11642 }
11643
11644 return zoom;
11645 },
11646
11647 _setView: function (center, zoom, noPrune, noUpdate) {
11648 var tileZoom = Math.round(zoom);
11649 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
11650 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
11651 tileZoom = undefined;
11652 } else {
11653 tileZoom = this._clampZoom(tileZoom);
11654 }
11655
11656 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
11657
11658 if (!noUpdate || tileZoomChanged) {
11659
11660 this._tileZoom = tileZoom;
11661
11662 if (this._abortLoading) {
11663 this._abortLoading();
11664 }
11665
11666 this._updateLevels();
11667 this._resetGrid();
11668
11669 if (tileZoom !== undefined) {
11670 this._update(center);
11671 }
11672
11673 if (!noPrune) {
11674 this._pruneTiles();
11675 }
11676
11677 // Flag to prevent _updateOpacity from pruning tiles during
11678 // a zoom anim or a pinch gesture
11679 this._noPrune = !!noPrune;
11680 }
11681
11682 this._setZoomTransforms(center, zoom);
11683 },
11684
11685 _setZoomTransforms: function (center, zoom) {
11686 for (var i in this._levels) {
11687 this._setZoomTransform(this._levels[i], center, zoom);
11688 }
11689 },
11690
11691 _setZoomTransform: function (level, center, zoom) {
11692 var scale = this._map.getZoomScale(zoom, level.zoom),
11693 translate = level.origin.multiplyBy(scale)
11694 .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
11695
11696 if (Browser.any3d) {
11697 setTransform(level.el, translate, scale);
11698 } else {
11699 setPosition(level.el, translate);
11700 }
11701 },
11702
11703 _resetGrid: function () {
11704 var map = this._map,
11705 crs = map.options.crs,
11706 tileSize = this._tileSize = this.getTileSize(),
11707 tileZoom = this._tileZoom;
11708
11709 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
11710 if (bounds) {
11711 this._globalTileRange = this._pxBoundsToTileRange(bounds);
11712 }
11713
11714 this._wrapX = crs.wrapLng && !this.options.noWrap && [
11715 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
11716 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
11717 ];
11718 this._wrapY = crs.wrapLat && !this.options.noWrap && [
11719 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
11720 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
11721 ];
11722 },
11723
11724 _onMoveEnd: function () {
11725 if (!this._map || this._map._animatingZoom) { return; }
11726
11727 this._update();
11728 },
11729
11730 _getTiledPixelBounds: function (center) {
11731 var map = this._map,
11732 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
11733 scale = map.getZoomScale(mapZoom, this._tileZoom),
11734 pixelCenter = map.project(center, this._tileZoom).floor(),
11735 halfSize = map.getSize().divideBy(scale * 2);
11736
11737 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
11738 },
11739
11740 // Private method to load tiles in the grid's active zoom level according to map bounds
11741 _update: function (center) {
11742 var map = this._map;
11743 if (!map) { return; }
11744 var zoom = this._clampZoom(map.getZoom());
11745
11746 if (center === undefined) { center = map.getCenter(); }
11747 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom
11748
11749 var pixelBounds = this._getTiledPixelBounds(center),
11750 tileRange = this._pxBoundsToTileRange(pixelBounds),
11751 tileCenter = tileRange.getCenter(),
11752 queue = [],
11753 margin = this.options.keepBuffer,
11754 noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
11755 tileRange.getTopRight().add([margin, -margin]));
11756
11757 // Sanity check: panic if the tile range contains Infinity somewhere.
11758 if (!(isFinite(tileRange.min.x) &&
11759 isFinite(tileRange.min.y) &&
11760 isFinite(tileRange.max.x) &&
11761 isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
11762
11763 for (var key in this._tiles) {
11764 var c = this._tiles[key].coords;
11765 if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
11766 this._tiles[key].current = false;
11767 }
11768 }
11769
11770 // _update just loads more tiles. If the tile zoom level differs too much
11771 // from the map's, let _setView reset levels and prune old tiles.
11772 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
11773
11774 // create a queue of coordinates to load tiles from
11775 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
11776 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
11777 var coords = new Point(i, j);
11778 coords.z = this._tileZoom;
11779
11780 if (!this._isValidTile(coords)) { continue; }
11781
11782 var tile = this._tiles[this._tileCoordsToKey(coords)];
11783 if (tile) {
11784 tile.current = true;
11785 } else {
11786 queue.push(coords);
11787 }
11788 }
11789 }
11790
11791 // sort tile queue to load tiles in order of their distance to center
11792 queue.sort(function (a, b) {
11793 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
11794 });
11795
11796 if (queue.length !== 0) {
11797 // if it's the first batch of tiles to load
11798 if (!this._loading) {
11799 this._loading = true;
11800 // @event loading: Event
11801 // Fired when the grid layer starts loading tiles.
11802 this.fire('loading');
11803 }
11804
11805 // create DOM fragment to append tiles in one batch
11806 var fragment = document.createDocumentFragment();
11807
11808 for (i = 0; i < queue.length; i++) {
11809 this._addTile(queue[i], fragment);
11810 }
11811
11812 this._level.el.appendChild(fragment);
11813 }
11814 },
11815
11816 _isValidTile: function (coords) {
11817 var crs = this._map.options.crs;
11818
11819 if (!crs.infinite) {
11820 // don't load tile if it's out of bounds and not wrapped
11821 var bounds = this._globalTileRange;
11822 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
11823 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
11824 }
11825
11826 if (!this.options.bounds) { return true; }
11827
11828 // don't load tile if it doesn't intersect the bounds in options
11829 var tileBounds = this._tileCoordsToBounds(coords);
11830 return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
11831 },
11832
11833 _keyToBounds: function (key) {
11834 return this._tileCoordsToBounds(this._keyToTileCoords(key));
11835 },
11836
11837 _tileCoordsToNwSe: function (coords) {
11838 var map = this._map,
11839 tileSize = this.getTileSize(),
11840 nwPoint = coords.scaleBy(tileSize),
11841 sePoint = nwPoint.add(tileSize),
11842 nw = map.unproject(nwPoint, coords.z),
11843 se = map.unproject(sePoint, coords.z);
11844 return [nw, se];
11845 },
11846
11847 // converts tile coordinates to its geographical bounds
11848 _tileCoordsToBounds: function (coords) {
11849 var bp = this._tileCoordsToNwSe(coords),
11850 bounds = new LatLngBounds(bp[0], bp[1]);
11851
11852 if (!this.options.noWrap) {
11853 bounds = this._map.wrapLatLngBounds(bounds);
11854 }
11855 return bounds;
11856 },
11857 // converts tile coordinates to key for the tile cache
11858 _tileCoordsToKey: function (coords) {
11859 return coords.x + ':' + coords.y + ':' + coords.z;
11860 },
11861
11862 // converts tile cache key to coordinates
11863 _keyToTileCoords: function (key) {
11864 var k = key.split(':'),
11865 coords = new Point(+k[0], +k[1]);
11866 coords.z = +k[2];
11867 return coords;
11868 },
11869
11870 _removeTile: function (key) {
11871 var tile = this._tiles[key];
11872 if (!tile) { return; }
11873
11874 remove(tile.el);
11875
11876 delete this._tiles[key];
11877
11878 // @event tileunload: TileEvent
11879 // Fired when a tile is removed (e.g. when a tile goes off the screen).
11880 this.fire('tileunload', {
11881 tile: tile.el,
11882 coords: this._keyToTileCoords(key)
11883 });
11884 },
11885
11886 _initTile: function (tile) {
11887 addClass(tile, 'leaflet-tile');
11888
11889 var tileSize = this.getTileSize();
11890 tile.style.width = tileSize.x + 'px';
11891 tile.style.height = tileSize.y + 'px';
11892
11893 tile.onselectstart = falseFn;
11894 tile.onmousemove = falseFn;
11895
11896 // update opacity on tiles in IE7-8 because of filter inheritance problems
11897 if (Browser.ielt9 && this.options.opacity < 1) {
11898 setOpacity(tile, this.options.opacity);
11899 }
11900 },
11901
11902 _addTile: function (coords, container) {
11903 var tilePos = this._getTilePos(coords),
11904 key = this._tileCoordsToKey(coords);
11905
11906 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
11907
11908 this._initTile(tile);
11909
11910 // if createTile is defined with a second argument ("done" callback),
11911 // we know that tile is async and will be ready later; otherwise
11912 if (this.createTile.length < 2) {
11913 // mark tile as ready, but delay one frame for opacity animation to happen
11914 requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
11915 }
11916
11917 setPosition(tile, tilePos);
11918
11919 // save tile in cache
11920 this._tiles[key] = {
11921 el: tile,
11922 coords: coords,
11923 current: true
11924 };
11925
11926 container.appendChild(tile);
11927 // @event tileloadstart: TileEvent
11928 // Fired when a tile is requested and starts loading.
11929 this.fire('tileloadstart', {
11930 tile: tile,
11931 coords: coords
11932 });
11933 },
11934
11935 _tileReady: function (coords, err, tile) {
11936 if (err) {
11937 // @event tileerror: TileErrorEvent
11938 // Fired when there is an error loading a tile.
11939 this.fire('tileerror', {
11940 error: err,
11941 tile: tile,
11942 coords: coords
11943 });
11944 }
11945
11946 var key = this._tileCoordsToKey(coords);
11947
11948 tile = this._tiles[key];
11949 if (!tile) { return; }
11950
11951 tile.loaded = +new Date();
11952 if (this._map._fadeAnimated) {
11953 setOpacity(tile.el, 0);
11954 cancelAnimFrame(this._fadeFrame);
11955 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11956 } else {
11957 tile.active = true;
11958 this._pruneTiles();
11959 }
11960
11961 if (!err) {
11962 addClass(tile.el, 'leaflet-tile-loaded');
11963
11964 // @event tileload: TileEvent
11965 // Fired when a tile loads.
11966 this.fire('tileload', {
11967 tile: tile.el,
11968 coords: coords
11969 });
11970 }
11971
11972 if (this._noTilesToLoad()) {
11973 this._loading = false;
11974 // @event load: Event
11975 // Fired when the grid layer loaded all visible tiles.
11976 this.fire('load');
11977
11978 if (Browser.ielt9 || !this._map._fadeAnimated) {
11979 requestAnimFrame(this._pruneTiles, this);
11980 } else {
11981 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
11982 // to trigger a pruning.
11983 setTimeout(bind(this._pruneTiles, this), 250);
11984 }
11985 }
11986 },
11987
11988 _getTilePos: function (coords) {
11989 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
11990 },
11991
11992 _wrapCoords: function (coords) {
11993 var newCoords = new Point(
11994 this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
11995 this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
11996 newCoords.z = coords.z;
11997 return newCoords;
11998 },
11999
12000 _pxBoundsToTileRange: function (bounds) {
12001 var tileSize = this.getTileSize();
12002 return new Bounds(
12003 bounds.min.unscaleBy(tileSize).floor(),
12004 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
12005 },
12006
12007 _noTilesToLoad: function () {
12008 for (var key in this._tiles) {
12009 if (!this._tiles[key].loaded) { return false; }
12010 }
12011 return true;
12012 }
12013 });
12014
12015 // @factory L.gridLayer(options?: GridLayer options)
12016 // Creates a new instance of GridLayer with the supplied options.
12017 function gridLayer(options) {
12018 return new GridLayer(options);
12019 }
12020
12021 /*
12022 * @class TileLayer
12023 * @inherits GridLayer
12024 * @aka L.TileLayer
12025 * 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`.
12026 *
12027 * @example
12028 *
12029 * ```js
12030 * 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);
12031 * ```
12032 *
12033 * @section URL template
12034 * @example
12035 *
12036 * A string of the following form:
12037 *
12038 * ```
12039 * 'https://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
12040 * ```
12041 *
12042 * `{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.
12043 *
12044 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
12045 *
12046 * ```
12047 * L.tileLayer('https://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
12048 * ```
12049 */
12050
12051
12052 var TileLayer = GridLayer.extend({
12053
12054 // @section
12055 // @aka TileLayer options
12056 options: {
12057 // @option minZoom: Number = 0
12058 // The minimum zoom level down to which this layer will be displayed (inclusive).
12059 minZoom: 0,
12060
12061 // @option maxZoom: Number = 18
12062 // The maximum zoom level up to which this layer will be displayed (inclusive).
12063 maxZoom: 18,
12064
12065 // @option subdomains: String|String[] = 'abc'
12066 // 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.
12067 subdomains: 'abc',
12068
12069 // @option errorTileUrl: String = ''
12070 // URL to the tile image to show in place of the tile that failed to load.
12071 errorTileUrl: '',
12072
12073 // @option zoomOffset: Number = 0
12074 // The zoom number used in tile URLs will be offset with this value.
12075 zoomOffset: 0,
12076
12077 // @option tms: Boolean = false
12078 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
12079 tms: false,
12080
12081 // @option zoomReverse: Boolean = false
12082 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
12083 zoomReverse: false,
12084
12085 // @option detectRetina: Boolean = false
12086 // 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.
12087 detectRetina: false,
12088
12089 // @option crossOrigin: Boolean|String = false
12090 // Whether the crossOrigin attribute will be added to the tiles.
12091 // 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.
12092 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
12093 crossOrigin: false,
12094
12095 // @option referrerPolicy: Boolean|String = false
12096 // Whether the referrerPolicy attribute will be added to the tiles.
12097 // If a String is provided, all tiles will have their referrerPolicy attribute set to the String provided.
12098 // This may be needed if your map's rendering context has a strict default but your tile provider expects a valid referrer
12099 // (e.g. to validate an API token).
12100 // Refer to [HTMLImageElement.referrerPolicy](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/referrerPolicy) for valid String values.
12101 referrerPolicy: false
12102 },
12103
12104 initialize: function (url, options) {
12105
12106 this._url = url;
12107
12108 options = setOptions(this, options);
12109
12110 // detecting retina displays, adjusting tileSize and zoom levels
12111 if (options.detectRetina && Browser.retina && options.maxZoom > 0) {
12112
12113 options.tileSize = Math.floor(options.tileSize / 2);
12114
12115 if (!options.zoomReverse) {
12116 options.zoomOffset++;
12117 options.maxZoom = Math.max(options.minZoom, options.maxZoom - 1);
12118 } else {
12119 options.zoomOffset--;
12120 options.minZoom = Math.min(options.maxZoom, options.minZoom + 1);
12121 }
12122
12123 options.minZoom = Math.max(0, options.minZoom);
12124 } else if (!options.zoomReverse) {
12125 // make sure maxZoom is gte minZoom
12126 options.maxZoom = Math.max(options.minZoom, options.maxZoom);
12127 } else {
12128 // make sure minZoom is lte maxZoom
12129 options.minZoom = Math.min(options.maxZoom, options.minZoom);
12130 }
12131
12132 if (typeof options.subdomains === 'string') {
12133 options.subdomains = options.subdomains.split('');
12134 }
12135
12136 this.on('tileunload', this._onTileRemove);
12137 },
12138
12139 // @method setUrl(url: String, noRedraw?: Boolean): this
12140 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
12141 // If the URL does not change, the layer will not be redrawn unless
12142 // the noRedraw parameter is set to false.
12143 setUrl: function (url, noRedraw) {
12144 if (this._url === url && noRedraw === undefined) {
12145 noRedraw = true;
12146 }
12147
12148 this._url = url;
12149
12150 if (!noRedraw) {
12151 this.redraw();
12152 }
12153 return this;
12154 },
12155
12156 // @method createTile(coords: Object, done?: Function): HTMLElement
12157 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
12158 // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
12159 // callback is called when the tile has been loaded.
12160 createTile: function (coords, done) {
12161 var tile = document.createElement('img');
12162
12163 on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
12164 on(tile, 'error', bind(this._tileOnError, this, done, tile));
12165
12166 if (this.options.crossOrigin || this.options.crossOrigin === '') {
12167 tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
12168 }
12169
12170 // for this new option we follow the documented behavior
12171 // more closely by only setting the property when string
12172 if (typeof this.options.referrerPolicy === 'string') {
12173 tile.referrerPolicy = this.options.referrerPolicy;
12174 }
12175
12176 // The alt attribute is set to the empty string,
12177 // allowing screen readers to ignore the decorative image tiles.
12178 // https://www.w3.org/WAI/tutorials/images/decorative/
12179 // https://www.w3.org/TR/html-aria/#el-img-empty-alt
12180 tile.alt = '';
12181
12182 tile.src = this.getTileUrl(coords);
12183
12184 return tile;
12185 },
12186
12187 // @section Extension methods
12188 // @uninheritable
12189 // Layers extending `TileLayer` might reimplement the following method.
12190 // @method getTileUrl(coords: Object): String
12191 // Called only internally, returns the URL for a tile given its coordinates.
12192 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
12193 getTileUrl: function (coords) {
12194 var data = {
12195 r: Browser.retina ? '@2x' : '',
12196 s: this._getSubdomain(coords),
12197 x: coords.x,
12198 y: coords.y,
12199 z: this._getZoomForUrl()
12200 };
12201 if (this._map && !this._map.options.crs.infinite) {
12202 var invertedY = this._globalTileRange.max.y - coords.y;
12203 if (this.options.tms) {
12204 data['y'] = invertedY;
12205 }
12206 data['-y'] = invertedY;
12207 }
12208
12209 return template(this._url, extend(data, this.options));
12210 },
12211
12212 _tileOnLoad: function (done, tile) {
12213 // For https://github.com/Leaflet/Leaflet/issues/3332
12214 if (Browser.ielt9) {
12215 setTimeout(bind(done, this, null, tile), 0);
12216 } else {
12217 done(null, tile);
12218 }
12219 },
12220
12221 _tileOnError: function (done, tile, e) {
12222 var errorUrl = this.options.errorTileUrl;
12223 if (errorUrl && tile.getAttribute('src') !== errorUrl) {
12224 tile.src = errorUrl;
12225 }
12226 done(e, tile);
12227 },
12228
12229 _onTileRemove: function (e) {
12230 e.tile.onload = null;
12231 },
12232
12233 _getZoomForUrl: function () {
12234 var zoom = this._tileZoom,
12235 maxZoom = this.options.maxZoom,
12236 zoomReverse = this.options.zoomReverse,
12237 zoomOffset = this.options.zoomOffset;
12238
12239 if (zoomReverse) {
12240 zoom = maxZoom - zoom;
12241 }
12242
12243 return zoom + zoomOffset;
12244 },
12245
12246 _getSubdomain: function (tilePoint) {
12247 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
12248 return this.options.subdomains[index];
12249 },
12250
12251 // stops loading all tiles in the background layer
12252 _abortLoading: function () {
12253 var i, tile;
12254 for (i in this._tiles) {
12255 if (this._tiles[i].coords.z !== this._tileZoom) {
12256 tile = this._tiles[i].el;
12257
12258 tile.onload = falseFn;
12259 tile.onerror = falseFn;
12260
12261 if (!tile.complete) {
12262 tile.src = emptyImageUrl;
12263 var coords = this._tiles[i].coords;
12264 remove(tile);
12265 delete this._tiles[i];
12266 // @event tileabort: TileEvent
12267 // Fired when a tile was loading but is now not wanted.
12268 this.fire('tileabort', {
12269 tile: tile,
12270 coords: coords
12271 });
12272 }
12273 }
12274 }
12275 },
12276
12277 _removeTile: function (key) {
12278 var tile = this._tiles[key];
12279 if (!tile) { return; }
12280
12281 // Cancels any pending http requests associated with the tile
12282 tile.el.setAttribute('src', emptyImageUrl);
12283
12284 return GridLayer.prototype._removeTile.call(this, key);
12285 },
12286
12287 _tileReady: function (coords, err, tile) {
12288 if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) {
12289 return;
12290 }
12291
12292 return GridLayer.prototype._tileReady.call(this, coords, err, tile);
12293 }
12294 });
12295
12296
12297 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
12298 // Instantiates a tile layer object given a `URL template` and optionally an options object.
12299
12300 function tileLayer(url, options) {
12301 return new TileLayer(url, options);
12302 }
12303
12304 /*
12305 * @class TileLayer.WMS
12306 * @inherits TileLayer
12307 * @aka L.TileLayer.WMS
12308 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
12309 *
12310 * @example
12311 *
12312 * ```js
12313 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
12314 * layers: 'nexrad-n0r-900913',
12315 * format: 'image/png',
12316 * transparent: true,
12317 * attribution: "Weather data © 2012 IEM Nexrad"
12318 * });
12319 * ```
12320 */
12321
12322 var TileLayerWMS = TileLayer.extend({
12323
12324 // @section
12325 // @aka TileLayer.WMS options
12326 // If any custom options not documented here are used, they will be sent to the
12327 // WMS server as extra parameters in each request URL. This can be useful for
12328 // [non-standard vendor WMS parameters](https://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
12329 defaultWmsParams: {
12330 service: 'WMS',
12331 request: 'GetMap',
12332
12333 // @option layers: String = ''
12334 // **(required)** Comma-separated list of WMS layers to show.
12335 layers: '',
12336
12337 // @option styles: String = ''
12338 // Comma-separated list of WMS styles.
12339 styles: '',
12340
12341 // @option format: String = 'image/jpeg'
12342 // WMS image format (use `'image/png'` for layers with transparency).
12343 format: 'image/jpeg',
12344
12345 // @option transparent: Boolean = false
12346 // If `true`, the WMS service will return images with transparency.
12347 transparent: false,
12348
12349 // @option version: String = '1.1.1'
12350 // Version of the WMS service to use
12351 version: '1.1.1'
12352 },
12353
12354 options: {
12355 // @option crs: CRS = null
12356 // Coordinate Reference System to use for the WMS requests, defaults to
12357 // map CRS. Don't change this if you're not sure what it means.
12358 crs: null,
12359
12360 // @option uppercase: Boolean = false
12361 // If `true`, WMS request parameter keys will be uppercase.
12362 uppercase: false
12363 },
12364
12365 initialize: function (url, options) {
12366
12367 this._url = url;
12368
12369 var wmsParams = extend({}, this.defaultWmsParams);
12370
12371 // all keys that are not TileLayer options go to WMS params
12372 for (var i in options) {
12373 if (!(i in this.options)) {
12374 wmsParams[i] = options[i];
12375 }
12376 }
12377
12378 options = setOptions(this, options);
12379
12380 var realRetina = options.detectRetina && Browser.retina ? 2 : 1;
12381 var tileSize = this.getTileSize();
12382 wmsParams.width = tileSize.x * realRetina;
12383 wmsParams.height = tileSize.y * realRetina;
12384
12385 this.wmsParams = wmsParams;
12386 },
12387
12388 onAdd: function (map) {
12389
12390 this._crs = this.options.crs || map.options.crs;
12391 this._wmsVersion = parseFloat(this.wmsParams.version);
12392
12393 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
12394 this.wmsParams[projectionKey] = this._crs.code;
12395
12396 TileLayer.prototype.onAdd.call(this, map);
12397 },
12398
12399 getTileUrl: function (coords) {
12400
12401 var tileBounds = this._tileCoordsToNwSe(coords),
12402 crs = this._crs,
12403 bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
12404 min = bounds.min,
12405 max = bounds.max,
12406 bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
12407 [min.y, min.x, max.y, max.x] :
12408 [min.x, min.y, max.x, max.y]).join(','),
12409 url = TileLayer.prototype.getTileUrl.call(this, coords);
12410 return url +
12411 getParamString(this.wmsParams, url, this.options.uppercase) +
12412 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
12413 },
12414
12415 // @method setParams(params: Object, noRedraw?: Boolean): this
12416 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
12417 setParams: function (params, noRedraw) {
12418
12419 extend(this.wmsParams, params);
12420
12421 if (!noRedraw) {
12422 this.redraw();
12423 }
12424
12425 return this;
12426 }
12427 });
12428
12429
12430 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
12431 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
12432 function tileLayerWMS(url, options) {
12433 return new TileLayerWMS(url, options);
12434 }
12435
12436 TileLayer.WMS = TileLayerWMS;
12437 tileLayer.wms = tileLayerWMS;
12438
12439 /*
12440 * @class Renderer
12441 * @inherits Layer
12442 * @aka L.Renderer
12443 *
12444 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
12445 * DOM container of the renderer, its bounds, and its zoom animation.
12446 *
12447 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
12448 * itself can be added or removed to the map. All paths use a renderer, which can
12449 * be implicit (the map will decide the type of renderer and use it automatically)
12450 * or explicit (using the [`renderer`](#path-renderer) option of the path).
12451 *
12452 * Do not use this class directly, use `SVG` and `Canvas` instead.
12453 *
12454 * @event update: Event
12455 * Fired when the renderer updates its bounds, center and zoom, for example when
12456 * its map has moved
12457 */
12458
12459 var Renderer = Layer.extend({
12460
12461 // @section
12462 // @aka Renderer options
12463 options: {
12464 // @option padding: Number = 0.1
12465 // How much to extend the clip area around the map view (relative to its size)
12466 // e.g. 0.1 would be 10% of map view in each direction
12467 padding: 0.1
12468 },
12469
12470 initialize: function (options) {
12471 setOptions(this, options);
12472 stamp(this);
12473 this._layers = this._layers || {};
12474 },
12475
12476 onAdd: function () {
12477 if (!this._container) {
12478 this._initContainer(); // defined by renderer implementations
12479
12480 // always keep transform-origin as 0 0
12481 addClass(this._container, 'leaflet-zoom-animated');
12482 }
12483
12484 this.getPane().appendChild(this._container);
12485 this._update();
12486 this.on('update', this._updatePaths, this);
12487 },
12488
12489 onRemove: function () {
12490 this.off('update', this._updatePaths, this);
12491 this._destroyContainer();
12492 },
12493
12494 getEvents: function () {
12495 var events = {
12496 viewreset: this._reset,
12497 zoom: this._onZoom,
12498 moveend: this._update,
12499 zoomend: this._onZoomEnd
12500 };
12501 if (this._zoomAnimated) {
12502 events.zoomanim = this._onAnimZoom;
12503 }
12504 return events;
12505 },
12506
12507 _onAnimZoom: function (ev) {
12508 this._updateTransform(ev.center, ev.zoom);
12509 },
12510
12511 _onZoom: function () {
12512 this._updateTransform(this._map.getCenter(), this._map.getZoom());
12513 },
12514
12515 _updateTransform: function (center, zoom) {
12516 var scale = this._map.getZoomScale(zoom, this._zoom),
12517 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
12518 currentCenterPoint = this._map.project(this._center, zoom),
12519
12520 topLeftOffset = viewHalf.multiplyBy(-scale).add(currentCenterPoint)
12521 .subtract(this._map._getNewPixelOrigin(center, zoom));
12522
12523 if (Browser.any3d) {
12524 setTransform(this._container, topLeftOffset, scale);
12525 } else {
12526 setPosition(this._container, topLeftOffset);
12527 }
12528 },
12529
12530 _reset: function () {
12531 this._update();
12532 this._updateTransform(this._center, this._zoom);
12533
12534 for (var id in this._layers) {
12535 this._layers[id]._reset();
12536 }
12537 },
12538
12539 _onZoomEnd: function () {
12540 for (var id in this._layers) {
12541 this._layers[id]._project();
12542 }
12543 },
12544
12545 _updatePaths: function () {
12546 for (var id in this._layers) {
12547 this._layers[id]._update();
12548 }
12549 },
12550
12551 _update: function () {
12552 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
12553 // Subclasses are responsible of firing the 'update' event.
12554 var p = this.options.padding,
12555 size = this._map.getSize(),
12556 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
12557
12558 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
12559
12560 this._center = this._map.getCenter();
12561 this._zoom = this._map.getZoom();
12562 }
12563 });
12564
12565 /*
12566 * @class Canvas
12567 * @inherits Renderer
12568 * @aka L.Canvas
12569 *
12570 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
12571 * Inherits `Renderer`.
12572 *
12573 * Due to [technical limitations](https://caniuse.com/canvas), Canvas is not
12574 * available in all web browsers, notably IE8, and overlapping geometries might
12575 * not display properly in some edge cases.
12576 *
12577 * @example
12578 *
12579 * Use Canvas by default for all paths in the map:
12580 *
12581 * ```js
12582 * var map = L.map('map', {
12583 * renderer: L.canvas()
12584 * });
12585 * ```
12586 *
12587 * Use a Canvas renderer with extra padding for specific vector geometries:
12588 *
12589 * ```js
12590 * var map = L.map('map');
12591 * var myRenderer = L.canvas({ padding: 0.5 });
12592 * var line = L.polyline( coordinates, { renderer: myRenderer } );
12593 * var circle = L.circle( center, { renderer: myRenderer } );
12594 * ```
12595 */
12596
12597 var Canvas = Renderer.extend({
12598
12599 // @section
12600 // @aka Canvas options
12601 options: {
12602 // @option tolerance: Number = 0
12603 // How much to extend the click tolerance around a path/object on the map.
12604 tolerance: 0
12605 },
12606
12607 getEvents: function () {
12608 var events = Renderer.prototype.getEvents.call(this);
12609 events.viewprereset = this._onViewPreReset;
12610 return events;
12611 },
12612
12613 _onViewPreReset: function () {
12614 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
12615 this._postponeUpdatePaths = true;
12616 },
12617
12618 onAdd: function () {
12619 Renderer.prototype.onAdd.call(this);
12620
12621 // Redraw vectors since canvas is cleared upon removal,
12622 // in case of removing the renderer itself from the map.
12623 this._draw();
12624 },
12625
12626 _initContainer: function () {
12627 var container = this._container = document.createElement('canvas');
12628
12629 on(container, 'mousemove', this._onMouseMove, this);
12630 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
12631 on(container, 'mouseout', this._handleMouseOut, this);
12632 container['_leaflet_disable_events'] = true;
12633
12634 this._ctx = container.getContext('2d');
12635 },
12636
12637 _destroyContainer: function () {
12638 cancelAnimFrame(this._redrawRequest);
12639 delete this._ctx;
12640 remove(this._container);
12641 off(this._container);
12642 delete this._container;
12643 },
12644
12645 _updatePaths: function () {
12646 if (this._postponeUpdatePaths) { return; }
12647
12648 var layer;
12649 this._redrawBounds = null;
12650 for (var id in this._layers) {
12651 layer = this._layers[id];
12652 layer._update();
12653 }
12654 this._redraw();
12655 },
12656
12657 _update: function () {
12658 if (this._map._animatingZoom && this._bounds) { return; }
12659
12660 Renderer.prototype._update.call(this);
12661
12662 var b = this._bounds,
12663 container = this._container,
12664 size = b.getSize(),
12665 m = Browser.retina ? 2 : 1;
12666
12667 setPosition(container, b.min);
12668
12669 // set canvas size (also clearing it); use double size on retina
12670 container.width = m * size.x;
12671 container.height = m * size.y;
12672 container.style.width = size.x + 'px';
12673 container.style.height = size.y + 'px';
12674
12675 if (Browser.retina) {
12676 this._ctx.scale(2, 2);
12677 }
12678
12679 // translate so we use the same path coordinates after canvas element moves
12680 this._ctx.translate(-b.min.x, -b.min.y);
12681
12682 // Tell paths to redraw themselves
12683 this.fire('update');
12684 },
12685
12686 _reset: function () {
12687 Renderer.prototype._reset.call(this);
12688
12689 if (this._postponeUpdatePaths) {
12690 this._postponeUpdatePaths = false;
12691 this._updatePaths();
12692 }
12693 },
12694
12695 _initPath: function (layer) {
12696 this._updateDashArray(layer);
12697 this._layers[stamp(layer)] = layer;
12698
12699 var order = layer._order = {
12700 layer: layer,
12701 prev: this._drawLast,
12702 next: null
12703 };
12704 if (this._drawLast) { this._drawLast.next = order; }
12705 this._drawLast = order;
12706 this._drawFirst = this._drawFirst || this._drawLast;
12707 },
12708
12709 _addPath: function (layer) {
12710 this._requestRedraw(layer);
12711 },
12712
12713 _removePath: function (layer) {
12714 var order = layer._order;
12715 var next = order.next;
12716 var prev = order.prev;
12717
12718 if (next) {
12719 next.prev = prev;
12720 } else {
12721 this._drawLast = prev;
12722 }
12723 if (prev) {
12724 prev.next = next;
12725 } else {
12726 this._drawFirst = next;
12727 }
12728
12729 delete layer._order;
12730
12731 delete this._layers[stamp(layer)];
12732
12733 this._requestRedraw(layer);
12734 },
12735
12736 _updatePath: function (layer) {
12737 // Redraw the union of the layer's old pixel
12738 // bounds and the new pixel bounds.
12739 this._extendRedrawBounds(layer);
12740 layer._project();
12741 layer._update();
12742 // The redraw will extend the redraw bounds
12743 // with the new pixel bounds.
12744 this._requestRedraw(layer);
12745 },
12746
12747 _updateStyle: function (layer) {
12748 this._updateDashArray(layer);
12749 this._requestRedraw(layer);
12750 },
12751
12752 _updateDashArray: function (layer) {
12753 if (typeof layer.options.dashArray === 'string') {
12754 var parts = layer.options.dashArray.split(/[, ]+/),
12755 dashArray = [],
12756 dashValue,
12757 i;
12758 for (i = 0; i < parts.length; i++) {
12759 dashValue = Number(parts[i]);
12760 // Ignore dash array containing invalid lengths
12761 if (isNaN(dashValue)) { return; }
12762 dashArray.push(dashValue);
12763 }
12764 layer.options._dashArray = dashArray;
12765 } else {
12766 layer.options._dashArray = layer.options.dashArray;
12767 }
12768 },
12769
12770 _requestRedraw: function (layer) {
12771 if (!this._map) { return; }
12772
12773 this._extendRedrawBounds(layer);
12774 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
12775 },
12776
12777 _extendRedrawBounds: function (layer) {
12778 if (layer._pxBounds) {
12779 var padding = (layer.options.weight || 0) + 1;
12780 this._redrawBounds = this._redrawBounds || new Bounds();
12781 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
12782 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
12783 }
12784 },
12785
12786 _redraw: function () {
12787 this._redrawRequest = null;
12788
12789 if (this._redrawBounds) {
12790 this._redrawBounds.min._floor();
12791 this._redrawBounds.max._ceil();
12792 }
12793
12794 this._clear(); // clear layers in redraw bounds
12795 this._draw(); // draw layers
12796
12797 this._redrawBounds = null;
12798 },
12799
12800 _clear: function () {
12801 var bounds = this._redrawBounds;
12802 if (bounds) {
12803 var size = bounds.getSize();
12804 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
12805 } else {
12806 this._ctx.save();
12807 this._ctx.setTransform(1, 0, 0, 1, 0, 0);
12808 this._ctx.clearRect(0, 0, this._container.width, this._container.height);
12809 this._ctx.restore();
12810 }
12811 },
12812
12813 _draw: function () {
12814 var layer, bounds = this._redrawBounds;
12815 this._ctx.save();
12816 if (bounds) {
12817 var size = bounds.getSize();
12818 this._ctx.beginPath();
12819 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
12820 this._ctx.clip();
12821 }
12822
12823 this._drawing = true;
12824
12825 for (var order = this._drawFirst; order; order = order.next) {
12826 layer = order.layer;
12827 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
12828 layer._updatePath();
12829 }
12830 }
12831
12832 this._drawing = false;
12833
12834 this._ctx.restore(); // Restore state before clipping.
12835 },
12836
12837 _updatePoly: function (layer, closed) {
12838 if (!this._drawing) { return; }
12839
12840 var i, j, len2, p,
12841 parts = layer._parts,
12842 len = parts.length,
12843 ctx = this._ctx;
12844
12845 if (!len) { return; }
12846
12847 ctx.beginPath();
12848
12849 for (i = 0; i < len; i++) {
12850 for (j = 0, len2 = parts[i].length; j < len2; j++) {
12851 p = parts[i][j];
12852 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
12853 }
12854 if (closed) {
12855 ctx.closePath();
12856 }
12857 }
12858
12859 this._fillStroke(ctx, layer);
12860
12861 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
12862 },
12863
12864 _updateCircle: function (layer) {
12865
12866 if (!this._drawing || layer._empty()) { return; }
12867
12868 var p = layer._point,
12869 ctx = this._ctx,
12870 r = Math.max(Math.round(layer._radius), 1),
12871 s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
12872
12873 if (s !== 1) {
12874 ctx.save();
12875 ctx.scale(1, s);
12876 }
12877
12878 ctx.beginPath();
12879 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
12880
12881 if (s !== 1) {
12882 ctx.restore();
12883 }
12884
12885 this._fillStroke(ctx, layer);
12886 },
12887
12888 _fillStroke: function (ctx, layer) {
12889 var options = layer.options;
12890
12891 if (options.fill) {
12892 ctx.globalAlpha = options.fillOpacity;
12893 ctx.fillStyle = options.fillColor || options.color;
12894 ctx.fill(options.fillRule || 'evenodd');
12895 }
12896
12897 if (options.stroke && options.weight !== 0) {
12898 if (ctx.setLineDash) {
12899 ctx.setLineDash(layer.options && layer.options._dashArray || []);
12900 }
12901 ctx.globalAlpha = options.opacity;
12902 ctx.lineWidth = options.weight;
12903 ctx.strokeStyle = options.color;
12904 ctx.lineCap = options.lineCap;
12905 ctx.lineJoin = options.lineJoin;
12906 ctx.stroke();
12907 }
12908 },
12909
12910 // Canvas obviously doesn't have mouse events for individual drawn objects,
12911 // so we emulate that by calculating what's under the mouse on mousemove/click manually
12912
12913 _onClick: function (e) {
12914 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
12915
12916 for (var order = this._drawFirst; order; order = order.next) {
12917 layer = order.layer;
12918 if (layer.options.interactive && layer._containsPoint(point)) {
12919 if (!(e.type === 'click' || e.type === 'preclick') || !this._map._draggableMoved(layer)) {
12920 clickedLayer = layer;
12921 }
12922 }
12923 }
12924 this._fireEvent(clickedLayer ? [clickedLayer] : false, e);
12925 },
12926
12927 _onMouseMove: function (e) {
12928 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
12929
12930 var point = this._map.mouseEventToLayerPoint(e);
12931 this._handleMouseHover(e, point);
12932 },
12933
12934
12935 _handleMouseOut: function (e) {
12936 var layer = this._hoveredLayer;
12937 if (layer) {
12938 // if we're leaving the layer, fire mouseout
12939 removeClass(this._container, 'leaflet-interactive');
12940 this._fireEvent([layer], e, 'mouseout');
12941 this._hoveredLayer = null;
12942 this._mouseHoverThrottled = false;
12943 }
12944 },
12945
12946 _handleMouseHover: function (e, point) {
12947 if (this._mouseHoverThrottled) {
12948 return;
12949 }
12950
12951 var layer, candidateHoveredLayer;
12952
12953 for (var order = this._drawFirst; order; order = order.next) {
12954 layer = order.layer;
12955 if (layer.options.interactive && layer._containsPoint(point)) {
12956 candidateHoveredLayer = layer;
12957 }
12958 }
12959
12960 if (candidateHoveredLayer !== this._hoveredLayer) {
12961 this._handleMouseOut(e);
12962
12963 if (candidateHoveredLayer) {
12964 addClass(this._container, 'leaflet-interactive'); // change cursor
12965 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
12966 this._hoveredLayer = candidateHoveredLayer;
12967 }
12968 }
12969
12970 this._fireEvent(this._hoveredLayer ? [this._hoveredLayer] : false, e);
12971
12972 this._mouseHoverThrottled = true;
12973 setTimeout(bind(function () {
12974 this._mouseHoverThrottled = false;
12975 }, this), 32);
12976 },
12977
12978 _fireEvent: function (layers, e, type) {
12979 this._map._fireDOMEvent(e, type || e.type, layers);
12980 },
12981
12982 _bringToFront: function (layer) {
12983 var order = layer._order;
12984
12985 if (!order) { return; }
12986
12987 var next = order.next;
12988 var prev = order.prev;
12989
12990 if (next) {
12991 next.prev = prev;
12992 } else {
12993 // Already last
12994 return;
12995 }
12996 if (prev) {
12997 prev.next = next;
12998 } else if (next) {
12999 // Update first entry unless this is the
13000 // single entry
13001 this._drawFirst = next;
13002 }
13003
13004 order.prev = this._drawLast;
13005 this._drawLast.next = order;
13006
13007 order.next = null;
13008 this._drawLast = order;
13009
13010 this._requestRedraw(layer);
13011 },
13012
13013 _bringToBack: function (layer) {
13014 var order = layer._order;
13015
13016 if (!order) { return; }
13017
13018 var next = order.next;
13019 var prev = order.prev;
13020
13021 if (prev) {
13022 prev.next = next;
13023 } else {
13024 // Already first
13025 return;
13026 }
13027 if (next) {
13028 next.prev = prev;
13029 } else if (prev) {
13030 // Update last entry unless this is the
13031 // single entry
13032 this._drawLast = prev;
13033 }
13034
13035 order.prev = null;
13036
13037 order.next = this._drawFirst;
13038 this._drawFirst.prev = order;
13039 this._drawFirst = order;
13040
13041 this._requestRedraw(layer);
13042 }
13043 });
13044
13045 // @factory L.canvas(options?: Renderer options)
13046 // Creates a Canvas renderer with the given options.
13047 function canvas(options) {
13048 return Browser.canvas ? new Canvas(options) : null;
13049 }
13050
13051 /*
13052 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
13053 */
13054
13055
13056 var vmlCreate = (function () {
13057 try {
13058 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
13059 return function (name) {
13060 return document.createElement('<lvml:' + name + ' class="lvml">');
13061 };
13062 } catch (e) {
13063 // Do not return fn from catch block so `e` can be garbage collected
13064 // See https://github.com/Leaflet/Leaflet/pull/7279
13065 }
13066 return function (name) {
13067 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
13068 };
13069 })();
13070
13071
13072 /*
13073 * @class SVG
13074 *
13075 *
13076 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
13077 * with old versions of Internet Explorer.
13078 */
13079
13080 // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
13081 var vmlMixin = {
13082
13083 _initContainer: function () {
13084 this._container = create$1('div', 'leaflet-vml-container');
13085 },
13086
13087 _update: function () {
13088 if (this._map._animatingZoom) { return; }
13089 Renderer.prototype._update.call(this);
13090 this.fire('update');
13091 },
13092
13093 _initPath: function (layer) {
13094 var container = layer._container = vmlCreate('shape');
13095
13096 addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
13097
13098 container.coordsize = '1 1';
13099
13100 layer._path = vmlCreate('path');
13101 container.appendChild(layer._path);
13102
13103 this._updateStyle(layer);
13104 this._layers[stamp(layer)] = layer;
13105 },
13106
13107 _addPath: function (layer) {
13108 var container = layer._container;
13109 this._container.appendChild(container);
13110
13111 if (layer.options.interactive) {
13112 layer.addInteractiveTarget(container);
13113 }
13114 },
13115
13116 _removePath: function (layer) {
13117 var container = layer._container;
13118 remove(container);
13119 layer.removeInteractiveTarget(container);
13120 delete this._layers[stamp(layer)];
13121 },
13122
13123 _updateStyle: function (layer) {
13124 var stroke = layer._stroke,
13125 fill = layer._fill,
13126 options = layer.options,
13127 container = layer._container;
13128
13129 container.stroked = !!options.stroke;
13130 container.filled = !!options.fill;
13131
13132 if (options.stroke) {
13133 if (!stroke) {
13134 stroke = layer._stroke = vmlCreate('stroke');
13135 }
13136 container.appendChild(stroke);
13137 stroke.weight = options.weight + 'px';
13138 stroke.color = options.color;
13139 stroke.opacity = options.opacity;
13140
13141 if (options.dashArray) {
13142 stroke.dashStyle = isArray(options.dashArray) ?
13143 options.dashArray.join(' ') :
13144 options.dashArray.replace(/( *, *)/g, ' ');
13145 } else {
13146 stroke.dashStyle = '';
13147 }
13148 stroke.endcap = options.lineCap.replace('butt', 'flat');
13149 stroke.joinstyle = options.lineJoin;
13150
13151 } else if (stroke) {
13152 container.removeChild(stroke);
13153 layer._stroke = null;
13154 }
13155
13156 if (options.fill) {
13157 if (!fill) {
13158 fill = layer._fill = vmlCreate('fill');
13159 }
13160 container.appendChild(fill);
13161 fill.color = options.fillColor || options.color;
13162 fill.opacity = options.fillOpacity;
13163
13164 } else if (fill) {
13165 container.removeChild(fill);
13166 layer._fill = null;
13167 }
13168 },
13169
13170 _updateCircle: function (layer) {
13171 var p = layer._point.round(),
13172 r = Math.round(layer._radius),
13173 r2 = Math.round(layer._radiusY || r);
13174
13175 this._setPath(layer, layer._empty() ? 'M0 0' :
13176 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
13177 },
13178
13179 _setPath: function (layer, path) {
13180 layer._path.v = path;
13181 },
13182
13183 _bringToFront: function (layer) {
13184 toFront(layer._container);
13185 },
13186
13187 _bringToBack: function (layer) {
13188 toBack(layer._container);
13189 }
13190 };
13191
13192 var create = Browser.vml ? vmlCreate : svgCreate;
13193
13194 /*
13195 * @class SVG
13196 * @inherits Renderer
13197 * @aka L.SVG
13198 *
13199 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
13200 * Inherits `Renderer`.
13201 *
13202 * Due to [technical limitations](https://caniuse.com/svg), SVG is not
13203 * available in all web browsers, notably Android 2.x and 3.x.
13204 *
13205 * Although SVG is not available on IE7 and IE8, these browsers support
13206 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
13207 * (a now deprecated technology), and the SVG renderer will fall back to VML in
13208 * this case.
13209 *
13210 * @example
13211 *
13212 * Use SVG by default for all paths in the map:
13213 *
13214 * ```js
13215 * var map = L.map('map', {
13216 * renderer: L.svg()
13217 * });
13218 * ```
13219 *
13220 * Use a SVG renderer with extra padding for specific vector geometries:
13221 *
13222 * ```js
13223 * var map = L.map('map');
13224 * var myRenderer = L.svg({ padding: 0.5 });
13225 * var line = L.polyline( coordinates, { renderer: myRenderer } );
13226 * var circle = L.circle( center, { renderer: myRenderer } );
13227 * ```
13228 */
13229
13230 var SVG = Renderer.extend({
13231
13232 _initContainer: function () {
13233 this._container = create('svg');
13234
13235 // makes it possible to click through svg root; we'll reset it back in individual paths
13236 this._container.setAttribute('pointer-events', 'none');
13237
13238 this._rootGroup = create('g');
13239 this._container.appendChild(this._rootGroup);
13240 },
13241
13242 _destroyContainer: function () {
13243 remove(this._container);
13244 off(this._container);
13245 delete this._container;
13246 delete this._rootGroup;
13247 delete this._svgSize;
13248 },
13249
13250 _update: function () {
13251 if (this._map._animatingZoom && this._bounds) { return; }
13252
13253 Renderer.prototype._update.call(this);
13254
13255 var b = this._bounds,
13256 size = b.getSize(),
13257 container = this._container;
13258
13259 // set size of svg-container if changed
13260 if (!this._svgSize || !this._svgSize.equals(size)) {
13261 this._svgSize = size;
13262 container.setAttribute('width', size.x);
13263 container.setAttribute('height', size.y);
13264 }
13265
13266 // movement: update container viewBox so that we don't have to change coordinates of individual layers
13267 setPosition(container, b.min);
13268 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
13269
13270 this.fire('update');
13271 },
13272
13273 // methods below are called by vector layers implementations
13274
13275 _initPath: function (layer) {
13276 var path = layer._path = create('path');
13277
13278 // @namespace Path
13279 // @option className: String = null
13280 // Custom class name set on an element. Only for SVG renderer.
13281 if (layer.options.className) {
13282 addClass(path, layer.options.className);
13283 }
13284
13285 if (layer.options.interactive) {
13286 addClass(path, 'leaflet-interactive');
13287 }
13288
13289 this._updateStyle(layer);
13290 this._layers[stamp(layer)] = layer;
13291 },
13292
13293 _addPath: function (layer) {
13294 if (!this._rootGroup) { this._initContainer(); }
13295 this._rootGroup.appendChild(layer._path);
13296 layer.addInteractiveTarget(layer._path);
13297 },
13298
13299 _removePath: function (layer) {
13300 remove(layer._path);
13301 layer.removeInteractiveTarget(layer._path);
13302 delete this._layers[stamp(layer)];
13303 },
13304
13305 _updatePath: function (layer) {
13306 layer._project();
13307 layer._update();
13308 },
13309
13310 _updateStyle: function (layer) {
13311 var path = layer._path,
13312 options = layer.options;
13313
13314 if (!path) { return; }
13315
13316 if (options.stroke) {
13317 path.setAttribute('stroke', options.color);
13318 path.setAttribute('stroke-opacity', options.opacity);
13319 path.setAttribute('stroke-width', options.weight);
13320 path.setAttribute('stroke-linecap', options.lineCap);
13321 path.setAttribute('stroke-linejoin', options.lineJoin);
13322
13323 if (options.dashArray) {
13324 path.setAttribute('stroke-dasharray', options.dashArray);
13325 } else {
13326 path.removeAttribute('stroke-dasharray');
13327 }
13328
13329 if (options.dashOffset) {
13330 path.setAttribute('stroke-dashoffset', options.dashOffset);
13331 } else {
13332 path.removeAttribute('stroke-dashoffset');
13333 }
13334 } else {
13335 path.setAttribute('stroke', 'none');
13336 }
13337
13338 if (options.fill) {
13339 path.setAttribute('fill', options.fillColor || options.color);
13340 path.setAttribute('fill-opacity', options.fillOpacity);
13341 path.setAttribute('fill-rule', options.fillRule || 'evenodd');
13342 } else {
13343 path.setAttribute('fill', 'none');
13344 }
13345 },
13346
13347 _updatePoly: function (layer, closed) {
13348 this._setPath(layer, pointsToPath(layer._parts, closed));
13349 },
13350
13351 _updateCircle: function (layer) {
13352 var p = layer._point,
13353 r = Math.max(Math.round(layer._radius), 1),
13354 r2 = Math.max(Math.round(layer._radiusY), 1) || r,
13355 arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
13356
13357 // drawing a circle with two half-arcs
13358 var d = layer._empty() ? 'M0 0' :
13359 'M' + (p.x - r) + ',' + p.y +
13360 arc + (r * 2) + ',0 ' +
13361 arc + (-r * 2) + ',0 ';
13362
13363 this._setPath(layer, d);
13364 },
13365
13366 _setPath: function (layer, path) {
13367 layer._path.setAttribute('d', path);
13368 },
13369
13370 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
13371 _bringToFront: function (layer) {
13372 toFront(layer._path);
13373 },
13374
13375 _bringToBack: function (layer) {
13376 toBack(layer._path);
13377 }
13378 });
13379
13380 if (Browser.vml) {
13381 SVG.include(vmlMixin);
13382 }
13383
13384 // @namespace SVG
13385 // @factory L.svg(options?: Renderer options)
13386 // Creates a SVG renderer with the given options.
13387 function svg(options) {
13388 return Browser.svg || Browser.vml ? new SVG(options) : null;
13389 }
13390
13391 Map.include({
13392 // @namespace Map; @method getRenderer(layer: Path): Renderer
13393 // Returns the instance of `Renderer` that should be used to render the given
13394 // `Path`. It will ensure that the `renderer` options of the map and paths
13395 // are respected, and that the renderers do exist on the map.
13396 getRenderer: function (layer) {
13397 // @namespace Path; @option renderer: Renderer
13398 // Use this specific instance of `Renderer` for this path. Takes
13399 // precedence over the map's [default renderer](#map-renderer).
13400 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
13401
13402 if (!renderer) {
13403 renderer = this._renderer = this._createRenderer();
13404 }
13405
13406 if (!this.hasLayer(renderer)) {
13407 this.addLayer(renderer);
13408 }
13409 return renderer;
13410 },
13411
13412 _getPaneRenderer: function (name) {
13413 if (name === 'overlayPane' || name === undefined) {
13414 return false;
13415 }
13416
13417 var renderer = this._paneRenderers[name];
13418 if (renderer === undefined) {
13419 renderer = this._createRenderer({pane: name});
13420 this._paneRenderers[name] = renderer;
13421 }
13422 return renderer;
13423 },
13424
13425 _createRenderer: function (options) {
13426 // @namespace Map; @option preferCanvas: Boolean = false
13427 // Whether `Path`s should be rendered on a `Canvas` renderer.
13428 // By default, all `Path`s are rendered in a `SVG` renderer.
13429 return (this.options.preferCanvas && canvas(options)) || svg(options);
13430 }
13431 });
13432
13433 /*
13434 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
13435 */
13436
13437 /*
13438 * @class Rectangle
13439 * @aka L.Rectangle
13440 * @inherits Polygon
13441 *
13442 * A class for drawing rectangle overlays on a map. Extends `Polygon`.
13443 *
13444 * @example
13445 *
13446 * ```js
13447 * // define rectangle geographical bounds
13448 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
13449 *
13450 * // create an orange rectangle
13451 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
13452 *
13453 * // zoom the map to the rectangle bounds
13454 * map.fitBounds(bounds);
13455 * ```
13456 *
13457 */
13458
13459
13460 var Rectangle = Polygon.extend({
13461 initialize: function (latLngBounds, options) {
13462 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
13463 },
13464
13465 // @method setBounds(latLngBounds: LatLngBounds): this
13466 // Redraws the rectangle with the passed bounds.
13467 setBounds: function (latLngBounds) {
13468 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
13469 },
13470
13471 _boundsToLatLngs: function (latLngBounds) {
13472 latLngBounds = toLatLngBounds(latLngBounds);
13473 return [
13474 latLngBounds.getSouthWest(),
13475 latLngBounds.getNorthWest(),
13476 latLngBounds.getNorthEast(),
13477 latLngBounds.getSouthEast()
13478 ];
13479 }
13480 });
13481
13482
13483 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
13484 function rectangle(latLngBounds, options) {
13485 return new Rectangle(latLngBounds, options);
13486 }
13487
13488 SVG.create = create;
13489 SVG.pointsToPath = pointsToPath;
13490
13491 GeoJSON.geometryToLayer = geometryToLayer;
13492 GeoJSON.coordsToLatLng = coordsToLatLng;
13493 GeoJSON.coordsToLatLngs = coordsToLatLngs;
13494 GeoJSON.latLngToCoords = latLngToCoords;
13495 GeoJSON.latLngsToCoords = latLngsToCoords;
13496 GeoJSON.getFeature = getFeature;
13497 GeoJSON.asFeature = asFeature;
13498
13499 /*
13500 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
13501 * (zoom to a selected bounding box), enabled by default.
13502 */
13503
13504 // @namespace Map
13505 // @section Interaction Options
13506 Map.mergeOptions({
13507 // @option boxZoom: Boolean = true
13508 // Whether the map can be zoomed to a rectangular area specified by
13509 // dragging the mouse while pressing the shift key.
13510 boxZoom: true
13511 });
13512
13513 var BoxZoom = Handler.extend({
13514 initialize: function (map) {
13515 this._map = map;
13516 this._container = map._container;
13517 this._pane = map._panes.overlayPane;
13518 this._resetStateTimeout = 0;
13519 map.on('unload', this._destroy, this);
13520 },
13521
13522 addHooks: function () {
13523 on(this._container, 'mousedown', this._onMouseDown, this);
13524 },
13525
13526 removeHooks: function () {
13527 off(this._container, 'mousedown', this._onMouseDown, this);
13528 },
13529
13530 moved: function () {
13531 return this._moved;
13532 },
13533
13534 _destroy: function () {
13535 remove(this._pane);
13536 delete this._pane;
13537 },
13538
13539 _resetState: function () {
13540 this._resetStateTimeout = 0;
13541 this._moved = false;
13542 },
13543
13544 _clearDeferredResetState: function () {
13545 if (this._resetStateTimeout !== 0) {
13546 clearTimeout(this._resetStateTimeout);
13547 this._resetStateTimeout = 0;
13548 }
13549 },
13550
13551 _onMouseDown: function (e) {
13552 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
13553
13554 // Clear the deferred resetState if it hasn't executed yet, otherwise it
13555 // will interrupt the interaction and orphan a box element in the container.
13556 this._clearDeferredResetState();
13557 this._resetState();
13558
13559 disableTextSelection();
13560 disableImageDrag();
13561
13562 this._startPoint = this._map.mouseEventToContainerPoint(e);
13563
13564 on(document, {
13565 contextmenu: stop,
13566 mousemove: this._onMouseMove,
13567 mouseup: this._onMouseUp,
13568 keydown: this._onKeyDown
13569 }, this);
13570 },
13571
13572 _onMouseMove: function (e) {
13573 if (!this._moved) {
13574 this._moved = true;
13575
13576 this._box = create$1('div', 'leaflet-zoom-box', this._container);
13577 addClass(this._container, 'leaflet-crosshair');
13578
13579 this._map.fire('boxzoomstart');
13580 }
13581
13582 this._point = this._map.mouseEventToContainerPoint(e);
13583
13584 var bounds = new Bounds(this._point, this._startPoint),
13585 size = bounds.getSize();
13586
13587 setPosition(this._box, bounds.min);
13588
13589 this._box.style.width = size.x + 'px';
13590 this._box.style.height = size.y + 'px';
13591 },
13592
13593 _finish: function () {
13594 if (this._moved) {
13595 remove(this._box);
13596 removeClass(this._container, 'leaflet-crosshair');
13597 }
13598
13599 enableTextSelection();
13600 enableImageDrag();
13601
13602 off(document, {
13603 contextmenu: stop,
13604 mousemove: this._onMouseMove,
13605 mouseup: this._onMouseUp,
13606 keydown: this._onKeyDown
13607 }, this);
13608 },
13609
13610 _onMouseUp: function (e) {
13611 if ((e.which !== 1) && (e.button !== 1)) { return; }
13612
13613 this._finish();
13614
13615 if (!this._moved) { return; }
13616 // Postpone to next JS tick so internal click event handling
13617 // still see it as "moved".
13618 this._clearDeferredResetState();
13619 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
13620
13621 var bounds = new LatLngBounds(
13622 this._map.containerPointToLatLng(this._startPoint),
13623 this._map.containerPointToLatLng(this._point));
13624
13625 this._map
13626 .fitBounds(bounds)
13627 .fire('boxzoomend', {boxZoomBounds: bounds});
13628 },
13629
13630 _onKeyDown: function (e) {
13631 if (e.keyCode === 27) {
13632 this._finish();
13633 this._clearDeferredResetState();
13634 this._resetState();
13635 }
13636 }
13637 });
13638
13639 // @section Handlers
13640 // @property boxZoom: Handler
13641 // Box (shift-drag with mouse) zoom handler.
13642 Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
13643
13644 /*
13645 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
13646 */
13647
13648 // @namespace Map
13649 // @section Interaction Options
13650
13651 Map.mergeOptions({
13652 // @option doubleClickZoom: Boolean|String = true
13653 // Whether the map can be zoomed in by double clicking on it and
13654 // zoomed out by double clicking while holding shift. If passed
13655 // `'center'`, double-click zoom will zoom to the center of the
13656 // view regardless of where the mouse was.
13657 doubleClickZoom: true
13658 });
13659
13660 var DoubleClickZoom = Handler.extend({
13661 addHooks: function () {
13662 this._map.on('dblclick', this._onDoubleClick, this);
13663 },
13664
13665 removeHooks: function () {
13666 this._map.off('dblclick', this._onDoubleClick, this);
13667 },
13668
13669 _onDoubleClick: function (e) {
13670 var map = this._map,
13671 oldZoom = map.getZoom(),
13672 delta = map.options.zoomDelta,
13673 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
13674
13675 if (map.options.doubleClickZoom === 'center') {
13676 map.setZoom(zoom);
13677 } else {
13678 map.setZoomAround(e.containerPoint, zoom);
13679 }
13680 }
13681 });
13682
13683 // @section Handlers
13684 //
13685 // Map properties include interaction handlers that allow you to control
13686 // interaction behavior in runtime, enabling or disabling certain features such
13687 // as dragging or touch zoom (see `Handler` methods). For example:
13688 //
13689 // ```js
13690 // map.doubleClickZoom.disable();
13691 // ```
13692 //
13693 // @property doubleClickZoom: Handler
13694 // Double click zoom handler.
13695 Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
13696
13697 /*
13698 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
13699 */
13700
13701 // @namespace Map
13702 // @section Interaction Options
13703 Map.mergeOptions({
13704 // @option dragging: Boolean = true
13705 // Whether the map is draggable with mouse/touch or not.
13706 dragging: true,
13707
13708 // @section Panning Inertia Options
13709 // @option inertia: Boolean = *
13710 // If enabled, panning of the map will have an inertia effect where
13711 // the map builds momentum while dragging and continues moving in
13712 // the same direction for some time. Feels especially nice on touch
13713 // devices. Enabled by default.
13714 inertia: true,
13715
13716 // @option inertiaDeceleration: Number = 3000
13717 // The rate with which the inertial movement slows down, in pixels/second².
13718 inertiaDeceleration: 3400, // px/s^2
13719
13720 // @option inertiaMaxSpeed: Number = Infinity
13721 // Max speed of the inertial movement, in pixels/second.
13722 inertiaMaxSpeed: Infinity, // px/s
13723
13724 // @option easeLinearity: Number = 0.2
13725 easeLinearity: 0.2,
13726
13727 // TODO refactor, move to CRS
13728 // @option worldCopyJump: Boolean = false
13729 // With this option enabled, the map tracks when you pan to another "copy"
13730 // of the world and seamlessly jumps to the original one so that all overlays
13731 // like markers and vector layers are still visible.
13732 worldCopyJump: false,
13733
13734 // @option maxBoundsViscosity: Number = 0.0
13735 // If `maxBounds` is set, this option will control how solid the bounds
13736 // are when dragging the map around. The default value of `0.0` allows the
13737 // user to drag outside the bounds at normal speed, higher values will
13738 // slow down map dragging outside bounds, and `1.0` makes the bounds fully
13739 // solid, preventing the user from dragging outside the bounds.
13740 maxBoundsViscosity: 0.0
13741 });
13742
13743 var Drag = Handler.extend({
13744 addHooks: function () {
13745 if (!this._draggable) {
13746 var map = this._map;
13747
13748 this._draggable = new Draggable(map._mapPane, map._container);
13749
13750 this._draggable.on({
13751 dragstart: this._onDragStart,
13752 drag: this._onDrag,
13753 dragend: this._onDragEnd
13754 }, this);
13755
13756 this._draggable.on('predrag', this._onPreDragLimit, this);
13757 if (map.options.worldCopyJump) {
13758 this._draggable.on('predrag', this._onPreDragWrap, this);
13759 map.on('zoomend', this._onZoomEnd, this);
13760
13761 map.whenReady(this._onZoomEnd, this);
13762 }
13763 }
13764 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
13765 this._draggable.enable();
13766 this._positions = [];
13767 this._times = [];
13768 },
13769
13770 removeHooks: function () {
13771 removeClass(this._map._container, 'leaflet-grab');
13772 removeClass(this._map._container, 'leaflet-touch-drag');
13773 this._draggable.disable();
13774 },
13775
13776 moved: function () {
13777 return this._draggable && this._draggable._moved;
13778 },
13779
13780 moving: function () {
13781 return this._draggable && this._draggable._moving;
13782 },
13783
13784 _onDragStart: function () {
13785 var map = this._map;
13786
13787 map._stop();
13788 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
13789 var bounds = toLatLngBounds(this._map.options.maxBounds);
13790
13791 this._offsetLimit = toBounds(
13792 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
13793 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
13794 .add(this._map.getSize()));
13795
13796 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
13797 } else {
13798 this._offsetLimit = null;
13799 }
13800
13801 map
13802 .fire('movestart')
13803 .fire('dragstart');
13804
13805 if (map.options.inertia) {
13806 this._positions = [];
13807 this._times = [];
13808 }
13809 },
13810
13811 _onDrag: function (e) {
13812 if (this._map.options.inertia) {
13813 var time = this._lastTime = +new Date(),
13814 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
13815
13816 this._positions.push(pos);
13817 this._times.push(time);
13818
13819 this._prunePositions(time);
13820 }
13821
13822 this._map
13823 .fire('move', e)
13824 .fire('drag', e);
13825 },
13826
13827 _prunePositions: function (time) {
13828 while (this._positions.length > 1 && time - this._times[0] > 50) {
13829 this._positions.shift();
13830 this._times.shift();
13831 }
13832 },
13833
13834 _onZoomEnd: function () {
13835 var pxCenter = this._map.getSize().divideBy(2),
13836 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
13837
13838 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
13839 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
13840 },
13841
13842 _viscousLimit: function (value, threshold) {
13843 return value - (value - threshold) * this._viscosity;
13844 },
13845
13846 _onPreDragLimit: function () {
13847 if (!this._viscosity || !this._offsetLimit) { return; }
13848
13849 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
13850
13851 var limit = this._offsetLimit;
13852 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
13853 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
13854 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
13855 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
13856
13857 this._draggable._newPos = this._draggable._startPos.add(offset);
13858 },
13859
13860 _onPreDragWrap: function () {
13861 // TODO refactor to be able to adjust map pane position after zoom
13862 var worldWidth = this._worldWidth,
13863 halfWidth = Math.round(worldWidth / 2),
13864 dx = this._initialWorldOffset,
13865 x = this._draggable._newPos.x,
13866 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
13867 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
13868 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
13869
13870 this._draggable._absPos = this._draggable._newPos.clone();
13871 this._draggable._newPos.x = newX;
13872 },
13873
13874 _onDragEnd: function (e) {
13875 var map = this._map,
13876 options = map.options,
13877
13878 noInertia = !options.inertia || e.noInertia || this._times.length < 2;
13879
13880 map.fire('dragend', e);
13881
13882 if (noInertia) {
13883 map.fire('moveend');
13884
13885 } else {
13886 this._prunePositions(+new Date());
13887
13888 var direction = this._lastPos.subtract(this._positions[0]),
13889 duration = (this._lastTime - this._times[0]) / 1000,
13890 ease = options.easeLinearity,
13891
13892 speedVector = direction.multiplyBy(ease / duration),
13893 speed = speedVector.distanceTo([0, 0]),
13894
13895 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
13896 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
13897
13898 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
13899 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
13900
13901 if (!offset.x && !offset.y) {
13902 map.fire('moveend');
13903
13904 } else {
13905 offset = map._limitOffset(offset, map.options.maxBounds);
13906
13907 requestAnimFrame(function () {
13908 map.panBy(offset, {
13909 duration: decelerationDuration,
13910 easeLinearity: ease,
13911 noMoveStart: true,
13912 animate: true
13913 });
13914 });
13915 }
13916 }
13917 }
13918 });
13919
13920 // @section Handlers
13921 // @property dragging: Handler
13922 // Map dragging handler (by both mouse and touch).
13923 Map.addInitHook('addHandler', 'dragging', Drag);
13924
13925 /*
13926 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
13927 */
13928
13929 // @namespace Map
13930 // @section Keyboard Navigation Options
13931 Map.mergeOptions({
13932 // @option keyboard: Boolean = true
13933 // Makes the map focusable and allows users to navigate the map with keyboard
13934 // arrows and `+`/`-` keys.
13935 keyboard: true,
13936
13937 // @option keyboardPanDelta: Number = 80
13938 // Amount of pixels to pan when pressing an arrow key.
13939 keyboardPanDelta: 80
13940 });
13941
13942 var Keyboard = Handler.extend({
13943
13944 keyCodes: {
13945 left: [37],
13946 right: [39],
13947 down: [40],
13948 up: [38],
13949 zoomIn: [187, 107, 61, 171],
13950 zoomOut: [189, 109, 54, 173]
13951 },
13952
13953 initialize: function (map) {
13954 this._map = map;
13955
13956 this._setPanDelta(map.options.keyboardPanDelta);
13957 this._setZoomDelta(map.options.zoomDelta);
13958 },
13959
13960 addHooks: function () {
13961 var container = this._map._container;
13962
13963 // make the container focusable by tabbing
13964 if (container.tabIndex <= 0) {
13965 container.tabIndex = '0';
13966 }
13967
13968 on(container, {
13969 focus: this._onFocus,
13970 blur: this._onBlur,
13971 mousedown: this._onMouseDown
13972 }, this);
13973
13974 this._map.on({
13975 focus: this._addHooks,
13976 blur: this._removeHooks
13977 }, this);
13978 },
13979
13980 removeHooks: function () {
13981 this._removeHooks();
13982
13983 off(this._map._container, {
13984 focus: this._onFocus,
13985 blur: this._onBlur,
13986 mousedown: this._onMouseDown
13987 }, this);
13988
13989 this._map.off({
13990 focus: this._addHooks,
13991 blur: this._removeHooks
13992 }, this);
13993 },
13994
13995 _onMouseDown: function () {
13996 if (this._focused) { return; }
13997
13998 var body = document.body,
13999 docEl = document.documentElement,
14000 top = body.scrollTop || docEl.scrollTop,
14001 left = body.scrollLeft || docEl.scrollLeft;
14002
14003 this._map._container.focus();
14004
14005 window.scrollTo(left, top);
14006 },
14007
14008 _onFocus: function () {
14009 this._focused = true;
14010 this._map.fire('focus');
14011 },
14012
14013 _onBlur: function () {
14014 this._focused = false;
14015 this._map.fire('blur');
14016 },
14017
14018 _setPanDelta: function (panDelta) {
14019 var keys = this._panKeys = {},
14020 codes = this.keyCodes,
14021 i, len;
14022
14023 for (i = 0, len = codes.left.length; i < len; i++) {
14024 keys[codes.left[i]] = [-1 * panDelta, 0];
14025 }
14026 for (i = 0, len = codes.right.length; i < len; i++) {
14027 keys[codes.right[i]] = [panDelta, 0];
14028 }
14029 for (i = 0, len = codes.down.length; i < len; i++) {
14030 keys[codes.down[i]] = [0, panDelta];
14031 }
14032 for (i = 0, len = codes.up.length; i < len; i++) {
14033 keys[codes.up[i]] = [0, -1 * panDelta];
14034 }
14035 },
14036
14037 _setZoomDelta: function (zoomDelta) {
14038 var keys = this._zoomKeys = {},
14039 codes = this.keyCodes,
14040 i, len;
14041
14042 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
14043 keys[codes.zoomIn[i]] = zoomDelta;
14044 }
14045 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
14046 keys[codes.zoomOut[i]] = -zoomDelta;
14047 }
14048 },
14049
14050 _addHooks: function () {
14051 on(document, 'keydown', this._onKeyDown, this);
14052 },
14053
14054 _removeHooks: function () {
14055 off(document, 'keydown', this._onKeyDown, this);
14056 },
14057
14058 _onKeyDown: function (e) {
14059 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
14060
14061 var key = e.keyCode,
14062 map = this._map,
14063 offset;
14064
14065 if (key in this._panKeys) {
14066 if (!map._panAnim || !map._panAnim._inProgress) {
14067 offset = this._panKeys[key];
14068 if (e.shiftKey) {
14069 offset = toPoint(offset).multiplyBy(3);
14070 }
14071
14072 if (map.options.maxBounds) {
14073 offset = map._limitOffset(toPoint(offset), map.options.maxBounds);
14074 }
14075
14076 if (map.options.worldCopyJump) {
14077 var newLatLng = map.wrapLatLng(map.unproject(map.project(map.getCenter()).add(offset)));
14078 map.panTo(newLatLng);
14079 } else {
14080 map.panBy(offset);
14081 }
14082 }
14083 } else if (key in this._zoomKeys) {
14084 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
14085
14086 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
14087 map.closePopup();
14088
14089 } else {
14090 return;
14091 }
14092
14093 stop(e);
14094 }
14095 });
14096
14097 // @section Handlers
14098 // @section Handlers
14099 // @property keyboard: Handler
14100 // Keyboard navigation handler.
14101 Map.addInitHook('addHandler', 'keyboard', Keyboard);
14102
14103 /*
14104 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
14105 */
14106
14107 // @namespace Map
14108 // @section Interaction Options
14109 Map.mergeOptions({
14110 // @section Mouse wheel options
14111 // @option scrollWheelZoom: Boolean|String = true
14112 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
14113 // it will zoom to the center of the view regardless of where the mouse was.
14114 scrollWheelZoom: true,
14115
14116 // @option wheelDebounceTime: Number = 40
14117 // Limits the rate at which a wheel can fire (in milliseconds). By default
14118 // user can't zoom via wheel more often than once per 40 ms.
14119 wheelDebounceTime: 40,
14120
14121 // @option wheelPxPerZoomLevel: Number = 60
14122 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
14123 // mean a change of one full zoom level. Smaller values will make wheel-zooming
14124 // faster (and vice versa).
14125 wheelPxPerZoomLevel: 60
14126 });
14127
14128 var ScrollWheelZoom = Handler.extend({
14129 addHooks: function () {
14130 on(this._map._container, 'wheel', this._onWheelScroll, this);
14131
14132 this._delta = 0;
14133 },
14134
14135 removeHooks: function () {
14136 off(this._map._container, 'wheel', this._onWheelScroll, this);
14137 },
14138
14139 _onWheelScroll: function (e) {
14140 var delta = getWheelDelta(e);
14141
14142 var debounce = this._map.options.wheelDebounceTime;
14143
14144 this._delta += delta;
14145 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
14146
14147 if (!this._startTime) {
14148 this._startTime = +new Date();
14149 }
14150
14151 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
14152
14153 clearTimeout(this._timer);
14154 this._timer = setTimeout(bind(this._performZoom, this), left);
14155
14156 stop(e);
14157 },
14158
14159 _performZoom: function () {
14160 var map = this._map,
14161 zoom = map.getZoom(),
14162 snap = this._map.options.zoomSnap || 0;
14163
14164 map._stop(); // stop panning and fly animations if any
14165
14166 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
14167 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
14168 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
14169 d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
14170 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
14171
14172 this._delta = 0;
14173 this._startTime = null;
14174
14175 if (!delta) { return; }
14176
14177 if (map.options.scrollWheelZoom === 'center') {
14178 map.setZoom(zoom + delta);
14179 } else {
14180 map.setZoomAround(this._lastMousePos, zoom + delta);
14181 }
14182 }
14183 });
14184
14185 // @section Handlers
14186 // @property scrollWheelZoom: Handler
14187 // Scroll wheel zoom handler.
14188 Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
14189
14190 /*
14191 * L.Map.TapHold is used to simulate `contextmenu` event on long hold,
14192 * which otherwise is not fired by mobile Safari.
14193 */
14194
14195 var tapHoldDelay = 600;
14196
14197 // @namespace Map
14198 // @section Interaction Options
14199 Map.mergeOptions({
14200 // @section Touch interaction options
14201 // @option tapHold: Boolean
14202 // Enables simulation of `contextmenu` event, default is `true` for mobile Safari.
14203 tapHold: Browser.touchNative && Browser.safari && Browser.mobile,
14204
14205 // @option tapTolerance: Number = 15
14206 // The max number of pixels a user can shift his finger during touch
14207 // for it to be considered a valid tap.
14208 tapTolerance: 15
14209 });
14210
14211 var TapHold = Handler.extend({
14212 addHooks: function () {
14213 on(this._map._container, 'touchstart', this._onDown, this);
14214 },
14215
14216 removeHooks: function () {
14217 off(this._map._container, 'touchstart', this._onDown, this);
14218 },
14219
14220 _onDown: function (e) {
14221 clearTimeout(this._holdTimeout);
14222 if (e.touches.length !== 1) { return; }
14223
14224 var first = e.touches[0];
14225 this._startPos = this._newPos = new Point(first.clientX, first.clientY);
14226
14227 this._holdTimeout = setTimeout(bind(function () {
14228 this._cancel();
14229 if (!this._isTapValid()) { return; }
14230
14231 // prevent simulated mouse events https://w3c.github.io/touch-events/#mouse-events
14232 on(document, 'touchend', preventDefault);
14233 on(document, 'touchend touchcancel', this._cancelClickPrevent);
14234 this._simulateEvent('contextmenu', first);
14235 }, this), tapHoldDelay);
14236
14237 on(document, 'touchend touchcancel contextmenu', this._cancel, this);
14238 on(document, 'touchmove', this._onMove, this);
14239 },
14240
14241 _cancelClickPrevent: function cancelClickPrevent() {
14242 off(document, 'touchend', preventDefault);
14243 off(document, 'touchend touchcancel', cancelClickPrevent);
14244 },
14245
14246 _cancel: function () {
14247 clearTimeout(this._holdTimeout);
14248 off(document, 'touchend touchcancel contextmenu', this._cancel, this);
14249 off(document, 'touchmove', this._onMove, this);
14250 },
14251
14252 _onMove: function (e) {
14253 var first = e.touches[0];
14254 this._newPos = new Point(first.clientX, first.clientY);
14255 },
14256
14257 _isTapValid: function () {
14258 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
14259 },
14260
14261 _simulateEvent: function (type, e) {
14262 var simulatedEvent = new MouseEvent(type, {
14263 bubbles: true,
14264 cancelable: true,
14265 view: window,
14266 // detail: 1,
14267 screenX: e.screenX,
14268 screenY: e.screenY,
14269 clientX: e.clientX,
14270 clientY: e.clientY,
14271 // button: 2,
14272 // buttons: 2
14273 });
14274
14275 simulatedEvent._simulated = true;
14276
14277 e.target.dispatchEvent(simulatedEvent);
14278 }
14279 });
14280
14281 // @section Handlers
14282 // @property tapHold: Handler
14283 // Long tap handler to simulate `contextmenu` event (useful in mobile Safari).
14284 Map.addInitHook('addHandler', 'tapHold', TapHold);
14285
14286 /*
14287 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
14288 */
14289
14290 // @namespace Map
14291 // @section Interaction Options
14292 Map.mergeOptions({
14293 // @section Touch interaction options
14294 // @option touchZoom: Boolean|String = *
14295 // Whether the map can be zoomed by touch-dragging with two fingers. If
14296 // passed `'center'`, it will zoom to the center of the view regardless of
14297 // where the touch events (fingers) were. Enabled for touch-capable web
14298 // browsers.
14299 touchZoom: Browser.touch,
14300
14301 // @option bounceAtZoomLimits: Boolean = true
14302 // Set it to false if you don't want the map to zoom beyond min/max zoom
14303 // and then bounce back when pinch-zooming.
14304 bounceAtZoomLimits: true
14305 });
14306
14307 var TouchZoom = Handler.extend({
14308 addHooks: function () {
14309 addClass(this._map._container, 'leaflet-touch-zoom');
14310 on(this._map._container, 'touchstart', this._onTouchStart, this);
14311 },
14312
14313 removeHooks: function () {
14314 removeClass(this._map._container, 'leaflet-touch-zoom');
14315 off(this._map._container, 'touchstart', this._onTouchStart, this);
14316 },
14317
14318 _onTouchStart: function (e) {
14319 var map = this._map;
14320 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
14321
14322 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
14323 p2 = map.mouseEventToContainerPoint(e.touches[1]);
14324
14325 this._centerPoint = map.getSize()._divideBy(2);
14326 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
14327 if (map.options.touchZoom !== 'center') {
14328 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
14329 }
14330
14331 this._startDist = p1.distanceTo(p2);
14332 this._startZoom = map.getZoom();
14333
14334 this._moved = false;
14335 this._zooming = true;
14336
14337 map._stop();
14338
14339 on(document, 'touchmove', this._onTouchMove, this);
14340 on(document, 'touchend touchcancel', this._onTouchEnd, this);
14341
14342 preventDefault(e);
14343 },
14344
14345 _onTouchMove: function (e) {
14346 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
14347
14348 var map = this._map,
14349 p1 = map.mouseEventToContainerPoint(e.touches[0]),
14350 p2 = map.mouseEventToContainerPoint(e.touches[1]),
14351 scale = p1.distanceTo(p2) / this._startDist;
14352
14353 this._zoom = map.getScaleZoom(scale, this._startZoom);
14354
14355 if (!map.options.bounceAtZoomLimits && (
14356 (this._zoom < map.getMinZoom() && scale < 1) ||
14357 (this._zoom > map.getMaxZoom() && scale > 1))) {
14358 this._zoom = map._limitZoom(this._zoom);
14359 }
14360
14361 if (map.options.touchZoom === 'center') {
14362 this._center = this._startLatLng;
14363 if (scale === 1) { return; }
14364 } else {
14365 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
14366 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
14367 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
14368 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
14369 }
14370
14371 if (!this._moved) {
14372 map._moveStart(true, false);
14373 this._moved = true;
14374 }
14375
14376 cancelAnimFrame(this._animRequest);
14377
14378 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false}, undefined);
14379 this._animRequest = requestAnimFrame(moveFn, this, true);
14380
14381 preventDefault(e);
14382 },
14383
14384 _onTouchEnd: function () {
14385 if (!this._moved || !this._zooming) {
14386 this._zooming = false;
14387 return;
14388 }
14389
14390 this._zooming = false;
14391 cancelAnimFrame(this._animRequest);
14392
14393 off(document, 'touchmove', this._onTouchMove, this);
14394 off(document, 'touchend touchcancel', this._onTouchEnd, this);
14395
14396 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
14397 if (this._map.options.zoomAnimation) {
14398 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
14399 } else {
14400 this._map._resetView(this._center, this._map._limitZoom(this._zoom));
14401 }
14402 }
14403 });
14404
14405 // @section Handlers
14406 // @property touchZoom: Handler
14407 // Touch zoom handler.
14408 Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
14409
14410 Map.BoxZoom = BoxZoom;
14411 Map.DoubleClickZoom = DoubleClickZoom;
14412 Map.Drag = Drag;
14413 Map.Keyboard = Keyboard;
14414 Map.ScrollWheelZoom = ScrollWheelZoom;
14415 Map.TapHold = TapHold;
14416 Map.TouchZoom = TouchZoom;
14417
14418 export { Bounds, Browser, CRS, Canvas, Circle, CircleMarker, Class, Control, DivIcon, DivOverlay, DomEvent, DomUtil, Draggable, Evented, FeatureGroup, GeoJSON, GridLayer, Handler, Icon, ImageOverlay, LatLng, LatLngBounds, Layer, LayerGroup, LineUtil, Map, Marker, Mixin, Path, Point, PolyUtil, Polygon, Polyline, Popup, PosAnimation, index as Projection, Rectangle, Renderer, SVG, SVGOverlay, TileLayer, Tooltip, Transformation, Util, VideoOverlay, bind, toBounds as bounds, canvas, circle, circleMarker, control, divIcon, extend, featureGroup, geoJSON, geoJson, gridLayer, icon, imageOverlay, toLatLng as latLng, toLatLngBounds as latLngBounds, layerGroup, createMap as map, marker, toPoint as point, polygon, polyline, popup, rectangle, setOptions, stamp, svg, svgOverlay, tileLayer, tooltip, toTransformation as transformation, version, videoOverlay };
14419 //# sourceMappingURL=leaflet-src.esm.js.map
14420