UNPKG

422 kBJavaScriptView Raw
1/* @preserve
2 * Leaflet 1.9.3, a JS library for interactive maps. https://leafletjs.com
3 * (c) 2010-2022 Vladimir Agafonkin, (c) 2010-2011 CloudMade
4 */
5
6var version = "1.9.3";
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.
16function 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)
30var 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.
41function 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)
57var 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.
61function 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.
75function 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`.
107function 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`.
116function 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).
122function 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)
130function 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.
136function 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.
142function 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'`)
157function 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
165var 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.
172function 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)
188var 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)
194function 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).
205var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
206
207// inspired by https://paulirish.com/2011/requestanimationframe-for-smart-animating/
208
209function getPrefixed(name) {
210 return window['webkit' + name] || window['moz' + name] || window['ms' + name];
211}
212
213var lastTime = 0;
214
215// fallback for IE 7-8
216function 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
224var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
225var 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.
234function 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).
244function cancelAnimFrame(id) {
245 if (id) {
246 cancelFn.call(window, id);
247 }
248}
249
250var 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
283function Class() {}
284
285Class.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.
363Class.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.
375Class.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.
382Class.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
394function 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
434var 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)
728Events.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)
735Events.removeEventListener = Events.clearAllEventListeners = Events.off;
736
737// @method addOneTimeEventListener(…): this
738// Alias to [`once(…)`](#evented-once)
739Events.addOneTimeEventListener = Events.once;
740
741// @method fireEvent(…): this
742// Alias to [`fire(…)`](#evented-fire)
743Events.fireEvent = Events.fire;
744
745// @method hasEventListeners(…): Boolean
746// Alias to [`listens(…)`](#evented-listens)
747Events.hasEventListeners = Events.listens;
748
749var 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
775function 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
782var trunc = Math.trunc || function (v) {
783 return v > 0 ? Math.floor(v) : Math.ceil(v);
784};
785
786Point.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.
956function 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
997function 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
1007Bounds.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.
1183function 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
1220function 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
1230LatLngBounds.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).
1433function 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
1465function 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
1485LatLng.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
1549function 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
1591var 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
1718var 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
1748var earthRadius = 6378137;
1749
1750var 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.
1801function 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
1816Transformation.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
1853function 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
1866var 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
1876var 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).
1887function 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
1894function 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
1929var style = document.documentElement.style;
1930
1931// @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
1932var ie = 'ActiveXObject' in window;
1933
1934// @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
1935var ielt9 = ie && !document.addEventListener;
1936
1937// @property edge: Boolean; `true` for the Edge web browser.
1938var 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).
1942var webkit = userAgentContains('webkit');
1943
1944// @property android: Boolean
1945// **Deprecated.** `true` for any browser running on an Android platform.
1946var android = userAgentContains('android');
1947
1948// @property android23: Boolean; **Deprecated.** `true` for browsers running on Android 2 or Android 3.
1949var android23 = userAgentContains('android 2') || userAgentContains('android 3');
1950
1951/* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */
1952var 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)
1954var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);
1955
1956// @property opera: Boolean; `true` for the Opera browser
1957var opera = !!window.opera;
1958
1959// @property chrome: Boolean; `true` for the Chrome browser.
1960var chrome = !edge && userAgentContains('chrome');
1961
1962// @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
1963var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
1964
1965// @property safari: Boolean; `true` for the Safari browser.
1966var safari = !chrome && userAgentContains('safari');
1967
1968var phantom = userAgentContains('phantom');
1969
1970// @property opera12: Boolean
1971// `true` for the Opera browser supporting CSS transforms (version 12 or later).
1972var opera12 = 'OTransition' in style;
1973
1974// @property win: Boolean; `true` when the browser is running in a Windows platform
1975var win = navigator.platform.indexOf('Win') === 0;
1976
1977// @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
1978var ie3d = ie && ('transition' in style);
1979
1980// @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
1981var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;
1982
1983// @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
1984var gecko3d = 'MozPerspective' in style;
1985
1986// @property any3d: Boolean
1987// `true` for all browsers supporting CSS transforms.
1988var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
1989
1990// @property mobile: Boolean; `true` for all browsers running in a mobile device.
1991var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
1992
1993// @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
1994var mobileWebkit = mobile && webkit;
1995
1996// @property mobileWebkit3d: Boolean
1997// `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
1998var mobileWebkit3d = mobile && webkit3d;
1999
2000// @property msPointer: Boolean
2001// `true` for browsers implementing the Microsoft touch events model (notably IE10).
2002var 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).
2006var 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.
2013var 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.
2018var touch = !window.L_NO_TOUCH && (touchNative || pointer);
2019
2020// @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
2021var mobileOpera = mobile && opera;
2022
2023// @property mobileGecko: Boolean
2024// `true` for gecko-based browsers running in a mobile device.
2025var 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%.
2029var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
2030
2031// @property passiveEvents: Boolean
2032// `true` for browsers that support passive events.
2033var 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).
2051var 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).
2057var svg$1 = !!(document.createElementNS && svgCreate('svg').createSVGRect);
2058
2059var 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).
2067var 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
2084var mac = navigator.platform.indexOf('Mac') === 0;
2085
2086// @property mac: Boolean; `true` when the browser is running in a Linux platform
2087var linux = navigator.platform.indexOf('Linux') === 0;
2088
2089function userAgentContains(str) {
2090 return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
2091}
2092
2093
2094var 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
2136var POINTER_DOWN = Browser.msPointer ? 'MSPointerDown' : 'pointerdown';
2137var POINTER_MOVE = Browser.msPointer ? 'MSPointerMove' : 'pointermove';
2138var POINTER_UP = Browser.msPointer ? 'MSPointerUp' : 'pointerup';
2139var POINTER_CANCEL = Browser.msPointer ? 'MSPointerCancel' : 'pointercancel';
2140var pEvent = {
2141 touchstart : POINTER_DOWN,
2142 touchmove : POINTER_MOVE,
2143 touchend : POINTER_UP,
2144 touchcancel : POINTER_CANCEL
2145};
2146var handle = {
2147 touchstart : _onPointerStart,
2148 touchmove : _handlePointer,
2149 touchend : _handlePointer,
2150 touchcancel : _handlePointer
2151};
2152var _pointers = {};
2153var _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
2158function 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
2171function 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
2179function _globalPointerDown(e) {
2180 _pointers[e.pointerId] = e;
2181}
2182
2183function _globalPointerMove(e) {
2184 if (_pointers[e.pointerId]) {
2185 _pointers[e.pointerId] = e;
2186 }
2187}
2188
2189function _globalPointerUp(e) {
2190 delete _pointers[e.pointerId];
2191}
2192
2193function _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
2206function _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
2218function _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
2233function 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
2250var delay = 200;
2251function 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
2311function 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).
2330var 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.
2338var TRANSITION = testProp(
2339 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
2340
2341// @property TRANSITION_END: String
2342// Vendor-prefixed transitionend event name.
2343var 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.
2350function 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.
2357function 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.
2369function 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
2381function 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`
2390function 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.
2398function 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.
2407function 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`.
2416function 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.
2426function 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.
2440function 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.
2450function 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.
2461function 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`.
2473function 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
2481function _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`.
2508function 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.
2523function 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).
2537function 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.
2553function 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).
2568var disableTextSelection;
2569var enableTextSelection;
2570var _userSelect;
2571if ('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.
2600function disableImageDrag() {
2601 on(window, 'dragstart', preventDefault);
2602}
2603
2604// @function enableImageDrag()
2605// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
2606function enableImageDrag() {
2607 off(window, 'dragstart', preventDefault);
2608}
2609
2610var _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.
2616function 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.outline;
2624 element.style.outline = 'none';
2625 on(window, 'keydown', restoreOutline);
2626}
2627
2628// @function restoreOutline()
2629// Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
2630function restoreOutline() {
2631 if (!_outlineElement) { return; }
2632 _outlineElement.style.outline = _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.
2640function 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).
2651function 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
2661var 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}`
2709function 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
2726var 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
2744function 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
2772function 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
2781var mouseSubst = {
2782 mouseenter: 'mouseover',
2783 mouseleave: 'mouseout',
2784 wheel: !('onwheel' in window) && 'mousewheel'
2785};
2786
2787function 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
2831function 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// ```
2861function 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).
2876function 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).
2884function 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.
2895function 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.
2906function 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).
2916function 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.
2935function 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
2956var 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.
2965function 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)
2978function 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
2994var 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
3041var 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
3139var 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, 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`.
4856function 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
4869var 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
4967var 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 */
4986Map.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
5068var 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
5109 for (var i in baseLayers) {
5110 this._addLayer(baseLayers[i], i);
5111 }
5112
5113 for (i in overlays) {
5114 this._addLayer(overlays[i], i, true);
5115 }
5116 },
5117
5118 onAdd: function (map) {
5119 this._initLayout();
5120 this._update();
5121
5122 this._map = map;
5123 map.on('zoomend', this._checkDisabledLayers, this);
5124
5125 for (var i = 0; i < this._layers.length; i++) {
5126 this._layers[i].layer.on('add remove', this._onLayerChange, this);
5127 }
5128
5129 return this._container;
5130 },
5131
5132 addTo: function (map) {
5133 Control.prototype.addTo.call(this, map);
5134 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
5135 return this._expandIfNotCollapsed();
5136 },
5137
5138 onRemove: function () {
5139 this._map.off('zoomend', this._checkDisabledLayers, this);
5140
5141 for (var i = 0; i < this._layers.length; i++) {
5142 this._layers[i].layer.off('add remove', this._onLayerChange, this);
5143 }
5144 },
5145
5146 // @method addBaseLayer(layer: Layer, name: String): this
5147 // Adds a base layer (radio button entry) with the given name to the control.
5148 addBaseLayer: function (layer, name) {
5149 this._addLayer(layer, name);
5150 return (this._map) ? this._update() : this;
5151 },
5152
5153 // @method addOverlay(layer: Layer, name: String): this
5154 // Adds an overlay (checkbox entry) with the given name to the control.
5155 addOverlay: function (layer, name) {
5156 this._addLayer(layer, name, true);
5157 return (this._map) ? this._update() : this;
5158 },
5159
5160 // @method removeLayer(layer: Layer): this
5161 // Remove the given layer from the control.
5162 removeLayer: function (layer) {
5163 layer.off('add remove', this._onLayerChange, this);
5164
5165 var obj = this._getLayer(stamp(layer));
5166 if (obj) {
5167 this._layers.splice(this._layers.indexOf(obj), 1);
5168 }
5169 return (this._map) ? this._update() : this;
5170 },
5171
5172 // @method expand(): this
5173 // Expand the control container if collapsed.
5174 expand: function () {
5175 addClass(this._container, 'leaflet-control-layers-expanded');
5176 this._section.style.height = null;
5177 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
5178 if (acceptableHeight < this._section.clientHeight) {
5179 addClass(this._section, 'leaflet-control-layers-scrollbar');
5180 this._section.style.height = acceptableHeight + 'px';
5181 } else {
5182 removeClass(this._section, 'leaflet-control-layers-scrollbar');
5183 }
5184 this._checkDisabledLayers();
5185 return this;
5186 },
5187
5188 // @method collapse(): this
5189 // Collapse the control container if expanded.
5190 collapse: function () {
5191 removeClass(this._container, 'leaflet-control-layers-expanded');
5192 return this;
5193 },
5194
5195 _initLayout: function () {
5196 var className = 'leaflet-control-layers',
5197 container = this._container = create$1('div', className),
5198 collapsed = this.options.collapsed;
5199
5200 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
5201 container.setAttribute('aria-haspopup', true);
5202
5203 disableClickPropagation(container);
5204 disableScrollPropagation(container);
5205
5206 var section = this._section = create$1('section', className + '-list');
5207
5208 if (collapsed) {
5209 this._map.on('click', this.collapse, this);
5210
5211 on(container, {
5212 mouseenter: this._expandSafely,
5213 mouseleave: this.collapse
5214 }, this);
5215 }
5216
5217 var link = this._layersLink = create$1('a', className + '-toggle', container);
5218 link.href = '#';
5219 link.title = 'Layers';
5220 link.setAttribute('role', 'button');
5221
5222 on(link, {
5223 keydown: function (e) {
5224 if (e.keyCode === 13) {
5225 this._expandSafely();
5226 }
5227 },
5228 // Certain screen readers intercept the key event and instead send a click event
5229 click: function (e) {
5230 preventDefault(e);
5231 this._expandSafely();
5232 }
5233 }, this);
5234
5235 if (!collapsed) {
5236 this.expand();
5237 }
5238
5239 this._baseLayersList = create$1('div', className + '-base', section);
5240 this._separator = create$1('div', className + '-separator', section);
5241 this._overlaysList = create$1('div', className + '-overlays', section);
5242
5243 container.appendChild(section);
5244 },
5245
5246 _getLayer: function (id) {
5247 for (var i = 0; i < this._layers.length; i++) {
5248
5249 if (this._layers[i] && stamp(this._layers[i].layer) === id) {
5250 return this._layers[i];
5251 }
5252 }
5253 },
5254
5255 _addLayer: function (layer, name, overlay) {
5256 if (this._map) {
5257 layer.on('add remove', this._onLayerChange, this);
5258 }
5259
5260 this._layers.push({
5261 layer: layer,
5262 name: name,
5263 overlay: overlay
5264 });
5265
5266 if (this.options.sortLayers) {
5267 this._layers.sort(bind(function (a, b) {
5268 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
5269 }, this));
5270 }
5271
5272 if (this.options.autoZIndex && layer.setZIndex) {
5273 this._lastZIndex++;
5274 layer.setZIndex(this._lastZIndex);
5275 }
5276
5277 this._expandIfNotCollapsed();
5278 },
5279
5280 _update: function () {
5281 if (!this._container) { return this; }
5282
5283 empty(this._baseLayersList);
5284 empty(this._overlaysList);
5285
5286 this._layerControlInputs = [];
5287 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
5288
5289 for (i = 0; i < this._layers.length; i++) {
5290 obj = this._layers[i];
5291 this._addItem(obj);
5292 overlaysPresent = overlaysPresent || obj.overlay;
5293 baseLayersPresent = baseLayersPresent || !obj.overlay;
5294 baseLayersCount += !obj.overlay ? 1 : 0;
5295 }
5296
5297 // Hide base layers section if there's only one layer.
5298 if (this.options.hideSingleBase) {
5299 baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
5300 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
5301 }
5302
5303 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
5304
5305 return this;
5306 },
5307
5308 _onLayerChange: function (e) {
5309 if (!this._handlingClick) {
5310 this._update();
5311 }
5312
5313 var obj = this._getLayer(stamp(e.target));
5314
5315 // @namespace Map
5316 // @section Layer events
5317 // @event baselayerchange: LayersControlEvent
5318 // Fired when the base layer is changed through the [layers control](#control-layers).
5319 // @event overlayadd: LayersControlEvent
5320 // Fired when an overlay is selected through the [layers control](#control-layers).
5321 // @event overlayremove: LayersControlEvent
5322 // Fired when an overlay is deselected through the [layers control](#control-layers).
5323 // @namespace Control.Layers
5324 var type = obj.overlay ?
5325 (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
5326 (e.type === 'add' ? 'baselayerchange' : null);
5327
5328 if (type) {
5329 this._map.fire(type, obj);
5330 }
5331 },
5332
5333 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see https://stackoverflow.com/a/119079)
5334 _createRadioElement: function (name, checked) {
5335
5336 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
5337 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
5338
5339 var radioFragment = document.createElement('div');
5340 radioFragment.innerHTML = radioHtml;
5341
5342 return radioFragment.firstChild;
5343 },
5344
5345 _addItem: function (obj) {
5346 var label = document.createElement('label'),
5347 checked = this._map.hasLayer(obj.layer),
5348 input;
5349
5350 if (obj.overlay) {
5351 input = document.createElement('input');
5352 input.type = 'checkbox';
5353 input.className = 'leaflet-control-layers-selector';
5354 input.defaultChecked = checked;
5355 } else {
5356 input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked);
5357 }
5358
5359 this._layerControlInputs.push(input);
5360 input.layerId = stamp(obj.layer);
5361
5362 on(input, 'click', this._onInputClick, this);
5363
5364 var name = document.createElement('span');
5365 name.innerHTML = ' ' + obj.name;
5366
5367 // Helps from preventing layer control flicker when checkboxes are disabled
5368 // https://github.com/Leaflet/Leaflet/issues/2771
5369 var holder = document.createElement('span');
5370
5371 label.appendChild(holder);
5372 holder.appendChild(input);
5373 holder.appendChild(name);
5374
5375 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
5376 container.appendChild(label);
5377
5378 this._checkDisabledLayers();
5379 return label;
5380 },
5381
5382 _onInputClick: function () {
5383 var inputs = this._layerControlInputs,
5384 input, layer;
5385 var addedLayers = [],
5386 removedLayers = [];
5387
5388 this._handlingClick = true;
5389
5390 for (var i = inputs.length - 1; i >= 0; i--) {
5391 input = inputs[i];
5392 layer = this._getLayer(input.layerId).layer;
5393
5394 if (input.checked) {
5395 addedLayers.push(layer);
5396 } else if (!input.checked) {
5397 removedLayers.push(layer);
5398 }
5399 }
5400
5401 // Bugfix issue 2318: Should remove all old layers before readding new ones
5402 for (i = 0; i < removedLayers.length; i++) {
5403 if (this._map.hasLayer(removedLayers[i])) {
5404 this._map.removeLayer(removedLayers[i]);
5405 }
5406 }
5407 for (i = 0; i < addedLayers.length; i++) {
5408 if (!this._map.hasLayer(addedLayers[i])) {
5409 this._map.addLayer(addedLayers[i]);
5410 }
5411 }
5412
5413 this._handlingClick = false;
5414
5415 this._refocusOnMap();
5416 },
5417
5418 _checkDisabledLayers: function () {
5419 var inputs = this._layerControlInputs,
5420 input,
5421 layer,
5422 zoom = this._map.getZoom();
5423
5424 for (var i = inputs.length - 1; i >= 0; i--) {
5425 input = inputs[i];
5426 layer = this._getLayer(input.layerId).layer;
5427 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
5428 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
5429
5430 }
5431 },
5432
5433 _expandIfNotCollapsed: function () {
5434 if (this._map && !this.options.collapsed) {
5435 this.expand();
5436 }
5437 return this;
5438 },
5439
5440 _expandSafely: function () {
5441 var section = this._section;
5442 on(section, 'click', preventDefault);
5443 this.expand();
5444 setTimeout(function () {
5445 off(section, 'click', preventDefault);
5446 });
5447 }
5448
5449});
5450
5451
5452// @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
5453// 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.
5454var layers = function (baseLayers, overlays, options) {
5455 return new Layers(baseLayers, overlays, options);
5456};
5457
5458/*
5459 * @class Control.Zoom
5460 * @aka L.Control.Zoom
5461 * @inherits Control
5462 *
5463 * 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`.
5464 */
5465
5466var Zoom = Control.extend({
5467 // @section
5468 // @aka Control.Zoom options
5469 options: {
5470 position: 'topleft',
5471
5472 // @option zoomInText: String = '<span aria-hidden="true">+</span>'
5473 // The text set on the 'zoom in' button.
5474 zoomInText: '<span aria-hidden="true">+</span>',
5475
5476 // @option zoomInTitle: String = 'Zoom in'
5477 // The title set on the 'zoom in' button.
5478 zoomInTitle: 'Zoom in',
5479
5480 // @option zoomOutText: String = '<span aria-hidden="true">&#x2212;</span>'
5481 // The text set on the 'zoom out' button.
5482 zoomOutText: '<span aria-hidden="true">&#x2212;</span>',
5483
5484 // @option zoomOutTitle: String = 'Zoom out'
5485 // The title set on the 'zoom out' button.
5486 zoomOutTitle: 'Zoom out'
5487 },
5488
5489 onAdd: function (map) {
5490 var zoomName = 'leaflet-control-zoom',
5491 container = create$1('div', zoomName + ' leaflet-bar'),
5492 options = this.options;
5493
5494 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle,
5495 zoomName + '-in', container, this._zoomIn);
5496 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
5497 zoomName + '-out', container, this._zoomOut);
5498
5499 this._updateDisabled();
5500 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
5501
5502 return container;
5503 },
5504
5505 onRemove: function (map) {
5506 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
5507 },
5508
5509 disable: function () {
5510 this._disabled = true;
5511 this._updateDisabled();
5512 return this;
5513 },
5514
5515 enable: function () {
5516 this._disabled = false;
5517 this._updateDisabled();
5518 return this;
5519 },
5520
5521 _zoomIn: function (e) {
5522 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
5523 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5524 }
5525 },
5526
5527 _zoomOut: function (e) {
5528 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
5529 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5530 }
5531 },
5532
5533 _createButton: function (html, title, className, container, fn) {
5534 var link = create$1('a', className, container);
5535 link.innerHTML = html;
5536 link.href = '#';
5537 link.title = title;
5538
5539 /*
5540 * Will force screen readers like VoiceOver to read this as "Zoom in - button"
5541 */
5542 link.setAttribute('role', 'button');
5543 link.setAttribute('aria-label', title);
5544
5545 disableClickPropagation(link);
5546 on(link, 'click', stop);
5547 on(link, 'click', fn, this);
5548 on(link, 'click', this._refocusOnMap, this);
5549
5550 return link;
5551 },
5552
5553 _updateDisabled: function () {
5554 var map = this._map,
5555 className = 'leaflet-disabled';
5556
5557 removeClass(this._zoomInButton, className);
5558 removeClass(this._zoomOutButton, className);
5559 this._zoomInButton.setAttribute('aria-disabled', 'false');
5560 this._zoomOutButton.setAttribute('aria-disabled', 'false');
5561
5562 if (this._disabled || map._zoom === map.getMinZoom()) {
5563 addClass(this._zoomOutButton, className);
5564 this._zoomOutButton.setAttribute('aria-disabled', 'true');
5565 }
5566 if (this._disabled || map._zoom === map.getMaxZoom()) {
5567 addClass(this._zoomInButton, className);
5568 this._zoomInButton.setAttribute('aria-disabled', 'true');
5569 }
5570 }
5571});
5572
5573// @namespace Map
5574// @section Control options
5575// @option zoomControl: Boolean = true
5576// Whether a [zoom control](#control-zoom) is added to the map by default.
5577Map.mergeOptions({
5578 zoomControl: true
5579});
5580
5581Map.addInitHook(function () {
5582 if (this.options.zoomControl) {
5583 // @section Controls
5584 // @property zoomControl: Control.Zoom
5585 // The default zoom control (only available if the
5586 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
5587 this.zoomControl = new Zoom();
5588 this.addControl(this.zoomControl);
5589 }
5590});
5591
5592// @namespace Control.Zoom
5593// @factory L.control.zoom(options: Control.Zoom options)
5594// Creates a zoom control
5595var zoom = function (options) {
5596 return new Zoom(options);
5597};
5598
5599/*
5600 * @class Control.Scale
5601 * @aka L.Control.Scale
5602 * @inherits Control
5603 *
5604 * 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`.
5605 *
5606 * @example
5607 *
5608 * ```js
5609 * L.control.scale().addTo(map);
5610 * ```
5611 */
5612
5613var Scale = Control.extend({
5614 // @section
5615 // @aka Control.Scale options
5616 options: {
5617 position: 'bottomleft',
5618
5619 // @option maxWidth: Number = 100
5620 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
5621 maxWidth: 100,
5622
5623 // @option metric: Boolean = True
5624 // Whether to show the metric scale line (m/km).
5625 metric: true,
5626
5627 // @option imperial: Boolean = True
5628 // Whether to show the imperial scale line (mi/ft).
5629 imperial: true
5630
5631 // @option updateWhenIdle: Boolean = false
5632 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
5633 },
5634
5635 onAdd: function (map) {
5636 var className = 'leaflet-control-scale',
5637 container = create$1('div', className),
5638 options = this.options;
5639
5640 this._addScales(options, className + '-line', container);
5641
5642 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5643 map.whenReady(this._update, this);
5644
5645 return container;
5646 },
5647
5648 onRemove: function (map) {
5649 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5650 },
5651
5652 _addScales: function (options, className, container) {
5653 if (options.metric) {
5654 this._mScale = create$1('div', className, container);
5655 }
5656 if (options.imperial) {
5657 this._iScale = create$1('div', className, container);
5658 }
5659 },
5660
5661 _update: function () {
5662 var map = this._map,
5663 y = map.getSize().y / 2;
5664
5665 var maxMeters = map.distance(
5666 map.containerPointToLatLng([0, y]),
5667 map.containerPointToLatLng([this.options.maxWidth, y]));
5668
5669 this._updateScales(maxMeters);
5670 },
5671
5672 _updateScales: function (maxMeters) {
5673 if (this.options.metric && maxMeters) {
5674 this._updateMetric(maxMeters);
5675 }
5676 if (this.options.imperial && maxMeters) {
5677 this._updateImperial(maxMeters);
5678 }
5679 },
5680
5681 _updateMetric: function (maxMeters) {
5682 var meters = this._getRoundNum(maxMeters),
5683 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
5684
5685 this._updateScale(this._mScale, label, meters / maxMeters);
5686 },
5687
5688 _updateImperial: function (maxMeters) {
5689 var maxFeet = maxMeters * 3.2808399,
5690 maxMiles, miles, feet;
5691
5692 if (maxFeet > 5280) {
5693 maxMiles = maxFeet / 5280;
5694 miles = this._getRoundNum(maxMiles);
5695 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
5696
5697 } else {
5698 feet = this._getRoundNum(maxFeet);
5699 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
5700 }
5701 },
5702
5703 _updateScale: function (scale, text, ratio) {
5704 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
5705 scale.innerHTML = text;
5706 },
5707
5708 _getRoundNum: function (num) {
5709 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5710 d = num / pow10;
5711
5712 d = d >= 10 ? 10 :
5713 d >= 5 ? 5 :
5714 d >= 3 ? 3 :
5715 d >= 2 ? 2 : 1;
5716
5717 return pow10 * d;
5718 }
5719});
5720
5721
5722// @factory L.control.scale(options?: Control.Scale options)
5723// Creates an scale control with the given options.
5724var scale = function (options) {
5725 return new Scale(options);
5726};
5727
5728var 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>';
5729
5730
5731/*
5732 * @class Control.Attribution
5733 * @aka L.Control.Attribution
5734 * @inherits Control
5735 *
5736 * 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.
5737 */
5738
5739var Attribution = Control.extend({
5740 // @section
5741 // @aka Control.Attribution options
5742 options: {
5743 position: 'bottomright',
5744
5745 // @option prefix: String|false = 'Leaflet'
5746 // The HTML text shown before the attributions. Pass `false` to disable.
5747 prefix: '<a href="https://leafletjs.com" title="A JavaScript library for interactive maps">' + (Browser.inlineSvg ? ukrainianFlag + ' ' : '') + 'Leaflet</a>'
5748 },
5749
5750 initialize: function (options) {
5751 setOptions(this, options);
5752
5753 this._attributions = {};
5754 },
5755
5756 onAdd: function (map) {
5757 map.attributionControl = this;
5758 this._container = create$1('div', 'leaflet-control-attribution');
5759 disableClickPropagation(this._container);
5760
5761 // TODO ugly, refactor
5762 for (var i in map._layers) {
5763 if (map._layers[i].getAttribution) {
5764 this.addAttribution(map._layers[i].getAttribution());
5765 }
5766 }
5767
5768 this._update();
5769
5770 map.on('layeradd', this._addAttribution, this);
5771
5772 return this._container;
5773 },
5774
5775 onRemove: function (map) {
5776 map.off('layeradd', this._addAttribution, this);
5777 },
5778
5779 _addAttribution: function (ev) {
5780 if (ev.layer.getAttribution) {
5781 this.addAttribution(ev.layer.getAttribution());
5782 ev.layer.once('remove', function () {
5783 this.removeAttribution(ev.layer.getAttribution());
5784 }, this);
5785 }
5786 },
5787
5788 // @method setPrefix(prefix: String|false): this
5789 // The HTML text shown before the attributions. Pass `false` to disable.
5790 setPrefix: function (prefix) {
5791 this.options.prefix = prefix;
5792 this._update();
5793 return this;
5794 },
5795
5796 // @method addAttribution(text: String): this
5797 // Adds an attribution text (e.g. `'&copy; OpenStreetMap contributors'`).
5798 addAttribution: function (text) {
5799 if (!text) { return this; }
5800
5801 if (!this._attributions[text]) {
5802 this._attributions[text] = 0;
5803 }
5804 this._attributions[text]++;
5805
5806 this._update();
5807
5808 return this;
5809 },
5810
5811 // @method removeAttribution(text: String): this
5812 // Removes an attribution text.
5813 removeAttribution: function (text) {
5814 if (!text) { return this; }
5815
5816 if (this._attributions[text]) {
5817 this._attributions[text]--;
5818 this._update();
5819 }
5820
5821 return this;
5822 },
5823
5824 _update: function () {
5825 if (!this._map) { return; }
5826
5827 var attribs = [];
5828
5829 for (var i in this._attributions) {
5830 if (this._attributions[i]) {
5831 attribs.push(i);
5832 }
5833 }
5834
5835 var prefixAndAttribs = [];
5836
5837 if (this.options.prefix) {
5838 prefixAndAttribs.push(this.options.prefix);
5839 }
5840 if (attribs.length) {
5841 prefixAndAttribs.push(attribs.join(', '));
5842 }
5843
5844 this._container.innerHTML = prefixAndAttribs.join(' <span aria-hidden="true">|</span> ');
5845 }
5846});
5847
5848// @namespace Map
5849// @section Control options
5850// @option attributionControl: Boolean = true
5851// Whether a [attribution control](#control-attribution) is added to the map by default.
5852Map.mergeOptions({
5853 attributionControl: true
5854});
5855
5856Map.addInitHook(function () {
5857 if (this.options.attributionControl) {
5858 new Attribution().addTo(this);
5859 }
5860});
5861
5862// @namespace Control.Attribution
5863// @factory L.control.attribution(options: Control.Attribution options)
5864// Creates an attribution control.
5865var attribution = function (options) {
5866 return new Attribution(options);
5867};
5868
5869Control.Layers = Layers;
5870Control.Zoom = Zoom;
5871Control.Scale = Scale;
5872Control.Attribution = Attribution;
5873
5874control.layers = layers;
5875control.zoom = zoom;
5876control.scale = scale;
5877control.attribution = attribution;
5878
5879/*
5880 L.Handler is a base class for handler classes that are used internally to inject
5881 interaction features like dragging to classes like Map and Marker.
5882*/
5883
5884// @class Handler
5885// @aka L.Handler
5886// Abstract class for map interaction handlers
5887
5888var Handler = Class.extend({
5889 initialize: function (map) {
5890 this._map = map;
5891 },
5892
5893 // @method enable(): this
5894 // Enables the handler
5895 enable: function () {
5896 if (this._enabled) { return this; }
5897
5898 this._enabled = true;
5899 this.addHooks();
5900 return this;
5901 },
5902
5903 // @method disable(): this
5904 // Disables the handler
5905 disable: function () {
5906 if (!this._enabled) { return this; }
5907
5908 this._enabled = false;
5909 this.removeHooks();
5910 return this;
5911 },
5912
5913 // @method enabled(): Boolean
5914 // Returns `true` if the handler is enabled
5915 enabled: function () {
5916 return !!this._enabled;
5917 }
5918
5919 // @section Extension methods
5920 // Classes inheriting from `Handler` must implement the two following methods:
5921 // @method addHooks()
5922 // Called when the handler is enabled, should add event hooks.
5923 // @method removeHooks()
5924 // Called when the handler is disabled, should remove the event hooks added previously.
5925});
5926
5927// @section There is static function which can be called without instantiating L.Handler:
5928// @function addTo(map: Map, name: String): this
5929// Adds a new Handler to the given map with the given name.
5930Handler.addTo = function (map, name) {
5931 map.addHandler(name, this);
5932 return this;
5933};
5934
5935var Mixin = {Events: Events};
5936
5937/*
5938 * @class Draggable
5939 * @aka L.Draggable
5940 * @inherits Evented
5941 *
5942 * A class for making DOM elements draggable (including touch support).
5943 * Used internally for map and marker dragging. Only works for elements
5944 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
5945 *
5946 * @example
5947 * ```js
5948 * var draggable = new L.Draggable(elementToDrag);
5949 * draggable.enable();
5950 * ```
5951 */
5952
5953var START = Browser.touch ? 'touchstart mousedown' : 'mousedown';
5954
5955var Draggable = Evented.extend({
5956
5957 options: {
5958 // @section
5959 // @aka Draggable options
5960 // @option clickTolerance: Number = 3
5961 // The max number of pixels a user can shift the mouse pointer during a click
5962 // for it to be considered a valid click (as opposed to a mouse drag).
5963 clickTolerance: 3
5964 },
5965
5966 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
5967 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
5968 initialize: function (element, dragStartTarget, preventOutline, options) {
5969 setOptions(this, options);
5970
5971 this._element = element;
5972 this._dragStartTarget = dragStartTarget || element;
5973 this._preventOutline = preventOutline;
5974 },
5975
5976 // @method enable()
5977 // Enables the dragging ability
5978 enable: function () {
5979 if (this._enabled) { return; }
5980
5981 on(this._dragStartTarget, START, this._onDown, this);
5982
5983 this._enabled = true;
5984 },
5985
5986 // @method disable()
5987 // Disables the dragging ability
5988 disable: function () {
5989 if (!this._enabled) { return; }
5990
5991 // If we're currently dragging this draggable,
5992 // disabling it counts as first ending the drag.
5993 if (Draggable._dragging === this) {
5994 this.finishDrag(true);
5995 }
5996
5997 off(this._dragStartTarget, START, this._onDown, this);
5998
5999 this._enabled = false;
6000 this._moved = false;
6001 },
6002
6003 _onDown: function (e) {
6004 // Ignore the event if disabled; this happens in IE11
6005 // under some circumstances, see #3666.
6006 if (!this._enabled) { return; }
6007
6008 this._moved = false;
6009
6010 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
6011
6012 if (e.touches && e.touches.length !== 1) {
6013 // Finish dragging to avoid conflict with touchZoom
6014 if (Draggable._dragging === this) {
6015 this.finishDrag();
6016 }
6017 return;
6018 }
6019
6020 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
6021 Draggable._dragging = this; // Prevent dragging multiple objects at once.
6022
6023 if (this._preventOutline) {
6024 preventOutline(this._element);
6025 }
6026
6027 disableImageDrag();
6028 disableTextSelection();
6029
6030 if (this._moving) { return; }
6031
6032 // @event down: Event
6033 // Fired when a drag is about to start.
6034 this.fire('down');
6035
6036 var first = e.touches ? e.touches[0] : e,
6037 sizedParent = getSizedParentNode(this._element);
6038
6039 this._startPoint = new Point(first.clientX, first.clientY);
6040 this._startPos = getPosition(this._element);
6041
6042 // Cache the scale, so that we can continuously compensate for it during drag (_onMove).
6043 this._parentScale = getScale(sizedParent);
6044
6045 var mouseevent = e.type === 'mousedown';
6046 on(document, mouseevent ? 'mousemove' : 'touchmove', this._onMove, this);
6047 on(document, mouseevent ? 'mouseup' : 'touchend touchcancel', this._onUp, this);
6048 },
6049
6050 _onMove: function (e) {
6051 // Ignore the event if disabled; this happens in IE11
6052 // under some circumstances, see #3666.
6053 if (!this._enabled) { return; }
6054
6055 if (e.touches && e.touches.length > 1) {
6056 this._moved = true;
6057 return;
6058 }
6059
6060 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
6061 offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
6062
6063 if (!offset.x && !offset.y) { return; }
6064 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
6065
6066 // We assume that the parent container's position, border and scale do not change for the duration of the drag.
6067 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
6068 // and we can use the cached value for the scale.
6069 offset.x /= this._parentScale.x;
6070 offset.y /= this._parentScale.y;
6071
6072 preventDefault(e);
6073
6074 if (!this._moved) {
6075 // @event dragstart: Event
6076 // Fired when a drag starts
6077 this.fire('dragstart');
6078
6079 this._moved = true;
6080
6081 addClass(document.body, 'leaflet-dragging');
6082
6083 this._lastTarget = e.target || e.srcElement;
6084 // IE and Edge do not give the <use> element, so fetch it
6085 // if necessary
6086 if (window.SVGElementInstance && this._lastTarget instanceof window.SVGElementInstance) {
6087 this._lastTarget = this._lastTarget.correspondingUseElement;
6088 }
6089 addClass(this._lastTarget, 'leaflet-drag-target');
6090 }
6091
6092 this._newPos = this._startPos.add(offset);
6093 this._moving = true;
6094
6095 this._lastEvent = e;
6096 this._updatePosition();
6097 },
6098
6099 _updatePosition: function () {
6100 var e = {originalEvent: this._lastEvent};
6101
6102 // @event predrag: Event
6103 // Fired continuously during dragging *before* each corresponding
6104 // update of the element's position.
6105 this.fire('predrag', e);
6106 setPosition(this._element, this._newPos);
6107
6108 // @event drag: Event
6109 // Fired continuously during dragging.
6110 this.fire('drag', e);
6111 },
6112
6113 _onUp: function () {
6114 // Ignore the event if disabled; this happens in IE11
6115 // under some circumstances, see #3666.
6116 if (!this._enabled) { return; }
6117 this.finishDrag();
6118 },
6119
6120 finishDrag: function (noInertia) {
6121 removeClass(document.body, 'leaflet-dragging');
6122
6123 if (this._lastTarget) {
6124 removeClass(this._lastTarget, 'leaflet-drag-target');
6125 this._lastTarget = null;
6126 }
6127
6128 off(document, 'mousemove touchmove', this._onMove, this);
6129 off(document, 'mouseup touchend touchcancel', this._onUp, this);
6130
6131 enableImageDrag();
6132 enableTextSelection();
6133
6134 if (this._moved && this._moving) {
6135
6136 // @event dragend: DragEndEvent
6137 // Fired when the drag ends.
6138 this.fire('dragend', {
6139 noInertia: noInertia,
6140 distance: this._newPos.distanceTo(this._startPos)
6141 });
6142 }
6143
6144 this._moving = false;
6145 Draggable._dragging = false;
6146 }
6147
6148});
6149
6150/*
6151 * @namespace LineUtil
6152 *
6153 * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
6154 */
6155
6156// Simplify polyline with vertex reduction and Douglas-Peucker simplification.
6157// Improves rendering performance dramatically by lessening the number of points to draw.
6158
6159// @function simplify(points: Point[], tolerance: Number): Point[]
6160// Dramatically reduces the number of points in a polyline while retaining
6161// its shape and returns a new array of simplified points, using the
6162// [Ramer-Douglas-Peucker algorithm](https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm).
6163// Used for a huge performance boost when processing/displaying Leaflet polylines for
6164// each zoom level and also reducing visual noise. tolerance affects the amount of
6165// simplification (lesser value means higher quality but slower and with more points).
6166// Also released as a separated micro-library [Simplify.js](https://mourner.github.io/simplify-js/).
6167function simplify(points, tolerance) {
6168 if (!tolerance || !points.length) {
6169 return points.slice();
6170 }
6171
6172 var sqTolerance = tolerance * tolerance;
6173
6174 // stage 1: vertex reduction
6175 points = _reducePoints(points, sqTolerance);
6176
6177 // stage 2: Douglas-Peucker simplification
6178 points = _simplifyDP(points, sqTolerance);
6179
6180 return points;
6181}
6182
6183// @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
6184// Returns the distance between point `p` and segment `p1` to `p2`.
6185function pointToSegmentDistance(p, p1, p2) {
6186 return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
6187}
6188
6189// @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
6190// Returns the closest point from a point `p` on a segment `p1` to `p2`.
6191function closestPointOnSegment(p, p1, p2) {
6192 return _sqClosestPointOnSegment(p, p1, p2);
6193}
6194
6195// Ramer-Douglas-Peucker simplification, see https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
6196function _simplifyDP(points, sqTolerance) {
6197
6198 var len = points.length,
6199 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
6200 markers = new ArrayConstructor(len);
6201
6202 markers[0] = markers[len - 1] = 1;
6203
6204 _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
6205
6206 var i,
6207 newPoints = [];
6208
6209 for (i = 0; i < len; i++) {
6210 if (markers[i]) {
6211 newPoints.push(points[i]);
6212 }
6213 }
6214
6215 return newPoints;
6216}
6217
6218function _simplifyDPStep(points, markers, sqTolerance, first, last) {
6219
6220 var maxSqDist = 0,
6221 index, i, sqDist;
6222
6223 for (i = first + 1; i <= last - 1; i++) {
6224 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
6225
6226 if (sqDist > maxSqDist) {
6227 index = i;
6228 maxSqDist = sqDist;
6229 }
6230 }
6231
6232 if (maxSqDist > sqTolerance) {
6233 markers[index] = 1;
6234
6235 _simplifyDPStep(points, markers, sqTolerance, first, index);
6236 _simplifyDPStep(points, markers, sqTolerance, index, last);
6237 }
6238}
6239
6240// reduce points that are too close to each other to a single point
6241function _reducePoints(points, sqTolerance) {
6242 var reducedPoints = [points[0]];
6243
6244 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
6245 if (_sqDist(points[i], points[prev]) > sqTolerance) {
6246 reducedPoints.push(points[i]);
6247 prev = i;
6248 }
6249 }
6250 if (prev < len - 1) {
6251 reducedPoints.push(points[len - 1]);
6252 }
6253 return reducedPoints;
6254}
6255
6256var _lastCode;
6257
6258// @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
6259// Clips the segment a to b by rectangular bounds with the
6260// [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
6261// (modifying the segment points directly!). Used by Leaflet to only show polyline
6262// points that are on the screen or near, increasing performance.
6263function clipSegment(a, b, bounds, useLastCode, round) {
6264 var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
6265 codeB = _getBitCode(b, bounds),
6266
6267 codeOut, p, newCode;
6268
6269 // save 2nd code to avoid calculating it on the next segment
6270 _lastCode = codeB;
6271
6272 while (true) {
6273 // if a,b is inside the clip window (trivial accept)
6274 if (!(codeA | codeB)) {
6275 return [a, b];
6276 }
6277
6278 // if a,b is outside the clip window (trivial reject)
6279 if (codeA & codeB) {
6280 return false;
6281 }
6282
6283 // other cases
6284 codeOut = codeA || codeB;
6285 p = _getEdgeIntersection(a, b, codeOut, bounds, round);
6286 newCode = _getBitCode(p, bounds);
6287
6288 if (codeOut === codeA) {
6289 a = p;
6290 codeA = newCode;
6291 } else {
6292 b = p;
6293 codeB = newCode;
6294 }
6295 }
6296}
6297
6298function _getEdgeIntersection(a, b, code, bounds, round) {
6299 var dx = b.x - a.x,
6300 dy = b.y - a.y,
6301 min = bounds.min,
6302 max = bounds.max,
6303 x, y;
6304
6305 if (code & 8) { // top
6306 x = a.x + dx * (max.y - a.y) / dy;
6307 y = max.y;
6308
6309 } else if (code & 4) { // bottom
6310 x = a.x + dx * (min.y - a.y) / dy;
6311 y = min.y;
6312
6313 } else if (code & 2) { // right
6314 x = max.x;
6315 y = a.y + dy * (max.x - a.x) / dx;
6316
6317 } else if (code & 1) { // left
6318 x = min.x;
6319 y = a.y + dy * (min.x - a.x) / dx;
6320 }
6321
6322 return new Point(x, y, round);
6323}
6324
6325function _getBitCode(p, bounds) {
6326 var code = 0;
6327
6328 if (p.x < bounds.min.x) { // left
6329 code |= 1;
6330 } else if (p.x > bounds.max.x) { // right
6331 code |= 2;
6332 }
6333
6334 if (p.y < bounds.min.y) { // bottom
6335 code |= 4;
6336 } else if (p.y > bounds.max.y) { // top
6337 code |= 8;
6338 }
6339
6340 return code;
6341}
6342
6343// square distance (to avoid unnecessary Math.sqrt calls)
6344function _sqDist(p1, p2) {
6345 var dx = p2.x - p1.x,
6346 dy = p2.y - p1.y;
6347 return dx * dx + dy * dy;
6348}
6349
6350// return closest point on segment or distance to that point
6351function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
6352 var x = p1.x,
6353 y = p1.y,
6354 dx = p2.x - x,
6355 dy = p2.y - y,
6356 dot = dx * dx + dy * dy,
6357 t;
6358
6359 if (dot > 0) {
6360 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
6361
6362 if (t > 1) {
6363 x = p2.x;
6364 y = p2.y;
6365 } else if (t > 0) {
6366 x += dx * t;
6367 y += dy * t;
6368 }
6369 }
6370
6371 dx = p.x - x;
6372 dy = p.y - y;
6373
6374 return sqDist ? dx * dx + dy * dy : new Point(x, y);
6375}
6376
6377
6378// @function isFlat(latlngs: LatLng[]): Boolean
6379// Returns true if `latlngs` is a flat array, false is nested.
6380function isFlat(latlngs) {
6381 return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
6382}
6383
6384function _flat(latlngs) {
6385 console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
6386 return isFlat(latlngs);
6387}
6388
6389/* @function polylineCenter(latlngs: LatLng[], crs: CRS): LatLng
6390 * Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the passed LatLngs (first ring) from a polyline.
6391 */
6392function polylineCenter(latlngs, crs) {
6393 var i, halfDist, segDist, dist, p1, p2, ratio, center;
6394
6395 if (!latlngs || latlngs.length === 0) {
6396 throw new Error('latlngs not passed');
6397 }
6398
6399 if (!isFlat(latlngs)) {
6400 console.warn('latlngs are not flat! Only the first ring will be used');
6401 latlngs = latlngs[0];
6402 }
6403
6404 var points = [];
6405 for (var j in latlngs) {
6406 points.push(crs.project(toLatLng(latlngs[j])));
6407 }
6408
6409 var len = points.length;
6410
6411 for (i = 0, halfDist = 0; i < len - 1; i++) {
6412 halfDist += points[i].distanceTo(points[i + 1]) / 2;
6413 }
6414
6415 // The line is so small in the current view that all points are on the same pixel.
6416 if (halfDist === 0) {
6417 center = points[0];
6418 } else {
6419 for (i = 0, dist = 0; i < len - 1; i++) {
6420 p1 = points[i];
6421 p2 = points[i + 1];
6422 segDist = p1.distanceTo(p2);
6423 dist += segDist;
6424
6425 if (dist > halfDist) {
6426 ratio = (dist - halfDist) / segDist;
6427 center = [
6428 p2.x - ratio * (p2.x - p1.x),
6429 p2.y - ratio * (p2.y - p1.y)
6430 ];
6431 break;
6432 }
6433 }
6434 }
6435 return crs.unproject(toPoint(center));
6436}
6437
6438var LineUtil = {
6439 __proto__: null,
6440 simplify: simplify,
6441 pointToSegmentDistance: pointToSegmentDistance,
6442 closestPointOnSegment: closestPointOnSegment,
6443 clipSegment: clipSegment,
6444 _getEdgeIntersection: _getEdgeIntersection,
6445 _getBitCode: _getBitCode,
6446 _sqClosestPointOnSegment: _sqClosestPointOnSegment,
6447 isFlat: isFlat,
6448 _flat: _flat,
6449 polylineCenter: polylineCenter
6450};
6451
6452/*
6453 * @namespace PolyUtil
6454 * Various utility functions for polygon geometries.
6455 */
6456
6457/* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
6458 * 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)).
6459 * Used by Leaflet to only show polygon points that are on the screen or near, increasing
6460 * performance. Note that polygon points needs different algorithm for clipping
6461 * than polyline, so there's a separate method for it.
6462 */
6463function clipPolygon(points, bounds, round) {
6464 var clippedPoints,
6465 edges = [1, 4, 2, 8],
6466 i, j, k,
6467 a, b,
6468 len, edge, p;
6469
6470 for (i = 0, len = points.length; i < len; i++) {
6471 points[i]._code = _getBitCode(points[i], bounds);
6472 }
6473
6474 // for each edge (left, bottom, right, top)
6475 for (k = 0; k < 4; k++) {
6476 edge = edges[k];
6477 clippedPoints = [];
6478
6479 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
6480 a = points[i];
6481 b = points[j];
6482
6483 // if a is inside the clip window
6484 if (!(a._code & edge)) {
6485 // if b is outside the clip window (a->b goes out of screen)
6486 if (b._code & edge) {
6487 p = _getEdgeIntersection(b, a, edge, bounds, round);
6488 p._code = _getBitCode(p, bounds);
6489 clippedPoints.push(p);
6490 }
6491 clippedPoints.push(a);
6492
6493 // else if b is inside the clip window (a->b enters the screen)
6494 } else if (!(b._code & edge)) {
6495 p = _getEdgeIntersection(b, a, edge, bounds, round);
6496 p._code = _getBitCode(p, bounds);
6497 clippedPoints.push(p);
6498 }
6499 }
6500 points = clippedPoints;
6501 }
6502
6503 return points;
6504}
6505
6506/* @function polygonCenter(latlngs: LatLng[] crs: CRS): LatLng
6507 * Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the passed LatLngs (first ring) from a polygon.
6508 */
6509function polygonCenter(latlngs, crs) {
6510 var i, j, p1, p2, f, area, x, y, center;
6511
6512 if (!latlngs || latlngs.length === 0) {
6513 throw new Error('latlngs not passed');
6514 }
6515
6516 if (!isFlat(latlngs)) {
6517 console.warn('latlngs are not flat! Only the first ring will be used');
6518 latlngs = latlngs[0];
6519 }
6520
6521 var points = [];
6522 for (var k in latlngs) {
6523 points.push(crs.project(toLatLng(latlngs[k])));
6524 }
6525
6526 var len = points.length;
6527 area = x = y = 0;
6528
6529 // polygon centroid algorithm;
6530 for (i = 0, j = len - 1; i < len; j = i++) {
6531 p1 = points[i];
6532 p2 = points[j];
6533
6534 f = p1.y * p2.x - p2.y * p1.x;
6535 x += (p1.x + p2.x) * f;
6536 y += (p1.y + p2.y) * f;
6537 area += f * 3;
6538 }
6539
6540 if (area === 0) {
6541 // Polygon is so small that all points are on same pixel.
6542 center = points[0];
6543 } else {
6544 center = [x / area, y / area];
6545 }
6546 return crs.unproject(toPoint(center));
6547}
6548
6549var PolyUtil = {
6550 __proto__: null,
6551 clipPolygon: clipPolygon,
6552 polygonCenter: polygonCenter
6553};
6554
6555/*
6556 * @namespace Projection
6557 * @section
6558 * Leaflet comes with a set of already defined Projections out of the box:
6559 *
6560 * @projection L.Projection.LonLat
6561 *
6562 * Equirectangular, or Plate Carree projection — the most simple projection,
6563 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
6564 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
6565 * `EPSG:4326` and `Simple` CRS.
6566 */
6567
6568var LonLat = {
6569 project: function (latlng) {
6570 return new Point(latlng.lng, latlng.lat);
6571 },
6572
6573 unproject: function (point) {
6574 return new LatLng(point.y, point.x);
6575 },
6576
6577 bounds: new Bounds([-180, -90], [180, 90])
6578};
6579
6580/*
6581 * @namespace Projection
6582 * @projection L.Projection.Mercator
6583 *
6584 * Elliptical Mercator projection — more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS.
6585 */
6586
6587var Mercator = {
6588 R: 6378137,
6589 R_MINOR: 6356752.314245179,
6590
6591 bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
6592
6593 project: function (latlng) {
6594 var d = Math.PI / 180,
6595 r = this.R,
6596 y = latlng.lat * d,
6597 tmp = this.R_MINOR / r,
6598 e = Math.sqrt(1 - tmp * tmp),
6599 con = e * Math.sin(y);
6600
6601 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
6602 y = -r * Math.log(Math.max(ts, 1E-10));
6603
6604 return new Point(latlng.lng * d * r, y);
6605 },
6606
6607 unproject: function (point) {
6608 var d = 180 / Math.PI,
6609 r = this.R,
6610 tmp = this.R_MINOR / r,
6611 e = Math.sqrt(1 - tmp * tmp),
6612 ts = Math.exp(-point.y / r),
6613 phi = Math.PI / 2 - 2 * Math.atan(ts);
6614
6615 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
6616 con = e * Math.sin(phi);
6617 con = Math.pow((1 - con) / (1 + con), e / 2);
6618 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
6619 phi += dphi;
6620 }
6621
6622 return new LatLng(phi * d, point.x * d / r);
6623 }
6624};
6625
6626/*
6627 * @class Projection
6628
6629 * An object with methods for projecting geographical coordinates of the world onto
6630 * a flat surface (and back). See [Map projection](https://en.wikipedia.org/wiki/Map_projection).
6631
6632 * @property bounds: Bounds
6633 * The bounds (specified in CRS units) where the projection is valid
6634
6635 * @method project(latlng: LatLng): Point
6636 * Projects geographical coordinates into a 2D point.
6637 * Only accepts actual `L.LatLng` instances, not arrays.
6638
6639 * @method unproject(point: Point): LatLng
6640 * The inverse of `project`. Projects a 2D point into a geographical location.
6641 * Only accepts actual `L.Point` instances, not arrays.
6642
6643 * Note that the projection instances do not inherit from Leaflet's `Class` object,
6644 * and can't be instantiated. Also, new classes can't inherit from them,
6645 * and methods can't be added to them with the `include` function.
6646
6647 */
6648
6649var index = {
6650 __proto__: null,
6651 LonLat: LonLat,
6652 Mercator: Mercator,
6653 SphericalMercator: SphericalMercator
6654};
6655
6656/*
6657 * @namespace CRS
6658 * @crs L.CRS.EPSG3395
6659 *
6660 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
6661 */
6662var EPSG3395 = extend({}, Earth, {
6663 code: 'EPSG:3395',
6664 projection: Mercator,
6665
6666 transformation: (function () {
6667 var scale = 0.5 / (Math.PI * Mercator.R);
6668 return toTransformation(scale, 0.5, -scale, 0.5);
6669 }())
6670});
6671
6672/*
6673 * @namespace CRS
6674 * @crs L.CRS.EPSG4326
6675 *
6676 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
6677 *
6678 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
6679 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer`
6680 * with this CRS, ensure that there are two 256x256 pixel tiles covering the
6681 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
6682 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
6683 */
6684
6685var EPSG4326 = extend({}, Earth, {
6686 code: 'EPSG:4326',
6687 projection: LonLat,
6688 transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
6689});
6690
6691/*
6692 * @namespace CRS
6693 * @crs L.CRS.Simple
6694 *
6695 * A simple CRS that maps longitude and latitude into `x` and `y` directly.
6696 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
6697 * axis should still be inverted (going from bottom to top). `distance()` returns
6698 * simple euclidean distance.
6699 */
6700
6701var Simple = extend({}, CRS, {
6702 projection: LonLat,
6703 transformation: toTransformation(1, 0, -1, 0),
6704
6705 scale: function (zoom) {
6706 return Math.pow(2, zoom);
6707 },
6708
6709 zoom: function (scale) {
6710 return Math.log(scale) / Math.LN2;
6711 },
6712
6713 distance: function (latlng1, latlng2) {
6714 var dx = latlng2.lng - latlng1.lng,
6715 dy = latlng2.lat - latlng1.lat;
6716
6717 return Math.sqrt(dx * dx + dy * dy);
6718 },
6719
6720 infinite: true
6721});
6722
6723CRS.Earth = Earth;
6724CRS.EPSG3395 = EPSG3395;
6725CRS.EPSG3857 = EPSG3857;
6726CRS.EPSG900913 = EPSG900913;
6727CRS.EPSG4326 = EPSG4326;
6728CRS.Simple = Simple;
6729
6730/*
6731 * @class Layer
6732 * @inherits Evented
6733 * @aka L.Layer
6734 * @aka ILayer
6735 *
6736 * A set of methods from the Layer base class that all Leaflet layers use.
6737 * Inherits all methods, options and events from `L.Evented`.
6738 *
6739 * @example
6740 *
6741 * ```js
6742 * var layer = L.marker(latlng).addTo(map);
6743 * layer.addTo(map);
6744 * layer.remove();
6745 * ```
6746 *
6747 * @event add: Event
6748 * Fired after the layer is added to a map
6749 *
6750 * @event remove: Event
6751 * Fired after the layer is removed from a map
6752 */
6753
6754
6755var Layer = Evented.extend({
6756
6757 // Classes extending `L.Layer` will inherit the following options:
6758 options: {
6759 // @option pane: String = 'overlayPane'
6760 // 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.
6761 pane: 'overlayPane',
6762
6763 // @option attribution: String = null
6764 // 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.
6765 attribution: null,
6766
6767 bubblingMouseEvents: true
6768 },
6769
6770 /* @section
6771 * Classes extending `L.Layer` will inherit the following methods:
6772 *
6773 * @method addTo(map: Map|LayerGroup): this
6774 * Adds the layer to the given map or layer group.
6775 */
6776 addTo: function (map) {
6777 map.addLayer(this);
6778 return this;
6779 },
6780
6781 // @method remove: this
6782 // Removes the layer from the map it is currently active on.
6783 remove: function () {
6784 return this.removeFrom(this._map || this._mapToAdd);
6785 },
6786
6787 // @method removeFrom(map: Map): this
6788 // Removes the layer from the given map
6789 //
6790 // @alternative
6791 // @method removeFrom(group: LayerGroup): this
6792 // Removes the layer from the given `LayerGroup`
6793 removeFrom: function (obj) {
6794 if (obj) {
6795 obj.removeLayer(this);
6796 }
6797 return this;
6798 },
6799
6800 // @method getPane(name? : String): HTMLElement
6801 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
6802 getPane: function (name) {
6803 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
6804 },
6805
6806 addInteractiveTarget: function (targetEl) {
6807 this._map._targets[stamp(targetEl)] = this;
6808 return this;
6809 },
6810
6811 removeInteractiveTarget: function (targetEl) {
6812 delete this._map._targets[stamp(targetEl)];
6813 return this;
6814 },
6815
6816 // @method getAttribution: String
6817 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
6818 getAttribution: function () {
6819 return this.options.attribution;
6820 },
6821
6822 _layerAdd: function (e) {
6823 var map = e.target;
6824
6825 // check in case layer gets added and then removed before the map is ready
6826 if (!map.hasLayer(this)) { return; }
6827
6828 this._map = map;
6829 this._zoomAnimated = map._zoomAnimated;
6830
6831 if (this.getEvents) {
6832 var events = this.getEvents();
6833 map.on(events, this);
6834 this.once('remove', function () {
6835 map.off(events, this);
6836 }, this);
6837 }
6838
6839 this.onAdd(map);
6840
6841 this.fire('add');
6842 map.fire('layeradd', {layer: this});
6843 }
6844});
6845
6846/* @section Extension methods
6847 * @uninheritable
6848 *
6849 * Every layer should extend from `L.Layer` and (re-)implement the following methods.
6850 *
6851 * @method onAdd(map: Map): this
6852 * 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).
6853 *
6854 * @method onRemove(map: Map): this
6855 * 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).
6856 *
6857 * @method getEvents(): Object
6858 * 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.
6859 *
6860 * @method getAttribution(): String
6861 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
6862 *
6863 * @method beforeAdd(map: Map): this
6864 * 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.
6865 */
6866
6867
6868/* @namespace Map
6869 * @section Layer events
6870 *
6871 * @event layeradd: LayerEvent
6872 * Fired when a new layer is added to the map.
6873 *
6874 * @event layerremove: LayerEvent
6875 * Fired when some layer is removed from the map
6876 *
6877 * @section Methods for Layers and Controls
6878 */
6879Map.include({
6880 // @method addLayer(layer: Layer): this
6881 // Adds the given layer to the map
6882 addLayer: function (layer) {
6883 if (!layer._layerAdd) {
6884 throw new Error('The provided object is not a Layer.');
6885 }
6886
6887 var id = stamp(layer);
6888 if (this._layers[id]) { return this; }
6889 this._layers[id] = layer;
6890
6891 layer._mapToAdd = this;
6892
6893 if (layer.beforeAdd) {
6894 layer.beforeAdd(this);
6895 }
6896
6897 this.whenReady(layer._layerAdd, layer);
6898
6899 return this;
6900 },
6901
6902 // @method removeLayer(layer: Layer): this
6903 // Removes the given layer from the map.
6904 removeLayer: function (layer) {
6905 var id = stamp(layer);
6906
6907 if (!this._layers[id]) { return this; }
6908
6909 if (this._loaded) {
6910 layer.onRemove(this);
6911 }
6912
6913 delete this._layers[id];
6914
6915 if (this._loaded) {
6916 this.fire('layerremove', {layer: layer});
6917 layer.fire('remove');
6918 }
6919
6920 layer._map = layer._mapToAdd = null;
6921
6922 return this;
6923 },
6924
6925 // @method hasLayer(layer: Layer): Boolean
6926 // Returns `true` if the given layer is currently added to the map
6927 hasLayer: function (layer) {
6928 return stamp(layer) in this._layers;
6929 },
6930
6931 /* @method eachLayer(fn: Function, context?: Object): this
6932 * Iterates over the layers of the map, optionally specifying context of the iterator function.
6933 * ```
6934 * map.eachLayer(function(layer){
6935 * layer.bindPopup('Hello');
6936 * });
6937 * ```
6938 */
6939 eachLayer: function (method, context) {
6940 for (var i in this._layers) {
6941 method.call(context, this._layers[i]);
6942 }
6943 return this;
6944 },
6945
6946 _addLayers: function (layers) {
6947 layers = layers ? (isArray(layers) ? layers : [layers]) : [];
6948
6949 for (var i = 0, len = layers.length; i < len; i++) {
6950 this.addLayer(layers[i]);
6951 }
6952 },
6953
6954 _addZoomLimit: function (layer) {
6955 if (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
6956 this._zoomBoundLayers[stamp(layer)] = layer;
6957 this._updateZoomLevels();
6958 }
6959 },
6960
6961 _removeZoomLimit: function (layer) {
6962 var id = stamp(layer);
6963
6964 if (this._zoomBoundLayers[id]) {
6965 delete this._zoomBoundLayers[id];
6966 this._updateZoomLevels();
6967 }
6968 },
6969
6970 _updateZoomLevels: function () {
6971 var minZoom = Infinity,
6972 maxZoom = -Infinity,
6973 oldZoomSpan = this._getZoomSpan();
6974
6975 for (var i in this._zoomBoundLayers) {
6976 var options = this._zoomBoundLayers[i].options;
6977
6978 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
6979 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
6980 }
6981
6982 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
6983 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
6984
6985 // @section Map state change events
6986 // @event zoomlevelschange: Event
6987 // Fired when the number of zoomlevels on the map is changed due
6988 // to adding or removing a layer.
6989 if (oldZoomSpan !== this._getZoomSpan()) {
6990 this.fire('zoomlevelschange');
6991 }
6992
6993 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
6994 this.setZoom(this._layersMaxZoom);
6995 }
6996 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
6997 this.setZoom(this._layersMinZoom);
6998 }
6999 }
7000});
7001
7002/*
7003 * @class LayerGroup
7004 * @aka L.LayerGroup
7005 * @inherits Interactive layer
7006 *
7007 * Used to group several layers and handle them as one. If you add it to the map,
7008 * any layers added or removed from the group will be added/removed on the map as
7009 * well. Extends `Layer`.
7010 *
7011 * @example
7012 *
7013 * ```js
7014 * L.layerGroup([marker1, marker2])
7015 * .addLayer(polyline)
7016 * .addTo(map);
7017 * ```
7018 */
7019
7020var LayerGroup = Layer.extend({
7021
7022 initialize: function (layers, options) {
7023 setOptions(this, options);
7024
7025 this._layers = {};
7026
7027 var i, len;
7028
7029 if (layers) {
7030 for (i = 0, len = layers.length; i < len; i++) {
7031 this.addLayer(layers[i]);
7032 }
7033 }
7034 },
7035
7036 // @method addLayer(layer: Layer): this
7037 // Adds the given layer to the group.
7038 addLayer: function (layer) {
7039 var id = this.getLayerId(layer);
7040
7041 this._layers[id] = layer;
7042
7043 if (this._map) {
7044 this._map.addLayer(layer);
7045 }
7046
7047 return this;
7048 },
7049
7050 // @method removeLayer(layer: Layer): this
7051 // Removes the given layer from the group.
7052 // @alternative
7053 // @method removeLayer(id: Number): this
7054 // Removes the layer with the given internal ID from the group.
7055 removeLayer: function (layer) {
7056 var id = layer in this._layers ? layer : this.getLayerId(layer);
7057
7058 if (this._map && this._layers[id]) {
7059 this._map.removeLayer(this._layers[id]);
7060 }
7061
7062 delete this._layers[id];
7063
7064 return this;
7065 },
7066
7067 // @method hasLayer(layer: Layer): Boolean
7068 // Returns `true` if the given layer is currently added to the group.
7069 // @alternative
7070 // @method hasLayer(id: Number): Boolean
7071 // Returns `true` if the given internal ID is currently added to the group.
7072 hasLayer: function (layer) {
7073 var layerId = typeof layer === 'number' ? layer : this.getLayerId(layer);
7074 return layerId in this._layers;
7075 },
7076
7077 // @method clearLayers(): this
7078 // Removes all the layers from the group.
7079 clearLayers: function () {
7080 return this.eachLayer(this.removeLayer, this);
7081 },
7082
7083 // @method invoke(methodName: String, …): this
7084 // Calls `methodName` on every layer contained in this group, passing any
7085 // additional parameters. Has no effect if the layers contained do not
7086 // implement `methodName`.
7087 invoke: function (methodName) {
7088 var args = Array.prototype.slice.call(arguments, 1),
7089 i, layer;
7090
7091 for (i in this._layers) {
7092 layer = this._layers[i];
7093
7094 if (layer[methodName]) {
7095 layer[methodName].apply(layer, args);
7096 }
7097 }
7098
7099 return this;
7100 },
7101
7102 onAdd: function (map) {
7103 this.eachLayer(map.addLayer, map);
7104 },
7105
7106 onRemove: function (map) {
7107 this.eachLayer(map.removeLayer, map);
7108 },
7109
7110 // @method eachLayer(fn: Function, context?: Object): this
7111 // Iterates over the layers of the group, optionally specifying context of the iterator function.
7112 // ```js
7113 // group.eachLayer(function (layer) {
7114 // layer.bindPopup('Hello');
7115 // });
7116 // ```
7117 eachLayer: function (method, context) {
7118 for (var i in this._layers) {
7119 method.call(context, this._layers[i]);
7120 }
7121 return this;
7122 },
7123
7124 // @method getLayer(id: Number): Layer
7125 // Returns the layer with the given internal ID.
7126 getLayer: function (id) {
7127 return this._layers[id];
7128 },
7129
7130 // @method getLayers(): Layer[]
7131 // Returns an array of all the layers added to the group.
7132 getLayers: function () {
7133 var layers = [];
7134 this.eachLayer(layers.push, layers);
7135 return layers;
7136 },
7137
7138 // @method setZIndex(zIndex: Number): this
7139 // Calls `setZIndex` on every layer contained in this group, passing the z-index.
7140 setZIndex: function (zIndex) {
7141 return this.invoke('setZIndex', zIndex);
7142 },
7143
7144 // @method getLayerId(layer: Layer): Number
7145 // Returns the internal ID for a layer
7146 getLayerId: function (layer) {
7147 return stamp(layer);
7148 }
7149});
7150
7151
7152// @factory L.layerGroup(layers?: Layer[], options?: Object)
7153// Create a layer group, optionally given an initial set of layers and an `options` object.
7154var layerGroup = function (layers, options) {
7155 return new LayerGroup(layers, options);
7156};
7157
7158/*
7159 * @class FeatureGroup
7160 * @aka L.FeatureGroup
7161 * @inherits LayerGroup
7162 *
7163 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
7164 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
7165 * * Events are propagated to the `FeatureGroup`, so if the group has an event
7166 * handler, it will handle events from any of the layers. This includes mouse events
7167 * and custom events.
7168 * * Has `layeradd` and `layerremove` events
7169 *
7170 * @example
7171 *
7172 * ```js
7173 * L.featureGroup([marker1, marker2, polyline])
7174 * .bindPopup('Hello world!')
7175 * .on('click', function() { alert('Clicked on a member of the group!'); })
7176 * .addTo(map);
7177 * ```
7178 */
7179
7180var FeatureGroup = LayerGroup.extend({
7181
7182 addLayer: function (layer) {
7183 if (this.hasLayer(layer)) {
7184 return this;
7185 }
7186
7187 layer.addEventParent(this);
7188
7189 LayerGroup.prototype.addLayer.call(this, layer);
7190
7191 // @event layeradd: LayerEvent
7192 // Fired when a layer is added to this `FeatureGroup`
7193 return this.fire('layeradd', {layer: layer});
7194 },
7195
7196 removeLayer: function (layer) {
7197 if (!this.hasLayer(layer)) {
7198 return this;
7199 }
7200 if (layer in this._layers) {
7201 layer = this._layers[layer];
7202 }
7203
7204 layer.removeEventParent(this);
7205
7206 LayerGroup.prototype.removeLayer.call(this, layer);
7207
7208 // @event layerremove: LayerEvent
7209 // Fired when a layer is removed from this `FeatureGroup`
7210 return this.fire('layerremove', {layer: layer});
7211 },
7212
7213 // @method setStyle(style: Path options): this
7214 // Sets the given path options to each layer of the group that has a `setStyle` method.
7215 setStyle: function (style) {
7216 return this.invoke('setStyle', style);
7217 },
7218
7219 // @method bringToFront(): this
7220 // Brings the layer group to the top of all other layers
7221 bringToFront: function () {
7222 return this.invoke('bringToFront');
7223 },
7224
7225 // @method bringToBack(): this
7226 // Brings the layer group to the back of all other layers
7227 bringToBack: function () {
7228 return this.invoke('bringToBack');
7229 },
7230
7231 // @method getBounds(): LatLngBounds
7232 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
7233 getBounds: function () {
7234 var bounds = new LatLngBounds();
7235
7236 for (var id in this._layers) {
7237 var layer = this._layers[id];
7238 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
7239 }
7240 return bounds;
7241 }
7242});
7243
7244// @factory L.featureGroup(layers?: Layer[], options?: Object)
7245// Create a feature group, optionally given an initial set of layers and an `options` object.
7246var featureGroup = function (layers, options) {
7247 return new FeatureGroup(layers, options);
7248};
7249
7250/*
7251 * @class Icon
7252 * @aka L.Icon
7253 *
7254 * Represents an icon to provide when creating a marker.
7255 *
7256 * @example
7257 *
7258 * ```js
7259 * var myIcon = L.icon({
7260 * iconUrl: 'my-icon.png',
7261 * iconRetinaUrl: 'my-icon@2x.png',
7262 * iconSize: [38, 95],
7263 * iconAnchor: [22, 94],
7264 * popupAnchor: [-3, -76],
7265 * shadowUrl: 'my-icon-shadow.png',
7266 * shadowRetinaUrl: 'my-icon-shadow@2x.png',
7267 * shadowSize: [68, 95],
7268 * shadowAnchor: [22, 94]
7269 * });
7270 *
7271 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
7272 * ```
7273 *
7274 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
7275 *
7276 */
7277
7278var Icon = Class.extend({
7279
7280 /* @section
7281 * @aka Icon options
7282 *
7283 * @option iconUrl: String = null
7284 * **(required)** The URL to the icon image (absolute or relative to your script path).
7285 *
7286 * @option iconRetinaUrl: String = null
7287 * The URL to a retina sized version of the icon image (absolute or relative to your
7288 * script path). Used for Retina screen devices.
7289 *
7290 * @option iconSize: Point = null
7291 * Size of the icon image in pixels.
7292 *
7293 * @option iconAnchor: Point = null
7294 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
7295 * will be aligned so that this point is at the marker's geographical location. Centered
7296 * by default if size is specified, also can be set in CSS with negative margins.
7297 *
7298 * @option popupAnchor: Point = [0, 0]
7299 * The coordinates of the point from which popups will "open", relative to the icon anchor.
7300 *
7301 * @option tooltipAnchor: Point = [0, 0]
7302 * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
7303 *
7304 * @option shadowUrl: String = null
7305 * The URL to the icon shadow image. If not specified, no shadow image will be created.
7306 *
7307 * @option shadowRetinaUrl: String = null
7308 *
7309 * @option shadowSize: Point = null
7310 * Size of the shadow image in pixels.
7311 *
7312 * @option shadowAnchor: Point = null
7313 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
7314 * as iconAnchor if not specified).
7315 *
7316 * @option className: String = ''
7317 * A custom class name to assign to both icon and shadow images. Empty by default.
7318 */
7319
7320 options: {
7321 popupAnchor: [0, 0],
7322 tooltipAnchor: [0, 0],
7323
7324 // @option crossOrigin: Boolean|String = false
7325 // Whether the crossOrigin attribute will be added to the tiles.
7326 // 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.
7327 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
7328 crossOrigin: false
7329 },
7330
7331 initialize: function (options) {
7332 setOptions(this, options);
7333 },
7334
7335 // @method createIcon(oldIcon?: HTMLElement): HTMLElement
7336 // Called internally when the icon has to be shown, returns a `<img>` HTML element
7337 // styled according to the options.
7338 createIcon: function (oldIcon) {
7339 return this._createIcon('icon', oldIcon);
7340 },
7341
7342 // @method createShadow(oldIcon?: HTMLElement): HTMLElement
7343 // As `createIcon`, but for the shadow beneath it.
7344 createShadow: function (oldIcon) {
7345 return this._createIcon('shadow', oldIcon);
7346 },
7347
7348 _createIcon: function (name, oldIcon) {
7349 var src = this._getIconUrl(name);
7350
7351 if (!src) {
7352 if (name === 'icon') {
7353 throw new Error('iconUrl not set in Icon options (see the docs).');
7354 }
7355 return null;
7356 }
7357
7358 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
7359 this._setIconStyles(img, name);
7360
7361 if (this.options.crossOrigin || this.options.crossOrigin === '') {
7362 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
7363 }
7364
7365 return img;
7366 },
7367
7368 _setIconStyles: function (img, name) {
7369 var options = this.options;
7370 var sizeOption = options[name + 'Size'];
7371
7372 if (typeof sizeOption === 'number') {
7373 sizeOption = [sizeOption, sizeOption];
7374 }
7375
7376 var size = toPoint(sizeOption),
7377 anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
7378 size && size.divideBy(2, true));
7379
7380 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
7381
7382 if (anchor) {
7383 img.style.marginLeft = (-anchor.x) + 'px';
7384 img.style.marginTop = (-anchor.y) + 'px';
7385 }
7386
7387 if (size) {
7388 img.style.width = size.x + 'px';
7389 img.style.height = size.y + 'px';
7390 }
7391 },
7392
7393 _createImg: function (src, el) {
7394 el = el || document.createElement('img');
7395 el.src = src;
7396 return el;
7397 },
7398
7399 _getIconUrl: function (name) {
7400 return Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
7401 }
7402});
7403
7404
7405// @factory L.icon(options: Icon options)
7406// Creates an icon instance with the given options.
7407function icon(options) {
7408 return new Icon(options);
7409}
7410
7411/*
7412 * @miniclass Icon.Default (Icon)
7413 * @aka L.Icon.Default
7414 * @section
7415 *
7416 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
7417 * no icon is specified. Points to the blue marker image distributed with Leaflet
7418 * releases.
7419 *
7420 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
7421 * (which is a set of `Icon options`).
7422 *
7423 * If you want to _completely_ replace the default icon, override the
7424 * `L.Marker.prototype.options.icon` with your own icon instead.
7425 */
7426
7427var IconDefault = Icon.extend({
7428
7429 options: {
7430 iconUrl: 'marker-icon.png',
7431 iconRetinaUrl: 'marker-icon-2x.png',
7432 shadowUrl: 'marker-shadow.png',
7433 iconSize: [25, 41],
7434 iconAnchor: [12, 41],
7435 popupAnchor: [1, -34],
7436 tooltipAnchor: [16, -28],
7437 shadowSize: [41, 41]
7438 },
7439
7440 _getIconUrl: function (name) {
7441 if (typeof IconDefault.imagePath !== 'string') { // Deprecated, backwards-compatibility only
7442 IconDefault.imagePath = this._detectIconPath();
7443 }
7444
7445 // @option imagePath: String
7446 // `Icon.Default` will try to auto-detect the location of the
7447 // blue icon images. If you are placing these images in a non-standard
7448 // way, set this option to point to the right path.
7449 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
7450 },
7451
7452 _stripUrl: function (path) { // separate function to use in tests
7453 var strip = function (str, re, idx) {
7454 var match = re.exec(str);
7455 return match && match[idx];
7456 };
7457 path = strip(path, /^url\((['"])?(.+)\1\)$/, 2);
7458 return path && strip(path, /^(.*)marker-icon\.png$/, 1);
7459 },
7460
7461 _detectIconPath: function () {
7462 var el = create$1('div', 'leaflet-default-icon-path', document.body);
7463 var path = getStyle(el, 'background-image') ||
7464 getStyle(el, 'backgroundImage'); // IE8
7465
7466 document.body.removeChild(el);
7467 path = this._stripUrl(path);
7468 if (path) { return path; }
7469 var link = document.querySelector('link[href$="leaflet.css"]');
7470 if (!link) { return ''; }
7471 return link.href.substring(0, link.href.length - 'leaflet.css'.length - 1);
7472 }
7473});
7474
7475/*
7476 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7477 */
7478
7479
7480/* @namespace Marker
7481 * @section Interaction handlers
7482 *
7483 * 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:
7484 *
7485 * ```js
7486 * marker.dragging.disable();
7487 * ```
7488 *
7489 * @property dragging: Handler
7490 * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
7491 */
7492
7493var MarkerDrag = Handler.extend({
7494 initialize: function (marker) {
7495 this._marker = marker;
7496 },
7497
7498 addHooks: function () {
7499 var icon = this._marker._icon;
7500
7501 if (!this._draggable) {
7502 this._draggable = new Draggable(icon, icon, true);
7503 }
7504
7505 this._draggable.on({
7506 dragstart: this._onDragStart,
7507 predrag: this._onPreDrag,
7508 drag: this._onDrag,
7509 dragend: this._onDragEnd
7510 }, this).enable();
7511
7512 addClass(icon, 'leaflet-marker-draggable');
7513 },
7514
7515 removeHooks: function () {
7516 this._draggable.off({
7517 dragstart: this._onDragStart,
7518 predrag: this._onPreDrag,
7519 drag: this._onDrag,
7520 dragend: this._onDragEnd
7521 }, this).disable();
7522
7523 if (this._marker._icon) {
7524 removeClass(this._marker._icon, 'leaflet-marker-draggable');
7525 }
7526 },
7527
7528 moved: function () {
7529 return this._draggable && this._draggable._moved;
7530 },
7531
7532 _adjustPan: function (e) {
7533 var marker = this._marker,
7534 map = marker._map,
7535 speed = this._marker.options.autoPanSpeed,
7536 padding = this._marker.options.autoPanPadding,
7537 iconPos = getPosition(marker._icon),
7538 bounds = map.getPixelBounds(),
7539 origin = map.getPixelOrigin();
7540
7541 var panBounds = toBounds(
7542 bounds.min._subtract(origin).add(padding),
7543 bounds.max._subtract(origin).subtract(padding)
7544 );
7545
7546 if (!panBounds.contains(iconPos)) {
7547 // Compute incremental movement
7548 var movement = toPoint(
7549 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
7550 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
7551
7552 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
7553 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
7554 ).multiplyBy(speed);
7555
7556 map.panBy(movement, {animate: false});
7557
7558 this._draggable._newPos._add(movement);
7559 this._draggable._startPos._add(movement);
7560
7561 setPosition(marker._icon, this._draggable._newPos);
7562 this._onDrag(e);
7563
7564 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7565 }
7566 },
7567
7568 _onDragStart: function () {
7569 // @section Dragging events
7570 // @event dragstart: Event
7571 // Fired when the user starts dragging the marker.
7572
7573 // @event movestart: Event
7574 // Fired when the marker starts moving (because of dragging).
7575
7576 this._oldLatLng = this._marker.getLatLng();
7577
7578 // When using ES6 imports it could not be set when `Popup` was not imported as well
7579 this._marker.closePopup && this._marker.closePopup();
7580
7581 this._marker
7582 .fire('movestart')
7583 .fire('dragstart');
7584 },
7585
7586 _onPreDrag: function (e) {
7587 if (this._marker.options.autoPan) {
7588 cancelAnimFrame(this._panRequest);
7589 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7590 }
7591 },
7592
7593 _onDrag: function (e) {
7594 var marker = this._marker,
7595 shadow = marker._shadow,
7596 iconPos = getPosition(marker._icon),
7597 latlng = marker._map.layerPointToLatLng(iconPos);
7598
7599 // update shadow position
7600 if (shadow) {
7601 setPosition(shadow, iconPos);
7602 }
7603
7604 marker._latlng = latlng;
7605 e.latlng = latlng;
7606 e.oldLatLng = this._oldLatLng;
7607
7608 // @event drag: Event
7609 // Fired repeatedly while the user drags the marker.
7610 marker
7611 .fire('move', e)
7612 .fire('drag', e);
7613 },
7614
7615 _onDragEnd: function (e) {
7616 // @event dragend: DragEndEvent
7617 // Fired when the user stops dragging the marker.
7618
7619 cancelAnimFrame(this._panRequest);
7620
7621 // @event moveend: Event
7622 // Fired when the marker stops moving (because of dragging).
7623 delete this._oldLatLng;
7624 this._marker
7625 .fire('moveend')
7626 .fire('dragend', e);
7627 }
7628});
7629
7630/*
7631 * @class Marker
7632 * @inherits Interactive layer
7633 * @aka L.Marker
7634 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
7635 *
7636 * @example
7637 *
7638 * ```js
7639 * L.marker([50.5, 30.5]).addTo(map);
7640 * ```
7641 */
7642
7643var Marker = Layer.extend({
7644
7645 // @section
7646 // @aka Marker options
7647 options: {
7648 // @option icon: Icon = *
7649 // Icon instance to use for rendering the marker.
7650 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
7651 // If not specified, a common instance of `L.Icon.Default` is used.
7652 icon: new IconDefault(),
7653
7654 // Option inherited from "Interactive layer" abstract class
7655 interactive: true,
7656
7657 // @option keyboard: Boolean = true
7658 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
7659 keyboard: true,
7660
7661 // @option title: String = ''
7662 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
7663 // [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled).
7664 title: '',
7665
7666 // @option alt: String = 'Marker'
7667 // Text for the `alt` attribute of the icon image.
7668 // [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled).
7669 alt: 'Marker',
7670
7671 // @option zIndexOffset: Number = 0
7672 // 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).
7673 zIndexOffset: 0,
7674
7675 // @option opacity: Number = 1.0
7676 // The opacity of the marker.
7677 opacity: 1,
7678
7679 // @option riseOnHover: Boolean = false
7680 // If `true`, the marker will get on top of others when you hover the mouse over it.
7681 riseOnHover: false,
7682
7683 // @option riseOffset: Number = 250
7684 // The z-index offset used for the `riseOnHover` feature.
7685 riseOffset: 250,
7686
7687 // @option pane: String = 'markerPane'
7688 // `Map pane` where the markers icon will be added.
7689 pane: 'markerPane',
7690
7691 // @option shadowPane: String = 'shadowPane'
7692 // `Map pane` where the markers shadow will be added.
7693 shadowPane: 'shadowPane',
7694
7695 // @option bubblingMouseEvents: Boolean = false
7696 // When `true`, a mouse event on this marker will trigger the same event on the map
7697 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7698 bubblingMouseEvents: false,
7699
7700 // @option autoPanOnFocus: Boolean = true
7701 // When `true`, the map will pan whenever the marker is focused (via
7702 // e.g. pressing `tab` on the keyboard) to ensure the marker is
7703 // visible within the map's bounds
7704 autoPanOnFocus: true,
7705
7706 // @section Draggable marker options
7707 // @option draggable: Boolean = false
7708 // Whether the marker is draggable with mouse/touch or not.
7709 draggable: false,
7710
7711 // @option autoPan: Boolean = false
7712 // Whether to pan the map when dragging this marker near its edge or not.
7713 autoPan: false,
7714
7715 // @option autoPanPadding: Point = Point(50, 50)
7716 // Distance (in pixels to the left/right and to the top/bottom) of the
7717 // map edge to start panning the map.
7718 autoPanPadding: [50, 50],
7719
7720 // @option autoPanSpeed: Number = 10
7721 // Number of pixels the map should pan by.
7722 autoPanSpeed: 10
7723 },
7724
7725 /* @section
7726 *
7727 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
7728 */
7729
7730 initialize: function (latlng, options) {
7731 setOptions(this, options);
7732 this._latlng = toLatLng(latlng);
7733 },
7734
7735 onAdd: function (map) {
7736 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
7737
7738 if (this._zoomAnimated) {
7739 map.on('zoomanim', this._animateZoom, this);
7740 }
7741
7742 this._initIcon();
7743 this.update();
7744 },
7745
7746 onRemove: function (map) {
7747 if (this.dragging && this.dragging.enabled()) {
7748 this.options.draggable = true;
7749 this.dragging.removeHooks();
7750 }
7751 delete this.dragging;
7752
7753 if (this._zoomAnimated) {
7754 map.off('zoomanim', this._animateZoom, this);
7755 }
7756
7757 this._removeIcon();
7758 this._removeShadow();
7759 },
7760
7761 getEvents: function () {
7762 return {
7763 zoom: this.update,
7764 viewreset: this.update
7765 };
7766 },
7767
7768 // @method getLatLng: LatLng
7769 // Returns the current geographical position of the marker.
7770 getLatLng: function () {
7771 return this._latlng;
7772 },
7773
7774 // @method setLatLng(latlng: LatLng): this
7775 // Changes the marker position to the given point.
7776 setLatLng: function (latlng) {
7777 var oldLatLng = this._latlng;
7778 this._latlng = toLatLng(latlng);
7779 this.update();
7780
7781 // @event move: Event
7782 // 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`.
7783 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7784 },
7785
7786 // @method setZIndexOffset(offset: Number): this
7787 // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
7788 setZIndexOffset: function (offset) {
7789 this.options.zIndexOffset = offset;
7790 return this.update();
7791 },
7792
7793 // @method getIcon: Icon
7794 // Returns the current icon used by the marker
7795 getIcon: function () {
7796 return this.options.icon;
7797 },
7798
7799 // @method setIcon(icon: Icon): this
7800 // Changes the marker icon.
7801 setIcon: function (icon) {
7802
7803 this.options.icon = icon;
7804
7805 if (this._map) {
7806 this._initIcon();
7807 this.update();
7808 }
7809
7810 if (this._popup) {
7811 this.bindPopup(this._popup, this._popup.options);
7812 }
7813
7814 return this;
7815 },
7816
7817 getElement: function () {
7818 return this._icon;
7819 },
7820
7821 update: function () {
7822
7823 if (this._icon && this._map) {
7824 var pos = this._map.latLngToLayerPoint(this._latlng).round();
7825 this._setPos(pos);
7826 }
7827
7828 return this;
7829 },
7830
7831 _initIcon: function () {
7832 var options = this.options,
7833 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
7834
7835 var icon = options.icon.createIcon(this._icon),
7836 addIcon = false;
7837
7838 // if we're not reusing the icon, remove the old one and init new one
7839 if (icon !== this._icon) {
7840 if (this._icon) {
7841 this._removeIcon();
7842 }
7843 addIcon = true;
7844
7845 if (options.title) {
7846 icon.title = options.title;
7847 }
7848
7849 if (icon.tagName === 'IMG') {
7850 icon.alt = options.alt || '';
7851 }
7852 }
7853
7854 addClass(icon, classToAdd);
7855
7856 if (options.keyboard) {
7857 icon.tabIndex = '0';
7858 icon.setAttribute('role', 'button');
7859 }
7860
7861 this._icon = icon;
7862
7863 if (options.riseOnHover) {
7864 this.on({
7865 mouseover: this._bringToFront,
7866 mouseout: this._resetZIndex
7867 });
7868 }
7869
7870 if (this.options.autoPanOnFocus) {
7871 on(icon, 'focus', this._panOnFocus, this);
7872 }
7873
7874 var newShadow = options.icon.createShadow(this._shadow),
7875 addShadow = false;
7876
7877 if (newShadow !== this._shadow) {
7878 this._removeShadow();
7879 addShadow = true;
7880 }
7881
7882 if (newShadow) {
7883 addClass(newShadow, classToAdd);
7884 newShadow.alt = '';
7885 }
7886 this._shadow = newShadow;
7887
7888
7889 if (options.opacity < 1) {
7890 this._updateOpacity();
7891 }
7892
7893
7894 if (addIcon) {
7895 this.getPane().appendChild(this._icon);
7896 }
7897 this._initInteraction();
7898 if (newShadow && addShadow) {
7899 this.getPane(options.shadowPane).appendChild(this._shadow);
7900 }
7901 },
7902
7903 _removeIcon: function () {
7904 if (this.options.riseOnHover) {
7905 this.off({
7906 mouseover: this._bringToFront,
7907 mouseout: this._resetZIndex
7908 });
7909 }
7910
7911 if (this.options.autoPanOnFocus) {
7912 off(this._icon, 'focus', this._panOnFocus, this);
7913 }
7914
7915 remove(this._icon);
7916 this.removeInteractiveTarget(this._icon);
7917
7918 this._icon = null;
7919 },
7920
7921 _removeShadow: function () {
7922 if (this._shadow) {
7923 remove(this._shadow);
7924 }
7925 this._shadow = null;
7926 },
7927
7928 _setPos: function (pos) {
7929
7930 if (this._icon) {
7931 setPosition(this._icon, pos);
7932 }
7933
7934 if (this._shadow) {
7935 setPosition(this._shadow, pos);
7936 }
7937
7938 this._zIndex = pos.y + this.options.zIndexOffset;
7939
7940 this._resetZIndex();
7941 },
7942
7943 _updateZIndex: function (offset) {
7944 if (this._icon) {
7945 this._icon.style.zIndex = this._zIndex + offset;
7946 }
7947 },
7948
7949 _animateZoom: function (opt) {
7950 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
7951
7952 this._setPos(pos);
7953 },
7954
7955 _initInteraction: function () {
7956
7957 if (!this.options.interactive) { return; }
7958
7959 addClass(this._icon, 'leaflet-interactive');
7960
7961 this.addInteractiveTarget(this._icon);
7962
7963 if (MarkerDrag) {
7964 var draggable = this.options.draggable;
7965 if (this.dragging) {
7966 draggable = this.dragging.enabled();
7967 this.dragging.disable();
7968 }
7969
7970 this.dragging = new MarkerDrag(this);
7971
7972 if (draggable) {
7973 this.dragging.enable();
7974 }
7975 }
7976 },
7977
7978 // @method setOpacity(opacity: Number): this
7979 // Changes the opacity of the marker.
7980 setOpacity: function (opacity) {
7981 this.options.opacity = opacity;
7982 if (this._map) {
7983 this._updateOpacity();
7984 }
7985
7986 return this;
7987 },
7988
7989 _updateOpacity: function () {
7990 var opacity = this.options.opacity;
7991
7992 if (this._icon) {
7993 setOpacity(this._icon, opacity);
7994 }
7995
7996 if (this._shadow) {
7997 setOpacity(this._shadow, opacity);
7998 }
7999 },
8000
8001 _bringToFront: function () {
8002 this._updateZIndex(this.options.riseOffset);
8003 },
8004
8005 _resetZIndex: function () {
8006 this._updateZIndex(0);
8007 },
8008
8009 _panOnFocus: function () {
8010 var map = this._map;
8011 if (!map) { return; }
8012
8013 var iconOpts = this.options.icon.options;
8014 var size = iconOpts.iconSize ? toPoint(iconOpts.iconSize) : toPoint(0, 0);
8015 var anchor = iconOpts.iconAnchor ? toPoint(iconOpts.iconAnchor) : toPoint(0, 0);
8016
8017 map.panInside(this._latlng, {
8018 paddingTopLeft: anchor,
8019 paddingBottomRight: size.subtract(anchor)
8020 });
8021 },
8022
8023 _getPopupAnchor: function () {
8024 return this.options.icon.options.popupAnchor;
8025 },
8026
8027 _getTooltipAnchor: function () {
8028 return this.options.icon.options.tooltipAnchor;
8029 }
8030});
8031
8032
8033// factory L.marker(latlng: LatLng, options? : Marker options)
8034
8035// @factory L.marker(latlng: LatLng, options? : Marker options)
8036// Instantiates a Marker object given a geographical point and optionally an options object.
8037function marker(latlng, options) {
8038 return new Marker(latlng, options);
8039}
8040
8041/*
8042 * @class Path
8043 * @aka L.Path
8044 * @inherits Interactive layer
8045 *
8046 * An abstract class that contains options and constants shared between vector
8047 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
8048 */
8049
8050var Path = Layer.extend({
8051
8052 // @section
8053 // @aka Path options
8054 options: {
8055 // @option stroke: Boolean = true
8056 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
8057 stroke: true,
8058
8059 // @option color: String = '#3388ff'
8060 // Stroke color
8061 color: '#3388ff',
8062
8063 // @option weight: Number = 3
8064 // Stroke width in pixels
8065 weight: 3,
8066
8067 // @option opacity: Number = 1.0
8068 // Stroke opacity
8069 opacity: 1,
8070
8071 // @option lineCap: String= 'round'
8072 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
8073 lineCap: 'round',
8074
8075 // @option lineJoin: String = 'round'
8076 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
8077 lineJoin: 'round',
8078
8079 // @option dashArray: String = null
8080 // 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).
8081 dashArray: null,
8082
8083 // @option dashOffset: String = null
8084 // 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).
8085 dashOffset: null,
8086
8087 // @option fill: Boolean = depends
8088 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
8089 fill: false,
8090
8091 // @option fillColor: String = *
8092 // Fill color. Defaults to the value of the [`color`](#path-color) option
8093 fillColor: null,
8094
8095 // @option fillOpacity: Number = 0.2
8096 // Fill opacity.
8097 fillOpacity: 0.2,
8098
8099 // @option fillRule: String = 'evenodd'
8100 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
8101 fillRule: 'evenodd',
8102
8103 // className: '',
8104
8105 // Option inherited from "Interactive layer" abstract class
8106 interactive: true,
8107
8108 // @option bubblingMouseEvents: Boolean = true
8109 // When `true`, a mouse event on this path will trigger the same event on the map
8110 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
8111 bubblingMouseEvents: true
8112 },
8113
8114 beforeAdd: function (map) {
8115 // Renderer is set here because we need to call renderer.getEvents
8116 // before this.getEvents.
8117 this._renderer = map.getRenderer(this);
8118 },
8119
8120 onAdd: function () {
8121 this._renderer._initPath(this);
8122 this._reset();
8123 this._renderer._addPath(this);
8124 },
8125
8126 onRemove: function () {
8127 this._renderer._removePath(this);
8128 },
8129
8130 // @method redraw(): this
8131 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
8132 redraw: function () {
8133 if (this._map) {
8134 this._renderer._updatePath(this);
8135 }
8136 return this;
8137 },
8138
8139 // @method setStyle(style: Path options): this
8140 // Changes the appearance of a Path based on the options in the `Path options` object.
8141 setStyle: function (style) {
8142 setOptions(this, style);
8143 if (this._renderer) {
8144 this._renderer._updateStyle(this);
8145 if (this.options.stroke && style && Object.prototype.hasOwnProperty.call(style, 'weight')) {
8146 this._updateBounds();
8147 }
8148 }
8149 return this;
8150 },
8151
8152 // @method bringToFront(): this
8153 // Brings the layer to the top of all path layers.
8154 bringToFront: function () {
8155 if (this._renderer) {
8156 this._renderer._bringToFront(this);
8157 }
8158 return this;
8159 },
8160
8161 // @method bringToBack(): this
8162 // Brings the layer to the bottom of all path layers.
8163 bringToBack: function () {
8164 if (this._renderer) {
8165 this._renderer._bringToBack(this);
8166 }
8167 return this;
8168 },
8169
8170 getElement: function () {
8171 return this._path;
8172 },
8173
8174 _reset: function () {
8175 // defined in child classes
8176 this._project();
8177 this._update();
8178 },
8179
8180 _clickTolerance: function () {
8181 // used when doing hit detection for Canvas layers
8182 return (this.options.stroke ? this.options.weight / 2 : 0) +
8183 (this._renderer.options.tolerance || 0);
8184 }
8185});
8186
8187/*
8188 * @class CircleMarker
8189 * @aka L.CircleMarker
8190 * @inherits Path
8191 *
8192 * A circle of a fixed size with radius specified in pixels. Extends `Path`.
8193 */
8194
8195var CircleMarker = Path.extend({
8196
8197 // @section
8198 // @aka CircleMarker options
8199 options: {
8200 fill: true,
8201
8202 // @option radius: Number = 10
8203 // Radius of the circle marker, in pixels
8204 radius: 10
8205 },
8206
8207 initialize: function (latlng, options) {
8208 setOptions(this, options);
8209 this._latlng = toLatLng(latlng);
8210 this._radius = this.options.radius;
8211 },
8212
8213 // @method setLatLng(latLng: LatLng): this
8214 // Sets the position of a circle marker to a new location.
8215 setLatLng: function (latlng) {
8216 var oldLatLng = this._latlng;
8217 this._latlng = toLatLng(latlng);
8218 this.redraw();
8219
8220 // @event move: Event
8221 // Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
8222 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
8223 },
8224
8225 // @method getLatLng(): LatLng
8226 // Returns the current geographical position of the circle marker
8227 getLatLng: function () {
8228 return this._latlng;
8229 },
8230
8231 // @method setRadius(radius: Number): this
8232 // Sets the radius of a circle marker. Units are in pixels.
8233 setRadius: function (radius) {
8234 this.options.radius = this._radius = radius;
8235 return this.redraw();
8236 },
8237
8238 // @method getRadius(): Number
8239 // Returns the current radius of the circle
8240 getRadius: function () {
8241 return this._radius;
8242 },
8243
8244 setStyle : function (options) {
8245 var radius = options && options.radius || this._radius;
8246 Path.prototype.setStyle.call(this, options);
8247 this.setRadius(radius);
8248 return this;
8249 },
8250
8251 _project: function () {
8252 this._point = this._map.latLngToLayerPoint(this._latlng);
8253 this._updateBounds();
8254 },
8255
8256 _updateBounds: function () {
8257 var r = this._radius,
8258 r2 = this._radiusY || r,
8259 w = this._clickTolerance(),
8260 p = [r + w, r2 + w];
8261 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
8262 },
8263
8264 _update: function () {
8265 if (this._map) {
8266 this._updatePath();
8267 }
8268 },
8269
8270 _updatePath: function () {
8271 this._renderer._updateCircle(this);
8272 },
8273
8274 _empty: function () {
8275 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
8276 },
8277
8278 // Needed by the `Canvas` renderer for interactivity
8279 _containsPoint: function (p) {
8280 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
8281 }
8282});
8283
8284
8285// @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
8286// Instantiates a circle marker object given a geographical point, and an optional options object.
8287function circleMarker(latlng, options) {
8288 return new CircleMarker(latlng, options);
8289}
8290
8291/*
8292 * @class Circle
8293 * @aka L.Circle
8294 * @inherits CircleMarker
8295 *
8296 * A class for drawing circle overlays on a map. Extends `CircleMarker`.
8297 *
8298 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
8299 *
8300 * @example
8301 *
8302 * ```js
8303 * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
8304 * ```
8305 */
8306
8307var Circle = CircleMarker.extend({
8308
8309 initialize: function (latlng, options, legacyOptions) {
8310 if (typeof options === 'number') {
8311 // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
8312 options = extend({}, legacyOptions, {radius: options});
8313 }
8314 setOptions(this, options);
8315 this._latlng = toLatLng(latlng);
8316
8317 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
8318
8319 // @section
8320 // @aka Circle options
8321 // @option radius: Number; Radius of the circle, in meters.
8322 this._mRadius = this.options.radius;
8323 },
8324
8325 // @method setRadius(radius: Number): this
8326 // Sets the radius of a circle. Units are in meters.
8327 setRadius: function (radius) {
8328 this._mRadius = radius;
8329 return this.redraw();
8330 },
8331
8332 // @method getRadius(): Number
8333 // Returns the current radius of a circle. Units are in meters.
8334 getRadius: function () {
8335 return this._mRadius;
8336 },
8337
8338 // @method getBounds(): LatLngBounds
8339 // Returns the `LatLngBounds` of the path.
8340 getBounds: function () {
8341 var half = [this._radius, this._radiusY || this._radius];
8342
8343 return new LatLngBounds(
8344 this._map.layerPointToLatLng(this._point.subtract(half)),
8345 this._map.layerPointToLatLng(this._point.add(half)));
8346 },
8347
8348 setStyle: Path.prototype.setStyle,
8349
8350 _project: function () {
8351
8352 var lng = this._latlng.lng,
8353 lat = this._latlng.lat,
8354 map = this._map,
8355 crs = map.options.crs;
8356
8357 if (crs.distance === Earth.distance) {
8358 var d = Math.PI / 180,
8359 latR = (this._mRadius / Earth.R) / d,
8360 top = map.project([lat + latR, lng]),
8361 bottom = map.project([lat - latR, lng]),
8362 p = top.add(bottom).divideBy(2),
8363 lat2 = map.unproject(p).lat,
8364 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
8365 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
8366
8367 if (isNaN(lngR) || lngR === 0) {
8368 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
8369 }
8370
8371 this._point = p.subtract(map.getPixelOrigin());
8372 this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
8373 this._radiusY = p.y - top.y;
8374
8375 } else {
8376 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
8377
8378 this._point = map.latLngToLayerPoint(this._latlng);
8379 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
8380 }
8381
8382 this._updateBounds();
8383 }
8384});
8385
8386// @factory L.circle(latlng: LatLng, options?: Circle options)
8387// Instantiates a circle object given a geographical point, and an options object
8388// which contains the circle radius.
8389// @alternative
8390// @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
8391// Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
8392// Do not use in new applications or plugins.
8393function circle(latlng, options, legacyOptions) {
8394 return new Circle(latlng, options, legacyOptions);
8395}
8396
8397/*
8398 * @class Polyline
8399 * @aka L.Polyline
8400 * @inherits Path
8401 *
8402 * A class for drawing polyline overlays on a map. Extends `Path`.
8403 *
8404 * @example
8405 *
8406 * ```js
8407 * // create a red polyline from an array of LatLng points
8408 * var latlngs = [
8409 * [45.51, -122.68],
8410 * [37.77, -122.43],
8411 * [34.04, -118.2]
8412 * ];
8413 *
8414 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
8415 *
8416 * // zoom the map to the polyline
8417 * map.fitBounds(polyline.getBounds());
8418 * ```
8419 *
8420 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
8421 *
8422 * ```js
8423 * // create a red polyline from an array of arrays of LatLng points
8424 * var latlngs = [
8425 * [[45.51, -122.68],
8426 * [37.77, -122.43],
8427 * [34.04, -118.2]],
8428 * [[40.78, -73.91],
8429 * [41.83, -87.62],
8430 * [32.76, -96.72]]
8431 * ];
8432 * ```
8433 */
8434
8435
8436var Polyline = Path.extend({
8437
8438 // @section
8439 // @aka Polyline options
8440 options: {
8441 // @option smoothFactor: Number = 1.0
8442 // How much to simplify the polyline on each zoom level. More means
8443 // better performance and smoother look, and less means more accurate representation.
8444 smoothFactor: 1.0,
8445
8446 // @option noClip: Boolean = false
8447 // Disable polyline clipping.
8448 noClip: false
8449 },
8450
8451 initialize: function (latlngs, options) {
8452 setOptions(this, options);
8453 this._setLatLngs(latlngs);
8454 },
8455
8456 // @method getLatLngs(): LatLng[]
8457 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8458 getLatLngs: function () {
8459 return this._latlngs;
8460 },
8461
8462 // @method setLatLngs(latlngs: LatLng[]): this
8463 // Replaces all the points in the polyline with the given array of geographical points.
8464 setLatLngs: function (latlngs) {
8465 this._setLatLngs(latlngs);
8466 return this.redraw();
8467 },
8468
8469 // @method isEmpty(): Boolean
8470 // Returns `true` if the Polyline has no LatLngs.
8471 isEmpty: function () {
8472 return !this._latlngs.length;
8473 },
8474
8475 // @method closestLayerPoint(p: Point): Point
8476 // Returns the point closest to `p` on the Polyline.
8477 closestLayerPoint: function (p) {
8478 var minDistance = Infinity,
8479 minPoint = null,
8480 closest = _sqClosestPointOnSegment,
8481 p1, p2;
8482
8483 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8484 var points = this._parts[j];
8485
8486 for (var i = 1, len = points.length; i < len; i++) {
8487 p1 = points[i - 1];
8488 p2 = points[i];
8489
8490 var sqDist = closest(p, p1, p2, true);
8491
8492 if (sqDist < minDistance) {
8493 minDistance = sqDist;
8494 minPoint = closest(p, p1, p2);
8495 }
8496 }
8497 }
8498 if (minPoint) {
8499 minPoint.distance = Math.sqrt(minDistance);
8500 }
8501 return minPoint;
8502 },
8503
8504 // @method getCenter(): LatLng
8505 // Returns the center ([centroid](https://en.wikipedia.org/wiki/Centroid)) of the polyline.
8506 getCenter: function () {
8507 // throws error when not yet added to map as this center calculation requires projected coordinates
8508 if (!this._map) {
8509 throw new Error('Must add layer to map before using getCenter()');
8510 }
8511 return polylineCenter(this._defaultShape(), this._map.options.crs);
8512 },
8513
8514 // @method getBounds(): LatLngBounds
8515 // Returns the `LatLngBounds` of the path.
8516 getBounds: function () {
8517 return this._bounds;
8518 },
8519
8520 // @method addLatLng(latlng: LatLng, latlngs?: LatLng[]): this
8521 // Adds a given point to the polyline. By default, adds to the first ring of
8522 // the polyline in case of a multi-polyline, but can be overridden by passing
8523 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8524 addLatLng: function (latlng, latlngs) {
8525 latlngs = latlngs || this._defaultShape();
8526 latlng = toLatLng(latlng);
8527 latlngs.push(latlng);
8528 this._bounds.extend(latlng);
8529 return this.redraw();
8530 },
8531
8532 _setLatLngs: function (latlngs) {
8533 this._bounds = new LatLngBounds();
8534 this._latlngs = this._convertLatLngs(latlngs);
8535 },
8536
8537 _defaultShape: function () {
8538 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
8539 },
8540
8541 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8542 _convertLatLngs: function (latlngs) {
8543 var result = [],
8544 flat = isFlat(latlngs);
8545
8546 for (var i = 0, len = latlngs.length; i < len; i++) {
8547 if (flat) {
8548 result[i] = toLatLng(latlngs[i]);
8549 this._bounds.extend(result[i]);
8550 } else {
8551 result[i] = this._convertLatLngs(latlngs[i]);
8552 }
8553 }
8554
8555 return result;
8556 },
8557
8558 _project: function () {
8559 var pxBounds = new Bounds();
8560 this._rings = [];
8561 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8562
8563 if (this._bounds.isValid() && pxBounds.isValid()) {
8564 this._rawPxBounds = pxBounds;
8565 this._updateBounds();
8566 }
8567 },
8568
8569 _updateBounds: function () {
8570 var w = this._clickTolerance(),
8571 p = new Point(w, w);
8572
8573 if (!this._rawPxBounds) {
8574 return;
8575 }
8576
8577 this._pxBounds = new Bounds([
8578 this._rawPxBounds.min.subtract(p),
8579 this._rawPxBounds.max.add(p)
8580 ]);
8581 },
8582
8583 // recursively turns latlngs into a set of rings with projected coordinates
8584 _projectLatlngs: function (latlngs, result, projectedBounds) {
8585 var flat = latlngs[0] instanceof LatLng,
8586 len = latlngs.length,
8587 i, ring;
8588
8589 if (flat) {
8590 ring = [];
8591 for (i = 0; i < len; i++) {
8592 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8593 projectedBounds.extend(ring[i]);
8594 }
8595 result.push(ring);
8596 } else {
8597 for (i = 0; i < len; i++) {
8598 this._projectLatlngs(latlngs[i], result, projectedBounds);
8599 }
8600 }
8601 },
8602
8603 // clip polyline by renderer bounds so that we have less to render for performance
8604 _clipPoints: function () {
8605 var bounds = this._renderer._bounds;
8606
8607 this._parts = [];
8608 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8609 return;
8610 }
8611
8612 if (this.options.noClip) {
8613 this._parts = this._rings;
8614 return;
8615 }
8616
8617 var parts = this._parts,
8618 i, j, k, len, len2, segment, points;
8619
8620 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8621 points = this._rings[i];
8622
8623 for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8624 segment = clipSegment(points[j], points[j + 1], bounds, j, true);
8625
8626 if (!segment) { continue; }
8627
8628 parts[k] = parts[k] || [];
8629 parts[k].push(segment[0]);
8630
8631 // if segment goes out of screen, or it's the last one, it's the end of the line part
8632 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8633 parts[k].push(segment[1]);
8634 k++;
8635 }
8636 }
8637 }
8638 },
8639
8640 // simplify each clipped part of the polyline for performance
8641 _simplifyPoints: function () {
8642 var parts = this._parts,
8643 tolerance = this.options.smoothFactor;
8644
8645 for (var i = 0, len = parts.length; i < len; i++) {
8646 parts[i] = simplify(parts[i], tolerance);
8647 }
8648 },
8649
8650 _update: function () {
8651 if (!this._map) { return; }
8652
8653 this._clipPoints();
8654 this._simplifyPoints();
8655 this._updatePath();
8656 },
8657
8658 _updatePath: function () {
8659 this._renderer._updatePoly(this);
8660 },
8661
8662 // Needed by the `Canvas` renderer for interactivity
8663 _containsPoint: function (p, closed) {
8664 var i, j, k, len, len2, part,
8665 w = this._clickTolerance();
8666
8667 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8668
8669 // hit detection for polylines
8670 for (i = 0, len = this._parts.length; i < len; i++) {
8671 part = this._parts[i];
8672
8673 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8674 if (!closed && (j === 0)) { continue; }
8675
8676 if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
8677 return true;
8678 }
8679 }
8680 }
8681 return false;
8682 }
8683});
8684
8685// @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8686// Instantiates a polyline object given an array of geographical points and
8687// optionally an options object. You can create a `Polyline` object with
8688// multiple separate lines (`MultiPolyline`) by passing an array of arrays
8689// of geographic points.
8690function polyline(latlngs, options) {
8691 return new Polyline(latlngs, options);
8692}
8693
8694// Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
8695Polyline._flat = _flat;
8696
8697/*
8698 * @class Polygon
8699 * @aka L.Polygon
8700 * @inherits Polyline
8701 *
8702 * A class for drawing polygon overlays on a map. Extends `Polyline`.
8703 *
8704 * 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.
8705 *
8706 *
8707 * @example
8708 *
8709 * ```js
8710 * // create a red polygon from an array of LatLng points
8711 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
8712 *
8713 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8714 *
8715 * // zoom the map to the polygon
8716 * map.fitBounds(polygon.getBounds());
8717 * ```
8718 *
8719 * 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:
8720 *
8721 * ```js
8722 * var latlngs = [
8723 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8724 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8725 * ];
8726 * ```
8727 *
8728 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8729 *
8730 * ```js
8731 * var latlngs = [
8732 * [ // first polygon
8733 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8734 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8735 * ],
8736 * [ // second polygon
8737 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
8738 * ]
8739 * ];
8740 * ```
8741 */
8742
8743var Polygon = Polyline.extend({
8744
8745 options: {
8746 fill: true
8747 },
8748
8749 isEmpty: function () {
8750 return !this._latlngs.length || !this._latlngs[0].length;
8751 },
8752
8753 // @method getCenter(): LatLng
8754 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the Polygon.
8755 getCenter: function () {
8756 // throws error when not yet added to map as this center calculation requires projected coordinates
8757 if (!this._map) {
8758 throw new Error('Must add layer to map before using getCenter()');
8759 }
8760 return polygonCenter(this._defaultShape(), this._map.options.crs);
8761 },
8762
8763 _convertLatLngs: function (latlngs) {
8764 var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
8765 len = result.length;
8766
8767 // remove last point if it equals first one
8768 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
8769 result.pop();
8770 }
8771 return result;
8772 },
8773
8774 _setLatLngs: function (latlngs) {
8775 Polyline.prototype._setLatLngs.call(this, latlngs);
8776 if (isFlat(this._latlngs)) {
8777 this._latlngs = [this._latlngs];
8778 }
8779 },
8780
8781 _defaultShape: function () {
8782 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8783 },
8784
8785 _clipPoints: function () {
8786 // polygons need a different clipping algorithm so we redefine that
8787
8788 var bounds = this._renderer._bounds,
8789 w = this.options.weight,
8790 p = new Point(w, w);
8791
8792 // increase clip padding by stroke width to avoid stroke on clip edges
8793 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
8794
8795 this._parts = [];
8796 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8797 return;
8798 }
8799
8800 if (this.options.noClip) {
8801 this._parts = this._rings;
8802 return;
8803 }
8804
8805 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8806 clipped = clipPolygon(this._rings[i], bounds, true);
8807 if (clipped.length) {
8808 this._parts.push(clipped);
8809 }
8810 }
8811 },
8812
8813 _updatePath: function () {
8814 this._renderer._updatePoly(this, true);
8815 },
8816
8817 // Needed by the `Canvas` renderer for interactivity
8818 _containsPoint: function (p) {
8819 var inside = false,
8820 part, p1, p2, i, j, k, len, len2;
8821
8822 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8823
8824 // ray casting algorithm for detecting if point is in polygon
8825 for (i = 0, len = this._parts.length; i < len; i++) {
8826 part = this._parts[i];
8827
8828 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8829 p1 = part[j];
8830 p2 = part[k];
8831
8832 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)) {
8833 inside = !inside;
8834 }
8835 }
8836 }
8837
8838 // also check if it's on polygon stroke
8839 return inside || Polyline.prototype._containsPoint.call(this, p, true);
8840 }
8841
8842});
8843
8844
8845// @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8846function polygon(latlngs, options) {
8847 return new Polygon(latlngs, options);
8848}
8849
8850/*
8851 * @class GeoJSON
8852 * @aka L.GeoJSON
8853 * @inherits FeatureGroup
8854 *
8855 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
8856 * GeoJSON data and display it on the map. Extends `FeatureGroup`.
8857 *
8858 * @example
8859 *
8860 * ```js
8861 * L.geoJSON(data, {
8862 * style: function (feature) {
8863 * return {color: feature.properties.color};
8864 * }
8865 * }).bindPopup(function (layer) {
8866 * return layer.feature.properties.description;
8867 * }).addTo(map);
8868 * ```
8869 */
8870
8871var GeoJSON = FeatureGroup.extend({
8872
8873 /* @section
8874 * @aka GeoJSON options
8875 *
8876 * @option pointToLayer: Function = *
8877 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
8878 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
8879 * The default is to spawn a default `Marker`:
8880 * ```js
8881 * function(geoJsonPoint, latlng) {
8882 * return L.marker(latlng);
8883 * }
8884 * ```
8885 *
8886 * @option style: Function = *
8887 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
8888 * called internally when data is added.
8889 * The default value is to not override any defaults:
8890 * ```js
8891 * function (geoJsonFeature) {
8892 * return {}
8893 * }
8894 * ```
8895 *
8896 * @option onEachFeature: Function = *
8897 * A `Function` that will be called once for each created `Feature`, after it has
8898 * been created and styled. Useful for attaching events and popups to features.
8899 * The default is to do nothing with the newly created layers:
8900 * ```js
8901 * function (feature, layer) {}
8902 * ```
8903 *
8904 * @option filter: Function = *
8905 * A `Function` that will be used to decide whether to include a feature or not.
8906 * The default is to include all features:
8907 * ```js
8908 * function (geoJsonFeature) {
8909 * return true;
8910 * }
8911 * ```
8912 * Note: dynamically changing the `filter` option will have effect only on newly
8913 * added data. It will _not_ re-evaluate already included features.
8914 *
8915 * @option coordsToLatLng: Function = *
8916 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
8917 * The default is the `coordsToLatLng` static method.
8918 *
8919 * @option markersInheritOptions: Boolean = false
8920 * Whether default Markers for "Point" type Features inherit from group options.
8921 */
8922
8923 initialize: function (geojson, options) {
8924 setOptions(this, options);
8925
8926 this._layers = {};
8927
8928 if (geojson) {
8929 this.addData(geojson);
8930 }
8931 },
8932
8933 // @method addData( <GeoJSON> data ): this
8934 // Adds a GeoJSON object to the layer.
8935 addData: function (geojson) {
8936 var features = isArray(geojson) ? geojson : geojson.features,
8937 i, len, feature;
8938
8939 if (features) {
8940 for (i = 0, len = features.length; i < len; i++) {
8941 // only add this if geometry or geometries are set and not null
8942 feature = features[i];
8943 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
8944 this.addData(feature);
8945 }
8946 }
8947 return this;
8948 }
8949
8950 var options = this.options;
8951
8952 if (options.filter && !options.filter(geojson)) { return this; }
8953
8954 var layer = geometryToLayer(geojson, options);
8955 if (!layer) {
8956 return this;
8957 }
8958 layer.feature = asFeature(geojson);
8959
8960 layer.defaultOptions = layer.options;
8961 this.resetStyle(layer);
8962
8963 if (options.onEachFeature) {
8964 options.onEachFeature(geojson, layer);
8965 }
8966
8967 return this.addLayer(layer);
8968 },
8969
8970 // @method resetStyle( <Path> layer? ): this
8971 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
8972 // If `layer` is omitted, the style of all features in the current layer is reset.
8973 resetStyle: function (layer) {
8974 if (layer === undefined) {
8975 return this.eachLayer(this.resetStyle, this);
8976 }
8977 // reset any custom styles
8978 layer.options = extend({}, layer.defaultOptions);
8979 this._setLayerStyle(layer, this.options.style);
8980 return this;
8981 },
8982
8983 // @method setStyle( <Function> style ): this
8984 // Changes styles of GeoJSON vector layers with the given style function.
8985 setStyle: function (style) {
8986 return this.eachLayer(function (layer) {
8987 this._setLayerStyle(layer, style);
8988 }, this);
8989 },
8990
8991 _setLayerStyle: function (layer, style) {
8992 if (layer.setStyle) {
8993 if (typeof style === 'function') {
8994 style = style(layer.feature);
8995 }
8996 layer.setStyle(style);
8997 }
8998 }
8999});
9000
9001// @section
9002// There are several static functions which can be called without instantiating L.GeoJSON:
9003
9004// @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
9005// Creates a `Layer` from a given GeoJSON feature. Can use a custom
9006// [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
9007// functions if provided as options.
9008function geometryToLayer(geojson, options) {
9009
9010 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
9011 coords = geometry ? geometry.coordinates : null,
9012 layers = [],
9013 pointToLayer = options && options.pointToLayer,
9014 _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
9015 latlng, latlngs, i, len;
9016
9017 if (!coords && !geometry) {
9018 return null;
9019 }
9020
9021 switch (geometry.type) {
9022 case 'Point':
9023 latlng = _coordsToLatLng(coords);
9024 return _pointToLayer(pointToLayer, geojson, latlng, options);
9025
9026 case 'MultiPoint':
9027 for (i = 0, len = coords.length; i < len; i++) {
9028 latlng = _coordsToLatLng(coords[i]);
9029 layers.push(_pointToLayer(pointToLayer, geojson, latlng, options));
9030 }
9031 return new FeatureGroup(layers);
9032
9033 case 'LineString':
9034 case 'MultiLineString':
9035 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
9036 return new Polyline(latlngs, options);
9037
9038 case 'Polygon':
9039 case 'MultiPolygon':
9040 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
9041 return new Polygon(latlngs, options);
9042
9043 case 'GeometryCollection':
9044 for (i = 0, len = geometry.geometries.length; i < len; i++) {
9045 var geoLayer = geometryToLayer({
9046 geometry: geometry.geometries[i],
9047 type: 'Feature',
9048 properties: geojson.properties
9049 }, options);
9050
9051 if (geoLayer) {
9052 layers.push(geoLayer);
9053 }
9054 }
9055 return new FeatureGroup(layers);
9056
9057 case 'FeatureCollection':
9058 for (i = 0, len = geometry.features.length; i < len; i++) {
9059 var featureLayer = geometryToLayer(geometry.features[i], options);
9060
9061 if (featureLayer) {
9062 layers.push(featureLayer);
9063 }
9064 }
9065 return new FeatureGroup(layers);
9066
9067 default:
9068 throw new Error('Invalid GeoJSON object.');
9069 }
9070}
9071
9072function _pointToLayer(pointToLayerFn, geojson, latlng, options) {
9073 return pointToLayerFn ?
9074 pointToLayerFn(geojson, latlng) :
9075 new Marker(latlng, options && options.markersInheritOptions && options);
9076}
9077
9078// @function coordsToLatLng(coords: Array): LatLng
9079// Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
9080// or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
9081function coordsToLatLng(coords) {
9082 return new LatLng(coords[1], coords[0], coords[2]);
9083}
9084
9085// @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
9086// Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
9087// `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
9088// Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
9089function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
9090 var latlngs = [];
9091
9092 for (var i = 0, len = coords.length, latlng; i < len; i++) {
9093 latlng = levelsDeep ?
9094 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
9095 (_coordsToLatLng || coordsToLatLng)(coords[i]);
9096
9097 latlngs.push(latlng);
9098 }
9099
9100 return latlngs;
9101}
9102
9103// @function latLngToCoords(latlng: LatLng, precision?: Number|false): Array
9104// Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
9105// Coordinates values are rounded with [`formatNum`](#util-formatnum) function.
9106function latLngToCoords(latlng, precision) {
9107 latlng = toLatLng(latlng);
9108 return latlng.alt !== undefined ?
9109 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
9110 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
9111}
9112
9113// @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean, precision?: Number|false): Array
9114// Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
9115// `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.
9116// Coordinates values are rounded with [`formatNum`](#util-formatnum) function.
9117function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
9118 var coords = [];
9119
9120 for (var i = 0, len = latlngs.length; i < len; i++) {
9121 // Check for flat arrays required to ensure unbalanced arrays are correctly converted in recursion
9122 coords.push(levelsDeep ?
9123 latLngsToCoords(latlngs[i], isFlat(latlngs[i]) ? 0 : levelsDeep - 1, closed, precision) :
9124 latLngToCoords(latlngs[i], precision));
9125 }
9126
9127 if (!levelsDeep && closed) {
9128 coords.push(coords[0].slice());
9129 }
9130
9131 return coords;
9132}
9133
9134function getFeature(layer, newGeometry) {
9135 return layer.feature ?
9136 extend({}, layer.feature, {geometry: newGeometry}) :
9137 asFeature(newGeometry);
9138}
9139
9140// @function asFeature(geojson: Object): Object
9141// Normalize GeoJSON geometries/features into GeoJSON features.
9142function asFeature(geojson) {
9143 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
9144 return geojson;
9145 }
9146
9147 return {
9148 type: 'Feature',
9149 properties: {},
9150 geometry: geojson
9151 };
9152}
9153
9154var PointToGeoJSON = {
9155 toGeoJSON: function (precision) {
9156 return getFeature(this, {
9157 type: 'Point',
9158 coordinates: latLngToCoords(this.getLatLng(), precision)
9159 });
9160 }
9161};
9162
9163// @namespace Marker
9164// @section Other methods
9165// @method toGeoJSON(precision?: Number|false): Object
9166// Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
9167// Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
9168Marker.include(PointToGeoJSON);
9169
9170// @namespace CircleMarker
9171// @method toGeoJSON(precision?: Number|false): Object
9172// Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
9173// Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
9174Circle.include(PointToGeoJSON);
9175CircleMarker.include(PointToGeoJSON);
9176
9177
9178// @namespace Polyline
9179// @method toGeoJSON(precision?: Number|false): Object
9180// Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
9181// Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
9182Polyline.include({
9183 toGeoJSON: function (precision) {
9184 var multi = !isFlat(this._latlngs);
9185
9186 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
9187
9188 return getFeature(this, {
9189 type: (multi ? 'Multi' : '') + 'LineString',
9190 coordinates: coords
9191 });
9192 }
9193});
9194
9195// @namespace Polygon
9196// @method toGeoJSON(precision?: Number|false): Object
9197// Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
9198// Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
9199Polygon.include({
9200 toGeoJSON: function (precision) {
9201 var holes = !isFlat(this._latlngs),
9202 multi = holes && !isFlat(this._latlngs[0]);
9203
9204 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
9205
9206 if (!holes) {
9207 coords = [coords];
9208 }
9209
9210 return getFeature(this, {
9211 type: (multi ? 'Multi' : '') + 'Polygon',
9212 coordinates: coords
9213 });
9214 }
9215});
9216
9217
9218// @namespace LayerGroup
9219LayerGroup.include({
9220 toMultiPoint: function (precision) {
9221 var coords = [];
9222
9223 this.eachLayer(function (layer) {
9224 coords.push(layer.toGeoJSON(precision).geometry.coordinates);
9225 });
9226
9227 return getFeature(this, {
9228 type: 'MultiPoint',
9229 coordinates: coords
9230 });
9231 },
9232
9233 // @method toGeoJSON(precision?: Number|false): Object
9234 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
9235 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
9236 toGeoJSON: function (precision) {
9237
9238 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
9239
9240 if (type === 'MultiPoint') {
9241 return this.toMultiPoint(precision);
9242 }
9243
9244 var isGeometryCollection = type === 'GeometryCollection',
9245 jsons = [];
9246
9247 this.eachLayer(function (layer) {
9248 if (layer.toGeoJSON) {
9249 var json = layer.toGeoJSON(precision);
9250 if (isGeometryCollection) {
9251 jsons.push(json.geometry);
9252 } else {
9253 var feature = asFeature(json);
9254 // Squash nested feature collections
9255 if (feature.type === 'FeatureCollection') {
9256 jsons.push.apply(jsons, feature.features);
9257 } else {
9258 jsons.push(feature);
9259 }
9260 }
9261 }
9262 });
9263
9264 if (isGeometryCollection) {
9265 return getFeature(this, {
9266 geometries: jsons,
9267 type: 'GeometryCollection'
9268 });
9269 }
9270
9271 return {
9272 type: 'FeatureCollection',
9273 features: jsons
9274 };
9275 }
9276});
9277
9278// @namespace GeoJSON
9279// @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
9280// Creates a GeoJSON layer. Optionally accepts an object in
9281// [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
9282// (you can alternatively add it later with `addData` method) and an `options` object.
9283function geoJSON(geojson, options) {
9284 return new GeoJSON(geojson, options);
9285}
9286
9287// Backward compatibility.
9288var geoJson = geoJSON;
9289
9290/*
9291 * @class ImageOverlay
9292 * @aka L.ImageOverlay
9293 * @inherits Interactive layer
9294 *
9295 * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
9296 *
9297 * @example
9298 *
9299 * ```js
9300 * var imageUrl = 'https://maps.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
9301 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
9302 * L.imageOverlay(imageUrl, imageBounds).addTo(map);
9303 * ```
9304 */
9305
9306var ImageOverlay = Layer.extend({
9307
9308 // @section
9309 // @aka ImageOverlay options
9310 options: {
9311 // @option opacity: Number = 1.0
9312 // The opacity of the image overlay.
9313 opacity: 1,
9314
9315 // @option alt: String = ''
9316 // Text for the `alt` attribute of the image (useful for accessibility).
9317 alt: '',
9318
9319 // @option interactive: Boolean = false
9320 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
9321 interactive: false,
9322
9323 // @option crossOrigin: Boolean|String = false
9324 // Whether the crossOrigin attribute will be added to the image.
9325 // 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.
9326 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
9327 crossOrigin: false,
9328
9329 // @option errorOverlayUrl: String = ''
9330 // URL to the overlay image to show in place of the overlay that failed to load.
9331 errorOverlayUrl: '',
9332
9333 // @option zIndex: Number = 1
9334 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
9335 zIndex: 1,
9336
9337 // @option className: String = ''
9338 // A custom class name to assign to the image. Empty by default.
9339 className: ''
9340 },
9341
9342 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
9343 this._url = url;
9344 this._bounds = toLatLngBounds(bounds);
9345
9346 setOptions(this, options);
9347 },
9348
9349 onAdd: function () {
9350 if (!this._image) {
9351 this._initImage();
9352
9353 if (this.options.opacity < 1) {
9354 this._updateOpacity();
9355 }
9356 }
9357
9358 if (this.options.interactive) {
9359 addClass(this._image, 'leaflet-interactive');
9360 this.addInteractiveTarget(this._image);
9361 }
9362
9363 this.getPane().appendChild(this._image);
9364 this._reset();
9365 },
9366
9367 onRemove: function () {
9368 remove(this._image);
9369 if (this.options.interactive) {
9370 this.removeInteractiveTarget(this._image);
9371 }
9372 },
9373
9374 // @method setOpacity(opacity: Number): this
9375 // Sets the opacity of the overlay.
9376 setOpacity: function (opacity) {
9377 this.options.opacity = opacity;
9378
9379 if (this._image) {
9380 this._updateOpacity();
9381 }
9382 return this;
9383 },
9384
9385 setStyle: function (styleOpts) {
9386 if (styleOpts.opacity) {
9387 this.setOpacity(styleOpts.opacity);
9388 }
9389 return this;
9390 },
9391
9392 // @method bringToFront(): this
9393 // Brings the layer to the top of all overlays.
9394 bringToFront: function () {
9395 if (this._map) {
9396 toFront(this._image);
9397 }
9398 return this;
9399 },
9400
9401 // @method bringToBack(): this
9402 // Brings the layer to the bottom of all overlays.
9403 bringToBack: function () {
9404 if (this._map) {
9405 toBack(this._image);
9406 }
9407 return this;
9408 },
9409
9410 // @method setUrl(url: String): this
9411 // Changes the URL of the image.
9412 setUrl: function (url) {
9413 this._url = url;
9414
9415 if (this._image) {
9416 this._image.src = url;
9417 }
9418 return this;
9419 },
9420
9421 // @method setBounds(bounds: LatLngBounds): this
9422 // Update the bounds that this ImageOverlay covers
9423 setBounds: function (bounds) {
9424 this._bounds = toLatLngBounds(bounds);
9425
9426 if (this._map) {
9427 this._reset();
9428 }
9429 return this;
9430 },
9431
9432 getEvents: function () {
9433 var events = {
9434 zoom: this._reset,
9435 viewreset: this._reset
9436 };
9437
9438 if (this._zoomAnimated) {
9439 events.zoomanim = this._animateZoom;
9440 }
9441
9442 return events;
9443 },
9444
9445 // @method setZIndex(value: Number): this
9446 // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
9447 setZIndex: function (value) {
9448 this.options.zIndex = value;
9449 this._updateZIndex();
9450 return this;
9451 },
9452
9453 // @method getBounds(): LatLngBounds
9454 // Get the bounds that this ImageOverlay covers
9455 getBounds: function () {
9456 return this._bounds;
9457 },
9458
9459 // @method getElement(): HTMLElement
9460 // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
9461 // used by this overlay.
9462 getElement: function () {
9463 return this._image;
9464 },
9465
9466 _initImage: function () {
9467 var wasElementSupplied = this._url.tagName === 'IMG';
9468 var img = this._image = wasElementSupplied ? this._url : create$1('img');
9469
9470 addClass(img, 'leaflet-image-layer');
9471 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
9472 if (this.options.className) { addClass(img, this.options.className); }
9473
9474 img.onselectstart = falseFn;
9475 img.onmousemove = falseFn;
9476
9477 // @event load: Event
9478 // Fired when the ImageOverlay layer has loaded its image
9479 img.onload = bind(this.fire, this, 'load');
9480 img.onerror = bind(this._overlayOnError, this, 'error');
9481
9482 if (this.options.crossOrigin || this.options.crossOrigin === '') {
9483 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
9484 }
9485
9486 if (this.options.zIndex) {
9487 this._updateZIndex();
9488 }
9489
9490 if (wasElementSupplied) {
9491 this._url = img.src;
9492 return;
9493 }
9494
9495 img.src = this._url;
9496 img.alt = this.options.alt;
9497 },
9498
9499 _animateZoom: function (e) {
9500 var scale = this._map.getZoomScale(e.zoom),
9501 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
9502
9503 setTransform(this._image, offset, scale);
9504 },
9505
9506 _reset: function () {
9507 var image = this._image,
9508 bounds = new Bounds(
9509 this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
9510 this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
9511 size = bounds.getSize();
9512
9513 setPosition(image, bounds.min);
9514
9515 image.style.width = size.x + 'px';
9516 image.style.height = size.y + 'px';
9517 },
9518
9519 _updateOpacity: function () {
9520 setOpacity(this._image, this.options.opacity);
9521 },
9522
9523 _updateZIndex: function () {
9524 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
9525 this._image.style.zIndex = this.options.zIndex;
9526 }
9527 },
9528
9529 _overlayOnError: function () {
9530 // @event error: Event
9531 // Fired when the ImageOverlay layer fails to load its image
9532 this.fire('error');
9533
9534 var errorUrl = this.options.errorOverlayUrl;
9535 if (errorUrl && this._url !== errorUrl) {
9536 this._url = errorUrl;
9537 this._image.src = errorUrl;
9538 }
9539 },
9540
9541 // @method getCenter(): LatLng
9542 // Returns the center of the ImageOverlay.
9543 getCenter: function () {
9544 return this._bounds.getCenter();
9545 }
9546});
9547
9548// @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
9549// Instantiates an image overlay object given the URL of the image and the
9550// geographical bounds it is tied to.
9551var imageOverlay = function (url, bounds, options) {
9552 return new ImageOverlay(url, bounds, options);
9553};
9554
9555/*
9556 * @class VideoOverlay
9557 * @aka L.VideoOverlay
9558 * @inherits ImageOverlay
9559 *
9560 * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
9561 *
9562 * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
9563 * HTML5 element.
9564 *
9565 * @example
9566 *
9567 * ```js
9568 * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
9569 * videoBounds = [[ 32, -130], [ 13, -100]];
9570 * L.videoOverlay(videoUrl, videoBounds ).addTo(map);
9571 * ```
9572 */
9573
9574var VideoOverlay = ImageOverlay.extend({
9575
9576 // @section
9577 // @aka VideoOverlay options
9578 options: {
9579 // @option autoplay: Boolean = true
9580 // Whether the video starts playing automatically when loaded.
9581 // On some browsers autoplay will only work with `muted: true`
9582 autoplay: true,
9583
9584 // @option loop: Boolean = true
9585 // Whether the video will loop back to the beginning when played.
9586 loop: true,
9587
9588 // @option keepAspectRatio: Boolean = true
9589 // Whether the video will save aspect ratio after the projection.
9590 // Relevant for supported browsers. See [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit)
9591 keepAspectRatio: true,
9592
9593 // @option muted: Boolean = false
9594 // Whether the video starts on mute when loaded.
9595 muted: false,
9596
9597 // @option playsInline: Boolean = true
9598 // Mobile browsers will play the video right where it is instead of open it up in fullscreen mode.
9599 playsInline: true
9600 },
9601
9602 _initImage: function () {
9603 var wasElementSupplied = this._url.tagName === 'VIDEO';
9604 var vid = this._image = wasElementSupplied ? this._url : create$1('video');
9605
9606 addClass(vid, 'leaflet-image-layer');
9607 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
9608 if (this.options.className) { addClass(vid, this.options.className); }
9609
9610 vid.onselectstart = falseFn;
9611 vid.onmousemove = falseFn;
9612
9613 // @event load: Event
9614 // Fired when the video has finished loading the first frame
9615 vid.onloadeddata = bind(this.fire, this, 'load');
9616
9617 if (wasElementSupplied) {
9618 var sourceElements = vid.getElementsByTagName('source');
9619 var sources = [];
9620 for (var j = 0; j < sourceElements.length; j++) {
9621 sources.push(sourceElements[j].src);
9622 }
9623
9624 this._url = (sourceElements.length > 0) ? sources : [vid.src];
9625 return;
9626 }
9627
9628 if (!isArray(this._url)) { this._url = [this._url]; }
9629
9630 if (!this.options.keepAspectRatio && Object.prototype.hasOwnProperty.call(vid.style, 'objectFit')) {
9631 vid.style['objectFit'] = 'fill';
9632 }
9633 vid.autoplay = !!this.options.autoplay;
9634 vid.loop = !!this.options.loop;
9635 vid.muted = !!this.options.muted;
9636 vid.playsInline = !!this.options.playsInline;
9637 for (var i = 0; i < this._url.length; i++) {
9638 var source = create$1('source');
9639 source.src = this._url[i];
9640 vid.appendChild(source);
9641 }
9642 }
9643
9644 // @method getElement(): HTMLVideoElement
9645 // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
9646 // used by this overlay.
9647});
9648
9649
9650// @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
9651// Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
9652// geographical bounds it is tied to.
9653
9654function videoOverlay(video, bounds, options) {
9655 return new VideoOverlay(video, bounds, options);
9656}
9657
9658/*
9659 * @class SVGOverlay
9660 * @aka L.SVGOverlay
9661 * @inherits ImageOverlay
9662 *
9663 * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`.
9664 *
9665 * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element.
9666 *
9667 * @example
9668 *
9669 * ```js
9670 * var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
9671 * svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg");
9672 * svgElement.setAttribute('viewBox', "0 0 200 200");
9673 * 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"/>';
9674 * var svgElementBounds = [ [ 32, -130 ], [ 13, -100 ] ];
9675 * L.svgOverlay(svgElement, svgElementBounds).addTo(map);
9676 * ```
9677 */
9678
9679var SVGOverlay = ImageOverlay.extend({
9680 _initImage: function () {
9681 var el = this._image = this._url;
9682
9683 addClass(el, 'leaflet-image-layer');
9684 if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); }
9685 if (this.options.className) { addClass(el, this.options.className); }
9686
9687 el.onselectstart = falseFn;
9688 el.onmousemove = falseFn;
9689 }
9690
9691 // @method getElement(): SVGElement
9692 // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement)
9693 // used by this overlay.
9694});
9695
9696
9697// @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options)
9698// Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to.
9699// A viewBox attribute is required on the SVG element to zoom in and out properly.
9700
9701function svgOverlay(el, bounds, options) {
9702 return new SVGOverlay(el, bounds, options);
9703}
9704
9705/*
9706 * @class DivOverlay
9707 * @inherits Interactive layer
9708 * @aka L.DivOverlay
9709 * Base model for L.Popup and L.Tooltip. Inherit from it for custom overlays like plugins.
9710 */
9711
9712// @namespace DivOverlay
9713var DivOverlay = Layer.extend({
9714
9715 // @section
9716 // @aka DivOverlay options
9717 options: {
9718 // @option interactive: Boolean = false
9719 // If true, the popup/tooltip will listen to the mouse events.
9720 interactive: false,
9721
9722 // @option offset: Point = Point(0, 0)
9723 // The offset of the overlay position.
9724 offset: [0, 0],
9725
9726 // @option className: String = ''
9727 // A custom CSS class name to assign to the overlay.
9728 className: '',
9729
9730 // @option pane: String = undefined
9731 // `Map pane` where the overlay will be added.
9732 pane: undefined,
9733
9734 // @option content: String|HTMLElement|Function = ''
9735 // Sets the HTML content of the overlay while initializing. If a function is passed the source layer will be
9736 // passed to the function. The function should return a `String` or `HTMLElement` to be used in the overlay.
9737 content: ''
9738 },
9739
9740 initialize: function (options, source) {
9741 if (options && (options instanceof LatLng || isArray(options))) {
9742 this._latlng = toLatLng(options);
9743 setOptions(this, source);
9744 } else {
9745 setOptions(this, options);
9746 this._source = source;
9747 }
9748 if (this.options.content) {
9749 this._content = this.options.content;
9750 }
9751 },
9752
9753 // @method openOn(map: Map): this
9754 // Adds the overlay to the map.
9755 // Alternative to `map.openPopup(popup)`/`.openTooltip(tooltip)`.
9756 openOn: function (map) {
9757 map = arguments.length ? map : this._source._map; // experimental, not the part of public api
9758 if (!map.hasLayer(this)) {
9759 map.addLayer(this);
9760 }
9761 return this;
9762 },
9763
9764 // @method close(): this
9765 // Closes the overlay.
9766 // Alternative to `map.closePopup(popup)`/`.closeTooltip(tooltip)`
9767 // and `layer.closePopup()`/`.closeTooltip()`.
9768 close: function () {
9769 if (this._map) {
9770 this._map.removeLayer(this);
9771 }
9772 return this;
9773 },
9774
9775 // @method toggle(layer?: Layer): this
9776 // Opens or closes the overlay bound to layer depending on its current state.
9777 // Argument may be omitted only for overlay bound to layer.
9778 // Alternative to `layer.togglePopup()`/`.toggleTooltip()`.
9779 toggle: function (layer) {
9780 if (this._map) {
9781 this.close();
9782 } else {
9783 if (arguments.length) {
9784 this._source = layer;
9785 } else {
9786 layer = this._source;
9787 }
9788 this._prepareOpen();
9789
9790 // open the overlay on the map
9791 this.openOn(layer._map);
9792 }
9793 return this;
9794 },
9795
9796 onAdd: function (map) {
9797 this._zoomAnimated = map._zoomAnimated;
9798
9799 if (!this._container) {
9800 this._initLayout();
9801 }
9802
9803 if (map._fadeAnimated) {
9804 setOpacity(this._container, 0);
9805 }
9806
9807 clearTimeout(this._removeTimeout);
9808 this.getPane().appendChild(this._container);
9809 this.update();
9810
9811 if (map._fadeAnimated) {
9812 setOpacity(this._container, 1);
9813 }
9814
9815 this.bringToFront();
9816
9817 if (this.options.interactive) {
9818 addClass(this._container, 'leaflet-interactive');
9819 this.addInteractiveTarget(this._container);
9820 }
9821 },
9822
9823 onRemove: function (map) {
9824 if (map._fadeAnimated) {
9825 setOpacity(this._container, 0);
9826 this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
9827 } else {
9828 remove(this._container);
9829 }
9830
9831 if (this.options.interactive) {
9832 removeClass(this._container, 'leaflet-interactive');
9833 this.removeInteractiveTarget(this._container);
9834 }
9835 },
9836
9837 // @namespace DivOverlay
9838 // @method getLatLng: LatLng
9839 // Returns the geographical point of the overlay.
9840 getLatLng: function () {
9841 return this._latlng;
9842 },
9843
9844 // @method setLatLng(latlng: LatLng): this
9845 // Sets the geographical point where the overlay will open.
9846 setLatLng: function (latlng) {
9847 this._latlng = toLatLng(latlng);
9848 if (this._map) {
9849 this._updatePosition();
9850 this._adjustPan();
9851 }
9852 return this;
9853 },
9854
9855 // @method getContent: String|HTMLElement
9856 // Returns the content of the overlay.
9857 getContent: function () {
9858 return this._content;
9859 },
9860
9861 // @method setContent(htmlContent: String|HTMLElement|Function): this
9862 // Sets the HTML content of the overlay. If a function is passed the source layer will be passed to the function.
9863 // The function should return a `String` or `HTMLElement` to be used in the overlay.
9864 setContent: function (content) {
9865 this._content = content;
9866 this.update();
9867 return this;
9868 },
9869
9870 // @method getElement: String|HTMLElement
9871 // Returns the HTML container of the overlay.
9872 getElement: function () {
9873 return this._container;
9874 },
9875
9876 // @method update: null
9877 // Updates the overlay content, layout and position. Useful for updating the overlay after something inside changed, e.g. image loaded.
9878 update: function () {
9879 if (!this._map) { return; }
9880
9881 this._container.style.visibility = 'hidden';
9882
9883 this._updateContent();
9884 this._updateLayout();
9885 this._updatePosition();
9886
9887 this._container.style.visibility = '';
9888
9889 this._adjustPan();
9890 },
9891
9892 getEvents: function () {
9893 var events = {
9894 zoom: this._updatePosition,
9895 viewreset: this._updatePosition
9896 };
9897
9898 if (this._zoomAnimated) {
9899 events.zoomanim = this._animateZoom;
9900 }
9901 return events;
9902 },
9903
9904 // @method isOpen: Boolean
9905 // Returns `true` when the overlay is visible on the map.
9906 isOpen: function () {
9907 return !!this._map && this._map.hasLayer(this);
9908 },
9909
9910 // @method bringToFront: this
9911 // Brings this overlay in front of other overlays (in the same map pane).
9912 bringToFront: function () {
9913 if (this._map) {
9914 toFront(this._container);
9915 }
9916 return this;
9917 },
9918
9919 // @method bringToBack: this
9920 // Brings this overlay to the back of other overlays (in the same map pane).
9921 bringToBack: function () {
9922 if (this._map) {
9923 toBack(this._container);
9924 }
9925 return this;
9926 },
9927
9928 // prepare bound overlay to open: update latlng pos / content source (for FeatureGroup)
9929 _prepareOpen: function (latlng) {
9930 var source = this._source;
9931 if (!source._map) { return false; }
9932
9933 if (source instanceof FeatureGroup) {
9934 source = null;
9935 var layers = this._source._layers;
9936 for (var id in layers) {
9937 if (layers[id]._map) {
9938 source = layers[id];
9939 break;
9940 }
9941 }
9942 if (!source) { return false; } // Unable to get source layer.
9943
9944 // set overlay source to this layer
9945 this._source = source;
9946 }
9947
9948 if (!latlng) {
9949 if (source.getCenter) {
9950 latlng = source.getCenter();
9951 } else if (source.getLatLng) {
9952 latlng = source.getLatLng();
9953 } else if (source.getBounds) {
9954 latlng = source.getBounds().getCenter();
9955 } else {
9956 throw new Error('Unable to get source layer LatLng.');
9957 }
9958 }
9959 this.setLatLng(latlng);
9960
9961 if (this._map) {
9962 // update the overlay (content, layout, etc...)
9963 this.update();
9964 }
9965
9966 return true;
9967 },
9968
9969 _updateContent: function () {
9970 if (!this._content) { return; }
9971
9972 var node = this._contentNode;
9973 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
9974
9975 if (typeof content === 'string') {
9976 node.innerHTML = content;
9977 } else {
9978 while (node.hasChildNodes()) {
9979 node.removeChild(node.firstChild);
9980 }
9981 node.appendChild(content);
9982 }
9983
9984 // @namespace DivOverlay
9985 // @section DivOverlay events
9986 // @event contentupdate: Event
9987 // Fired when the content of the overlay is updated
9988 this.fire('contentupdate');
9989 },
9990
9991 _updatePosition: function () {
9992 if (!this._map) { return; }
9993
9994 var pos = this._map.latLngToLayerPoint(this._latlng),
9995 offset = toPoint(this.options.offset),
9996 anchor = this._getAnchor();
9997
9998 if (this._zoomAnimated) {
9999 setPosition(this._container, pos.add(anchor));
10000 } else {
10001 offset = offset.add(pos).add(anchor);
10002 }
10003
10004 var bottom = this._containerBottom = -offset.y,
10005 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
10006
10007 // bottom position the overlay in case the height of the overlay changes (images loading etc)
10008 this._container.style.bottom = bottom + 'px';
10009 this._container.style.left = left + 'px';
10010 },
10011
10012 _getAnchor: function () {
10013 return [0, 0];
10014 }
10015
10016});
10017
10018Map.include({
10019 _initOverlay: function (OverlayClass, content, latlng, options) {
10020 var overlay = content;
10021 if (!(overlay instanceof OverlayClass)) {
10022 overlay = new OverlayClass(options).setContent(content);
10023 }
10024 if (latlng) {
10025 overlay.setLatLng(latlng);
10026 }
10027 return overlay;
10028 }
10029});
10030
10031
10032Layer.include({
10033 _initOverlay: function (OverlayClass, old, content, options) {
10034 var overlay = content;
10035 if (overlay instanceof OverlayClass) {
10036 setOptions(overlay, options);
10037 overlay._source = this;
10038 } else {
10039 overlay = (old && !options) ? old : new OverlayClass(options, this);
10040 overlay.setContent(content);
10041 }
10042 return overlay;
10043 }
10044});
10045
10046/*
10047 * @class Popup
10048 * @inherits DivOverlay
10049 * @aka L.Popup
10050 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
10051 * open popups while making sure that only one popup is open at one time
10052 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
10053 *
10054 * @example
10055 *
10056 * If you want to just bind a popup to marker click and then open it, it's really easy:
10057 *
10058 * ```js
10059 * marker.bindPopup(popupContent).openPopup();
10060 * ```
10061 * Path overlays like polylines also have a `bindPopup` method.
10062 *
10063 * A popup can be also standalone:
10064 *
10065 * ```js
10066 * var popup = L.popup()
10067 * .setLatLng(latlng)
10068 * .setContent('<p>Hello world!<br />This is a nice popup.</p>')
10069 * .openOn(map);
10070 * ```
10071 * or
10072 * ```js
10073 * var popup = L.popup(latlng, {content: '<p>Hello world!<br />This is a nice popup.</p>')
10074 * .openOn(map);
10075 * ```
10076 */
10077
10078
10079// @namespace Popup
10080var Popup = DivOverlay.extend({
10081
10082 // @section
10083 // @aka Popup options
10084 options: {
10085 // @option pane: String = 'popupPane'
10086 // `Map pane` where the popup will be added.
10087 pane: 'popupPane',
10088
10089 // @option offset: Point = Point(0, 7)
10090 // The offset of the popup position.
10091 offset: [0, 7],
10092
10093 // @option maxWidth: Number = 300
10094 // Max width of the popup, in pixels.
10095 maxWidth: 300,
10096
10097 // @option minWidth: Number = 50
10098 // Min width of the popup, in pixels.
10099 minWidth: 50,
10100
10101 // @option maxHeight: Number = null
10102 // If set, creates a scrollable container of the given height
10103 // inside a popup if its content exceeds it.
10104 // The scrollable container can be styled using the
10105 // `leaflet-popup-scrolled` CSS class selector.
10106 maxHeight: null,
10107
10108 // @option autoPan: Boolean = true
10109 // Set it to `false` if you don't want the map to do panning animation
10110 // to fit the opened popup.
10111 autoPan: true,
10112
10113 // @option autoPanPaddingTopLeft: Point = null
10114 // The margin between the popup and the top left corner of the map
10115 // view after autopanning was performed.
10116 autoPanPaddingTopLeft: null,
10117
10118 // @option autoPanPaddingBottomRight: Point = null
10119 // The margin between the popup and the bottom right corner of the map
10120 // view after autopanning was performed.
10121 autoPanPaddingBottomRight: null,
10122
10123 // @option autoPanPadding: Point = Point(5, 5)
10124 // Equivalent of setting both top left and bottom right autopan padding to the same value.
10125 autoPanPadding: [5, 5],
10126
10127 // @option keepInView: Boolean = false
10128 // Set it to `true` if you want to prevent users from panning the popup
10129 // off of the screen while it is open.
10130 keepInView: false,
10131
10132 // @option closeButton: Boolean = true
10133 // Controls the presence of a close button in the popup.
10134 closeButton: true,
10135
10136 // @option autoClose: Boolean = true
10137 // Set it to `false` if you want to override the default behavior of
10138 // the popup closing when another popup is opened.
10139 autoClose: true,
10140
10141 // @option closeOnEscapeKey: Boolean = true
10142 // Set it to `false` if you want to override the default behavior of
10143 // the ESC key for closing of the popup.
10144 closeOnEscapeKey: true,
10145
10146 // @option closeOnClick: Boolean = *
10147 // Set it if you want to override the default behavior of the popup closing when user clicks
10148 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
10149
10150 // @option className: String = ''
10151 // A custom CSS class name to assign to the popup.
10152 className: ''
10153 },
10154
10155 // @namespace Popup
10156 // @method openOn(map: Map): this
10157 // Alternative to `map.openPopup(popup)`.
10158 // Adds the popup to the map and closes the previous one.
10159 openOn: function (map) {
10160 map = arguments.length ? map : this._source._map; // experimental, not the part of public api
10161
10162 if (!map.hasLayer(this) && map._popup && map._popup.options.autoClose) {
10163 map.removeLayer(map._popup);
10164 }
10165 map._popup = this;
10166
10167 return DivOverlay.prototype.openOn.call(this, map);
10168 },
10169
10170 onAdd: function (map) {
10171 DivOverlay.prototype.onAdd.call(this, map);
10172
10173 // @namespace Map
10174 // @section Popup events
10175 // @event popupopen: PopupEvent
10176 // Fired when a popup is opened in the map
10177 map.fire('popupopen', {popup: this});
10178
10179 if (this._source) {
10180 // @namespace Layer
10181 // @section Popup events
10182 // @event popupopen: PopupEvent
10183 // Fired when a popup bound to this layer is opened
10184 this._source.fire('popupopen', {popup: this}, true);
10185 // For non-path layers, we toggle the popup when clicking
10186 // again the layer, so prevent the map to reopen it.
10187 if (!(this._source instanceof Path)) {
10188 this._source.on('preclick', stopPropagation);
10189 }
10190 }
10191 },
10192
10193 onRemove: function (map) {
10194 DivOverlay.prototype.onRemove.call(this, map);
10195
10196 // @namespace Map
10197 // @section Popup events
10198 // @event popupclose: PopupEvent
10199 // Fired when a popup in the map is closed
10200 map.fire('popupclose', {popup: this});
10201
10202 if (this._source) {
10203 // @namespace Layer
10204 // @section Popup events
10205 // @event popupclose: PopupEvent
10206 // Fired when a popup bound to this layer is closed
10207 this._source.fire('popupclose', {popup: this}, true);
10208 if (!(this._source instanceof Path)) {
10209 this._source.off('preclick', stopPropagation);
10210 }
10211 }
10212 },
10213
10214 getEvents: function () {
10215 var events = DivOverlay.prototype.getEvents.call(this);
10216
10217 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
10218 events.preclick = this.close;
10219 }
10220
10221 if (this.options.keepInView) {
10222 events.moveend = this._adjustPan;
10223 }
10224
10225 return events;
10226 },
10227
10228 _initLayout: function () {
10229 var prefix = 'leaflet-popup',
10230 container = this._container = create$1('div',
10231 prefix + ' ' + (this.options.className || '') +
10232 ' leaflet-zoom-animated');
10233
10234 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
10235 this._contentNode = create$1('div', prefix + '-content', wrapper);
10236
10237 disableClickPropagation(container);
10238 disableScrollPropagation(this._contentNode);
10239 on(container, 'contextmenu', stopPropagation);
10240
10241 this._tipContainer = create$1('div', prefix + '-tip-container', container);
10242 this._tip = create$1('div', prefix + '-tip', this._tipContainer);
10243
10244 if (this.options.closeButton) {
10245 var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
10246 closeButton.setAttribute('role', 'button'); // overrides the implicit role=link of <a> elements #7399
10247 closeButton.setAttribute('aria-label', 'Close popup');
10248 closeButton.href = '#close';
10249 closeButton.innerHTML = '<span aria-hidden="true">&#215;</span>';
10250
10251 on(closeButton, 'click', function (ev) {
10252 preventDefault(ev);
10253 this.close();
10254 }, this);
10255 }
10256 },
10257
10258 _updateLayout: function () {
10259 var container = this._contentNode,
10260 style = container.style;
10261
10262 style.width = '';
10263 style.whiteSpace = 'nowrap';
10264
10265 var width = container.offsetWidth;
10266 width = Math.min(width, this.options.maxWidth);
10267 width = Math.max(width, this.options.minWidth);
10268
10269 style.width = (width + 1) + 'px';
10270 style.whiteSpace = '';
10271
10272 style.height = '';
10273
10274 var height = container.offsetHeight,
10275 maxHeight = this.options.maxHeight,
10276 scrolledClass = 'leaflet-popup-scrolled';
10277
10278 if (maxHeight && height > maxHeight) {
10279 style.height = maxHeight + 'px';
10280 addClass(container, scrolledClass);
10281 } else {
10282 removeClass(container, scrolledClass);
10283 }
10284
10285 this._containerWidth = this._container.offsetWidth;
10286 },
10287
10288 _animateZoom: function (e) {
10289 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
10290 anchor = this._getAnchor();
10291 setPosition(this._container, pos.add(anchor));
10292 },
10293
10294 _adjustPan: function () {
10295 if (!this.options.autoPan) { return; }
10296 if (this._map._panAnim) { this._map._panAnim.stop(); }
10297
10298 // We can endlessly recurse if keepInView is set and the view resets.
10299 // Let's guard against that by exiting early if we're responding to our own autopan.
10300 if (this._autopanning) {
10301 this._autopanning = false;
10302 return;
10303 }
10304
10305 var map = this._map,
10306 marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
10307 containerHeight = this._container.offsetHeight + marginBottom,
10308 containerWidth = this._containerWidth,
10309 layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
10310
10311 layerPos._add(getPosition(this._container));
10312
10313 var containerPos = map.layerPointToContainerPoint(layerPos),
10314 padding = toPoint(this.options.autoPanPadding),
10315 paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
10316 paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
10317 size = map.getSize(),
10318 dx = 0,
10319 dy = 0;
10320
10321 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
10322 dx = containerPos.x + containerWidth - size.x + paddingBR.x;
10323 }
10324 if (containerPos.x - dx - paddingTL.x < 0) { // left
10325 dx = containerPos.x - paddingTL.x;
10326 }
10327 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
10328 dy = containerPos.y + containerHeight - size.y + paddingBR.y;
10329 }
10330 if (containerPos.y - dy - paddingTL.y < 0) { // top
10331 dy = containerPos.y - paddingTL.y;
10332 }
10333
10334 // @namespace Map
10335 // @section Popup events
10336 // @event autopanstart: Event
10337 // Fired when the map starts autopanning when opening a popup.
10338 if (dx || dy) {
10339 // Track that we're autopanning, as this function will be re-ran on moveend
10340 if (this.options.keepInView) {
10341 this._autopanning = true;
10342 }
10343
10344 map
10345 .fire('autopanstart')
10346 .panBy([dx, dy]);
10347 }
10348 },
10349
10350 _getAnchor: function () {
10351 // Where should we anchor the popup on the source layer?
10352 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
10353 }
10354
10355});
10356
10357// @namespace Popup
10358// @factory L.popup(options?: Popup options, source?: Layer)
10359// 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.
10360// @alternative
10361// @factory L.popup(latlng: LatLng, options?: Popup options)
10362// Instantiates a `Popup` object given `latlng` where the popup will open and an optional `options` object that describes its appearance and location.
10363var popup = function (options, source) {
10364 return new Popup(options, source);
10365};
10366
10367
10368/* @namespace Map
10369 * @section Interaction Options
10370 * @option closePopupOnClick: Boolean = true
10371 * Set it to `false` if you don't want popups to close when user clicks the map.
10372 */
10373Map.mergeOptions({
10374 closePopupOnClick: true
10375});
10376
10377
10378// @namespace Map
10379// @section Methods for Layers and Controls
10380Map.include({
10381 // @method openPopup(popup: Popup): this
10382 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
10383 // @alternative
10384 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
10385 // Creates a popup with the specified content and options and opens it in the given point on a map.
10386 openPopup: function (popup, latlng, options) {
10387 this._initOverlay(Popup, popup, latlng, options)
10388 .openOn(this);
10389
10390 return this;
10391 },
10392
10393 // @method closePopup(popup?: Popup): this
10394 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
10395 closePopup: function (popup) {
10396 popup = arguments.length ? popup : this._popup;
10397 if (popup) {
10398 popup.close();
10399 }
10400 return this;
10401 }
10402});
10403
10404/*
10405 * @namespace Layer
10406 * @section Popup methods example
10407 *
10408 * All layers share a set of methods convenient for binding popups to it.
10409 *
10410 * ```js
10411 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
10412 * layer.openPopup();
10413 * layer.closePopup();
10414 * ```
10415 *
10416 * 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.
10417 */
10418
10419// @section Popup methods
10420Layer.include({
10421
10422 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
10423 // Binds a popup to the layer with the passed `content` and sets up the
10424 // necessary event listeners. If a `Function` is passed it will receive
10425 // the layer as the first argument and should return a `String` or `HTMLElement`.
10426 bindPopup: function (content, options) {
10427 this._popup = this._initOverlay(Popup, this._popup, content, options);
10428 if (!this._popupHandlersAdded) {
10429 this.on({
10430 click: this._openPopup,
10431 keypress: this._onKeyPress,
10432 remove: this.closePopup,
10433 move: this._movePopup
10434 });
10435 this._popupHandlersAdded = true;
10436 }
10437
10438 return this;
10439 },
10440
10441 // @method unbindPopup(): this
10442 // Removes the popup previously bound with `bindPopup`.
10443 unbindPopup: function () {
10444 if (this._popup) {
10445 this.off({
10446 click: this._openPopup,
10447 keypress: this._onKeyPress,
10448 remove: this.closePopup,
10449 move: this._movePopup
10450 });
10451 this._popupHandlersAdded = false;
10452 this._popup = null;
10453 }
10454 return this;
10455 },
10456
10457 // @method openPopup(latlng?: LatLng): this
10458 // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
10459 openPopup: function (latlng) {
10460 if (this._popup) {
10461 if (!(this instanceof FeatureGroup)) {
10462 this._popup._source = this;
10463 }
10464 if (this._popup._prepareOpen(latlng || this._latlng)) {
10465 // open the popup on the map
10466 this._popup.openOn(this._map);
10467 }
10468 }
10469 return this;
10470 },
10471
10472 // @method closePopup(): this
10473 // Closes the popup bound to this layer if it is open.
10474 closePopup: function () {
10475 if (this._popup) {
10476 this._popup.close();
10477 }
10478 return this;
10479 },
10480
10481 // @method togglePopup(): this
10482 // Opens or closes the popup bound to this layer depending on its current state.
10483 togglePopup: function () {
10484 if (this._popup) {
10485 this._popup.toggle(this);
10486 }
10487 return this;
10488 },
10489
10490 // @method isPopupOpen(): boolean
10491 // Returns `true` if the popup bound to this layer is currently open.
10492 isPopupOpen: function () {
10493 return (this._popup ? this._popup.isOpen() : false);
10494 },
10495
10496 // @method setPopupContent(content: String|HTMLElement|Popup): this
10497 // Sets the content of the popup bound to this layer.
10498 setPopupContent: function (content) {
10499 if (this._popup) {
10500 this._popup.setContent(content);
10501 }
10502 return this;
10503 },
10504
10505 // @method getPopup(): Popup
10506 // Returns the popup bound to this layer.
10507 getPopup: function () {
10508 return this._popup;
10509 },
10510
10511 _openPopup: function (e) {
10512 if (!this._popup || !this._map) {
10513 return;
10514 }
10515 // prevent map click
10516 stop(e);
10517
10518 var target = e.layer || e.target;
10519 if (this._popup._source === target && !(target instanceof Path)) {
10520 // treat it like a marker and figure out
10521 // if we should toggle it open/closed
10522 if (this._map.hasLayer(this._popup)) {
10523 this.closePopup();
10524 } else {
10525 this.openPopup(e.latlng);
10526 }
10527 return;
10528 }
10529 this._popup._source = target;
10530 this.openPopup(e.latlng);
10531 },
10532
10533 _movePopup: function (e) {
10534 this._popup.setLatLng(e.latlng);
10535 },
10536
10537 _onKeyPress: function (e) {
10538 if (e.originalEvent.keyCode === 13) {
10539 this._openPopup(e);
10540 }
10541 }
10542});
10543
10544/*
10545 * @class Tooltip
10546 * @inherits DivOverlay
10547 * @aka L.Tooltip
10548 * Used to display small texts on top of map layers.
10549 *
10550 * @example
10551 * If you want to just bind a tooltip to marker:
10552 *
10553 * ```js
10554 * marker.bindTooltip("my tooltip text").openTooltip();
10555 * ```
10556 * Path overlays like polylines also have a `bindTooltip` method.
10557 *
10558 * A tooltip can be also standalone:
10559 *
10560 * ```js
10561 * var tooltip = L.tooltip()
10562 * .setLatLng(latlng)
10563 * .setContent('Hello world!<br />This is a nice tooltip.')
10564 * .addTo(map);
10565 * ```
10566 * or
10567 * ```js
10568 * var tooltip = L.tooltip(latlng, {content: 'Hello world!<br />This is a nice tooltip.'})
10569 * .addTo(map);
10570 * ```
10571 *
10572 *
10573 * Note about tooltip offset. Leaflet takes two options in consideration
10574 * for computing tooltip offsetting:
10575 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
10576 * Add a positive x offset to move the tooltip to the right, and a positive y offset to
10577 * move it to the bottom. Negatives will move to the left and top.
10578 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
10579 * should adapt this value if you use a custom icon.
10580 */
10581
10582
10583// @namespace Tooltip
10584var Tooltip = DivOverlay.extend({
10585
10586 // @section
10587 // @aka Tooltip options
10588 options: {
10589 // @option pane: String = 'tooltipPane'
10590 // `Map pane` where the tooltip will be added.
10591 pane: 'tooltipPane',
10592
10593 // @option offset: Point = Point(0, 0)
10594 // Optional offset of the tooltip position.
10595 offset: [0, 0],
10596
10597 // @option direction: String = 'auto'
10598 // Direction where to open the tooltip. Possible values are: `right`, `left`,
10599 // `top`, `bottom`, `center`, `auto`.
10600 // `auto` will dynamically switch between `right` and `left` according to the tooltip
10601 // position on the map.
10602 direction: 'auto',
10603
10604 // @option permanent: Boolean = false
10605 // Whether to open the tooltip permanently or only on mouseover.
10606 permanent: false,
10607
10608 // @option sticky: Boolean = false
10609 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
10610 sticky: false,
10611
10612 // @option opacity: Number = 0.9
10613 // Tooltip container opacity.
10614 opacity: 0.9
10615 },
10616
10617 onAdd: function (map) {
10618 DivOverlay.prototype.onAdd.call(this, map);
10619 this.setOpacity(this.options.opacity);
10620
10621 // @namespace Map
10622 // @section Tooltip events
10623 // @event tooltipopen: TooltipEvent
10624 // Fired when a tooltip is opened in the map.
10625 map.fire('tooltipopen', {tooltip: this});
10626
10627 if (this._source) {
10628 this.addEventParent(this._source);
10629
10630 // @namespace Layer
10631 // @section Tooltip events
10632 // @event tooltipopen: TooltipEvent
10633 // Fired when a tooltip bound to this layer is opened.
10634 this._source.fire('tooltipopen', {tooltip: this}, true);
10635 }
10636 },
10637
10638 onRemove: function (map) {
10639 DivOverlay.prototype.onRemove.call(this, map);
10640
10641 // @namespace Map
10642 // @section Tooltip events
10643 // @event tooltipclose: TooltipEvent
10644 // Fired when a tooltip in the map is closed.
10645 map.fire('tooltipclose', {tooltip: this});
10646
10647 if (this._source) {
10648 this.removeEventParent(this._source);
10649
10650 // @namespace Layer
10651 // @section Tooltip events
10652 // @event tooltipclose: TooltipEvent
10653 // Fired when a tooltip bound to this layer is closed.
10654 this._source.fire('tooltipclose', {tooltip: this}, true);
10655 }
10656 },
10657
10658 getEvents: function () {
10659 var events = DivOverlay.prototype.getEvents.call(this);
10660
10661 if (!this.options.permanent) {
10662 events.preclick = this.close;
10663 }
10664
10665 return events;
10666 },
10667
10668 _initLayout: function () {
10669 var prefix = 'leaflet-tooltip',
10670 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
10671
10672 this._contentNode = this._container = create$1('div', className);
10673
10674 this._container.setAttribute('role', 'tooltip');
10675 this._container.setAttribute('id', 'leaflet-tooltip-' + stamp(this));
10676 },
10677
10678 _updateLayout: function () {},
10679
10680 _adjustPan: function () {},
10681
10682 _setPosition: function (pos) {
10683 var subX, subY,
10684 map = this._map,
10685 container = this._container,
10686 centerPoint = map.latLngToContainerPoint(map.getCenter()),
10687 tooltipPoint = map.layerPointToContainerPoint(pos),
10688 direction = this.options.direction,
10689 tooltipWidth = container.offsetWidth,
10690 tooltipHeight = container.offsetHeight,
10691 offset = toPoint(this.options.offset),
10692 anchor = this._getAnchor();
10693
10694 if (direction === 'top') {
10695 subX = tooltipWidth / 2;
10696 subY = tooltipHeight;
10697 } else if (direction === 'bottom') {
10698 subX = tooltipWidth / 2;
10699 subY = 0;
10700 } else if (direction === 'center') {
10701 subX = tooltipWidth / 2;
10702 subY = tooltipHeight / 2;
10703 } else if (direction === 'right') {
10704 subX = 0;
10705 subY = tooltipHeight / 2;
10706 } else if (direction === 'left') {
10707 subX = tooltipWidth;
10708 subY = tooltipHeight / 2;
10709 } else if (tooltipPoint.x < centerPoint.x) {
10710 direction = 'right';
10711 subX = 0;
10712 subY = tooltipHeight / 2;
10713 } else {
10714 direction = 'left';
10715 subX = tooltipWidth + (offset.x + anchor.x) * 2;
10716 subY = tooltipHeight / 2;
10717 }
10718
10719 pos = pos.subtract(toPoint(subX, subY, true)).add(offset).add(anchor);
10720
10721 removeClass(container, 'leaflet-tooltip-right');
10722 removeClass(container, 'leaflet-tooltip-left');
10723 removeClass(container, 'leaflet-tooltip-top');
10724 removeClass(container, 'leaflet-tooltip-bottom');
10725 addClass(container, 'leaflet-tooltip-' + direction);
10726 setPosition(container, pos);
10727 },
10728
10729 _updatePosition: function () {
10730 var pos = this._map.latLngToLayerPoint(this._latlng);
10731 this._setPosition(pos);
10732 },
10733
10734 setOpacity: function (opacity) {
10735 this.options.opacity = opacity;
10736
10737 if (this._container) {
10738 setOpacity(this._container, opacity);
10739 }
10740 },
10741
10742 _animateZoom: function (e) {
10743 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
10744 this._setPosition(pos);
10745 },
10746
10747 _getAnchor: function () {
10748 // Where should we anchor the tooltip on the source layer?
10749 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
10750 }
10751
10752});
10753
10754// @namespace Tooltip
10755// @factory L.tooltip(options?: Tooltip options, source?: Layer)
10756// 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.
10757// @alternative
10758// @factory L.tooltip(latlng: LatLng, options?: Tooltip options)
10759// Instantiates a `Tooltip` object given `latlng` where the tooltip will open and an optional `options` object that describes its appearance and location.
10760var tooltip = function (options, source) {
10761 return new Tooltip(options, source);
10762};
10763
10764// @namespace Map
10765// @section Methods for Layers and Controls
10766Map.include({
10767
10768 // @method openTooltip(tooltip: Tooltip): this
10769 // Opens the specified tooltip.
10770 // @alternative
10771 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
10772 // Creates a tooltip with the specified content and options and open it.
10773 openTooltip: function (tooltip, latlng, options) {
10774 this._initOverlay(Tooltip, tooltip, latlng, options)
10775 .openOn(this);
10776
10777 return this;
10778 },
10779
10780 // @method closeTooltip(tooltip: Tooltip): this
10781 // Closes the tooltip given as parameter.
10782 closeTooltip: function (tooltip) {
10783 tooltip.close();
10784 return this;
10785 }
10786
10787});
10788
10789/*
10790 * @namespace Layer
10791 * @section Tooltip methods example
10792 *
10793 * All layers share a set of methods convenient for binding tooltips to it.
10794 *
10795 * ```js
10796 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
10797 * layer.openTooltip();
10798 * layer.closeTooltip();
10799 * ```
10800 */
10801
10802// @section Tooltip methods
10803Layer.include({
10804
10805 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
10806 // Binds a tooltip to the layer with the passed `content` and sets up the
10807 // necessary event listeners. If a `Function` is passed it will receive
10808 // the layer as the first argument and should return a `String` or `HTMLElement`.
10809 bindTooltip: function (content, options) {
10810
10811 if (this._tooltip && this.isTooltipOpen()) {
10812 this.unbindTooltip();
10813 }
10814
10815 this._tooltip = this._initOverlay(Tooltip, this._tooltip, content, options);
10816 this._initTooltipInteractions();
10817
10818 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
10819 this.openTooltip();
10820 }
10821
10822 return this;
10823 },
10824
10825 // @method unbindTooltip(): this
10826 // Removes the tooltip previously bound with `bindTooltip`.
10827 unbindTooltip: function () {
10828 if (this._tooltip) {
10829 this._initTooltipInteractions(true);
10830 this.closeTooltip();
10831 this._tooltip = null;
10832 }
10833 return this;
10834 },
10835
10836 _initTooltipInteractions: function (remove) {
10837 if (!remove && this._tooltipHandlersAdded) { return; }
10838 var onOff = remove ? 'off' : 'on',
10839 events = {
10840 remove: this.closeTooltip,
10841 move: this._moveTooltip
10842 };
10843 if (!this._tooltip.options.permanent) {
10844 events.mouseover = this._openTooltip;
10845 events.mouseout = this.closeTooltip;
10846 events.click = this._openTooltip;
10847 if (this._map) {
10848 this._addFocusListeners();
10849 } else {
10850 events.add = this._addFocusListeners;
10851 }
10852 } else {
10853 events.add = this._openTooltip;
10854 }
10855 if (this._tooltip.options.sticky) {
10856 events.mousemove = this._moveTooltip;
10857 }
10858 this[onOff](events);
10859 this._tooltipHandlersAdded = !remove;
10860 },
10861
10862 // @method openTooltip(latlng?: LatLng): this
10863 // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
10864 openTooltip: function (latlng) {
10865 if (this._tooltip) {
10866 if (!(this instanceof FeatureGroup)) {
10867 this._tooltip._source = this;
10868 }
10869 if (this._tooltip._prepareOpen(latlng)) {
10870 // open the tooltip on the map
10871 this._tooltip.openOn(this._map);
10872
10873 if (this.getElement) {
10874 this._setAriaDescribedByOnLayer(this);
10875 } else if (this.eachLayer) {
10876 this.eachLayer(this._setAriaDescribedByOnLayer, this);
10877 }
10878 }
10879 }
10880 return this;
10881 },
10882
10883 // @method closeTooltip(): this
10884 // Closes the tooltip bound to this layer if it is open.
10885 closeTooltip: function () {
10886 if (this._tooltip) {
10887 return this._tooltip.close();
10888 }
10889 },
10890
10891 // @method toggleTooltip(): this
10892 // Opens or closes the tooltip bound to this layer depending on its current state.
10893 toggleTooltip: function () {
10894 if (this._tooltip) {
10895 this._tooltip.toggle(this);
10896 }
10897 return this;
10898 },
10899
10900 // @method isTooltipOpen(): boolean
10901 // Returns `true` if the tooltip bound to this layer is currently open.
10902 isTooltipOpen: function () {
10903 return this._tooltip.isOpen();
10904 },
10905
10906 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10907 // Sets the content of the tooltip bound to this layer.
10908 setTooltipContent: function (content) {
10909 if (this._tooltip) {
10910 this._tooltip.setContent(content);
10911 }
10912 return this;
10913 },
10914
10915 // @method getTooltip(): Tooltip
10916 // Returns the tooltip bound to this layer.
10917 getTooltip: function () {
10918 return this._tooltip;
10919 },
10920
10921 _addFocusListeners: function () {
10922 if (this.getElement) {
10923 this._addFocusListenersOnLayer(this);
10924 } else if (this.eachLayer) {
10925 this.eachLayer(this._addFocusListenersOnLayer, this);
10926 }
10927 },
10928
10929 _addFocusListenersOnLayer: function (layer) {
10930 var el = layer.getElement();
10931 if (el) {
10932 on(el, 'focus', function () {
10933 this._tooltip._source = layer;
10934 this.openTooltip();
10935 }, this);
10936 on(el, 'blur', this.closeTooltip, this);
10937 }
10938 },
10939
10940 _setAriaDescribedByOnLayer: function (layer) {
10941 var el = layer.getElement();
10942 if (el) {
10943 el.setAttribute('aria-describedby', this._tooltip._container.id);
10944 }
10945 },
10946
10947
10948 _openTooltip: function (e) {
10949 if (!this._tooltip || !this._map || (this._map.dragging && this._map.dragging.moving())) {
10950 return;
10951 }
10952 this._tooltip._source = e.layer || e.target;
10953
10954 this.openTooltip(this._tooltip.options.sticky ? e.latlng : undefined);
10955 },
10956
10957 _moveTooltip: function (e) {
10958 var latlng = e.latlng, containerPoint, layerPoint;
10959 if (this._tooltip.options.sticky && e.originalEvent) {
10960 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
10961 layerPoint = this._map.containerPointToLayerPoint(containerPoint);
10962 latlng = this._map.layerPointToLatLng(layerPoint);
10963 }
10964 this._tooltip.setLatLng(latlng);
10965 }
10966});
10967
10968/*
10969 * @class DivIcon
10970 * @aka L.DivIcon
10971 * @inherits Icon
10972 *
10973 * Represents a lightweight icon for markers that uses a simple `<div>`
10974 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
10975 *
10976 * @example
10977 * ```js
10978 * var myIcon = L.divIcon({className: 'my-div-icon'});
10979 * // you can set .my-div-icon styles in CSS
10980 *
10981 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
10982 * ```
10983 *
10984 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
10985 */
10986
10987var DivIcon = Icon.extend({
10988 options: {
10989 // @section
10990 // @aka DivIcon options
10991 iconSize: [12, 12], // also can be set through CSS
10992
10993 // iconAnchor: (Point),
10994 // popupAnchor: (Point),
10995
10996 // @option html: String|HTMLElement = ''
10997 // Custom HTML code to put inside the div element, empty by default. Alternatively,
10998 // an instance of `HTMLElement`.
10999 html: false,
11000
11001 // @option bgPos: Point = [0, 0]
11002 // Optional relative position of the background, in pixels
11003 bgPos: null,
11004
11005 className: 'leaflet-div-icon'
11006 },
11007
11008 createIcon: function (oldIcon) {
11009 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
11010 options = this.options;
11011
11012 if (options.html instanceof Element) {
11013 empty(div);
11014 div.appendChild(options.html);
11015 } else {
11016 div.innerHTML = options.html !== false ? options.html : '';
11017 }
11018
11019 if (options.bgPos) {
11020 var bgPos = toPoint(options.bgPos);
11021 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
11022 }
11023 this._setIconStyles(div, 'icon');
11024
11025 return div;
11026 },
11027
11028 createShadow: function () {
11029 return null;
11030 }
11031});
11032
11033// @factory L.divIcon(options: DivIcon options)
11034// Creates a `DivIcon` instance with the given options.
11035function divIcon(options) {
11036 return new DivIcon(options);
11037}
11038
11039Icon.Default = IconDefault;
11040
11041/*
11042 * @class GridLayer
11043 * @inherits Layer
11044 * @aka L.GridLayer
11045 *
11046 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
11047 * 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.
11048 *
11049 *
11050 * @section Synchronous usage
11051 * @example
11052 *
11053 * 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.
11054 *
11055 * ```js
11056 * var CanvasLayer = L.GridLayer.extend({
11057 * createTile: function(coords){
11058 * // create a <canvas> element for drawing
11059 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
11060 *
11061 * // setup tile width and height according to the options
11062 * var size = this.getTileSize();
11063 * tile.width = size.x;
11064 * tile.height = size.y;
11065 *
11066 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z
11067 * var ctx = tile.getContext('2d');
11068 *
11069 * // return the tile so it can be rendered on screen
11070 * return tile;
11071 * }
11072 * });
11073 * ```
11074 *
11075 * @section Asynchronous usage
11076 * @example
11077 *
11078 * 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.
11079 *
11080 * ```js
11081 * var CanvasLayer = L.GridLayer.extend({
11082 * createTile: function(coords, done){
11083 * var error;
11084 *
11085 * // create a <canvas> element for drawing
11086 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
11087 *
11088 * // setup tile width and height according to the options
11089 * var size = this.getTileSize();
11090 * tile.width = size.x;
11091 * tile.height = size.y;
11092 *
11093 * // draw something asynchronously and pass the tile to the done() callback
11094 * setTimeout(function() {
11095 * done(error, tile);
11096 * }, 1000);
11097 *
11098 * return tile;
11099 * }
11100 * });
11101 * ```
11102 *
11103 * @section
11104 */
11105
11106
11107var GridLayer = Layer.extend({
11108
11109 // @section
11110 // @aka GridLayer options
11111 options: {
11112 // @option tileSize: Number|Point = 256
11113 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
11114 tileSize: 256,
11115
11116 // @option opacity: Number = 1.0
11117 // Opacity of the tiles. Can be used in the `createTile()` function.
11118 opacity: 1,
11119
11120 // @option updateWhenIdle: Boolean = (depends)
11121 // Load new tiles only when panning ends.
11122 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
11123 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
11124 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
11125 updateWhenIdle: Browser.mobile,
11126
11127 // @option updateWhenZooming: Boolean = true
11128 // 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.
11129 updateWhenZooming: true,
11130
11131 // @option updateInterval: Number = 200
11132 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
11133 updateInterval: 200,
11134
11135 // @option zIndex: Number = 1
11136 // The explicit zIndex of the tile layer.
11137 zIndex: 1,
11138
11139 // @option bounds: LatLngBounds = undefined
11140 // If set, tiles will only be loaded inside the set `LatLngBounds`.
11141 bounds: null,
11142
11143 // @option minZoom: Number = 0
11144 // The minimum zoom level down to which this layer will be displayed (inclusive).
11145 minZoom: 0,
11146
11147 // @option maxZoom: Number = undefined
11148 // The maximum zoom level up to which this layer will be displayed (inclusive).
11149 maxZoom: undefined,
11150
11151 // @option maxNativeZoom: Number = undefined
11152 // Maximum zoom number the tile source has available. If it is specified,
11153 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
11154 // from `maxNativeZoom` level and auto-scaled.
11155 maxNativeZoom: undefined,
11156
11157 // @option minNativeZoom: Number = undefined
11158 // Minimum zoom number the tile source has available. If it is specified,
11159 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
11160 // from `minNativeZoom` level and auto-scaled.
11161 minNativeZoom: undefined,
11162
11163 // @option noWrap: Boolean = false
11164 // Whether the layer is wrapped around the antimeridian. If `true`, the
11165 // GridLayer will only be displayed once at low zoom levels. Has no
11166 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
11167 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
11168 // tiles outside the CRS limits.
11169 noWrap: false,
11170
11171 // @option pane: String = 'tilePane'
11172 // `Map pane` where the grid layer will be added.
11173 pane: 'tilePane',
11174
11175 // @option className: String = ''
11176 // A custom class name to assign to the tile layer. Empty by default.
11177 className: '',
11178
11179 // @option keepBuffer: Number = 2
11180 // When panning the map, keep this many rows and columns of tiles before unloading them.
11181 keepBuffer: 2
11182 },
11183
11184 initialize: function (options) {
11185 setOptions(this, options);
11186 },
11187
11188 onAdd: function () {
11189 this._initContainer();
11190
11191 this._levels = {};
11192 this._tiles = {};
11193
11194 this._resetView(); // implicit _update() call
11195 },
11196
11197 beforeAdd: function (map) {
11198 map._addZoomLimit(this);
11199 },
11200
11201 onRemove: function (map) {
11202 this._removeAllTiles();
11203 remove(this._container);
11204 map._removeZoomLimit(this);
11205 this._container = null;
11206 this._tileZoom = undefined;
11207 },
11208
11209 // @method bringToFront: this
11210 // Brings the tile layer to the top of all tile layers.
11211 bringToFront: function () {
11212 if (this._map) {
11213 toFront(this._container);
11214 this._setAutoZIndex(Math.max);
11215 }
11216 return this;
11217 },
11218
11219 // @method bringToBack: this
11220 // Brings the tile layer to the bottom of all tile layers.
11221 bringToBack: function () {
11222 if (this._map) {
11223 toBack(this._container);
11224 this._setAutoZIndex(Math.min);
11225 }
11226 return this;
11227 },
11228
11229 // @method getContainer: HTMLElement
11230 // Returns the HTML element that contains the tiles for this layer.
11231 getContainer: function () {
11232 return this._container;
11233 },
11234
11235 // @method setOpacity(opacity: Number): this
11236 // Changes the [opacity](#gridlayer-opacity) of the grid layer.
11237 setOpacity: function (opacity) {
11238 this.options.opacity = opacity;
11239 this._updateOpacity();
11240 return this;
11241 },
11242
11243 // @method setZIndex(zIndex: Number): this
11244 // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
11245 setZIndex: function (zIndex) {
11246 this.options.zIndex = zIndex;
11247 this._updateZIndex();
11248
11249 return this;
11250 },
11251
11252 // @method isLoading: Boolean
11253 // Returns `true` if any tile in the grid layer has not finished loading.
11254 isLoading: function () {
11255 return this._loading;
11256 },
11257
11258 // @method redraw: this
11259 // Causes the layer to clear all the tiles and request them again.
11260 redraw: function () {
11261 if (this._map) {
11262 this._removeAllTiles();
11263 var tileZoom = this._clampZoom(this._map.getZoom());
11264 if (tileZoom !== this._tileZoom) {
11265 this._tileZoom = tileZoom;
11266 this._updateLevels();
11267 }
11268 this._update();
11269 }
11270 return this;
11271 },
11272
11273 getEvents: function () {
11274 var events = {
11275 viewprereset: this._invalidateAll,
11276 viewreset: this._resetView,
11277 zoom: this._resetView,
11278 moveend: this._onMoveEnd
11279 };
11280
11281 if (!this.options.updateWhenIdle) {
11282 // update tiles on move, but not more often than once per given interval
11283 if (!this._onMove) {
11284 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
11285 }
11286
11287 events.move = this._onMove;
11288 }
11289
11290 if (this._zoomAnimated) {
11291 events.zoomanim = this._animateZoom;
11292 }
11293
11294 return events;
11295 },
11296
11297 // @section Extension methods
11298 // Layers extending `GridLayer` shall reimplement the following method.
11299 // @method createTile(coords: Object, done?: Function): HTMLElement
11300 // Called only internally, must be overridden by classes extending `GridLayer`.
11301 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
11302 // is specified, it must be called when the tile has finished loading and drawing.
11303 createTile: function () {
11304 return document.createElement('div');
11305 },
11306
11307 // @section
11308 // @method getTileSize: Point
11309 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
11310 getTileSize: function () {
11311 var s = this.options.tileSize;
11312 return s instanceof Point ? s : new Point(s, s);
11313 },
11314
11315 _updateZIndex: function () {
11316 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
11317 this._container.style.zIndex = this.options.zIndex;
11318 }
11319 },
11320
11321 _setAutoZIndex: function (compare) {
11322 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
11323
11324 var layers = this.getPane().children,
11325 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
11326
11327 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
11328
11329 zIndex = layers[i].style.zIndex;
11330
11331 if (layers[i] !== this._container && zIndex) {
11332 edgeZIndex = compare(edgeZIndex, +zIndex);
11333 }
11334 }
11335
11336 if (isFinite(edgeZIndex)) {
11337 this.options.zIndex = edgeZIndex + compare(-1, 1);
11338 this._updateZIndex();
11339 }
11340 },
11341
11342 _updateOpacity: function () {
11343 if (!this._map) { return; }
11344
11345 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
11346 if (Browser.ielt9) { return; }
11347
11348 setOpacity(this._container, this.options.opacity);
11349
11350 var now = +new Date(),
11351 nextFrame = false,
11352 willPrune = false;
11353
11354 for (var key in this._tiles) {
11355 var tile = this._tiles[key];
11356 if (!tile.current || !tile.loaded) { continue; }
11357
11358 var fade = Math.min(1, (now - tile.loaded) / 200);
11359
11360 setOpacity(tile.el, fade);
11361 if (fade < 1) {
11362 nextFrame = true;
11363 } else {
11364 if (tile.active) {
11365 willPrune = true;
11366 } else {
11367 this._onOpaqueTile(tile);
11368 }
11369 tile.active = true;
11370 }
11371 }
11372
11373 if (willPrune && !this._noPrune) { this._pruneTiles(); }
11374
11375 if (nextFrame) {
11376 cancelAnimFrame(this._fadeFrame);
11377 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11378 }
11379 },
11380
11381 _onOpaqueTile: falseFn,
11382
11383 _initContainer: function () {
11384 if (this._container) { return; }
11385
11386 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
11387 this._updateZIndex();
11388
11389 if (this.options.opacity < 1) {
11390 this._updateOpacity();
11391 }
11392
11393 this.getPane().appendChild(this._container);
11394 },
11395
11396 _updateLevels: function () {
11397
11398 var zoom = this._tileZoom,
11399 maxZoom = this.options.maxZoom;
11400
11401 if (zoom === undefined) { return undefined; }
11402
11403 for (var z in this._levels) {
11404 z = Number(z);
11405 if (this._levels[z].el.children.length || z === zoom) {
11406 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
11407 this._onUpdateLevel(z);
11408 } else {
11409 remove(this._levels[z].el);
11410 this._removeTilesAtZoom(z);
11411 this._onRemoveLevel(z);
11412 delete this._levels[z];
11413 }
11414 }
11415
11416 var level = this._levels[zoom],
11417 map = this._map;
11418
11419 if (!level) {
11420 level = this._levels[zoom] = {};
11421
11422 level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
11423 level.el.style.zIndex = maxZoom;
11424
11425 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
11426 level.zoom = zoom;
11427
11428 this._setZoomTransform(level, map.getCenter(), map.getZoom());
11429
11430 // force the browser to consider the newly added element for transition
11431 falseFn(level.el.offsetWidth);
11432
11433 this._onCreateLevel(level);
11434 }
11435
11436 this._level = level;
11437
11438 return level;
11439 },
11440
11441 _onUpdateLevel: falseFn,
11442
11443 _onRemoveLevel: falseFn,
11444
11445 _onCreateLevel: falseFn,
11446
11447 _pruneTiles: function () {
11448 if (!this._map) {
11449 return;
11450 }
11451
11452 var key, tile;
11453
11454 var zoom = this._map.getZoom();
11455 if (zoom > this.options.maxZoom ||
11456 zoom < this.options.minZoom) {
11457 this._removeAllTiles();
11458 return;
11459 }
11460
11461 for (key in this._tiles) {
11462 tile = this._tiles[key];
11463 tile.retain = tile.current;
11464 }
11465
11466 for (key in this._tiles) {
11467 tile = this._tiles[key];
11468 if (tile.current && !tile.active) {
11469 var coords = tile.coords;
11470 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
11471 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
11472 }
11473 }
11474 }
11475
11476 for (key in this._tiles) {
11477 if (!this._tiles[key].retain) {
11478 this._removeTile(key);
11479 }
11480 }
11481 },
11482
11483 _removeTilesAtZoom: function (zoom) {
11484 for (var key in this._tiles) {
11485 if (this._tiles[key].coords.z !== zoom) {
11486 continue;
11487 }
11488 this._removeTile(key);
11489 }
11490 },
11491
11492 _removeAllTiles: function () {
11493 for (var key in this._tiles) {
11494 this._removeTile(key);
11495 }
11496 },
11497
11498 _invalidateAll: function () {
11499 for (var z in this._levels) {
11500 remove(this._levels[z].el);
11501 this._onRemoveLevel(Number(z));
11502 delete this._levels[z];
11503 }
11504 this._removeAllTiles();
11505
11506 this._tileZoom = undefined;
11507 },
11508
11509 _retainParent: function (x, y, z, minZoom) {
11510 var x2 = Math.floor(x / 2),
11511 y2 = Math.floor(y / 2),
11512 z2 = z - 1,
11513 coords2 = new Point(+x2, +y2);
11514 coords2.z = +z2;
11515
11516 var key = this._tileCoordsToKey(coords2),
11517 tile = this._tiles[key];
11518
11519 if (tile && tile.active) {
11520 tile.retain = true;
11521 return true;
11522
11523 } else if (tile && tile.loaded) {
11524 tile.retain = true;
11525 }
11526
11527 if (z2 > minZoom) {
11528 return this._retainParent(x2, y2, z2, minZoom);
11529 }
11530
11531 return false;
11532 },
11533
11534 _retainChildren: function (x, y, z, maxZoom) {
11535
11536 for (var i = 2 * x; i < 2 * x + 2; i++) {
11537 for (var j = 2 * y; j < 2 * y + 2; j++) {
11538
11539 var coords = new Point(i, j);
11540 coords.z = z + 1;
11541
11542 var key = this._tileCoordsToKey(coords),
11543 tile = this._tiles[key];
11544
11545 if (tile && tile.active) {
11546 tile.retain = true;
11547 continue;
11548
11549 } else if (tile && tile.loaded) {
11550 tile.retain = true;
11551 }
11552
11553 if (z + 1 < maxZoom) {
11554 this._retainChildren(i, j, z + 1, maxZoom);
11555 }
11556 }
11557 }
11558 },
11559
11560 _resetView: function (e) {
11561 var animating = e && (e.pinch || e.flyTo);
11562 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
11563 },
11564
11565 _animateZoom: function (e) {
11566 this._setView(e.center, e.zoom, true, e.noUpdate);
11567 },
11568
11569 _clampZoom: function (zoom) {
11570 var options = this.options;
11571
11572 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
11573 return options.minNativeZoom;
11574 }
11575
11576 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
11577 return options.maxNativeZoom;
11578 }
11579
11580 return zoom;
11581 },
11582
11583 _setView: function (center, zoom, noPrune, noUpdate) {
11584 var tileZoom = Math.round(zoom);
11585 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
11586 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
11587 tileZoom = undefined;
11588 } else {
11589 tileZoom = this._clampZoom(tileZoom);
11590 }
11591
11592 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
11593
11594 if (!noUpdate || tileZoomChanged) {
11595
11596 this._tileZoom = tileZoom;
11597
11598 if (this._abortLoading) {
11599 this._abortLoading();
11600 }
11601
11602 this._updateLevels();
11603 this._resetGrid();
11604
11605 if (tileZoom !== undefined) {
11606 this._update(center);
11607 }
11608
11609 if (!noPrune) {
11610 this._pruneTiles();
11611 }
11612
11613 // Flag to prevent _updateOpacity from pruning tiles during
11614 // a zoom anim or a pinch gesture
11615 this._noPrune = !!noPrune;
11616 }
11617
11618 this._setZoomTransforms(center, zoom);
11619 },
11620
11621 _setZoomTransforms: function (center, zoom) {
11622 for (var i in this._levels) {
11623 this._setZoomTransform(this._levels[i], center, zoom);
11624 }
11625 },
11626
11627 _setZoomTransform: function (level, center, zoom) {
11628 var scale = this._map.getZoomScale(zoom, level.zoom),
11629 translate = level.origin.multiplyBy(scale)
11630 .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
11631
11632 if (Browser.any3d) {
11633 setTransform(level.el, translate, scale);
11634 } else {
11635 setPosition(level.el, translate);
11636 }
11637 },
11638
11639 _resetGrid: function () {
11640 var map = this._map,
11641 crs = map.options.crs,
11642 tileSize = this._tileSize = this.getTileSize(),
11643 tileZoom = this._tileZoom;
11644
11645 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
11646 if (bounds) {
11647 this._globalTileRange = this._pxBoundsToTileRange(bounds);
11648 }
11649
11650 this._wrapX = crs.wrapLng && !this.options.noWrap && [
11651 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
11652 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
11653 ];
11654 this._wrapY = crs.wrapLat && !this.options.noWrap && [
11655 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
11656 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
11657 ];
11658 },
11659
11660 _onMoveEnd: function () {
11661 if (!this._map || this._map._animatingZoom) { return; }
11662
11663 this._update();
11664 },
11665
11666 _getTiledPixelBounds: function (center) {
11667 var map = this._map,
11668 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
11669 scale = map.getZoomScale(mapZoom, this._tileZoom),
11670 pixelCenter = map.project(center, this._tileZoom).floor(),
11671 halfSize = map.getSize().divideBy(scale * 2);
11672
11673 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
11674 },
11675
11676 // Private method to load tiles in the grid's active zoom level according to map bounds
11677 _update: function (center) {
11678 var map = this._map;
11679 if (!map) { return; }
11680 var zoom = this._clampZoom(map.getZoom());
11681
11682 if (center === undefined) { center = map.getCenter(); }
11683 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom
11684
11685 var pixelBounds = this._getTiledPixelBounds(center),
11686 tileRange = this._pxBoundsToTileRange(pixelBounds),
11687 tileCenter = tileRange.getCenter(),
11688 queue = [],
11689 margin = this.options.keepBuffer,
11690 noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
11691 tileRange.getTopRight().add([margin, -margin]));
11692
11693 // Sanity check: panic if the tile range contains Infinity somewhere.
11694 if (!(isFinite(tileRange.min.x) &&
11695 isFinite(tileRange.min.y) &&
11696 isFinite(tileRange.max.x) &&
11697 isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
11698
11699 for (var key in this._tiles) {
11700 var c = this._tiles[key].coords;
11701 if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
11702 this._tiles[key].current = false;
11703 }
11704 }
11705
11706 // _update just loads more tiles. If the tile zoom level differs too much
11707 // from the map's, let _setView reset levels and prune old tiles.
11708 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
11709
11710 // create a queue of coordinates to load tiles from
11711 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
11712 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
11713 var coords = new Point(i, j);
11714 coords.z = this._tileZoom;
11715
11716 if (!this._isValidTile(coords)) { continue; }
11717
11718 var tile = this._tiles[this._tileCoordsToKey(coords)];
11719 if (tile) {
11720 tile.current = true;
11721 } else {
11722 queue.push(coords);
11723 }
11724 }
11725 }
11726
11727 // sort tile queue to load tiles in order of their distance to center
11728 queue.sort(function (a, b) {
11729 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
11730 });
11731
11732 if (queue.length !== 0) {
11733 // if it's the first batch of tiles to load
11734 if (!this._loading) {
11735 this._loading = true;
11736 // @event loading: Event
11737 // Fired when the grid layer starts loading tiles.
11738 this.fire('loading');
11739 }
11740
11741 // create DOM fragment to append tiles in one batch
11742 var fragment = document.createDocumentFragment();
11743
11744 for (i = 0; i < queue.length; i++) {
11745 this._addTile(queue[i], fragment);
11746 }
11747
11748 this._level.el.appendChild(fragment);
11749 }
11750 },
11751
11752 _isValidTile: function (coords) {
11753 var crs = this._map.options.crs;
11754
11755 if (!crs.infinite) {
11756 // don't load tile if it's out of bounds and not wrapped
11757 var bounds = this._globalTileRange;
11758 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
11759 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
11760 }
11761
11762 if (!this.options.bounds) { return true; }
11763
11764 // don't load tile if it doesn't intersect the bounds in options
11765 var tileBounds = this._tileCoordsToBounds(coords);
11766 return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
11767 },
11768
11769 _keyToBounds: function (key) {
11770 return this._tileCoordsToBounds(this._keyToTileCoords(key));
11771 },
11772
11773 _tileCoordsToNwSe: function (coords) {
11774 var map = this._map,
11775 tileSize = this.getTileSize(),
11776 nwPoint = coords.scaleBy(tileSize),
11777 sePoint = nwPoint.add(tileSize),
11778 nw = map.unproject(nwPoint, coords.z),
11779 se = map.unproject(sePoint, coords.z);
11780 return [nw, se];
11781 },
11782
11783 // converts tile coordinates to its geographical bounds
11784 _tileCoordsToBounds: function (coords) {
11785 var bp = this._tileCoordsToNwSe(coords),
11786 bounds = new LatLngBounds(bp[0], bp[1]);
11787
11788 if (!this.options.noWrap) {
11789 bounds = this._map.wrapLatLngBounds(bounds);
11790 }
11791 return bounds;
11792 },
11793 // converts tile coordinates to key for the tile cache
11794 _tileCoordsToKey: function (coords) {
11795 return coords.x + ':' + coords.y + ':' + coords.z;
11796 },
11797
11798 // converts tile cache key to coordinates
11799 _keyToTileCoords: function (key) {
11800 var k = key.split(':'),
11801 coords = new Point(+k[0], +k[1]);
11802 coords.z = +k[2];
11803 return coords;
11804 },
11805
11806 _removeTile: function (key) {
11807 var tile = this._tiles[key];
11808 if (!tile) { return; }
11809
11810 remove(tile.el);
11811
11812 delete this._tiles[key];
11813
11814 // @event tileunload: TileEvent
11815 // Fired when a tile is removed (e.g. when a tile goes off the screen).
11816 this.fire('tileunload', {
11817 tile: tile.el,
11818 coords: this._keyToTileCoords(key)
11819 });
11820 },
11821
11822 _initTile: function (tile) {
11823 addClass(tile, 'leaflet-tile');
11824
11825 var tileSize = this.getTileSize();
11826 tile.style.width = tileSize.x + 'px';
11827 tile.style.height = tileSize.y + 'px';
11828
11829 tile.onselectstart = falseFn;
11830 tile.onmousemove = falseFn;
11831
11832 // update opacity on tiles in IE7-8 because of filter inheritance problems
11833 if (Browser.ielt9 && this.options.opacity < 1) {
11834 setOpacity(tile, this.options.opacity);
11835 }
11836 },
11837
11838 _addTile: function (coords, container) {
11839 var tilePos = this._getTilePos(coords),
11840 key = this._tileCoordsToKey(coords);
11841
11842 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
11843
11844 this._initTile(tile);
11845
11846 // if createTile is defined with a second argument ("done" callback),
11847 // we know that tile is async and will be ready later; otherwise
11848 if (this.createTile.length < 2) {
11849 // mark tile as ready, but delay one frame for opacity animation to happen
11850 requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
11851 }
11852
11853 setPosition(tile, tilePos);
11854
11855 // save tile in cache
11856 this._tiles[key] = {
11857 el: tile,
11858 coords: coords,
11859 current: true
11860 };
11861
11862 container.appendChild(tile);
11863 // @event tileloadstart: TileEvent
11864 // Fired when a tile is requested and starts loading.
11865 this.fire('tileloadstart', {
11866 tile: tile,
11867 coords: coords
11868 });
11869 },
11870
11871 _tileReady: function (coords, err, tile) {
11872 if (err) {
11873 // @event tileerror: TileErrorEvent
11874 // Fired when there is an error loading a tile.
11875 this.fire('tileerror', {
11876 error: err,
11877 tile: tile,
11878 coords: coords
11879 });
11880 }
11881
11882 var key = this._tileCoordsToKey(coords);
11883
11884 tile = this._tiles[key];
11885 if (!tile) { return; }
11886
11887 tile.loaded = +new Date();
11888 if (this._map._fadeAnimated) {
11889 setOpacity(tile.el, 0);
11890 cancelAnimFrame(this._fadeFrame);
11891 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11892 } else {
11893 tile.active = true;
11894 this._pruneTiles();
11895 }
11896
11897 if (!err) {
11898 addClass(tile.el, 'leaflet-tile-loaded');
11899
11900 // @event tileload: TileEvent
11901 // Fired when a tile loads.
11902 this.fire('tileload', {
11903 tile: tile.el,
11904 coords: coords
11905 });
11906 }
11907
11908 if (this._noTilesToLoad()) {
11909 this._loading = false;
11910 // @event load: Event
11911 // Fired when the grid layer loaded all visible tiles.
11912 this.fire('load');
11913
11914 if (Browser.ielt9 || !this._map._fadeAnimated) {
11915 requestAnimFrame(this._pruneTiles, this);
11916 } else {
11917 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
11918 // to trigger a pruning.
11919 setTimeout(bind(this._pruneTiles, this), 250);
11920 }
11921 }
11922 },
11923
11924 _getTilePos: function (coords) {
11925 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
11926 },
11927
11928 _wrapCoords: function (coords) {
11929 var newCoords = new Point(
11930 this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
11931 this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
11932 newCoords.z = coords.z;
11933 return newCoords;
11934 },
11935
11936 _pxBoundsToTileRange: function (bounds) {
11937 var tileSize = this.getTileSize();
11938 return new Bounds(
11939 bounds.min.unscaleBy(tileSize).floor(),
11940 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
11941 },
11942
11943 _noTilesToLoad: function () {
11944 for (var key in this._tiles) {
11945 if (!this._tiles[key].loaded) { return false; }
11946 }
11947 return true;
11948 }
11949});
11950
11951// @factory L.gridLayer(options?: GridLayer options)
11952// Creates a new instance of GridLayer with the supplied options.
11953function gridLayer(options) {
11954 return new GridLayer(options);
11955}
11956
11957/*
11958 * @class TileLayer
11959 * @inherits GridLayer
11960 * @aka L.TileLayer
11961 * 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`.
11962 *
11963 * @example
11964 *
11965 * ```js
11966 * 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);
11967 * ```
11968 *
11969 * @section URL template
11970 * @example
11971 *
11972 * A string of the following form:
11973 *
11974 * ```
11975 * 'https://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
11976 * ```
11977 *
11978 * `{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.
11979 *
11980 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
11981 *
11982 * ```
11983 * L.tileLayer('https://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
11984 * ```
11985 */
11986
11987
11988var TileLayer = GridLayer.extend({
11989
11990 // @section
11991 // @aka TileLayer options
11992 options: {
11993 // @option minZoom: Number = 0
11994 // The minimum zoom level down to which this layer will be displayed (inclusive).
11995 minZoom: 0,
11996
11997 // @option maxZoom: Number = 18
11998 // The maximum zoom level up to which this layer will be displayed (inclusive).
11999 maxZoom: 18,
12000
12001 // @option subdomains: String|String[] = 'abc'
12002 // 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.
12003 subdomains: 'abc',
12004
12005 // @option errorTileUrl: String = ''
12006 // URL to the tile image to show in place of the tile that failed to load.
12007 errorTileUrl: '',
12008
12009 // @option zoomOffset: Number = 0
12010 // The zoom number used in tile URLs will be offset with this value.
12011 zoomOffset: 0,
12012
12013 // @option tms: Boolean = false
12014 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
12015 tms: false,
12016
12017 // @option zoomReverse: Boolean = false
12018 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
12019 zoomReverse: false,
12020
12021 // @option detectRetina: Boolean = false
12022 // 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.
12023 detectRetina: false,
12024
12025 // @option crossOrigin: Boolean|String = false
12026 // Whether the crossOrigin attribute will be added to the tiles.
12027 // 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.
12028 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
12029 crossOrigin: false,
12030
12031 // @option referrerPolicy: Boolean|String = false
12032 // Whether the referrerPolicy attribute will be added to the tiles.
12033 // If a String is provided, all tiles will have their referrerPolicy attribute set to the String provided.
12034 // This may be needed if your map's rendering context has a strict default but your tile provider expects a valid referrer
12035 // (e.g. to validate an API token).
12036 // Refer to [HTMLImageElement.referrerPolicy](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/referrerPolicy) for valid String values.
12037 referrerPolicy: false
12038 },
12039
12040 initialize: function (url, options) {
12041
12042 this._url = url;
12043
12044 options = setOptions(this, options);
12045
12046 // detecting retina displays, adjusting tileSize and zoom levels
12047 if (options.detectRetina && Browser.retina && options.maxZoom > 0) {
12048
12049 options.tileSize = Math.floor(options.tileSize / 2);
12050
12051 if (!options.zoomReverse) {
12052 options.zoomOffset++;
12053 options.maxZoom = Math.max(options.minZoom, options.maxZoom - 1);
12054 } else {
12055 options.zoomOffset--;
12056 options.minZoom = Math.min(options.maxZoom, options.minZoom + 1);
12057 }
12058
12059 options.minZoom = Math.max(0, options.minZoom);
12060 } else if (!options.zoomReverse) {
12061 // make sure maxZoom is gte minZoom
12062 options.maxZoom = Math.max(options.minZoom, options.maxZoom);
12063 } else {
12064 // make sure minZoom is lte maxZoom
12065 options.minZoom = Math.min(options.maxZoom, options.minZoom);
12066 }
12067
12068 if (typeof options.subdomains === 'string') {
12069 options.subdomains = options.subdomains.split('');
12070 }
12071
12072 this.on('tileunload', this._onTileRemove);
12073 },
12074
12075 // @method setUrl(url: String, noRedraw?: Boolean): this
12076 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
12077 // If the URL does not change, the layer will not be redrawn unless
12078 // the noRedraw parameter is set to false.
12079 setUrl: function (url, noRedraw) {
12080 if (this._url === url && noRedraw === undefined) {
12081 noRedraw = true;
12082 }
12083
12084 this._url = url;
12085
12086 if (!noRedraw) {
12087 this.redraw();
12088 }
12089 return this;
12090 },
12091
12092 // @method createTile(coords: Object, done?: Function): HTMLElement
12093 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
12094 // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
12095 // callback is called when the tile has been loaded.
12096 createTile: function (coords, done) {
12097 var tile = document.createElement('img');
12098
12099 on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
12100 on(tile, 'error', bind(this._tileOnError, this, done, tile));
12101
12102 if (this.options.crossOrigin || this.options.crossOrigin === '') {
12103 tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
12104 }
12105
12106 // for this new option we follow the documented behavior
12107 // more closely by only setting the property when string
12108 if (typeof this.options.referrerPolicy === 'string') {
12109 tile.referrerPolicy = this.options.referrerPolicy;
12110 }
12111
12112 // The alt attribute is set to the empty string,
12113 // allowing screen readers to ignore the decorative image tiles.
12114 // https://www.w3.org/WAI/tutorials/images/decorative/
12115 // https://www.w3.org/TR/html-aria/#el-img-empty-alt
12116 tile.alt = '';
12117
12118 tile.src = this.getTileUrl(coords);
12119
12120 return tile;
12121 },
12122
12123 // @section Extension methods
12124 // @uninheritable
12125 // Layers extending `TileLayer` might reimplement the following method.
12126 // @method getTileUrl(coords: Object): String
12127 // Called only internally, returns the URL for a tile given its coordinates.
12128 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
12129 getTileUrl: function (coords) {
12130 var data = {
12131 r: Browser.retina ? '@2x' : '',
12132 s: this._getSubdomain(coords),
12133 x: coords.x,
12134 y: coords.y,
12135 z: this._getZoomForUrl()
12136 };
12137 if (this._map && !this._map.options.crs.infinite) {
12138 var invertedY = this._globalTileRange.max.y - coords.y;
12139 if (this.options.tms) {
12140 data['y'] = invertedY;
12141 }
12142 data['-y'] = invertedY;
12143 }
12144
12145 return template(this._url, extend(data, this.options));
12146 },
12147
12148 _tileOnLoad: function (done, tile) {
12149 // For https://github.com/Leaflet/Leaflet/issues/3332
12150 if (Browser.ielt9) {
12151 setTimeout(bind(done, this, null, tile), 0);
12152 } else {
12153 done(null, tile);
12154 }
12155 },
12156
12157 _tileOnError: function (done, tile, e) {
12158 var errorUrl = this.options.errorTileUrl;
12159 if (errorUrl && tile.getAttribute('src') !== errorUrl) {
12160 tile.src = errorUrl;
12161 }
12162 done(e, tile);
12163 },
12164
12165 _onTileRemove: function (e) {
12166 e.tile.onload = null;
12167 },
12168
12169 _getZoomForUrl: function () {
12170 var zoom = this._tileZoom,
12171 maxZoom = this.options.maxZoom,
12172 zoomReverse = this.options.zoomReverse,
12173 zoomOffset = this.options.zoomOffset;
12174
12175 if (zoomReverse) {
12176 zoom = maxZoom - zoom;
12177 }
12178
12179 return zoom + zoomOffset;
12180 },
12181
12182 _getSubdomain: function (tilePoint) {
12183 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
12184 return this.options.subdomains[index];
12185 },
12186
12187 // stops loading all tiles in the background layer
12188 _abortLoading: function () {
12189 var i, tile;
12190 for (i in this._tiles) {
12191 if (this._tiles[i].coords.z !== this._tileZoom) {
12192 tile = this._tiles[i].el;
12193
12194 tile.onload = falseFn;
12195 tile.onerror = falseFn;
12196
12197 if (!tile.complete) {
12198 tile.src = emptyImageUrl;
12199 var coords = this._tiles[i].coords;
12200 remove(tile);
12201 delete this._tiles[i];
12202 // @event tileabort: TileEvent
12203 // Fired when a tile was loading but is now not wanted.
12204 this.fire('tileabort', {
12205 tile: tile,
12206 coords: coords
12207 });
12208 }
12209 }
12210 }
12211 },
12212
12213 _removeTile: function (key) {
12214 var tile = this._tiles[key];
12215 if (!tile) { return; }
12216
12217 // Cancels any pending http requests associated with the tile
12218 tile.el.setAttribute('src', emptyImageUrl);
12219
12220 return GridLayer.prototype._removeTile.call(this, key);
12221 },
12222
12223 _tileReady: function (coords, err, tile) {
12224 if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) {
12225 return;
12226 }
12227
12228 return GridLayer.prototype._tileReady.call(this, coords, err, tile);
12229 }
12230});
12231
12232
12233// @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
12234// Instantiates a tile layer object given a `URL template` and optionally an options object.
12235
12236function tileLayer(url, options) {
12237 return new TileLayer(url, options);
12238}
12239
12240/*
12241 * @class TileLayer.WMS
12242 * @inherits TileLayer
12243 * @aka L.TileLayer.WMS
12244 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
12245 *
12246 * @example
12247 *
12248 * ```js
12249 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
12250 * layers: 'nexrad-n0r-900913',
12251 * format: 'image/png',
12252 * transparent: true,
12253 * attribution: "Weather data © 2012 IEM Nexrad"
12254 * });
12255 * ```
12256 */
12257
12258var TileLayerWMS = TileLayer.extend({
12259
12260 // @section
12261 // @aka TileLayer.WMS options
12262 // If any custom options not documented here are used, they will be sent to the
12263 // WMS server as extra parameters in each request URL. This can be useful for
12264 // [non-standard vendor WMS parameters](https://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
12265 defaultWmsParams: {
12266 service: 'WMS',
12267 request: 'GetMap',
12268
12269 // @option layers: String = ''
12270 // **(required)** Comma-separated list of WMS layers to show.
12271 layers: '',
12272
12273 // @option styles: String = ''
12274 // Comma-separated list of WMS styles.
12275 styles: '',
12276
12277 // @option format: String = 'image/jpeg'
12278 // WMS image format (use `'image/png'` for layers with transparency).
12279 format: 'image/jpeg',
12280
12281 // @option transparent: Boolean = false
12282 // If `true`, the WMS service will return images with transparency.
12283 transparent: false,
12284
12285 // @option version: String = '1.1.1'
12286 // Version of the WMS service to use
12287 version: '1.1.1'
12288 },
12289
12290 options: {
12291 // @option crs: CRS = null
12292 // Coordinate Reference System to use for the WMS requests, defaults to
12293 // map CRS. Don't change this if you're not sure what it means.
12294 crs: null,
12295
12296 // @option uppercase: Boolean = false
12297 // If `true`, WMS request parameter keys will be uppercase.
12298 uppercase: false
12299 },
12300
12301 initialize: function (url, options) {
12302
12303 this._url = url;
12304
12305 var wmsParams = extend({}, this.defaultWmsParams);
12306
12307 // all keys that are not TileLayer options go to WMS params
12308 for (var i in options) {
12309 if (!(i in this.options)) {
12310 wmsParams[i] = options[i];
12311 }
12312 }
12313
12314 options = setOptions(this, options);
12315
12316 var realRetina = options.detectRetina && Browser.retina ? 2 : 1;
12317 var tileSize = this.getTileSize();
12318 wmsParams.width = tileSize.x * realRetina;
12319 wmsParams.height = tileSize.y * realRetina;
12320
12321 this.wmsParams = wmsParams;
12322 },
12323
12324 onAdd: function (map) {
12325
12326 this._crs = this.options.crs || map.options.crs;
12327 this._wmsVersion = parseFloat(this.wmsParams.version);
12328
12329 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
12330 this.wmsParams[projectionKey] = this._crs.code;
12331
12332 TileLayer.prototype.onAdd.call(this, map);
12333 },
12334
12335 getTileUrl: function (coords) {
12336
12337 var tileBounds = this._tileCoordsToNwSe(coords),
12338 crs = this._crs,
12339 bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
12340 min = bounds.min,
12341 max = bounds.max,
12342 bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
12343 [min.y, min.x, max.y, max.x] :
12344 [min.x, min.y, max.x, max.y]).join(','),
12345 url = TileLayer.prototype.getTileUrl.call(this, coords);
12346 return url +
12347 getParamString(this.wmsParams, url, this.options.uppercase) +
12348 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
12349 },
12350
12351 // @method setParams(params: Object, noRedraw?: Boolean): this
12352 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
12353 setParams: function (params, noRedraw) {
12354
12355 extend(this.wmsParams, params);
12356
12357 if (!noRedraw) {
12358 this.redraw();
12359 }
12360
12361 return this;
12362 }
12363});
12364
12365
12366// @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
12367// Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
12368function tileLayerWMS(url, options) {
12369 return new TileLayerWMS(url, options);
12370}
12371
12372TileLayer.WMS = TileLayerWMS;
12373tileLayer.wms = tileLayerWMS;
12374
12375/*
12376 * @class Renderer
12377 * @inherits Layer
12378 * @aka L.Renderer
12379 *
12380 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
12381 * DOM container of the renderer, its bounds, and its zoom animation.
12382 *
12383 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
12384 * itself can be added or removed to the map. All paths use a renderer, which can
12385 * be implicit (the map will decide the type of renderer and use it automatically)
12386 * or explicit (using the [`renderer`](#path-renderer) option of the path).
12387 *
12388 * Do not use this class directly, use `SVG` and `Canvas` instead.
12389 *
12390 * @event update: Event
12391 * Fired when the renderer updates its bounds, center and zoom, for example when
12392 * its map has moved
12393 */
12394
12395var Renderer = Layer.extend({
12396
12397 // @section
12398 // @aka Renderer options
12399 options: {
12400 // @option padding: Number = 0.1
12401 // How much to extend the clip area around the map view (relative to its size)
12402 // e.g. 0.1 would be 10% of map view in each direction
12403 padding: 0.1
12404 },
12405
12406 initialize: function (options) {
12407 setOptions(this, options);
12408 stamp(this);
12409 this._layers = this._layers || {};
12410 },
12411
12412 onAdd: function () {
12413 if (!this._container) {
12414 this._initContainer(); // defined by renderer implementations
12415
12416 if (this._zoomAnimated) {
12417 addClass(this._container, 'leaflet-zoom-animated');
12418 }
12419 }
12420
12421 this.getPane().appendChild(this._container);
12422 this._update();
12423 this.on('update', this._updatePaths, this);
12424 },
12425
12426 onRemove: function () {
12427 this.off('update', this._updatePaths, this);
12428 this._destroyContainer();
12429 },
12430
12431 getEvents: function () {
12432 var events = {
12433 viewreset: this._reset,
12434 zoom: this._onZoom,
12435 moveend: this._update,
12436 zoomend: this._onZoomEnd
12437 };
12438 if (this._zoomAnimated) {
12439 events.zoomanim = this._onAnimZoom;
12440 }
12441 return events;
12442 },
12443
12444 _onAnimZoom: function (ev) {
12445 this._updateTransform(ev.center, ev.zoom);
12446 },
12447
12448 _onZoom: function () {
12449 this._updateTransform(this._map.getCenter(), this._map.getZoom());
12450 },
12451
12452 _updateTransform: function (center, zoom) {
12453 var scale = this._map.getZoomScale(zoom, this._zoom),
12454 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
12455 currentCenterPoint = this._map.project(this._center, zoom),
12456
12457 topLeftOffset = viewHalf.multiplyBy(-scale).add(currentCenterPoint)
12458 .subtract(this._map._getNewPixelOrigin(center, zoom));
12459
12460 if (Browser.any3d) {
12461 setTransform(this._container, topLeftOffset, scale);
12462 } else {
12463 setPosition(this._container, topLeftOffset);
12464 }
12465 },
12466
12467 _reset: function () {
12468 this._update();
12469 this._updateTransform(this._center, this._zoom);
12470
12471 for (var id in this._layers) {
12472 this._layers[id]._reset();
12473 }
12474 },
12475
12476 _onZoomEnd: function () {
12477 for (var id in this._layers) {
12478 this._layers[id]._project();
12479 }
12480 },
12481
12482 _updatePaths: function () {
12483 for (var id in this._layers) {
12484 this._layers[id]._update();
12485 }
12486 },
12487
12488 _update: function () {
12489 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
12490 // Subclasses are responsible of firing the 'update' event.
12491 var p = this.options.padding,
12492 size = this._map.getSize(),
12493 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
12494
12495 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
12496
12497 this._center = this._map.getCenter();
12498 this._zoom = this._map.getZoom();
12499 }
12500});
12501
12502/*
12503 * @class Canvas
12504 * @inherits Renderer
12505 * @aka L.Canvas
12506 *
12507 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
12508 * Inherits `Renderer`.
12509 *
12510 * Due to [technical limitations](https://caniuse.com/canvas), Canvas is not
12511 * available in all web browsers, notably IE8, and overlapping geometries might
12512 * not display properly in some edge cases.
12513 *
12514 * @example
12515 *
12516 * Use Canvas by default for all paths in the map:
12517 *
12518 * ```js
12519 * var map = L.map('map', {
12520 * renderer: L.canvas()
12521 * });
12522 * ```
12523 *
12524 * Use a Canvas renderer with extra padding for specific vector geometries:
12525 *
12526 * ```js
12527 * var map = L.map('map');
12528 * var myRenderer = L.canvas({ padding: 0.5 });
12529 * var line = L.polyline( coordinates, { renderer: myRenderer } );
12530 * var circle = L.circle( center, { renderer: myRenderer } );
12531 * ```
12532 */
12533
12534var Canvas = Renderer.extend({
12535
12536 // @section
12537 // @aka Canvas options
12538 options: {
12539 // @option tolerance: Number = 0
12540 // How much to extend the click tolerance around a path/object on the map.
12541 tolerance: 0
12542 },
12543
12544 getEvents: function () {
12545 var events = Renderer.prototype.getEvents.call(this);
12546 events.viewprereset = this._onViewPreReset;
12547 return events;
12548 },
12549
12550 _onViewPreReset: function () {
12551 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
12552 this._postponeUpdatePaths = true;
12553 },
12554
12555 onAdd: function () {
12556 Renderer.prototype.onAdd.call(this);
12557
12558 // Redraw vectors since canvas is cleared upon removal,
12559 // in case of removing the renderer itself from the map.
12560 this._draw();
12561 },
12562
12563 _initContainer: function () {
12564 var container = this._container = document.createElement('canvas');
12565
12566 on(container, 'mousemove', this._onMouseMove, this);
12567 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
12568 on(container, 'mouseout', this._handleMouseOut, this);
12569 container['_leaflet_disable_events'] = true;
12570
12571 this._ctx = container.getContext('2d');
12572 },
12573
12574 _destroyContainer: function () {
12575 cancelAnimFrame(this._redrawRequest);
12576 delete this._ctx;
12577 remove(this._container);
12578 off(this._container);
12579 delete this._container;
12580 },
12581
12582 _updatePaths: function () {
12583 if (this._postponeUpdatePaths) { return; }
12584
12585 var layer;
12586 this._redrawBounds = null;
12587 for (var id in this._layers) {
12588 layer = this._layers[id];
12589 layer._update();
12590 }
12591 this._redraw();
12592 },
12593
12594 _update: function () {
12595 if (this._map._animatingZoom && this._bounds) { return; }
12596
12597 Renderer.prototype._update.call(this);
12598
12599 var b = this._bounds,
12600 container = this._container,
12601 size = b.getSize(),
12602 m = Browser.retina ? 2 : 1;
12603
12604 setPosition(container, b.min);
12605
12606 // set canvas size (also clearing it); use double size on retina
12607 container.width = m * size.x;
12608 container.height = m * size.y;
12609 container.style.width = size.x + 'px';
12610 container.style.height = size.y + 'px';
12611
12612 if (Browser.retina) {
12613 this._ctx.scale(2, 2);
12614 }
12615
12616 // translate so we use the same path coordinates after canvas element moves
12617 this._ctx.translate(-b.min.x, -b.min.y);
12618
12619 // Tell paths to redraw themselves
12620 this.fire('update');
12621 },
12622
12623 _reset: function () {
12624 Renderer.prototype._reset.call(this);
12625
12626 if (this._postponeUpdatePaths) {
12627 this._postponeUpdatePaths = false;
12628 this._updatePaths();
12629 }
12630 },
12631
12632 _initPath: function (layer) {
12633 this._updateDashArray(layer);
12634 this._layers[stamp(layer)] = layer;
12635
12636 var order = layer._order = {
12637 layer: layer,
12638 prev: this._drawLast,
12639 next: null
12640 };
12641 if (this._drawLast) { this._drawLast.next = order; }
12642 this._drawLast = order;
12643 this._drawFirst = this._drawFirst || this._drawLast;
12644 },
12645
12646 _addPath: function (layer) {
12647 this._requestRedraw(layer);
12648 },
12649
12650 _removePath: function (layer) {
12651 var order = layer._order;
12652 var next = order.next;
12653 var prev = order.prev;
12654
12655 if (next) {
12656 next.prev = prev;
12657 } else {
12658 this._drawLast = prev;
12659 }
12660 if (prev) {
12661 prev.next = next;
12662 } else {
12663 this._drawFirst = next;
12664 }
12665
12666 delete layer._order;
12667
12668 delete this._layers[stamp(layer)];
12669
12670 this._requestRedraw(layer);
12671 },
12672
12673 _updatePath: function (layer) {
12674 // Redraw the union of the layer's old pixel
12675 // bounds and the new pixel bounds.
12676 this._extendRedrawBounds(layer);
12677 layer._project();
12678 layer._update();
12679 // The redraw will extend the redraw bounds
12680 // with the new pixel bounds.
12681 this._requestRedraw(layer);
12682 },
12683
12684 _updateStyle: function (layer) {
12685 this._updateDashArray(layer);
12686 this._requestRedraw(layer);
12687 },
12688
12689 _updateDashArray: function (layer) {
12690 if (typeof layer.options.dashArray === 'string') {
12691 var parts = layer.options.dashArray.split(/[, ]+/),
12692 dashArray = [],
12693 dashValue,
12694 i;
12695 for (i = 0; i < parts.length; i++) {
12696 dashValue = Number(parts[i]);
12697 // Ignore dash array containing invalid lengths
12698 if (isNaN(dashValue)) { return; }
12699 dashArray.push(dashValue);
12700 }
12701 layer.options._dashArray = dashArray;
12702 } else {
12703 layer.options._dashArray = layer.options.dashArray;
12704 }
12705 },
12706
12707 _requestRedraw: function (layer) {
12708 if (!this._map) { return; }
12709
12710 this._extendRedrawBounds(layer);
12711 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
12712 },
12713
12714 _extendRedrawBounds: function (layer) {
12715 if (layer._pxBounds) {
12716 var padding = (layer.options.weight || 0) + 1;
12717 this._redrawBounds = this._redrawBounds || new Bounds();
12718 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
12719 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
12720 }
12721 },
12722
12723 _redraw: function () {
12724 this._redrawRequest = null;
12725
12726 if (this._redrawBounds) {
12727 this._redrawBounds.min._floor();
12728 this._redrawBounds.max._ceil();
12729 }
12730
12731 this._clear(); // clear layers in redraw bounds
12732 this._draw(); // draw layers
12733
12734 this._redrawBounds = null;
12735 },
12736
12737 _clear: function () {
12738 var bounds = this._redrawBounds;
12739 if (bounds) {
12740 var size = bounds.getSize();
12741 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
12742 } else {
12743 this._ctx.save();
12744 this._ctx.setTransform(1, 0, 0, 1, 0, 0);
12745 this._ctx.clearRect(0, 0, this._container.width, this._container.height);
12746 this._ctx.restore();
12747 }
12748 },
12749
12750 _draw: function () {
12751 var layer, bounds = this._redrawBounds;
12752 this._ctx.save();
12753 if (bounds) {
12754 var size = bounds.getSize();
12755 this._ctx.beginPath();
12756 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
12757 this._ctx.clip();
12758 }
12759
12760 this._drawing = true;
12761
12762 for (var order = this._drawFirst; order; order = order.next) {
12763 layer = order.layer;
12764 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
12765 layer._updatePath();
12766 }
12767 }
12768
12769 this._drawing = false;
12770
12771 this._ctx.restore(); // Restore state before clipping.
12772 },
12773
12774 _updatePoly: function (layer, closed) {
12775 if (!this._drawing) { return; }
12776
12777 var i, j, len2, p,
12778 parts = layer._parts,
12779 len = parts.length,
12780 ctx = this._ctx;
12781
12782 if (!len) { return; }
12783
12784 ctx.beginPath();
12785
12786 for (i = 0; i < len; i++) {
12787 for (j = 0, len2 = parts[i].length; j < len2; j++) {
12788 p = parts[i][j];
12789 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
12790 }
12791 if (closed) {
12792 ctx.closePath();
12793 }
12794 }
12795
12796 this._fillStroke(ctx, layer);
12797
12798 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
12799 },
12800
12801 _updateCircle: function (layer) {
12802
12803 if (!this._drawing || layer._empty()) { return; }
12804
12805 var p = layer._point,
12806 ctx = this._ctx,
12807 r = Math.max(Math.round(layer._radius), 1),
12808 s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
12809
12810 if (s !== 1) {
12811 ctx.save();
12812 ctx.scale(1, s);
12813 }
12814
12815 ctx.beginPath();
12816 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
12817
12818 if (s !== 1) {
12819 ctx.restore();
12820 }
12821
12822 this._fillStroke(ctx, layer);
12823 },
12824
12825 _fillStroke: function (ctx, layer) {
12826 var options = layer.options;
12827
12828 if (options.fill) {
12829 ctx.globalAlpha = options.fillOpacity;
12830 ctx.fillStyle = options.fillColor || options.color;
12831 ctx.fill(options.fillRule || 'evenodd');
12832 }
12833
12834 if (options.stroke && options.weight !== 0) {
12835 if (ctx.setLineDash) {
12836 ctx.setLineDash(layer.options && layer.options._dashArray || []);
12837 }
12838 ctx.globalAlpha = options.opacity;
12839 ctx.lineWidth = options.weight;
12840 ctx.strokeStyle = options.color;
12841 ctx.lineCap = options.lineCap;
12842 ctx.lineJoin = options.lineJoin;
12843 ctx.stroke();
12844 }
12845 },
12846
12847 // Canvas obviously doesn't have mouse events for individual drawn objects,
12848 // so we emulate that by calculating what's under the mouse on mousemove/click manually
12849
12850 _onClick: function (e) {
12851 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
12852
12853 for (var order = this._drawFirst; order; order = order.next) {
12854 layer = order.layer;
12855 if (layer.options.interactive && layer._containsPoint(point)) {
12856 if (!(e.type === 'click' || e.type === 'preclick') || !this._map._draggableMoved(layer)) {
12857 clickedLayer = layer;
12858 }
12859 }
12860 }
12861 this._fireEvent(clickedLayer ? [clickedLayer] : false, e);
12862 },
12863
12864 _onMouseMove: function (e) {
12865 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
12866
12867 var point = this._map.mouseEventToLayerPoint(e);
12868 this._handleMouseHover(e, point);
12869 },
12870
12871
12872 _handleMouseOut: function (e) {
12873 var layer = this._hoveredLayer;
12874 if (layer) {
12875 // if we're leaving the layer, fire mouseout
12876 removeClass(this._container, 'leaflet-interactive');
12877 this._fireEvent([layer], e, 'mouseout');
12878 this._hoveredLayer = null;
12879 this._mouseHoverThrottled = false;
12880 }
12881 },
12882
12883 _handleMouseHover: function (e, point) {
12884 if (this._mouseHoverThrottled) {
12885 return;
12886 }
12887
12888 var layer, candidateHoveredLayer;
12889
12890 for (var order = this._drawFirst; order; order = order.next) {
12891 layer = order.layer;
12892 if (layer.options.interactive && layer._containsPoint(point)) {
12893 candidateHoveredLayer = layer;
12894 }
12895 }
12896
12897 if (candidateHoveredLayer !== this._hoveredLayer) {
12898 this._handleMouseOut(e);
12899
12900 if (candidateHoveredLayer) {
12901 addClass(this._container, 'leaflet-interactive'); // change cursor
12902 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
12903 this._hoveredLayer = candidateHoveredLayer;
12904 }
12905 }
12906
12907 this._fireEvent(this._hoveredLayer ? [this._hoveredLayer] : false, e);
12908
12909 this._mouseHoverThrottled = true;
12910 setTimeout(bind(function () {
12911 this._mouseHoverThrottled = false;
12912 }, this), 32);
12913 },
12914
12915 _fireEvent: function (layers, e, type) {
12916 this._map._fireDOMEvent(e, type || e.type, layers);
12917 },
12918
12919 _bringToFront: function (layer) {
12920 var order = layer._order;
12921
12922 if (!order) { return; }
12923
12924 var next = order.next;
12925 var prev = order.prev;
12926
12927 if (next) {
12928 next.prev = prev;
12929 } else {
12930 // Already last
12931 return;
12932 }
12933 if (prev) {
12934 prev.next = next;
12935 } else if (next) {
12936 // Update first entry unless this is the
12937 // single entry
12938 this._drawFirst = next;
12939 }
12940
12941 order.prev = this._drawLast;
12942 this._drawLast.next = order;
12943
12944 order.next = null;
12945 this._drawLast = order;
12946
12947 this._requestRedraw(layer);
12948 },
12949
12950 _bringToBack: function (layer) {
12951 var order = layer._order;
12952
12953 if (!order) { return; }
12954
12955 var next = order.next;
12956 var prev = order.prev;
12957
12958 if (prev) {
12959 prev.next = next;
12960 } else {
12961 // Already first
12962 return;
12963 }
12964 if (next) {
12965 next.prev = prev;
12966 } else if (prev) {
12967 // Update last entry unless this is the
12968 // single entry
12969 this._drawLast = prev;
12970 }
12971
12972 order.prev = null;
12973
12974 order.next = this._drawFirst;
12975 this._drawFirst.prev = order;
12976 this._drawFirst = order;
12977
12978 this._requestRedraw(layer);
12979 }
12980});
12981
12982// @factory L.canvas(options?: Renderer options)
12983// Creates a Canvas renderer with the given options.
12984function canvas(options) {
12985 return Browser.canvas ? new Canvas(options) : null;
12986}
12987
12988/*
12989 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
12990 */
12991
12992
12993var vmlCreate = (function () {
12994 try {
12995 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
12996 return function (name) {
12997 return document.createElement('<lvml:' + name + ' class="lvml">');
12998 };
12999 } catch (e) {
13000 // Do not return fn from catch block so `e` can be garbage collected
13001 // See https://github.com/Leaflet/Leaflet/pull/7279
13002 }
13003 return function (name) {
13004 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
13005 };
13006})();
13007
13008
13009/*
13010 * @class SVG
13011 *
13012 *
13013 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
13014 * with old versions of Internet Explorer.
13015 */
13016
13017// mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
13018var vmlMixin = {
13019
13020 _initContainer: function () {
13021 this._container = create$1('div', 'leaflet-vml-container');
13022 },
13023
13024 _update: function () {
13025 if (this._map._animatingZoom) { return; }
13026 Renderer.prototype._update.call(this);
13027 this.fire('update');
13028 },
13029
13030 _initPath: function (layer) {
13031 var container = layer._container = vmlCreate('shape');
13032
13033 addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
13034
13035 container.coordsize = '1 1';
13036
13037 layer._path = vmlCreate('path');
13038 container.appendChild(layer._path);
13039
13040 this._updateStyle(layer);
13041 this._layers[stamp(layer)] = layer;
13042 },
13043
13044 _addPath: function (layer) {
13045 var container = layer._container;
13046 this._container.appendChild(container);
13047
13048 if (layer.options.interactive) {
13049 layer.addInteractiveTarget(container);
13050 }
13051 },
13052
13053 _removePath: function (layer) {
13054 var container = layer._container;
13055 remove(container);
13056 layer.removeInteractiveTarget(container);
13057 delete this._layers[stamp(layer)];
13058 },
13059
13060 _updateStyle: function (layer) {
13061 var stroke = layer._stroke,
13062 fill = layer._fill,
13063 options = layer.options,
13064 container = layer._container;
13065
13066 container.stroked = !!options.stroke;
13067 container.filled = !!options.fill;
13068
13069 if (options.stroke) {
13070 if (!stroke) {
13071 stroke = layer._stroke = vmlCreate('stroke');
13072 }
13073 container.appendChild(stroke);
13074 stroke.weight = options.weight + 'px';
13075 stroke.color = options.color;
13076 stroke.opacity = options.opacity;
13077
13078 if (options.dashArray) {
13079 stroke.dashStyle = isArray(options.dashArray) ?
13080 options.dashArray.join(' ') :
13081 options.dashArray.replace(/( *, *)/g, ' ');
13082 } else {
13083 stroke.dashStyle = '';
13084 }
13085 stroke.endcap = options.lineCap.replace('butt', 'flat');
13086 stroke.joinstyle = options.lineJoin;
13087
13088 } else if (stroke) {
13089 container.removeChild(stroke);
13090 layer._stroke = null;
13091 }
13092
13093 if (options.fill) {
13094 if (!fill) {
13095 fill = layer._fill = vmlCreate('fill');
13096 }
13097 container.appendChild(fill);
13098 fill.color = options.fillColor || options.color;
13099 fill.opacity = options.fillOpacity;
13100
13101 } else if (fill) {
13102 container.removeChild(fill);
13103 layer._fill = null;
13104 }
13105 },
13106
13107 _updateCircle: function (layer) {
13108 var p = layer._point.round(),
13109 r = Math.round(layer._radius),
13110 r2 = Math.round(layer._radiusY || r);
13111
13112 this._setPath(layer, layer._empty() ? 'M0 0' :
13113 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
13114 },
13115
13116 _setPath: function (layer, path) {
13117 layer._path.v = path;
13118 },
13119
13120 _bringToFront: function (layer) {
13121 toFront(layer._container);
13122 },
13123
13124 _bringToBack: function (layer) {
13125 toBack(layer._container);
13126 }
13127};
13128
13129var create = Browser.vml ? vmlCreate : svgCreate;
13130
13131/*
13132 * @class SVG
13133 * @inherits Renderer
13134 * @aka L.SVG
13135 *
13136 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
13137 * Inherits `Renderer`.
13138 *
13139 * Due to [technical limitations](https://caniuse.com/svg), SVG is not
13140 * available in all web browsers, notably Android 2.x and 3.x.
13141 *
13142 * Although SVG is not available on IE7 and IE8, these browsers support
13143 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
13144 * (a now deprecated technology), and the SVG renderer will fall back to VML in
13145 * this case.
13146 *
13147 * @example
13148 *
13149 * Use SVG by default for all paths in the map:
13150 *
13151 * ```js
13152 * var map = L.map('map', {
13153 * renderer: L.svg()
13154 * });
13155 * ```
13156 *
13157 * Use a SVG renderer with extra padding for specific vector geometries:
13158 *
13159 * ```js
13160 * var map = L.map('map');
13161 * var myRenderer = L.svg({ padding: 0.5 });
13162 * var line = L.polyline( coordinates, { renderer: myRenderer } );
13163 * var circle = L.circle( center, { renderer: myRenderer } );
13164 * ```
13165 */
13166
13167var SVG = Renderer.extend({
13168
13169 _initContainer: function () {
13170 this._container = create('svg');
13171
13172 // makes it possible to click through svg root; we'll reset it back in individual paths
13173 this._container.setAttribute('pointer-events', 'none');
13174
13175 this._rootGroup = create('g');
13176 this._container.appendChild(this._rootGroup);
13177 },
13178
13179 _destroyContainer: function () {
13180 remove(this._container);
13181 off(this._container);
13182 delete this._container;
13183 delete this._rootGroup;
13184 delete this._svgSize;
13185 },
13186
13187 _update: function () {
13188 if (this._map._animatingZoom && this._bounds) { return; }
13189
13190 Renderer.prototype._update.call(this);
13191
13192 var b = this._bounds,
13193 size = b.getSize(),
13194 container = this._container;
13195
13196 // set size of svg-container if changed
13197 if (!this._svgSize || !this._svgSize.equals(size)) {
13198 this._svgSize = size;
13199 container.setAttribute('width', size.x);
13200 container.setAttribute('height', size.y);
13201 }
13202
13203 // movement: update container viewBox so that we don't have to change coordinates of individual layers
13204 setPosition(container, b.min);
13205 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
13206
13207 this.fire('update');
13208 },
13209
13210 // methods below are called by vector layers implementations
13211
13212 _initPath: function (layer) {
13213 var path = layer._path = create('path');
13214
13215 // @namespace Path
13216 // @option className: String = null
13217 // Custom class name set on an element. Only for SVG renderer.
13218 if (layer.options.className) {
13219 addClass(path, layer.options.className);
13220 }
13221
13222 if (layer.options.interactive) {
13223 addClass(path, 'leaflet-interactive');
13224 }
13225
13226 this._updateStyle(layer);
13227 this._layers[stamp(layer)] = layer;
13228 },
13229
13230 _addPath: function (layer) {
13231 if (!this._rootGroup) { this._initContainer(); }
13232 this._rootGroup.appendChild(layer._path);
13233 layer.addInteractiveTarget(layer._path);
13234 },
13235
13236 _removePath: function (layer) {
13237 remove(layer._path);
13238 layer.removeInteractiveTarget(layer._path);
13239 delete this._layers[stamp(layer)];
13240 },
13241
13242 _updatePath: function (layer) {
13243 layer._project();
13244 layer._update();
13245 },
13246
13247 _updateStyle: function (layer) {
13248 var path = layer._path,
13249 options = layer.options;
13250
13251 if (!path) { return; }
13252
13253 if (options.stroke) {
13254 path.setAttribute('stroke', options.color);
13255 path.setAttribute('stroke-opacity', options.opacity);
13256 path.setAttribute('stroke-width', options.weight);
13257 path.setAttribute('stroke-linecap', options.lineCap);
13258 path.setAttribute('stroke-linejoin', options.lineJoin);
13259
13260 if (options.dashArray) {
13261 path.setAttribute('stroke-dasharray', options.dashArray);
13262 } else {
13263 path.removeAttribute('stroke-dasharray');
13264 }
13265
13266 if (options.dashOffset) {
13267 path.setAttribute('stroke-dashoffset', options.dashOffset);
13268 } else {
13269 path.removeAttribute('stroke-dashoffset');
13270 }
13271 } else {
13272 path.setAttribute('stroke', 'none');
13273 }
13274
13275 if (options.fill) {
13276 path.setAttribute('fill', options.fillColor || options.color);
13277 path.setAttribute('fill-opacity', options.fillOpacity);
13278 path.setAttribute('fill-rule', options.fillRule || 'evenodd');
13279 } else {
13280 path.setAttribute('fill', 'none');
13281 }
13282 },
13283
13284 _updatePoly: function (layer, closed) {
13285 this._setPath(layer, pointsToPath(layer._parts, closed));
13286 },
13287
13288 _updateCircle: function (layer) {
13289 var p = layer._point,
13290 r = Math.max(Math.round(layer._radius), 1),
13291 r2 = Math.max(Math.round(layer._radiusY), 1) || r,
13292 arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
13293
13294 // drawing a circle with two half-arcs
13295 var d = layer._empty() ? 'M0 0' :
13296 'M' + (p.x - r) + ',' + p.y +
13297 arc + (r * 2) + ',0 ' +
13298 arc + (-r * 2) + ',0 ';
13299
13300 this._setPath(layer, d);
13301 },
13302
13303 _setPath: function (layer, path) {
13304 layer._path.setAttribute('d', path);
13305 },
13306
13307 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
13308 _bringToFront: function (layer) {
13309 toFront(layer._path);
13310 },
13311
13312 _bringToBack: function (layer) {
13313 toBack(layer._path);
13314 }
13315});
13316
13317if (Browser.vml) {
13318 SVG.include(vmlMixin);
13319}
13320
13321// @namespace SVG
13322// @factory L.svg(options?: Renderer options)
13323// Creates a SVG renderer with the given options.
13324function svg(options) {
13325 return Browser.svg || Browser.vml ? new SVG(options) : null;
13326}
13327
13328Map.include({
13329 // @namespace Map; @method getRenderer(layer: Path): Renderer
13330 // Returns the instance of `Renderer` that should be used to render the given
13331 // `Path`. It will ensure that the `renderer` options of the map and paths
13332 // are respected, and that the renderers do exist on the map.
13333 getRenderer: function (layer) {
13334 // @namespace Path; @option renderer: Renderer
13335 // Use this specific instance of `Renderer` for this path. Takes
13336 // precedence over the map's [default renderer](#map-renderer).
13337 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
13338
13339 if (!renderer) {
13340 renderer = this._renderer = this._createRenderer();
13341 }
13342
13343 if (!this.hasLayer(renderer)) {
13344 this.addLayer(renderer);
13345 }
13346 return renderer;
13347 },
13348
13349 _getPaneRenderer: function (name) {
13350 if (name === 'overlayPane' || name === undefined) {
13351 return false;
13352 }
13353
13354 var renderer = this._paneRenderers[name];
13355 if (renderer === undefined) {
13356 renderer = this._createRenderer({pane: name});
13357 this._paneRenderers[name] = renderer;
13358 }
13359 return renderer;
13360 },
13361
13362 _createRenderer: function (options) {
13363 // @namespace Map; @option preferCanvas: Boolean = false
13364 // Whether `Path`s should be rendered on a `Canvas` renderer.
13365 // By default, all `Path`s are rendered in a `SVG` renderer.
13366 return (this.options.preferCanvas && canvas(options)) || svg(options);
13367 }
13368});
13369
13370/*
13371 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
13372 */
13373
13374/*
13375 * @class Rectangle
13376 * @aka L.Rectangle
13377 * @inherits Polygon
13378 *
13379 * A class for drawing rectangle overlays on a map. Extends `Polygon`.
13380 *
13381 * @example
13382 *
13383 * ```js
13384 * // define rectangle geographical bounds
13385 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
13386 *
13387 * // create an orange rectangle
13388 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
13389 *
13390 * // zoom the map to the rectangle bounds
13391 * map.fitBounds(bounds);
13392 * ```
13393 *
13394 */
13395
13396
13397var Rectangle = Polygon.extend({
13398 initialize: function (latLngBounds, options) {
13399 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
13400 },
13401
13402 // @method setBounds(latLngBounds: LatLngBounds): this
13403 // Redraws the rectangle with the passed bounds.
13404 setBounds: function (latLngBounds) {
13405 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
13406 },
13407
13408 _boundsToLatLngs: function (latLngBounds) {
13409 latLngBounds = toLatLngBounds(latLngBounds);
13410 return [
13411 latLngBounds.getSouthWest(),
13412 latLngBounds.getNorthWest(),
13413 latLngBounds.getNorthEast(),
13414 latLngBounds.getSouthEast()
13415 ];
13416 }
13417});
13418
13419
13420// @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
13421function rectangle(latLngBounds, options) {
13422 return new Rectangle(latLngBounds, options);
13423}
13424
13425SVG.create = create;
13426SVG.pointsToPath = pointsToPath;
13427
13428GeoJSON.geometryToLayer = geometryToLayer;
13429GeoJSON.coordsToLatLng = coordsToLatLng;
13430GeoJSON.coordsToLatLngs = coordsToLatLngs;
13431GeoJSON.latLngToCoords = latLngToCoords;
13432GeoJSON.latLngsToCoords = latLngsToCoords;
13433GeoJSON.getFeature = getFeature;
13434GeoJSON.asFeature = asFeature;
13435
13436/*
13437 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
13438 * (zoom to a selected bounding box), enabled by default.
13439 */
13440
13441// @namespace Map
13442// @section Interaction Options
13443Map.mergeOptions({
13444 // @option boxZoom: Boolean = true
13445 // Whether the map can be zoomed to a rectangular area specified by
13446 // dragging the mouse while pressing the shift key.
13447 boxZoom: true
13448});
13449
13450var BoxZoom = Handler.extend({
13451 initialize: function (map) {
13452 this._map = map;
13453 this._container = map._container;
13454 this._pane = map._panes.overlayPane;
13455 this._resetStateTimeout = 0;
13456 map.on('unload', this._destroy, this);
13457 },
13458
13459 addHooks: function () {
13460 on(this._container, 'mousedown', this._onMouseDown, this);
13461 },
13462
13463 removeHooks: function () {
13464 off(this._container, 'mousedown', this._onMouseDown, this);
13465 },
13466
13467 moved: function () {
13468 return this._moved;
13469 },
13470
13471 _destroy: function () {
13472 remove(this._pane);
13473 delete this._pane;
13474 },
13475
13476 _resetState: function () {
13477 this._resetStateTimeout = 0;
13478 this._moved = false;
13479 },
13480
13481 _clearDeferredResetState: function () {
13482 if (this._resetStateTimeout !== 0) {
13483 clearTimeout(this._resetStateTimeout);
13484 this._resetStateTimeout = 0;
13485 }
13486 },
13487
13488 _onMouseDown: function (e) {
13489 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
13490
13491 // Clear the deferred resetState if it hasn't executed yet, otherwise it
13492 // will interrupt the interaction and orphan a box element in the container.
13493 this._clearDeferredResetState();
13494 this._resetState();
13495
13496 disableTextSelection();
13497 disableImageDrag();
13498
13499 this._startPoint = this._map.mouseEventToContainerPoint(e);
13500
13501 on(document, {
13502 contextmenu: stop,
13503 mousemove: this._onMouseMove,
13504 mouseup: this._onMouseUp,
13505 keydown: this._onKeyDown
13506 }, this);
13507 },
13508
13509 _onMouseMove: function (e) {
13510 if (!this._moved) {
13511 this._moved = true;
13512
13513 this._box = create$1('div', 'leaflet-zoom-box', this._container);
13514 addClass(this._container, 'leaflet-crosshair');
13515
13516 this._map.fire('boxzoomstart');
13517 }
13518
13519 this._point = this._map.mouseEventToContainerPoint(e);
13520
13521 var bounds = new Bounds(this._point, this._startPoint),
13522 size = bounds.getSize();
13523
13524 setPosition(this._box, bounds.min);
13525
13526 this._box.style.width = size.x + 'px';
13527 this._box.style.height = size.y + 'px';
13528 },
13529
13530 _finish: function () {
13531 if (this._moved) {
13532 remove(this._box);
13533 removeClass(this._container, 'leaflet-crosshair');
13534 }
13535
13536 enableTextSelection();
13537 enableImageDrag();
13538
13539 off(document, {
13540 contextmenu: stop,
13541 mousemove: this._onMouseMove,
13542 mouseup: this._onMouseUp,
13543 keydown: this._onKeyDown
13544 }, this);
13545 },
13546
13547 _onMouseUp: function (e) {
13548 if ((e.which !== 1) && (e.button !== 1)) { return; }
13549
13550 this._finish();
13551
13552 if (!this._moved) { return; }
13553 // Postpone to next JS tick so internal click event handling
13554 // still see it as "moved".
13555 this._clearDeferredResetState();
13556 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
13557
13558 var bounds = new LatLngBounds(
13559 this._map.containerPointToLatLng(this._startPoint),
13560 this._map.containerPointToLatLng(this._point));
13561
13562 this._map
13563 .fitBounds(bounds)
13564 .fire('boxzoomend', {boxZoomBounds: bounds});
13565 },
13566
13567 _onKeyDown: function (e) {
13568 if (e.keyCode === 27) {
13569 this._finish();
13570 this._clearDeferredResetState();
13571 this._resetState();
13572 }
13573 }
13574});
13575
13576// @section Handlers
13577// @property boxZoom: Handler
13578// Box (shift-drag with mouse) zoom handler.
13579Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
13580
13581/*
13582 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
13583 */
13584
13585// @namespace Map
13586// @section Interaction Options
13587
13588Map.mergeOptions({
13589 // @option doubleClickZoom: Boolean|String = true
13590 // Whether the map can be zoomed in by double clicking on it and
13591 // zoomed out by double clicking while holding shift. If passed
13592 // `'center'`, double-click zoom will zoom to the center of the
13593 // view regardless of where the mouse was.
13594 doubleClickZoom: true
13595});
13596
13597var DoubleClickZoom = Handler.extend({
13598 addHooks: function () {
13599 this._map.on('dblclick', this._onDoubleClick, this);
13600 },
13601
13602 removeHooks: function () {
13603 this._map.off('dblclick', this._onDoubleClick, this);
13604 },
13605
13606 _onDoubleClick: function (e) {
13607 var map = this._map,
13608 oldZoom = map.getZoom(),
13609 delta = map.options.zoomDelta,
13610 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
13611
13612 if (map.options.doubleClickZoom === 'center') {
13613 map.setZoom(zoom);
13614 } else {
13615 map.setZoomAround(e.containerPoint, zoom);
13616 }
13617 }
13618});
13619
13620// @section Handlers
13621//
13622// Map properties include interaction handlers that allow you to control
13623// interaction behavior in runtime, enabling or disabling certain features such
13624// as dragging or touch zoom (see `Handler` methods). For example:
13625//
13626// ```js
13627// map.doubleClickZoom.disable();
13628// ```
13629//
13630// @property doubleClickZoom: Handler
13631// Double click zoom handler.
13632Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
13633
13634/*
13635 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
13636 */
13637
13638// @namespace Map
13639// @section Interaction Options
13640Map.mergeOptions({
13641 // @option dragging: Boolean = true
13642 // Whether the map is draggable with mouse/touch or not.
13643 dragging: true,
13644
13645 // @section Panning Inertia Options
13646 // @option inertia: Boolean = *
13647 // If enabled, panning of the map will have an inertia effect where
13648 // the map builds momentum while dragging and continues moving in
13649 // the same direction for some time. Feels especially nice on touch
13650 // devices. Enabled by default.
13651 inertia: true,
13652
13653 // @option inertiaDeceleration: Number = 3000
13654 // The rate with which the inertial movement slows down, in pixels/second².
13655 inertiaDeceleration: 3400, // px/s^2
13656
13657 // @option inertiaMaxSpeed: Number = Infinity
13658 // Max speed of the inertial movement, in pixels/second.
13659 inertiaMaxSpeed: Infinity, // px/s
13660
13661 // @option easeLinearity: Number = 0.2
13662 easeLinearity: 0.2,
13663
13664 // TODO refactor, move to CRS
13665 // @option worldCopyJump: Boolean = false
13666 // With this option enabled, the map tracks when you pan to another "copy"
13667 // of the world and seamlessly jumps to the original one so that all overlays
13668 // like markers and vector layers are still visible.
13669 worldCopyJump: false,
13670
13671 // @option maxBoundsViscosity: Number = 0.0
13672 // If `maxBounds` is set, this option will control how solid the bounds
13673 // are when dragging the map around. The default value of `0.0` allows the
13674 // user to drag outside the bounds at normal speed, higher values will
13675 // slow down map dragging outside bounds, and `1.0` makes the bounds fully
13676 // solid, preventing the user from dragging outside the bounds.
13677 maxBoundsViscosity: 0.0
13678});
13679
13680var Drag = Handler.extend({
13681 addHooks: function () {
13682 if (!this._draggable) {
13683 var map = this._map;
13684
13685 this._draggable = new Draggable(map._mapPane, map._container);
13686
13687 this._draggable.on({
13688 dragstart: this._onDragStart,
13689 drag: this._onDrag,
13690 dragend: this._onDragEnd
13691 }, this);
13692
13693 this._draggable.on('predrag', this._onPreDragLimit, this);
13694 if (map.options.worldCopyJump) {
13695 this._draggable.on('predrag', this._onPreDragWrap, this);
13696 map.on('zoomend', this._onZoomEnd, this);
13697
13698 map.whenReady(this._onZoomEnd, this);
13699 }
13700 }
13701 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
13702 this._draggable.enable();
13703 this._positions = [];
13704 this._times = [];
13705 },
13706
13707 removeHooks: function () {
13708 removeClass(this._map._container, 'leaflet-grab');
13709 removeClass(this._map._container, 'leaflet-touch-drag');
13710 this._draggable.disable();
13711 },
13712
13713 moved: function () {
13714 return this._draggable && this._draggable._moved;
13715 },
13716
13717 moving: function () {
13718 return this._draggable && this._draggable._moving;
13719 },
13720
13721 _onDragStart: function () {
13722 var map = this._map;
13723
13724 map._stop();
13725 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
13726 var bounds = toLatLngBounds(this._map.options.maxBounds);
13727
13728 this._offsetLimit = toBounds(
13729 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
13730 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
13731 .add(this._map.getSize()));
13732
13733 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
13734 } else {
13735 this._offsetLimit = null;
13736 }
13737
13738 map
13739 .fire('movestart')
13740 .fire('dragstart');
13741
13742 if (map.options.inertia) {
13743 this._positions = [];
13744 this._times = [];
13745 }
13746 },
13747
13748 _onDrag: function (e) {
13749 if (this._map.options.inertia) {
13750 var time = this._lastTime = +new Date(),
13751 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
13752
13753 this._positions.push(pos);
13754 this._times.push(time);
13755
13756 this._prunePositions(time);
13757 }
13758
13759 this._map
13760 .fire('move', e)
13761 .fire('drag', e);
13762 },
13763
13764 _prunePositions: function (time) {
13765 while (this._positions.length > 1 && time - this._times[0] > 50) {
13766 this._positions.shift();
13767 this._times.shift();
13768 }
13769 },
13770
13771 _onZoomEnd: function () {
13772 var pxCenter = this._map.getSize().divideBy(2),
13773 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
13774
13775 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
13776 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
13777 },
13778
13779 _viscousLimit: function (value, threshold) {
13780 return value - (value - threshold) * this._viscosity;
13781 },
13782
13783 _onPreDragLimit: function () {
13784 if (!this._viscosity || !this._offsetLimit) { return; }
13785
13786 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
13787
13788 var limit = this._offsetLimit;
13789 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
13790 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
13791 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
13792 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
13793
13794 this._draggable._newPos = this._draggable._startPos.add(offset);
13795 },
13796
13797 _onPreDragWrap: function () {
13798 // TODO refactor to be able to adjust map pane position after zoom
13799 var worldWidth = this._worldWidth,
13800 halfWidth = Math.round(worldWidth / 2),
13801 dx = this._initialWorldOffset,
13802 x = this._draggable._newPos.x,
13803 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
13804 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
13805 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
13806
13807 this._draggable._absPos = this._draggable._newPos.clone();
13808 this._draggable._newPos.x = newX;
13809 },
13810
13811 _onDragEnd: function (e) {
13812 var map = this._map,
13813 options = map.options,
13814
13815 noInertia = !options.inertia || e.noInertia || this._times.length < 2;
13816
13817 map.fire('dragend', e);
13818
13819 if (noInertia) {
13820 map.fire('moveend');
13821
13822 } else {
13823 this._prunePositions(+new Date());
13824
13825 var direction = this._lastPos.subtract(this._positions[0]),
13826 duration = (this._lastTime - this._times[0]) / 1000,
13827 ease = options.easeLinearity,
13828
13829 speedVector = direction.multiplyBy(ease / duration),
13830 speed = speedVector.distanceTo([0, 0]),
13831
13832 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
13833 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
13834
13835 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
13836 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
13837
13838 if (!offset.x && !offset.y) {
13839 map.fire('moveend');
13840
13841 } else {
13842 offset = map._limitOffset(offset, map.options.maxBounds);
13843
13844 requestAnimFrame(function () {
13845 map.panBy(offset, {
13846 duration: decelerationDuration,
13847 easeLinearity: ease,
13848 noMoveStart: true,
13849 animate: true
13850 });
13851 });
13852 }
13853 }
13854 }
13855});
13856
13857// @section Handlers
13858// @property dragging: Handler
13859// Map dragging handler (by both mouse and touch).
13860Map.addInitHook('addHandler', 'dragging', Drag);
13861
13862/*
13863 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
13864 */
13865
13866// @namespace Map
13867// @section Keyboard Navigation Options
13868Map.mergeOptions({
13869 // @option keyboard: Boolean = true
13870 // Makes the map focusable and allows users to navigate the map with keyboard
13871 // arrows and `+`/`-` keys.
13872 keyboard: true,
13873
13874 // @option keyboardPanDelta: Number = 80
13875 // Amount of pixels to pan when pressing an arrow key.
13876 keyboardPanDelta: 80
13877});
13878
13879var Keyboard = Handler.extend({
13880
13881 keyCodes: {
13882 left: [37],
13883 right: [39],
13884 down: [40],
13885 up: [38],
13886 zoomIn: [187, 107, 61, 171],
13887 zoomOut: [189, 109, 54, 173]
13888 },
13889
13890 initialize: function (map) {
13891 this._map = map;
13892
13893 this._setPanDelta(map.options.keyboardPanDelta);
13894 this._setZoomDelta(map.options.zoomDelta);
13895 },
13896
13897 addHooks: function () {
13898 var container = this._map._container;
13899
13900 // make the container focusable by tabbing
13901 if (container.tabIndex <= 0) {
13902 container.tabIndex = '0';
13903 }
13904
13905 on(container, {
13906 focus: this._onFocus,
13907 blur: this._onBlur,
13908 mousedown: this._onMouseDown
13909 }, this);
13910
13911 this._map.on({
13912 focus: this._addHooks,
13913 blur: this._removeHooks
13914 }, this);
13915 },
13916
13917 removeHooks: function () {
13918 this._removeHooks();
13919
13920 off(this._map._container, {
13921 focus: this._onFocus,
13922 blur: this._onBlur,
13923 mousedown: this._onMouseDown
13924 }, this);
13925
13926 this._map.off({
13927 focus: this._addHooks,
13928 blur: this._removeHooks
13929 }, this);
13930 },
13931
13932 _onMouseDown: function () {
13933 if (this._focused) { return; }
13934
13935 var body = document.body,
13936 docEl = document.documentElement,
13937 top = body.scrollTop || docEl.scrollTop,
13938 left = body.scrollLeft || docEl.scrollLeft;
13939
13940 this._map._container.focus();
13941
13942 window.scrollTo(left, top);
13943 },
13944
13945 _onFocus: function () {
13946 this._focused = true;
13947 this._map.fire('focus');
13948 },
13949
13950 _onBlur: function () {
13951 this._focused = false;
13952 this._map.fire('blur');
13953 },
13954
13955 _setPanDelta: function (panDelta) {
13956 var keys = this._panKeys = {},
13957 codes = this.keyCodes,
13958 i, len;
13959
13960 for (i = 0, len = codes.left.length; i < len; i++) {
13961 keys[codes.left[i]] = [-1 * panDelta, 0];
13962 }
13963 for (i = 0, len = codes.right.length; i < len; i++) {
13964 keys[codes.right[i]] = [panDelta, 0];
13965 }
13966 for (i = 0, len = codes.down.length; i < len; i++) {
13967 keys[codes.down[i]] = [0, panDelta];
13968 }
13969 for (i = 0, len = codes.up.length; i < len; i++) {
13970 keys[codes.up[i]] = [0, -1 * panDelta];
13971 }
13972 },
13973
13974 _setZoomDelta: function (zoomDelta) {
13975 var keys = this._zoomKeys = {},
13976 codes = this.keyCodes,
13977 i, len;
13978
13979 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
13980 keys[codes.zoomIn[i]] = zoomDelta;
13981 }
13982 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
13983 keys[codes.zoomOut[i]] = -zoomDelta;
13984 }
13985 },
13986
13987 _addHooks: function () {
13988 on(document, 'keydown', this._onKeyDown, this);
13989 },
13990
13991 _removeHooks: function () {
13992 off(document, 'keydown', this._onKeyDown, this);
13993 },
13994
13995 _onKeyDown: function (e) {
13996 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
13997
13998 var key = e.keyCode,
13999 map = this._map,
14000 offset;
14001
14002 if (key in this._panKeys) {
14003 if (!map._panAnim || !map._panAnim._inProgress) {
14004 offset = this._panKeys[key];
14005 if (e.shiftKey) {
14006 offset = toPoint(offset).multiplyBy(3);
14007 }
14008
14009 if (map.options.maxBounds) {
14010 offset = map._limitOffset(toPoint(offset), map.options.maxBounds);
14011 }
14012
14013 if (map.options.worldCopyJump) {
14014 var newLatLng = map.wrapLatLng(map.unproject(map.project(map.getCenter()).add(offset)));
14015 map.panTo(newLatLng);
14016 } else {
14017 map.panBy(offset);
14018 }
14019 }
14020 } else if (key in this._zoomKeys) {
14021 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
14022
14023 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
14024 map.closePopup();
14025
14026 } else {
14027 return;
14028 }
14029
14030 stop(e);
14031 }
14032});
14033
14034// @section Handlers
14035// @section Handlers
14036// @property keyboard: Handler
14037// Keyboard navigation handler.
14038Map.addInitHook('addHandler', 'keyboard', Keyboard);
14039
14040/*
14041 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
14042 */
14043
14044// @namespace Map
14045// @section Interaction Options
14046Map.mergeOptions({
14047 // @section Mouse wheel options
14048 // @option scrollWheelZoom: Boolean|String = true
14049 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
14050 // it will zoom to the center of the view regardless of where the mouse was.
14051 scrollWheelZoom: true,
14052
14053 // @option wheelDebounceTime: Number = 40
14054 // Limits the rate at which a wheel can fire (in milliseconds). By default
14055 // user can't zoom via wheel more often than once per 40 ms.
14056 wheelDebounceTime: 40,
14057
14058 // @option wheelPxPerZoomLevel: Number = 60
14059 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
14060 // mean a change of one full zoom level. Smaller values will make wheel-zooming
14061 // faster (and vice versa).
14062 wheelPxPerZoomLevel: 60
14063});
14064
14065var ScrollWheelZoom = Handler.extend({
14066 addHooks: function () {
14067 on(this._map._container, 'wheel', this._onWheelScroll, this);
14068
14069 this._delta = 0;
14070 },
14071
14072 removeHooks: function () {
14073 off(this._map._container, 'wheel', this._onWheelScroll, this);
14074 },
14075
14076 _onWheelScroll: function (e) {
14077 var delta = getWheelDelta(e);
14078
14079 var debounce = this._map.options.wheelDebounceTime;
14080
14081 this._delta += delta;
14082 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
14083
14084 if (!this._startTime) {
14085 this._startTime = +new Date();
14086 }
14087
14088 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
14089
14090 clearTimeout(this._timer);
14091 this._timer = setTimeout(bind(this._performZoom, this), left);
14092
14093 stop(e);
14094 },
14095
14096 _performZoom: function () {
14097 var map = this._map,
14098 zoom = map.getZoom(),
14099 snap = this._map.options.zoomSnap || 0;
14100
14101 map._stop(); // stop panning and fly animations if any
14102
14103 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
14104 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
14105 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
14106 d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
14107 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
14108
14109 this._delta = 0;
14110 this._startTime = null;
14111
14112 if (!delta) { return; }
14113
14114 if (map.options.scrollWheelZoom === 'center') {
14115 map.setZoom(zoom + delta);
14116 } else {
14117 map.setZoomAround(this._lastMousePos, zoom + delta);
14118 }
14119 }
14120});
14121
14122// @section Handlers
14123// @property scrollWheelZoom: Handler
14124// Scroll wheel zoom handler.
14125Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
14126
14127/*
14128 * L.Map.TapHold is used to simulate `contextmenu` event on long hold,
14129 * which otherwise is not fired by mobile Safari.
14130 */
14131
14132var tapHoldDelay = 600;
14133
14134// @namespace Map
14135// @section Interaction Options
14136Map.mergeOptions({
14137 // @section Touch interaction options
14138 // @option tapHold: Boolean
14139 // Enables simulation of `contextmenu` event, default is `true` for mobile Safari.
14140 tapHold: Browser.touchNative && Browser.safari && Browser.mobile,
14141
14142 // @option tapTolerance: Number = 15
14143 // The max number of pixels a user can shift his finger during touch
14144 // for it to be considered a valid tap.
14145 tapTolerance: 15
14146});
14147
14148var TapHold = Handler.extend({
14149 addHooks: function () {
14150 on(this._map._container, 'touchstart', this._onDown, this);
14151 },
14152
14153 removeHooks: function () {
14154 off(this._map._container, 'touchstart', this._onDown, this);
14155 },
14156
14157 _onDown: function (e) {
14158 clearTimeout(this._holdTimeout);
14159 if (e.touches.length !== 1) { return; }
14160
14161 var first = e.touches[0];
14162 this._startPos = this._newPos = new Point(first.clientX, first.clientY);
14163
14164 this._holdTimeout = setTimeout(bind(function () {
14165 this._cancel();
14166 if (!this._isTapValid()) { return; }
14167
14168 // prevent simulated mouse events https://w3c.github.io/touch-events/#mouse-events
14169 on(document, 'touchend', preventDefault);
14170 on(document, 'touchend touchcancel', this._cancelClickPrevent);
14171 this._simulateEvent('contextmenu', first);
14172 }, this), tapHoldDelay);
14173
14174 on(document, 'touchend touchcancel contextmenu', this._cancel, this);
14175 on(document, 'touchmove', this._onMove, this);
14176 },
14177
14178 _cancelClickPrevent: function cancelClickPrevent() {
14179 off(document, 'touchend', preventDefault);
14180 off(document, 'touchend touchcancel', cancelClickPrevent);
14181 },
14182
14183 _cancel: function () {
14184 clearTimeout(this._holdTimeout);
14185 off(document, 'touchend touchcancel contextmenu', this._cancel, this);
14186 off(document, 'touchmove', this._onMove, this);
14187 },
14188
14189 _onMove: function (e) {
14190 var first = e.touches[0];
14191 this._newPos = new Point(first.clientX, first.clientY);
14192 },
14193
14194 _isTapValid: function () {
14195 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
14196 },
14197
14198 _simulateEvent: function (type, e) {
14199 var simulatedEvent = new MouseEvent(type, {
14200 bubbles: true,
14201 cancelable: true,
14202 view: window,
14203 // detail: 1,
14204 screenX: e.screenX,
14205 screenY: e.screenY,
14206 clientX: e.clientX,
14207 clientY: e.clientY,
14208 // button: 2,
14209 // buttons: 2
14210 });
14211
14212 simulatedEvent._simulated = true;
14213
14214 e.target.dispatchEvent(simulatedEvent);
14215 }
14216});
14217
14218// @section Handlers
14219// @property tapHold: Handler
14220// Long tap handler to simulate `contextmenu` event (useful in mobile Safari).
14221Map.addInitHook('addHandler', 'tapHold', TapHold);
14222
14223/*
14224 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
14225 */
14226
14227// @namespace Map
14228// @section Interaction Options
14229Map.mergeOptions({
14230 // @section Touch interaction options
14231 // @option touchZoom: Boolean|String = *
14232 // Whether the map can be zoomed by touch-dragging with two fingers. If
14233 // passed `'center'`, it will zoom to the center of the view regardless of
14234 // where the touch events (fingers) were. Enabled for touch-capable web
14235 // browsers.
14236 touchZoom: Browser.touch,
14237
14238 // @option bounceAtZoomLimits: Boolean = true
14239 // Set it to false if you don't want the map to zoom beyond min/max zoom
14240 // and then bounce back when pinch-zooming.
14241 bounceAtZoomLimits: true
14242});
14243
14244var TouchZoom = Handler.extend({
14245 addHooks: function () {
14246 addClass(this._map._container, 'leaflet-touch-zoom');
14247 on(this._map._container, 'touchstart', this._onTouchStart, this);
14248 },
14249
14250 removeHooks: function () {
14251 removeClass(this._map._container, 'leaflet-touch-zoom');
14252 off(this._map._container, 'touchstart', this._onTouchStart, this);
14253 },
14254
14255 _onTouchStart: function (e) {
14256 var map = this._map;
14257 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
14258
14259 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
14260 p2 = map.mouseEventToContainerPoint(e.touches[1]);
14261
14262 this._centerPoint = map.getSize()._divideBy(2);
14263 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
14264 if (map.options.touchZoom !== 'center') {
14265 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
14266 }
14267
14268 this._startDist = p1.distanceTo(p2);
14269 this._startZoom = map.getZoom();
14270
14271 this._moved = false;
14272 this._zooming = true;
14273
14274 map._stop();
14275
14276 on(document, 'touchmove', this._onTouchMove, this);
14277 on(document, 'touchend touchcancel', this._onTouchEnd, this);
14278
14279 preventDefault(e);
14280 },
14281
14282 _onTouchMove: function (e) {
14283 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
14284
14285 var map = this._map,
14286 p1 = map.mouseEventToContainerPoint(e.touches[0]),
14287 p2 = map.mouseEventToContainerPoint(e.touches[1]),
14288 scale = p1.distanceTo(p2) / this._startDist;
14289
14290 this._zoom = map.getScaleZoom(scale, this._startZoom);
14291
14292 if (!map.options.bounceAtZoomLimits && (
14293 (this._zoom < map.getMinZoom() && scale < 1) ||
14294 (this._zoom > map.getMaxZoom() && scale > 1))) {
14295 this._zoom = map._limitZoom(this._zoom);
14296 }
14297
14298 if (map.options.touchZoom === 'center') {
14299 this._center = this._startLatLng;
14300 if (scale === 1) { return; }
14301 } else {
14302 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
14303 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
14304 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
14305 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
14306 }
14307
14308 if (!this._moved) {
14309 map._moveStart(true, false);
14310 this._moved = true;
14311 }
14312
14313 cancelAnimFrame(this._animRequest);
14314
14315 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false}, undefined);
14316 this._animRequest = requestAnimFrame(moveFn, this, true);
14317
14318 preventDefault(e);
14319 },
14320
14321 _onTouchEnd: function () {
14322 if (!this._moved || !this._zooming) {
14323 this._zooming = false;
14324 return;
14325 }
14326
14327 this._zooming = false;
14328 cancelAnimFrame(this._animRequest);
14329
14330 off(document, 'touchmove', this._onTouchMove, this);
14331 off(document, 'touchend touchcancel', this._onTouchEnd, this);
14332
14333 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
14334 if (this._map.options.zoomAnimation) {
14335 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
14336 } else {
14337 this._map._resetView(this._center, this._map._limitZoom(this._zoom));
14338 }
14339 }
14340});
14341
14342// @section Handlers
14343// @property touchZoom: Handler
14344// Touch zoom handler.
14345Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
14346
14347Map.BoxZoom = BoxZoom;
14348Map.DoubleClickZoom = DoubleClickZoom;
14349Map.Drag = Drag;
14350Map.Keyboard = Keyboard;
14351Map.ScrollWheelZoom = ScrollWheelZoom;
14352Map.TapHold = TapHold;
14353Map.TouchZoom = TouchZoom;
14354
14355export { 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 };
14356//# sourceMappingURL=leaflet-src.esm.js.map