UNPKG

406 kBJavaScriptView Raw
1/* @preserve
2 * Leaflet 1.7.1, a JS library for interactive maps. http://leafletjs.com
3 * (c) 2010-2019 Vladimir Agafonkin, (c) 2010-2011 CloudMade
4 */
5
6var version = "1.7.1";
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 = 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 /*eslint-disable */
63 obj._leaflet_id = obj._leaflet_id || ++lastId;
64 return obj._leaflet_id;
65 /* eslint-enable */
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, digits?: Number): Number
119// Returns the number `num` rounded to `digits` decimals, or to 6 decimals by default.
120function formatNum(num, digits) {
121 var pow = Math.pow(10, (digits === undefined ? 6 : digits));
122 return Math.round(num * pow) / pow;
123}
124
125// @function trim(str: String): String
126// Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
127function trim(str) {
128 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
129}
130
131// @function splitWords(str: String): String[]
132// Trims and splits the string on whitespace and returns the array of parts.
133function splitWords(str) {
134 return trim(str).split(/\s+/);
135}
136
137// @function setOptions(obj: Object, options: Object): Object
138// Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
139function setOptions(obj, options) {
140 if (!Object.prototype.hasOwnProperty.call(obj, 'options')) {
141 obj.options = obj.options ? create(obj.options) : {};
142 }
143 for (var i in options) {
144 obj.options[i] = options[i];
145 }
146 return obj.options;
147}
148
149// @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
150// Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
151// translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
152// be appended at the end. If `uppercase` is `true`, the parameter names will
153// be uppercased (e.g. `'?A=foo&B=bar'`)
154function getParamString(obj, existingUrl, uppercase) {
155 var params = [];
156 for (var i in obj) {
157 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
158 }
159 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
160}
161
162var templateRe = /\{ *([\w_-]+) *\}/g;
163
164// @function template(str: String, data: Object): String
165// Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
166// and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
167// `('Hello foo, bar')`. You can also specify functions instead of strings for
168// data values — they will be evaluated passing `data` as an argument.
169function template(str, data) {
170 return str.replace(templateRe, function (str, key) {
171 var value = data[key];
172
173 if (value === undefined) {
174 throw new Error('No value provided for variable ' + str);
175
176 } else if (typeof value === 'function') {
177 value = value(data);
178 }
179 return value;
180 });
181}
182
183// @function isArray(obj): Boolean
184// Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
185var isArray = Array.isArray || function (obj) {
186 return (Object.prototype.toString.call(obj) === '[object Array]');
187};
188
189// @function indexOf(array: Array, el: Object): Number
190// Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
191function indexOf(array, el) {
192 for (var i = 0; i < array.length; i++) {
193 if (array[i] === el) { return i; }
194 }
195 return -1;
196}
197
198// @property emptyImageUrl: String
199// Data URI string containing a base64-encoded empty GIF image.
200// Used as a hack to free memory from unused images on WebKit-powered
201// mobile devices (by setting image `src` to this string).
202var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
203
204// inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
205
206function getPrefixed(name) {
207 return window['webkit' + name] || window['moz' + name] || window['ms' + name];
208}
209
210var lastTime = 0;
211
212// fallback for IE 7-8
213function timeoutDefer(fn) {
214 var time = +new Date(),
215 timeToCall = Math.max(0, 16 - (time - lastTime));
216
217 lastTime = time + timeToCall;
218 return window.setTimeout(fn, timeToCall);
219}
220
221var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
222var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
223 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
224
225// @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
226// Schedules `fn` to be executed when the browser repaints. `fn` is bound to
227// `context` if given. When `immediate` is set, `fn` is called immediately if
228// the browser doesn't have native support for
229// [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
230// otherwise it's delayed. Returns a request ID that can be used to cancel the request.
231function requestAnimFrame(fn, context, immediate) {
232 if (immediate && requestFn === timeoutDefer) {
233 fn.call(context);
234 } else {
235 return requestFn.call(window, bind(fn, context));
236 }
237}
238
239// @function cancelAnimFrame(id: Number): undefined
240// Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
241function cancelAnimFrame(id) {
242 if (id) {
243 cancelFn.call(window, id);
244 }
245}
246
247var Util = ({
248 extend: extend,
249 create: create,
250 bind: bind,
251 get lastId () { return lastId; },
252 stamp: stamp,
253 throttle: throttle,
254 wrapNum: wrapNum,
255 falseFn: falseFn,
256 formatNum: formatNum,
257 trim: trim,
258 splitWords: splitWords,
259 setOptions: setOptions,
260 getParamString: getParamString,
261 template: template,
262 isArray: isArray,
263 indexOf: indexOf,
264 emptyImageUrl: emptyImageUrl,
265 requestFn: requestFn,
266 cancelFn: cancelFn,
267 requestAnimFrame: requestAnimFrame,
268 cancelAnimFrame: cancelAnimFrame
269});
270
271// @class Class
272// @aka L.Class
273
274// @section
275// @uninheritable
276
277// Thanks to John Resig and Dean Edwards for inspiration!
278
279function Class() {}
280
281Class.extend = function (props) {
282
283 // @function extend(props: Object): Function
284 // [Extends the current class](#class-inheritance) given the properties to be included.
285 // Returns a Javascript function that is a class constructor (to be called with `new`).
286 var NewClass = function () {
287
288 // call the constructor
289 if (this.initialize) {
290 this.initialize.apply(this, arguments);
291 }
292
293 // call all constructor hooks
294 this.callInitHooks();
295 };
296
297 var parentProto = NewClass.__super__ = this.prototype;
298
299 var proto = create(parentProto);
300 proto.constructor = NewClass;
301
302 NewClass.prototype = proto;
303
304 // inherit parent's statics
305 for (var i in this) {
306 if (Object.prototype.hasOwnProperty.call(this, i) && i !== 'prototype' && i !== '__super__') {
307 NewClass[i] = this[i];
308 }
309 }
310
311 // mix static properties into the class
312 if (props.statics) {
313 extend(NewClass, props.statics);
314 delete props.statics;
315 }
316
317 // mix includes into the prototype
318 if (props.includes) {
319 checkDeprecatedMixinEvents(props.includes);
320 extend.apply(null, [proto].concat(props.includes));
321 delete props.includes;
322 }
323
324 // merge options
325 if (proto.options) {
326 props.options = extend(create(proto.options), props.options);
327 }
328
329 // mix given properties into the prototype
330 extend(proto, props);
331
332 proto._initHooks = [];
333
334 // add method for calling all hooks
335 proto.callInitHooks = function () {
336
337 if (this._initHooksCalled) { return; }
338
339 if (parentProto.callInitHooks) {
340 parentProto.callInitHooks.call(this);
341 }
342
343 this._initHooksCalled = true;
344
345 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
346 proto._initHooks[i].call(this);
347 }
348 };
349
350 return NewClass;
351};
352
353
354// @function include(properties: Object): this
355// [Includes a mixin](#class-includes) into the current class.
356Class.include = function (props) {
357 extend(this.prototype, props);
358 return this;
359};
360
361// @function mergeOptions(options: Object): this
362// [Merges `options`](#class-options) into the defaults of the class.
363Class.mergeOptions = function (options) {
364 extend(this.prototype.options, options);
365 return this;
366};
367
368// @function addInitHook(fn: Function): this
369// Adds a [constructor hook](#class-constructor-hooks) to the class.
370Class.addInitHook = function (fn) { // (Function) || (String, args...)
371 var args = Array.prototype.slice.call(arguments, 1);
372
373 var init = typeof fn === 'function' ? fn : function () {
374 this[fn].apply(this, args);
375 };
376
377 this.prototype._initHooks = this.prototype._initHooks || [];
378 this.prototype._initHooks.push(init);
379 return this;
380};
381
382function checkDeprecatedMixinEvents(includes) {
383 if (typeof L === 'undefined' || !L || !L.Mixin) { return; }
384
385 includes = isArray(includes) ? includes : [includes];
386
387 for (var i = 0; i < includes.length; i++) {
388 if (includes[i] === L.Mixin.Events) {
389 console.warn('Deprecated include of L.Mixin.Events: ' +
390 'this property will be removed in future releases, ' +
391 'please inherit from L.Evented instead.', new Error().stack);
392 }
393 }
394}
395
396/*
397 * @class Evented
398 * @aka L.Evented
399 * @inherits Class
400 *
401 * 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).
402 *
403 * @example
404 *
405 * ```js
406 * map.on('click', function(e) {
407 * alert(e.latlng);
408 * } );
409 * ```
410 *
411 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
412 *
413 * ```js
414 * function onClick(e) { ... }
415 *
416 * map.on('click', onClick);
417 * map.off('click', onClick);
418 * ```
419 */
420
421var Events = {
422 /* @method on(type: String, fn: Function, context?: Object): this
423 * 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'`).
424 *
425 * @alternative
426 * @method on(eventMap: Object): this
427 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
428 */
429 on: function (types, fn, context) {
430
431 // types can be a map of types/handlers
432 if (typeof types === 'object') {
433 for (var type in types) {
434 // we don't process space-separated events here for performance;
435 // it's a hot path since Layer uses the on(obj) syntax
436 this._on(type, types[type], fn);
437 }
438
439 } else {
440 // types can be a string of space-separated words
441 types = splitWords(types);
442
443 for (var i = 0, len = types.length; i < len; i++) {
444 this._on(types[i], fn, context);
445 }
446 }
447
448 return this;
449 },
450
451 /* @method off(type: String, fn?: Function, context?: Object): this
452 * 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.
453 *
454 * @alternative
455 * @method off(eventMap: Object): this
456 * Removes a set of type/listener pairs.
457 *
458 * @alternative
459 * @method off: this
460 * Removes all listeners to all events on the object. This includes implicitly attached events.
461 */
462 off: function (types, fn, context) {
463
464 if (!types) {
465 // clear all listeners if called without arguments
466 delete this._events;
467
468 } else if (typeof types === 'object') {
469 for (var type in types) {
470 this._off(type, types[type], fn);
471 }
472
473 } else {
474 types = splitWords(types);
475
476 for (var i = 0, len = types.length; i < len; i++) {
477 this._off(types[i], fn, context);
478 }
479 }
480
481 return this;
482 },
483
484 // attach listener (without syntactic sugar now)
485 _on: function (type, fn, context) {
486 this._events = this._events || {};
487
488 /* get/init listeners for type */
489 var typeListeners = this._events[type];
490 if (!typeListeners) {
491 typeListeners = [];
492 this._events[type] = typeListeners;
493 }
494
495 if (context === this) {
496 // Less memory footprint.
497 context = undefined;
498 }
499 var newListener = {fn: fn, ctx: context},
500 listeners = typeListeners;
501
502 // check if fn already there
503 for (var i = 0, len = listeners.length; i < len; i++) {
504 if (listeners[i].fn === fn && listeners[i].ctx === context) {
505 return;
506 }
507 }
508
509 listeners.push(newListener);
510 },
511
512 _off: function (type, fn, context) {
513 var listeners,
514 i,
515 len;
516
517 if (!this._events) { return; }
518
519 listeners = this._events[type];
520
521 if (!listeners) {
522 return;
523 }
524
525 if (!fn) {
526 // Set all removed listeners to noop so they are not called if remove happens in fire
527 for (i = 0, len = listeners.length; i < len; i++) {
528 listeners[i].fn = falseFn;
529 }
530 // clear all listeners for a type if function isn't specified
531 delete this._events[type];
532 return;
533 }
534
535 if (context === this) {
536 context = undefined;
537 }
538
539 if (listeners) {
540
541 // find fn and remove it
542 for (i = 0, len = listeners.length; i < len; i++) {
543 var l = listeners[i];
544 if (l.ctx !== context) { continue; }
545 if (l.fn === fn) {
546
547 // set the removed listener to noop so that's not called if remove happens in fire
548 l.fn = falseFn;
549
550 if (this._firingCount) {
551 /* copy array in case events are being fired */
552 this._events[type] = listeners = listeners.slice();
553 }
554 listeners.splice(i, 1);
555
556 return;
557 }
558 }
559 }
560 },
561
562 // @method fire(type: String, data?: Object, propagate?: Boolean): this
563 // Fires an event of the specified type. You can optionally provide an data
564 // object — the first argument of the listener function will contain its
565 // properties. The event can optionally be propagated to event parents.
566 fire: function (type, data, propagate) {
567 if (!this.listens(type, propagate)) { return this; }
568
569 var event = extend({}, data, {
570 type: type,
571 target: this,
572 sourceTarget: data && data.sourceTarget || this
573 });
574
575 if (this._events) {
576 var listeners = this._events[type];
577
578 if (listeners) {
579 this._firingCount = (this._firingCount + 1) || 1;
580 for (var i = 0, len = listeners.length; i < len; i++) {
581 var l = listeners[i];
582 l.fn.call(l.ctx || this, event);
583 }
584
585 this._firingCount--;
586 }
587 }
588
589 if (propagate) {
590 // propagate the event to parents (set with addEventParent)
591 this._propagateEvent(event);
592 }
593
594 return this;
595 },
596
597 // @method listens(type: String): Boolean
598 // Returns `true` if a particular event type has any listeners attached to it.
599 listens: function (type, propagate) {
600 var listeners = this._events && this._events[type];
601 if (listeners && listeners.length) { return true; }
602
603 if (propagate) {
604 // also check parents for listeners if event propagates
605 for (var id in this._eventParents) {
606 if (this._eventParents[id].listens(type, propagate)) { return true; }
607 }
608 }
609 return false;
610 },
611
612 // @method once(…): this
613 // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
614 once: function (types, fn, context) {
615
616 if (typeof types === 'object') {
617 for (var type in types) {
618 this.once(type, types[type], fn);
619 }
620 return this;
621 }
622
623 var handler = bind(function () {
624 this
625 .off(types, fn, context)
626 .off(types, handler, context);
627 }, this);
628
629 // add a listener that's executed once and removed after that
630 return this
631 .on(types, fn, context)
632 .on(types, handler, context);
633 },
634
635 // @method addEventParent(obj: Evented): this
636 // Adds an event parent - an `Evented` that will receive propagated events
637 addEventParent: function (obj) {
638 this._eventParents = this._eventParents || {};
639 this._eventParents[stamp(obj)] = obj;
640 return this;
641 },
642
643 // @method removeEventParent(obj: Evented): this
644 // Removes an event parent, so it will stop receiving propagated events
645 removeEventParent: function (obj) {
646 if (this._eventParents) {
647 delete this._eventParents[stamp(obj)];
648 }
649 return this;
650 },
651
652 _propagateEvent: function (e) {
653 for (var id in this._eventParents) {
654 this._eventParents[id].fire(e.type, extend({
655 layer: e.target,
656 propagatedFrom: e.target
657 }, e), true);
658 }
659 }
660};
661
662// aliases; we should ditch those eventually
663
664// @method addEventListener(…): this
665// Alias to [`on(…)`](#evented-on)
666Events.addEventListener = Events.on;
667
668// @method removeEventListener(…): this
669// Alias to [`off(…)`](#evented-off)
670
671// @method clearAllEventListeners(…): this
672// Alias to [`off()`](#evented-off)
673Events.removeEventListener = Events.clearAllEventListeners = Events.off;
674
675// @method addOneTimeEventListener(…): this
676// Alias to [`once(…)`](#evented-once)
677Events.addOneTimeEventListener = Events.once;
678
679// @method fireEvent(…): this
680// Alias to [`fire(…)`](#evented-fire)
681Events.fireEvent = Events.fire;
682
683// @method hasEventListeners(…): Boolean
684// Alias to [`listens(…)`](#evented-listens)
685Events.hasEventListeners = Events.listens;
686
687var Evented = Class.extend(Events);
688
689/*
690 * @class Point
691 * @aka L.Point
692 *
693 * Represents a point with `x` and `y` coordinates in pixels.
694 *
695 * @example
696 *
697 * ```js
698 * var point = L.point(200, 300);
699 * ```
700 *
701 * 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:
702 *
703 * ```js
704 * map.panBy([200, 300]);
705 * map.panBy(L.point(200, 300));
706 * ```
707 *
708 * Note that `Point` does not inherit from Leaflet's `Class` object,
709 * which means new classes can't inherit from it, and new methods
710 * can't be added to it with the `include` function.
711 */
712
713function Point(x, y, round) {
714 // @property x: Number; The `x` coordinate of the point
715 this.x = (round ? Math.round(x) : x);
716 // @property y: Number; The `y` coordinate of the point
717 this.y = (round ? Math.round(y) : y);
718}
719
720var trunc = Math.trunc || function (v) {
721 return v > 0 ? Math.floor(v) : Math.ceil(v);
722};
723
724Point.prototype = {
725
726 // @method clone(): Point
727 // Returns a copy of the current point.
728 clone: function () {
729 return new Point(this.x, this.y);
730 },
731
732 // @method add(otherPoint: Point): Point
733 // Returns the result of addition of the current and the given points.
734 add: function (point) {
735 // non-destructive, returns a new point
736 return this.clone()._add(toPoint(point));
737 },
738
739 _add: function (point) {
740 // destructive, used directly for performance in situations where it's safe to modify existing point
741 this.x += point.x;
742 this.y += point.y;
743 return this;
744 },
745
746 // @method subtract(otherPoint: Point): Point
747 // Returns the result of subtraction of the given point from the current.
748 subtract: function (point) {
749 return this.clone()._subtract(toPoint(point));
750 },
751
752 _subtract: function (point) {
753 this.x -= point.x;
754 this.y -= point.y;
755 return this;
756 },
757
758 // @method divideBy(num: Number): Point
759 // Returns the result of division of the current point by the given number.
760 divideBy: function (num) {
761 return this.clone()._divideBy(num);
762 },
763
764 _divideBy: function (num) {
765 this.x /= num;
766 this.y /= num;
767 return this;
768 },
769
770 // @method multiplyBy(num: Number): Point
771 // Returns the result of multiplication of the current point by the given number.
772 multiplyBy: function (num) {
773 return this.clone()._multiplyBy(num);
774 },
775
776 _multiplyBy: function (num) {
777 this.x *= num;
778 this.y *= num;
779 return this;
780 },
781
782 // @method scaleBy(scale: Point): Point
783 // Multiply each coordinate of the current point by each coordinate of
784 // `scale`. In linear algebra terms, multiply the point by the
785 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
786 // defined by `scale`.
787 scaleBy: function (point) {
788 return new Point(this.x * point.x, this.y * point.y);
789 },
790
791 // @method unscaleBy(scale: Point): Point
792 // Inverse of `scaleBy`. Divide each coordinate of the current point by
793 // each coordinate of `scale`.
794 unscaleBy: function (point) {
795 return new Point(this.x / point.x, this.y / point.y);
796 },
797
798 // @method round(): Point
799 // Returns a copy of the current point with rounded coordinates.
800 round: function () {
801 return this.clone()._round();
802 },
803
804 _round: function () {
805 this.x = Math.round(this.x);
806 this.y = Math.round(this.y);
807 return this;
808 },
809
810 // @method floor(): Point
811 // Returns a copy of the current point with floored coordinates (rounded down).
812 floor: function () {
813 return this.clone()._floor();
814 },
815
816 _floor: function () {
817 this.x = Math.floor(this.x);
818 this.y = Math.floor(this.y);
819 return this;
820 },
821
822 // @method ceil(): Point
823 // Returns a copy of the current point with ceiled coordinates (rounded up).
824 ceil: function () {
825 return this.clone()._ceil();
826 },
827
828 _ceil: function () {
829 this.x = Math.ceil(this.x);
830 this.y = Math.ceil(this.y);
831 return this;
832 },
833
834 // @method trunc(): Point
835 // Returns a copy of the current point with truncated coordinates (rounded towards zero).
836 trunc: function () {
837 return this.clone()._trunc();
838 },
839
840 _trunc: function () {
841 this.x = trunc(this.x);
842 this.y = trunc(this.y);
843 return this;
844 },
845
846 // @method distanceTo(otherPoint: Point): Number
847 // Returns the cartesian distance between the current and the given points.
848 distanceTo: function (point) {
849 point = toPoint(point);
850
851 var x = point.x - this.x,
852 y = point.y - this.y;
853
854 return Math.sqrt(x * x + y * y);
855 },
856
857 // @method equals(otherPoint: Point): Boolean
858 // Returns `true` if the given point has the same coordinates.
859 equals: function (point) {
860 point = toPoint(point);
861
862 return point.x === this.x &&
863 point.y === this.y;
864 },
865
866 // @method contains(otherPoint: Point): Boolean
867 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
868 contains: function (point) {
869 point = toPoint(point);
870
871 return Math.abs(point.x) <= Math.abs(this.x) &&
872 Math.abs(point.y) <= Math.abs(this.y);
873 },
874
875 // @method toString(): String
876 // Returns a string representation of the point for debugging purposes.
877 toString: function () {
878 return 'Point(' +
879 formatNum(this.x) + ', ' +
880 formatNum(this.y) + ')';
881 }
882};
883
884// @factory L.point(x: Number, y: Number, round?: Boolean)
885// Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
886
887// @alternative
888// @factory L.point(coords: Number[])
889// Expects an array of the form `[x, y]` instead.
890
891// @alternative
892// @factory L.point(coords: Object)
893// Expects a plain object of the form `{x: Number, y: Number}` instead.
894function toPoint(x, y, round) {
895 if (x instanceof Point) {
896 return x;
897 }
898 if (isArray(x)) {
899 return new Point(x[0], x[1]);
900 }
901 if (x === undefined || x === null) {
902 return x;
903 }
904 if (typeof x === 'object' && 'x' in x && 'y' in x) {
905 return new Point(x.x, x.y);
906 }
907 return new Point(x, y, round);
908}
909
910/*
911 * @class Bounds
912 * @aka L.Bounds
913 *
914 * Represents a rectangular area in pixel coordinates.
915 *
916 * @example
917 *
918 * ```js
919 * var p1 = L.point(10, 10),
920 * p2 = L.point(40, 60),
921 * bounds = L.bounds(p1, p2);
922 * ```
923 *
924 * 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:
925 *
926 * ```js
927 * otherBounds.intersects([[10, 10], [40, 60]]);
928 * ```
929 *
930 * Note that `Bounds` does not inherit from Leaflet's `Class` object,
931 * which means new classes can't inherit from it, and new methods
932 * can't be added to it with the `include` function.
933 */
934
935function Bounds(a, b) {
936 if (!a) { return; }
937
938 var points = b ? [a, b] : a;
939
940 for (var i = 0, len = points.length; i < len; i++) {
941 this.extend(points[i]);
942 }
943}
944
945Bounds.prototype = {
946 // @method extend(point: Point): this
947 // Extends the bounds to contain the given point.
948 extend: function (point) { // (Point)
949 point = toPoint(point);
950
951 // @property min: Point
952 // The top left corner of the rectangle.
953 // @property max: Point
954 // The bottom right corner of the rectangle.
955 if (!this.min && !this.max) {
956 this.min = point.clone();
957 this.max = point.clone();
958 } else {
959 this.min.x = Math.min(point.x, this.min.x);
960 this.max.x = Math.max(point.x, this.max.x);
961 this.min.y = Math.min(point.y, this.min.y);
962 this.max.y = Math.max(point.y, this.max.y);
963 }
964 return this;
965 },
966
967 // @method getCenter(round?: Boolean): Point
968 // Returns the center point of the bounds.
969 getCenter: function (round) {
970 return new Point(
971 (this.min.x + this.max.x) / 2,
972 (this.min.y + this.max.y) / 2, round);
973 },
974
975 // @method getBottomLeft(): Point
976 // Returns the bottom-left point of the bounds.
977 getBottomLeft: function () {
978 return new Point(this.min.x, this.max.y);
979 },
980
981 // @method getTopRight(): Point
982 // Returns the top-right point of the bounds.
983 getTopRight: function () { // -> Point
984 return new Point(this.max.x, this.min.y);
985 },
986
987 // @method getTopLeft(): Point
988 // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).
989 getTopLeft: function () {
990 return this.min; // left, top
991 },
992
993 // @method getBottomRight(): Point
994 // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).
995 getBottomRight: function () {
996 return this.max; // right, bottom
997 },
998
999 // @method getSize(): Point
1000 // Returns the size of the given bounds
1001 getSize: function () {
1002 return this.max.subtract(this.min);
1003 },
1004
1005 // @method contains(otherBounds: Bounds): Boolean
1006 // Returns `true` if the rectangle contains the given one.
1007 // @alternative
1008 // @method contains(point: Point): Boolean
1009 // Returns `true` if the rectangle contains the given point.
1010 contains: function (obj) {
1011 var min, max;
1012
1013 if (typeof obj[0] === 'number' || obj instanceof Point) {
1014 obj = toPoint(obj);
1015 } else {
1016 obj = toBounds(obj);
1017 }
1018
1019 if (obj instanceof Bounds) {
1020 min = obj.min;
1021 max = obj.max;
1022 } else {
1023 min = max = obj;
1024 }
1025
1026 return (min.x >= this.min.x) &&
1027 (max.x <= this.max.x) &&
1028 (min.y >= this.min.y) &&
1029 (max.y <= this.max.y);
1030 },
1031
1032 // @method intersects(otherBounds: Bounds): Boolean
1033 // Returns `true` if the rectangle intersects the given bounds. Two bounds
1034 // intersect if they have at least one point in common.
1035 intersects: function (bounds) { // (Bounds) -> Boolean
1036 bounds = toBounds(bounds);
1037
1038 var min = this.min,
1039 max = this.max,
1040 min2 = bounds.min,
1041 max2 = bounds.max,
1042 xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1043 yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1044
1045 return xIntersects && yIntersects;
1046 },
1047
1048 // @method overlaps(otherBounds: Bounds): Boolean
1049 // Returns `true` if the rectangle overlaps the given bounds. Two bounds
1050 // overlap if their intersection is an area.
1051 overlaps: function (bounds) { // (Bounds) -> Boolean
1052 bounds = toBounds(bounds);
1053
1054 var min = this.min,
1055 max = this.max,
1056 min2 = bounds.min,
1057 max2 = bounds.max,
1058 xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1059 yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1060
1061 return xOverlaps && yOverlaps;
1062 },
1063
1064 isValid: function () {
1065 return !!(this.min && this.max);
1066 }
1067};
1068
1069
1070// @factory L.bounds(corner1: Point, corner2: Point)
1071// Creates a Bounds object from two corners coordinate pairs.
1072// @alternative
1073// @factory L.bounds(points: Point[])
1074// Creates a Bounds object from the given array of points.
1075function toBounds(a, b) {
1076 if (!a || a instanceof Bounds) {
1077 return a;
1078 }
1079 return new Bounds(a, b);
1080}
1081
1082/*
1083 * @class LatLngBounds
1084 * @aka L.LatLngBounds
1085 *
1086 * Represents a rectangular geographical area on a map.
1087 *
1088 * @example
1089 *
1090 * ```js
1091 * var corner1 = L.latLng(40.712, -74.227),
1092 * corner2 = L.latLng(40.774, -74.125),
1093 * bounds = L.latLngBounds(corner1, corner2);
1094 * ```
1095 *
1096 * 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:
1097 *
1098 * ```js
1099 * map.fitBounds([
1100 * [40.712, -74.227],
1101 * [40.774, -74.125]
1102 * ]);
1103 * ```
1104 *
1105 * 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.
1106 *
1107 * Note that `LatLngBounds` does not inherit from Leaflet's `Class` object,
1108 * which means new classes can't inherit from it, and new methods
1109 * can't be added to it with the `include` function.
1110 */
1111
1112function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
1113 if (!corner1) { return; }
1114
1115 var latlngs = corner2 ? [corner1, corner2] : corner1;
1116
1117 for (var i = 0, len = latlngs.length; i < len; i++) {
1118 this.extend(latlngs[i]);
1119 }
1120}
1121
1122LatLngBounds.prototype = {
1123
1124 // @method extend(latlng: LatLng): this
1125 // Extend the bounds to contain the given point
1126
1127 // @alternative
1128 // @method extend(otherBounds: LatLngBounds): this
1129 // Extend the bounds to contain the given bounds
1130 extend: function (obj) {
1131 var sw = this._southWest,
1132 ne = this._northEast,
1133 sw2, ne2;
1134
1135 if (obj instanceof LatLng) {
1136 sw2 = obj;
1137 ne2 = obj;
1138
1139 } else if (obj instanceof LatLngBounds) {
1140 sw2 = obj._southWest;
1141 ne2 = obj._northEast;
1142
1143 if (!sw2 || !ne2) { return this; }
1144
1145 } else {
1146 return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
1147 }
1148
1149 if (!sw && !ne) {
1150 this._southWest = new LatLng(sw2.lat, sw2.lng);
1151 this._northEast = new LatLng(ne2.lat, ne2.lng);
1152 } else {
1153 sw.lat = Math.min(sw2.lat, sw.lat);
1154 sw.lng = Math.min(sw2.lng, sw.lng);
1155 ne.lat = Math.max(ne2.lat, ne.lat);
1156 ne.lng = Math.max(ne2.lng, ne.lng);
1157 }
1158
1159 return this;
1160 },
1161
1162 // @method pad(bufferRatio: Number): LatLngBounds
1163 // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
1164 // For example, a ratio of 0.5 extends the bounds by 50% in each direction.
1165 // Negative values will retract the bounds.
1166 pad: function (bufferRatio) {
1167 var sw = this._southWest,
1168 ne = this._northEast,
1169 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1170 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1171
1172 return new LatLngBounds(
1173 new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1174 new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1175 },
1176
1177 // @method getCenter(): LatLng
1178 // Returns the center point of the bounds.
1179 getCenter: function () {
1180 return new LatLng(
1181 (this._southWest.lat + this._northEast.lat) / 2,
1182 (this._southWest.lng + this._northEast.lng) / 2);
1183 },
1184
1185 // @method getSouthWest(): LatLng
1186 // Returns the south-west point of the bounds.
1187 getSouthWest: function () {
1188 return this._southWest;
1189 },
1190
1191 // @method getNorthEast(): LatLng
1192 // Returns the north-east point of the bounds.
1193 getNorthEast: function () {
1194 return this._northEast;
1195 },
1196
1197 // @method getNorthWest(): LatLng
1198 // Returns the north-west point of the bounds.
1199 getNorthWest: function () {
1200 return new LatLng(this.getNorth(), this.getWest());
1201 },
1202
1203 // @method getSouthEast(): LatLng
1204 // Returns the south-east point of the bounds.
1205 getSouthEast: function () {
1206 return new LatLng(this.getSouth(), this.getEast());
1207 },
1208
1209 // @method getWest(): Number
1210 // Returns the west longitude of the bounds
1211 getWest: function () {
1212 return this._southWest.lng;
1213 },
1214
1215 // @method getSouth(): Number
1216 // Returns the south latitude of the bounds
1217 getSouth: function () {
1218 return this._southWest.lat;
1219 },
1220
1221 // @method getEast(): Number
1222 // Returns the east longitude of the bounds
1223 getEast: function () {
1224 return this._northEast.lng;
1225 },
1226
1227 // @method getNorth(): Number
1228 // Returns the north latitude of the bounds
1229 getNorth: function () {
1230 return this._northEast.lat;
1231 },
1232
1233 // @method contains(otherBounds: LatLngBounds): Boolean
1234 // Returns `true` if the rectangle contains the given one.
1235
1236 // @alternative
1237 // @method contains (latlng: LatLng): Boolean
1238 // Returns `true` if the rectangle contains the given point.
1239 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1240 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
1241 obj = toLatLng(obj);
1242 } else {
1243 obj = toLatLngBounds(obj);
1244 }
1245
1246 var sw = this._southWest,
1247 ne = this._northEast,
1248 sw2, ne2;
1249
1250 if (obj instanceof LatLngBounds) {
1251 sw2 = obj.getSouthWest();
1252 ne2 = obj.getNorthEast();
1253 } else {
1254 sw2 = ne2 = obj;
1255 }
1256
1257 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1258 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1259 },
1260
1261 // @method intersects(otherBounds: LatLngBounds): Boolean
1262 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1263 intersects: function (bounds) {
1264 bounds = toLatLngBounds(bounds);
1265
1266 var sw = this._southWest,
1267 ne = this._northEast,
1268 sw2 = bounds.getSouthWest(),
1269 ne2 = bounds.getNorthEast(),
1270
1271 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1272 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1273
1274 return latIntersects && lngIntersects;
1275 },
1276
1277 // @method overlaps(otherBounds: LatLngBounds): Boolean
1278 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1279 overlaps: function (bounds) {
1280 bounds = toLatLngBounds(bounds);
1281
1282 var sw = this._southWest,
1283 ne = this._northEast,
1284 sw2 = bounds.getSouthWest(),
1285 ne2 = bounds.getNorthEast(),
1286
1287 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1288 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1289
1290 return latOverlaps && lngOverlaps;
1291 },
1292
1293 // @method toBBoxString(): String
1294 // 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.
1295 toBBoxString: function () {
1296 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1297 },
1298
1299 // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
1300 // 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.
1301 equals: function (bounds, maxMargin) {
1302 if (!bounds) { return false; }
1303
1304 bounds = toLatLngBounds(bounds);
1305
1306 return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
1307 this._northEast.equals(bounds.getNorthEast(), maxMargin);
1308 },
1309
1310 // @method isValid(): Boolean
1311 // Returns `true` if the bounds are properly initialized.
1312 isValid: function () {
1313 return !!(this._southWest && this._northEast);
1314 }
1315};
1316
1317// TODO International date line?
1318
1319// @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
1320// Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
1321
1322// @alternative
1323// @factory L.latLngBounds(latlngs: LatLng[])
1324// 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).
1325function toLatLngBounds(a, b) {
1326 if (a instanceof LatLngBounds) {
1327 return a;
1328 }
1329 return new LatLngBounds(a, b);
1330}
1331
1332/* @class LatLng
1333 * @aka L.LatLng
1334 *
1335 * Represents a geographical point with a certain latitude and longitude.
1336 *
1337 * @example
1338 *
1339 * ```
1340 * var latlng = L.latLng(50.5, 30.5);
1341 * ```
1342 *
1343 * 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:
1344 *
1345 * ```
1346 * map.panTo([50, 30]);
1347 * map.panTo({lon: 30, lat: 50});
1348 * map.panTo({lat: 50, lng: 30});
1349 * map.panTo(L.latLng(50, 30));
1350 * ```
1351 *
1352 * Note that `LatLng` does not inherit from Leaflet's `Class` object,
1353 * which means new classes can't inherit from it, and new methods
1354 * can't be added to it with the `include` function.
1355 */
1356
1357function LatLng(lat, lng, alt) {
1358 if (isNaN(lat) || isNaN(lng)) {
1359 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1360 }
1361
1362 // @property lat: Number
1363 // Latitude in degrees
1364 this.lat = +lat;
1365
1366 // @property lng: Number
1367 // Longitude in degrees
1368 this.lng = +lng;
1369
1370 // @property alt: Number
1371 // Altitude in meters (optional)
1372 if (alt !== undefined) {
1373 this.alt = +alt;
1374 }
1375}
1376
1377LatLng.prototype = {
1378 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1379 // 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.
1380 equals: function (obj, maxMargin) {
1381 if (!obj) { return false; }
1382
1383 obj = toLatLng(obj);
1384
1385 var margin = Math.max(
1386 Math.abs(this.lat - obj.lat),
1387 Math.abs(this.lng - obj.lng));
1388
1389 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1390 },
1391
1392 // @method toString(): String
1393 // Returns a string representation of the point (for debugging purposes).
1394 toString: function (precision) {
1395 return 'LatLng(' +
1396 formatNum(this.lat, precision) + ', ' +
1397 formatNum(this.lng, precision) + ')';
1398 },
1399
1400 // @method distanceTo(otherLatLng: LatLng): Number
1401 // 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).
1402 distanceTo: function (other) {
1403 return Earth.distance(this, toLatLng(other));
1404 },
1405
1406 // @method wrap(): LatLng
1407 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1408 wrap: function () {
1409 return Earth.wrapLatLng(this);
1410 },
1411
1412 // @method toBounds(sizeInMeters: Number): LatLngBounds
1413 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
1414 toBounds: function (sizeInMeters) {
1415 var latAccuracy = 180 * sizeInMeters / 40075017,
1416 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1417
1418 return toLatLngBounds(
1419 [this.lat - latAccuracy, this.lng - lngAccuracy],
1420 [this.lat + latAccuracy, this.lng + lngAccuracy]);
1421 },
1422
1423 clone: function () {
1424 return new LatLng(this.lat, this.lng, this.alt);
1425 }
1426};
1427
1428
1429
1430// @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1431// Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1432
1433// @alternative
1434// @factory L.latLng(coords: Array): LatLng
1435// Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1436
1437// @alternative
1438// @factory L.latLng(coords: Object): LatLng
1439// Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1440
1441function toLatLng(a, b, c) {
1442 if (a instanceof LatLng) {
1443 return a;
1444 }
1445 if (isArray(a) && typeof a[0] !== 'object') {
1446 if (a.length === 3) {
1447 return new LatLng(a[0], a[1], a[2]);
1448 }
1449 if (a.length === 2) {
1450 return new LatLng(a[0], a[1]);
1451 }
1452 return null;
1453 }
1454 if (a === undefined || a === null) {
1455 return a;
1456 }
1457 if (typeof a === 'object' && 'lat' in a) {
1458 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1459 }
1460 if (b === undefined) {
1461 return null;
1462 }
1463 return new LatLng(a, b, c);
1464}
1465
1466/*
1467 * @namespace CRS
1468 * @crs L.CRS.Base
1469 * Object that defines coordinate reference systems for projecting
1470 * geographical points into pixel (screen) coordinates and back (and to
1471 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
1472 * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
1473 *
1474 * Leaflet defines the most usual CRSs by default. If you want to use a
1475 * CRS not defined by default, take a look at the
1476 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
1477 *
1478 * Note that the CRS instances do not inherit from Leaflet's `Class` object,
1479 * and can't be instantiated. Also, new classes can't inherit from them,
1480 * and methods can't be added to them with the `include` function.
1481 */
1482
1483var CRS = {
1484 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
1485 // Projects geographical coordinates into pixel coordinates for a given zoom.
1486 latLngToPoint: function (latlng, zoom) {
1487 var projectedPoint = this.projection.project(latlng),
1488 scale = this.scale(zoom);
1489
1490 return this.transformation._transform(projectedPoint, scale);
1491 },
1492
1493 // @method pointToLatLng(point: Point, zoom: Number): LatLng
1494 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
1495 // zoom into geographical coordinates.
1496 pointToLatLng: function (point, zoom) {
1497 var scale = this.scale(zoom),
1498 untransformedPoint = this.transformation.untransform(point, scale);
1499
1500 return this.projection.unproject(untransformedPoint);
1501 },
1502
1503 // @method project(latlng: LatLng): Point
1504 // Projects geographical coordinates into coordinates in units accepted for
1505 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
1506 project: function (latlng) {
1507 return this.projection.project(latlng);
1508 },
1509
1510 // @method unproject(point: Point): LatLng
1511 // Given a projected coordinate returns the corresponding LatLng.
1512 // The inverse of `project`.
1513 unproject: function (point) {
1514 return this.projection.unproject(point);
1515 },
1516
1517 // @method scale(zoom: Number): Number
1518 // Returns the scale used when transforming projected coordinates into
1519 // pixel coordinates for a particular zoom. For example, it returns
1520 // `256 * 2^zoom` for Mercator-based CRS.
1521 scale: function (zoom) {
1522 return 256 * Math.pow(2, zoom);
1523 },
1524
1525 // @method zoom(scale: Number): Number
1526 // Inverse of `scale()`, returns the zoom level corresponding to a scale
1527 // factor of `scale`.
1528 zoom: function (scale) {
1529 return Math.log(scale / 256) / Math.LN2;
1530 },
1531
1532 // @method getProjectedBounds(zoom: Number): Bounds
1533 // Returns the projection's bounds scaled and transformed for the provided `zoom`.
1534 getProjectedBounds: function (zoom) {
1535 if (this.infinite) { return null; }
1536
1537 var b = this.projection.bounds,
1538 s = this.scale(zoom),
1539 min = this.transformation.transform(b.min, s),
1540 max = this.transformation.transform(b.max, s);
1541
1542 return new Bounds(min, max);
1543 },
1544
1545 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
1546 // Returns the distance between two geographical coordinates.
1547
1548 // @property code: String
1549 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
1550 //
1551 // @property wrapLng: Number[]
1552 // An array of two numbers defining whether the longitude (horizontal) coordinate
1553 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
1554 // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
1555 //
1556 // @property wrapLat: Number[]
1557 // Like `wrapLng`, but for the latitude (vertical) axis.
1558
1559 // wrapLng: [min, max],
1560 // wrapLat: [min, max],
1561
1562 // @property infinite: Boolean
1563 // If true, the coordinate space will be unbounded (infinite in both axes)
1564 infinite: false,
1565
1566 // @method wrapLatLng(latlng: LatLng): LatLng
1567 // Returns a `LatLng` where lat and lng has been wrapped according to the
1568 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
1569 wrapLatLng: function (latlng) {
1570 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
1571 lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
1572 alt = latlng.alt;
1573
1574 return new LatLng(lat, lng, alt);
1575 },
1576
1577 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
1578 // Returns a `LatLngBounds` with the same size as the given one, ensuring
1579 // that its center is within the CRS's bounds.
1580 // Only accepts actual `L.LatLngBounds` instances, not arrays.
1581 wrapLatLngBounds: function (bounds) {
1582 var center = bounds.getCenter(),
1583 newCenter = this.wrapLatLng(center),
1584 latShift = center.lat - newCenter.lat,
1585 lngShift = center.lng - newCenter.lng;
1586
1587 if (latShift === 0 && lngShift === 0) {
1588 return bounds;
1589 }
1590
1591 var sw = bounds.getSouthWest(),
1592 ne = bounds.getNorthEast(),
1593 newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),
1594 newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);
1595
1596 return new LatLngBounds(newSw, newNe);
1597 }
1598};
1599
1600/*
1601 * @namespace CRS
1602 * @crs L.CRS.Earth
1603 *
1604 * Serves as the base for CRS that are global such that they cover the earth.
1605 * Can only be used as the base for other CRS and cannot be used directly,
1606 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
1607 * meters.
1608 */
1609
1610var Earth = extend({}, CRS, {
1611 wrapLng: [-180, 180],
1612
1613 // Mean Earth Radius, as recommended for use by
1614 // the International Union of Geodesy and Geophysics,
1615 // see http://rosettacode.org/wiki/Haversine_formula
1616 R: 6371000,
1617
1618 // distance between two geographical points using spherical law of cosines approximation
1619 distance: function (latlng1, latlng2) {
1620 var rad = Math.PI / 180,
1621 lat1 = latlng1.lat * rad,
1622 lat2 = latlng2.lat * rad,
1623 sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2),
1624 sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2),
1625 a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon,
1626 c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1627 return this.R * c;
1628 }
1629});
1630
1631/*
1632 * @namespace Projection
1633 * @projection L.Projection.SphericalMercator
1634 *
1635 * Spherical Mercator projection — the most common projection for online maps,
1636 * used by almost all free and commercial tile providers. Assumes that Earth is
1637 * a sphere. Used by the `EPSG:3857` CRS.
1638 */
1639
1640var earthRadius = 6378137;
1641
1642var SphericalMercator = {
1643
1644 R: earthRadius,
1645 MAX_LATITUDE: 85.0511287798,
1646
1647 project: function (latlng) {
1648 var d = Math.PI / 180,
1649 max = this.MAX_LATITUDE,
1650 lat = Math.max(Math.min(max, latlng.lat), -max),
1651 sin = Math.sin(lat * d);
1652
1653 return new Point(
1654 this.R * latlng.lng * d,
1655 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
1656 },
1657
1658 unproject: function (point) {
1659 var d = 180 / Math.PI;
1660
1661 return new LatLng(
1662 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
1663 point.x * d / this.R);
1664 },
1665
1666 bounds: (function () {
1667 var d = earthRadius * Math.PI;
1668 return new Bounds([-d, -d], [d, d]);
1669 })()
1670};
1671
1672/*
1673 * @class Transformation
1674 * @aka L.Transformation
1675 *
1676 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1677 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1678 * the reverse. Used by Leaflet in its projections code.
1679 *
1680 * @example
1681 *
1682 * ```js
1683 * var transformation = L.transformation(2, 5, -1, 10),
1684 * p = L.point(1, 2),
1685 * p2 = transformation.transform(p), // L.point(7, 8)
1686 * p3 = transformation.untransform(p2); // L.point(1, 2)
1687 * ```
1688 */
1689
1690
1691// factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1692// Creates a `Transformation` object with the given coefficients.
1693function Transformation(a, b, c, d) {
1694 if (isArray(a)) {
1695 // use array properties
1696 this._a = a[0];
1697 this._b = a[1];
1698 this._c = a[2];
1699 this._d = a[3];
1700 return;
1701 }
1702 this._a = a;
1703 this._b = b;
1704 this._c = c;
1705 this._d = d;
1706}
1707
1708Transformation.prototype = {
1709 // @method transform(point: Point, scale?: Number): Point
1710 // Returns a transformed point, optionally multiplied by the given scale.
1711 // Only accepts actual `L.Point` instances, not arrays.
1712 transform: function (point, scale) { // (Point, Number) -> Point
1713 return this._transform(point.clone(), scale);
1714 },
1715
1716 // destructive transform (faster)
1717 _transform: function (point, scale) {
1718 scale = scale || 1;
1719 point.x = scale * (this._a * point.x + this._b);
1720 point.y = scale * (this._c * point.y + this._d);
1721 return point;
1722 },
1723
1724 // @method untransform(point: Point, scale?: Number): Point
1725 // Returns the reverse transformation of the given point, optionally divided
1726 // by the given scale. Only accepts actual `L.Point` instances, not arrays.
1727 untransform: function (point, scale) {
1728 scale = scale || 1;
1729 return new Point(
1730 (point.x / scale - this._b) / this._a,
1731 (point.y / scale - this._d) / this._c);
1732 }
1733};
1734
1735// factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1736
1737// @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1738// Instantiates a Transformation object with the given coefficients.
1739
1740// @alternative
1741// @factory L.transformation(coefficients: Array): Transformation
1742// Expects an coefficients array of the form
1743// `[a: Number, b: Number, c: Number, d: Number]`.
1744
1745function toTransformation(a, b, c, d) {
1746 return new Transformation(a, b, c, d);
1747}
1748
1749/*
1750 * @namespace CRS
1751 * @crs L.CRS.EPSG3857
1752 *
1753 * The most common CRS for online maps, used by almost all free and commercial
1754 * tile providers. Uses Spherical Mercator projection. Set in by default in
1755 * Map's `crs` option.
1756 */
1757
1758var EPSG3857 = extend({}, Earth, {
1759 code: 'EPSG:3857',
1760 projection: SphericalMercator,
1761
1762 transformation: (function () {
1763 var scale = 0.5 / (Math.PI * SphericalMercator.R);
1764 return toTransformation(scale, 0.5, -scale, 0.5);
1765 }())
1766});
1767
1768var EPSG900913 = extend({}, EPSG3857, {
1769 code: 'EPSG:900913'
1770});
1771
1772// @namespace SVG; @section
1773// There are several static functions which can be called without instantiating L.SVG:
1774
1775// @function create(name: String): SVGElement
1776// Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
1777// corresponding to the class name passed. For example, using 'line' will return
1778// an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
1779function svgCreate(name) {
1780 return document.createElementNS('http://www.w3.org/2000/svg', name);
1781}
1782
1783// @function pointsToPath(rings: Point[], closed: Boolean): String
1784// Generates a SVG path string for multiple rings, with each ring turning
1785// into "M..L..L.." instructions
1786function pointsToPath(rings, closed) {
1787 var str = '',
1788 i, j, len, len2, points, p;
1789
1790 for (i = 0, len = rings.length; i < len; i++) {
1791 points = rings[i];
1792
1793 for (j = 0, len2 = points.length; j < len2; j++) {
1794 p = points[j];
1795 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
1796 }
1797
1798 // closes the ring for polygons; "x" is VML syntax
1799 str += closed ? (svg ? 'z' : 'x') : '';
1800 }
1801
1802 // SVG complains about empty path strings
1803 return str || 'M0 0';
1804}
1805
1806/*
1807 * @namespace Browser
1808 * @aka L.Browser
1809 *
1810 * A namespace with static properties for browser/feature detection used by Leaflet internally.
1811 *
1812 * @example
1813 *
1814 * ```js
1815 * if (L.Browser.ielt9) {
1816 * alert('Upgrade your browser, dude!');
1817 * }
1818 * ```
1819 */
1820
1821var style$1 = document.documentElement.style;
1822
1823// @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
1824var ie = 'ActiveXObject' in window;
1825
1826// @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
1827var ielt9 = ie && !document.addEventListener;
1828
1829// @property edge: Boolean; `true` for the Edge web browser.
1830var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
1831
1832// @property webkit: Boolean;
1833// `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
1834var webkit = userAgentContains('webkit');
1835
1836// @property android: Boolean
1837// `true` for any browser running on an Android platform.
1838var android = userAgentContains('android');
1839
1840// @property android23: Boolean; `true` for browsers running on Android 2 or Android 3.
1841var android23 = userAgentContains('android 2') || userAgentContains('android 3');
1842
1843/* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */
1844var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit
1845// @property androidStock: Boolean; `true` for the Android stock browser (i.e. not Chrome)
1846var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);
1847
1848// @property opera: Boolean; `true` for the Opera browser
1849var opera = !!window.opera;
1850
1851// @property chrome: Boolean; `true` for the Chrome browser.
1852var chrome = !edge && userAgentContains('chrome');
1853
1854// @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
1855var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
1856
1857// @property safari: Boolean; `true` for the Safari browser.
1858var safari = !chrome && userAgentContains('safari');
1859
1860var phantom = userAgentContains('phantom');
1861
1862// @property opera12: Boolean
1863// `true` for the Opera browser supporting CSS transforms (version 12 or later).
1864var opera12 = 'OTransition' in style$1;
1865
1866// @property win: Boolean; `true` when the browser is running in a Windows platform
1867var win = navigator.platform.indexOf('Win') === 0;
1868
1869// @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
1870var ie3d = ie && ('transition' in style$1);
1871
1872// @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
1873var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;
1874
1875// @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
1876var gecko3d = 'MozPerspective' in style$1;
1877
1878// @property any3d: Boolean
1879// `true` for all browsers supporting CSS transforms.
1880var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
1881
1882// @property mobile: Boolean; `true` for all browsers running in a mobile device.
1883var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
1884
1885// @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
1886var mobileWebkit = mobile && webkit;
1887
1888// @property mobileWebkit3d: Boolean
1889// `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
1890var mobileWebkit3d = mobile && webkit3d;
1891
1892// @property msPointer: Boolean
1893// `true` for browsers implementing the Microsoft touch events model (notably IE10).
1894var msPointer = !window.PointerEvent && window.MSPointerEvent;
1895
1896// @property pointer: Boolean
1897// `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
1898var pointer = !!(window.PointerEvent || msPointer);
1899
1900// @property touch: Boolean
1901// `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
1902// This does not necessarily mean that the browser is running in a computer with
1903// a touchscreen, it only means that the browser is capable of understanding
1904// touch events.
1905var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
1906 (window.DocumentTouch && document instanceof window.DocumentTouch));
1907
1908// @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
1909var mobileOpera = mobile && opera;
1910
1911// @property mobileGecko: Boolean
1912// `true` for gecko-based browsers running in a mobile device.
1913var mobileGecko = mobile && gecko;
1914
1915// @property retina: Boolean
1916// `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%.
1917var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
1918
1919// @property passiveEvents: Boolean
1920// `true` for browsers that support passive events.
1921var passiveEvents = (function () {
1922 var supportsPassiveOption = false;
1923 try {
1924 var opts = Object.defineProperty({}, 'passive', {
1925 get: function () { // eslint-disable-line getter-return
1926 supportsPassiveOption = true;
1927 }
1928 });
1929 window.addEventListener('testPassiveEventSupport', falseFn, opts);
1930 window.removeEventListener('testPassiveEventSupport', falseFn, opts);
1931 } catch (e) {
1932 // Errors can safely be ignored since this is only a browser support test.
1933 }
1934 return supportsPassiveOption;
1935}());
1936
1937// @property canvas: Boolean
1938// `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
1939var canvas = (function () {
1940 return !!document.createElement('canvas').getContext;
1941}());
1942
1943// @property svg: Boolean
1944// `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
1945var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect);
1946
1947// @property vml: Boolean
1948// `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
1949var vml = !svg && (function () {
1950 try {
1951 var div = document.createElement('div');
1952 div.innerHTML = '<v:shape adj="1"/>';
1953
1954 var shape = div.firstChild;
1955 shape.style.behavior = 'url(#default#VML)';
1956
1957 return shape && (typeof shape.adj === 'object');
1958
1959 } catch (e) {
1960 return false;
1961 }
1962}());
1963
1964
1965function userAgentContains(str) {
1966 return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
1967}
1968
1969var Browser = ({
1970 ie: ie,
1971 ielt9: ielt9,
1972 edge: edge,
1973 webkit: webkit,
1974 android: android,
1975 android23: android23,
1976 androidStock: androidStock,
1977 opera: opera,
1978 chrome: chrome,
1979 gecko: gecko,
1980 safari: safari,
1981 phantom: phantom,
1982 opera12: opera12,
1983 win: win,
1984 ie3d: ie3d,
1985 webkit3d: webkit3d,
1986 gecko3d: gecko3d,
1987 any3d: any3d,
1988 mobile: mobile,
1989 mobileWebkit: mobileWebkit,
1990 mobileWebkit3d: mobileWebkit3d,
1991 msPointer: msPointer,
1992 pointer: pointer,
1993 touch: touch,
1994 mobileOpera: mobileOpera,
1995 mobileGecko: mobileGecko,
1996 retina: retina,
1997 passiveEvents: passiveEvents,
1998 canvas: canvas,
1999 svg: svg,
2000 vml: vml
2001});
2002
2003/*
2004 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
2005 */
2006
2007
2008var POINTER_DOWN = msPointer ? 'MSPointerDown' : 'pointerdown';
2009var POINTER_MOVE = msPointer ? 'MSPointerMove' : 'pointermove';
2010var POINTER_UP = msPointer ? 'MSPointerUp' : 'pointerup';
2011var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel';
2012
2013var _pointers = {};
2014var _pointerDocListener = false;
2015
2016// Provides a touch events wrapper for (ms)pointer events.
2017// ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
2018
2019function addPointerListener(obj, type, handler, id) {
2020 if (type === 'touchstart') {
2021 _addPointerStart(obj, handler, id);
2022
2023 } else if (type === 'touchmove') {
2024 _addPointerMove(obj, handler, id);
2025
2026 } else if (type === 'touchend') {
2027 _addPointerEnd(obj, handler, id);
2028 }
2029
2030 return this;
2031}
2032
2033function removePointerListener(obj, type, id) {
2034 var handler = obj['_leaflet_' + type + id];
2035
2036 if (type === 'touchstart') {
2037 obj.removeEventListener(POINTER_DOWN, handler, false);
2038
2039 } else if (type === 'touchmove') {
2040 obj.removeEventListener(POINTER_MOVE, handler, false);
2041
2042 } else if (type === 'touchend') {
2043 obj.removeEventListener(POINTER_UP, handler, false);
2044 obj.removeEventListener(POINTER_CANCEL, handler, false);
2045 }
2046
2047 return this;
2048}
2049
2050function _addPointerStart(obj, handler, id) {
2051 var onDown = bind(function (e) {
2052 // IE10 specific: MsTouch needs preventDefault. See #2000
2053 if (e.MSPOINTER_TYPE_TOUCH && e.pointerType === e.MSPOINTER_TYPE_TOUCH) {
2054 preventDefault(e);
2055 }
2056
2057 _handlePointer(e, handler);
2058 });
2059
2060 obj['_leaflet_touchstart' + id] = onDown;
2061 obj.addEventListener(POINTER_DOWN, onDown, false);
2062
2063 // need to keep track of what pointers and how many are active to provide e.touches emulation
2064 if (!_pointerDocListener) {
2065 // we listen document as any drags that end by moving the touch off the screen get fired there
2066 document.addEventListener(POINTER_DOWN, _globalPointerDown, true);
2067 document.addEventListener(POINTER_MOVE, _globalPointerMove, true);
2068 document.addEventListener(POINTER_UP, _globalPointerUp, true);
2069 document.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
2070
2071 _pointerDocListener = true;
2072 }
2073}
2074
2075function _globalPointerDown(e) {
2076 _pointers[e.pointerId] = e;
2077}
2078
2079function _globalPointerMove(e) {
2080 if (_pointers[e.pointerId]) {
2081 _pointers[e.pointerId] = e;
2082 }
2083}
2084
2085function _globalPointerUp(e) {
2086 delete _pointers[e.pointerId];
2087}
2088
2089function _handlePointer(e, handler) {
2090 e.touches = [];
2091 for (var i in _pointers) {
2092 e.touches.push(_pointers[i]);
2093 }
2094 e.changedTouches = [e];
2095
2096 handler(e);
2097}
2098
2099function _addPointerMove(obj, handler, id) {
2100 var onMove = function (e) {
2101 // don't fire touch moves when mouse isn't down
2102 if ((e.pointerType === (e.MSPOINTER_TYPE_MOUSE || 'mouse')) && e.buttons === 0) {
2103 return;
2104 }
2105
2106 _handlePointer(e, handler);
2107 };
2108
2109 obj['_leaflet_touchmove' + id] = onMove;
2110 obj.addEventListener(POINTER_MOVE, onMove, false);
2111}
2112
2113function _addPointerEnd(obj, handler, id) {
2114 var onUp = function (e) {
2115 _handlePointer(e, handler);
2116 };
2117
2118 obj['_leaflet_touchend' + id] = onUp;
2119 obj.addEventListener(POINTER_UP, onUp, false);
2120 obj.addEventListener(POINTER_CANCEL, onUp, false);
2121}
2122
2123/*
2124 * Extends the event handling code with double tap support for mobile browsers.
2125 */
2126
2127var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart';
2128var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend';
2129var _pre = '_leaflet_';
2130
2131// inspired by Zepto touch code by Thomas Fuchs
2132function addDoubleTapListener(obj, handler, id) {
2133 var last, touch$$1,
2134 doubleTap = false,
2135 delay = 250;
2136
2137 function onTouchStart(e) {
2138
2139 if (pointer) {
2140 if (!e.isPrimary) { return; }
2141 if (e.pointerType === 'mouse') { return; } // mouse fires native dblclick
2142 } else if (e.touches.length > 1) {
2143 return;
2144 }
2145
2146 var now = Date.now(),
2147 delta = now - (last || now);
2148
2149 touch$$1 = e.touches ? e.touches[0] : e;
2150 doubleTap = (delta > 0 && delta <= delay);
2151 last = now;
2152 }
2153
2154 function onTouchEnd(e) {
2155 if (doubleTap && !touch$$1.cancelBubble) {
2156 if (pointer) {
2157 if (e.pointerType === 'mouse') { return; }
2158 // work around .type being readonly with MSPointer* events
2159 var newTouch = {},
2160 prop, i;
2161
2162 for (i in touch$$1) {
2163 prop = touch$$1[i];
2164 newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop;
2165 }
2166 touch$$1 = newTouch;
2167 }
2168 touch$$1.type = 'dblclick';
2169 touch$$1.button = 0;
2170 handler(touch$$1);
2171 last = null;
2172 }
2173 }
2174
2175 obj[_pre + _touchstart + id] = onTouchStart;
2176 obj[_pre + _touchend + id] = onTouchEnd;
2177 obj[_pre + 'dblclick' + id] = handler;
2178
2179 obj.addEventListener(_touchstart, onTouchStart, passiveEvents ? {passive: false} : false);
2180 obj.addEventListener(_touchend, onTouchEnd, passiveEvents ? {passive: false} : false);
2181
2182 // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
2183 // the browser doesn't fire touchend/pointerup events but does fire
2184 // native dblclicks. See #4127.
2185 // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
2186 obj.addEventListener('dblclick', handler, false);
2187
2188 return this;
2189}
2190
2191function removeDoubleTapListener(obj, id) {
2192 var touchstart = obj[_pre + _touchstart + id],
2193 touchend = obj[_pre + _touchend + id],
2194 dblclick = obj[_pre + 'dblclick' + id];
2195
2196 obj.removeEventListener(_touchstart, touchstart, passiveEvents ? {passive: false} : false);
2197 obj.removeEventListener(_touchend, touchend, passiveEvents ? {passive: false} : false);
2198 obj.removeEventListener('dblclick', dblclick, false);
2199
2200 return this;
2201}
2202
2203/*
2204 * @namespace DomUtil
2205 *
2206 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
2207 * tree, used by Leaflet internally.
2208 *
2209 * Most functions expecting or returning a `HTMLElement` also work for
2210 * SVG elements. The only difference is that classes refer to CSS classes
2211 * in HTML and SVG classes in SVG.
2212 */
2213
2214
2215// @property TRANSFORM: String
2216// Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
2217var TRANSFORM = testProp(
2218 ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
2219
2220// webkitTransition comes first because some browser versions that drop vendor prefix don't do
2221// the same for the transitionend event, in particular the Android 4.1 stock browser
2222
2223// @property TRANSITION: String
2224// Vendor-prefixed transition style name.
2225var TRANSITION = testProp(
2226 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
2227
2228// @property TRANSITION_END: String
2229// Vendor-prefixed transitionend event name.
2230var TRANSITION_END =
2231 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
2232
2233
2234// @function get(id: String|HTMLElement): HTMLElement
2235// Returns an element given its DOM id, or returns the element itself
2236// if it was passed directly.
2237function get(id) {
2238 return typeof id === 'string' ? document.getElementById(id) : id;
2239}
2240
2241// @function getStyle(el: HTMLElement, styleAttrib: String): String
2242// Returns the value for a certain style attribute on an element,
2243// including computed values or values set through CSS.
2244function getStyle(el, style) {
2245 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
2246
2247 if ((!value || value === 'auto') && document.defaultView) {
2248 var css = document.defaultView.getComputedStyle(el, null);
2249 value = css ? css[style] : null;
2250 }
2251 return value === 'auto' ? null : value;
2252}
2253
2254// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
2255// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
2256function create$1(tagName, className, container) {
2257 var el = document.createElement(tagName);
2258 el.className = className || '';
2259
2260 if (container) {
2261 container.appendChild(el);
2262 }
2263 return el;
2264}
2265
2266// @function remove(el: HTMLElement)
2267// Removes `el` from its parent element
2268function remove(el) {
2269 var parent = el.parentNode;
2270 if (parent) {
2271 parent.removeChild(el);
2272 }
2273}
2274
2275// @function empty(el: HTMLElement)
2276// Removes all of `el`'s children elements from `el`
2277function empty(el) {
2278 while (el.firstChild) {
2279 el.removeChild(el.firstChild);
2280 }
2281}
2282
2283// @function toFront(el: HTMLElement)
2284// Makes `el` the last child of its parent, so it renders in front of the other children.
2285function toFront(el) {
2286 var parent = el.parentNode;
2287 if (parent && parent.lastChild !== el) {
2288 parent.appendChild(el);
2289 }
2290}
2291
2292// @function toBack(el: HTMLElement)
2293// Makes `el` the first child of its parent, so it renders behind the other children.
2294function toBack(el) {
2295 var parent = el.parentNode;
2296 if (parent && parent.firstChild !== el) {
2297 parent.insertBefore(el, parent.firstChild);
2298 }
2299}
2300
2301// @function hasClass(el: HTMLElement, name: String): Boolean
2302// Returns `true` if the element's class attribute contains `name`.
2303function hasClass(el, name) {
2304 if (el.classList !== undefined) {
2305 return el.classList.contains(name);
2306 }
2307 var className = getClass(el);
2308 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
2309}
2310
2311// @function addClass(el: HTMLElement, name: String)
2312// Adds `name` to the element's class attribute.
2313function addClass(el, name) {
2314 if (el.classList !== undefined) {
2315 var classes = splitWords(name);
2316 for (var i = 0, len = classes.length; i < len; i++) {
2317 el.classList.add(classes[i]);
2318 }
2319 } else if (!hasClass(el, name)) {
2320 var className = getClass(el);
2321 setClass(el, (className ? className + ' ' : '') + name);
2322 }
2323}
2324
2325// @function removeClass(el: HTMLElement, name: String)
2326// Removes `name` from the element's class attribute.
2327function removeClass(el, name) {
2328 if (el.classList !== undefined) {
2329 el.classList.remove(name);
2330 } else {
2331 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
2332 }
2333}
2334
2335// @function setClass(el: HTMLElement, name: String)
2336// Sets the element's class.
2337function setClass(el, name) {
2338 if (el.className.baseVal === undefined) {
2339 el.className = name;
2340 } else {
2341 // in case of SVG element
2342 el.className.baseVal = name;
2343 }
2344}
2345
2346// @function getClass(el: HTMLElement): String
2347// Returns the element's class.
2348function getClass(el) {
2349 // Check if the element is an SVGElementInstance and use the correspondingElement instead
2350 // (Required for linked SVG elements in IE11.)
2351 if (el.correspondingElement) {
2352 el = el.correspondingElement;
2353 }
2354 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
2355}
2356
2357// @function setOpacity(el: HTMLElement, opacity: Number)
2358// Set the opacity of an element (including old IE support).
2359// `opacity` must be a number from `0` to `1`.
2360function setOpacity(el, value) {
2361 if ('opacity' in el.style) {
2362 el.style.opacity = value;
2363 } else if ('filter' in el.style) {
2364 _setOpacityIE(el, value);
2365 }
2366}
2367
2368function _setOpacityIE(el, value) {
2369 var filter = false,
2370 filterName = 'DXImageTransform.Microsoft.Alpha';
2371
2372 // filters collection throws an error if we try to retrieve a filter that doesn't exist
2373 try {
2374 filter = el.filters.item(filterName);
2375 } catch (e) {
2376 // don't set opacity to 1 if we haven't already set an opacity,
2377 // it isn't needed and breaks transparent pngs.
2378 if (value === 1) { return; }
2379 }
2380
2381 value = Math.round(value * 100);
2382
2383 if (filter) {
2384 filter.Enabled = (value !== 100);
2385 filter.Opacity = value;
2386 } else {
2387 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
2388 }
2389}
2390
2391// @function testProp(props: String[]): String|false
2392// Goes through the array of style names and returns the first name
2393// that is a valid style name for an element. If no such name is found,
2394// it returns false. Useful for vendor-prefixed styles like `transform`.
2395function testProp(props) {
2396 var style = document.documentElement.style;
2397
2398 for (var i = 0; i < props.length; i++) {
2399 if (props[i] in style) {
2400 return props[i];
2401 }
2402 }
2403 return false;
2404}
2405
2406// @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
2407// Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
2408// and optionally scaled by `scale`. Does not have an effect if the
2409// browser doesn't support 3D CSS transforms.
2410function setTransform(el, offset, scale) {
2411 var pos = offset || new Point(0, 0);
2412
2413 el.style[TRANSFORM] =
2414 (ie3d ?
2415 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
2416 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
2417 (scale ? ' scale(' + scale + ')' : '');
2418}
2419
2420// @function setPosition(el: HTMLElement, position: Point)
2421// Sets the position of `el` to coordinates specified by `position`,
2422// using CSS translate or top/left positioning depending on the browser
2423// (used by Leaflet internally to position its layers).
2424function setPosition(el, point) {
2425
2426 /*eslint-disable */
2427 el._leaflet_pos = point;
2428 /* eslint-enable */
2429
2430 if (any3d) {
2431 setTransform(el, point);
2432 } else {
2433 el.style.left = point.x + 'px';
2434 el.style.top = point.y + 'px';
2435 }
2436}
2437
2438// @function getPosition(el: HTMLElement): Point
2439// Returns the coordinates of an element previously positioned with setPosition.
2440function getPosition(el) {
2441 // this method is only used for elements previously positioned using setPosition,
2442 // so it's safe to cache the position for performance
2443
2444 return el._leaflet_pos || new Point(0, 0);
2445}
2446
2447// @function disableTextSelection()
2448// Prevents the user from generating `selectstart` DOM events, usually generated
2449// when the user drags the mouse through a page with text. Used internally
2450// by Leaflet to override the behaviour of any click-and-drag interaction on
2451// the map. Affects drag interactions on the whole document.
2452
2453// @function enableTextSelection()
2454// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
2455var disableTextSelection;
2456var enableTextSelection;
2457var _userSelect;
2458if ('onselectstart' in document) {
2459 disableTextSelection = function () {
2460 on(window, 'selectstart', preventDefault);
2461 };
2462 enableTextSelection = function () {
2463 off(window, 'selectstart', preventDefault);
2464 };
2465} else {
2466 var userSelectProperty = testProp(
2467 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
2468
2469 disableTextSelection = function () {
2470 if (userSelectProperty) {
2471 var style = document.documentElement.style;
2472 _userSelect = style[userSelectProperty];
2473 style[userSelectProperty] = 'none';
2474 }
2475 };
2476 enableTextSelection = function () {
2477 if (userSelectProperty) {
2478 document.documentElement.style[userSelectProperty] = _userSelect;
2479 _userSelect = undefined;
2480 }
2481 };
2482}
2483
2484// @function disableImageDrag()
2485// As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
2486// for `dragstart` DOM events, usually generated when the user drags an image.
2487function disableImageDrag() {
2488 on(window, 'dragstart', preventDefault);
2489}
2490
2491// @function enableImageDrag()
2492// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
2493function enableImageDrag() {
2494 off(window, 'dragstart', preventDefault);
2495}
2496
2497var _outlineElement, _outlineStyle;
2498// @function preventOutline(el: HTMLElement)
2499// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
2500// of the element `el` invisible. Used internally by Leaflet to prevent
2501// focusable elements from displaying an outline when the user performs a
2502// drag interaction on them.
2503function preventOutline(element) {
2504 while (element.tabIndex === -1) {
2505 element = element.parentNode;
2506 }
2507 if (!element.style) { return; }
2508 restoreOutline();
2509 _outlineElement = element;
2510 _outlineStyle = element.style.outline;
2511 element.style.outline = 'none';
2512 on(window, 'keydown', restoreOutline);
2513}
2514
2515// @function restoreOutline()
2516// Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
2517function restoreOutline() {
2518 if (!_outlineElement) { return; }
2519 _outlineElement.style.outline = _outlineStyle;
2520 _outlineElement = undefined;
2521 _outlineStyle = undefined;
2522 off(window, 'keydown', restoreOutline);
2523}
2524
2525// @function getSizedParentNode(el: HTMLElement): HTMLElement
2526// Finds the closest parent node which size (width and height) is not null.
2527function getSizedParentNode(element) {
2528 do {
2529 element = element.parentNode;
2530 } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
2531 return element;
2532}
2533
2534// @function getScale(el: HTMLElement): Object
2535// Computes the CSS scale currently applied on the element.
2536// Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
2537// and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
2538function getScale(element) {
2539 var rect = element.getBoundingClientRect(); // Read-only in old browsers.
2540
2541 return {
2542 x: rect.width / element.offsetWidth || 1,
2543 y: rect.height / element.offsetHeight || 1,
2544 boundingClientRect: rect
2545 };
2546}
2547
2548var DomUtil = ({
2549 TRANSFORM: TRANSFORM,
2550 TRANSITION: TRANSITION,
2551 TRANSITION_END: TRANSITION_END,
2552 get: get,
2553 getStyle: getStyle,
2554 create: create$1,
2555 remove: remove,
2556 empty: empty,
2557 toFront: toFront,
2558 toBack: toBack,
2559 hasClass: hasClass,
2560 addClass: addClass,
2561 removeClass: removeClass,
2562 setClass: setClass,
2563 getClass: getClass,
2564 setOpacity: setOpacity,
2565 testProp: testProp,
2566 setTransform: setTransform,
2567 setPosition: setPosition,
2568 getPosition: getPosition,
2569 get disableTextSelection () { return disableTextSelection; },
2570 get enableTextSelection () { return enableTextSelection; },
2571 disableImageDrag: disableImageDrag,
2572 enableImageDrag: enableImageDrag,
2573 preventOutline: preventOutline,
2574 restoreOutline: restoreOutline,
2575 getSizedParentNode: getSizedParentNode,
2576 getScale: getScale
2577});
2578
2579/*
2580 * @namespace DomEvent
2581 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
2582 */
2583
2584// Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
2585
2586// @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
2587// Adds a listener function (`fn`) to a particular DOM event type of the
2588// element `el`. You can optionally specify the context of the listener
2589// (object the `this` keyword will point to). You can also pass several
2590// space-separated types (e.g. `'click dblclick'`).
2591
2592// @alternative
2593// @function on(el: HTMLElement, eventMap: Object, context?: Object): this
2594// Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2595function on(obj, types, fn, context) {
2596
2597 if (typeof types === 'object') {
2598 for (var type in types) {
2599 addOne(obj, type, types[type], fn);
2600 }
2601 } else {
2602 types = splitWords(types);
2603
2604 for (var i = 0, len = types.length; i < len; i++) {
2605 addOne(obj, types[i], fn, context);
2606 }
2607 }
2608
2609 return this;
2610}
2611
2612var eventsKey = '_leaflet_events';
2613
2614// @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
2615// Removes a previously added listener function.
2616// Note that if you passed a custom context to on, you must pass the same
2617// context to `off` in order to remove the listener.
2618
2619// @alternative
2620// @function off(el: HTMLElement, eventMap: Object, context?: Object): this
2621// Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2622function off(obj, types, fn, context) {
2623
2624 if (typeof types === 'object') {
2625 for (var type in types) {
2626 removeOne(obj, type, types[type], fn);
2627 }
2628 } else if (types) {
2629 types = splitWords(types);
2630
2631 for (var i = 0, len = types.length; i < len; i++) {
2632 removeOne(obj, types[i], fn, context);
2633 }
2634 } else {
2635 for (var j in obj[eventsKey]) {
2636 removeOne(obj, j, obj[eventsKey][j]);
2637 }
2638 delete obj[eventsKey];
2639 }
2640
2641 return this;
2642}
2643
2644function browserFiresNativeDblClick() {
2645 // See https://github.com/w3c/pointerevents/issues/171
2646 if (pointer) {
2647 return !(edge || safari);
2648 }
2649}
2650
2651var mouseSubst = {
2652 mouseenter: 'mouseover',
2653 mouseleave: 'mouseout',
2654 wheel: !('onwheel' in window) && 'mousewheel'
2655};
2656
2657function addOne(obj, type, fn, context) {
2658 var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
2659
2660 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
2661
2662 var handler = function (e) {
2663 return fn.call(context || obj, e || window.event);
2664 };
2665
2666 var originalHandler = handler;
2667
2668 if (pointer && type.indexOf('touch') === 0) {
2669 // Needs DomEvent.Pointer.js
2670 addPointerListener(obj, type, handler, id);
2671
2672 } else if (touch && (type === 'dblclick') && !browserFiresNativeDblClick()) {
2673 addDoubleTapListener(obj, handler, id);
2674
2675 } else if ('addEventListener' in obj) {
2676
2677 if (type === 'touchstart' || type === 'touchmove' || type === 'wheel' || type === 'mousewheel') {
2678 obj.addEventListener(mouseSubst[type] || type, handler, passiveEvents ? {passive: false} : false);
2679
2680 } else if (type === 'mouseenter' || type === 'mouseleave') {
2681 handler = function (e) {
2682 e = e || window.event;
2683 if (isExternalTarget(obj, e)) {
2684 originalHandler(e);
2685 }
2686 };
2687 obj.addEventListener(mouseSubst[type], handler, false);
2688
2689 } else {
2690 obj.addEventListener(type, originalHandler, false);
2691 }
2692
2693 } else if ('attachEvent' in obj) {
2694 obj.attachEvent('on' + type, handler);
2695 }
2696
2697 obj[eventsKey] = obj[eventsKey] || {};
2698 obj[eventsKey][id] = handler;
2699}
2700
2701function removeOne(obj, type, fn, context) {
2702
2703 var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
2704 handler = obj[eventsKey] && obj[eventsKey][id];
2705
2706 if (!handler) { return this; }
2707
2708 if (pointer && type.indexOf('touch') === 0) {
2709 removePointerListener(obj, type, id);
2710
2711 } else if (touch && (type === 'dblclick') && !browserFiresNativeDblClick()) {
2712 removeDoubleTapListener(obj, id);
2713
2714 } else if ('removeEventListener' in obj) {
2715
2716 obj.removeEventListener(mouseSubst[type] || type, handler, false);
2717
2718 } else if ('detachEvent' in obj) {
2719 obj.detachEvent('on' + type, handler);
2720 }
2721
2722 obj[eventsKey][id] = null;
2723}
2724
2725// @function stopPropagation(ev: DOMEvent): this
2726// Stop the given event from propagation to parent elements. Used inside the listener functions:
2727// ```js
2728// L.DomEvent.on(div, 'click', function (ev) {
2729// L.DomEvent.stopPropagation(ev);
2730// });
2731// ```
2732function stopPropagation(e) {
2733
2734 if (e.stopPropagation) {
2735 e.stopPropagation();
2736 } else if (e.originalEvent) { // In case of Leaflet event.
2737 e.originalEvent._stopped = true;
2738 } else {
2739 e.cancelBubble = true;
2740 }
2741 skipped(e);
2742
2743 return this;
2744}
2745
2746// @function disableScrollPropagation(el: HTMLElement): this
2747// Adds `stopPropagation` to the element's `'wheel'` events (plus browser variants).
2748function disableScrollPropagation(el) {
2749 addOne(el, 'wheel', stopPropagation);
2750 return this;
2751}
2752
2753// @function disableClickPropagation(el: HTMLElement): this
2754// Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
2755// `'mousedown'` and `'touchstart'` events (plus browser variants).
2756function disableClickPropagation(el) {
2757 on(el, 'mousedown touchstart dblclick', stopPropagation);
2758 addOne(el, 'click', fakeStop);
2759 return this;
2760}
2761
2762// @function preventDefault(ev: DOMEvent): this
2763// Prevents the default action of the DOM Event `ev` from happening (such as
2764// following a link in the href of the a element, or doing a POST request
2765// with page reload when a `<form>` is submitted).
2766// Use it inside listener functions.
2767function preventDefault(e) {
2768 if (e.preventDefault) {
2769 e.preventDefault();
2770 } else {
2771 e.returnValue = false;
2772 }
2773 return this;
2774}
2775
2776// @function stop(ev: DOMEvent): this
2777// Does `stopPropagation` and `preventDefault` at the same time.
2778function stop(e) {
2779 preventDefault(e);
2780 stopPropagation(e);
2781 return this;
2782}
2783
2784// @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
2785// Gets normalized mouse position from a DOM event relative to the
2786// `container` (border excluded) or to the whole page if not specified.
2787function getMousePosition(e, container) {
2788 if (!container) {
2789 return new Point(e.clientX, e.clientY);
2790 }
2791
2792 var scale = getScale(container),
2793 offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y)
2794
2795 return new Point(
2796 // offset.left/top values are in page scale (like clientX/Y),
2797 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
2798 (e.clientX - offset.left) / scale.x - container.clientLeft,
2799 (e.clientY - offset.top) / scale.y - container.clientTop
2800 );
2801}
2802
2803// Chrome on Win scrolls double the pixels as in other platforms (see #4538),
2804// and Firefox scrolls device pixels, not CSS pixels
2805var wheelPxFactor =
2806 (win && chrome) ? 2 * window.devicePixelRatio :
2807 gecko ? window.devicePixelRatio : 1;
2808
2809// @function getWheelDelta(ev: DOMEvent): Number
2810// Gets normalized wheel delta from a wheel DOM event, in vertical
2811// pixels scrolled (negative if scrolling down).
2812// Events from pointing devices without precise scrolling are mapped to
2813// a best guess of 60 pixels.
2814function getWheelDelta(e) {
2815 return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
2816 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
2817 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
2818 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
2819 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
2820 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
2821 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
2822 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
2823 0;
2824}
2825
2826var skipEvents = {};
2827
2828function fakeStop(e) {
2829 // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
2830 skipEvents[e.type] = true;
2831}
2832
2833function skipped(e) {
2834 var events = skipEvents[e.type];
2835 // reset when checking, as it's only used in map container and propagates outside of the map
2836 skipEvents[e.type] = false;
2837 return events;
2838}
2839
2840// check if element really left/entered the event target (for mouseenter/mouseleave)
2841function isExternalTarget(el, e) {
2842
2843 var related = e.relatedTarget;
2844
2845 if (!related) { return true; }
2846
2847 try {
2848 while (related && (related !== el)) {
2849 related = related.parentNode;
2850 }
2851 } catch (err) {
2852 return false;
2853 }
2854 return (related !== el);
2855}
2856
2857var DomEvent = ({
2858 on: on,
2859 off: off,
2860 stopPropagation: stopPropagation,
2861 disableScrollPropagation: disableScrollPropagation,
2862 disableClickPropagation: disableClickPropagation,
2863 preventDefault: preventDefault,
2864 stop: stop,
2865 getMousePosition: getMousePosition,
2866 getWheelDelta: getWheelDelta,
2867 fakeStop: fakeStop,
2868 skipped: skipped,
2869 isExternalTarget: isExternalTarget,
2870 addListener: on,
2871 removeListener: off
2872});
2873
2874/*
2875 * @class PosAnimation
2876 * @aka L.PosAnimation
2877 * @inherits Evented
2878 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
2879 *
2880 * @example
2881 * ```js
2882 * var fx = new L.PosAnimation();
2883 * fx.run(el, [300, 500], 0.5);
2884 * ```
2885 *
2886 * @constructor L.PosAnimation()
2887 * Creates a `PosAnimation` object.
2888 *
2889 */
2890
2891var PosAnimation = Evented.extend({
2892
2893 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
2894 // Run an animation of a given element to a new position, optionally setting
2895 // duration in seconds (`0.25` by default) and easing linearity factor (3rd
2896 // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
2897 // `0.5` by default).
2898 run: function (el, newPos, duration, easeLinearity) {
2899 this.stop();
2900
2901 this._el = el;
2902 this._inProgress = true;
2903 this._duration = duration || 0.25;
2904 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
2905
2906 this._startPos = getPosition(el);
2907 this._offset = newPos.subtract(this._startPos);
2908 this._startTime = +new Date();
2909
2910 // @event start: Event
2911 // Fired when the animation starts
2912 this.fire('start');
2913
2914 this._animate();
2915 },
2916
2917 // @method stop()
2918 // Stops the animation (if currently running).
2919 stop: function () {
2920 if (!this._inProgress) { return; }
2921
2922 this._step(true);
2923 this._complete();
2924 },
2925
2926 _animate: function () {
2927 // animation loop
2928 this._animId = requestAnimFrame(this._animate, this);
2929 this._step();
2930 },
2931
2932 _step: function (round) {
2933 var elapsed = (+new Date()) - this._startTime,
2934 duration = this._duration * 1000;
2935
2936 if (elapsed < duration) {
2937 this._runFrame(this._easeOut(elapsed / duration), round);
2938 } else {
2939 this._runFrame(1);
2940 this._complete();
2941 }
2942 },
2943
2944 _runFrame: function (progress, round) {
2945 var pos = this._startPos.add(this._offset.multiplyBy(progress));
2946 if (round) {
2947 pos._round();
2948 }
2949 setPosition(this._el, pos);
2950
2951 // @event step: Event
2952 // Fired continuously during the animation.
2953 this.fire('step');
2954 },
2955
2956 _complete: function () {
2957 cancelAnimFrame(this._animId);
2958
2959 this._inProgress = false;
2960 // @event end: Event
2961 // Fired when the animation ends.
2962 this.fire('end');
2963 },
2964
2965 _easeOut: function (t) {
2966 return 1 - Math.pow(1 - t, this._easeOutPower);
2967 }
2968});
2969
2970/*
2971 * @class Map
2972 * @aka L.Map
2973 * @inherits Evented
2974 *
2975 * The central class of the API — it is used to create a map on a page and manipulate it.
2976 *
2977 * @example
2978 *
2979 * ```js
2980 * // initialize the map on the "map" div with a given center and zoom
2981 * var map = L.map('map', {
2982 * center: [51.505, -0.09],
2983 * zoom: 13
2984 * });
2985 * ```
2986 *
2987 */
2988
2989var Map = Evented.extend({
2990
2991 options: {
2992 // @section Map State Options
2993 // @option crs: CRS = L.CRS.EPSG3857
2994 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
2995 // sure what it means.
2996 crs: EPSG3857,
2997
2998 // @option center: LatLng = undefined
2999 // Initial geographic center of the map
3000 center: undefined,
3001
3002 // @option zoom: Number = undefined
3003 // Initial map zoom level
3004 zoom: undefined,
3005
3006 // @option minZoom: Number = *
3007 // Minimum zoom level of the map.
3008 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3009 // the lowest of their `minZoom` options will be used instead.
3010 minZoom: undefined,
3011
3012 // @option maxZoom: Number = *
3013 // Maximum zoom level of the map.
3014 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3015 // the highest of their `maxZoom` options will be used instead.
3016 maxZoom: undefined,
3017
3018 // @option layers: Layer[] = []
3019 // Array of layers that will be added to the map initially
3020 layers: [],
3021
3022 // @option maxBounds: LatLngBounds = null
3023 // When this option is set, the map restricts the view to the given
3024 // geographical bounds, bouncing the user back if the user tries to pan
3025 // outside the view. To set the restriction dynamically, use
3026 // [`setMaxBounds`](#map-setmaxbounds) method.
3027 maxBounds: undefined,
3028
3029 // @option renderer: Renderer = *
3030 // The default method for drawing vector layers on the map. `L.SVG`
3031 // or `L.Canvas` by default depending on browser support.
3032 renderer: undefined,
3033
3034
3035 // @section Animation Options
3036 // @option zoomAnimation: Boolean = true
3037 // Whether the map zoom animation is enabled. By default it's enabled
3038 // in all browsers that support CSS3 Transitions except Android.
3039 zoomAnimation: true,
3040
3041 // @option zoomAnimationThreshold: Number = 4
3042 // Won't animate zoom if the zoom difference exceeds this value.
3043 zoomAnimationThreshold: 4,
3044
3045 // @option fadeAnimation: Boolean = true
3046 // Whether the tile fade animation is enabled. By default it's enabled
3047 // in all browsers that support CSS3 Transitions except Android.
3048 fadeAnimation: true,
3049
3050 // @option markerZoomAnimation: Boolean = true
3051 // Whether markers animate their zoom with the zoom animation, if disabled
3052 // they will disappear for the length of the animation. By default it's
3053 // enabled in all browsers that support CSS3 Transitions except Android.
3054 markerZoomAnimation: true,
3055
3056 // @option transform3DLimit: Number = 2^23
3057 // Defines the maximum size of a CSS translation transform. The default
3058 // value should not be changed unless a web browser positions layers in
3059 // the wrong place after doing a large `panBy`.
3060 transform3DLimit: 8388608, // Precision limit of a 32-bit float
3061
3062 // @section Interaction Options
3063 // @option zoomSnap: Number = 1
3064 // Forces the map's zoom level to always be a multiple of this, particularly
3065 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
3066 // By default, the zoom level snaps to the nearest integer; lower values
3067 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
3068 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
3069 zoomSnap: 1,
3070
3071 // @option zoomDelta: Number = 1
3072 // Controls how much the map's zoom level will change after a
3073 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
3074 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
3075 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
3076 zoomDelta: 1,
3077
3078 // @option trackResize: Boolean = true
3079 // Whether the map automatically handles browser window resize to update itself.
3080 trackResize: true
3081 },
3082
3083 initialize: function (id, options) { // (HTMLElement or String, Object)
3084 options = setOptions(this, options);
3085
3086 // Make sure to assign internal flags at the beginning,
3087 // to avoid inconsistent state in some edge cases.
3088 this._handlers = [];
3089 this._layers = {};
3090 this._zoomBoundLayers = {};
3091 this._sizeChanged = true;
3092
3093 this._initContainer(id);
3094 this._initLayout();
3095
3096 // hack for https://github.com/Leaflet/Leaflet/issues/1980
3097 this._onResize = bind(this._onResize, this);
3098
3099 this._initEvents();
3100
3101 if (options.maxBounds) {
3102 this.setMaxBounds(options.maxBounds);
3103 }
3104
3105 if (options.zoom !== undefined) {
3106 this._zoom = this._limitZoom(options.zoom);
3107 }
3108
3109 if (options.center && options.zoom !== undefined) {
3110 this.setView(toLatLng(options.center), options.zoom, {reset: true});
3111 }
3112
3113 this.callInitHooks();
3114
3115 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
3116 this._zoomAnimated = TRANSITION && any3d && !mobileOpera &&
3117 this.options.zoomAnimation;
3118
3119 // zoom transitions run with the same duration for all layers, so if one of transitionend events
3120 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
3121 if (this._zoomAnimated) {
3122 this._createAnimProxy();
3123 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
3124 }
3125
3126 this._addLayers(this.options.layers);
3127 },
3128
3129
3130 // @section Methods for modifying map state
3131
3132 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
3133 // Sets the view of the map (geographical center and zoom) with the given
3134 // animation options.
3135 setView: function (center, zoom, options) {
3136
3137 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
3138 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
3139 options = options || {};
3140
3141 this._stop();
3142
3143 if (this._loaded && !options.reset && options !== true) {
3144
3145 if (options.animate !== undefined) {
3146 options.zoom = extend({animate: options.animate}, options.zoom);
3147 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
3148 }
3149
3150 // try animating pan or zoom
3151 var moved = (this._zoom !== zoom) ?
3152 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
3153 this._tryAnimatedPan(center, options.pan);
3154
3155 if (moved) {
3156 // prevent resize handler call, the view will refresh after animation anyway
3157 clearTimeout(this._sizeTimer);
3158 return this;
3159 }
3160 }
3161
3162 // animation didn't start, just reset the map view
3163 this._resetView(center, zoom);
3164
3165 return this;
3166 },
3167
3168 // @method setZoom(zoom: Number, options?: Zoom/pan options): this
3169 // Sets the zoom of the map.
3170 setZoom: function (zoom, options) {
3171 if (!this._loaded) {
3172 this._zoom = zoom;
3173 return this;
3174 }
3175 return this.setView(this.getCenter(), zoom, {zoom: options});
3176 },
3177
3178 // @method zoomIn(delta?: Number, options?: Zoom options): this
3179 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3180 zoomIn: function (delta, options) {
3181 delta = delta || (any3d ? this.options.zoomDelta : 1);
3182 return this.setZoom(this._zoom + delta, options);
3183 },
3184
3185 // @method zoomOut(delta?: Number, options?: Zoom options): this
3186 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3187 zoomOut: function (delta, options) {
3188 delta = delta || (any3d ? this.options.zoomDelta : 1);
3189 return this.setZoom(this._zoom - delta, options);
3190 },
3191
3192 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
3193 // Zooms the map while keeping a specified geographical point on the map
3194 // stationary (e.g. used internally for scroll zoom and double-click zoom).
3195 // @alternative
3196 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
3197 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
3198 setZoomAround: function (latlng, zoom, options) {
3199 var scale = this.getZoomScale(zoom),
3200 viewHalf = this.getSize().divideBy(2),
3201 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
3202
3203 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
3204 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
3205
3206 return this.setView(newCenter, zoom, {zoom: options});
3207 },
3208
3209 _getBoundsCenterZoom: function (bounds, options) {
3210
3211 options = options || {};
3212 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
3213
3214 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3215 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3216
3217 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
3218
3219 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
3220
3221 if (zoom === Infinity) {
3222 return {
3223 center: bounds.getCenter(),
3224 zoom: zoom
3225 };
3226 }
3227
3228 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
3229
3230 swPoint = this.project(bounds.getSouthWest(), zoom),
3231 nePoint = this.project(bounds.getNorthEast(), zoom),
3232 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
3233
3234 return {
3235 center: center,
3236 zoom: zoom
3237 };
3238 },
3239
3240 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
3241 // Sets a map view that contains the given geographical bounds with the
3242 // maximum zoom level possible.
3243 fitBounds: function (bounds, options) {
3244
3245 bounds = toLatLngBounds(bounds);
3246
3247 if (!bounds.isValid()) {
3248 throw new Error('Bounds are not valid.');
3249 }
3250
3251 var target = this._getBoundsCenterZoom(bounds, options);
3252 return this.setView(target.center, target.zoom, options);
3253 },
3254
3255 // @method fitWorld(options?: fitBounds options): this
3256 // Sets a map view that mostly contains the whole world with the maximum
3257 // zoom level possible.
3258 fitWorld: function (options) {
3259 return this.fitBounds([[-90, -180], [90, 180]], options);
3260 },
3261
3262 // @method panTo(latlng: LatLng, options?: Pan options): this
3263 // Pans the map to a given center.
3264 panTo: function (center, options) { // (LatLng)
3265 return this.setView(center, this._zoom, {pan: options});
3266 },
3267
3268 // @method panBy(offset: Point, options?: Pan options): this
3269 // Pans the map by a given number of pixels (animated).
3270 panBy: function (offset, options) {
3271 offset = toPoint(offset).round();
3272 options = options || {};
3273
3274 if (!offset.x && !offset.y) {
3275 return this.fire('moveend');
3276 }
3277 // If we pan too far, Chrome gets issues with tiles
3278 // and makes them disappear or appear in the wrong place (slightly offset) #2602
3279 if (options.animate !== true && !this.getSize().contains(offset)) {
3280 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
3281 return this;
3282 }
3283
3284 if (!this._panAnim) {
3285 this._panAnim = new PosAnimation();
3286
3287 this._panAnim.on({
3288 'step': this._onPanTransitionStep,
3289 'end': this._onPanTransitionEnd
3290 }, this);
3291 }
3292
3293 // don't fire movestart if animating inertia
3294 if (!options.noMoveStart) {
3295 this.fire('movestart');
3296 }
3297
3298 // animate pan unless animate: false specified
3299 if (options.animate !== false) {
3300 addClass(this._mapPane, 'leaflet-pan-anim');
3301
3302 var newPos = this._getMapPanePos().subtract(offset).round();
3303 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
3304 } else {
3305 this._rawPanBy(offset);
3306 this.fire('move').fire('moveend');
3307 }
3308
3309 return this;
3310 },
3311
3312 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
3313 // Sets the view of the map (geographical center and zoom) performing a smooth
3314 // pan-zoom animation.
3315 flyTo: function (targetCenter, targetZoom, options) {
3316
3317 options = options || {};
3318 if (options.animate === false || !any3d) {
3319 return this.setView(targetCenter, targetZoom, options);
3320 }
3321
3322 this._stop();
3323
3324 var from = this.project(this.getCenter()),
3325 to = this.project(targetCenter),
3326 size = this.getSize(),
3327 startZoom = this._zoom;
3328
3329 targetCenter = toLatLng(targetCenter);
3330 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
3331
3332 var w0 = Math.max(size.x, size.y),
3333 w1 = w0 * this.getZoomScale(startZoom, targetZoom),
3334 u1 = (to.distanceTo(from)) || 1,
3335 rho = 1.42,
3336 rho2 = rho * rho;
3337
3338 function r(i) {
3339 var s1 = i ? -1 : 1,
3340 s2 = i ? w1 : w0,
3341 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
3342 b1 = 2 * s2 * rho2 * u1,
3343 b = t1 / b1,
3344 sq = Math.sqrt(b * b + 1) - b;
3345
3346 // workaround for floating point precision bug when sq = 0, log = -Infinite,
3347 // thus triggering an infinite loop in flyTo
3348 var log = sq < 0.000000001 ? -18 : Math.log(sq);
3349
3350 return log;
3351 }
3352
3353 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
3354 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
3355 function tanh(n) { return sinh(n) / cosh(n); }
3356
3357 var r0 = r(0);
3358
3359 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
3360 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
3361
3362 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
3363
3364 var start = Date.now(),
3365 S = (r(1) - r0) / rho,
3366 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
3367
3368 function frame() {
3369 var t = (Date.now() - start) / duration,
3370 s = easeOut(t) * S;
3371
3372 if (t <= 1) {
3373 this._flyToFrame = requestAnimFrame(frame, this);
3374
3375 this._move(
3376 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
3377 this.getScaleZoom(w0 / w(s), startZoom),
3378 {flyTo: true});
3379
3380 } else {
3381 this
3382 ._move(targetCenter, targetZoom)
3383 ._moveEnd(true);
3384 }
3385 }
3386
3387 this._moveStart(true, options.noMoveStart);
3388
3389 frame.call(this);
3390 return this;
3391 },
3392
3393 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
3394 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
3395 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
3396 flyToBounds: function (bounds, options) {
3397 var target = this._getBoundsCenterZoom(bounds, options);
3398 return this.flyTo(target.center, target.zoom, options);
3399 },
3400
3401 // @method setMaxBounds(bounds: LatLngBounds): this
3402 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
3403 setMaxBounds: function (bounds) {
3404 bounds = toLatLngBounds(bounds);
3405
3406 if (!bounds.isValid()) {
3407 this.options.maxBounds = null;
3408 return this.off('moveend', this._panInsideMaxBounds);
3409 } else if (this.options.maxBounds) {
3410 this.off('moveend', this._panInsideMaxBounds);
3411 }
3412
3413 this.options.maxBounds = bounds;
3414
3415 if (this._loaded) {
3416 this._panInsideMaxBounds();
3417 }
3418
3419 return this.on('moveend', this._panInsideMaxBounds);
3420 },
3421
3422 // @method setMinZoom(zoom: Number): this
3423 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
3424 setMinZoom: function (zoom) {
3425 var oldZoom = this.options.minZoom;
3426 this.options.minZoom = zoom;
3427
3428 if (this._loaded && oldZoom !== zoom) {
3429 this.fire('zoomlevelschange');
3430
3431 if (this.getZoom() < this.options.minZoom) {
3432 return this.setZoom(zoom);
3433 }
3434 }
3435
3436 return this;
3437 },
3438
3439 // @method setMaxZoom(zoom: Number): this
3440 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
3441 setMaxZoom: function (zoom) {
3442 var oldZoom = this.options.maxZoom;
3443 this.options.maxZoom = zoom;
3444
3445 if (this._loaded && oldZoom !== zoom) {
3446 this.fire('zoomlevelschange');
3447
3448 if (this.getZoom() > this.options.maxZoom) {
3449 return this.setZoom(zoom);
3450 }
3451 }
3452
3453 return this;
3454 },
3455
3456 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
3457 // 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.
3458 panInsideBounds: function (bounds, options) {
3459 this._enforcingBounds = true;
3460 var center = this.getCenter(),
3461 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
3462
3463 if (!center.equals(newCenter)) {
3464 this.panTo(newCenter, options);
3465 }
3466
3467 this._enforcingBounds = false;
3468 return this;
3469 },
3470
3471 // @method panInside(latlng: LatLng, options?: options): this
3472 // Pans the map the minimum amount to make the `latlng` visible. Use
3473 // `padding`, `paddingTopLeft` and `paddingTopRight` options to fit
3474 // the display to more restricted bounds, like [`fitBounds`](#map-fitbounds).
3475 // If `latlng` is already within the (optionally padded) display bounds,
3476 // the map will not be panned.
3477 panInside: function (latlng, options) {
3478 options = options || {};
3479
3480 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3481 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3482 center = this.getCenter(),
3483 pixelCenter = this.project(center),
3484 pixelPoint = this.project(latlng),
3485 pixelBounds = this.getPixelBounds(),
3486 halfPixelBounds = pixelBounds.getSize().divideBy(2),
3487 paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]);
3488
3489 if (!paddedBounds.contains(pixelPoint)) {
3490 this._enforcingBounds = true;
3491 var diff = pixelCenter.subtract(pixelPoint),
3492 newCenter = toPoint(pixelPoint.x + diff.x, pixelPoint.y + diff.y);
3493
3494 if (pixelPoint.x < paddedBounds.min.x || pixelPoint.x > paddedBounds.max.x) {
3495 newCenter.x = pixelCenter.x - diff.x;
3496 if (diff.x > 0) {
3497 newCenter.x += halfPixelBounds.x - paddingTL.x;
3498 } else {
3499 newCenter.x -= halfPixelBounds.x - paddingBR.x;
3500 }
3501 }
3502 if (pixelPoint.y < paddedBounds.min.y || pixelPoint.y > paddedBounds.max.y) {
3503 newCenter.y = pixelCenter.y - diff.y;
3504 if (diff.y > 0) {
3505 newCenter.y += halfPixelBounds.y - paddingTL.y;
3506 } else {
3507 newCenter.y -= halfPixelBounds.y - paddingBR.y;
3508 }
3509 }
3510 this.panTo(this.unproject(newCenter), options);
3511 this._enforcingBounds = false;
3512 }
3513 return this;
3514 },
3515
3516 // @method invalidateSize(options: Zoom/pan options): this
3517 // Checks if the map container size changed and updates the map if so —
3518 // call it after you've changed the map size dynamically, also animating
3519 // pan by default. If `options.pan` is `false`, panning will not occur.
3520 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
3521 // that it doesn't happen often even if the method is called many
3522 // times in a row.
3523
3524 // @alternative
3525 // @method invalidateSize(animate: Boolean): this
3526 // Checks if the map container size changed and updates the map if so —
3527 // call it after you've changed the map size dynamically, also animating
3528 // pan by default.
3529 invalidateSize: function (options) {
3530 if (!this._loaded) { return this; }
3531
3532 options = extend({
3533 animate: false,
3534 pan: true
3535 }, options === true ? {animate: true} : options);
3536
3537 var oldSize = this.getSize();
3538 this._sizeChanged = true;
3539 this._lastCenter = null;
3540
3541 var newSize = this.getSize(),
3542 oldCenter = oldSize.divideBy(2).round(),
3543 newCenter = newSize.divideBy(2).round(),
3544 offset = oldCenter.subtract(newCenter);
3545
3546 if (!offset.x && !offset.y) { return this; }
3547
3548 if (options.animate && options.pan) {
3549 this.panBy(offset);
3550
3551 } else {
3552 if (options.pan) {
3553 this._rawPanBy(offset);
3554 }
3555
3556 this.fire('move');
3557
3558 if (options.debounceMoveend) {
3559 clearTimeout(this._sizeTimer);
3560 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
3561 } else {
3562 this.fire('moveend');
3563 }
3564 }
3565
3566 // @section Map state change events
3567 // @event resize: ResizeEvent
3568 // Fired when the map is resized.
3569 return this.fire('resize', {
3570 oldSize: oldSize,
3571 newSize: newSize
3572 });
3573 },
3574
3575 // @section Methods for modifying map state
3576 // @method stop(): this
3577 // Stops the currently running `panTo` or `flyTo` animation, if any.
3578 stop: function () {
3579 this.setZoom(this._limitZoom(this._zoom));
3580 if (!this.options.zoomSnap) {
3581 this.fire('viewreset');
3582 }
3583 return this._stop();
3584 },
3585
3586 // @section Geolocation methods
3587 // @method locate(options?: Locate options): this
3588 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
3589 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
3590 // and optionally sets the map view to the user's location with respect to
3591 // detection accuracy (or to the world view if geolocation failed).
3592 // Note that, if your page doesn't use HTTPS, this method will fail in
3593 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
3594 // See `Locate options` for more details.
3595 locate: function (options) {
3596
3597 options = this._locateOptions = extend({
3598 timeout: 10000,
3599 watch: false
3600 // setView: false
3601 // maxZoom: <Number>
3602 // maximumAge: 0
3603 // enableHighAccuracy: false
3604 }, options);
3605
3606 if (!('geolocation' in navigator)) {
3607 this._handleGeolocationError({
3608 code: 0,
3609 message: 'Geolocation not supported.'
3610 });
3611 return this;
3612 }
3613
3614 var onResponse = bind(this._handleGeolocationResponse, this),
3615 onError = bind(this._handleGeolocationError, this);
3616
3617 if (options.watch) {
3618 this._locationWatchId =
3619 navigator.geolocation.watchPosition(onResponse, onError, options);
3620 } else {
3621 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
3622 }
3623 return this;
3624 },
3625
3626 // @method stopLocate(): this
3627 // Stops watching location previously initiated by `map.locate({watch: true})`
3628 // and aborts resetting the map view if map.locate was called with
3629 // `{setView: true}`.
3630 stopLocate: function () {
3631 if (navigator.geolocation && navigator.geolocation.clearWatch) {
3632 navigator.geolocation.clearWatch(this._locationWatchId);
3633 }
3634 if (this._locateOptions) {
3635 this._locateOptions.setView = false;
3636 }
3637 return this;
3638 },
3639
3640 _handleGeolocationError: function (error) {
3641 var c = error.code,
3642 message = error.message ||
3643 (c === 1 ? 'permission denied' :
3644 (c === 2 ? 'position unavailable' : 'timeout'));
3645
3646 if (this._locateOptions.setView && !this._loaded) {
3647 this.fitWorld();
3648 }
3649
3650 // @section Location events
3651 // @event locationerror: ErrorEvent
3652 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
3653 this.fire('locationerror', {
3654 code: c,
3655 message: 'Geolocation error: ' + message + '.'
3656 });
3657 },
3658
3659 _handleGeolocationResponse: function (pos) {
3660 var lat = pos.coords.latitude,
3661 lng = pos.coords.longitude,
3662 latlng = new LatLng(lat, lng),
3663 bounds = latlng.toBounds(pos.coords.accuracy * 2),
3664 options = this._locateOptions;
3665
3666 if (options.setView) {
3667 var zoom = this.getBoundsZoom(bounds);
3668 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
3669 }
3670
3671 var data = {
3672 latlng: latlng,
3673 bounds: bounds,
3674 timestamp: pos.timestamp
3675 };
3676
3677 for (var i in pos.coords) {
3678 if (typeof pos.coords[i] === 'number') {
3679 data[i] = pos.coords[i];
3680 }
3681 }
3682
3683 // @event locationfound: LocationEvent
3684 // Fired when geolocation (using the [`locate`](#map-locate) method)
3685 // went successfully.
3686 this.fire('locationfound', data);
3687 },
3688
3689 // TODO Appropriate docs section?
3690 // @section Other Methods
3691 // @method addHandler(name: String, HandlerClass: Function): this
3692 // Adds a new `Handler` to the map, given its name and constructor function.
3693 addHandler: function (name, HandlerClass) {
3694 if (!HandlerClass) { return this; }
3695
3696 var handler = this[name] = new HandlerClass(this);
3697
3698 this._handlers.push(handler);
3699
3700 if (this.options[name]) {
3701 handler.enable();
3702 }
3703
3704 return this;
3705 },
3706
3707 // @method remove(): this
3708 // Destroys the map and clears all related event listeners.
3709 remove: function () {
3710
3711 this._initEvents(true);
3712 this.off('moveend', this._panInsideMaxBounds);
3713
3714 if (this._containerId !== this._container._leaflet_id) {
3715 throw new Error('Map container is being reused by another instance');
3716 }
3717
3718 try {
3719 // throws error in IE6-8
3720 delete this._container._leaflet_id;
3721 delete this._containerId;
3722 } catch (e) {
3723 /*eslint-disable */
3724 this._container._leaflet_id = undefined;
3725 /* eslint-enable */
3726 this._containerId = undefined;
3727 }
3728
3729 if (this._locationWatchId !== undefined) {
3730 this.stopLocate();
3731 }
3732
3733 this._stop();
3734
3735 remove(this._mapPane);
3736
3737 if (this._clearControlPos) {
3738 this._clearControlPos();
3739 }
3740 if (this._resizeRequest) {
3741 cancelAnimFrame(this._resizeRequest);
3742 this._resizeRequest = null;
3743 }
3744
3745 this._clearHandlers();
3746
3747 if (this._loaded) {
3748 // @section Map state change events
3749 // @event unload: Event
3750 // Fired when the map is destroyed with [remove](#map-remove) method.
3751 this.fire('unload');
3752 }
3753
3754 var i;
3755 for (i in this._layers) {
3756 this._layers[i].remove();
3757 }
3758 for (i in this._panes) {
3759 remove(this._panes[i]);
3760 }
3761
3762 this._layers = [];
3763 this._panes = [];
3764 delete this._mapPane;
3765 delete this._renderer;
3766
3767 return this;
3768 },
3769
3770 // @section Other Methods
3771 // @method createPane(name: String, container?: HTMLElement): HTMLElement
3772 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
3773 // then returns it. The pane is created as a child of `container`, or
3774 // as a child of the main map pane if not set.
3775 createPane: function (name, container) {
3776 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3777 pane = create$1('div', className, container || this._mapPane);
3778
3779 if (name) {
3780 this._panes[name] = pane;
3781 }
3782 return pane;
3783 },
3784
3785 // @section Methods for Getting Map State
3786
3787 // @method getCenter(): LatLng
3788 // Returns the geographical center of the map view
3789 getCenter: function () {
3790 this._checkIfLoaded();
3791
3792 if (this._lastCenter && !this._moved()) {
3793 return this._lastCenter;
3794 }
3795 return this.layerPointToLatLng(this._getCenterLayerPoint());
3796 },
3797
3798 // @method getZoom(): Number
3799 // Returns the current zoom level of the map view
3800 getZoom: function () {
3801 return this._zoom;
3802 },
3803
3804 // @method getBounds(): LatLngBounds
3805 // Returns the geographical bounds visible in the current map view
3806 getBounds: function () {
3807 var bounds = this.getPixelBounds(),
3808 sw = this.unproject(bounds.getBottomLeft()),
3809 ne = this.unproject(bounds.getTopRight());
3810
3811 return new LatLngBounds(sw, ne);
3812 },
3813
3814 // @method getMinZoom(): Number
3815 // 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.
3816 getMinZoom: function () {
3817 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3818 },
3819
3820 // @method getMaxZoom(): Number
3821 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3822 getMaxZoom: function () {
3823 return this.options.maxZoom === undefined ?
3824 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3825 this.options.maxZoom;
3826 },
3827
3828 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
3829 // Returns the maximum zoom level on which the given bounds fit to the map
3830 // view in its entirety. If `inside` (optional) is set to `true`, the method
3831 // instead returns the minimum zoom level on which the map view fits into
3832 // the given bounds in its entirety.
3833 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3834 bounds = toLatLngBounds(bounds);
3835 padding = toPoint(padding || [0, 0]);
3836
3837 var zoom = this.getZoom() || 0,
3838 min = this.getMinZoom(),
3839 max = this.getMaxZoom(),
3840 nw = bounds.getNorthWest(),
3841 se = bounds.getSouthEast(),
3842 size = this.getSize().subtract(padding),
3843 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3844 snap = any3d ? this.options.zoomSnap : 1,
3845 scalex = size.x / boundsSize.x,
3846 scaley = size.y / boundsSize.y,
3847 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
3848
3849 zoom = this.getScaleZoom(scale, zoom);
3850
3851 if (snap) {
3852 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3853 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3854 }
3855
3856 return Math.max(min, Math.min(max, zoom));
3857 },
3858
3859 // @method getSize(): Point
3860 // Returns the current size of the map container (in pixels).
3861 getSize: function () {
3862 if (!this._size || this._sizeChanged) {
3863 this._size = new Point(
3864 this._container.clientWidth || 0,
3865 this._container.clientHeight || 0);
3866
3867 this._sizeChanged = false;
3868 }
3869 return this._size.clone();
3870 },
3871
3872 // @method getPixelBounds(): Bounds
3873 // Returns the bounds of the current map view in projected pixel
3874 // coordinates (sometimes useful in layer and overlay implementations).
3875 getPixelBounds: function (center, zoom) {
3876 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3877 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3878 },
3879
3880 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3881 // the map pane? "left point of the map layer" can be confusing, specially
3882 // since there can be negative offsets.
3883 // @method getPixelOrigin(): Point
3884 // Returns the projected pixel coordinates of the top left point of
3885 // the map layer (useful in custom layer and overlay implementations).
3886 getPixelOrigin: function () {
3887 this._checkIfLoaded();
3888 return this._pixelOrigin;
3889 },
3890
3891 // @method getPixelWorldBounds(zoom?: Number): Bounds
3892 // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3893 // If `zoom` is omitted, the map's current zoom level is used.
3894 getPixelWorldBounds: function (zoom) {
3895 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3896 },
3897
3898 // @section Other Methods
3899
3900 // @method getPane(pane: String|HTMLElement): HTMLElement
3901 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3902 getPane: function (pane) {
3903 return typeof pane === 'string' ? this._panes[pane] : pane;
3904 },
3905
3906 // @method getPanes(): Object
3907 // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3908 // the panes as values.
3909 getPanes: function () {
3910 return this._panes;
3911 },
3912
3913 // @method getContainer: HTMLElement
3914 // Returns the HTML element that contains the map.
3915 getContainer: function () {
3916 return this._container;
3917 },
3918
3919
3920 // @section Conversion Methods
3921
3922 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3923 // Returns the scale factor to be applied to a map transition from zoom level
3924 // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3925 getZoomScale: function (toZoom, fromZoom) {
3926 // TODO replace with universal implementation after refactoring projections
3927 var crs = this.options.crs;
3928 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3929 return crs.scale(toZoom) / crs.scale(fromZoom);
3930 },
3931
3932 // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3933 // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3934 // level and everything is scaled by a factor of `scale`. Inverse of
3935 // [`getZoomScale`](#map-getZoomScale).
3936 getScaleZoom: function (scale, fromZoom) {
3937 var crs = this.options.crs;
3938 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3939 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3940 return isNaN(zoom) ? Infinity : zoom;
3941 },
3942
3943 // @method project(latlng: LatLng, zoom: Number): Point
3944 // Projects a geographical coordinate `LatLng` according to the projection
3945 // of the map's CRS, then scales it according to `zoom` and the CRS's
3946 // `Transformation`. The result is pixel coordinate relative to
3947 // the CRS origin.
3948 project: function (latlng, zoom) {
3949 zoom = zoom === undefined ? this._zoom : zoom;
3950 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
3951 },
3952
3953 // @method unproject(point: Point, zoom: Number): LatLng
3954 // Inverse of [`project`](#map-project).
3955 unproject: function (point, zoom) {
3956 zoom = zoom === undefined ? this._zoom : zoom;
3957 return this.options.crs.pointToLatLng(toPoint(point), zoom);
3958 },
3959
3960 // @method layerPointToLatLng(point: Point): LatLng
3961 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3962 // returns the corresponding geographical coordinate (for the current zoom level).
3963 layerPointToLatLng: function (point) {
3964 var projectedPoint = toPoint(point).add(this.getPixelOrigin());
3965 return this.unproject(projectedPoint);
3966 },
3967
3968 // @method latLngToLayerPoint(latlng: LatLng): Point
3969 // Given a geographical coordinate, returns the corresponding pixel coordinate
3970 // relative to the [origin pixel](#map-getpixelorigin).
3971 latLngToLayerPoint: function (latlng) {
3972 var projectedPoint = this.project(toLatLng(latlng))._round();
3973 return projectedPoint._subtract(this.getPixelOrigin());
3974 },
3975
3976 // @method wrapLatLng(latlng: LatLng): LatLng
3977 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
3978 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
3979 // CRS's bounds.
3980 // By default this means longitude is wrapped around the dateline so its
3981 // value is between -180 and +180 degrees.
3982 wrapLatLng: function (latlng) {
3983 return this.options.crs.wrapLatLng(toLatLng(latlng));
3984 },
3985
3986 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
3987 // Returns a `LatLngBounds` with the same size as the given one, ensuring that
3988 // its center is within the CRS's bounds.
3989 // By default this means the center longitude is wrapped around the dateline so its
3990 // value is between -180 and +180 degrees, and the majority of the bounds
3991 // overlaps the CRS's bounds.
3992 wrapLatLngBounds: function (latlng) {
3993 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
3994 },
3995
3996 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
3997 // Returns the distance between two geographical coordinates according to
3998 // the map's CRS. By default this measures distance in meters.
3999 distance: function (latlng1, latlng2) {
4000 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
4001 },
4002
4003 // @method containerPointToLayerPoint(point: Point): Point
4004 // Given a pixel coordinate relative to the map container, returns the corresponding
4005 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
4006 containerPointToLayerPoint: function (point) { // (Point)
4007 return toPoint(point).subtract(this._getMapPanePos());
4008 },
4009
4010 // @method layerPointToContainerPoint(point: Point): Point
4011 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
4012 // returns the corresponding pixel coordinate relative to the map container.
4013 layerPointToContainerPoint: function (point) { // (Point)
4014 return toPoint(point).add(this._getMapPanePos());
4015 },
4016
4017 // @method containerPointToLatLng(point: Point): LatLng
4018 // Given a pixel coordinate relative to the map container, returns
4019 // the corresponding geographical coordinate (for the current zoom level).
4020 containerPointToLatLng: function (point) {
4021 var layerPoint = this.containerPointToLayerPoint(toPoint(point));
4022 return this.layerPointToLatLng(layerPoint);
4023 },
4024
4025 // @method latLngToContainerPoint(latlng: LatLng): Point
4026 // Given a geographical coordinate, returns the corresponding pixel coordinate
4027 // relative to the map container.
4028 latLngToContainerPoint: function (latlng) {
4029 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
4030 },
4031
4032 // @method mouseEventToContainerPoint(ev: MouseEvent): Point
4033 // Given a MouseEvent object, returns the pixel coordinate relative to the
4034 // map container where the event took place.
4035 mouseEventToContainerPoint: function (e) {
4036 return getMousePosition(e, this._container);
4037 },
4038
4039 // @method mouseEventToLayerPoint(ev: MouseEvent): Point
4040 // Given a MouseEvent object, returns the pixel coordinate relative to
4041 // the [origin pixel](#map-getpixelorigin) where the event took place.
4042 mouseEventToLayerPoint: function (e) {
4043 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
4044 },
4045
4046 // @method mouseEventToLatLng(ev: MouseEvent): LatLng
4047 // Given a MouseEvent object, returns geographical coordinate where the
4048 // event took place.
4049 mouseEventToLatLng: function (e) { // (MouseEvent)
4050 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
4051 },
4052
4053
4054 // map initialization methods
4055
4056 _initContainer: function (id) {
4057 var container = this._container = get(id);
4058
4059 if (!container) {
4060 throw new Error('Map container not found.');
4061 } else if (container._leaflet_id) {
4062 throw new Error('Map container is already initialized.');
4063 }
4064
4065 on(container, 'scroll', this._onScroll, this);
4066 this._containerId = stamp(container);
4067 },
4068
4069 _initLayout: function () {
4070 var container = this._container;
4071
4072 this._fadeAnimated = this.options.fadeAnimation && any3d;
4073
4074 addClass(container, 'leaflet-container' +
4075 (touch ? ' leaflet-touch' : '') +
4076 (retina ? ' leaflet-retina' : '') +
4077 (ielt9 ? ' leaflet-oldie' : '') +
4078 (safari ? ' leaflet-safari' : '') +
4079 (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
4080
4081 var position = getStyle(container, 'position');
4082
4083 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
4084 container.style.position = 'relative';
4085 }
4086
4087 this._initPanes();
4088
4089 if (this._initControlPos) {
4090 this._initControlPos();
4091 }
4092 },
4093
4094 _initPanes: function () {
4095 var panes = this._panes = {};
4096 this._paneRenderers = {};
4097
4098 // @section
4099 //
4100 // Panes are DOM elements used to control the ordering of layers on the map. You
4101 // can access panes with [`map.getPane`](#map-getpane) or
4102 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
4103 // [`map.createPane`](#map-createpane) method.
4104 //
4105 // Every map has the following default panes that differ only in zIndex.
4106 //
4107 // @pane mapPane: HTMLElement = 'auto'
4108 // Pane that contains all other map panes
4109
4110 this._mapPane = this.createPane('mapPane', this._container);
4111 setPosition(this._mapPane, new Point(0, 0));
4112
4113 // @pane tilePane: HTMLElement = 200
4114 // Pane for `GridLayer`s and `TileLayer`s
4115 this.createPane('tilePane');
4116 // @pane overlayPane: HTMLElement = 400
4117 // Pane for overlay shadows (e.g. `Marker` shadows)
4118 this.createPane('shadowPane');
4119 // @pane shadowPane: HTMLElement = 500
4120 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
4121 this.createPane('overlayPane');
4122 // @pane markerPane: HTMLElement = 600
4123 // Pane for `Icon`s of `Marker`s
4124 this.createPane('markerPane');
4125 // @pane tooltipPane: HTMLElement = 650
4126 // Pane for `Tooltip`s.
4127 this.createPane('tooltipPane');
4128 // @pane popupPane: HTMLElement = 700
4129 // Pane for `Popup`s.
4130 this.createPane('popupPane');
4131
4132 if (!this.options.markerZoomAnimation) {
4133 addClass(panes.markerPane, 'leaflet-zoom-hide');
4134 addClass(panes.shadowPane, 'leaflet-zoom-hide');
4135 }
4136 },
4137
4138
4139 // private methods that modify map state
4140
4141 // @section Map state change events
4142 _resetView: function (center, zoom) {
4143 setPosition(this._mapPane, new Point(0, 0));
4144
4145 var loading = !this._loaded;
4146 this._loaded = true;
4147 zoom = this._limitZoom(zoom);
4148
4149 this.fire('viewprereset');
4150
4151 var zoomChanged = this._zoom !== zoom;
4152 this
4153 ._moveStart(zoomChanged, false)
4154 ._move(center, zoom)
4155 ._moveEnd(zoomChanged);
4156
4157 // @event viewreset: Event
4158 // Fired when the map needs to redraw its content (this usually happens
4159 // on map zoom or load). Very useful for creating custom overlays.
4160 this.fire('viewreset');
4161
4162 // @event load: Event
4163 // Fired when the map is initialized (when its center and zoom are set
4164 // for the first time).
4165 if (loading) {
4166 this.fire('load');
4167 }
4168 },
4169
4170 _moveStart: function (zoomChanged, noMoveStart) {
4171 // @event zoomstart: Event
4172 // Fired when the map zoom is about to change (e.g. before zoom animation).
4173 // @event movestart: Event
4174 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
4175 if (zoomChanged) {
4176 this.fire('zoomstart');
4177 }
4178 if (!noMoveStart) {
4179 this.fire('movestart');
4180 }
4181 return this;
4182 },
4183
4184 _move: function (center, zoom, data) {
4185 if (zoom === undefined) {
4186 zoom = this._zoom;
4187 }
4188 var zoomChanged = this._zoom !== zoom;
4189
4190 this._zoom = zoom;
4191 this._lastCenter = center;
4192 this._pixelOrigin = this._getNewPixelOrigin(center);
4193
4194 // @event zoom: Event
4195 // Fired repeatedly during any change in zoom level, including zoom
4196 // and fly animations.
4197 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530
4198 this.fire('zoom', data);
4199 }
4200
4201 // @event move: Event
4202 // Fired repeatedly during any movement of the map, including pan and
4203 // fly animations.
4204 return this.fire('move', data);
4205 },
4206
4207 _moveEnd: function (zoomChanged) {
4208 // @event zoomend: Event
4209 // Fired when the map has changed, after any animations.
4210 if (zoomChanged) {
4211 this.fire('zoomend');
4212 }
4213
4214 // @event moveend: Event
4215 // Fired when the center of the map stops changing (e.g. user stopped
4216 // dragging the map).
4217 return this.fire('moveend');
4218 },
4219
4220 _stop: function () {
4221 cancelAnimFrame(this._flyToFrame);
4222 if (this._panAnim) {
4223 this._panAnim.stop();
4224 }
4225 return this;
4226 },
4227
4228 _rawPanBy: function (offset) {
4229 setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
4230 },
4231
4232 _getZoomSpan: function () {
4233 return this.getMaxZoom() - this.getMinZoom();
4234 },
4235
4236 _panInsideMaxBounds: function () {
4237 if (!this._enforcingBounds) {
4238 this.panInsideBounds(this.options.maxBounds);
4239 }
4240 },
4241
4242 _checkIfLoaded: function () {
4243 if (!this._loaded) {
4244 throw new Error('Set map center and zoom first.');
4245 }
4246 },
4247
4248 // DOM event handling
4249
4250 // @section Interaction events
4251 _initEvents: function (remove$$1) {
4252 this._targets = {};
4253 this._targets[stamp(this._container)] = this;
4254
4255 var onOff = remove$$1 ? off : on;
4256
4257 // @event click: MouseEvent
4258 // Fired when the user clicks (or taps) the map.
4259 // @event dblclick: MouseEvent
4260 // Fired when the user double-clicks (or double-taps) the map.
4261 // @event mousedown: MouseEvent
4262 // Fired when the user pushes the mouse button on the map.
4263 // @event mouseup: MouseEvent
4264 // Fired when the user releases the mouse button on the map.
4265 // @event mouseover: MouseEvent
4266 // Fired when the mouse enters the map.
4267 // @event mouseout: MouseEvent
4268 // Fired when the mouse leaves the map.
4269 // @event mousemove: MouseEvent
4270 // Fired while the mouse moves over the map.
4271 // @event contextmenu: MouseEvent
4272 // Fired when the user pushes the right mouse button on the map, prevents
4273 // default browser context menu from showing if there are listeners on
4274 // this event. Also fired on mobile when the user holds a single touch
4275 // for a second (also called long press).
4276 // @event keypress: KeyboardEvent
4277 // Fired when the user presses a key from the keyboard that produces a character value while the map is focused.
4278 // @event keydown: KeyboardEvent
4279 // Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event,
4280 // the `keydown` event is fired for keys that produce a character value and for keys
4281 // that do not produce a character value.
4282 // @event keyup: KeyboardEvent
4283 // Fired when the user releases a key from the keyboard while the map is focused.
4284 onOff(this._container, 'click dblclick mousedown mouseup ' +
4285 'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this);
4286
4287 if (this.options.trackResize) {
4288 onOff(window, 'resize', this._onResize, this);
4289 }
4290
4291 if (any3d && this.options.transform3DLimit) {
4292 (remove$$1 ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
4293 }
4294 },
4295
4296 _onResize: function () {
4297 cancelAnimFrame(this._resizeRequest);
4298 this._resizeRequest = requestAnimFrame(
4299 function () { this.invalidateSize({debounceMoveend: true}); }, this);
4300 },
4301
4302 _onScroll: function () {
4303 this._container.scrollTop = 0;
4304 this._container.scrollLeft = 0;
4305 },
4306
4307 _onMoveEnd: function () {
4308 var pos = this._getMapPanePos();
4309 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
4310 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
4311 // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
4312 this._resetView(this.getCenter(), this.getZoom());
4313 }
4314 },
4315
4316 _findEventTargets: function (e, type) {
4317 var targets = [],
4318 target,
4319 isHover = type === 'mouseout' || type === 'mouseover',
4320 src = e.target || e.srcElement,
4321 dragging = false;
4322
4323 while (src) {
4324 target = this._targets[stamp(src)];
4325 if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
4326 // Prevent firing click after you just dragged an object.
4327 dragging = true;
4328 break;
4329 }
4330 if (target && target.listens(type, true)) {
4331 if (isHover && !isExternalTarget(src, e)) { break; }
4332 targets.push(target);
4333 if (isHover) { break; }
4334 }
4335 if (src === this._container) { break; }
4336 src = src.parentNode;
4337 }
4338 if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) {
4339 targets = [this];
4340 }
4341 return targets;
4342 },
4343
4344 _handleDOMEvent: function (e) {
4345 if (!this._loaded || skipped(e)) { return; }
4346
4347 var type = e.type;
4348
4349 if (type === 'mousedown' || type === 'keypress' || type === 'keyup' || type === 'keydown') {
4350 // prevents outline when clicking on keyboard-focusable element
4351 preventOutline(e.target || e.srcElement);
4352 }
4353
4354 this._fireDOMEvent(e, type);
4355 },
4356
4357 _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
4358
4359 _fireDOMEvent: function (e, type, targets) {
4360
4361 if (e.type === 'click') {
4362 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
4363 // @event preclick: MouseEvent
4364 // Fired before mouse click on the map (sometimes useful when you
4365 // want something to happen on click before any existing click
4366 // handlers start running).
4367 var synth = extend({}, e);
4368 synth.type = 'preclick';
4369 this._fireDOMEvent(synth, synth.type, targets);
4370 }
4371
4372 if (e._stopped) { return; }
4373
4374 // Find the layer the event is propagating from and its parents.
4375 targets = (targets || []).concat(this._findEventTargets(e, type));
4376
4377 if (!targets.length) { return; }
4378
4379 var target = targets[0];
4380 if (type === 'contextmenu' && target.listens(type, true)) {
4381 preventDefault(e);
4382 }
4383
4384 var data = {
4385 originalEvent: e
4386 };
4387
4388 if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') {
4389 var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
4390 data.containerPoint = isMarker ?
4391 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
4392 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
4393 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
4394 }
4395
4396 for (var i = 0; i < targets.length; i++) {
4397 targets[i].fire(type, data, true);
4398 if (data.originalEvent._stopped ||
4399 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
4400 }
4401 },
4402
4403 _draggableMoved: function (obj) {
4404 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
4405 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
4406 },
4407
4408 _clearHandlers: function () {
4409 for (var i = 0, len = this._handlers.length; i < len; i++) {
4410 this._handlers[i].disable();
4411 }
4412 },
4413
4414 // @section Other Methods
4415
4416 // @method whenReady(fn: Function, context?: Object): this
4417 // Runs the given function `fn` when the map gets initialized with
4418 // a view (center and zoom) and at least one layer, or immediately
4419 // if it's already initialized, optionally passing a function context.
4420 whenReady: function (callback, context) {
4421 if (this._loaded) {
4422 callback.call(context || this, {target: this});
4423 } else {
4424 this.on('load', callback, context);
4425 }
4426 return this;
4427 },
4428
4429
4430 // private methods for getting map state
4431
4432 _getMapPanePos: function () {
4433 return getPosition(this._mapPane) || new Point(0, 0);
4434 },
4435
4436 _moved: function () {
4437 var pos = this._getMapPanePos();
4438 return pos && !pos.equals([0, 0]);
4439 },
4440
4441 _getTopLeftPoint: function (center, zoom) {
4442 var pixelOrigin = center && zoom !== undefined ?
4443 this._getNewPixelOrigin(center, zoom) :
4444 this.getPixelOrigin();
4445 return pixelOrigin.subtract(this._getMapPanePos());
4446 },
4447
4448 _getNewPixelOrigin: function (center, zoom) {
4449 var viewHalf = this.getSize()._divideBy(2);
4450 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
4451 },
4452
4453 _latLngToNewLayerPoint: function (latlng, zoom, center) {
4454 var topLeft = this._getNewPixelOrigin(center, zoom);
4455 return this.project(latlng, zoom)._subtract(topLeft);
4456 },
4457
4458 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
4459 var topLeft = this._getNewPixelOrigin(center, zoom);
4460 return toBounds([
4461 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
4462 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
4463 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
4464 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
4465 ]);
4466 },
4467
4468 // layer point of the current center
4469 _getCenterLayerPoint: function () {
4470 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
4471 },
4472
4473 // offset of the specified place to the current center in pixels
4474 _getCenterOffset: function (latlng) {
4475 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
4476 },
4477
4478 // adjust center for view to get inside bounds
4479 _limitCenter: function (center, zoom, bounds) {
4480
4481 if (!bounds) { return center; }
4482
4483 var centerPoint = this.project(center, zoom),
4484 viewHalf = this.getSize().divideBy(2),
4485 viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
4486 offset = this._getBoundsOffset(viewBounds, bounds, zoom);
4487
4488 // If offset is less than a pixel, ignore.
4489 // This prevents unstable projections from getting into
4490 // an infinite loop of tiny offsets.
4491 if (offset.round().equals([0, 0])) {
4492 return center;
4493 }
4494
4495 return this.unproject(centerPoint.add(offset), zoom);
4496 },
4497
4498 // adjust offset for view to get inside bounds
4499 _limitOffset: function (offset, bounds) {
4500 if (!bounds) { return offset; }
4501
4502 var viewBounds = this.getPixelBounds(),
4503 newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
4504
4505 return offset.add(this._getBoundsOffset(newBounds, bounds));
4506 },
4507
4508 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
4509 _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
4510 var projectedMaxBounds = toBounds(
4511 this.project(maxBounds.getNorthEast(), zoom),
4512 this.project(maxBounds.getSouthWest(), zoom)
4513 ),
4514 minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
4515 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
4516
4517 dx = this._rebound(minOffset.x, -maxOffset.x),
4518 dy = this._rebound(minOffset.y, -maxOffset.y);
4519
4520 return new Point(dx, dy);
4521 },
4522
4523 _rebound: function (left, right) {
4524 return left + right > 0 ?
4525 Math.round(left - right) / 2 :
4526 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
4527 },
4528
4529 _limitZoom: function (zoom) {
4530 var min = this.getMinZoom(),
4531 max = this.getMaxZoom(),
4532 snap = any3d ? this.options.zoomSnap : 1;
4533 if (snap) {
4534 zoom = Math.round(zoom / snap) * snap;
4535 }
4536 return Math.max(min, Math.min(max, zoom));
4537 },
4538
4539 _onPanTransitionStep: function () {
4540 this.fire('move');
4541 },
4542
4543 _onPanTransitionEnd: function () {
4544 removeClass(this._mapPane, 'leaflet-pan-anim');
4545 this.fire('moveend');
4546 },
4547
4548 _tryAnimatedPan: function (center, options) {
4549 // difference between the new and current centers in pixels
4550 var offset = this._getCenterOffset(center)._trunc();
4551
4552 // don't animate too far unless animate: true specified in options
4553 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
4554
4555 this.panBy(offset, options);
4556
4557 return true;
4558 },
4559
4560 _createAnimProxy: function () {
4561
4562 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
4563 this._panes.mapPane.appendChild(proxy);
4564
4565 this.on('zoomanim', function (e) {
4566 var prop = TRANSFORM,
4567 transform = this._proxy.style[prop];
4568
4569 setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
4570
4571 // workaround for case when transform is the same and so transitionend event is not fired
4572 if (transform === this._proxy.style[prop] && this._animatingZoom) {
4573 this._onZoomTransitionEnd();
4574 }
4575 }, this);
4576
4577 this.on('load moveend', this._animMoveEnd, this);
4578
4579 this._on('unload', this._destroyAnimProxy, this);
4580 },
4581
4582 _destroyAnimProxy: function () {
4583 remove(this._proxy);
4584 this.off('load moveend', this._animMoveEnd, this);
4585 delete this._proxy;
4586 },
4587
4588 _animMoveEnd: function () {
4589 var c = this.getCenter(),
4590 z = this.getZoom();
4591 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
4592 },
4593
4594 _catchTransitionEnd: function (e) {
4595 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
4596 this._onZoomTransitionEnd();
4597 }
4598 },
4599
4600 _nothingToAnimate: function () {
4601 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
4602 },
4603
4604 _tryAnimatedZoom: function (center, zoom, options) {
4605
4606 if (this._animatingZoom) { return true; }
4607
4608 options = options || {};
4609
4610 // don't animate if disabled, not supported or zoom difference is too large
4611 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
4612 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
4613
4614 // offset is the pixel coords of the zoom origin relative to the current center
4615 var scale = this.getZoomScale(zoom),
4616 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
4617
4618 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
4619 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
4620
4621 requestAnimFrame(function () {
4622 this
4623 ._moveStart(true, false)
4624 ._animateZoom(center, zoom, true);
4625 }, this);
4626
4627 return true;
4628 },
4629
4630 _animateZoom: function (center, zoom, startAnim, noUpdate) {
4631 if (!this._mapPane) { return; }
4632
4633 if (startAnim) {
4634 this._animatingZoom = true;
4635
4636 // remember what center/zoom to set after animation
4637 this._animateToCenter = center;
4638 this._animateToZoom = zoom;
4639
4640 addClass(this._mapPane, 'leaflet-zoom-anim');
4641 }
4642
4643 // @section Other Events
4644 // @event zoomanim: ZoomAnimEvent
4645 // Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom.
4646 this.fire('zoomanim', {
4647 center: center,
4648 zoom: zoom,
4649 noUpdate: noUpdate
4650 });
4651
4652 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
4653 setTimeout(bind(this._onZoomTransitionEnd, this), 250);
4654 },
4655
4656 _onZoomTransitionEnd: function () {
4657 if (!this._animatingZoom) { return; }
4658
4659 if (this._mapPane) {
4660 removeClass(this._mapPane, 'leaflet-zoom-anim');
4661 }
4662
4663 this._animatingZoom = false;
4664
4665 this._move(this._animateToCenter, this._animateToZoom);
4666
4667 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
4668 requestAnimFrame(function () {
4669 this._moveEnd(true);
4670 }, this);
4671 }
4672});
4673
4674// @section
4675
4676// @factory L.map(id: String, options?: Map options)
4677// Instantiates a map object given the DOM ID of a `<div>` element
4678// and optionally an object literal with `Map options`.
4679//
4680// @alternative
4681// @factory L.map(el: HTMLElement, options?: Map options)
4682// Instantiates a map object given an instance of a `<div>` HTML element
4683// and optionally an object literal with `Map options`.
4684function createMap(id, options) {
4685 return new Map(id, options);
4686}
4687
4688/*
4689 * @class Control
4690 * @aka L.Control
4691 * @inherits Class
4692 *
4693 * L.Control is a base class for implementing map controls. Handles positioning.
4694 * All other controls extend from this class.
4695 */
4696
4697var Control = Class.extend({
4698 // @section
4699 // @aka Control options
4700 options: {
4701 // @option position: String = 'topright'
4702 // The position of the control (one of the map corners). Possible values are `'topleft'`,
4703 // `'topright'`, `'bottomleft'` or `'bottomright'`
4704 position: 'topright'
4705 },
4706
4707 initialize: function (options) {
4708 setOptions(this, options);
4709 },
4710
4711 /* @section
4712 * Classes extending L.Control will inherit the following methods:
4713 *
4714 * @method getPosition: string
4715 * Returns the position of the control.
4716 */
4717 getPosition: function () {
4718 return this.options.position;
4719 },
4720
4721 // @method setPosition(position: string): this
4722 // Sets the position of the control.
4723 setPosition: function (position) {
4724 var map = this._map;
4725
4726 if (map) {
4727 map.removeControl(this);
4728 }
4729
4730 this.options.position = position;
4731
4732 if (map) {
4733 map.addControl(this);
4734 }
4735
4736 return this;
4737 },
4738
4739 // @method getContainer: HTMLElement
4740 // Returns the HTMLElement that contains the control.
4741 getContainer: function () {
4742 return this._container;
4743 },
4744
4745 // @method addTo(map: Map): this
4746 // Adds the control to the given map.
4747 addTo: function (map) {
4748 this.remove();
4749 this._map = map;
4750
4751 var container = this._container = this.onAdd(map),
4752 pos = this.getPosition(),
4753 corner = map._controlCorners[pos];
4754
4755 addClass(container, 'leaflet-control');
4756
4757 if (pos.indexOf('bottom') !== -1) {
4758 corner.insertBefore(container, corner.firstChild);
4759 } else {
4760 corner.appendChild(container);
4761 }
4762
4763 this._map.on('unload', this.remove, this);
4764
4765 return this;
4766 },
4767
4768 // @method remove: this
4769 // Removes the control from the map it is currently active on.
4770 remove: function () {
4771 if (!this._map) {
4772 return this;
4773 }
4774
4775 remove(this._container);
4776
4777 if (this.onRemove) {
4778 this.onRemove(this._map);
4779 }
4780
4781 this._map.off('unload', this.remove, this);
4782 this._map = null;
4783
4784 return this;
4785 },
4786
4787 _refocusOnMap: function (e) {
4788 // if map exists and event is not a keyboard event
4789 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
4790 this._map.getContainer().focus();
4791 }
4792 }
4793});
4794
4795var control = function (options) {
4796 return new Control(options);
4797};
4798
4799/* @section Extension methods
4800 * @uninheritable
4801 *
4802 * Every control should extend from `L.Control` and (re-)implement the following methods.
4803 *
4804 * @method onAdd(map: Map): HTMLElement
4805 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
4806 *
4807 * @method onRemove(map: Map)
4808 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
4809 */
4810
4811/* @namespace Map
4812 * @section Methods for Layers and Controls
4813 */
4814Map.include({
4815 // @method addControl(control: Control): this
4816 // Adds the given control to the map
4817 addControl: function (control) {
4818 control.addTo(this);
4819 return this;
4820 },
4821
4822 // @method removeControl(control: Control): this
4823 // Removes the given control from the map
4824 removeControl: function (control) {
4825 control.remove();
4826 return this;
4827 },
4828
4829 _initControlPos: function () {
4830 var corners = this._controlCorners = {},
4831 l = 'leaflet-',
4832 container = this._controlContainer =
4833 create$1('div', l + 'control-container', this._container);
4834
4835 function createCorner(vSide, hSide) {
4836 var className = l + vSide + ' ' + l + hSide;
4837
4838 corners[vSide + hSide] = create$1('div', className, container);
4839 }
4840
4841 createCorner('top', 'left');
4842 createCorner('top', 'right');
4843 createCorner('bottom', 'left');
4844 createCorner('bottom', 'right');
4845 },
4846
4847 _clearControlPos: function () {
4848 for (var i in this._controlCorners) {
4849 remove(this._controlCorners[i]);
4850 }
4851 remove(this._controlContainer);
4852 delete this._controlCorners;
4853 delete this._controlContainer;
4854 }
4855});
4856
4857/*
4858 * @class Control.Layers
4859 * @aka L.Control.Layers
4860 * @inherits Control
4861 *
4862 * The layers control gives users the ability to switch between different base layers and switch overlays on/off (check out the [detailed example](http://leafletjs.com/examples/layers-control/)). Extends `Control`.
4863 *
4864 * @example
4865 *
4866 * ```js
4867 * var baseLayers = {
4868 * "Mapbox": mapbox,
4869 * "OpenStreetMap": osm
4870 * };
4871 *
4872 * var overlays = {
4873 * "Marker": marker,
4874 * "Roads": roadsLayer
4875 * };
4876 *
4877 * L.control.layers(baseLayers, overlays).addTo(map);
4878 * ```
4879 *
4880 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
4881 *
4882 * ```js
4883 * {
4884 * "<someName1>": layer1,
4885 * "<someName2>": layer2
4886 * }
4887 * ```
4888 *
4889 * The layer names can contain HTML, which allows you to add additional styling to the items:
4890 *
4891 * ```js
4892 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
4893 * ```
4894 */
4895
4896var Layers = Control.extend({
4897 // @section
4898 // @aka Control.Layers options
4899 options: {
4900 // @option collapsed: Boolean = true
4901 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
4902 collapsed: true,
4903 position: 'topright',
4904
4905 // @option autoZIndex: Boolean = true
4906 // 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.
4907 autoZIndex: true,
4908
4909 // @option hideSingleBase: Boolean = false
4910 // If `true`, the base layers in the control will be hidden when there is only one.
4911 hideSingleBase: false,
4912
4913 // @option sortLayers: Boolean = false
4914 // Whether to sort the layers. When `false`, layers will keep the order
4915 // in which they were added to the control.
4916 sortLayers: false,
4917
4918 // @option sortFunction: Function = *
4919 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
4920 // that will be used for sorting the layers, when `sortLayers` is `true`.
4921 // The function receives both the `L.Layer` instances and their names, as in
4922 // `sortFunction(layerA, layerB, nameA, nameB)`.
4923 // By default, it sorts layers alphabetically by their name.
4924 sortFunction: function (layerA, layerB, nameA, nameB) {
4925 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
4926 }
4927 },
4928
4929 initialize: function (baseLayers, overlays, options) {
4930 setOptions(this, options);
4931
4932 this._layerControlInputs = [];
4933 this._layers = [];
4934 this._lastZIndex = 0;
4935 this._handlingClick = false;
4936
4937 for (var i in baseLayers) {
4938 this._addLayer(baseLayers[i], i);
4939 }
4940
4941 for (i in overlays) {
4942 this._addLayer(overlays[i], i, true);
4943 }
4944 },
4945
4946 onAdd: function (map) {
4947 this._initLayout();
4948 this._update();
4949
4950 this._map = map;
4951 map.on('zoomend', this._checkDisabledLayers, this);
4952
4953 for (var i = 0; i < this._layers.length; i++) {
4954 this._layers[i].layer.on('add remove', this._onLayerChange, this);
4955 }
4956
4957 return this._container;
4958 },
4959
4960 addTo: function (map) {
4961 Control.prototype.addTo.call(this, map);
4962 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
4963 return this._expandIfNotCollapsed();
4964 },
4965
4966 onRemove: function () {
4967 this._map.off('zoomend', this._checkDisabledLayers, this);
4968
4969 for (var i = 0; i < this._layers.length; i++) {
4970 this._layers[i].layer.off('add remove', this._onLayerChange, this);
4971 }
4972 },
4973
4974 // @method addBaseLayer(layer: Layer, name: String): this
4975 // Adds a base layer (radio button entry) with the given name to the control.
4976 addBaseLayer: function (layer, name) {
4977 this._addLayer(layer, name);
4978 return (this._map) ? this._update() : this;
4979 },
4980
4981 // @method addOverlay(layer: Layer, name: String): this
4982 // Adds an overlay (checkbox entry) with the given name to the control.
4983 addOverlay: function (layer, name) {
4984 this._addLayer(layer, name, true);
4985 return (this._map) ? this._update() : this;
4986 },
4987
4988 // @method removeLayer(layer: Layer): this
4989 // Remove the given layer from the control.
4990 removeLayer: function (layer) {
4991 layer.off('add remove', this._onLayerChange, this);
4992
4993 var obj = this._getLayer(stamp(layer));
4994 if (obj) {
4995 this._layers.splice(this._layers.indexOf(obj), 1);
4996 }
4997 return (this._map) ? this._update() : this;
4998 },
4999
5000 // @method expand(): this
5001 // Expand the control container if collapsed.
5002 expand: function () {
5003 addClass(this._container, 'leaflet-control-layers-expanded');
5004 this._section.style.height = null;
5005 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
5006 if (acceptableHeight < this._section.clientHeight) {
5007 addClass(this._section, 'leaflet-control-layers-scrollbar');
5008 this._section.style.height = acceptableHeight + 'px';
5009 } else {
5010 removeClass(this._section, 'leaflet-control-layers-scrollbar');
5011 }
5012 this._checkDisabledLayers();
5013 return this;
5014 },
5015
5016 // @method collapse(): this
5017 // Collapse the control container if expanded.
5018 collapse: function () {
5019 removeClass(this._container, 'leaflet-control-layers-expanded');
5020 return this;
5021 },
5022
5023 _initLayout: function () {
5024 var className = 'leaflet-control-layers',
5025 container = this._container = create$1('div', className),
5026 collapsed = this.options.collapsed;
5027
5028 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
5029 container.setAttribute('aria-haspopup', true);
5030
5031 disableClickPropagation(container);
5032 disableScrollPropagation(container);
5033
5034 var section = this._section = create$1('section', className + '-list');
5035
5036 if (collapsed) {
5037 this._map.on('click', this.collapse, this);
5038
5039 if (!android) {
5040 on(container, {
5041 mouseenter: this.expand,
5042 mouseleave: this.collapse
5043 }, this);
5044 }
5045 }
5046
5047 var link = this._layersLink = create$1('a', className + '-toggle', container);
5048 link.href = '#';
5049 link.title = 'Layers';
5050
5051 if (touch) {
5052 on(link, 'click', stop);
5053 on(link, 'click', this.expand, this);
5054 } else {
5055 on(link, 'focus', this.expand, this);
5056 }
5057
5058 if (!collapsed) {
5059 this.expand();
5060 }
5061
5062 this._baseLayersList = create$1('div', className + '-base', section);
5063 this._separator = create$1('div', className + '-separator', section);
5064 this._overlaysList = create$1('div', className + '-overlays', section);
5065
5066 container.appendChild(section);
5067 },
5068
5069 _getLayer: function (id) {
5070 for (var i = 0; i < this._layers.length; i++) {
5071
5072 if (this._layers[i] && stamp(this._layers[i].layer) === id) {
5073 return this._layers[i];
5074 }
5075 }
5076 },
5077
5078 _addLayer: function (layer, name, overlay) {
5079 if (this._map) {
5080 layer.on('add remove', this._onLayerChange, this);
5081 }
5082
5083 this._layers.push({
5084 layer: layer,
5085 name: name,
5086 overlay: overlay
5087 });
5088
5089 if (this.options.sortLayers) {
5090 this._layers.sort(bind(function (a, b) {
5091 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
5092 }, this));
5093 }
5094
5095 if (this.options.autoZIndex && layer.setZIndex) {
5096 this._lastZIndex++;
5097 layer.setZIndex(this._lastZIndex);
5098 }
5099
5100 this._expandIfNotCollapsed();
5101 },
5102
5103 _update: function () {
5104 if (!this._container) { return this; }
5105
5106 empty(this._baseLayersList);
5107 empty(this._overlaysList);
5108
5109 this._layerControlInputs = [];
5110 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
5111
5112 for (i = 0; i < this._layers.length; i++) {
5113 obj = this._layers[i];
5114 this._addItem(obj);
5115 overlaysPresent = overlaysPresent || obj.overlay;
5116 baseLayersPresent = baseLayersPresent || !obj.overlay;
5117 baseLayersCount += !obj.overlay ? 1 : 0;
5118 }
5119
5120 // Hide base layers section if there's only one layer.
5121 if (this.options.hideSingleBase) {
5122 baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
5123 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
5124 }
5125
5126 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
5127
5128 return this;
5129 },
5130
5131 _onLayerChange: function (e) {
5132 if (!this._handlingClick) {
5133 this._update();
5134 }
5135
5136 var obj = this._getLayer(stamp(e.target));
5137
5138 // @namespace Map
5139 // @section Layer events
5140 // @event baselayerchange: LayersControlEvent
5141 // Fired when the base layer is changed through the [layers control](#control-layers).
5142 // @event overlayadd: LayersControlEvent
5143 // Fired when an overlay is selected through the [layers control](#control-layers).
5144 // @event overlayremove: LayersControlEvent
5145 // Fired when an overlay is deselected through the [layers control](#control-layers).
5146 // @namespace Control.Layers
5147 var type = obj.overlay ?
5148 (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
5149 (e.type === 'add' ? 'baselayerchange' : null);
5150
5151 if (type) {
5152 this._map.fire(type, obj);
5153 }
5154 },
5155
5156 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
5157 _createRadioElement: function (name, checked) {
5158
5159 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
5160 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
5161
5162 var radioFragment = document.createElement('div');
5163 radioFragment.innerHTML = radioHtml;
5164
5165 return radioFragment.firstChild;
5166 },
5167
5168 _addItem: function (obj) {
5169 var label = document.createElement('label'),
5170 checked = this._map.hasLayer(obj.layer),
5171 input;
5172
5173 if (obj.overlay) {
5174 input = document.createElement('input');
5175 input.type = 'checkbox';
5176 input.className = 'leaflet-control-layers-selector';
5177 input.defaultChecked = checked;
5178 } else {
5179 input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked);
5180 }
5181
5182 this._layerControlInputs.push(input);
5183 input.layerId = stamp(obj.layer);
5184
5185 on(input, 'click', this._onInputClick, this);
5186
5187 var name = document.createElement('span');
5188 name.innerHTML = ' ' + obj.name;
5189
5190 // Helps from preventing layer control flicker when checkboxes are disabled
5191 // https://github.com/Leaflet/Leaflet/issues/2771
5192 var holder = document.createElement('div');
5193
5194 label.appendChild(holder);
5195 holder.appendChild(input);
5196 holder.appendChild(name);
5197
5198 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
5199 container.appendChild(label);
5200
5201 this._checkDisabledLayers();
5202 return label;
5203 },
5204
5205 _onInputClick: function () {
5206 var inputs = this._layerControlInputs,
5207 input, layer;
5208 var addedLayers = [],
5209 removedLayers = [];
5210
5211 this._handlingClick = true;
5212
5213 for (var i = inputs.length - 1; i >= 0; i--) {
5214 input = inputs[i];
5215 layer = this._getLayer(input.layerId).layer;
5216
5217 if (input.checked) {
5218 addedLayers.push(layer);
5219 } else if (!input.checked) {
5220 removedLayers.push(layer);
5221 }
5222 }
5223
5224 // Bugfix issue 2318: Should remove all old layers before readding new ones
5225 for (i = 0; i < removedLayers.length; i++) {
5226 if (this._map.hasLayer(removedLayers[i])) {
5227 this._map.removeLayer(removedLayers[i]);
5228 }
5229 }
5230 for (i = 0; i < addedLayers.length; i++) {
5231 if (!this._map.hasLayer(addedLayers[i])) {
5232 this._map.addLayer(addedLayers[i]);
5233 }
5234 }
5235
5236 this._handlingClick = false;
5237
5238 this._refocusOnMap();
5239 },
5240
5241 _checkDisabledLayers: function () {
5242 var inputs = this._layerControlInputs,
5243 input,
5244 layer,
5245 zoom = this._map.getZoom();
5246
5247 for (var i = inputs.length - 1; i >= 0; i--) {
5248 input = inputs[i];
5249 layer = this._getLayer(input.layerId).layer;
5250 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
5251 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
5252
5253 }
5254 },
5255
5256 _expandIfNotCollapsed: function () {
5257 if (this._map && !this.options.collapsed) {
5258 this.expand();
5259 }
5260 return this;
5261 },
5262
5263 _expand: function () {
5264 // Backward compatibility, remove me in 1.1.
5265 return this.expand();
5266 },
5267
5268 _collapse: function () {
5269 // Backward compatibility, remove me in 1.1.
5270 return this.collapse();
5271 }
5272
5273});
5274
5275
5276// @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
5277// 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.
5278var layers = function (baseLayers, overlays, options) {
5279 return new Layers(baseLayers, overlays, options);
5280};
5281
5282/*
5283 * @class Control.Zoom
5284 * @aka L.Control.Zoom
5285 * @inherits Control
5286 *
5287 * 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`.
5288 */
5289
5290var Zoom = Control.extend({
5291 // @section
5292 // @aka Control.Zoom options
5293 options: {
5294 position: 'topleft',
5295
5296 // @option zoomInText: String = '+'
5297 // The text set on the 'zoom in' button.
5298 zoomInText: '+',
5299
5300 // @option zoomInTitle: String = 'Zoom in'
5301 // The title set on the 'zoom in' button.
5302 zoomInTitle: 'Zoom in',
5303
5304 // @option zoomOutText: String = '&#x2212;'
5305 // The text set on the 'zoom out' button.
5306 zoomOutText: '&#x2212;',
5307
5308 // @option zoomOutTitle: String = 'Zoom out'
5309 // The title set on the 'zoom out' button.
5310 zoomOutTitle: 'Zoom out'
5311 },
5312
5313 onAdd: function (map) {
5314 var zoomName = 'leaflet-control-zoom',
5315 container = create$1('div', zoomName + ' leaflet-bar'),
5316 options = this.options;
5317
5318 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle,
5319 zoomName + '-in', container, this._zoomIn);
5320 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
5321 zoomName + '-out', container, this._zoomOut);
5322
5323 this._updateDisabled();
5324 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
5325
5326 return container;
5327 },
5328
5329 onRemove: function (map) {
5330 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
5331 },
5332
5333 disable: function () {
5334 this._disabled = true;
5335 this._updateDisabled();
5336 return this;
5337 },
5338
5339 enable: function () {
5340 this._disabled = false;
5341 this._updateDisabled();
5342 return this;
5343 },
5344
5345 _zoomIn: function (e) {
5346 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
5347 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5348 }
5349 },
5350
5351 _zoomOut: function (e) {
5352 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
5353 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5354 }
5355 },
5356
5357 _createButton: function (html, title, className, container, fn) {
5358 var link = create$1('a', className, container);
5359 link.innerHTML = html;
5360 link.href = '#';
5361 link.title = title;
5362
5363 /*
5364 * Will force screen readers like VoiceOver to read this as "Zoom in - button"
5365 */
5366 link.setAttribute('role', 'button');
5367 link.setAttribute('aria-label', title);
5368
5369 disableClickPropagation(link);
5370 on(link, 'click', stop);
5371 on(link, 'click', fn, this);
5372 on(link, 'click', this._refocusOnMap, this);
5373
5374 return link;
5375 },
5376
5377 _updateDisabled: function () {
5378 var map = this._map,
5379 className = 'leaflet-disabled';
5380
5381 removeClass(this._zoomInButton, className);
5382 removeClass(this._zoomOutButton, className);
5383
5384 if (this._disabled || map._zoom === map.getMinZoom()) {
5385 addClass(this._zoomOutButton, className);
5386 }
5387 if (this._disabled || map._zoom === map.getMaxZoom()) {
5388 addClass(this._zoomInButton, className);
5389 }
5390 }
5391});
5392
5393// @namespace Map
5394// @section Control options
5395// @option zoomControl: Boolean = true
5396// Whether a [zoom control](#control-zoom) is added to the map by default.
5397Map.mergeOptions({
5398 zoomControl: true
5399});
5400
5401Map.addInitHook(function () {
5402 if (this.options.zoomControl) {
5403 // @section Controls
5404 // @property zoomControl: Control.Zoom
5405 // The default zoom control (only available if the
5406 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
5407 this.zoomControl = new Zoom();
5408 this.addControl(this.zoomControl);
5409 }
5410});
5411
5412// @namespace Control.Zoom
5413// @factory L.control.zoom(options: Control.Zoom options)
5414// Creates a zoom control
5415var zoom = function (options) {
5416 return new Zoom(options);
5417};
5418
5419/*
5420 * @class Control.Scale
5421 * @aka L.Control.Scale
5422 * @inherits Control
5423 *
5424 * 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`.
5425 *
5426 * @example
5427 *
5428 * ```js
5429 * L.control.scale().addTo(map);
5430 * ```
5431 */
5432
5433var Scale = Control.extend({
5434 // @section
5435 // @aka Control.Scale options
5436 options: {
5437 position: 'bottomleft',
5438
5439 // @option maxWidth: Number = 100
5440 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
5441 maxWidth: 100,
5442
5443 // @option metric: Boolean = True
5444 // Whether to show the metric scale line (m/km).
5445 metric: true,
5446
5447 // @option imperial: Boolean = True
5448 // Whether to show the imperial scale line (mi/ft).
5449 imperial: true
5450
5451 // @option updateWhenIdle: Boolean = false
5452 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
5453 },
5454
5455 onAdd: function (map) {
5456 var className = 'leaflet-control-scale',
5457 container = create$1('div', className),
5458 options = this.options;
5459
5460 this._addScales(options, className + '-line', container);
5461
5462 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5463 map.whenReady(this._update, this);
5464
5465 return container;
5466 },
5467
5468 onRemove: function (map) {
5469 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5470 },
5471
5472 _addScales: function (options, className, container) {
5473 if (options.metric) {
5474 this._mScale = create$1('div', className, container);
5475 }
5476 if (options.imperial) {
5477 this._iScale = create$1('div', className, container);
5478 }
5479 },
5480
5481 _update: function () {
5482 var map = this._map,
5483 y = map.getSize().y / 2;
5484
5485 var maxMeters = map.distance(
5486 map.containerPointToLatLng([0, y]),
5487 map.containerPointToLatLng([this.options.maxWidth, y]));
5488
5489 this._updateScales(maxMeters);
5490 },
5491
5492 _updateScales: function (maxMeters) {
5493 if (this.options.metric && maxMeters) {
5494 this._updateMetric(maxMeters);
5495 }
5496 if (this.options.imperial && maxMeters) {
5497 this._updateImperial(maxMeters);
5498 }
5499 },
5500
5501 _updateMetric: function (maxMeters) {
5502 var meters = this._getRoundNum(maxMeters),
5503 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
5504
5505 this._updateScale(this._mScale, label, meters / maxMeters);
5506 },
5507
5508 _updateImperial: function (maxMeters) {
5509 var maxFeet = maxMeters * 3.2808399,
5510 maxMiles, miles, feet;
5511
5512 if (maxFeet > 5280) {
5513 maxMiles = maxFeet / 5280;
5514 miles = this._getRoundNum(maxMiles);
5515 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
5516
5517 } else {
5518 feet = this._getRoundNum(maxFeet);
5519 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
5520 }
5521 },
5522
5523 _updateScale: function (scale, text, ratio) {
5524 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
5525 scale.innerHTML = text;
5526 },
5527
5528 _getRoundNum: function (num) {
5529 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5530 d = num / pow10;
5531
5532 d = d >= 10 ? 10 :
5533 d >= 5 ? 5 :
5534 d >= 3 ? 3 :
5535 d >= 2 ? 2 : 1;
5536
5537 return pow10 * d;
5538 }
5539});
5540
5541
5542// @factory L.control.scale(options?: Control.Scale options)
5543// Creates an scale control with the given options.
5544var scale = function (options) {
5545 return new Scale(options);
5546};
5547
5548/*
5549 * @class Control.Attribution
5550 * @aka L.Control.Attribution
5551 * @inherits Control
5552 *
5553 * 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.
5554 */
5555
5556var Attribution = Control.extend({
5557 // @section
5558 // @aka Control.Attribution options
5559 options: {
5560 position: 'bottomright',
5561
5562 // @option prefix: String = 'Leaflet'
5563 // The HTML text shown before the attributions. Pass `false` to disable.
5564 prefix: '<a href="https://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
5565 },
5566
5567 initialize: function (options) {
5568 setOptions(this, options);
5569
5570 this._attributions = {};
5571 },
5572
5573 onAdd: function (map) {
5574 map.attributionControl = this;
5575 this._container = create$1('div', 'leaflet-control-attribution');
5576 disableClickPropagation(this._container);
5577
5578 // TODO ugly, refactor
5579 for (var i in map._layers) {
5580 if (map._layers[i].getAttribution) {
5581 this.addAttribution(map._layers[i].getAttribution());
5582 }
5583 }
5584
5585 this._update();
5586
5587 return this._container;
5588 },
5589
5590 // @method setPrefix(prefix: String): this
5591 // Sets the text before the attributions.
5592 setPrefix: function (prefix) {
5593 this.options.prefix = prefix;
5594 this._update();
5595 return this;
5596 },
5597
5598 // @method addAttribution(text: String): this
5599 // Adds an attribution text (e.g. `'Vector data &copy; Mapbox'`).
5600 addAttribution: function (text) {
5601 if (!text) { return this; }
5602
5603 if (!this._attributions[text]) {
5604 this._attributions[text] = 0;
5605 }
5606 this._attributions[text]++;
5607
5608 this._update();
5609
5610 return this;
5611 },
5612
5613 // @method removeAttribution(text: String): this
5614 // Removes an attribution text.
5615 removeAttribution: function (text) {
5616 if (!text) { return this; }
5617
5618 if (this._attributions[text]) {
5619 this._attributions[text]--;
5620 this._update();
5621 }
5622
5623 return this;
5624 },
5625
5626 _update: function () {
5627 if (!this._map) { return; }
5628
5629 var attribs = [];
5630
5631 for (var i in this._attributions) {
5632 if (this._attributions[i]) {
5633 attribs.push(i);
5634 }
5635 }
5636
5637 var prefixAndAttribs = [];
5638
5639 if (this.options.prefix) {
5640 prefixAndAttribs.push(this.options.prefix);
5641 }
5642 if (attribs.length) {
5643 prefixAndAttribs.push(attribs.join(', '));
5644 }
5645
5646 this._container.innerHTML = prefixAndAttribs.join(' | ');
5647 }
5648});
5649
5650// @namespace Map
5651// @section Control options
5652// @option attributionControl: Boolean = true
5653// Whether a [attribution control](#control-attribution) is added to the map by default.
5654Map.mergeOptions({
5655 attributionControl: true
5656});
5657
5658Map.addInitHook(function () {
5659 if (this.options.attributionControl) {
5660 new Attribution().addTo(this);
5661 }
5662});
5663
5664// @namespace Control.Attribution
5665// @factory L.control.attribution(options: Control.Attribution options)
5666// Creates an attribution control.
5667var attribution = function (options) {
5668 return new Attribution(options);
5669};
5670
5671Control.Layers = Layers;
5672Control.Zoom = Zoom;
5673Control.Scale = Scale;
5674Control.Attribution = Attribution;
5675
5676control.layers = layers;
5677control.zoom = zoom;
5678control.scale = scale;
5679control.attribution = attribution;
5680
5681/*
5682 L.Handler is a base class for handler classes that are used internally to inject
5683 interaction features like dragging to classes like Map and Marker.
5684*/
5685
5686// @class Handler
5687// @aka L.Handler
5688// Abstract class for map interaction handlers
5689
5690var Handler = Class.extend({
5691 initialize: function (map) {
5692 this._map = map;
5693 },
5694
5695 // @method enable(): this
5696 // Enables the handler
5697 enable: function () {
5698 if (this._enabled) { return this; }
5699
5700 this._enabled = true;
5701 this.addHooks();
5702 return this;
5703 },
5704
5705 // @method disable(): this
5706 // Disables the handler
5707 disable: function () {
5708 if (!this._enabled) { return this; }
5709
5710 this._enabled = false;
5711 this.removeHooks();
5712 return this;
5713 },
5714
5715 // @method enabled(): Boolean
5716 // Returns `true` if the handler is enabled
5717 enabled: function () {
5718 return !!this._enabled;
5719 }
5720
5721 // @section Extension methods
5722 // Classes inheriting from `Handler` must implement the two following methods:
5723 // @method addHooks()
5724 // Called when the handler is enabled, should add event hooks.
5725 // @method removeHooks()
5726 // Called when the handler is disabled, should remove the event hooks added previously.
5727});
5728
5729// @section There is static function which can be called without instantiating L.Handler:
5730// @function addTo(map: Map, name: String): this
5731// Adds a new Handler to the given map with the given name.
5732Handler.addTo = function (map, name) {
5733 map.addHandler(name, this);
5734 return this;
5735};
5736
5737var Mixin = {Events: Events};
5738
5739/*
5740 * @class Draggable
5741 * @aka L.Draggable
5742 * @inherits Evented
5743 *
5744 * A class for making DOM elements draggable (including touch support).
5745 * Used internally for map and marker dragging. Only works for elements
5746 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
5747 *
5748 * @example
5749 * ```js
5750 * var draggable = new L.Draggable(elementToDrag);
5751 * draggable.enable();
5752 * ```
5753 */
5754
5755var START = touch ? 'touchstart mousedown' : 'mousedown';
5756var END = {
5757 mousedown: 'mouseup',
5758 touchstart: 'touchend',
5759 pointerdown: 'touchend',
5760 MSPointerDown: 'touchend'
5761};
5762var MOVE = {
5763 mousedown: 'mousemove',
5764 touchstart: 'touchmove',
5765 pointerdown: 'touchmove',
5766 MSPointerDown: 'touchmove'
5767};
5768
5769
5770var Draggable = Evented.extend({
5771
5772 options: {
5773 // @section
5774 // @aka Draggable options
5775 // @option clickTolerance: Number = 3
5776 // The max number of pixels a user can shift the mouse pointer during a click
5777 // for it to be considered a valid click (as opposed to a mouse drag).
5778 clickTolerance: 3
5779 },
5780
5781 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
5782 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
5783 initialize: function (element, dragStartTarget, preventOutline$$1, options) {
5784 setOptions(this, options);
5785
5786 this._element = element;
5787 this._dragStartTarget = dragStartTarget || element;
5788 this._preventOutline = preventOutline$$1;
5789 },
5790
5791 // @method enable()
5792 // Enables the dragging ability
5793 enable: function () {
5794 if (this._enabled) { return; }
5795
5796 on(this._dragStartTarget, START, this._onDown, this);
5797
5798 this._enabled = true;
5799 },
5800
5801 // @method disable()
5802 // Disables the dragging ability
5803 disable: function () {
5804 if (!this._enabled) { return; }
5805
5806 // If we're currently dragging this draggable,
5807 // disabling it counts as first ending the drag.
5808 if (Draggable._dragging === this) {
5809 this.finishDrag();
5810 }
5811
5812 off(this._dragStartTarget, START, this._onDown, this);
5813
5814 this._enabled = false;
5815 this._moved = false;
5816 },
5817
5818 _onDown: function (e) {
5819 // Ignore simulated events, since we handle both touch and
5820 // mouse explicitly; otherwise we risk getting duplicates of
5821 // touch events, see #4315.
5822 // Also ignore the event if disabled; this happens in IE11
5823 // under some circumstances, see #3666.
5824 if (e._simulated || !this._enabled) { return; }
5825
5826 this._moved = false;
5827
5828 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
5829
5830 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
5831 Draggable._dragging = this; // Prevent dragging multiple objects at once.
5832
5833 if (this._preventOutline) {
5834 preventOutline(this._element);
5835 }
5836
5837 disableImageDrag();
5838 disableTextSelection();
5839
5840 if (this._moving) { return; }
5841
5842 // @event down: Event
5843 // Fired when a drag is about to start.
5844 this.fire('down');
5845
5846 var first = e.touches ? e.touches[0] : e,
5847 sizedParent = getSizedParentNode(this._element);
5848
5849 this._startPoint = new Point(first.clientX, first.clientY);
5850
5851 // Cache the scale, so that we can continuously compensate for it during drag (_onMove).
5852 this._parentScale = getScale(sizedParent);
5853
5854 on(document, MOVE[e.type], this._onMove, this);
5855 on(document, END[e.type], this._onUp, this);
5856 },
5857
5858 _onMove: function (e) {
5859 // Ignore simulated events, since we handle both touch and
5860 // mouse explicitly; otherwise we risk getting duplicates of
5861 // touch events, see #4315.
5862 // Also ignore the event if disabled; this happens in IE11
5863 // under some circumstances, see #3666.
5864 if (e._simulated || !this._enabled) { return; }
5865
5866 if (e.touches && e.touches.length > 1) {
5867 this._moved = true;
5868 return;
5869 }
5870
5871 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
5872 offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
5873
5874 if (!offset.x && !offset.y) { return; }
5875 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
5876
5877 // We assume that the parent container's position, border and scale do not change for the duration of the drag.
5878 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
5879 // and we can use the cached value for the scale.
5880 offset.x /= this._parentScale.x;
5881 offset.y /= this._parentScale.y;
5882
5883 preventDefault(e);
5884
5885 if (!this._moved) {
5886 // @event dragstart: Event
5887 // Fired when a drag starts
5888 this.fire('dragstart');
5889
5890 this._moved = true;
5891 this._startPos = getPosition(this._element).subtract(offset);
5892
5893 addClass(document.body, 'leaflet-dragging');
5894
5895 this._lastTarget = e.target || e.srcElement;
5896 // IE and Edge do not give the <use> element, so fetch it
5897 // if necessary
5898 if (window.SVGElementInstance && this._lastTarget instanceof window.SVGElementInstance) {
5899 this._lastTarget = this._lastTarget.correspondingUseElement;
5900 }
5901 addClass(this._lastTarget, 'leaflet-drag-target');
5902 }
5903
5904 this._newPos = this._startPos.add(offset);
5905 this._moving = true;
5906
5907 cancelAnimFrame(this._animRequest);
5908 this._lastEvent = e;
5909 this._animRequest = requestAnimFrame(this._updatePosition, this, true);
5910 },
5911
5912 _updatePosition: function () {
5913 var e = {originalEvent: this._lastEvent};
5914
5915 // @event predrag: Event
5916 // Fired continuously during dragging *before* each corresponding
5917 // update of the element's position.
5918 this.fire('predrag', e);
5919 setPosition(this._element, this._newPos);
5920
5921 // @event drag: Event
5922 // Fired continuously during dragging.
5923 this.fire('drag', e);
5924 },
5925
5926 _onUp: function (e) {
5927 // Ignore simulated events, since we handle both touch and
5928 // mouse explicitly; otherwise we risk getting duplicates of
5929 // touch events, see #4315.
5930 // Also ignore the event if disabled; this happens in IE11
5931 // under some circumstances, see #3666.
5932 if (e._simulated || !this._enabled) { return; }
5933 this.finishDrag();
5934 },
5935
5936 finishDrag: function () {
5937 removeClass(document.body, 'leaflet-dragging');
5938
5939 if (this._lastTarget) {
5940 removeClass(this._lastTarget, 'leaflet-drag-target');
5941 this._lastTarget = null;
5942 }
5943
5944 for (var i in MOVE) {
5945 off(document, MOVE[i], this._onMove, this);
5946 off(document, END[i], this._onUp, this);
5947 }
5948
5949 enableImageDrag();
5950 enableTextSelection();
5951
5952 if (this._moved && this._moving) {
5953 // ensure drag is not fired after dragend
5954 cancelAnimFrame(this._animRequest);
5955
5956 // @event dragend: DragEndEvent
5957 // Fired when the drag ends.
5958 this.fire('dragend', {
5959 distance: this._newPos.distanceTo(this._startPos)
5960 });
5961 }
5962
5963 this._moving = false;
5964 Draggable._dragging = false;
5965 }
5966
5967});
5968
5969/*
5970 * @namespace LineUtil
5971 *
5972 * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
5973 */
5974
5975// Simplify polyline with vertex reduction and Douglas-Peucker simplification.
5976// Improves rendering performance dramatically by lessening the number of points to draw.
5977
5978// @function simplify(points: Point[], tolerance: Number): Point[]
5979// Dramatically reduces the number of points in a polyline while retaining
5980// its shape and returns a new array of simplified points, using the
5981// [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
5982// Used for a huge performance boost when processing/displaying Leaflet polylines for
5983// each zoom level and also reducing visual noise. tolerance affects the amount of
5984// simplification (lesser value means higher quality but slower and with more points).
5985// Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
5986function simplify(points, tolerance) {
5987 if (!tolerance || !points.length) {
5988 return points.slice();
5989 }
5990
5991 var sqTolerance = tolerance * tolerance;
5992
5993 // stage 1: vertex reduction
5994 points = _reducePoints(points, sqTolerance);
5995
5996 // stage 2: Douglas-Peucker simplification
5997 points = _simplifyDP(points, sqTolerance);
5998
5999 return points;
6000}
6001
6002// @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
6003// Returns the distance between point `p` and segment `p1` to `p2`.
6004function pointToSegmentDistance(p, p1, p2) {
6005 return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
6006}
6007
6008// @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
6009// Returns the closest point from a point `p` on a segment `p1` to `p2`.
6010function closestPointOnSegment(p, p1, p2) {
6011 return _sqClosestPointOnSegment(p, p1, p2);
6012}
6013
6014// Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
6015function _simplifyDP(points, sqTolerance) {
6016
6017 var len = points.length,
6018 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
6019 markers = new ArrayConstructor(len);
6020
6021 markers[0] = markers[len - 1] = 1;
6022
6023 _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
6024
6025 var i,
6026 newPoints = [];
6027
6028 for (i = 0; i < len; i++) {
6029 if (markers[i]) {
6030 newPoints.push(points[i]);
6031 }
6032 }
6033
6034 return newPoints;
6035}
6036
6037function _simplifyDPStep(points, markers, sqTolerance, first, last) {
6038
6039 var maxSqDist = 0,
6040 index, i, sqDist;
6041
6042 for (i = first + 1; i <= last - 1; i++) {
6043 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
6044
6045 if (sqDist > maxSqDist) {
6046 index = i;
6047 maxSqDist = sqDist;
6048 }
6049 }
6050
6051 if (maxSqDist > sqTolerance) {
6052 markers[index] = 1;
6053
6054 _simplifyDPStep(points, markers, sqTolerance, first, index);
6055 _simplifyDPStep(points, markers, sqTolerance, index, last);
6056 }
6057}
6058
6059// reduce points that are too close to each other to a single point
6060function _reducePoints(points, sqTolerance) {
6061 var reducedPoints = [points[0]];
6062
6063 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
6064 if (_sqDist(points[i], points[prev]) > sqTolerance) {
6065 reducedPoints.push(points[i]);
6066 prev = i;
6067 }
6068 }
6069 if (prev < len - 1) {
6070 reducedPoints.push(points[len - 1]);
6071 }
6072 return reducedPoints;
6073}
6074
6075var _lastCode;
6076
6077// @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
6078// Clips the segment a to b by rectangular bounds with the
6079// [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
6080// (modifying the segment points directly!). Used by Leaflet to only show polyline
6081// points that are on the screen or near, increasing performance.
6082function clipSegment(a, b, bounds, useLastCode, round) {
6083 var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
6084 codeB = _getBitCode(b, bounds),
6085
6086 codeOut, p, newCode;
6087
6088 // save 2nd code to avoid calculating it on the next segment
6089 _lastCode = codeB;
6090
6091 while (true) {
6092 // if a,b is inside the clip window (trivial accept)
6093 if (!(codeA | codeB)) {
6094 return [a, b];
6095 }
6096
6097 // if a,b is outside the clip window (trivial reject)
6098 if (codeA & codeB) {
6099 return false;
6100 }
6101
6102 // other cases
6103 codeOut = codeA || codeB;
6104 p = _getEdgeIntersection(a, b, codeOut, bounds, round);
6105 newCode = _getBitCode(p, bounds);
6106
6107 if (codeOut === codeA) {
6108 a = p;
6109 codeA = newCode;
6110 } else {
6111 b = p;
6112 codeB = newCode;
6113 }
6114 }
6115}
6116
6117function _getEdgeIntersection(a, b, code, bounds, round) {
6118 var dx = b.x - a.x,
6119 dy = b.y - a.y,
6120 min = bounds.min,
6121 max = bounds.max,
6122 x, y;
6123
6124 if (code & 8) { // top
6125 x = a.x + dx * (max.y - a.y) / dy;
6126 y = max.y;
6127
6128 } else if (code & 4) { // bottom
6129 x = a.x + dx * (min.y - a.y) / dy;
6130 y = min.y;
6131
6132 } else if (code & 2) { // right
6133 x = max.x;
6134 y = a.y + dy * (max.x - a.x) / dx;
6135
6136 } else if (code & 1) { // left
6137 x = min.x;
6138 y = a.y + dy * (min.x - a.x) / dx;
6139 }
6140
6141 return new Point(x, y, round);
6142}
6143
6144function _getBitCode(p, bounds) {
6145 var code = 0;
6146
6147 if (p.x < bounds.min.x) { // left
6148 code |= 1;
6149 } else if (p.x > bounds.max.x) { // right
6150 code |= 2;
6151 }
6152
6153 if (p.y < bounds.min.y) { // bottom
6154 code |= 4;
6155 } else if (p.y > bounds.max.y) { // top
6156 code |= 8;
6157 }
6158
6159 return code;
6160}
6161
6162// square distance (to avoid unnecessary Math.sqrt calls)
6163function _sqDist(p1, p2) {
6164 var dx = p2.x - p1.x,
6165 dy = p2.y - p1.y;
6166 return dx * dx + dy * dy;
6167}
6168
6169// return closest point on segment or distance to that point
6170function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
6171 var x = p1.x,
6172 y = p1.y,
6173 dx = p2.x - x,
6174 dy = p2.y - y,
6175 dot = dx * dx + dy * dy,
6176 t;
6177
6178 if (dot > 0) {
6179 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
6180
6181 if (t > 1) {
6182 x = p2.x;
6183 y = p2.y;
6184 } else if (t > 0) {
6185 x += dx * t;
6186 y += dy * t;
6187 }
6188 }
6189
6190 dx = p.x - x;
6191 dy = p.y - y;
6192
6193 return sqDist ? dx * dx + dy * dy : new Point(x, y);
6194}
6195
6196
6197// @function isFlat(latlngs: LatLng[]): Boolean
6198// Returns true if `latlngs` is a flat array, false is nested.
6199function isFlat(latlngs) {
6200 return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
6201}
6202
6203function _flat(latlngs) {
6204 console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
6205 return isFlat(latlngs);
6206}
6207
6208var LineUtil = ({
6209 simplify: simplify,
6210 pointToSegmentDistance: pointToSegmentDistance,
6211 closestPointOnSegment: closestPointOnSegment,
6212 clipSegment: clipSegment,
6213 _getEdgeIntersection: _getEdgeIntersection,
6214 _getBitCode: _getBitCode,
6215 _sqClosestPointOnSegment: _sqClosestPointOnSegment,
6216 isFlat: isFlat,
6217 _flat: _flat
6218});
6219
6220/*
6221 * @namespace PolyUtil
6222 * Various utility functions for polygon geometries.
6223 */
6224
6225/* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
6226 * 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)).
6227 * Used by Leaflet to only show polygon points that are on the screen or near, increasing
6228 * performance. Note that polygon points needs different algorithm for clipping
6229 * than polyline, so there's a separate method for it.
6230 */
6231function clipPolygon(points, bounds, round) {
6232 var clippedPoints,
6233 edges = [1, 4, 2, 8],
6234 i, j, k,
6235 a, b,
6236 len, edge, p;
6237
6238 for (i = 0, len = points.length; i < len; i++) {
6239 points[i]._code = _getBitCode(points[i], bounds);
6240 }
6241
6242 // for each edge (left, bottom, right, top)
6243 for (k = 0; k < 4; k++) {
6244 edge = edges[k];
6245 clippedPoints = [];
6246
6247 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
6248 a = points[i];
6249 b = points[j];
6250
6251 // if a is inside the clip window
6252 if (!(a._code & edge)) {
6253 // if b is outside the clip window (a->b goes out of screen)
6254 if (b._code & edge) {
6255 p = _getEdgeIntersection(b, a, edge, bounds, round);
6256 p._code = _getBitCode(p, bounds);
6257 clippedPoints.push(p);
6258 }
6259 clippedPoints.push(a);
6260
6261 // else if b is inside the clip window (a->b enters the screen)
6262 } else if (!(b._code & edge)) {
6263 p = _getEdgeIntersection(b, a, edge, bounds, round);
6264 p._code = _getBitCode(p, bounds);
6265 clippedPoints.push(p);
6266 }
6267 }
6268 points = clippedPoints;
6269 }
6270
6271 return points;
6272}
6273
6274var PolyUtil = ({
6275 clipPolygon: clipPolygon
6276});
6277
6278/*
6279 * @namespace Projection
6280 * @section
6281 * Leaflet comes with a set of already defined Projections out of the box:
6282 *
6283 * @projection L.Projection.LonLat
6284 *
6285 * Equirectangular, or Plate Carree projection — the most simple projection,
6286 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
6287 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
6288 * `EPSG:4326` and `Simple` CRS.
6289 */
6290
6291var LonLat = {
6292 project: function (latlng) {
6293 return new Point(latlng.lng, latlng.lat);
6294 },
6295
6296 unproject: function (point) {
6297 return new LatLng(point.y, point.x);
6298 },
6299
6300 bounds: new Bounds([-180, -90], [180, 90])
6301};
6302
6303/*
6304 * @namespace Projection
6305 * @projection L.Projection.Mercator
6306 *
6307 * Elliptical Mercator projection — more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS.
6308 */
6309
6310var Mercator = {
6311 R: 6378137,
6312 R_MINOR: 6356752.314245179,
6313
6314 bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
6315
6316 project: function (latlng) {
6317 var d = Math.PI / 180,
6318 r = this.R,
6319 y = latlng.lat * d,
6320 tmp = this.R_MINOR / r,
6321 e = Math.sqrt(1 - tmp * tmp),
6322 con = e * Math.sin(y);
6323
6324 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
6325 y = -r * Math.log(Math.max(ts, 1E-10));
6326
6327 return new Point(latlng.lng * d * r, y);
6328 },
6329
6330 unproject: function (point) {
6331 var d = 180 / Math.PI,
6332 r = this.R,
6333 tmp = this.R_MINOR / r,
6334 e = Math.sqrt(1 - tmp * tmp),
6335 ts = Math.exp(-point.y / r),
6336 phi = Math.PI / 2 - 2 * Math.atan(ts);
6337
6338 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
6339 con = e * Math.sin(phi);
6340 con = Math.pow((1 - con) / (1 + con), e / 2);
6341 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
6342 phi += dphi;
6343 }
6344
6345 return new LatLng(phi * d, point.x * d / r);
6346 }
6347};
6348
6349/*
6350 * @class Projection
6351
6352 * An object with methods for projecting geographical coordinates of the world onto
6353 * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection).
6354
6355 * @property bounds: Bounds
6356 * The bounds (specified in CRS units) where the projection is valid
6357
6358 * @method project(latlng: LatLng): Point
6359 * Projects geographical coordinates into a 2D point.
6360 * Only accepts actual `L.LatLng` instances, not arrays.
6361
6362 * @method unproject(point: Point): LatLng
6363 * The inverse of `project`. Projects a 2D point into a geographical location.
6364 * Only accepts actual `L.Point` instances, not arrays.
6365
6366 * Note that the projection instances do not inherit from Leaflet's `Class` object,
6367 * and can't be instantiated. Also, new classes can't inherit from them,
6368 * and methods can't be added to them with the `include` function.
6369
6370 */
6371
6372var index = ({
6373 LonLat: LonLat,
6374 Mercator: Mercator,
6375 SphericalMercator: SphericalMercator
6376});
6377
6378/*
6379 * @namespace CRS
6380 * @crs L.CRS.EPSG3395
6381 *
6382 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
6383 */
6384var EPSG3395 = extend({}, Earth, {
6385 code: 'EPSG:3395',
6386 projection: Mercator,
6387
6388 transformation: (function () {
6389 var scale = 0.5 / (Math.PI * Mercator.R);
6390 return toTransformation(scale, 0.5, -scale, 0.5);
6391 }())
6392});
6393
6394/*
6395 * @namespace CRS
6396 * @crs L.CRS.EPSG4326
6397 *
6398 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
6399 *
6400 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
6401 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer`
6402 * with this CRS, ensure that there are two 256x256 pixel tiles covering the
6403 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
6404 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
6405 */
6406
6407var EPSG4326 = extend({}, Earth, {
6408 code: 'EPSG:4326',
6409 projection: LonLat,
6410 transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
6411});
6412
6413/*
6414 * @namespace CRS
6415 * @crs L.CRS.Simple
6416 *
6417 * A simple CRS that maps longitude and latitude into `x` and `y` directly.
6418 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
6419 * axis should still be inverted (going from bottom to top). `distance()` returns
6420 * simple euclidean distance.
6421 */
6422
6423var Simple = extend({}, CRS, {
6424 projection: LonLat,
6425 transformation: toTransformation(1, 0, -1, 0),
6426
6427 scale: function (zoom) {
6428 return Math.pow(2, zoom);
6429 },
6430
6431 zoom: function (scale) {
6432 return Math.log(scale) / Math.LN2;
6433 },
6434
6435 distance: function (latlng1, latlng2) {
6436 var dx = latlng2.lng - latlng1.lng,
6437 dy = latlng2.lat - latlng1.lat;
6438
6439 return Math.sqrt(dx * dx + dy * dy);
6440 },
6441
6442 infinite: true
6443});
6444
6445CRS.Earth = Earth;
6446CRS.EPSG3395 = EPSG3395;
6447CRS.EPSG3857 = EPSG3857;
6448CRS.EPSG900913 = EPSG900913;
6449CRS.EPSG4326 = EPSG4326;
6450CRS.Simple = Simple;
6451
6452/*
6453 * @class Layer
6454 * @inherits Evented
6455 * @aka L.Layer
6456 * @aka ILayer
6457 *
6458 * A set of methods from the Layer base class that all Leaflet layers use.
6459 * Inherits all methods, options and events from `L.Evented`.
6460 *
6461 * @example
6462 *
6463 * ```js
6464 * var layer = L.marker(latlng).addTo(map);
6465 * layer.addTo(map);
6466 * layer.remove();
6467 * ```
6468 *
6469 * @event add: Event
6470 * Fired after the layer is added to a map
6471 *
6472 * @event remove: Event
6473 * Fired after the layer is removed from a map
6474 */
6475
6476
6477var Layer = Evented.extend({
6478
6479 // Classes extending `L.Layer` will inherit the following options:
6480 options: {
6481 // @option pane: String = 'overlayPane'
6482 // 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.
6483 pane: 'overlayPane',
6484
6485 // @option attribution: String = null
6486 // 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.
6487 attribution: null,
6488
6489 bubblingMouseEvents: true
6490 },
6491
6492 /* @section
6493 * Classes extending `L.Layer` will inherit the following methods:
6494 *
6495 * @method addTo(map: Map|LayerGroup): this
6496 * Adds the layer to the given map or layer group.
6497 */
6498 addTo: function (map) {
6499 map.addLayer(this);
6500 return this;
6501 },
6502
6503 // @method remove: this
6504 // Removes the layer from the map it is currently active on.
6505 remove: function () {
6506 return this.removeFrom(this._map || this._mapToAdd);
6507 },
6508
6509 // @method removeFrom(map: Map): this
6510 // Removes the layer from the given map
6511 //
6512 // @alternative
6513 // @method removeFrom(group: LayerGroup): this
6514 // Removes the layer from the given `LayerGroup`
6515 removeFrom: function (obj) {
6516 if (obj) {
6517 obj.removeLayer(this);
6518 }
6519 return this;
6520 },
6521
6522 // @method getPane(name? : String): HTMLElement
6523 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
6524 getPane: function (name) {
6525 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
6526 },
6527
6528 addInteractiveTarget: function (targetEl) {
6529 this._map._targets[stamp(targetEl)] = this;
6530 return this;
6531 },
6532
6533 removeInteractiveTarget: function (targetEl) {
6534 delete this._map._targets[stamp(targetEl)];
6535 return this;
6536 },
6537
6538 // @method getAttribution: String
6539 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
6540 getAttribution: function () {
6541 return this.options.attribution;
6542 },
6543
6544 _layerAdd: function (e) {
6545 var map = e.target;
6546
6547 // check in case layer gets added and then removed before the map is ready
6548 if (!map.hasLayer(this)) { return; }
6549
6550 this._map = map;
6551 this._zoomAnimated = map._zoomAnimated;
6552
6553 if (this.getEvents) {
6554 var events = this.getEvents();
6555 map.on(events, this);
6556 this.once('remove', function () {
6557 map.off(events, this);
6558 }, this);
6559 }
6560
6561 this.onAdd(map);
6562
6563 if (this.getAttribution && map.attributionControl) {
6564 map.attributionControl.addAttribution(this.getAttribution());
6565 }
6566
6567 this.fire('add');
6568 map.fire('layeradd', {layer: this});
6569 }
6570});
6571
6572/* @section Extension methods
6573 * @uninheritable
6574 *
6575 * Every layer should extend from `L.Layer` and (re-)implement the following methods.
6576 *
6577 * @method onAdd(map: Map): this
6578 * 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).
6579 *
6580 * @method onRemove(map: Map): this
6581 * 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).
6582 *
6583 * @method getEvents(): Object
6584 * 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.
6585 *
6586 * @method getAttribution(): String
6587 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
6588 *
6589 * @method beforeAdd(map: Map): this
6590 * 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.
6591 */
6592
6593
6594/* @namespace Map
6595 * @section Layer events
6596 *
6597 * @event layeradd: LayerEvent
6598 * Fired when a new layer is added to the map.
6599 *
6600 * @event layerremove: LayerEvent
6601 * Fired when some layer is removed from the map
6602 *
6603 * @section Methods for Layers and Controls
6604 */
6605Map.include({
6606 // @method addLayer(layer: Layer): this
6607 // Adds the given layer to the map
6608 addLayer: function (layer) {
6609 if (!layer._layerAdd) {
6610 throw new Error('The provided object is not a Layer.');
6611 }
6612
6613 var id = stamp(layer);
6614 if (this._layers[id]) { return this; }
6615 this._layers[id] = layer;
6616
6617 layer._mapToAdd = this;
6618
6619 if (layer.beforeAdd) {
6620 layer.beforeAdd(this);
6621 }
6622
6623 this.whenReady(layer._layerAdd, layer);
6624
6625 return this;
6626 },
6627
6628 // @method removeLayer(layer: Layer): this
6629 // Removes the given layer from the map.
6630 removeLayer: function (layer) {
6631 var id = stamp(layer);
6632
6633 if (!this._layers[id]) { return this; }
6634
6635 if (this._loaded) {
6636 layer.onRemove(this);
6637 }
6638
6639 if (layer.getAttribution && this.attributionControl) {
6640 this.attributionControl.removeAttribution(layer.getAttribution());
6641 }
6642
6643 delete this._layers[id];
6644
6645 if (this._loaded) {
6646 this.fire('layerremove', {layer: layer});
6647 layer.fire('remove');
6648 }
6649
6650 layer._map = layer._mapToAdd = null;
6651
6652 return this;
6653 },
6654
6655 // @method hasLayer(layer: Layer): Boolean
6656 // Returns `true` if the given layer is currently added to the map
6657 hasLayer: function (layer) {
6658 return !!layer && (stamp(layer) in this._layers);
6659 },
6660
6661 /* @method eachLayer(fn: Function, context?: Object): this
6662 * Iterates over the layers of the map, optionally specifying context of the iterator function.
6663 * ```
6664 * map.eachLayer(function(layer){
6665 * layer.bindPopup('Hello');
6666 * });
6667 * ```
6668 */
6669 eachLayer: function (method, context) {
6670 for (var i in this._layers) {
6671 method.call(context, this._layers[i]);
6672 }
6673 return this;
6674 },
6675
6676 _addLayers: function (layers) {
6677 layers = layers ? (isArray(layers) ? layers : [layers]) : [];
6678
6679 for (var i = 0, len = layers.length; i < len; i++) {
6680 this.addLayer(layers[i]);
6681 }
6682 },
6683
6684 _addZoomLimit: function (layer) {
6685 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
6686 this._zoomBoundLayers[stamp(layer)] = layer;
6687 this._updateZoomLevels();
6688 }
6689 },
6690
6691 _removeZoomLimit: function (layer) {
6692 var id = stamp(layer);
6693
6694 if (this._zoomBoundLayers[id]) {
6695 delete this._zoomBoundLayers[id];
6696 this._updateZoomLevels();
6697 }
6698 },
6699
6700 _updateZoomLevels: function () {
6701 var minZoom = Infinity,
6702 maxZoom = -Infinity,
6703 oldZoomSpan = this._getZoomSpan();
6704
6705 for (var i in this._zoomBoundLayers) {
6706 var options = this._zoomBoundLayers[i].options;
6707
6708 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
6709 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
6710 }
6711
6712 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
6713 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
6714
6715 // @section Map state change events
6716 // @event zoomlevelschange: Event
6717 // Fired when the number of zoomlevels on the map is changed due
6718 // to adding or removing a layer.
6719 if (oldZoomSpan !== this._getZoomSpan()) {
6720 this.fire('zoomlevelschange');
6721 }
6722
6723 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
6724 this.setZoom(this._layersMaxZoom);
6725 }
6726 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
6727 this.setZoom(this._layersMinZoom);
6728 }
6729 }
6730});
6731
6732/*
6733 * @class LayerGroup
6734 * @aka L.LayerGroup
6735 * @inherits Layer
6736 *
6737 * Used to group several layers and handle them as one. If you add it to the map,
6738 * any layers added or removed from the group will be added/removed on the map as
6739 * well. Extends `Layer`.
6740 *
6741 * @example
6742 *
6743 * ```js
6744 * L.layerGroup([marker1, marker2])
6745 * .addLayer(polyline)
6746 * .addTo(map);
6747 * ```
6748 */
6749
6750var LayerGroup = Layer.extend({
6751
6752 initialize: function (layers, options) {
6753 setOptions(this, options);
6754
6755 this._layers = {};
6756
6757 var i, len;
6758
6759 if (layers) {
6760 for (i = 0, len = layers.length; i < len; i++) {
6761 this.addLayer(layers[i]);
6762 }
6763 }
6764 },
6765
6766 // @method addLayer(layer: Layer): this
6767 // Adds the given layer to the group.
6768 addLayer: function (layer) {
6769 var id = this.getLayerId(layer);
6770
6771 this._layers[id] = layer;
6772
6773 if (this._map) {
6774 this._map.addLayer(layer);
6775 }
6776
6777 return this;
6778 },
6779
6780 // @method removeLayer(layer: Layer): this
6781 // Removes the given layer from the group.
6782 // @alternative
6783 // @method removeLayer(id: Number): this
6784 // Removes the layer with the given internal ID from the group.
6785 removeLayer: function (layer) {
6786 var id = layer in this._layers ? layer : this.getLayerId(layer);
6787
6788 if (this._map && this._layers[id]) {
6789 this._map.removeLayer(this._layers[id]);
6790 }
6791
6792 delete this._layers[id];
6793
6794 return this;
6795 },
6796
6797 // @method hasLayer(layer: Layer): Boolean
6798 // Returns `true` if the given layer is currently added to the group.
6799 // @alternative
6800 // @method hasLayer(id: Number): Boolean
6801 // Returns `true` if the given internal ID is currently added to the group.
6802 hasLayer: function (layer) {
6803 if (!layer) { return false; }
6804 var layerId = typeof layer === 'number' ? layer : this.getLayerId(layer);
6805 return layerId in this._layers;
6806 },
6807
6808 // @method clearLayers(): this
6809 // Removes all the layers from the group.
6810 clearLayers: function () {
6811 return this.eachLayer(this.removeLayer, this);
6812 },
6813
6814 // @method invoke(methodName: String, …): this
6815 // Calls `methodName` on every layer contained in this group, passing any
6816 // additional parameters. Has no effect if the layers contained do not
6817 // implement `methodName`.
6818 invoke: function (methodName) {
6819 var args = Array.prototype.slice.call(arguments, 1),
6820 i, layer;
6821
6822 for (i in this._layers) {
6823 layer = this._layers[i];
6824
6825 if (layer[methodName]) {
6826 layer[methodName].apply(layer, args);
6827 }
6828 }
6829
6830 return this;
6831 },
6832
6833 onAdd: function (map) {
6834 this.eachLayer(map.addLayer, map);
6835 },
6836
6837 onRemove: function (map) {
6838 this.eachLayer(map.removeLayer, map);
6839 },
6840
6841 // @method eachLayer(fn: Function, context?: Object): this
6842 // Iterates over the layers of the group, optionally specifying context of the iterator function.
6843 // ```js
6844 // group.eachLayer(function (layer) {
6845 // layer.bindPopup('Hello');
6846 // });
6847 // ```
6848 eachLayer: function (method, context) {
6849 for (var i in this._layers) {
6850 method.call(context, this._layers[i]);
6851 }
6852 return this;
6853 },
6854
6855 // @method getLayer(id: Number): Layer
6856 // Returns the layer with the given internal ID.
6857 getLayer: function (id) {
6858 return this._layers[id];
6859 },
6860
6861 // @method getLayers(): Layer[]
6862 // Returns an array of all the layers added to the group.
6863 getLayers: function () {
6864 var layers = [];
6865 this.eachLayer(layers.push, layers);
6866 return layers;
6867 },
6868
6869 // @method setZIndex(zIndex: Number): this
6870 // Calls `setZIndex` on every layer contained in this group, passing the z-index.
6871 setZIndex: function (zIndex) {
6872 return this.invoke('setZIndex', zIndex);
6873 },
6874
6875 // @method getLayerId(layer: Layer): Number
6876 // Returns the internal ID for a layer
6877 getLayerId: function (layer) {
6878 return stamp(layer);
6879 }
6880});
6881
6882
6883// @factory L.layerGroup(layers?: Layer[], options?: Object)
6884// Create a layer group, optionally given an initial set of layers and an `options` object.
6885var layerGroup = function (layers, options) {
6886 return new LayerGroup(layers, options);
6887};
6888
6889/*
6890 * @class FeatureGroup
6891 * @aka L.FeatureGroup
6892 * @inherits LayerGroup
6893 *
6894 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
6895 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
6896 * * Events are propagated to the `FeatureGroup`, so if the group has an event
6897 * handler, it will handle events from any of the layers. This includes mouse events
6898 * and custom events.
6899 * * Has `layeradd` and `layerremove` events
6900 *
6901 * @example
6902 *
6903 * ```js
6904 * L.featureGroup([marker1, marker2, polyline])
6905 * .bindPopup('Hello world!')
6906 * .on('click', function() { alert('Clicked on a member of the group!'); })
6907 * .addTo(map);
6908 * ```
6909 */
6910
6911var FeatureGroup = LayerGroup.extend({
6912
6913 addLayer: function (layer) {
6914 if (this.hasLayer(layer)) {
6915 return this;
6916 }
6917
6918 layer.addEventParent(this);
6919
6920 LayerGroup.prototype.addLayer.call(this, layer);
6921
6922 // @event layeradd: LayerEvent
6923 // Fired when a layer is added to this `FeatureGroup`
6924 return this.fire('layeradd', {layer: layer});
6925 },
6926
6927 removeLayer: function (layer) {
6928 if (!this.hasLayer(layer)) {
6929 return this;
6930 }
6931 if (layer in this._layers) {
6932 layer = this._layers[layer];
6933 }
6934
6935 layer.removeEventParent(this);
6936
6937 LayerGroup.prototype.removeLayer.call(this, layer);
6938
6939 // @event layerremove: LayerEvent
6940 // Fired when a layer is removed from this `FeatureGroup`
6941 return this.fire('layerremove', {layer: layer});
6942 },
6943
6944 // @method setStyle(style: Path options): this
6945 // Sets the given path options to each layer of the group that has a `setStyle` method.
6946 setStyle: function (style) {
6947 return this.invoke('setStyle', style);
6948 },
6949
6950 // @method bringToFront(): this
6951 // Brings the layer group to the top of all other layers
6952 bringToFront: function () {
6953 return this.invoke('bringToFront');
6954 },
6955
6956 // @method bringToBack(): this
6957 // Brings the layer group to the back of all other layers
6958 bringToBack: function () {
6959 return this.invoke('bringToBack');
6960 },
6961
6962 // @method getBounds(): LatLngBounds
6963 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
6964 getBounds: function () {
6965 var bounds = new LatLngBounds();
6966
6967 for (var id in this._layers) {
6968 var layer = this._layers[id];
6969 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
6970 }
6971 return bounds;
6972 }
6973});
6974
6975// @factory L.featureGroup(layers?: Layer[], options?: Object)
6976// Create a feature group, optionally given an initial set of layers and an `options` object.
6977var featureGroup = function (layers, options) {
6978 return new FeatureGroup(layers, options);
6979};
6980
6981/*
6982 * @class Icon
6983 * @aka L.Icon
6984 *
6985 * Represents an icon to provide when creating a marker.
6986 *
6987 * @example
6988 *
6989 * ```js
6990 * var myIcon = L.icon({
6991 * iconUrl: 'my-icon.png',
6992 * iconRetinaUrl: 'my-icon@2x.png',
6993 * iconSize: [38, 95],
6994 * iconAnchor: [22, 94],
6995 * popupAnchor: [-3, -76],
6996 * shadowUrl: 'my-icon-shadow.png',
6997 * shadowRetinaUrl: 'my-icon-shadow@2x.png',
6998 * shadowSize: [68, 95],
6999 * shadowAnchor: [22, 94]
7000 * });
7001 *
7002 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
7003 * ```
7004 *
7005 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
7006 *
7007 */
7008
7009var Icon = Class.extend({
7010
7011 /* @section
7012 * @aka Icon options
7013 *
7014 * @option iconUrl: String = null
7015 * **(required)** The URL to the icon image (absolute or relative to your script path).
7016 *
7017 * @option iconRetinaUrl: String = null
7018 * The URL to a retina sized version of the icon image (absolute or relative to your
7019 * script path). Used for Retina screen devices.
7020 *
7021 * @option iconSize: Point = null
7022 * Size of the icon image in pixels.
7023 *
7024 * @option iconAnchor: Point = null
7025 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
7026 * will be aligned so that this point is at the marker's geographical location. Centered
7027 * by default if size is specified, also can be set in CSS with negative margins.
7028 *
7029 * @option popupAnchor: Point = [0, 0]
7030 * The coordinates of the point from which popups will "open", relative to the icon anchor.
7031 *
7032 * @option tooltipAnchor: Point = [0, 0]
7033 * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
7034 *
7035 * @option shadowUrl: String = null
7036 * The URL to the icon shadow image. If not specified, no shadow image will be created.
7037 *
7038 * @option shadowRetinaUrl: String = null
7039 *
7040 * @option shadowSize: Point = null
7041 * Size of the shadow image in pixels.
7042 *
7043 * @option shadowAnchor: Point = null
7044 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
7045 * as iconAnchor if not specified).
7046 *
7047 * @option className: String = ''
7048 * A custom class name to assign to both icon and shadow images. Empty by default.
7049 */
7050
7051 options: {
7052 popupAnchor: [0, 0],
7053 tooltipAnchor: [0, 0]
7054 },
7055
7056 initialize: function (options) {
7057 setOptions(this, options);
7058 },
7059
7060 // @method createIcon(oldIcon?: HTMLElement): HTMLElement
7061 // Called internally when the icon has to be shown, returns a `<img>` HTML element
7062 // styled according to the options.
7063 createIcon: function (oldIcon) {
7064 return this._createIcon('icon', oldIcon);
7065 },
7066
7067 // @method createShadow(oldIcon?: HTMLElement): HTMLElement
7068 // As `createIcon`, but for the shadow beneath it.
7069 createShadow: function (oldIcon) {
7070 return this._createIcon('shadow', oldIcon);
7071 },
7072
7073 _createIcon: function (name, oldIcon) {
7074 var src = this._getIconUrl(name);
7075
7076 if (!src) {
7077 if (name === 'icon') {
7078 throw new Error('iconUrl not set in Icon options (see the docs).');
7079 }
7080 return null;
7081 }
7082
7083 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
7084 this._setIconStyles(img, name);
7085
7086 return img;
7087 },
7088
7089 _setIconStyles: function (img, name) {
7090 var options = this.options;
7091 var sizeOption = options[name + 'Size'];
7092
7093 if (typeof sizeOption === 'number') {
7094 sizeOption = [sizeOption, sizeOption];
7095 }
7096
7097 var size = toPoint(sizeOption),
7098 anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
7099 size && size.divideBy(2, true));
7100
7101 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
7102
7103 if (anchor) {
7104 img.style.marginLeft = (-anchor.x) + 'px';
7105 img.style.marginTop = (-anchor.y) + 'px';
7106 }
7107
7108 if (size) {
7109 img.style.width = size.x + 'px';
7110 img.style.height = size.y + 'px';
7111 }
7112 },
7113
7114 _createImg: function (src, el) {
7115 el = el || document.createElement('img');
7116 el.src = src;
7117 return el;
7118 },
7119
7120 _getIconUrl: function (name) {
7121 return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
7122 }
7123});
7124
7125
7126// @factory L.icon(options: Icon options)
7127// Creates an icon instance with the given options.
7128function icon(options) {
7129 return new Icon(options);
7130}
7131
7132/*
7133 * @miniclass Icon.Default (Icon)
7134 * @aka L.Icon.Default
7135 * @section
7136 *
7137 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
7138 * no icon is specified. Points to the blue marker image distributed with Leaflet
7139 * releases.
7140 *
7141 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
7142 * (which is a set of `Icon options`).
7143 *
7144 * If you want to _completely_ replace the default icon, override the
7145 * `L.Marker.prototype.options.icon` with your own icon instead.
7146 */
7147
7148var IconDefault = Icon.extend({
7149
7150 options: {
7151 iconUrl: 'marker-icon.png',
7152 iconRetinaUrl: 'marker-icon-2x.png',
7153 shadowUrl: 'marker-shadow.png',
7154 iconSize: [25, 41],
7155 iconAnchor: [12, 41],
7156 popupAnchor: [1, -34],
7157 tooltipAnchor: [16, -28],
7158 shadowSize: [41, 41]
7159 },
7160
7161 _getIconUrl: function (name) {
7162 if (!IconDefault.imagePath) { // Deprecated, backwards-compatibility only
7163 IconDefault.imagePath = this._detectIconPath();
7164 }
7165
7166 // @option imagePath: String
7167 // `Icon.Default` will try to auto-detect the location of the
7168 // blue icon images. If you are placing these images in a non-standard
7169 // way, set this option to point to the right path.
7170 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
7171 },
7172
7173 _detectIconPath: function () {
7174 var el = create$1('div', 'leaflet-default-icon-path', document.body);
7175 var path = getStyle(el, 'background-image') ||
7176 getStyle(el, 'backgroundImage'); // IE8
7177
7178 document.body.removeChild(el);
7179
7180 if (path === null || path.indexOf('url') !== 0) {
7181 path = '';
7182 } else {
7183 path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, '');
7184 }
7185
7186 return path;
7187 }
7188});
7189
7190/*
7191 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7192 */
7193
7194
7195/* @namespace Marker
7196 * @section Interaction handlers
7197 *
7198 * 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:
7199 *
7200 * ```js
7201 * marker.dragging.disable();
7202 * ```
7203 *
7204 * @property dragging: Handler
7205 * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
7206 */
7207
7208var MarkerDrag = Handler.extend({
7209 initialize: function (marker) {
7210 this._marker = marker;
7211 },
7212
7213 addHooks: function () {
7214 var icon = this._marker._icon;
7215
7216 if (!this._draggable) {
7217 this._draggable = new Draggable(icon, icon, true);
7218 }
7219
7220 this._draggable.on({
7221 dragstart: this._onDragStart,
7222 predrag: this._onPreDrag,
7223 drag: this._onDrag,
7224 dragend: this._onDragEnd
7225 }, this).enable();
7226
7227 addClass(icon, 'leaflet-marker-draggable');
7228 },
7229
7230 removeHooks: function () {
7231 this._draggable.off({
7232 dragstart: this._onDragStart,
7233 predrag: this._onPreDrag,
7234 drag: this._onDrag,
7235 dragend: this._onDragEnd
7236 }, this).disable();
7237
7238 if (this._marker._icon) {
7239 removeClass(this._marker._icon, 'leaflet-marker-draggable');
7240 }
7241 },
7242
7243 moved: function () {
7244 return this._draggable && this._draggable._moved;
7245 },
7246
7247 _adjustPan: function (e) {
7248 var marker = this._marker,
7249 map = marker._map,
7250 speed = this._marker.options.autoPanSpeed,
7251 padding = this._marker.options.autoPanPadding,
7252 iconPos = getPosition(marker._icon),
7253 bounds = map.getPixelBounds(),
7254 origin = map.getPixelOrigin();
7255
7256 var panBounds = toBounds(
7257 bounds.min._subtract(origin).add(padding),
7258 bounds.max._subtract(origin).subtract(padding)
7259 );
7260
7261 if (!panBounds.contains(iconPos)) {
7262 // Compute incremental movement
7263 var movement = toPoint(
7264 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
7265 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
7266
7267 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
7268 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
7269 ).multiplyBy(speed);
7270
7271 map.panBy(movement, {animate: false});
7272
7273 this._draggable._newPos._add(movement);
7274 this._draggable._startPos._add(movement);
7275
7276 setPosition(marker._icon, this._draggable._newPos);
7277 this._onDrag(e);
7278
7279 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7280 }
7281 },
7282
7283 _onDragStart: function () {
7284 // @section Dragging events
7285 // @event dragstart: Event
7286 // Fired when the user starts dragging the marker.
7287
7288 // @event movestart: Event
7289 // Fired when the marker starts moving (because of dragging).
7290
7291 this._oldLatLng = this._marker.getLatLng();
7292
7293 // When using ES6 imports it could not be set when `Popup` was not imported as well
7294 this._marker.closePopup && this._marker.closePopup();
7295
7296 this._marker
7297 .fire('movestart')
7298 .fire('dragstart');
7299 },
7300
7301 _onPreDrag: function (e) {
7302 if (this._marker.options.autoPan) {
7303 cancelAnimFrame(this._panRequest);
7304 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7305 }
7306 },
7307
7308 _onDrag: function (e) {
7309 var marker = this._marker,
7310 shadow = marker._shadow,
7311 iconPos = getPosition(marker._icon),
7312 latlng = marker._map.layerPointToLatLng(iconPos);
7313
7314 // update shadow position
7315 if (shadow) {
7316 setPosition(shadow, iconPos);
7317 }
7318
7319 marker._latlng = latlng;
7320 e.latlng = latlng;
7321 e.oldLatLng = this._oldLatLng;
7322
7323 // @event drag: Event
7324 // Fired repeatedly while the user drags the marker.
7325 marker
7326 .fire('move', e)
7327 .fire('drag', e);
7328 },
7329
7330 _onDragEnd: function (e) {
7331 // @event dragend: DragEndEvent
7332 // Fired when the user stops dragging the marker.
7333
7334 cancelAnimFrame(this._panRequest);
7335
7336 // @event moveend: Event
7337 // Fired when the marker stops moving (because of dragging).
7338 delete this._oldLatLng;
7339 this._marker
7340 .fire('moveend')
7341 .fire('dragend', e);
7342 }
7343});
7344
7345/*
7346 * @class Marker
7347 * @inherits Interactive layer
7348 * @aka L.Marker
7349 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
7350 *
7351 * @example
7352 *
7353 * ```js
7354 * L.marker([50.5, 30.5]).addTo(map);
7355 * ```
7356 */
7357
7358var Marker = Layer.extend({
7359
7360 // @section
7361 // @aka Marker options
7362 options: {
7363 // @option icon: Icon = *
7364 // Icon instance to use for rendering the marker.
7365 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
7366 // If not specified, a common instance of `L.Icon.Default` is used.
7367 icon: new IconDefault(),
7368
7369 // Option inherited from "Interactive layer" abstract class
7370 interactive: true,
7371
7372 // @option keyboard: Boolean = true
7373 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
7374 keyboard: true,
7375
7376 // @option title: String = ''
7377 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
7378 title: '',
7379
7380 // @option alt: String = ''
7381 // Text for the `alt` attribute of the icon image (useful for accessibility).
7382 alt: '',
7383
7384 // @option zIndexOffset: Number = 0
7385 // 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).
7386 zIndexOffset: 0,
7387
7388 // @option opacity: Number = 1.0
7389 // The opacity of the marker.
7390 opacity: 1,
7391
7392 // @option riseOnHover: Boolean = false
7393 // If `true`, the marker will get on top of others when you hover the mouse over it.
7394 riseOnHover: false,
7395
7396 // @option riseOffset: Number = 250
7397 // The z-index offset used for the `riseOnHover` feature.
7398 riseOffset: 250,
7399
7400 // @option pane: String = 'markerPane'
7401 // `Map pane` where the markers icon will be added.
7402 pane: 'markerPane',
7403
7404 // @option shadowPane: String = 'shadowPane'
7405 // `Map pane` where the markers shadow will be added.
7406 shadowPane: 'shadowPane',
7407
7408 // @option bubblingMouseEvents: Boolean = false
7409 // When `true`, a mouse event on this marker will trigger the same event on the map
7410 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7411 bubblingMouseEvents: false,
7412
7413 // @section Draggable marker options
7414 // @option draggable: Boolean = false
7415 // Whether the marker is draggable with mouse/touch or not.
7416 draggable: false,
7417
7418 // @option autoPan: Boolean = false
7419 // Whether to pan the map when dragging this marker near its edge or not.
7420 autoPan: false,
7421
7422 // @option autoPanPadding: Point = Point(50, 50)
7423 // Distance (in pixels to the left/right and to the top/bottom) of the
7424 // map edge to start panning the map.
7425 autoPanPadding: [50, 50],
7426
7427 // @option autoPanSpeed: Number = 10
7428 // Number of pixels the map should pan by.
7429 autoPanSpeed: 10
7430 },
7431
7432 /* @section
7433 *
7434 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
7435 */
7436
7437 initialize: function (latlng, options) {
7438 setOptions(this, options);
7439 this._latlng = toLatLng(latlng);
7440 },
7441
7442 onAdd: function (map) {
7443 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
7444
7445 if (this._zoomAnimated) {
7446 map.on('zoomanim', this._animateZoom, this);
7447 }
7448
7449 this._initIcon();
7450 this.update();
7451 },
7452
7453 onRemove: function (map) {
7454 if (this.dragging && this.dragging.enabled()) {
7455 this.options.draggable = true;
7456 this.dragging.removeHooks();
7457 }
7458 delete this.dragging;
7459
7460 if (this._zoomAnimated) {
7461 map.off('zoomanim', this._animateZoom, this);
7462 }
7463
7464 this._removeIcon();
7465 this._removeShadow();
7466 },
7467
7468 getEvents: function () {
7469 return {
7470 zoom: this.update,
7471 viewreset: this.update
7472 };
7473 },
7474
7475 // @method getLatLng: LatLng
7476 // Returns the current geographical position of the marker.
7477 getLatLng: function () {
7478 return this._latlng;
7479 },
7480
7481 // @method setLatLng(latlng: LatLng): this
7482 // Changes the marker position to the given point.
7483 setLatLng: function (latlng) {
7484 var oldLatLng = this._latlng;
7485 this._latlng = toLatLng(latlng);
7486 this.update();
7487
7488 // @event move: Event
7489 // 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`.
7490 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7491 },
7492
7493 // @method setZIndexOffset(offset: Number): this
7494 // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
7495 setZIndexOffset: function (offset) {
7496 this.options.zIndexOffset = offset;
7497 return this.update();
7498 },
7499
7500 // @method getIcon: Icon
7501 // Returns the current icon used by the marker
7502 getIcon: function () {
7503 return this.options.icon;
7504 },
7505
7506 // @method setIcon(icon: Icon): this
7507 // Changes the marker icon.
7508 setIcon: function (icon) {
7509
7510 this.options.icon = icon;
7511
7512 if (this._map) {
7513 this._initIcon();
7514 this.update();
7515 }
7516
7517 if (this._popup) {
7518 this.bindPopup(this._popup, this._popup.options);
7519 }
7520
7521 return this;
7522 },
7523
7524 getElement: function () {
7525 return this._icon;
7526 },
7527
7528 update: function () {
7529
7530 if (this._icon && this._map) {
7531 var pos = this._map.latLngToLayerPoint(this._latlng).round();
7532 this._setPos(pos);
7533 }
7534
7535 return this;
7536 },
7537
7538 _initIcon: function () {
7539 var options = this.options,
7540 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
7541
7542 var icon = options.icon.createIcon(this._icon),
7543 addIcon = false;
7544
7545 // if we're not reusing the icon, remove the old one and init new one
7546 if (icon !== this._icon) {
7547 if (this._icon) {
7548 this._removeIcon();
7549 }
7550 addIcon = true;
7551
7552 if (options.title) {
7553 icon.title = options.title;
7554 }
7555
7556 if (icon.tagName === 'IMG') {
7557 icon.alt = options.alt || '';
7558 }
7559 }
7560
7561 addClass(icon, classToAdd);
7562
7563 if (options.keyboard) {
7564 icon.tabIndex = '0';
7565 }
7566
7567 this._icon = icon;
7568
7569 if (options.riseOnHover) {
7570 this.on({
7571 mouseover: this._bringToFront,
7572 mouseout: this._resetZIndex
7573 });
7574 }
7575
7576 var newShadow = options.icon.createShadow(this._shadow),
7577 addShadow = false;
7578
7579 if (newShadow !== this._shadow) {
7580 this._removeShadow();
7581 addShadow = true;
7582 }
7583
7584 if (newShadow) {
7585 addClass(newShadow, classToAdd);
7586 newShadow.alt = '';
7587 }
7588 this._shadow = newShadow;
7589
7590
7591 if (options.opacity < 1) {
7592 this._updateOpacity();
7593 }
7594
7595
7596 if (addIcon) {
7597 this.getPane().appendChild(this._icon);
7598 }
7599 this._initInteraction();
7600 if (newShadow && addShadow) {
7601 this.getPane(options.shadowPane).appendChild(this._shadow);
7602 }
7603 },
7604
7605 _removeIcon: function () {
7606 if (this.options.riseOnHover) {
7607 this.off({
7608 mouseover: this._bringToFront,
7609 mouseout: this._resetZIndex
7610 });
7611 }
7612
7613 remove(this._icon);
7614 this.removeInteractiveTarget(this._icon);
7615
7616 this._icon = null;
7617 },
7618
7619 _removeShadow: function () {
7620 if (this._shadow) {
7621 remove(this._shadow);
7622 }
7623 this._shadow = null;
7624 },
7625
7626 _setPos: function (pos) {
7627
7628 if (this._icon) {
7629 setPosition(this._icon, pos);
7630 }
7631
7632 if (this._shadow) {
7633 setPosition(this._shadow, pos);
7634 }
7635
7636 this._zIndex = pos.y + this.options.zIndexOffset;
7637
7638 this._resetZIndex();
7639 },
7640
7641 _updateZIndex: function (offset) {
7642 if (this._icon) {
7643 this._icon.style.zIndex = this._zIndex + offset;
7644 }
7645 },
7646
7647 _animateZoom: function (opt) {
7648 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
7649
7650 this._setPos(pos);
7651 },
7652
7653 _initInteraction: function () {
7654
7655 if (!this.options.interactive) { return; }
7656
7657 addClass(this._icon, 'leaflet-interactive');
7658
7659 this.addInteractiveTarget(this._icon);
7660
7661 if (MarkerDrag) {
7662 var draggable = this.options.draggable;
7663 if (this.dragging) {
7664 draggable = this.dragging.enabled();
7665 this.dragging.disable();
7666 }
7667
7668 this.dragging = new MarkerDrag(this);
7669
7670 if (draggable) {
7671 this.dragging.enable();
7672 }
7673 }
7674 },
7675
7676 // @method setOpacity(opacity: Number): this
7677 // Changes the opacity of the marker.
7678 setOpacity: function (opacity) {
7679 this.options.opacity = opacity;
7680 if (this._map) {
7681 this._updateOpacity();
7682 }
7683
7684 return this;
7685 },
7686
7687 _updateOpacity: function () {
7688 var opacity = this.options.opacity;
7689
7690 if (this._icon) {
7691 setOpacity(this._icon, opacity);
7692 }
7693
7694 if (this._shadow) {
7695 setOpacity(this._shadow, opacity);
7696 }
7697 },
7698
7699 _bringToFront: function () {
7700 this._updateZIndex(this.options.riseOffset);
7701 },
7702
7703 _resetZIndex: function () {
7704 this._updateZIndex(0);
7705 },
7706
7707 _getPopupAnchor: function () {
7708 return this.options.icon.options.popupAnchor;
7709 },
7710
7711 _getTooltipAnchor: function () {
7712 return this.options.icon.options.tooltipAnchor;
7713 }
7714});
7715
7716
7717// factory L.marker(latlng: LatLng, options? : Marker options)
7718
7719// @factory L.marker(latlng: LatLng, options? : Marker options)
7720// Instantiates a Marker object given a geographical point and optionally an options object.
7721function marker(latlng, options) {
7722 return new Marker(latlng, options);
7723}
7724
7725/*
7726 * @class Path
7727 * @aka L.Path
7728 * @inherits Interactive layer
7729 *
7730 * An abstract class that contains options and constants shared between vector
7731 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
7732 */
7733
7734var Path = Layer.extend({
7735
7736 // @section
7737 // @aka Path options
7738 options: {
7739 // @option stroke: Boolean = true
7740 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
7741 stroke: true,
7742
7743 // @option color: String = '#3388ff'
7744 // Stroke color
7745 color: '#3388ff',
7746
7747 // @option weight: Number = 3
7748 // Stroke width in pixels
7749 weight: 3,
7750
7751 // @option opacity: Number = 1.0
7752 // Stroke opacity
7753 opacity: 1,
7754
7755 // @option lineCap: String= 'round'
7756 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
7757 lineCap: 'round',
7758
7759 // @option lineJoin: String = 'round'
7760 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
7761 lineJoin: 'round',
7762
7763 // @option dashArray: String = null
7764 // 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).
7765 dashArray: null,
7766
7767 // @option dashOffset: String = null
7768 // 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).
7769 dashOffset: null,
7770
7771 // @option fill: Boolean = depends
7772 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
7773 fill: false,
7774
7775 // @option fillColor: String = *
7776 // Fill color. Defaults to the value of the [`color`](#path-color) option
7777 fillColor: null,
7778
7779 // @option fillOpacity: Number = 0.2
7780 // Fill opacity.
7781 fillOpacity: 0.2,
7782
7783 // @option fillRule: String = 'evenodd'
7784 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
7785 fillRule: 'evenodd',
7786
7787 // className: '',
7788
7789 // Option inherited from "Interactive layer" abstract class
7790 interactive: true,
7791
7792 // @option bubblingMouseEvents: Boolean = true
7793 // When `true`, a mouse event on this path will trigger the same event on the map
7794 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7795 bubblingMouseEvents: true
7796 },
7797
7798 beforeAdd: function (map) {
7799 // Renderer is set here because we need to call renderer.getEvents
7800 // before this.getEvents.
7801 this._renderer = map.getRenderer(this);
7802 },
7803
7804 onAdd: function () {
7805 this._renderer._initPath(this);
7806 this._reset();
7807 this._renderer._addPath(this);
7808 },
7809
7810 onRemove: function () {
7811 this._renderer._removePath(this);
7812 },
7813
7814 // @method redraw(): this
7815 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
7816 redraw: function () {
7817 if (this._map) {
7818 this._renderer._updatePath(this);
7819 }
7820 return this;
7821 },
7822
7823 // @method setStyle(style: Path options): this
7824 // Changes the appearance of a Path based on the options in the `Path options` object.
7825 setStyle: function (style) {
7826 setOptions(this, style);
7827 if (this._renderer) {
7828 this._renderer._updateStyle(this);
7829 if (this.options.stroke && style && Object.prototype.hasOwnProperty.call(style, 'weight')) {
7830 this._updateBounds();
7831 }
7832 }
7833 return this;
7834 },
7835
7836 // @method bringToFront(): this
7837 // Brings the layer to the top of all path layers.
7838 bringToFront: function () {
7839 if (this._renderer) {
7840 this._renderer._bringToFront(this);
7841 }
7842 return this;
7843 },
7844
7845 // @method bringToBack(): this
7846 // Brings the layer to the bottom of all path layers.
7847 bringToBack: function () {
7848 if (this._renderer) {
7849 this._renderer._bringToBack(this);
7850 }
7851 return this;
7852 },
7853
7854 getElement: function () {
7855 return this._path;
7856 },
7857
7858 _reset: function () {
7859 // defined in child classes
7860 this._project();
7861 this._update();
7862 },
7863
7864 _clickTolerance: function () {
7865 // used when doing hit detection for Canvas layers
7866 return (this.options.stroke ? this.options.weight / 2 : 0) + this._renderer.options.tolerance;
7867 }
7868});
7869
7870/*
7871 * @class CircleMarker
7872 * @aka L.CircleMarker
7873 * @inherits Path
7874 *
7875 * A circle of a fixed size with radius specified in pixels. Extends `Path`.
7876 */
7877
7878var CircleMarker = Path.extend({
7879
7880 // @section
7881 // @aka CircleMarker options
7882 options: {
7883 fill: true,
7884
7885 // @option radius: Number = 10
7886 // Radius of the circle marker, in pixels
7887 radius: 10
7888 },
7889
7890 initialize: function (latlng, options) {
7891 setOptions(this, options);
7892 this._latlng = toLatLng(latlng);
7893 this._radius = this.options.radius;
7894 },
7895
7896 // @method setLatLng(latLng: LatLng): this
7897 // Sets the position of a circle marker to a new location.
7898 setLatLng: function (latlng) {
7899 var oldLatLng = this._latlng;
7900 this._latlng = toLatLng(latlng);
7901 this.redraw();
7902
7903 // @event move: Event
7904 // Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
7905 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7906 },
7907
7908 // @method getLatLng(): LatLng
7909 // Returns the current geographical position of the circle marker
7910 getLatLng: function () {
7911 return this._latlng;
7912 },
7913
7914 // @method setRadius(radius: Number): this
7915 // Sets the radius of a circle marker. Units are in pixels.
7916 setRadius: function (radius) {
7917 this.options.radius = this._radius = radius;
7918 return this.redraw();
7919 },
7920
7921 // @method getRadius(): Number
7922 // Returns the current radius of the circle
7923 getRadius: function () {
7924 return this._radius;
7925 },
7926
7927 setStyle : function (options) {
7928 var radius = options && options.radius || this._radius;
7929 Path.prototype.setStyle.call(this, options);
7930 this.setRadius(radius);
7931 return this;
7932 },
7933
7934 _project: function () {
7935 this._point = this._map.latLngToLayerPoint(this._latlng);
7936 this._updateBounds();
7937 },
7938
7939 _updateBounds: function () {
7940 var r = this._radius,
7941 r2 = this._radiusY || r,
7942 w = this._clickTolerance(),
7943 p = [r + w, r2 + w];
7944 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
7945 },
7946
7947 _update: function () {
7948 if (this._map) {
7949 this._updatePath();
7950 }
7951 },
7952
7953 _updatePath: function () {
7954 this._renderer._updateCircle(this);
7955 },
7956
7957 _empty: function () {
7958 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
7959 },
7960
7961 // Needed by the `Canvas` renderer for interactivity
7962 _containsPoint: function (p) {
7963 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
7964 }
7965});
7966
7967
7968// @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
7969// Instantiates a circle marker object given a geographical point, and an optional options object.
7970function circleMarker(latlng, options) {
7971 return new CircleMarker(latlng, options);
7972}
7973
7974/*
7975 * @class Circle
7976 * @aka L.Circle
7977 * @inherits CircleMarker
7978 *
7979 * A class for drawing circle overlays on a map. Extends `CircleMarker`.
7980 *
7981 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
7982 *
7983 * @example
7984 *
7985 * ```js
7986 * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
7987 * ```
7988 */
7989
7990var Circle = CircleMarker.extend({
7991
7992 initialize: function (latlng, options, legacyOptions) {
7993 if (typeof options === 'number') {
7994 // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
7995 options = extend({}, legacyOptions, {radius: options});
7996 }
7997 setOptions(this, options);
7998 this._latlng = toLatLng(latlng);
7999
8000 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
8001
8002 // @section
8003 // @aka Circle options
8004 // @option radius: Number; Radius of the circle, in meters.
8005 this._mRadius = this.options.radius;
8006 },
8007
8008 // @method setRadius(radius: Number): this
8009 // Sets the radius of a circle. Units are in meters.
8010 setRadius: function (radius) {
8011 this._mRadius = radius;
8012 return this.redraw();
8013 },
8014
8015 // @method getRadius(): Number
8016 // Returns the current radius of a circle. Units are in meters.
8017 getRadius: function () {
8018 return this._mRadius;
8019 },
8020
8021 // @method getBounds(): LatLngBounds
8022 // Returns the `LatLngBounds` of the path.
8023 getBounds: function () {
8024 var half = [this._radius, this._radiusY || this._radius];
8025
8026 return new LatLngBounds(
8027 this._map.layerPointToLatLng(this._point.subtract(half)),
8028 this._map.layerPointToLatLng(this._point.add(half)));
8029 },
8030
8031 setStyle: Path.prototype.setStyle,
8032
8033 _project: function () {
8034
8035 var lng = this._latlng.lng,
8036 lat = this._latlng.lat,
8037 map = this._map,
8038 crs = map.options.crs;
8039
8040 if (crs.distance === Earth.distance) {
8041 var d = Math.PI / 180,
8042 latR = (this._mRadius / Earth.R) / d,
8043 top = map.project([lat + latR, lng]),
8044 bottom = map.project([lat - latR, lng]),
8045 p = top.add(bottom).divideBy(2),
8046 lat2 = map.unproject(p).lat,
8047 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
8048 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
8049
8050 if (isNaN(lngR) || lngR === 0) {
8051 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
8052 }
8053
8054 this._point = p.subtract(map.getPixelOrigin());
8055 this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
8056 this._radiusY = p.y - top.y;
8057
8058 } else {
8059 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
8060
8061 this._point = map.latLngToLayerPoint(this._latlng);
8062 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
8063 }
8064
8065 this._updateBounds();
8066 }
8067});
8068
8069// @factory L.circle(latlng: LatLng, options?: Circle options)
8070// Instantiates a circle object given a geographical point, and an options object
8071// which contains the circle radius.
8072// @alternative
8073// @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
8074// Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
8075// Do not use in new applications or plugins.
8076function circle(latlng, options, legacyOptions) {
8077 return new Circle(latlng, options, legacyOptions);
8078}
8079
8080/*
8081 * @class Polyline
8082 * @aka L.Polyline
8083 * @inherits Path
8084 *
8085 * A class for drawing polyline overlays on a map. Extends `Path`.
8086 *
8087 * @example
8088 *
8089 * ```js
8090 * // create a red polyline from an array of LatLng points
8091 * var latlngs = [
8092 * [45.51, -122.68],
8093 * [37.77, -122.43],
8094 * [34.04, -118.2]
8095 * ];
8096 *
8097 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
8098 *
8099 * // zoom the map to the polyline
8100 * map.fitBounds(polyline.getBounds());
8101 * ```
8102 *
8103 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
8104 *
8105 * ```js
8106 * // create a red polyline from an array of arrays of LatLng points
8107 * var latlngs = [
8108 * [[45.51, -122.68],
8109 * [37.77, -122.43],
8110 * [34.04, -118.2]],
8111 * [[40.78, -73.91],
8112 * [41.83, -87.62],
8113 * [32.76, -96.72]]
8114 * ];
8115 * ```
8116 */
8117
8118
8119var Polyline = Path.extend({
8120
8121 // @section
8122 // @aka Polyline options
8123 options: {
8124 // @option smoothFactor: Number = 1.0
8125 // How much to simplify the polyline on each zoom level. More means
8126 // better performance and smoother look, and less means more accurate representation.
8127 smoothFactor: 1.0,
8128
8129 // @option noClip: Boolean = false
8130 // Disable polyline clipping.
8131 noClip: false
8132 },
8133
8134 initialize: function (latlngs, options) {
8135 setOptions(this, options);
8136 this._setLatLngs(latlngs);
8137 },
8138
8139 // @method getLatLngs(): LatLng[]
8140 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8141 getLatLngs: function () {
8142 return this._latlngs;
8143 },
8144
8145 // @method setLatLngs(latlngs: LatLng[]): this
8146 // Replaces all the points in the polyline with the given array of geographical points.
8147 setLatLngs: function (latlngs) {
8148 this._setLatLngs(latlngs);
8149 return this.redraw();
8150 },
8151
8152 // @method isEmpty(): Boolean
8153 // Returns `true` if the Polyline has no LatLngs.
8154 isEmpty: function () {
8155 return !this._latlngs.length;
8156 },
8157
8158 // @method closestLayerPoint(p: Point): Point
8159 // Returns the point closest to `p` on the Polyline.
8160 closestLayerPoint: function (p) {
8161 var minDistance = Infinity,
8162 minPoint = null,
8163 closest = _sqClosestPointOnSegment,
8164 p1, p2;
8165
8166 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8167 var points = this._parts[j];
8168
8169 for (var i = 1, len = points.length; i < len; i++) {
8170 p1 = points[i - 1];
8171 p2 = points[i];
8172
8173 var sqDist = closest(p, p1, p2, true);
8174
8175 if (sqDist < minDistance) {
8176 minDistance = sqDist;
8177 minPoint = closest(p, p1, p2);
8178 }
8179 }
8180 }
8181 if (minPoint) {
8182 minPoint.distance = Math.sqrt(minDistance);
8183 }
8184 return minPoint;
8185 },
8186
8187 // @method getCenter(): LatLng
8188 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
8189 getCenter: function () {
8190 // throws error when not yet added to map as this center calculation requires projected coordinates
8191 if (!this._map) {
8192 throw new Error('Must add layer to map before using getCenter()');
8193 }
8194
8195 var i, halfDist, segDist, dist, p1, p2, ratio,
8196 points = this._rings[0],
8197 len = points.length;
8198
8199 if (!len) { return null; }
8200
8201 // polyline centroid algorithm; only uses the first ring if there are multiple
8202
8203 for (i = 0, halfDist = 0; i < len - 1; i++) {
8204 halfDist += points[i].distanceTo(points[i + 1]) / 2;
8205 }
8206
8207 // The line is so small in the current view that all points are on the same pixel.
8208 if (halfDist === 0) {
8209 return this._map.layerPointToLatLng(points[0]);
8210 }
8211
8212 for (i = 0, dist = 0; i < len - 1; i++) {
8213 p1 = points[i];
8214 p2 = points[i + 1];
8215 segDist = p1.distanceTo(p2);
8216 dist += segDist;
8217
8218 if (dist > halfDist) {
8219 ratio = (dist - halfDist) / segDist;
8220 return this._map.layerPointToLatLng([
8221 p2.x - ratio * (p2.x - p1.x),
8222 p2.y - ratio * (p2.y - p1.y)
8223 ]);
8224 }
8225 }
8226 },
8227
8228 // @method getBounds(): LatLngBounds
8229 // Returns the `LatLngBounds` of the path.
8230 getBounds: function () {
8231 return this._bounds;
8232 },
8233
8234 // @method addLatLng(latlng: LatLng, latlngs?: LatLng[]): this
8235 // Adds a given point to the polyline. By default, adds to the first ring of
8236 // the polyline in case of a multi-polyline, but can be overridden by passing
8237 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8238 addLatLng: function (latlng, latlngs) {
8239 latlngs = latlngs || this._defaultShape();
8240 latlng = toLatLng(latlng);
8241 latlngs.push(latlng);
8242 this._bounds.extend(latlng);
8243 return this.redraw();
8244 },
8245
8246 _setLatLngs: function (latlngs) {
8247 this._bounds = new LatLngBounds();
8248 this._latlngs = this._convertLatLngs(latlngs);
8249 },
8250
8251 _defaultShape: function () {
8252 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
8253 },
8254
8255 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8256 _convertLatLngs: function (latlngs) {
8257 var result = [],
8258 flat = isFlat(latlngs);
8259
8260 for (var i = 0, len = latlngs.length; i < len; i++) {
8261 if (flat) {
8262 result[i] = toLatLng(latlngs[i]);
8263 this._bounds.extend(result[i]);
8264 } else {
8265 result[i] = this._convertLatLngs(latlngs[i]);
8266 }
8267 }
8268
8269 return result;
8270 },
8271
8272 _project: function () {
8273 var pxBounds = new Bounds();
8274 this._rings = [];
8275 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8276
8277 if (this._bounds.isValid() && pxBounds.isValid()) {
8278 this._rawPxBounds = pxBounds;
8279 this._updateBounds();
8280 }
8281 },
8282
8283 _updateBounds: function () {
8284 var w = this._clickTolerance(),
8285 p = new Point(w, w);
8286 this._pxBounds = new Bounds([
8287 this._rawPxBounds.min.subtract(p),
8288 this._rawPxBounds.max.add(p)
8289 ]);
8290 },
8291
8292 // recursively turns latlngs into a set of rings with projected coordinates
8293 _projectLatlngs: function (latlngs, result, projectedBounds) {
8294 var flat = latlngs[0] instanceof LatLng,
8295 len = latlngs.length,
8296 i, ring;
8297
8298 if (flat) {
8299 ring = [];
8300 for (i = 0; i < len; i++) {
8301 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8302 projectedBounds.extend(ring[i]);
8303 }
8304 result.push(ring);
8305 } else {
8306 for (i = 0; i < len; i++) {
8307 this._projectLatlngs(latlngs[i], result, projectedBounds);
8308 }
8309 }
8310 },
8311
8312 // clip polyline by renderer bounds so that we have less to render for performance
8313 _clipPoints: function () {
8314 var bounds = this._renderer._bounds;
8315
8316 this._parts = [];
8317 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8318 return;
8319 }
8320
8321 if (this.options.noClip) {
8322 this._parts = this._rings;
8323 return;
8324 }
8325
8326 var parts = this._parts,
8327 i, j, k, len, len2, segment, points;
8328
8329 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8330 points = this._rings[i];
8331
8332 for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8333 segment = clipSegment(points[j], points[j + 1], bounds, j, true);
8334
8335 if (!segment) { continue; }
8336
8337 parts[k] = parts[k] || [];
8338 parts[k].push(segment[0]);
8339
8340 // if segment goes out of screen, or it's the last one, it's the end of the line part
8341 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8342 parts[k].push(segment[1]);
8343 k++;
8344 }
8345 }
8346 }
8347 },
8348
8349 // simplify each clipped part of the polyline for performance
8350 _simplifyPoints: function () {
8351 var parts = this._parts,
8352 tolerance = this.options.smoothFactor;
8353
8354 for (var i = 0, len = parts.length; i < len; i++) {
8355 parts[i] = simplify(parts[i], tolerance);
8356 }
8357 },
8358
8359 _update: function () {
8360 if (!this._map) { return; }
8361
8362 this._clipPoints();
8363 this._simplifyPoints();
8364 this._updatePath();
8365 },
8366
8367 _updatePath: function () {
8368 this._renderer._updatePoly(this);
8369 },
8370
8371 // Needed by the `Canvas` renderer for interactivity
8372 _containsPoint: function (p, closed) {
8373 var i, j, k, len, len2, part,
8374 w = this._clickTolerance();
8375
8376 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8377
8378 // hit detection for polylines
8379 for (i = 0, len = this._parts.length; i < len; i++) {
8380 part = this._parts[i];
8381
8382 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8383 if (!closed && (j === 0)) { continue; }
8384
8385 if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
8386 return true;
8387 }
8388 }
8389 }
8390 return false;
8391 }
8392});
8393
8394// @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8395// Instantiates a polyline object given an array of geographical points and
8396// optionally an options object. You can create a `Polyline` object with
8397// multiple separate lines (`MultiPolyline`) by passing an array of arrays
8398// of geographic points.
8399function polyline(latlngs, options) {
8400 return new Polyline(latlngs, options);
8401}
8402
8403// Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
8404Polyline._flat = _flat;
8405
8406/*
8407 * @class Polygon
8408 * @aka L.Polygon
8409 * @inherits Polyline
8410 *
8411 * A class for drawing polygon overlays on a map. Extends `Polyline`.
8412 *
8413 * 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.
8414 *
8415 *
8416 * @example
8417 *
8418 * ```js
8419 * // create a red polygon from an array of LatLng points
8420 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
8421 *
8422 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8423 *
8424 * // zoom the map to the polygon
8425 * map.fitBounds(polygon.getBounds());
8426 * ```
8427 *
8428 * 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:
8429 *
8430 * ```js
8431 * var latlngs = [
8432 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8433 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8434 * ];
8435 * ```
8436 *
8437 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8438 *
8439 * ```js
8440 * var latlngs = [
8441 * [ // first polygon
8442 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8443 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8444 * ],
8445 * [ // second polygon
8446 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
8447 * ]
8448 * ];
8449 * ```
8450 */
8451
8452var Polygon = Polyline.extend({
8453
8454 options: {
8455 fill: true
8456 },
8457
8458 isEmpty: function () {
8459 return !this._latlngs.length || !this._latlngs[0].length;
8460 },
8461
8462 getCenter: function () {
8463 // throws error when not yet added to map as this center calculation requires projected coordinates
8464 if (!this._map) {
8465 throw new Error('Must add layer to map before using getCenter()');
8466 }
8467
8468 var i, j, p1, p2, f, area, x, y, center,
8469 points = this._rings[0],
8470 len = points.length;
8471
8472 if (!len) { return null; }
8473
8474 // polygon centroid algorithm; only uses the first ring if there are multiple
8475
8476 area = x = y = 0;
8477
8478 for (i = 0, j = len - 1; i < len; j = i++) {
8479 p1 = points[i];
8480 p2 = points[j];
8481
8482 f = p1.y * p2.x - p2.y * p1.x;
8483 x += (p1.x + p2.x) * f;
8484 y += (p1.y + p2.y) * f;
8485 area += f * 3;
8486 }
8487
8488 if (area === 0) {
8489 // Polygon is so small that all points are on same pixel.
8490 center = points[0];
8491 } else {
8492 center = [x / area, y / area];
8493 }
8494 return this._map.layerPointToLatLng(center);
8495 },
8496
8497 _convertLatLngs: function (latlngs) {
8498 var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
8499 len = result.length;
8500
8501 // remove last point if it equals first one
8502 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
8503 result.pop();
8504 }
8505 return result;
8506 },
8507
8508 _setLatLngs: function (latlngs) {
8509 Polyline.prototype._setLatLngs.call(this, latlngs);
8510 if (isFlat(this._latlngs)) {
8511 this._latlngs = [this._latlngs];
8512 }
8513 },
8514
8515 _defaultShape: function () {
8516 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8517 },
8518
8519 _clipPoints: function () {
8520 // polygons need a different clipping algorithm so we redefine that
8521
8522 var bounds = this._renderer._bounds,
8523 w = this.options.weight,
8524 p = new Point(w, w);
8525
8526 // increase clip padding by stroke width to avoid stroke on clip edges
8527 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
8528
8529 this._parts = [];
8530 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8531 return;
8532 }
8533
8534 if (this.options.noClip) {
8535 this._parts = this._rings;
8536 return;
8537 }
8538
8539 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8540 clipped = clipPolygon(this._rings[i], bounds, true);
8541 if (clipped.length) {
8542 this._parts.push(clipped);
8543 }
8544 }
8545 },
8546
8547 _updatePath: function () {
8548 this._renderer._updatePoly(this, true);
8549 },
8550
8551 // Needed by the `Canvas` renderer for interactivity
8552 _containsPoint: function (p) {
8553 var inside = false,
8554 part, p1, p2, i, j, k, len, len2;
8555
8556 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8557
8558 // ray casting algorithm for detecting if point is in polygon
8559 for (i = 0, len = this._parts.length; i < len; i++) {
8560 part = this._parts[i];
8561
8562 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8563 p1 = part[j];
8564 p2 = part[k];
8565
8566 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)) {
8567 inside = !inside;
8568 }
8569 }
8570 }
8571
8572 // also check if it's on polygon stroke
8573 return inside || Polyline.prototype._containsPoint.call(this, p, true);
8574 }
8575
8576});
8577
8578
8579// @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8580function polygon(latlngs, options) {
8581 return new Polygon(latlngs, options);
8582}
8583
8584/*
8585 * @class GeoJSON
8586 * @aka L.GeoJSON
8587 * @inherits FeatureGroup
8588 *
8589 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
8590 * GeoJSON data and display it on the map. Extends `FeatureGroup`.
8591 *
8592 * @example
8593 *
8594 * ```js
8595 * L.geoJSON(data, {
8596 * style: function (feature) {
8597 * return {color: feature.properties.color};
8598 * }
8599 * }).bindPopup(function (layer) {
8600 * return layer.feature.properties.description;
8601 * }).addTo(map);
8602 * ```
8603 */
8604
8605var GeoJSON = FeatureGroup.extend({
8606
8607 /* @section
8608 * @aka GeoJSON options
8609 *
8610 * @option pointToLayer: Function = *
8611 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
8612 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
8613 * The default is to spawn a default `Marker`:
8614 * ```js
8615 * function(geoJsonPoint, latlng) {
8616 * return L.marker(latlng);
8617 * }
8618 * ```
8619 *
8620 * @option style: Function = *
8621 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
8622 * called internally when data is added.
8623 * The default value is to not override any defaults:
8624 * ```js
8625 * function (geoJsonFeature) {
8626 * return {}
8627 * }
8628 * ```
8629 *
8630 * @option onEachFeature: Function = *
8631 * A `Function` that will be called once for each created `Feature`, after it has
8632 * been created and styled. Useful for attaching events and popups to features.
8633 * The default is to do nothing with the newly created layers:
8634 * ```js
8635 * function (feature, layer) {}
8636 * ```
8637 *
8638 * @option filter: Function = *
8639 * A `Function` that will be used to decide whether to include a feature or not.
8640 * The default is to include all features:
8641 * ```js
8642 * function (geoJsonFeature) {
8643 * return true;
8644 * }
8645 * ```
8646 * Note: dynamically changing the `filter` option will have effect only on newly
8647 * added data. It will _not_ re-evaluate already included features.
8648 *
8649 * @option coordsToLatLng: Function = *
8650 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
8651 * The default is the `coordsToLatLng` static method.
8652 *
8653 * @option markersInheritOptions: Boolean = false
8654 * Whether default Markers for "Point" type Features inherit from group options.
8655 */
8656
8657 initialize: function (geojson, options) {
8658 setOptions(this, options);
8659
8660 this._layers = {};
8661
8662 if (geojson) {
8663 this.addData(geojson);
8664 }
8665 },
8666
8667 // @method addData( <GeoJSON> data ): this
8668 // Adds a GeoJSON object to the layer.
8669 addData: function (geojson) {
8670 var features = isArray(geojson) ? geojson : geojson.features,
8671 i, len, feature;
8672
8673 if (features) {
8674 for (i = 0, len = features.length; i < len; i++) {
8675 // only add this if geometry or geometries are set and not null
8676 feature = features[i];
8677 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
8678 this.addData(feature);
8679 }
8680 }
8681 return this;
8682 }
8683
8684 var options = this.options;
8685
8686 if (options.filter && !options.filter(geojson)) { return this; }
8687
8688 var layer = geometryToLayer(geojson, options);
8689 if (!layer) {
8690 return this;
8691 }
8692 layer.feature = asFeature(geojson);
8693
8694 layer.defaultOptions = layer.options;
8695 this.resetStyle(layer);
8696
8697 if (options.onEachFeature) {
8698 options.onEachFeature(geojson, layer);
8699 }
8700
8701 return this.addLayer(layer);
8702 },
8703
8704 // @method resetStyle( <Path> layer? ): this
8705 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
8706 // If `layer` is omitted, the style of all features in the current layer is reset.
8707 resetStyle: function (layer) {
8708 if (layer === undefined) {
8709 return this.eachLayer(this.resetStyle, this);
8710 }
8711 // reset any custom styles
8712 layer.options = extend({}, layer.defaultOptions);
8713 this._setLayerStyle(layer, this.options.style);
8714 return this;
8715 },
8716
8717 // @method setStyle( <Function> style ): this
8718 // Changes styles of GeoJSON vector layers with the given style function.
8719 setStyle: function (style) {
8720 return this.eachLayer(function (layer) {
8721 this._setLayerStyle(layer, style);
8722 }, this);
8723 },
8724
8725 _setLayerStyle: function (layer, style) {
8726 if (layer.setStyle) {
8727 if (typeof style === 'function') {
8728 style = style(layer.feature);
8729 }
8730 layer.setStyle(style);
8731 }
8732 }
8733});
8734
8735// @section
8736// There are several static functions which can be called without instantiating L.GeoJSON:
8737
8738// @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
8739// Creates a `Layer` from a given GeoJSON feature. Can use a custom
8740// [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
8741// functions if provided as options.
8742function geometryToLayer(geojson, options) {
8743
8744 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
8745 coords = geometry ? geometry.coordinates : null,
8746 layers = [],
8747 pointToLayer = options && options.pointToLayer,
8748 _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
8749 latlng, latlngs, i, len;
8750
8751 if (!coords && !geometry) {
8752 return null;
8753 }
8754
8755 switch (geometry.type) {
8756 case 'Point':
8757 latlng = _coordsToLatLng(coords);
8758 return _pointToLayer(pointToLayer, geojson, latlng, options);
8759
8760 case 'MultiPoint':
8761 for (i = 0, len = coords.length; i < len; i++) {
8762 latlng = _coordsToLatLng(coords[i]);
8763 layers.push(_pointToLayer(pointToLayer, geojson, latlng, options));
8764 }
8765 return new FeatureGroup(layers);
8766
8767 case 'LineString':
8768 case 'MultiLineString':
8769 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
8770 return new Polyline(latlngs, options);
8771
8772 case 'Polygon':
8773 case 'MultiPolygon':
8774 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
8775 return new Polygon(latlngs, options);
8776
8777 case 'GeometryCollection':
8778 for (i = 0, len = geometry.geometries.length; i < len; i++) {
8779 var layer = geometryToLayer({
8780 geometry: geometry.geometries[i],
8781 type: 'Feature',
8782 properties: geojson.properties
8783 }, options);
8784
8785 if (layer) {
8786 layers.push(layer);
8787 }
8788 }
8789 return new FeatureGroup(layers);
8790
8791 default:
8792 throw new Error('Invalid GeoJSON object.');
8793 }
8794}
8795
8796function _pointToLayer(pointToLayerFn, geojson, latlng, options) {
8797 return pointToLayerFn ?
8798 pointToLayerFn(geojson, latlng) :
8799 new Marker(latlng, options && options.markersInheritOptions && options);
8800}
8801
8802// @function coordsToLatLng(coords: Array): LatLng
8803// Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
8804// or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
8805function coordsToLatLng(coords) {
8806 return new LatLng(coords[1], coords[0], coords[2]);
8807}
8808
8809// @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
8810// Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
8811// `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
8812// Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
8813function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
8814 var latlngs = [];
8815
8816 for (var i = 0, len = coords.length, latlng; i < len; i++) {
8817 latlng = levelsDeep ?
8818 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
8819 (_coordsToLatLng || coordsToLatLng)(coords[i]);
8820
8821 latlngs.push(latlng);
8822 }
8823
8824 return latlngs;
8825}
8826
8827// @function latLngToCoords(latlng: LatLng, precision?: Number): Array
8828// Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
8829function latLngToCoords(latlng, precision) {
8830 precision = typeof precision === 'number' ? precision : 6;
8831 return latlng.alt !== undefined ?
8832 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
8833 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
8834}
8835
8836// @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
8837// Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
8838// `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.
8839function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
8840 var coords = [];
8841
8842 for (var i = 0, len = latlngs.length; i < len; i++) {
8843 coords.push(levelsDeep ?
8844 latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) :
8845 latLngToCoords(latlngs[i], precision));
8846 }
8847
8848 if (!levelsDeep && closed) {
8849 coords.push(coords[0]);
8850 }
8851
8852 return coords;
8853}
8854
8855function getFeature(layer, newGeometry) {
8856 return layer.feature ?
8857 extend({}, layer.feature, {geometry: newGeometry}) :
8858 asFeature(newGeometry);
8859}
8860
8861// @function asFeature(geojson: Object): Object
8862// Normalize GeoJSON geometries/features into GeoJSON features.
8863function asFeature(geojson) {
8864 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
8865 return geojson;
8866 }
8867
8868 return {
8869 type: 'Feature',
8870 properties: {},
8871 geometry: geojson
8872 };
8873}
8874
8875var PointToGeoJSON = {
8876 toGeoJSON: function (precision) {
8877 return getFeature(this, {
8878 type: 'Point',
8879 coordinates: latLngToCoords(this.getLatLng(), precision)
8880 });
8881 }
8882};
8883
8884// @namespace Marker
8885// @section Other methods
8886// @method toGeoJSON(precision?: Number): Object
8887// `precision` is the number of decimal places for coordinates.
8888// The default value is 6 places.
8889// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
8890Marker.include(PointToGeoJSON);
8891
8892// @namespace CircleMarker
8893// @method toGeoJSON(precision?: Number): Object
8894// `precision` is the number of decimal places for coordinates.
8895// The default value is 6 places.
8896// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
8897Circle.include(PointToGeoJSON);
8898CircleMarker.include(PointToGeoJSON);
8899
8900
8901// @namespace Polyline
8902// @method toGeoJSON(precision?: Number): Object
8903// `precision` is the number of decimal places for coordinates.
8904// The default value is 6 places.
8905// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
8906Polyline.include({
8907 toGeoJSON: function (precision) {
8908 var multi = !isFlat(this._latlngs);
8909
8910 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
8911
8912 return getFeature(this, {
8913 type: (multi ? 'Multi' : '') + 'LineString',
8914 coordinates: coords
8915 });
8916 }
8917});
8918
8919// @namespace Polygon
8920// @method toGeoJSON(precision?: Number): Object
8921// `precision` is the number of decimal places for coordinates.
8922// The default value is 6 places.
8923// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
8924Polygon.include({
8925 toGeoJSON: function (precision) {
8926 var holes = !isFlat(this._latlngs),
8927 multi = holes && !isFlat(this._latlngs[0]);
8928
8929 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
8930
8931 if (!holes) {
8932 coords = [coords];
8933 }
8934
8935 return getFeature(this, {
8936 type: (multi ? 'Multi' : '') + 'Polygon',
8937 coordinates: coords
8938 });
8939 }
8940});
8941
8942
8943// @namespace LayerGroup
8944LayerGroup.include({
8945 toMultiPoint: function (precision) {
8946 var coords = [];
8947
8948 this.eachLayer(function (layer) {
8949 coords.push(layer.toGeoJSON(precision).geometry.coordinates);
8950 });
8951
8952 return getFeature(this, {
8953 type: 'MultiPoint',
8954 coordinates: coords
8955 });
8956 },
8957
8958 // @method toGeoJSON(precision?: Number): Object
8959 // `precision` is the number of decimal places for coordinates.
8960 // The default value is 6 places.
8961 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
8962 toGeoJSON: function (precision) {
8963
8964 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
8965
8966 if (type === 'MultiPoint') {
8967 return this.toMultiPoint(precision);
8968 }
8969
8970 var isGeometryCollection = type === 'GeometryCollection',
8971 jsons = [];
8972
8973 this.eachLayer(function (layer) {
8974 if (layer.toGeoJSON) {
8975 var json = layer.toGeoJSON(precision);
8976 if (isGeometryCollection) {
8977 jsons.push(json.geometry);
8978 } else {
8979 var feature = asFeature(json);
8980 // Squash nested feature collections
8981 if (feature.type === 'FeatureCollection') {
8982 jsons.push.apply(jsons, feature.features);
8983 } else {
8984 jsons.push(feature);
8985 }
8986 }
8987 }
8988 });
8989
8990 if (isGeometryCollection) {
8991 return getFeature(this, {
8992 geometries: jsons,
8993 type: 'GeometryCollection'
8994 });
8995 }
8996
8997 return {
8998 type: 'FeatureCollection',
8999 features: jsons
9000 };
9001 }
9002});
9003
9004// @namespace GeoJSON
9005// @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
9006// Creates a GeoJSON layer. Optionally accepts an object in
9007// [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
9008// (you can alternatively add it later with `addData` method) and an `options` object.
9009function geoJSON(geojson, options) {
9010 return new GeoJSON(geojson, options);
9011}
9012
9013// Backward compatibility.
9014var geoJson = geoJSON;
9015
9016/*
9017 * @class ImageOverlay
9018 * @aka L.ImageOverlay
9019 * @inherits Interactive layer
9020 *
9021 * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
9022 *
9023 * @example
9024 *
9025 * ```js
9026 * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
9027 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
9028 * L.imageOverlay(imageUrl, imageBounds).addTo(map);
9029 * ```
9030 */
9031
9032var ImageOverlay = Layer.extend({
9033
9034 // @section
9035 // @aka ImageOverlay options
9036 options: {
9037 // @option opacity: Number = 1.0
9038 // The opacity of the image overlay.
9039 opacity: 1,
9040
9041 // @option alt: String = ''
9042 // Text for the `alt` attribute of the image (useful for accessibility).
9043 alt: '',
9044
9045 // @option interactive: Boolean = false
9046 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
9047 interactive: false,
9048
9049 // @option crossOrigin: Boolean|String = false
9050 // Whether the crossOrigin attribute will be added to the image.
9051 // 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.
9052 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
9053 crossOrigin: false,
9054
9055 // @option errorOverlayUrl: String = ''
9056 // URL to the overlay image to show in place of the overlay that failed to load.
9057 errorOverlayUrl: '',
9058
9059 // @option zIndex: Number = 1
9060 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
9061 zIndex: 1,
9062
9063 // @option className: String = ''
9064 // A custom class name to assign to the image. Empty by default.
9065 className: ''
9066 },
9067
9068 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
9069 this._url = url;
9070 this._bounds = toLatLngBounds(bounds);
9071
9072 setOptions(this, options);
9073 },
9074
9075 onAdd: function () {
9076 if (!this._image) {
9077 this._initImage();
9078
9079 if (this.options.opacity < 1) {
9080 this._updateOpacity();
9081 }
9082 }
9083
9084 if (this.options.interactive) {
9085 addClass(this._image, 'leaflet-interactive');
9086 this.addInteractiveTarget(this._image);
9087 }
9088
9089 this.getPane().appendChild(this._image);
9090 this._reset();
9091 },
9092
9093 onRemove: function () {
9094 remove(this._image);
9095 if (this.options.interactive) {
9096 this.removeInteractiveTarget(this._image);
9097 }
9098 },
9099
9100 // @method setOpacity(opacity: Number): this
9101 // Sets the opacity of the overlay.
9102 setOpacity: function (opacity) {
9103 this.options.opacity = opacity;
9104
9105 if (this._image) {
9106 this._updateOpacity();
9107 }
9108 return this;
9109 },
9110
9111 setStyle: function (styleOpts) {
9112 if (styleOpts.opacity) {
9113 this.setOpacity(styleOpts.opacity);
9114 }
9115 return this;
9116 },
9117
9118 // @method bringToFront(): this
9119 // Brings the layer to the top of all overlays.
9120 bringToFront: function () {
9121 if (this._map) {
9122 toFront(this._image);
9123 }
9124 return this;
9125 },
9126
9127 // @method bringToBack(): this
9128 // Brings the layer to the bottom of all overlays.
9129 bringToBack: function () {
9130 if (this._map) {
9131 toBack(this._image);
9132 }
9133 return this;
9134 },
9135
9136 // @method setUrl(url: String): this
9137 // Changes the URL of the image.
9138 setUrl: function (url) {
9139 this._url = url;
9140
9141 if (this._image) {
9142 this._image.src = url;
9143 }
9144 return this;
9145 },
9146
9147 // @method setBounds(bounds: LatLngBounds): this
9148 // Update the bounds that this ImageOverlay covers
9149 setBounds: function (bounds) {
9150 this._bounds = toLatLngBounds(bounds);
9151
9152 if (this._map) {
9153 this._reset();
9154 }
9155 return this;
9156 },
9157
9158 getEvents: function () {
9159 var events = {
9160 zoom: this._reset,
9161 viewreset: this._reset
9162 };
9163
9164 if (this._zoomAnimated) {
9165 events.zoomanim = this._animateZoom;
9166 }
9167
9168 return events;
9169 },
9170
9171 // @method setZIndex(value: Number): this
9172 // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
9173 setZIndex: function (value) {
9174 this.options.zIndex = value;
9175 this._updateZIndex();
9176 return this;
9177 },
9178
9179 // @method getBounds(): LatLngBounds
9180 // Get the bounds that this ImageOverlay covers
9181 getBounds: function () {
9182 return this._bounds;
9183 },
9184
9185 // @method getElement(): HTMLElement
9186 // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
9187 // used by this overlay.
9188 getElement: function () {
9189 return this._image;
9190 },
9191
9192 _initImage: function () {
9193 var wasElementSupplied = this._url.tagName === 'IMG';
9194 var img = this._image = wasElementSupplied ? this._url : create$1('img');
9195
9196 addClass(img, 'leaflet-image-layer');
9197 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
9198 if (this.options.className) { addClass(img, this.options.className); }
9199
9200 img.onselectstart = falseFn;
9201 img.onmousemove = falseFn;
9202
9203 // @event load: Event
9204 // Fired when the ImageOverlay layer has loaded its image
9205 img.onload = bind(this.fire, this, 'load');
9206 img.onerror = bind(this._overlayOnError, this, 'error');
9207
9208 if (this.options.crossOrigin || this.options.crossOrigin === '') {
9209 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
9210 }
9211
9212 if (this.options.zIndex) {
9213 this._updateZIndex();
9214 }
9215
9216 if (wasElementSupplied) {
9217 this._url = img.src;
9218 return;
9219 }
9220
9221 img.src = this._url;
9222 img.alt = this.options.alt;
9223 },
9224
9225 _animateZoom: function (e) {
9226 var scale = this._map.getZoomScale(e.zoom),
9227 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
9228
9229 setTransform(this._image, offset, scale);
9230 },
9231
9232 _reset: function () {
9233 var image = this._image,
9234 bounds = new Bounds(
9235 this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
9236 this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
9237 size = bounds.getSize();
9238
9239 setPosition(image, bounds.min);
9240
9241 image.style.width = size.x + 'px';
9242 image.style.height = size.y + 'px';
9243 },
9244
9245 _updateOpacity: function () {
9246 setOpacity(this._image, this.options.opacity);
9247 },
9248
9249 _updateZIndex: function () {
9250 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
9251 this._image.style.zIndex = this.options.zIndex;
9252 }
9253 },
9254
9255 _overlayOnError: function () {
9256 // @event error: Event
9257 // Fired when the ImageOverlay layer fails to load its image
9258 this.fire('error');
9259
9260 var errorUrl = this.options.errorOverlayUrl;
9261 if (errorUrl && this._url !== errorUrl) {
9262 this._url = errorUrl;
9263 this._image.src = errorUrl;
9264 }
9265 }
9266});
9267
9268// @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
9269// Instantiates an image overlay object given the URL of the image and the
9270// geographical bounds it is tied to.
9271var imageOverlay = function (url, bounds, options) {
9272 return new ImageOverlay(url, bounds, options);
9273};
9274
9275/*
9276 * @class VideoOverlay
9277 * @aka L.VideoOverlay
9278 * @inherits ImageOverlay
9279 *
9280 * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
9281 *
9282 * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
9283 * HTML5 element.
9284 *
9285 * @example
9286 *
9287 * ```js
9288 * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
9289 * videoBounds = [[ 32, -130], [ 13, -100]];
9290 * L.videoOverlay(videoUrl, videoBounds ).addTo(map);
9291 * ```
9292 */
9293
9294var VideoOverlay = ImageOverlay.extend({
9295
9296 // @section
9297 // @aka VideoOverlay options
9298 options: {
9299 // @option autoplay: Boolean = true
9300 // Whether the video starts playing automatically when loaded.
9301 autoplay: true,
9302
9303 // @option loop: Boolean = true
9304 // Whether the video will loop back to the beginning when played.
9305 loop: true,
9306
9307 // @option keepAspectRatio: Boolean = true
9308 // Whether the video will save aspect ratio after the projection.
9309 // Relevant for supported browsers. Browser compatibility- https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit
9310 keepAspectRatio: true,
9311
9312 // @option muted: Boolean = false
9313 // Whether the video starts on mute when loaded.
9314 muted: false
9315 },
9316
9317 _initImage: function () {
9318 var wasElementSupplied = this._url.tagName === 'VIDEO';
9319 var vid = this._image = wasElementSupplied ? this._url : create$1('video');
9320
9321 addClass(vid, 'leaflet-image-layer');
9322 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
9323 if (this.options.className) { addClass(vid, this.options.className); }
9324
9325 vid.onselectstart = falseFn;
9326 vid.onmousemove = falseFn;
9327
9328 // @event load: Event
9329 // Fired when the video has finished loading the first frame
9330 vid.onloadeddata = bind(this.fire, this, 'load');
9331
9332 if (wasElementSupplied) {
9333 var sourceElements = vid.getElementsByTagName('source');
9334 var sources = [];
9335 for (var j = 0; j < sourceElements.length; j++) {
9336 sources.push(sourceElements[j].src);
9337 }
9338
9339 this._url = (sourceElements.length > 0) ? sources : [vid.src];
9340 return;
9341 }
9342
9343 if (!isArray(this._url)) { this._url = [this._url]; }
9344
9345 if (!this.options.keepAspectRatio && Object.prototype.hasOwnProperty.call(vid.style, 'objectFit')) {
9346 vid.style['objectFit'] = 'fill';
9347 }
9348 vid.autoplay = !!this.options.autoplay;
9349 vid.loop = !!this.options.loop;
9350 vid.muted = !!this.options.muted;
9351 for (var i = 0; i < this._url.length; i++) {
9352 var source = create$1('source');
9353 source.src = this._url[i];
9354 vid.appendChild(source);
9355 }
9356 }
9357
9358 // @method getElement(): HTMLVideoElement
9359 // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
9360 // used by this overlay.
9361});
9362
9363
9364// @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
9365// Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
9366// geographical bounds it is tied to.
9367
9368function videoOverlay(video, bounds, options) {
9369 return new VideoOverlay(video, bounds, options);
9370}
9371
9372/*
9373 * @class SVGOverlay
9374 * @aka L.SVGOverlay
9375 * @inherits ImageOverlay
9376 *
9377 * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`.
9378 *
9379 * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element.
9380 *
9381 * @example
9382 *
9383 * ```js
9384 * var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
9385 * svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg");
9386 * svgElement.setAttribute('viewBox', "0 0 200 200");
9387 * 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"/>';
9388 * var svgElementBounds = [ [ 32, -130 ], [ 13, -100 ] ];
9389 * L.svgOverlay(svgElement, svgElementBounds).addTo(map);
9390 * ```
9391 */
9392
9393var SVGOverlay = ImageOverlay.extend({
9394 _initImage: function () {
9395 var el = this._image = this._url;
9396
9397 addClass(el, 'leaflet-image-layer');
9398 if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); }
9399 if (this.options.className) { addClass(el, this.options.className); }
9400
9401 el.onselectstart = falseFn;
9402 el.onmousemove = falseFn;
9403 }
9404
9405 // @method getElement(): SVGElement
9406 // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement)
9407 // used by this overlay.
9408});
9409
9410
9411// @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options)
9412// Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to.
9413// A viewBox attribute is required on the SVG element to zoom in and out properly.
9414
9415function svgOverlay(el, bounds, options) {
9416 return new SVGOverlay(el, bounds, options);
9417}
9418
9419/*
9420 * @class DivOverlay
9421 * @inherits Layer
9422 * @aka L.DivOverlay
9423 * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
9424 */
9425
9426// @namespace DivOverlay
9427var DivOverlay = Layer.extend({
9428
9429 // @section
9430 // @aka DivOverlay options
9431 options: {
9432 // @option offset: Point = Point(0, 7)
9433 // The offset of the popup position. Useful to control the anchor
9434 // of the popup when opening it on some overlays.
9435 offset: [0, 7],
9436
9437 // @option className: String = ''
9438 // A custom CSS class name to assign to the popup.
9439 className: '',
9440
9441 // @option pane: String = 'popupPane'
9442 // `Map pane` where the popup will be added.
9443 pane: 'popupPane'
9444 },
9445
9446 initialize: function (options, source) {
9447 setOptions(this, options);
9448
9449 this._source = source;
9450 },
9451
9452 onAdd: function (map) {
9453 this._zoomAnimated = map._zoomAnimated;
9454
9455 if (!this._container) {
9456 this._initLayout();
9457 }
9458
9459 if (map._fadeAnimated) {
9460 setOpacity(this._container, 0);
9461 }
9462
9463 clearTimeout(this._removeTimeout);
9464 this.getPane().appendChild(this._container);
9465 this.update();
9466
9467 if (map._fadeAnimated) {
9468 setOpacity(this._container, 1);
9469 }
9470
9471 this.bringToFront();
9472 },
9473
9474 onRemove: function (map) {
9475 if (map._fadeAnimated) {
9476 setOpacity(this._container, 0);
9477 this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
9478 } else {
9479 remove(this._container);
9480 }
9481 },
9482
9483 // @namespace Popup
9484 // @method getLatLng: LatLng
9485 // Returns the geographical point of popup.
9486 getLatLng: function () {
9487 return this._latlng;
9488 },
9489
9490 // @method setLatLng(latlng: LatLng): this
9491 // Sets the geographical point where the popup will open.
9492 setLatLng: function (latlng) {
9493 this._latlng = toLatLng(latlng);
9494 if (this._map) {
9495 this._updatePosition();
9496 this._adjustPan();
9497 }
9498 return this;
9499 },
9500
9501 // @method getContent: String|HTMLElement
9502 // Returns the content of the popup.
9503 getContent: function () {
9504 return this._content;
9505 },
9506
9507 // @method setContent(htmlContent: String|HTMLElement|Function): this
9508 // Sets the HTML content of the popup. If a function is passed the source layer will be passed to the function. The function should return a `String` or `HTMLElement` to be used in the popup.
9509 setContent: function (content) {
9510 this._content = content;
9511 this.update();
9512 return this;
9513 },
9514
9515 // @method getElement: String|HTMLElement
9516 // Returns the HTML container of the popup.
9517 getElement: function () {
9518 return this._container;
9519 },
9520
9521 // @method update: null
9522 // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
9523 update: function () {
9524 if (!this._map) { return; }
9525
9526 this._container.style.visibility = 'hidden';
9527
9528 this._updateContent();
9529 this._updateLayout();
9530 this._updatePosition();
9531
9532 this._container.style.visibility = '';
9533
9534 this._adjustPan();
9535 },
9536
9537 getEvents: function () {
9538 var events = {
9539 zoom: this._updatePosition,
9540 viewreset: this._updatePosition
9541 };
9542
9543 if (this._zoomAnimated) {
9544 events.zoomanim = this._animateZoom;
9545 }
9546 return events;
9547 },
9548
9549 // @method isOpen: Boolean
9550 // Returns `true` when the popup is visible on the map.
9551 isOpen: function () {
9552 return !!this._map && this._map.hasLayer(this);
9553 },
9554
9555 // @method bringToFront: this
9556 // Brings this popup in front of other popups (in the same map pane).
9557 bringToFront: function () {
9558 if (this._map) {
9559 toFront(this._container);
9560 }
9561 return this;
9562 },
9563
9564 // @method bringToBack: this
9565 // Brings this popup to the back of other popups (in the same map pane).
9566 bringToBack: function () {
9567 if (this._map) {
9568 toBack(this._container);
9569 }
9570 return this;
9571 },
9572
9573 _prepareOpen: function (parent, layer, latlng) {
9574 if (!(layer instanceof Layer)) {
9575 latlng = layer;
9576 layer = parent;
9577 }
9578
9579 if (layer instanceof FeatureGroup) {
9580 for (var id in parent._layers) {
9581 layer = parent._layers[id];
9582 break;
9583 }
9584 }
9585
9586 if (!latlng) {
9587 if (layer.getCenter) {
9588 latlng = layer.getCenter();
9589 } else if (layer.getLatLng) {
9590 latlng = layer.getLatLng();
9591 } else {
9592 throw new Error('Unable to get source layer LatLng.');
9593 }
9594 }
9595
9596 // set overlay source to this layer
9597 this._source = layer;
9598
9599 // update the overlay (content, layout, ect...)
9600 this.update();
9601
9602 return latlng;
9603 },
9604
9605 _updateContent: function () {
9606 if (!this._content) { return; }
9607
9608 var node = this._contentNode;
9609 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
9610
9611 if (typeof content === 'string') {
9612 node.innerHTML = content;
9613 } else {
9614 while (node.hasChildNodes()) {
9615 node.removeChild(node.firstChild);
9616 }
9617 node.appendChild(content);
9618 }
9619 this.fire('contentupdate');
9620 },
9621
9622 _updatePosition: function () {
9623 if (!this._map) { return; }
9624
9625 var pos = this._map.latLngToLayerPoint(this._latlng),
9626 offset = toPoint(this.options.offset),
9627 anchor = this._getAnchor();
9628
9629 if (this._zoomAnimated) {
9630 setPosition(this._container, pos.add(anchor));
9631 } else {
9632 offset = offset.add(pos).add(anchor);
9633 }
9634
9635 var bottom = this._containerBottom = -offset.y,
9636 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
9637
9638 // bottom position the popup in case the height of the popup changes (images loading etc)
9639 this._container.style.bottom = bottom + 'px';
9640 this._container.style.left = left + 'px';
9641 },
9642
9643 _getAnchor: function () {
9644 return [0, 0];
9645 }
9646
9647});
9648
9649/*
9650 * @class Popup
9651 * @inherits DivOverlay
9652 * @aka L.Popup
9653 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
9654 * open popups while making sure that only one popup is open at one time
9655 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
9656 *
9657 * @example
9658 *
9659 * If you want to just bind a popup to marker click and then open it, it's really easy:
9660 *
9661 * ```js
9662 * marker.bindPopup(popupContent).openPopup();
9663 * ```
9664 * Path overlays like polylines also have a `bindPopup` method.
9665 * Here's a more complicated way to open a popup on a map:
9666 *
9667 * ```js
9668 * var popup = L.popup()
9669 * .setLatLng(latlng)
9670 * .setContent('<p>Hello world!<br />This is a nice popup.</p>')
9671 * .openOn(map);
9672 * ```
9673 */
9674
9675
9676// @namespace Popup
9677var Popup = DivOverlay.extend({
9678
9679 // @section
9680 // @aka Popup options
9681 options: {
9682 // @option maxWidth: Number = 300
9683 // Max width of the popup, in pixels.
9684 maxWidth: 300,
9685
9686 // @option minWidth: Number = 50
9687 // Min width of the popup, in pixels.
9688 minWidth: 50,
9689
9690 // @option maxHeight: Number = null
9691 // If set, creates a scrollable container of the given height
9692 // inside a popup if its content exceeds it.
9693 maxHeight: null,
9694
9695 // @option autoPan: Boolean = true
9696 // Set it to `false` if you don't want the map to do panning animation
9697 // to fit the opened popup.
9698 autoPan: true,
9699
9700 // @option autoPanPaddingTopLeft: Point = null
9701 // The margin between the popup and the top left corner of the map
9702 // view after autopanning was performed.
9703 autoPanPaddingTopLeft: null,
9704
9705 // @option autoPanPaddingBottomRight: Point = null
9706 // The margin between the popup and the bottom right corner of the map
9707 // view after autopanning was performed.
9708 autoPanPaddingBottomRight: null,
9709
9710 // @option autoPanPadding: Point = Point(5, 5)
9711 // Equivalent of setting both top left and bottom right autopan padding to the same value.
9712 autoPanPadding: [5, 5],
9713
9714 // @option keepInView: Boolean = false
9715 // Set it to `true` if you want to prevent users from panning the popup
9716 // off of the screen while it is open.
9717 keepInView: false,
9718
9719 // @option closeButton: Boolean = true
9720 // Controls the presence of a close button in the popup.
9721 closeButton: true,
9722
9723 // @option autoClose: Boolean = true
9724 // Set it to `false` if you want to override the default behavior of
9725 // the popup closing when another popup is opened.
9726 autoClose: true,
9727
9728 // @option closeOnEscapeKey: Boolean = true
9729 // Set it to `false` if you want to override the default behavior of
9730 // the ESC key for closing of the popup.
9731 closeOnEscapeKey: true,
9732
9733 // @option closeOnClick: Boolean = *
9734 // Set it if you want to override the default behavior of the popup closing when user clicks
9735 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
9736
9737 // @option className: String = ''
9738 // A custom CSS class name to assign to the popup.
9739 className: ''
9740 },
9741
9742 // @namespace Popup
9743 // @method openOn(map: Map): this
9744 // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
9745 openOn: function (map) {
9746 map.openPopup(this);
9747 return this;
9748 },
9749
9750 onAdd: function (map) {
9751 DivOverlay.prototype.onAdd.call(this, map);
9752
9753 // @namespace Map
9754 // @section Popup events
9755 // @event popupopen: PopupEvent
9756 // Fired when a popup is opened in the map
9757 map.fire('popupopen', {popup: this});
9758
9759 if (this._source) {
9760 // @namespace Layer
9761 // @section Popup events
9762 // @event popupopen: PopupEvent
9763 // Fired when a popup bound to this layer is opened
9764 this._source.fire('popupopen', {popup: this}, true);
9765 // For non-path layers, we toggle the popup when clicking
9766 // again the layer, so prevent the map to reopen it.
9767 if (!(this._source instanceof Path)) {
9768 this._source.on('preclick', stopPropagation);
9769 }
9770 }
9771 },
9772
9773 onRemove: function (map) {
9774 DivOverlay.prototype.onRemove.call(this, map);
9775
9776 // @namespace Map
9777 // @section Popup events
9778 // @event popupclose: PopupEvent
9779 // Fired when a popup in the map is closed
9780 map.fire('popupclose', {popup: this});
9781
9782 if (this._source) {
9783 // @namespace Layer
9784 // @section Popup events
9785 // @event popupclose: PopupEvent
9786 // Fired when a popup bound to this layer is closed
9787 this._source.fire('popupclose', {popup: this}, true);
9788 if (!(this._source instanceof Path)) {
9789 this._source.off('preclick', stopPropagation);
9790 }
9791 }
9792 },
9793
9794 getEvents: function () {
9795 var events = DivOverlay.prototype.getEvents.call(this);
9796
9797 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
9798 events.preclick = this._close;
9799 }
9800
9801 if (this.options.keepInView) {
9802 events.moveend = this._adjustPan;
9803 }
9804
9805 return events;
9806 },
9807
9808 _close: function () {
9809 if (this._map) {
9810 this._map.closePopup(this);
9811 }
9812 },
9813
9814 _initLayout: function () {
9815 var prefix = 'leaflet-popup',
9816 container = this._container = create$1('div',
9817 prefix + ' ' + (this.options.className || '') +
9818 ' leaflet-zoom-animated');
9819
9820 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
9821 this._contentNode = create$1('div', prefix + '-content', wrapper);
9822
9823 disableClickPropagation(container);
9824 disableScrollPropagation(this._contentNode);
9825 on(container, 'contextmenu', stopPropagation);
9826
9827 this._tipContainer = create$1('div', prefix + '-tip-container', container);
9828 this._tip = create$1('div', prefix + '-tip', this._tipContainer);
9829
9830 if (this.options.closeButton) {
9831 var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
9832 closeButton.href = '#close';
9833 closeButton.innerHTML = '&#215;';
9834
9835 on(closeButton, 'click', this._onCloseButtonClick, this);
9836 }
9837 },
9838
9839 _updateLayout: function () {
9840 var container = this._contentNode,
9841 style = container.style;
9842
9843 style.width = '';
9844 style.whiteSpace = 'nowrap';
9845
9846 var width = container.offsetWidth;
9847 width = Math.min(width, this.options.maxWidth);
9848 width = Math.max(width, this.options.minWidth);
9849
9850 style.width = (width + 1) + 'px';
9851 style.whiteSpace = '';
9852
9853 style.height = '';
9854
9855 var height = container.offsetHeight,
9856 maxHeight = this.options.maxHeight,
9857 scrolledClass = 'leaflet-popup-scrolled';
9858
9859 if (maxHeight && height > maxHeight) {
9860 style.height = maxHeight + 'px';
9861 addClass(container, scrolledClass);
9862 } else {
9863 removeClass(container, scrolledClass);
9864 }
9865
9866 this._containerWidth = this._container.offsetWidth;
9867 },
9868
9869 _animateZoom: function (e) {
9870 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
9871 anchor = this._getAnchor();
9872 setPosition(this._container, pos.add(anchor));
9873 },
9874
9875 _adjustPan: function () {
9876 if (!this.options.autoPan) { return; }
9877 if (this._map._panAnim) { this._map._panAnim.stop(); }
9878
9879 var map = this._map,
9880 marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
9881 containerHeight = this._container.offsetHeight + marginBottom,
9882 containerWidth = this._containerWidth,
9883 layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
9884
9885 layerPos._add(getPosition(this._container));
9886
9887 var containerPos = map.layerPointToContainerPoint(layerPos),
9888 padding = toPoint(this.options.autoPanPadding),
9889 paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
9890 paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
9891 size = map.getSize(),
9892 dx = 0,
9893 dy = 0;
9894
9895 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
9896 dx = containerPos.x + containerWidth - size.x + paddingBR.x;
9897 }
9898 if (containerPos.x - dx - paddingTL.x < 0) { // left
9899 dx = containerPos.x - paddingTL.x;
9900 }
9901 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
9902 dy = containerPos.y + containerHeight - size.y + paddingBR.y;
9903 }
9904 if (containerPos.y - dy - paddingTL.y < 0) { // top
9905 dy = containerPos.y - paddingTL.y;
9906 }
9907
9908 // @namespace Map
9909 // @section Popup events
9910 // @event autopanstart: Event
9911 // Fired when the map starts autopanning when opening a popup.
9912 if (dx || dy) {
9913 map
9914 .fire('autopanstart')
9915 .panBy([dx, dy]);
9916 }
9917 },
9918
9919 _onCloseButtonClick: function (e) {
9920 this._close();
9921 stop(e);
9922 },
9923
9924 _getAnchor: function () {
9925 // Where should we anchor the popup on the source layer?
9926 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
9927 }
9928
9929});
9930
9931// @namespace Popup
9932// @factory L.popup(options?: Popup options, source?: Layer)
9933// 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.
9934var popup = function (options, source) {
9935 return new Popup(options, source);
9936};
9937
9938
9939/* @namespace Map
9940 * @section Interaction Options
9941 * @option closePopupOnClick: Boolean = true
9942 * Set it to `false` if you don't want popups to close when user clicks the map.
9943 */
9944Map.mergeOptions({
9945 closePopupOnClick: true
9946});
9947
9948
9949// @namespace Map
9950// @section Methods for Layers and Controls
9951Map.include({
9952 // @method openPopup(popup: Popup): this
9953 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
9954 // @alternative
9955 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
9956 // Creates a popup with the specified content and options and opens it in the given point on a map.
9957 openPopup: function (popup, latlng, options) {
9958 if (!(popup instanceof Popup)) {
9959 popup = new Popup(options).setContent(popup);
9960 }
9961
9962 if (latlng) {
9963 popup.setLatLng(latlng);
9964 }
9965
9966 if (this.hasLayer(popup)) {
9967 return this;
9968 }
9969
9970 if (this._popup && this._popup.options.autoClose) {
9971 this.closePopup();
9972 }
9973
9974 this._popup = popup;
9975 return this.addLayer(popup);
9976 },
9977
9978 // @method closePopup(popup?: Popup): this
9979 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
9980 closePopup: function (popup) {
9981 if (!popup || popup === this._popup) {
9982 popup = this._popup;
9983 this._popup = null;
9984 }
9985 if (popup) {
9986 this.removeLayer(popup);
9987 }
9988 return this;
9989 }
9990});
9991
9992/*
9993 * @namespace Layer
9994 * @section Popup methods example
9995 *
9996 * All layers share a set of methods convenient for binding popups to it.
9997 *
9998 * ```js
9999 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
10000 * layer.openPopup();
10001 * layer.closePopup();
10002 * ```
10003 *
10004 * 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.
10005 */
10006
10007// @section Popup methods
10008Layer.include({
10009
10010 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
10011 // Binds a popup to the layer with the passed `content` and sets up the
10012 // necessary event listeners. If a `Function` is passed it will receive
10013 // the layer as the first argument and should return a `String` or `HTMLElement`.
10014 bindPopup: function (content, options) {
10015
10016 if (content instanceof Popup) {
10017 setOptions(content, options);
10018 this._popup = content;
10019 content._source = this;
10020 } else {
10021 if (!this._popup || options) {
10022 this._popup = new Popup(options, this);
10023 }
10024 this._popup.setContent(content);
10025 }
10026
10027 if (!this._popupHandlersAdded) {
10028 this.on({
10029 click: this._openPopup,
10030 keypress: this._onKeyPress,
10031 remove: this.closePopup,
10032 move: this._movePopup
10033 });
10034 this._popupHandlersAdded = true;
10035 }
10036
10037 return this;
10038 },
10039
10040 // @method unbindPopup(): this
10041 // Removes the popup previously bound with `bindPopup`.
10042 unbindPopup: function () {
10043 if (this._popup) {
10044 this.off({
10045 click: this._openPopup,
10046 keypress: this._onKeyPress,
10047 remove: this.closePopup,
10048 move: this._movePopup
10049 });
10050 this._popupHandlersAdded = false;
10051 this._popup = null;
10052 }
10053 return this;
10054 },
10055
10056 // @method openPopup(latlng?: LatLng): this
10057 // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
10058 openPopup: function (layer, latlng) {
10059 if (this._popup && this._map) {
10060 latlng = this._popup._prepareOpen(this, layer, latlng);
10061
10062 // open the popup on the map
10063 this._map.openPopup(this._popup, latlng);
10064 }
10065
10066 return this;
10067 },
10068
10069 // @method closePopup(): this
10070 // Closes the popup bound to this layer if it is open.
10071 closePopup: function () {
10072 if (this._popup) {
10073 this._popup._close();
10074 }
10075 return this;
10076 },
10077
10078 // @method togglePopup(): this
10079 // Opens or closes the popup bound to this layer depending on its current state.
10080 togglePopup: function (target) {
10081 if (this._popup) {
10082 if (this._popup._map) {
10083 this.closePopup();
10084 } else {
10085 this.openPopup(target);
10086 }
10087 }
10088 return this;
10089 },
10090
10091 // @method isPopupOpen(): boolean
10092 // Returns `true` if the popup bound to this layer is currently open.
10093 isPopupOpen: function () {
10094 return (this._popup ? this._popup.isOpen() : false);
10095 },
10096
10097 // @method setPopupContent(content: String|HTMLElement|Popup): this
10098 // Sets the content of the popup bound to this layer.
10099 setPopupContent: function (content) {
10100 if (this._popup) {
10101 this._popup.setContent(content);
10102 }
10103 return this;
10104 },
10105
10106 // @method getPopup(): Popup
10107 // Returns the popup bound to this layer.
10108 getPopup: function () {
10109 return this._popup;
10110 },
10111
10112 _openPopup: function (e) {
10113 var layer = e.layer || e.target;
10114
10115 if (!this._popup) {
10116 return;
10117 }
10118
10119 if (!this._map) {
10120 return;
10121 }
10122
10123 // prevent map click
10124 stop(e);
10125
10126 // if this inherits from Path its a vector and we can just
10127 // open the popup at the new location
10128 if (layer instanceof Path) {
10129 this.openPopup(e.layer || e.target, e.latlng);
10130 return;
10131 }
10132
10133 // otherwise treat it like a marker and figure out
10134 // if we should toggle it open/closed
10135 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
10136 this.closePopup();
10137 } else {
10138 this.openPopup(layer, e.latlng);
10139 }
10140 },
10141
10142 _movePopup: function (e) {
10143 this._popup.setLatLng(e.latlng);
10144 },
10145
10146 _onKeyPress: function (e) {
10147 if (e.originalEvent.keyCode === 13) {
10148 this._openPopup(e);
10149 }
10150 }
10151});
10152
10153/*
10154 * @class Tooltip
10155 * @inherits DivOverlay
10156 * @aka L.Tooltip
10157 * Used to display small texts on top of map layers.
10158 *
10159 * @example
10160 *
10161 * ```js
10162 * marker.bindTooltip("my tooltip text").openTooltip();
10163 * ```
10164 * Note about tooltip offset. Leaflet takes two options in consideration
10165 * for computing tooltip offsetting:
10166 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
10167 * Add a positive x offset to move the tooltip to the right, and a positive y offset to
10168 * move it to the bottom. Negatives will move to the left and top.
10169 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
10170 * should adapt this value if you use a custom icon.
10171 */
10172
10173
10174// @namespace Tooltip
10175var Tooltip = DivOverlay.extend({
10176
10177 // @section
10178 // @aka Tooltip options
10179 options: {
10180 // @option pane: String = 'tooltipPane'
10181 // `Map pane` where the tooltip will be added.
10182 pane: 'tooltipPane',
10183
10184 // @option offset: Point = Point(0, 0)
10185 // Optional offset of the tooltip position.
10186 offset: [0, 0],
10187
10188 // @option direction: String = 'auto'
10189 // Direction where to open the tooltip. Possible values are: `right`, `left`,
10190 // `top`, `bottom`, `center`, `auto`.
10191 // `auto` will dynamically switch between `right` and `left` according to the tooltip
10192 // position on the map.
10193 direction: 'auto',
10194
10195 // @option permanent: Boolean = false
10196 // Whether to open the tooltip permanently or only on mouseover.
10197 permanent: false,
10198
10199 // @option sticky: Boolean = false
10200 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
10201 sticky: false,
10202
10203 // @option interactive: Boolean = false
10204 // If true, the tooltip will listen to the feature events.
10205 interactive: false,
10206
10207 // @option opacity: Number = 0.9
10208 // Tooltip container opacity.
10209 opacity: 0.9
10210 },
10211
10212 onAdd: function (map) {
10213 DivOverlay.prototype.onAdd.call(this, map);
10214 this.setOpacity(this.options.opacity);
10215
10216 // @namespace Map
10217 // @section Tooltip events
10218 // @event tooltipopen: TooltipEvent
10219 // Fired when a tooltip is opened in the map.
10220 map.fire('tooltipopen', {tooltip: this});
10221
10222 if (this._source) {
10223 // @namespace Layer
10224 // @section Tooltip events
10225 // @event tooltipopen: TooltipEvent
10226 // Fired when a tooltip bound to this layer is opened.
10227 this._source.fire('tooltipopen', {tooltip: this}, true);
10228 }
10229 },
10230
10231 onRemove: function (map) {
10232 DivOverlay.prototype.onRemove.call(this, map);
10233
10234 // @namespace Map
10235 // @section Tooltip events
10236 // @event tooltipclose: TooltipEvent
10237 // Fired when a tooltip in the map is closed.
10238 map.fire('tooltipclose', {tooltip: this});
10239
10240 if (this._source) {
10241 // @namespace Layer
10242 // @section Tooltip events
10243 // @event tooltipclose: TooltipEvent
10244 // Fired when a tooltip bound to this layer is closed.
10245 this._source.fire('tooltipclose', {tooltip: this}, true);
10246 }
10247 },
10248
10249 getEvents: function () {
10250 var events = DivOverlay.prototype.getEvents.call(this);
10251
10252 if (touch && !this.options.permanent) {
10253 events.preclick = this._close;
10254 }
10255
10256 return events;
10257 },
10258
10259 _close: function () {
10260 if (this._map) {
10261 this._map.closeTooltip(this);
10262 }
10263 },
10264
10265 _initLayout: function () {
10266 var prefix = 'leaflet-tooltip',
10267 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
10268
10269 this._contentNode = this._container = create$1('div', className);
10270 },
10271
10272 _updateLayout: function () {},
10273
10274 _adjustPan: function () {},
10275
10276 _setPosition: function (pos) {
10277 var subX, subY,
10278 map = this._map,
10279 container = this._container,
10280 centerPoint = map.latLngToContainerPoint(map.getCenter()),
10281 tooltipPoint = map.layerPointToContainerPoint(pos),
10282 direction = this.options.direction,
10283 tooltipWidth = container.offsetWidth,
10284 tooltipHeight = container.offsetHeight,
10285 offset = toPoint(this.options.offset),
10286 anchor = this._getAnchor();
10287
10288 if (direction === 'top') {
10289 subX = tooltipWidth / 2;
10290 subY = tooltipHeight;
10291 } else if (direction === 'bottom') {
10292 subX = tooltipWidth / 2;
10293 subY = 0;
10294 } else if (direction === 'center') {
10295 subX = tooltipWidth / 2;
10296 subY = tooltipHeight / 2;
10297 } else if (direction === 'right') {
10298 subX = 0;
10299 subY = tooltipHeight / 2;
10300 } else if (direction === 'left') {
10301 subX = tooltipWidth;
10302 subY = tooltipHeight / 2;
10303 } else if (tooltipPoint.x < centerPoint.x) {
10304 direction = 'right';
10305 subX = 0;
10306 subY = tooltipHeight / 2;
10307 } else {
10308 direction = 'left';
10309 subX = tooltipWidth + (offset.x + anchor.x) * 2;
10310 subY = tooltipHeight / 2;
10311 }
10312
10313 pos = pos.subtract(toPoint(subX, subY, true)).add(offset).add(anchor);
10314
10315 removeClass(container, 'leaflet-tooltip-right');
10316 removeClass(container, 'leaflet-tooltip-left');
10317 removeClass(container, 'leaflet-tooltip-top');
10318 removeClass(container, 'leaflet-tooltip-bottom');
10319 addClass(container, 'leaflet-tooltip-' + direction);
10320 setPosition(container, pos);
10321 },
10322
10323 _updatePosition: function () {
10324 var pos = this._map.latLngToLayerPoint(this._latlng);
10325 this._setPosition(pos);
10326 },
10327
10328 setOpacity: function (opacity) {
10329 this.options.opacity = opacity;
10330
10331 if (this._container) {
10332 setOpacity(this._container, opacity);
10333 }
10334 },
10335
10336 _animateZoom: function (e) {
10337 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
10338 this._setPosition(pos);
10339 },
10340
10341 _getAnchor: function () {
10342 // Where should we anchor the tooltip on the source layer?
10343 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
10344 }
10345
10346});
10347
10348// @namespace Tooltip
10349// @factory L.tooltip(options?: Tooltip options, source?: Layer)
10350// 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.
10351var tooltip = function (options, source) {
10352 return new Tooltip(options, source);
10353};
10354
10355// @namespace Map
10356// @section Methods for Layers and Controls
10357Map.include({
10358
10359 // @method openTooltip(tooltip: Tooltip): this
10360 // Opens the specified tooltip.
10361 // @alternative
10362 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
10363 // Creates a tooltip with the specified content and options and open it.
10364 openTooltip: function (tooltip, latlng, options) {
10365 if (!(tooltip instanceof Tooltip)) {
10366 tooltip = new Tooltip(options).setContent(tooltip);
10367 }
10368
10369 if (latlng) {
10370 tooltip.setLatLng(latlng);
10371 }
10372
10373 if (this.hasLayer(tooltip)) {
10374 return this;
10375 }
10376
10377 return this.addLayer(tooltip);
10378 },
10379
10380 // @method closeTooltip(tooltip?: Tooltip): this
10381 // Closes the tooltip given as parameter.
10382 closeTooltip: function (tooltip) {
10383 if (tooltip) {
10384 this.removeLayer(tooltip);
10385 }
10386 return this;
10387 }
10388
10389});
10390
10391/*
10392 * @namespace Layer
10393 * @section Tooltip methods example
10394 *
10395 * All layers share a set of methods convenient for binding tooltips to it.
10396 *
10397 * ```js
10398 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
10399 * layer.openTooltip();
10400 * layer.closeTooltip();
10401 * ```
10402 */
10403
10404// @section Tooltip methods
10405Layer.include({
10406
10407 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
10408 // Binds a tooltip to the layer with the passed `content` and sets up the
10409 // necessary event listeners. If a `Function` is passed it will receive
10410 // the layer as the first argument and should return a `String` or `HTMLElement`.
10411 bindTooltip: function (content, options) {
10412
10413 if (content instanceof Tooltip) {
10414 setOptions(content, options);
10415 this._tooltip = content;
10416 content._source = this;
10417 } else {
10418 if (!this._tooltip || options) {
10419 this._tooltip = new Tooltip(options, this);
10420 }
10421 this._tooltip.setContent(content);
10422
10423 }
10424
10425 this._initTooltipInteractions();
10426
10427 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
10428 this.openTooltip();
10429 }
10430
10431 return this;
10432 },
10433
10434 // @method unbindTooltip(): this
10435 // Removes the tooltip previously bound with `bindTooltip`.
10436 unbindTooltip: function () {
10437 if (this._tooltip) {
10438 this._initTooltipInteractions(true);
10439 this.closeTooltip();
10440 this._tooltip = null;
10441 }
10442 return this;
10443 },
10444
10445 _initTooltipInteractions: function (remove$$1) {
10446 if (!remove$$1 && this._tooltipHandlersAdded) { return; }
10447 var onOff = remove$$1 ? 'off' : 'on',
10448 events = {
10449 remove: this.closeTooltip,
10450 move: this._moveTooltip
10451 };
10452 if (!this._tooltip.options.permanent) {
10453 events.mouseover = this._openTooltip;
10454 events.mouseout = this.closeTooltip;
10455 if (this._tooltip.options.sticky) {
10456 events.mousemove = this._moveTooltip;
10457 }
10458 if (touch) {
10459 events.click = this._openTooltip;
10460 }
10461 } else {
10462 events.add = this._openTooltip;
10463 }
10464 this[onOff](events);
10465 this._tooltipHandlersAdded = !remove$$1;
10466 },
10467
10468 // @method openTooltip(latlng?: LatLng): this
10469 // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
10470 openTooltip: function (layer, latlng) {
10471 if (this._tooltip && this._map) {
10472 latlng = this._tooltip._prepareOpen(this, layer, latlng);
10473
10474 // open the tooltip on the map
10475 this._map.openTooltip(this._tooltip, latlng);
10476
10477 // Tooltip container may not be defined if not permanent and never
10478 // opened.
10479 if (this._tooltip.options.interactive && this._tooltip._container) {
10480 addClass(this._tooltip._container, 'leaflet-clickable');
10481 this.addInteractiveTarget(this._tooltip._container);
10482 }
10483 }
10484
10485 return this;
10486 },
10487
10488 // @method closeTooltip(): this
10489 // Closes the tooltip bound to this layer if it is open.
10490 closeTooltip: function () {
10491 if (this._tooltip) {
10492 this._tooltip._close();
10493 if (this._tooltip.options.interactive && this._tooltip._container) {
10494 removeClass(this._tooltip._container, 'leaflet-clickable');
10495 this.removeInteractiveTarget(this._tooltip._container);
10496 }
10497 }
10498 return this;
10499 },
10500
10501 // @method toggleTooltip(): this
10502 // Opens or closes the tooltip bound to this layer depending on its current state.
10503 toggleTooltip: function (target) {
10504 if (this._tooltip) {
10505 if (this._tooltip._map) {
10506 this.closeTooltip();
10507 } else {
10508 this.openTooltip(target);
10509 }
10510 }
10511 return this;
10512 },
10513
10514 // @method isTooltipOpen(): boolean
10515 // Returns `true` if the tooltip bound to this layer is currently open.
10516 isTooltipOpen: function () {
10517 return this._tooltip.isOpen();
10518 },
10519
10520 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10521 // Sets the content of the tooltip bound to this layer.
10522 setTooltipContent: function (content) {
10523 if (this._tooltip) {
10524 this._tooltip.setContent(content);
10525 }
10526 return this;
10527 },
10528
10529 // @method getTooltip(): Tooltip
10530 // Returns the tooltip bound to this layer.
10531 getTooltip: function () {
10532 return this._tooltip;
10533 },
10534
10535 _openTooltip: function (e) {
10536 var layer = e.layer || e.target;
10537
10538 if (!this._tooltip || !this._map) {
10539 return;
10540 }
10541 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
10542 },
10543
10544 _moveTooltip: function (e) {
10545 var latlng = e.latlng, containerPoint, layerPoint;
10546 if (this._tooltip.options.sticky && e.originalEvent) {
10547 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
10548 layerPoint = this._map.containerPointToLayerPoint(containerPoint);
10549 latlng = this._map.layerPointToLatLng(layerPoint);
10550 }
10551 this._tooltip.setLatLng(latlng);
10552 }
10553});
10554
10555/*
10556 * @class DivIcon
10557 * @aka L.DivIcon
10558 * @inherits Icon
10559 *
10560 * Represents a lightweight icon for markers that uses a simple `<div>`
10561 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
10562 *
10563 * @example
10564 * ```js
10565 * var myIcon = L.divIcon({className: 'my-div-icon'});
10566 * // you can set .my-div-icon styles in CSS
10567 *
10568 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
10569 * ```
10570 *
10571 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
10572 */
10573
10574var DivIcon = Icon.extend({
10575 options: {
10576 // @section
10577 // @aka DivIcon options
10578 iconSize: [12, 12], // also can be set through CSS
10579
10580 // iconAnchor: (Point),
10581 // popupAnchor: (Point),
10582
10583 // @option html: String|HTMLElement = ''
10584 // Custom HTML code to put inside the div element, empty by default. Alternatively,
10585 // an instance of `HTMLElement`.
10586 html: false,
10587
10588 // @option bgPos: Point = [0, 0]
10589 // Optional relative position of the background, in pixels
10590 bgPos: null,
10591
10592 className: 'leaflet-div-icon'
10593 },
10594
10595 createIcon: function (oldIcon) {
10596 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
10597 options = this.options;
10598
10599 if (options.html instanceof Element) {
10600 empty(div);
10601 div.appendChild(options.html);
10602 } else {
10603 div.innerHTML = options.html !== false ? options.html : '';
10604 }
10605
10606 if (options.bgPos) {
10607 var bgPos = toPoint(options.bgPos);
10608 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
10609 }
10610 this._setIconStyles(div, 'icon');
10611
10612 return div;
10613 },
10614
10615 createShadow: function () {
10616 return null;
10617 }
10618});
10619
10620// @factory L.divIcon(options: DivIcon options)
10621// Creates a `DivIcon` instance with the given options.
10622function divIcon(options) {
10623 return new DivIcon(options);
10624}
10625
10626Icon.Default = IconDefault;
10627
10628/*
10629 * @class GridLayer
10630 * @inherits Layer
10631 * @aka L.GridLayer
10632 *
10633 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
10634 * 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.
10635 *
10636 *
10637 * @section Synchronous usage
10638 * @example
10639 *
10640 * 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.
10641 *
10642 * ```js
10643 * var CanvasLayer = L.GridLayer.extend({
10644 * createTile: function(coords){
10645 * // create a <canvas> element for drawing
10646 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10647 *
10648 * // setup tile width and height according to the options
10649 * var size = this.getTileSize();
10650 * tile.width = size.x;
10651 * tile.height = size.y;
10652 *
10653 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z
10654 * var ctx = tile.getContext('2d');
10655 *
10656 * // return the tile so it can be rendered on screen
10657 * return tile;
10658 * }
10659 * });
10660 * ```
10661 *
10662 * @section Asynchronous usage
10663 * @example
10664 *
10665 * 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.
10666 *
10667 * ```js
10668 * var CanvasLayer = L.GridLayer.extend({
10669 * createTile: function(coords, done){
10670 * var error;
10671 *
10672 * // create a <canvas> element for drawing
10673 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10674 *
10675 * // setup tile width and height according to the options
10676 * var size = this.getTileSize();
10677 * tile.width = size.x;
10678 * tile.height = size.y;
10679 *
10680 * // draw something asynchronously and pass the tile to the done() callback
10681 * setTimeout(function() {
10682 * done(error, tile);
10683 * }, 1000);
10684 *
10685 * return tile;
10686 * }
10687 * });
10688 * ```
10689 *
10690 * @section
10691 */
10692
10693
10694var GridLayer = Layer.extend({
10695
10696 // @section
10697 // @aka GridLayer options
10698 options: {
10699 // @option tileSize: Number|Point = 256
10700 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
10701 tileSize: 256,
10702
10703 // @option opacity: Number = 1.0
10704 // Opacity of the tiles. Can be used in the `createTile()` function.
10705 opacity: 1,
10706
10707 // @option updateWhenIdle: Boolean = (depends)
10708 // Load new tiles only when panning ends.
10709 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
10710 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
10711 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
10712 updateWhenIdle: mobile,
10713
10714 // @option updateWhenZooming: Boolean = true
10715 // 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.
10716 updateWhenZooming: true,
10717
10718 // @option updateInterval: Number = 200
10719 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
10720 updateInterval: 200,
10721
10722 // @option zIndex: Number = 1
10723 // The explicit zIndex of the tile layer.
10724 zIndex: 1,
10725
10726 // @option bounds: LatLngBounds = undefined
10727 // If set, tiles will only be loaded inside the set `LatLngBounds`.
10728 bounds: null,
10729
10730 // @option minZoom: Number = 0
10731 // The minimum zoom level down to which this layer will be displayed (inclusive).
10732 minZoom: 0,
10733
10734 // @option maxZoom: Number = undefined
10735 // The maximum zoom level up to which this layer will be displayed (inclusive).
10736 maxZoom: undefined,
10737
10738 // @option maxNativeZoom: Number = undefined
10739 // Maximum zoom number the tile source has available. If it is specified,
10740 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
10741 // from `maxNativeZoom` level and auto-scaled.
10742 maxNativeZoom: undefined,
10743
10744 // @option minNativeZoom: Number = undefined
10745 // Minimum zoom number the tile source has available. If it is specified,
10746 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
10747 // from `minNativeZoom` level and auto-scaled.
10748 minNativeZoom: undefined,
10749
10750 // @option noWrap: Boolean = false
10751 // Whether the layer is wrapped around the antimeridian. If `true`, the
10752 // GridLayer will only be displayed once at low zoom levels. Has no
10753 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
10754 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
10755 // tiles outside the CRS limits.
10756 noWrap: false,
10757
10758 // @option pane: String = 'tilePane'
10759 // `Map pane` where the grid layer will be added.
10760 pane: 'tilePane',
10761
10762 // @option className: String = ''
10763 // A custom class name to assign to the tile layer. Empty by default.
10764 className: '',
10765
10766 // @option keepBuffer: Number = 2
10767 // When panning the map, keep this many rows and columns of tiles before unloading them.
10768 keepBuffer: 2
10769 },
10770
10771 initialize: function (options) {
10772 setOptions(this, options);
10773 },
10774
10775 onAdd: function () {
10776 this._initContainer();
10777
10778 this._levels = {};
10779 this._tiles = {};
10780
10781 this._resetView();
10782 this._update();
10783 },
10784
10785 beforeAdd: function (map) {
10786 map._addZoomLimit(this);
10787 },
10788
10789 onRemove: function (map) {
10790 this._removeAllTiles();
10791 remove(this._container);
10792 map._removeZoomLimit(this);
10793 this._container = null;
10794 this._tileZoom = undefined;
10795 },
10796
10797 // @method bringToFront: this
10798 // Brings the tile layer to the top of all tile layers.
10799 bringToFront: function () {
10800 if (this._map) {
10801 toFront(this._container);
10802 this._setAutoZIndex(Math.max);
10803 }
10804 return this;
10805 },
10806
10807 // @method bringToBack: this
10808 // Brings the tile layer to the bottom of all tile layers.
10809 bringToBack: function () {
10810 if (this._map) {
10811 toBack(this._container);
10812 this._setAutoZIndex(Math.min);
10813 }
10814 return this;
10815 },
10816
10817 // @method getContainer: HTMLElement
10818 // Returns the HTML element that contains the tiles for this layer.
10819 getContainer: function () {
10820 return this._container;
10821 },
10822
10823 // @method setOpacity(opacity: Number): this
10824 // Changes the [opacity](#gridlayer-opacity) of the grid layer.
10825 setOpacity: function (opacity) {
10826 this.options.opacity = opacity;
10827 this._updateOpacity();
10828 return this;
10829 },
10830
10831 // @method setZIndex(zIndex: Number): this
10832 // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
10833 setZIndex: function (zIndex) {
10834 this.options.zIndex = zIndex;
10835 this._updateZIndex();
10836
10837 return this;
10838 },
10839
10840 // @method isLoading: Boolean
10841 // Returns `true` if any tile in the grid layer has not finished loading.
10842 isLoading: function () {
10843 return this._loading;
10844 },
10845
10846 // @method redraw: this
10847 // Causes the layer to clear all the tiles and request them again.
10848 redraw: function () {
10849 if (this._map) {
10850 this._removeAllTiles();
10851 this._update();
10852 }
10853 return this;
10854 },
10855
10856 getEvents: function () {
10857 var events = {
10858 viewprereset: this._invalidateAll,
10859 viewreset: this._resetView,
10860 zoom: this._resetView,
10861 moveend: this._onMoveEnd
10862 };
10863
10864 if (!this.options.updateWhenIdle) {
10865 // update tiles on move, but not more often than once per given interval
10866 if (!this._onMove) {
10867 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
10868 }
10869
10870 events.move = this._onMove;
10871 }
10872
10873 if (this._zoomAnimated) {
10874 events.zoomanim = this._animateZoom;
10875 }
10876
10877 return events;
10878 },
10879
10880 // @section Extension methods
10881 // Layers extending `GridLayer` shall reimplement the following method.
10882 // @method createTile(coords: Object, done?: Function): HTMLElement
10883 // Called only internally, must be overridden by classes extending `GridLayer`.
10884 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
10885 // is specified, it must be called when the tile has finished loading and drawing.
10886 createTile: function () {
10887 return document.createElement('div');
10888 },
10889
10890 // @section
10891 // @method getTileSize: Point
10892 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
10893 getTileSize: function () {
10894 var s = this.options.tileSize;
10895 return s instanceof Point ? s : new Point(s, s);
10896 },
10897
10898 _updateZIndex: function () {
10899 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
10900 this._container.style.zIndex = this.options.zIndex;
10901 }
10902 },
10903
10904 _setAutoZIndex: function (compare) {
10905 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
10906
10907 var layers = this.getPane().children,
10908 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
10909
10910 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
10911
10912 zIndex = layers[i].style.zIndex;
10913
10914 if (layers[i] !== this._container && zIndex) {
10915 edgeZIndex = compare(edgeZIndex, +zIndex);
10916 }
10917 }
10918
10919 if (isFinite(edgeZIndex)) {
10920 this.options.zIndex = edgeZIndex + compare(-1, 1);
10921 this._updateZIndex();
10922 }
10923 },
10924
10925 _updateOpacity: function () {
10926 if (!this._map) { return; }
10927
10928 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
10929 if (ielt9) { return; }
10930
10931 setOpacity(this._container, this.options.opacity);
10932
10933 var now = +new Date(),
10934 nextFrame = false,
10935 willPrune = false;
10936
10937 for (var key in this._tiles) {
10938 var tile = this._tiles[key];
10939 if (!tile.current || !tile.loaded) { continue; }
10940
10941 var fade = Math.min(1, (now - tile.loaded) / 200);
10942
10943 setOpacity(tile.el, fade);
10944 if (fade < 1) {
10945 nextFrame = true;
10946 } else {
10947 if (tile.active) {
10948 willPrune = true;
10949 } else {
10950 this._onOpaqueTile(tile);
10951 }
10952 tile.active = true;
10953 }
10954 }
10955
10956 if (willPrune && !this._noPrune) { this._pruneTiles(); }
10957
10958 if (nextFrame) {
10959 cancelAnimFrame(this._fadeFrame);
10960 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
10961 }
10962 },
10963
10964 _onOpaqueTile: falseFn,
10965
10966 _initContainer: function () {
10967 if (this._container) { return; }
10968
10969 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
10970 this._updateZIndex();
10971
10972 if (this.options.opacity < 1) {
10973 this._updateOpacity();
10974 }
10975
10976 this.getPane().appendChild(this._container);
10977 },
10978
10979 _updateLevels: function () {
10980
10981 var zoom = this._tileZoom,
10982 maxZoom = this.options.maxZoom;
10983
10984 if (zoom === undefined) { return undefined; }
10985
10986 for (var z in this._levels) {
10987 z = Number(z);
10988 if (this._levels[z].el.children.length || z === zoom) {
10989 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
10990 this._onUpdateLevel(z);
10991 } else {
10992 remove(this._levels[z].el);
10993 this._removeTilesAtZoom(z);
10994 this._onRemoveLevel(z);
10995 delete this._levels[z];
10996 }
10997 }
10998
10999 var level = this._levels[zoom],
11000 map = this._map;
11001
11002 if (!level) {
11003 level = this._levels[zoom] = {};
11004
11005 level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
11006 level.el.style.zIndex = maxZoom;
11007
11008 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
11009 level.zoom = zoom;
11010
11011 this._setZoomTransform(level, map.getCenter(), map.getZoom());
11012
11013 // force the browser to consider the newly added element for transition
11014 falseFn(level.el.offsetWidth);
11015
11016 this._onCreateLevel(level);
11017 }
11018
11019 this._level = level;
11020
11021 return level;
11022 },
11023
11024 _onUpdateLevel: falseFn,
11025
11026 _onRemoveLevel: falseFn,
11027
11028 _onCreateLevel: falseFn,
11029
11030 _pruneTiles: function () {
11031 if (!this._map) {
11032 return;
11033 }
11034
11035 var key, tile;
11036
11037 var zoom = this._map.getZoom();
11038 if (zoom > this.options.maxZoom ||
11039 zoom < this.options.minZoom) {
11040 this._removeAllTiles();
11041 return;
11042 }
11043
11044 for (key in this._tiles) {
11045 tile = this._tiles[key];
11046 tile.retain = tile.current;
11047 }
11048
11049 for (key in this._tiles) {
11050 tile = this._tiles[key];
11051 if (tile.current && !tile.active) {
11052 var coords = tile.coords;
11053 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
11054 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
11055 }
11056 }
11057 }
11058
11059 for (key in this._tiles) {
11060 if (!this._tiles[key].retain) {
11061 this._removeTile(key);
11062 }
11063 }
11064 },
11065
11066 _removeTilesAtZoom: function (zoom) {
11067 for (var key in this._tiles) {
11068 if (this._tiles[key].coords.z !== zoom) {
11069 continue;
11070 }
11071 this._removeTile(key);
11072 }
11073 },
11074
11075 _removeAllTiles: function () {
11076 for (var key in this._tiles) {
11077 this._removeTile(key);
11078 }
11079 },
11080
11081 _invalidateAll: function () {
11082 for (var z in this._levels) {
11083 remove(this._levels[z].el);
11084 this._onRemoveLevel(Number(z));
11085 delete this._levels[z];
11086 }
11087 this._removeAllTiles();
11088
11089 this._tileZoom = undefined;
11090 },
11091
11092 _retainParent: function (x, y, z, minZoom) {
11093 var x2 = Math.floor(x / 2),
11094 y2 = Math.floor(y / 2),
11095 z2 = z - 1,
11096 coords2 = new Point(+x2, +y2);
11097 coords2.z = +z2;
11098
11099 var key = this._tileCoordsToKey(coords2),
11100 tile = this._tiles[key];
11101
11102 if (tile && tile.active) {
11103 tile.retain = true;
11104 return true;
11105
11106 } else if (tile && tile.loaded) {
11107 tile.retain = true;
11108 }
11109
11110 if (z2 > minZoom) {
11111 return this._retainParent(x2, y2, z2, minZoom);
11112 }
11113
11114 return false;
11115 },
11116
11117 _retainChildren: function (x, y, z, maxZoom) {
11118
11119 for (var i = 2 * x; i < 2 * x + 2; i++) {
11120 for (var j = 2 * y; j < 2 * y + 2; j++) {
11121
11122 var coords = new Point(i, j);
11123 coords.z = z + 1;
11124
11125 var key = this._tileCoordsToKey(coords),
11126 tile = this._tiles[key];
11127
11128 if (tile && tile.active) {
11129 tile.retain = true;
11130 continue;
11131
11132 } else if (tile && tile.loaded) {
11133 tile.retain = true;
11134 }
11135
11136 if (z + 1 < maxZoom) {
11137 this._retainChildren(i, j, z + 1, maxZoom);
11138 }
11139 }
11140 }
11141 },
11142
11143 _resetView: function (e) {
11144 var animating = e && (e.pinch || e.flyTo);
11145 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
11146 },
11147
11148 _animateZoom: function (e) {
11149 this._setView(e.center, e.zoom, true, e.noUpdate);
11150 },
11151
11152 _clampZoom: function (zoom) {
11153 var options = this.options;
11154
11155 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
11156 return options.minNativeZoom;
11157 }
11158
11159 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
11160 return options.maxNativeZoom;
11161 }
11162
11163 return zoom;
11164 },
11165
11166 _setView: function (center, zoom, noPrune, noUpdate) {
11167 var tileZoom = Math.round(zoom);
11168 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
11169 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
11170 tileZoom = undefined;
11171 } else {
11172 tileZoom = this._clampZoom(tileZoom);
11173 }
11174
11175 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
11176
11177 if (!noUpdate || tileZoomChanged) {
11178
11179 this._tileZoom = tileZoom;
11180
11181 if (this._abortLoading) {
11182 this._abortLoading();
11183 }
11184
11185 this._updateLevels();
11186 this._resetGrid();
11187
11188 if (tileZoom !== undefined) {
11189 this._update(center);
11190 }
11191
11192 if (!noPrune) {
11193 this._pruneTiles();
11194 }
11195
11196 // Flag to prevent _updateOpacity from pruning tiles during
11197 // a zoom anim or a pinch gesture
11198 this._noPrune = !!noPrune;
11199 }
11200
11201 this._setZoomTransforms(center, zoom);
11202 },
11203
11204 _setZoomTransforms: function (center, zoom) {
11205 for (var i in this._levels) {
11206 this._setZoomTransform(this._levels[i], center, zoom);
11207 }
11208 },
11209
11210 _setZoomTransform: function (level, center, zoom) {
11211 var scale = this._map.getZoomScale(zoom, level.zoom),
11212 translate = level.origin.multiplyBy(scale)
11213 .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
11214
11215 if (any3d) {
11216 setTransform(level.el, translate, scale);
11217 } else {
11218 setPosition(level.el, translate);
11219 }
11220 },
11221
11222 _resetGrid: function () {
11223 var map = this._map,
11224 crs = map.options.crs,
11225 tileSize = this._tileSize = this.getTileSize(),
11226 tileZoom = this._tileZoom;
11227
11228 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
11229 if (bounds) {
11230 this._globalTileRange = this._pxBoundsToTileRange(bounds);
11231 }
11232
11233 this._wrapX = crs.wrapLng && !this.options.noWrap && [
11234 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
11235 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
11236 ];
11237 this._wrapY = crs.wrapLat && !this.options.noWrap && [
11238 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
11239 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
11240 ];
11241 },
11242
11243 _onMoveEnd: function () {
11244 if (!this._map || this._map._animatingZoom) { return; }
11245
11246 this._update();
11247 },
11248
11249 _getTiledPixelBounds: function (center) {
11250 var map = this._map,
11251 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
11252 scale = map.getZoomScale(mapZoom, this._tileZoom),
11253 pixelCenter = map.project(center, this._tileZoom).floor(),
11254 halfSize = map.getSize().divideBy(scale * 2);
11255
11256 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
11257 },
11258
11259 // Private method to load tiles in the grid's active zoom level according to map bounds
11260 _update: function (center) {
11261 var map = this._map;
11262 if (!map) { return; }
11263 var zoom = this._clampZoom(map.getZoom());
11264
11265 if (center === undefined) { center = map.getCenter(); }
11266 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom
11267
11268 var pixelBounds = this._getTiledPixelBounds(center),
11269 tileRange = this._pxBoundsToTileRange(pixelBounds),
11270 tileCenter = tileRange.getCenter(),
11271 queue = [],
11272 margin = this.options.keepBuffer,
11273 noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
11274 tileRange.getTopRight().add([margin, -margin]));
11275
11276 // Sanity check: panic if the tile range contains Infinity somewhere.
11277 if (!(isFinite(tileRange.min.x) &&
11278 isFinite(tileRange.min.y) &&
11279 isFinite(tileRange.max.x) &&
11280 isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
11281
11282 for (var key in this._tiles) {
11283 var c = this._tiles[key].coords;
11284 if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
11285 this._tiles[key].current = false;
11286 }
11287 }
11288
11289 // _update just loads more tiles. If the tile zoom level differs too much
11290 // from the map's, let _setView reset levels and prune old tiles.
11291 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
11292
11293 // create a queue of coordinates to load tiles from
11294 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
11295 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
11296 var coords = new Point(i, j);
11297 coords.z = this._tileZoom;
11298
11299 if (!this._isValidTile(coords)) { continue; }
11300
11301 var tile = this._tiles[this._tileCoordsToKey(coords)];
11302 if (tile) {
11303 tile.current = true;
11304 } else {
11305 queue.push(coords);
11306 }
11307 }
11308 }
11309
11310 // sort tile queue to load tiles in order of their distance to center
11311 queue.sort(function (a, b) {
11312 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
11313 });
11314
11315 if (queue.length !== 0) {
11316 // if it's the first batch of tiles to load
11317 if (!this._loading) {
11318 this._loading = true;
11319 // @event loading: Event
11320 // Fired when the grid layer starts loading tiles.
11321 this.fire('loading');
11322 }
11323
11324 // create DOM fragment to append tiles in one batch
11325 var fragment = document.createDocumentFragment();
11326
11327 for (i = 0; i < queue.length; i++) {
11328 this._addTile(queue[i], fragment);
11329 }
11330
11331 this._level.el.appendChild(fragment);
11332 }
11333 },
11334
11335 _isValidTile: function (coords) {
11336 var crs = this._map.options.crs;
11337
11338 if (!crs.infinite) {
11339 // don't load tile if it's out of bounds and not wrapped
11340 var bounds = this._globalTileRange;
11341 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
11342 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
11343 }
11344
11345 if (!this.options.bounds) { return true; }
11346
11347 // don't load tile if it doesn't intersect the bounds in options
11348 var tileBounds = this._tileCoordsToBounds(coords);
11349 return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
11350 },
11351
11352 _keyToBounds: function (key) {
11353 return this._tileCoordsToBounds(this._keyToTileCoords(key));
11354 },
11355
11356 _tileCoordsToNwSe: function (coords) {
11357 var map = this._map,
11358 tileSize = this.getTileSize(),
11359 nwPoint = coords.scaleBy(tileSize),
11360 sePoint = nwPoint.add(tileSize),
11361 nw = map.unproject(nwPoint, coords.z),
11362 se = map.unproject(sePoint, coords.z);
11363 return [nw, se];
11364 },
11365
11366 // converts tile coordinates to its geographical bounds
11367 _tileCoordsToBounds: function (coords) {
11368 var bp = this._tileCoordsToNwSe(coords),
11369 bounds = new LatLngBounds(bp[0], bp[1]);
11370
11371 if (!this.options.noWrap) {
11372 bounds = this._map.wrapLatLngBounds(bounds);
11373 }
11374 return bounds;
11375 },
11376 // converts tile coordinates to key for the tile cache
11377 _tileCoordsToKey: function (coords) {
11378 return coords.x + ':' + coords.y + ':' + coords.z;
11379 },
11380
11381 // converts tile cache key to coordinates
11382 _keyToTileCoords: function (key) {
11383 var k = key.split(':'),
11384 coords = new Point(+k[0], +k[1]);
11385 coords.z = +k[2];
11386 return coords;
11387 },
11388
11389 _removeTile: function (key) {
11390 var tile = this._tiles[key];
11391 if (!tile) { return; }
11392
11393 remove(tile.el);
11394
11395 delete this._tiles[key];
11396
11397 // @event tileunload: TileEvent
11398 // Fired when a tile is removed (e.g. when a tile goes off the screen).
11399 this.fire('tileunload', {
11400 tile: tile.el,
11401 coords: this._keyToTileCoords(key)
11402 });
11403 },
11404
11405 _initTile: function (tile) {
11406 addClass(tile, 'leaflet-tile');
11407
11408 var tileSize = this.getTileSize();
11409 tile.style.width = tileSize.x + 'px';
11410 tile.style.height = tileSize.y + 'px';
11411
11412 tile.onselectstart = falseFn;
11413 tile.onmousemove = falseFn;
11414
11415 // update opacity on tiles in IE7-8 because of filter inheritance problems
11416 if (ielt9 && this.options.opacity < 1) {
11417 setOpacity(tile, this.options.opacity);
11418 }
11419
11420 // without this hack, tiles disappear after zoom on Chrome for Android
11421 // https://github.com/Leaflet/Leaflet/issues/2078
11422 if (android && !android23) {
11423 tile.style.WebkitBackfaceVisibility = 'hidden';
11424 }
11425 },
11426
11427 _addTile: function (coords, container) {
11428 var tilePos = this._getTilePos(coords),
11429 key = this._tileCoordsToKey(coords);
11430
11431 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
11432
11433 this._initTile(tile);
11434
11435 // if createTile is defined with a second argument ("done" callback),
11436 // we know that tile is async and will be ready later; otherwise
11437 if (this.createTile.length < 2) {
11438 // mark tile as ready, but delay one frame for opacity animation to happen
11439 requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
11440 }
11441
11442 setPosition(tile, tilePos);
11443
11444 // save tile in cache
11445 this._tiles[key] = {
11446 el: tile,
11447 coords: coords,
11448 current: true
11449 };
11450
11451 container.appendChild(tile);
11452 // @event tileloadstart: TileEvent
11453 // Fired when a tile is requested and starts loading.
11454 this.fire('tileloadstart', {
11455 tile: tile,
11456 coords: coords
11457 });
11458 },
11459
11460 _tileReady: function (coords, err, tile) {
11461 if (err) {
11462 // @event tileerror: TileErrorEvent
11463 // Fired when there is an error loading a tile.
11464 this.fire('tileerror', {
11465 error: err,
11466 tile: tile,
11467 coords: coords
11468 });
11469 }
11470
11471 var key = this._tileCoordsToKey(coords);
11472
11473 tile = this._tiles[key];
11474 if (!tile) { return; }
11475
11476 tile.loaded = +new Date();
11477 if (this._map._fadeAnimated) {
11478 setOpacity(tile.el, 0);
11479 cancelAnimFrame(this._fadeFrame);
11480 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11481 } else {
11482 tile.active = true;
11483 this._pruneTiles();
11484 }
11485
11486 if (!err) {
11487 addClass(tile.el, 'leaflet-tile-loaded');
11488
11489 // @event tileload: TileEvent
11490 // Fired when a tile loads.
11491 this.fire('tileload', {
11492 tile: tile.el,
11493 coords: coords
11494 });
11495 }
11496
11497 if (this._noTilesToLoad()) {
11498 this._loading = false;
11499 // @event load: Event
11500 // Fired when the grid layer loaded all visible tiles.
11501 this.fire('load');
11502
11503 if (ielt9 || !this._map._fadeAnimated) {
11504 requestAnimFrame(this._pruneTiles, this);
11505 } else {
11506 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
11507 // to trigger a pruning.
11508 setTimeout(bind(this._pruneTiles, this), 250);
11509 }
11510 }
11511 },
11512
11513 _getTilePos: function (coords) {
11514 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
11515 },
11516
11517 _wrapCoords: function (coords) {
11518 var newCoords = new Point(
11519 this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
11520 this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
11521 newCoords.z = coords.z;
11522 return newCoords;
11523 },
11524
11525 _pxBoundsToTileRange: function (bounds) {
11526 var tileSize = this.getTileSize();
11527 return new Bounds(
11528 bounds.min.unscaleBy(tileSize).floor(),
11529 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
11530 },
11531
11532 _noTilesToLoad: function () {
11533 for (var key in this._tiles) {
11534 if (!this._tiles[key].loaded) { return false; }
11535 }
11536 return true;
11537 }
11538});
11539
11540// @factory L.gridLayer(options?: GridLayer options)
11541// Creates a new instance of GridLayer with the supplied options.
11542function gridLayer(options) {
11543 return new GridLayer(options);
11544}
11545
11546/*
11547 * @class TileLayer
11548 * @inherits GridLayer
11549 * @aka L.TileLayer
11550 * 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`.
11551 *
11552 * @example
11553 *
11554 * ```js
11555 * L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar', attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'}).addTo(map);
11556 * ```
11557 *
11558 * @section URL template
11559 * @example
11560 *
11561 * A string of the following form:
11562 *
11563 * ```
11564 * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
11565 * ```
11566 *
11567 * `{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.
11568 *
11569 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
11570 *
11571 * ```
11572 * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
11573 * ```
11574 */
11575
11576
11577var TileLayer = GridLayer.extend({
11578
11579 // @section
11580 // @aka TileLayer options
11581 options: {
11582 // @option minZoom: Number = 0
11583 // The minimum zoom level down to which this layer will be displayed (inclusive).
11584 minZoom: 0,
11585
11586 // @option maxZoom: Number = 18
11587 // The maximum zoom level up to which this layer will be displayed (inclusive).
11588 maxZoom: 18,
11589
11590 // @option subdomains: String|String[] = 'abc'
11591 // 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.
11592 subdomains: 'abc',
11593
11594 // @option errorTileUrl: String = ''
11595 // URL to the tile image to show in place of the tile that failed to load.
11596 errorTileUrl: '',
11597
11598 // @option zoomOffset: Number = 0
11599 // The zoom number used in tile URLs will be offset with this value.
11600 zoomOffset: 0,
11601
11602 // @option tms: Boolean = false
11603 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
11604 tms: false,
11605
11606 // @option zoomReverse: Boolean = false
11607 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
11608 zoomReverse: false,
11609
11610 // @option detectRetina: Boolean = false
11611 // 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.
11612 detectRetina: false,
11613
11614 // @option crossOrigin: Boolean|String = false
11615 // Whether the crossOrigin attribute will be added to the tiles.
11616 // 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.
11617 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
11618 crossOrigin: false
11619 },
11620
11621 initialize: function (url, options) {
11622
11623 this._url = url;
11624
11625 options = setOptions(this, options);
11626
11627 // detecting retina displays, adjusting tileSize and zoom levels
11628 if (options.detectRetina && retina && options.maxZoom > 0) {
11629
11630 options.tileSize = Math.floor(options.tileSize / 2);
11631
11632 if (!options.zoomReverse) {
11633 options.zoomOffset++;
11634 options.maxZoom--;
11635 } else {
11636 options.zoomOffset--;
11637 options.minZoom++;
11638 }
11639
11640 options.minZoom = Math.max(0, options.minZoom);
11641 }
11642
11643 if (typeof options.subdomains === 'string') {
11644 options.subdomains = options.subdomains.split('');
11645 }
11646
11647 // for https://github.com/Leaflet/Leaflet/issues/137
11648 if (!android) {
11649 this.on('tileunload', this._onTileRemove);
11650 }
11651 },
11652
11653 // @method setUrl(url: String, noRedraw?: Boolean): this
11654 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
11655 // If the URL does not change, the layer will not be redrawn unless
11656 // the noRedraw parameter is set to false.
11657 setUrl: function (url, noRedraw) {
11658 if (this._url === url && noRedraw === undefined) {
11659 noRedraw = true;
11660 }
11661
11662 this._url = url;
11663
11664 if (!noRedraw) {
11665 this.redraw();
11666 }
11667 return this;
11668 },
11669
11670 // @method createTile(coords: Object, done?: Function): HTMLElement
11671 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
11672 // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
11673 // callback is called when the tile has been loaded.
11674 createTile: function (coords, done) {
11675 var tile = document.createElement('img');
11676
11677 on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
11678 on(tile, 'error', bind(this._tileOnError, this, done, tile));
11679
11680 if (this.options.crossOrigin || this.options.crossOrigin === '') {
11681 tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
11682 }
11683
11684 /*
11685 Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
11686 http://www.w3.org/TR/WCAG20-TECHS/H67
11687 */
11688 tile.alt = '';
11689
11690 /*
11691 Set role="presentation" to force screen readers to ignore this
11692 https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
11693 */
11694 tile.setAttribute('role', 'presentation');
11695
11696 tile.src = this.getTileUrl(coords);
11697
11698 return tile;
11699 },
11700
11701 // @section Extension methods
11702 // @uninheritable
11703 // Layers extending `TileLayer` might reimplement the following method.
11704 // @method getTileUrl(coords: Object): String
11705 // Called only internally, returns the URL for a tile given its coordinates.
11706 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
11707 getTileUrl: function (coords) {
11708 var data = {
11709 r: retina ? '@2x' : '',
11710 s: this._getSubdomain(coords),
11711 x: coords.x,
11712 y: coords.y,
11713 z: this._getZoomForUrl()
11714 };
11715 if (this._map && !this._map.options.crs.infinite) {
11716 var invertedY = this._globalTileRange.max.y - coords.y;
11717 if (this.options.tms) {
11718 data['y'] = invertedY;
11719 }
11720 data['-y'] = invertedY;
11721 }
11722
11723 return template(this._url, extend(data, this.options));
11724 },
11725
11726 _tileOnLoad: function (done, tile) {
11727 // For https://github.com/Leaflet/Leaflet/issues/3332
11728 if (ielt9) {
11729 setTimeout(bind(done, this, null, tile), 0);
11730 } else {
11731 done(null, tile);
11732 }
11733 },
11734
11735 _tileOnError: function (done, tile, e) {
11736 var errorUrl = this.options.errorTileUrl;
11737 if (errorUrl && tile.getAttribute('src') !== errorUrl) {
11738 tile.src = errorUrl;
11739 }
11740 done(e, tile);
11741 },
11742
11743 _onTileRemove: function (e) {
11744 e.tile.onload = null;
11745 },
11746
11747 _getZoomForUrl: function () {
11748 var zoom = this._tileZoom,
11749 maxZoom = this.options.maxZoom,
11750 zoomReverse = this.options.zoomReverse,
11751 zoomOffset = this.options.zoomOffset;
11752
11753 if (zoomReverse) {
11754 zoom = maxZoom - zoom;
11755 }
11756
11757 return zoom + zoomOffset;
11758 },
11759
11760 _getSubdomain: function (tilePoint) {
11761 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
11762 return this.options.subdomains[index];
11763 },
11764
11765 // stops loading all tiles in the background layer
11766 _abortLoading: function () {
11767 var i, tile;
11768 for (i in this._tiles) {
11769 if (this._tiles[i].coords.z !== this._tileZoom) {
11770 tile = this._tiles[i].el;
11771
11772 tile.onload = falseFn;
11773 tile.onerror = falseFn;
11774
11775 if (!tile.complete) {
11776 tile.src = emptyImageUrl;
11777 remove(tile);
11778 delete this._tiles[i];
11779 }
11780 }
11781 }
11782 },
11783
11784 _removeTile: function (key) {
11785 var tile = this._tiles[key];
11786 if (!tile) { return; }
11787
11788 // Cancels any pending http requests associated with the tile
11789 // unless we're on Android's stock browser,
11790 // see https://github.com/Leaflet/Leaflet/issues/137
11791 if (!androidStock) {
11792 tile.el.setAttribute('src', emptyImageUrl);
11793 }
11794
11795 return GridLayer.prototype._removeTile.call(this, key);
11796 },
11797
11798 _tileReady: function (coords, err, tile) {
11799 if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) {
11800 return;
11801 }
11802
11803 return GridLayer.prototype._tileReady.call(this, coords, err, tile);
11804 }
11805});
11806
11807
11808// @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
11809// Instantiates a tile layer object given a `URL template` and optionally an options object.
11810
11811function tileLayer(url, options) {
11812 return new TileLayer(url, options);
11813}
11814
11815/*
11816 * @class TileLayer.WMS
11817 * @inherits TileLayer
11818 * @aka L.TileLayer.WMS
11819 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
11820 *
11821 * @example
11822 *
11823 * ```js
11824 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
11825 * layers: 'nexrad-n0r-900913',
11826 * format: 'image/png',
11827 * transparent: true,
11828 * attribution: "Weather data © 2012 IEM Nexrad"
11829 * });
11830 * ```
11831 */
11832
11833var TileLayerWMS = TileLayer.extend({
11834
11835 // @section
11836 // @aka TileLayer.WMS options
11837 // If any custom options not documented here are used, they will be sent to the
11838 // WMS server as extra parameters in each request URL. This can be useful for
11839 // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
11840 defaultWmsParams: {
11841 service: 'WMS',
11842 request: 'GetMap',
11843
11844 // @option layers: String = ''
11845 // **(required)** Comma-separated list of WMS layers to show.
11846 layers: '',
11847
11848 // @option styles: String = ''
11849 // Comma-separated list of WMS styles.
11850 styles: '',
11851
11852 // @option format: String = 'image/jpeg'
11853 // WMS image format (use `'image/png'` for layers with transparency).
11854 format: 'image/jpeg',
11855
11856 // @option transparent: Boolean = false
11857 // If `true`, the WMS service will return images with transparency.
11858 transparent: false,
11859
11860 // @option version: String = '1.1.1'
11861 // Version of the WMS service to use
11862 version: '1.1.1'
11863 },
11864
11865 options: {
11866 // @option crs: CRS = null
11867 // Coordinate Reference System to use for the WMS requests, defaults to
11868 // map CRS. Don't change this if you're not sure what it means.
11869 crs: null,
11870
11871 // @option uppercase: Boolean = false
11872 // If `true`, WMS request parameter keys will be uppercase.
11873 uppercase: false
11874 },
11875
11876 initialize: function (url, options) {
11877
11878 this._url = url;
11879
11880 var wmsParams = extend({}, this.defaultWmsParams);
11881
11882 // all keys that are not TileLayer options go to WMS params
11883 for (var i in options) {
11884 if (!(i in this.options)) {
11885 wmsParams[i] = options[i];
11886 }
11887 }
11888
11889 options = setOptions(this, options);
11890
11891 var realRetina = options.detectRetina && retina ? 2 : 1;
11892 var tileSize = this.getTileSize();
11893 wmsParams.width = tileSize.x * realRetina;
11894 wmsParams.height = tileSize.y * realRetina;
11895
11896 this.wmsParams = wmsParams;
11897 },
11898
11899 onAdd: function (map) {
11900
11901 this._crs = this.options.crs || map.options.crs;
11902 this._wmsVersion = parseFloat(this.wmsParams.version);
11903
11904 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
11905 this.wmsParams[projectionKey] = this._crs.code;
11906
11907 TileLayer.prototype.onAdd.call(this, map);
11908 },
11909
11910 getTileUrl: function (coords) {
11911
11912 var tileBounds = this._tileCoordsToNwSe(coords),
11913 crs = this._crs,
11914 bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
11915 min = bounds.min,
11916 max = bounds.max,
11917 bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
11918 [min.y, min.x, max.y, max.x] :
11919 [min.x, min.y, max.x, max.y]).join(','),
11920 url = TileLayer.prototype.getTileUrl.call(this, coords);
11921 return url +
11922 getParamString(this.wmsParams, url, this.options.uppercase) +
11923 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
11924 },
11925
11926 // @method setParams(params: Object, noRedraw?: Boolean): this
11927 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
11928 setParams: function (params, noRedraw) {
11929
11930 extend(this.wmsParams, params);
11931
11932 if (!noRedraw) {
11933 this.redraw();
11934 }
11935
11936 return this;
11937 }
11938});
11939
11940
11941// @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
11942// Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
11943function tileLayerWMS(url, options) {
11944 return new TileLayerWMS(url, options);
11945}
11946
11947TileLayer.WMS = TileLayerWMS;
11948tileLayer.wms = tileLayerWMS;
11949
11950/*
11951 * @class Renderer
11952 * @inherits Layer
11953 * @aka L.Renderer
11954 *
11955 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
11956 * DOM container of the renderer, its bounds, and its zoom animation.
11957 *
11958 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
11959 * itself can be added or removed to the map. All paths use a renderer, which can
11960 * be implicit (the map will decide the type of renderer and use it automatically)
11961 * or explicit (using the [`renderer`](#path-renderer) option of the path).
11962 *
11963 * Do not use this class directly, use `SVG` and `Canvas` instead.
11964 *
11965 * @event update: Event
11966 * Fired when the renderer updates its bounds, center and zoom, for example when
11967 * its map has moved
11968 */
11969
11970var Renderer = Layer.extend({
11971
11972 // @section
11973 // @aka Renderer options
11974 options: {
11975 // @option padding: Number = 0.1
11976 // How much to extend the clip area around the map view (relative to its size)
11977 // e.g. 0.1 would be 10% of map view in each direction
11978 padding: 0.1,
11979
11980 // @option tolerance: Number = 0
11981 // How much to extend click tolerance round a path/object on the map
11982 tolerance : 0
11983 },
11984
11985 initialize: function (options) {
11986 setOptions(this, options);
11987 stamp(this);
11988 this._layers = this._layers || {};
11989 },
11990
11991 onAdd: function () {
11992 if (!this._container) {
11993 this._initContainer(); // defined by renderer implementations
11994
11995 if (this._zoomAnimated) {
11996 addClass(this._container, 'leaflet-zoom-animated');
11997 }
11998 }
11999
12000 this.getPane().appendChild(this._container);
12001 this._update();
12002 this.on('update', this._updatePaths, this);
12003 },
12004
12005 onRemove: function () {
12006 this.off('update', this._updatePaths, this);
12007 this._destroyContainer();
12008 },
12009
12010 getEvents: function () {
12011 var events = {
12012 viewreset: this._reset,
12013 zoom: this._onZoom,
12014 moveend: this._update,
12015 zoomend: this._onZoomEnd
12016 };
12017 if (this._zoomAnimated) {
12018 events.zoomanim = this._onAnimZoom;
12019 }
12020 return events;
12021 },
12022
12023 _onAnimZoom: function (ev) {
12024 this._updateTransform(ev.center, ev.zoom);
12025 },
12026
12027 _onZoom: function () {
12028 this._updateTransform(this._map.getCenter(), this._map.getZoom());
12029 },
12030
12031 _updateTransform: function (center, zoom) {
12032 var scale = this._map.getZoomScale(zoom, this._zoom),
12033 position = getPosition(this._container),
12034 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
12035 currentCenterPoint = this._map.project(this._center, zoom),
12036 destCenterPoint = this._map.project(center, zoom),
12037 centerOffset = destCenterPoint.subtract(currentCenterPoint),
12038
12039 topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
12040
12041 if (any3d) {
12042 setTransform(this._container, topLeftOffset, scale);
12043 } else {
12044 setPosition(this._container, topLeftOffset);
12045 }
12046 },
12047
12048 _reset: function () {
12049 this._update();
12050 this._updateTransform(this._center, this._zoom);
12051
12052 for (var id in this._layers) {
12053 this._layers[id]._reset();
12054 }
12055 },
12056
12057 _onZoomEnd: function () {
12058 for (var id in this._layers) {
12059 this._layers[id]._project();
12060 }
12061 },
12062
12063 _updatePaths: function () {
12064 for (var id in this._layers) {
12065 this._layers[id]._update();
12066 }
12067 },
12068
12069 _update: function () {
12070 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
12071 // Subclasses are responsible of firing the 'update' event.
12072 var p = this.options.padding,
12073 size = this._map.getSize(),
12074 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
12075
12076 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
12077
12078 this._center = this._map.getCenter();
12079 this._zoom = this._map.getZoom();
12080 }
12081});
12082
12083/*
12084 * @class Canvas
12085 * @inherits Renderer
12086 * @aka L.Canvas
12087 *
12088 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
12089 * Inherits `Renderer`.
12090 *
12091 * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
12092 * available in all web browsers, notably IE8, and overlapping geometries might
12093 * not display properly in some edge cases.
12094 *
12095 * @example
12096 *
12097 * Use Canvas by default for all paths in the map:
12098 *
12099 * ```js
12100 * var map = L.map('map', {
12101 * renderer: L.canvas()
12102 * });
12103 * ```
12104 *
12105 * Use a Canvas renderer with extra padding for specific vector geometries:
12106 *
12107 * ```js
12108 * var map = L.map('map');
12109 * var myRenderer = L.canvas({ padding: 0.5 });
12110 * var line = L.polyline( coordinates, { renderer: myRenderer } );
12111 * var circle = L.circle( center, { renderer: myRenderer } );
12112 * ```
12113 */
12114
12115var Canvas = Renderer.extend({
12116 getEvents: function () {
12117 var events = Renderer.prototype.getEvents.call(this);
12118 events.viewprereset = this._onViewPreReset;
12119 return events;
12120 },
12121
12122 _onViewPreReset: function () {
12123 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
12124 this._postponeUpdatePaths = true;
12125 },
12126
12127 onAdd: function () {
12128 Renderer.prototype.onAdd.call(this);
12129
12130 // Redraw vectors since canvas is cleared upon removal,
12131 // in case of removing the renderer itself from the map.
12132 this._draw();
12133 },
12134
12135 _initContainer: function () {
12136 var container = this._container = document.createElement('canvas');
12137
12138 on(container, 'mousemove', this._onMouseMove, this);
12139 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
12140 on(container, 'mouseout', this._handleMouseOut, this);
12141
12142 this._ctx = container.getContext('2d');
12143 },
12144
12145 _destroyContainer: function () {
12146 cancelAnimFrame(this._redrawRequest);
12147 delete this._ctx;
12148 remove(this._container);
12149 off(this._container);
12150 delete this._container;
12151 },
12152
12153 _updatePaths: function () {
12154 if (this._postponeUpdatePaths) { return; }
12155
12156 var layer;
12157 this._redrawBounds = null;
12158 for (var id in this._layers) {
12159 layer = this._layers[id];
12160 layer._update();
12161 }
12162 this._redraw();
12163 },
12164
12165 _update: function () {
12166 if (this._map._animatingZoom && this._bounds) { return; }
12167
12168 Renderer.prototype._update.call(this);
12169
12170 var b = this._bounds,
12171 container = this._container,
12172 size = b.getSize(),
12173 m = retina ? 2 : 1;
12174
12175 setPosition(container, b.min);
12176
12177 // set canvas size (also clearing it); use double size on retina
12178 container.width = m * size.x;
12179 container.height = m * size.y;
12180 container.style.width = size.x + 'px';
12181 container.style.height = size.y + 'px';
12182
12183 if (retina) {
12184 this._ctx.scale(2, 2);
12185 }
12186
12187 // translate so we use the same path coordinates after canvas element moves
12188 this._ctx.translate(-b.min.x, -b.min.y);
12189
12190 // Tell paths to redraw themselves
12191 this.fire('update');
12192 },
12193
12194 _reset: function () {
12195 Renderer.prototype._reset.call(this);
12196
12197 if (this._postponeUpdatePaths) {
12198 this._postponeUpdatePaths = false;
12199 this._updatePaths();
12200 }
12201 },
12202
12203 _initPath: function (layer) {
12204 this._updateDashArray(layer);
12205 this._layers[stamp(layer)] = layer;
12206
12207 var order = layer._order = {
12208 layer: layer,
12209 prev: this._drawLast,
12210 next: null
12211 };
12212 if (this._drawLast) { this._drawLast.next = order; }
12213 this._drawLast = order;
12214 this._drawFirst = this._drawFirst || this._drawLast;
12215 },
12216
12217 _addPath: function (layer) {
12218 this._requestRedraw(layer);
12219 },
12220
12221 _removePath: function (layer) {
12222 var order = layer._order;
12223 var next = order.next;
12224 var prev = order.prev;
12225
12226 if (next) {
12227 next.prev = prev;
12228 } else {
12229 this._drawLast = prev;
12230 }
12231 if (prev) {
12232 prev.next = next;
12233 } else {
12234 this._drawFirst = next;
12235 }
12236
12237 delete layer._order;
12238
12239 delete this._layers[stamp(layer)];
12240
12241 this._requestRedraw(layer);
12242 },
12243
12244 _updatePath: function (layer) {
12245 // Redraw the union of the layer's old pixel
12246 // bounds and the new pixel bounds.
12247 this._extendRedrawBounds(layer);
12248 layer._project();
12249 layer._update();
12250 // The redraw will extend the redraw bounds
12251 // with the new pixel bounds.
12252 this._requestRedraw(layer);
12253 },
12254
12255 _updateStyle: function (layer) {
12256 this._updateDashArray(layer);
12257 this._requestRedraw(layer);
12258 },
12259
12260 _updateDashArray: function (layer) {
12261 if (typeof layer.options.dashArray === 'string') {
12262 var parts = layer.options.dashArray.split(/[, ]+/),
12263 dashArray = [],
12264 dashValue,
12265 i;
12266 for (i = 0; i < parts.length; i++) {
12267 dashValue = Number(parts[i]);
12268 // Ignore dash array containing invalid lengths
12269 if (isNaN(dashValue)) { return; }
12270 dashArray.push(dashValue);
12271 }
12272 layer.options._dashArray = dashArray;
12273 } else {
12274 layer.options._dashArray = layer.options.dashArray;
12275 }
12276 },
12277
12278 _requestRedraw: function (layer) {
12279 if (!this._map) { return; }
12280
12281 this._extendRedrawBounds(layer);
12282 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
12283 },
12284
12285 _extendRedrawBounds: function (layer) {
12286 if (layer._pxBounds) {
12287 var padding = (layer.options.weight || 0) + 1;
12288 this._redrawBounds = this._redrawBounds || new Bounds();
12289 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
12290 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
12291 }
12292 },
12293
12294 _redraw: function () {
12295 this._redrawRequest = null;
12296
12297 if (this._redrawBounds) {
12298 this._redrawBounds.min._floor();
12299 this._redrawBounds.max._ceil();
12300 }
12301
12302 this._clear(); // clear layers in redraw bounds
12303 this._draw(); // draw layers
12304
12305 this._redrawBounds = null;
12306 },
12307
12308 _clear: function () {
12309 var bounds = this._redrawBounds;
12310 if (bounds) {
12311 var size = bounds.getSize();
12312 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
12313 } else {
12314 this._ctx.save();
12315 this._ctx.setTransform(1, 0, 0, 1, 0, 0);
12316 this._ctx.clearRect(0, 0, this._container.width, this._container.height);
12317 this._ctx.restore();
12318 }
12319 },
12320
12321 _draw: function () {
12322 var layer, bounds = this._redrawBounds;
12323 this._ctx.save();
12324 if (bounds) {
12325 var size = bounds.getSize();
12326 this._ctx.beginPath();
12327 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
12328 this._ctx.clip();
12329 }
12330
12331 this._drawing = true;
12332
12333 for (var order = this._drawFirst; order; order = order.next) {
12334 layer = order.layer;
12335 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
12336 layer._updatePath();
12337 }
12338 }
12339
12340 this._drawing = false;
12341
12342 this._ctx.restore(); // Restore state before clipping.
12343 },
12344
12345 _updatePoly: function (layer, closed) {
12346 if (!this._drawing) { return; }
12347
12348 var i, j, len2, p,
12349 parts = layer._parts,
12350 len = parts.length,
12351 ctx = this._ctx;
12352
12353 if (!len) { return; }
12354
12355 ctx.beginPath();
12356
12357 for (i = 0; i < len; i++) {
12358 for (j = 0, len2 = parts[i].length; j < len2; j++) {
12359 p = parts[i][j];
12360 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
12361 }
12362 if (closed) {
12363 ctx.closePath();
12364 }
12365 }
12366
12367 this._fillStroke(ctx, layer);
12368
12369 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
12370 },
12371
12372 _updateCircle: function (layer) {
12373
12374 if (!this._drawing || layer._empty()) { return; }
12375
12376 var p = layer._point,
12377 ctx = this._ctx,
12378 r = Math.max(Math.round(layer._radius), 1),
12379 s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
12380
12381 if (s !== 1) {
12382 ctx.save();
12383 ctx.scale(1, s);
12384 }
12385
12386 ctx.beginPath();
12387 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
12388
12389 if (s !== 1) {
12390 ctx.restore();
12391 }
12392
12393 this._fillStroke(ctx, layer);
12394 },
12395
12396 _fillStroke: function (ctx, layer) {
12397 var options = layer.options;
12398
12399 if (options.fill) {
12400 ctx.globalAlpha = options.fillOpacity;
12401 ctx.fillStyle = options.fillColor || options.color;
12402 ctx.fill(options.fillRule || 'evenodd');
12403 }
12404
12405 if (options.stroke && options.weight !== 0) {
12406 if (ctx.setLineDash) {
12407 ctx.setLineDash(layer.options && layer.options._dashArray || []);
12408 }
12409 ctx.globalAlpha = options.opacity;
12410 ctx.lineWidth = options.weight;
12411 ctx.strokeStyle = options.color;
12412 ctx.lineCap = options.lineCap;
12413 ctx.lineJoin = options.lineJoin;
12414 ctx.stroke();
12415 }
12416 },
12417
12418 // Canvas obviously doesn't have mouse events for individual drawn objects,
12419 // so we emulate that by calculating what's under the mouse on mousemove/click manually
12420
12421 _onClick: function (e) {
12422 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
12423
12424 for (var order = this._drawFirst; order; order = order.next) {
12425 layer = order.layer;
12426 if (layer.options.interactive && layer._containsPoint(point)) {
12427 if (!(e.type === 'click' || e.type !== 'preclick') || !this._map._draggableMoved(layer)) {
12428 clickedLayer = layer;
12429 }
12430 }
12431 }
12432 if (clickedLayer) {
12433 fakeStop(e);
12434 this._fireEvent([clickedLayer], e);
12435 }
12436 },
12437
12438 _onMouseMove: function (e) {
12439 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
12440
12441 var point = this._map.mouseEventToLayerPoint(e);
12442 this._handleMouseHover(e, point);
12443 },
12444
12445
12446 _handleMouseOut: function (e) {
12447 var layer = this._hoveredLayer;
12448 if (layer) {
12449 // if we're leaving the layer, fire mouseout
12450 removeClass(this._container, 'leaflet-interactive');
12451 this._fireEvent([layer], e, 'mouseout');
12452 this._hoveredLayer = null;
12453 this._mouseHoverThrottled = false;
12454 }
12455 },
12456
12457 _handleMouseHover: function (e, point) {
12458 if (this._mouseHoverThrottled) {
12459 return;
12460 }
12461
12462 var layer, candidateHoveredLayer;
12463
12464 for (var order = this._drawFirst; order; order = order.next) {
12465 layer = order.layer;
12466 if (layer.options.interactive && layer._containsPoint(point)) {
12467 candidateHoveredLayer = layer;
12468 }
12469 }
12470
12471 if (candidateHoveredLayer !== this._hoveredLayer) {
12472 this._handleMouseOut(e);
12473
12474 if (candidateHoveredLayer) {
12475 addClass(this._container, 'leaflet-interactive'); // change cursor
12476 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
12477 this._hoveredLayer = candidateHoveredLayer;
12478 }
12479 }
12480
12481 if (this._hoveredLayer) {
12482 this._fireEvent([this._hoveredLayer], e);
12483 }
12484
12485 this._mouseHoverThrottled = true;
12486 setTimeout(bind(function () {
12487 this._mouseHoverThrottled = false;
12488 }, this), 32);
12489 },
12490
12491 _fireEvent: function (layers, e, type) {
12492 this._map._fireDOMEvent(e, type || e.type, layers);
12493 },
12494
12495 _bringToFront: function (layer) {
12496 var order = layer._order;
12497
12498 if (!order) { return; }
12499
12500 var next = order.next;
12501 var prev = order.prev;
12502
12503 if (next) {
12504 next.prev = prev;
12505 } else {
12506 // Already last
12507 return;
12508 }
12509 if (prev) {
12510 prev.next = next;
12511 } else if (next) {
12512 // Update first entry unless this is the
12513 // single entry
12514 this._drawFirst = next;
12515 }
12516
12517 order.prev = this._drawLast;
12518 this._drawLast.next = order;
12519
12520 order.next = null;
12521 this._drawLast = order;
12522
12523 this._requestRedraw(layer);
12524 },
12525
12526 _bringToBack: function (layer) {
12527 var order = layer._order;
12528
12529 if (!order) { return; }
12530
12531 var next = order.next;
12532 var prev = order.prev;
12533
12534 if (prev) {
12535 prev.next = next;
12536 } else {
12537 // Already first
12538 return;
12539 }
12540 if (next) {
12541 next.prev = prev;
12542 } else if (prev) {
12543 // Update last entry unless this is the
12544 // single entry
12545 this._drawLast = prev;
12546 }
12547
12548 order.prev = null;
12549
12550 order.next = this._drawFirst;
12551 this._drawFirst.prev = order;
12552 this._drawFirst = order;
12553
12554 this._requestRedraw(layer);
12555 }
12556});
12557
12558// @factory L.canvas(options?: Renderer options)
12559// Creates a Canvas renderer with the given options.
12560function canvas$1(options) {
12561 return canvas ? new Canvas(options) : null;
12562}
12563
12564/*
12565 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
12566 */
12567
12568
12569var vmlCreate = (function () {
12570 try {
12571 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
12572 return function (name) {
12573 return document.createElement('<lvml:' + name + ' class="lvml">');
12574 };
12575 } catch (e) {
12576 return function (name) {
12577 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
12578 };
12579 }
12580})();
12581
12582
12583/*
12584 * @class SVG
12585 *
12586 *
12587 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
12588 * with old versions of Internet Explorer.
12589 */
12590
12591// mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
12592var vmlMixin = {
12593
12594 _initContainer: function () {
12595 this._container = create$1('div', 'leaflet-vml-container');
12596 },
12597
12598 _update: function () {
12599 if (this._map._animatingZoom) { return; }
12600 Renderer.prototype._update.call(this);
12601 this.fire('update');
12602 },
12603
12604 _initPath: function (layer) {
12605 var container = layer._container = vmlCreate('shape');
12606
12607 addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
12608
12609 container.coordsize = '1 1';
12610
12611 layer._path = vmlCreate('path');
12612 container.appendChild(layer._path);
12613
12614 this._updateStyle(layer);
12615 this._layers[stamp(layer)] = layer;
12616 },
12617
12618 _addPath: function (layer) {
12619 var container = layer._container;
12620 this._container.appendChild(container);
12621
12622 if (layer.options.interactive) {
12623 layer.addInteractiveTarget(container);
12624 }
12625 },
12626
12627 _removePath: function (layer) {
12628 var container = layer._container;
12629 remove(container);
12630 layer.removeInteractiveTarget(container);
12631 delete this._layers[stamp(layer)];
12632 },
12633
12634 _updateStyle: function (layer) {
12635 var stroke = layer._stroke,
12636 fill = layer._fill,
12637 options = layer.options,
12638 container = layer._container;
12639
12640 container.stroked = !!options.stroke;
12641 container.filled = !!options.fill;
12642
12643 if (options.stroke) {
12644 if (!stroke) {
12645 stroke = layer._stroke = vmlCreate('stroke');
12646 }
12647 container.appendChild(stroke);
12648 stroke.weight = options.weight + 'px';
12649 stroke.color = options.color;
12650 stroke.opacity = options.opacity;
12651
12652 if (options.dashArray) {
12653 stroke.dashStyle = isArray(options.dashArray) ?
12654 options.dashArray.join(' ') :
12655 options.dashArray.replace(/( *, *)/g, ' ');
12656 } else {
12657 stroke.dashStyle = '';
12658 }
12659 stroke.endcap = options.lineCap.replace('butt', 'flat');
12660 stroke.joinstyle = options.lineJoin;
12661
12662 } else if (stroke) {
12663 container.removeChild(stroke);
12664 layer._stroke = null;
12665 }
12666
12667 if (options.fill) {
12668 if (!fill) {
12669 fill = layer._fill = vmlCreate('fill');
12670 }
12671 container.appendChild(fill);
12672 fill.color = options.fillColor || options.color;
12673 fill.opacity = options.fillOpacity;
12674
12675 } else if (fill) {
12676 container.removeChild(fill);
12677 layer._fill = null;
12678 }
12679 },
12680
12681 _updateCircle: function (layer) {
12682 var p = layer._point.round(),
12683 r = Math.round(layer._radius),
12684 r2 = Math.round(layer._radiusY || r);
12685
12686 this._setPath(layer, layer._empty() ? 'M0 0' :
12687 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
12688 },
12689
12690 _setPath: function (layer, path) {
12691 layer._path.v = path;
12692 },
12693
12694 _bringToFront: function (layer) {
12695 toFront(layer._container);
12696 },
12697
12698 _bringToBack: function (layer) {
12699 toBack(layer._container);
12700 }
12701};
12702
12703var create$2 = vml ? vmlCreate : svgCreate;
12704
12705/*
12706 * @class SVG
12707 * @inherits Renderer
12708 * @aka L.SVG
12709 *
12710 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
12711 * Inherits `Renderer`.
12712 *
12713 * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
12714 * available in all web browsers, notably Android 2.x and 3.x.
12715 *
12716 * Although SVG is not available on IE7 and IE8, these browsers support
12717 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
12718 * (a now deprecated technology), and the SVG renderer will fall back to VML in
12719 * this case.
12720 *
12721 * @example
12722 *
12723 * Use SVG by default for all paths in the map:
12724 *
12725 * ```js
12726 * var map = L.map('map', {
12727 * renderer: L.svg()
12728 * });
12729 * ```
12730 *
12731 * Use a SVG renderer with extra padding for specific vector geometries:
12732 *
12733 * ```js
12734 * var map = L.map('map');
12735 * var myRenderer = L.svg({ padding: 0.5 });
12736 * var line = L.polyline( coordinates, { renderer: myRenderer } );
12737 * var circle = L.circle( center, { renderer: myRenderer } );
12738 * ```
12739 */
12740
12741var SVG = Renderer.extend({
12742
12743 getEvents: function () {
12744 var events = Renderer.prototype.getEvents.call(this);
12745 events.zoomstart = this._onZoomStart;
12746 return events;
12747 },
12748
12749 _initContainer: function () {
12750 this._container = create$2('svg');
12751
12752 // makes it possible to click through svg root; we'll reset it back in individual paths
12753 this._container.setAttribute('pointer-events', 'none');
12754
12755 this._rootGroup = create$2('g');
12756 this._container.appendChild(this._rootGroup);
12757 },
12758
12759 _destroyContainer: function () {
12760 remove(this._container);
12761 off(this._container);
12762 delete this._container;
12763 delete this._rootGroup;
12764 delete this._svgSize;
12765 },
12766
12767 _onZoomStart: function () {
12768 // Drag-then-pinch interactions might mess up the center and zoom.
12769 // In this case, the easiest way to prevent this is re-do the renderer
12770 // bounds and padding when the zooming starts.
12771 this._update();
12772 },
12773
12774 _update: function () {
12775 if (this._map._animatingZoom && this._bounds) { return; }
12776
12777 Renderer.prototype._update.call(this);
12778
12779 var b = this._bounds,
12780 size = b.getSize(),
12781 container = this._container;
12782
12783 // set size of svg-container if changed
12784 if (!this._svgSize || !this._svgSize.equals(size)) {
12785 this._svgSize = size;
12786 container.setAttribute('width', size.x);
12787 container.setAttribute('height', size.y);
12788 }
12789
12790 // movement: update container viewBox so that we don't have to change coordinates of individual layers
12791 setPosition(container, b.min);
12792 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
12793
12794 this.fire('update');
12795 },
12796
12797 // methods below are called by vector layers implementations
12798
12799 _initPath: function (layer) {
12800 var path = layer._path = create$2('path');
12801
12802 // @namespace Path
12803 // @option className: String = null
12804 // Custom class name set on an element. Only for SVG renderer.
12805 if (layer.options.className) {
12806 addClass(path, layer.options.className);
12807 }
12808
12809 if (layer.options.interactive) {
12810 addClass(path, 'leaflet-interactive');
12811 }
12812
12813 this._updateStyle(layer);
12814 this._layers[stamp(layer)] = layer;
12815 },
12816
12817 _addPath: function (layer) {
12818 if (!this._rootGroup) { this._initContainer(); }
12819 this._rootGroup.appendChild(layer._path);
12820 layer.addInteractiveTarget(layer._path);
12821 },
12822
12823 _removePath: function (layer) {
12824 remove(layer._path);
12825 layer.removeInteractiveTarget(layer._path);
12826 delete this._layers[stamp(layer)];
12827 },
12828
12829 _updatePath: function (layer) {
12830 layer._project();
12831 layer._update();
12832 },
12833
12834 _updateStyle: function (layer) {
12835 var path = layer._path,
12836 options = layer.options;
12837
12838 if (!path) { return; }
12839
12840 if (options.stroke) {
12841 path.setAttribute('stroke', options.color);
12842 path.setAttribute('stroke-opacity', options.opacity);
12843 path.setAttribute('stroke-width', options.weight);
12844 path.setAttribute('stroke-linecap', options.lineCap);
12845 path.setAttribute('stroke-linejoin', options.lineJoin);
12846
12847 if (options.dashArray) {
12848 path.setAttribute('stroke-dasharray', options.dashArray);
12849 } else {
12850 path.removeAttribute('stroke-dasharray');
12851 }
12852
12853 if (options.dashOffset) {
12854 path.setAttribute('stroke-dashoffset', options.dashOffset);
12855 } else {
12856 path.removeAttribute('stroke-dashoffset');
12857 }
12858 } else {
12859 path.setAttribute('stroke', 'none');
12860 }
12861
12862 if (options.fill) {
12863 path.setAttribute('fill', options.fillColor || options.color);
12864 path.setAttribute('fill-opacity', options.fillOpacity);
12865 path.setAttribute('fill-rule', options.fillRule || 'evenodd');
12866 } else {
12867 path.setAttribute('fill', 'none');
12868 }
12869 },
12870
12871 _updatePoly: function (layer, closed) {
12872 this._setPath(layer, pointsToPath(layer._parts, closed));
12873 },
12874
12875 _updateCircle: function (layer) {
12876 var p = layer._point,
12877 r = Math.max(Math.round(layer._radius), 1),
12878 r2 = Math.max(Math.round(layer._radiusY), 1) || r,
12879 arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
12880
12881 // drawing a circle with two half-arcs
12882 var d = layer._empty() ? 'M0 0' :
12883 'M' + (p.x - r) + ',' + p.y +
12884 arc + (r * 2) + ',0 ' +
12885 arc + (-r * 2) + ',0 ';
12886
12887 this._setPath(layer, d);
12888 },
12889
12890 _setPath: function (layer, path) {
12891 layer._path.setAttribute('d', path);
12892 },
12893
12894 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
12895 _bringToFront: function (layer) {
12896 toFront(layer._path);
12897 },
12898
12899 _bringToBack: function (layer) {
12900 toBack(layer._path);
12901 }
12902});
12903
12904if (vml) {
12905 SVG.include(vmlMixin);
12906}
12907
12908// @namespace SVG
12909// @factory L.svg(options?: Renderer options)
12910// Creates a SVG renderer with the given options.
12911function svg$1(options) {
12912 return svg || vml ? new SVG(options) : null;
12913}
12914
12915Map.include({
12916 // @namespace Map; @method getRenderer(layer: Path): Renderer
12917 // Returns the instance of `Renderer` that should be used to render the given
12918 // `Path`. It will ensure that the `renderer` options of the map and paths
12919 // are respected, and that the renderers do exist on the map.
12920 getRenderer: function (layer) {
12921 // @namespace Path; @option renderer: Renderer
12922 // Use this specific instance of `Renderer` for this path. Takes
12923 // precedence over the map's [default renderer](#map-renderer).
12924 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
12925
12926 if (!renderer) {
12927 renderer = this._renderer = this._createRenderer();
12928 }
12929
12930 if (!this.hasLayer(renderer)) {
12931 this.addLayer(renderer);
12932 }
12933 return renderer;
12934 },
12935
12936 _getPaneRenderer: function (name) {
12937 if (name === 'overlayPane' || name === undefined) {
12938 return false;
12939 }
12940
12941 var renderer = this._paneRenderers[name];
12942 if (renderer === undefined) {
12943 renderer = this._createRenderer({pane: name});
12944 this._paneRenderers[name] = renderer;
12945 }
12946 return renderer;
12947 },
12948
12949 _createRenderer: function (options) {
12950 // @namespace Map; @option preferCanvas: Boolean = false
12951 // Whether `Path`s should be rendered on a `Canvas` renderer.
12952 // By default, all `Path`s are rendered in a `SVG` renderer.
12953 return (this.options.preferCanvas && canvas$1(options)) || svg$1(options);
12954 }
12955});
12956
12957/*
12958 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
12959 */
12960
12961/*
12962 * @class Rectangle
12963 * @aka L.Rectangle
12964 * @inherits Polygon
12965 *
12966 * A class for drawing rectangle overlays on a map. Extends `Polygon`.
12967 *
12968 * @example
12969 *
12970 * ```js
12971 * // define rectangle geographical bounds
12972 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
12973 *
12974 * // create an orange rectangle
12975 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
12976 *
12977 * // zoom the map to the rectangle bounds
12978 * map.fitBounds(bounds);
12979 * ```
12980 *
12981 */
12982
12983
12984var Rectangle = Polygon.extend({
12985 initialize: function (latLngBounds, options) {
12986 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
12987 },
12988
12989 // @method setBounds(latLngBounds: LatLngBounds): this
12990 // Redraws the rectangle with the passed bounds.
12991 setBounds: function (latLngBounds) {
12992 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
12993 },
12994
12995 _boundsToLatLngs: function (latLngBounds) {
12996 latLngBounds = toLatLngBounds(latLngBounds);
12997 return [
12998 latLngBounds.getSouthWest(),
12999 latLngBounds.getNorthWest(),
13000 latLngBounds.getNorthEast(),
13001 latLngBounds.getSouthEast()
13002 ];
13003 }
13004});
13005
13006
13007// @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
13008function rectangle(latLngBounds, options) {
13009 return new Rectangle(latLngBounds, options);
13010}
13011
13012SVG.create = create$2;
13013SVG.pointsToPath = pointsToPath;
13014
13015GeoJSON.geometryToLayer = geometryToLayer;
13016GeoJSON.coordsToLatLng = coordsToLatLng;
13017GeoJSON.coordsToLatLngs = coordsToLatLngs;
13018GeoJSON.latLngToCoords = latLngToCoords;
13019GeoJSON.latLngsToCoords = latLngsToCoords;
13020GeoJSON.getFeature = getFeature;
13021GeoJSON.asFeature = asFeature;
13022
13023/*
13024 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
13025 * (zoom to a selected bounding box), enabled by default.
13026 */
13027
13028// @namespace Map
13029// @section Interaction Options
13030Map.mergeOptions({
13031 // @option boxZoom: Boolean = true
13032 // Whether the map can be zoomed to a rectangular area specified by
13033 // dragging the mouse while pressing the shift key.
13034 boxZoom: true
13035});
13036
13037var BoxZoom = Handler.extend({
13038 initialize: function (map) {
13039 this._map = map;
13040 this._container = map._container;
13041 this._pane = map._panes.overlayPane;
13042 this._resetStateTimeout = 0;
13043 map.on('unload', this._destroy, this);
13044 },
13045
13046 addHooks: function () {
13047 on(this._container, 'mousedown', this._onMouseDown, this);
13048 },
13049
13050 removeHooks: function () {
13051 off(this._container, 'mousedown', this._onMouseDown, this);
13052 },
13053
13054 moved: function () {
13055 return this._moved;
13056 },
13057
13058 _destroy: function () {
13059 remove(this._pane);
13060 delete this._pane;
13061 },
13062
13063 _resetState: function () {
13064 this._resetStateTimeout = 0;
13065 this._moved = false;
13066 },
13067
13068 _clearDeferredResetState: function () {
13069 if (this._resetStateTimeout !== 0) {
13070 clearTimeout(this._resetStateTimeout);
13071 this._resetStateTimeout = 0;
13072 }
13073 },
13074
13075 _onMouseDown: function (e) {
13076 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
13077
13078 // Clear the deferred resetState if it hasn't executed yet, otherwise it
13079 // will interrupt the interaction and orphan a box element in the container.
13080 this._clearDeferredResetState();
13081 this._resetState();
13082
13083 disableTextSelection();
13084 disableImageDrag();
13085
13086 this._startPoint = this._map.mouseEventToContainerPoint(e);
13087
13088 on(document, {
13089 contextmenu: stop,
13090 mousemove: this._onMouseMove,
13091 mouseup: this._onMouseUp,
13092 keydown: this._onKeyDown
13093 }, this);
13094 },
13095
13096 _onMouseMove: function (e) {
13097 if (!this._moved) {
13098 this._moved = true;
13099
13100 this._box = create$1('div', 'leaflet-zoom-box', this._container);
13101 addClass(this._container, 'leaflet-crosshair');
13102
13103 this._map.fire('boxzoomstart');
13104 }
13105
13106 this._point = this._map.mouseEventToContainerPoint(e);
13107
13108 var bounds = new Bounds(this._point, this._startPoint),
13109 size = bounds.getSize();
13110
13111 setPosition(this._box, bounds.min);
13112
13113 this._box.style.width = size.x + 'px';
13114 this._box.style.height = size.y + 'px';
13115 },
13116
13117 _finish: function () {
13118 if (this._moved) {
13119 remove(this._box);
13120 removeClass(this._container, 'leaflet-crosshair');
13121 }
13122
13123 enableTextSelection();
13124 enableImageDrag();
13125
13126 off(document, {
13127 contextmenu: stop,
13128 mousemove: this._onMouseMove,
13129 mouseup: this._onMouseUp,
13130 keydown: this._onKeyDown
13131 }, this);
13132 },
13133
13134 _onMouseUp: function (e) {
13135 if ((e.which !== 1) && (e.button !== 1)) { return; }
13136
13137 this._finish();
13138
13139 if (!this._moved) { return; }
13140 // Postpone to next JS tick so internal click event handling
13141 // still see it as "moved".
13142 this._clearDeferredResetState();
13143 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
13144
13145 var bounds = new LatLngBounds(
13146 this._map.containerPointToLatLng(this._startPoint),
13147 this._map.containerPointToLatLng(this._point));
13148
13149 this._map
13150 .fitBounds(bounds)
13151 .fire('boxzoomend', {boxZoomBounds: bounds});
13152 },
13153
13154 _onKeyDown: function (e) {
13155 if (e.keyCode === 27) {
13156 this._finish();
13157 }
13158 }
13159});
13160
13161// @section Handlers
13162// @property boxZoom: Handler
13163// Box (shift-drag with mouse) zoom handler.
13164Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
13165
13166/*
13167 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
13168 */
13169
13170// @namespace Map
13171// @section Interaction Options
13172
13173Map.mergeOptions({
13174 // @option doubleClickZoom: Boolean|String = true
13175 // Whether the map can be zoomed in by double clicking on it and
13176 // zoomed out by double clicking while holding shift. If passed
13177 // `'center'`, double-click zoom will zoom to the center of the
13178 // view regardless of where the mouse was.
13179 doubleClickZoom: true
13180});
13181
13182var DoubleClickZoom = Handler.extend({
13183 addHooks: function () {
13184 this._map.on('dblclick', this._onDoubleClick, this);
13185 },
13186
13187 removeHooks: function () {
13188 this._map.off('dblclick', this._onDoubleClick, this);
13189 },
13190
13191 _onDoubleClick: function (e) {
13192 var map = this._map,
13193 oldZoom = map.getZoom(),
13194 delta = map.options.zoomDelta,
13195 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
13196
13197 if (map.options.doubleClickZoom === 'center') {
13198 map.setZoom(zoom);
13199 } else {
13200 map.setZoomAround(e.containerPoint, zoom);
13201 }
13202 }
13203});
13204
13205// @section Handlers
13206//
13207// Map properties include interaction handlers that allow you to control
13208// interaction behavior in runtime, enabling or disabling certain features such
13209// as dragging or touch zoom (see `Handler` methods). For example:
13210//
13211// ```js
13212// map.doubleClickZoom.disable();
13213// ```
13214//
13215// @property doubleClickZoom: Handler
13216// Double click zoom handler.
13217Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
13218
13219/*
13220 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
13221 */
13222
13223// @namespace Map
13224// @section Interaction Options
13225Map.mergeOptions({
13226 // @option dragging: Boolean = true
13227 // Whether the map be draggable with mouse/touch or not.
13228 dragging: true,
13229
13230 // @section Panning Inertia Options
13231 // @option inertia: Boolean = *
13232 // If enabled, panning of the map will have an inertia effect where
13233 // the map builds momentum while dragging and continues moving in
13234 // the same direction for some time. Feels especially nice on touch
13235 // devices. Enabled by default unless running on old Android devices.
13236 inertia: !android23,
13237
13238 // @option inertiaDeceleration: Number = 3000
13239 // The rate with which the inertial movement slows down, in pixels/second².
13240 inertiaDeceleration: 3400, // px/s^2
13241
13242 // @option inertiaMaxSpeed: Number = Infinity
13243 // Max speed of the inertial movement, in pixels/second.
13244 inertiaMaxSpeed: Infinity, // px/s
13245
13246 // @option easeLinearity: Number = 0.2
13247 easeLinearity: 0.2,
13248
13249 // TODO refactor, move to CRS
13250 // @option worldCopyJump: Boolean = false
13251 // With this option enabled, the map tracks when you pan to another "copy"
13252 // of the world and seamlessly jumps to the original one so that all overlays
13253 // like markers and vector layers are still visible.
13254 worldCopyJump: false,
13255
13256 // @option maxBoundsViscosity: Number = 0.0
13257 // If `maxBounds` is set, this option will control how solid the bounds
13258 // are when dragging the map around. The default value of `0.0` allows the
13259 // user to drag outside the bounds at normal speed, higher values will
13260 // slow down map dragging outside bounds, and `1.0` makes the bounds fully
13261 // solid, preventing the user from dragging outside the bounds.
13262 maxBoundsViscosity: 0.0
13263});
13264
13265var Drag = Handler.extend({
13266 addHooks: function () {
13267 if (!this._draggable) {
13268 var map = this._map;
13269
13270 this._draggable = new Draggable(map._mapPane, map._container);
13271
13272 this._draggable.on({
13273 dragstart: this._onDragStart,
13274 drag: this._onDrag,
13275 dragend: this._onDragEnd
13276 }, this);
13277
13278 this._draggable.on('predrag', this._onPreDragLimit, this);
13279 if (map.options.worldCopyJump) {
13280 this._draggable.on('predrag', this._onPreDragWrap, this);
13281 map.on('zoomend', this._onZoomEnd, this);
13282
13283 map.whenReady(this._onZoomEnd, this);
13284 }
13285 }
13286 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
13287 this._draggable.enable();
13288 this._positions = [];
13289 this._times = [];
13290 },
13291
13292 removeHooks: function () {
13293 removeClass(this._map._container, 'leaflet-grab');
13294 removeClass(this._map._container, 'leaflet-touch-drag');
13295 this._draggable.disable();
13296 },
13297
13298 moved: function () {
13299 return this._draggable && this._draggable._moved;
13300 },
13301
13302 moving: function () {
13303 return this._draggable && this._draggable._moving;
13304 },
13305
13306 _onDragStart: function () {
13307 var map = this._map;
13308
13309 map._stop();
13310 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
13311 var bounds = toLatLngBounds(this._map.options.maxBounds);
13312
13313 this._offsetLimit = toBounds(
13314 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
13315 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
13316 .add(this._map.getSize()));
13317
13318 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
13319 } else {
13320 this._offsetLimit = null;
13321 }
13322
13323 map
13324 .fire('movestart')
13325 .fire('dragstart');
13326
13327 if (map.options.inertia) {
13328 this._positions = [];
13329 this._times = [];
13330 }
13331 },
13332
13333 _onDrag: function (e) {
13334 if (this._map.options.inertia) {
13335 var time = this._lastTime = +new Date(),
13336 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
13337
13338 this._positions.push(pos);
13339 this._times.push(time);
13340
13341 this._prunePositions(time);
13342 }
13343
13344 this._map
13345 .fire('move', e)
13346 .fire('drag', e);
13347 },
13348
13349 _prunePositions: function (time) {
13350 while (this._positions.length > 1 && time - this._times[0] > 50) {
13351 this._positions.shift();
13352 this._times.shift();
13353 }
13354 },
13355
13356 _onZoomEnd: function () {
13357 var pxCenter = this._map.getSize().divideBy(2),
13358 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
13359
13360 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
13361 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
13362 },
13363
13364 _viscousLimit: function (value, threshold) {
13365 return value - (value - threshold) * this._viscosity;
13366 },
13367
13368 _onPreDragLimit: function () {
13369 if (!this._viscosity || !this._offsetLimit) { return; }
13370
13371 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
13372
13373 var limit = this._offsetLimit;
13374 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
13375 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
13376 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
13377 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
13378
13379 this._draggable._newPos = this._draggable._startPos.add(offset);
13380 },
13381
13382 _onPreDragWrap: function () {
13383 // TODO refactor to be able to adjust map pane position after zoom
13384 var worldWidth = this._worldWidth,
13385 halfWidth = Math.round(worldWidth / 2),
13386 dx = this._initialWorldOffset,
13387 x = this._draggable._newPos.x,
13388 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
13389 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
13390 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
13391
13392 this._draggable._absPos = this._draggable._newPos.clone();
13393 this._draggable._newPos.x = newX;
13394 },
13395
13396 _onDragEnd: function (e) {
13397 var map = this._map,
13398 options = map.options,
13399
13400 noInertia = !options.inertia || this._times.length < 2;
13401
13402 map.fire('dragend', e);
13403
13404 if (noInertia) {
13405 map.fire('moveend');
13406
13407 } else {
13408 this._prunePositions(+new Date());
13409
13410 var direction = this._lastPos.subtract(this._positions[0]),
13411 duration = (this._lastTime - this._times[0]) / 1000,
13412 ease = options.easeLinearity,
13413
13414 speedVector = direction.multiplyBy(ease / duration),
13415 speed = speedVector.distanceTo([0, 0]),
13416
13417 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
13418 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
13419
13420 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
13421 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
13422
13423 if (!offset.x && !offset.y) {
13424 map.fire('moveend');
13425
13426 } else {
13427 offset = map._limitOffset(offset, map.options.maxBounds);
13428
13429 requestAnimFrame(function () {
13430 map.panBy(offset, {
13431 duration: decelerationDuration,
13432 easeLinearity: ease,
13433 noMoveStart: true,
13434 animate: true
13435 });
13436 });
13437 }
13438 }
13439 }
13440});
13441
13442// @section Handlers
13443// @property dragging: Handler
13444// Map dragging handler (by both mouse and touch).
13445Map.addInitHook('addHandler', 'dragging', Drag);
13446
13447/*
13448 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
13449 */
13450
13451// @namespace Map
13452// @section Keyboard Navigation Options
13453Map.mergeOptions({
13454 // @option keyboard: Boolean = true
13455 // Makes the map focusable and allows users to navigate the map with keyboard
13456 // arrows and `+`/`-` keys.
13457 keyboard: true,
13458
13459 // @option keyboardPanDelta: Number = 80
13460 // Amount of pixels to pan when pressing an arrow key.
13461 keyboardPanDelta: 80
13462});
13463
13464var Keyboard = Handler.extend({
13465
13466 keyCodes: {
13467 left: [37],
13468 right: [39],
13469 down: [40],
13470 up: [38],
13471 zoomIn: [187, 107, 61, 171],
13472 zoomOut: [189, 109, 54, 173]
13473 },
13474
13475 initialize: function (map) {
13476 this._map = map;
13477
13478 this._setPanDelta(map.options.keyboardPanDelta);
13479 this._setZoomDelta(map.options.zoomDelta);
13480 },
13481
13482 addHooks: function () {
13483 var container = this._map._container;
13484
13485 // make the container focusable by tabbing
13486 if (container.tabIndex <= 0) {
13487 container.tabIndex = '0';
13488 }
13489
13490 on(container, {
13491 focus: this._onFocus,
13492 blur: this._onBlur,
13493 mousedown: this._onMouseDown
13494 }, this);
13495
13496 this._map.on({
13497 focus: this._addHooks,
13498 blur: this._removeHooks
13499 }, this);
13500 },
13501
13502 removeHooks: function () {
13503 this._removeHooks();
13504
13505 off(this._map._container, {
13506 focus: this._onFocus,
13507 blur: this._onBlur,
13508 mousedown: this._onMouseDown
13509 }, this);
13510
13511 this._map.off({
13512 focus: this._addHooks,
13513 blur: this._removeHooks
13514 }, this);
13515 },
13516
13517 _onMouseDown: function () {
13518 if (this._focused) { return; }
13519
13520 var body = document.body,
13521 docEl = document.documentElement,
13522 top = body.scrollTop || docEl.scrollTop,
13523 left = body.scrollLeft || docEl.scrollLeft;
13524
13525 this._map._container.focus();
13526
13527 window.scrollTo(left, top);
13528 },
13529
13530 _onFocus: function () {
13531 this._focused = true;
13532 this._map.fire('focus');
13533 },
13534
13535 _onBlur: function () {
13536 this._focused = false;
13537 this._map.fire('blur');
13538 },
13539
13540 _setPanDelta: function (panDelta) {
13541 var keys = this._panKeys = {},
13542 codes = this.keyCodes,
13543 i, len;
13544
13545 for (i = 0, len = codes.left.length; i < len; i++) {
13546 keys[codes.left[i]] = [-1 * panDelta, 0];
13547 }
13548 for (i = 0, len = codes.right.length; i < len; i++) {
13549 keys[codes.right[i]] = [panDelta, 0];
13550 }
13551 for (i = 0, len = codes.down.length; i < len; i++) {
13552 keys[codes.down[i]] = [0, panDelta];
13553 }
13554 for (i = 0, len = codes.up.length; i < len; i++) {
13555 keys[codes.up[i]] = [0, -1 * panDelta];
13556 }
13557 },
13558
13559 _setZoomDelta: function (zoomDelta) {
13560 var keys = this._zoomKeys = {},
13561 codes = this.keyCodes,
13562 i, len;
13563
13564 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
13565 keys[codes.zoomIn[i]] = zoomDelta;
13566 }
13567 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
13568 keys[codes.zoomOut[i]] = -zoomDelta;
13569 }
13570 },
13571
13572 _addHooks: function () {
13573 on(document, 'keydown', this._onKeyDown, this);
13574 },
13575
13576 _removeHooks: function () {
13577 off(document, 'keydown', this._onKeyDown, this);
13578 },
13579
13580 _onKeyDown: function (e) {
13581 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
13582
13583 var key = e.keyCode,
13584 map = this._map,
13585 offset;
13586
13587 if (key in this._panKeys) {
13588 if (!map._panAnim || !map._panAnim._inProgress) {
13589 offset = this._panKeys[key];
13590 if (e.shiftKey) {
13591 offset = toPoint(offset).multiplyBy(3);
13592 }
13593
13594 map.panBy(offset);
13595
13596 if (map.options.maxBounds) {
13597 map.panInsideBounds(map.options.maxBounds);
13598 }
13599 }
13600 } else if (key in this._zoomKeys) {
13601 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
13602
13603 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
13604 map.closePopup();
13605
13606 } else {
13607 return;
13608 }
13609
13610 stop(e);
13611 }
13612});
13613
13614// @section Handlers
13615// @section Handlers
13616// @property keyboard: Handler
13617// Keyboard navigation handler.
13618Map.addInitHook('addHandler', 'keyboard', Keyboard);
13619
13620/*
13621 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
13622 */
13623
13624// @namespace Map
13625// @section Interaction Options
13626Map.mergeOptions({
13627 // @section Mouse wheel options
13628 // @option scrollWheelZoom: Boolean|String = true
13629 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
13630 // it will zoom to the center of the view regardless of where the mouse was.
13631 scrollWheelZoom: true,
13632
13633 // @option wheelDebounceTime: Number = 40
13634 // Limits the rate at which a wheel can fire (in milliseconds). By default
13635 // user can't zoom via wheel more often than once per 40 ms.
13636 wheelDebounceTime: 40,
13637
13638 // @option wheelPxPerZoomLevel: Number = 60
13639 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
13640 // mean a change of one full zoom level. Smaller values will make wheel-zooming
13641 // faster (and vice versa).
13642 wheelPxPerZoomLevel: 60
13643});
13644
13645var ScrollWheelZoom = Handler.extend({
13646 addHooks: function () {
13647 on(this._map._container, 'wheel', this._onWheelScroll, this);
13648
13649 this._delta = 0;
13650 },
13651
13652 removeHooks: function () {
13653 off(this._map._container, 'wheel', this._onWheelScroll, this);
13654 },
13655
13656 _onWheelScroll: function (e) {
13657 var delta = getWheelDelta(e);
13658
13659 var debounce = this._map.options.wheelDebounceTime;
13660
13661 this._delta += delta;
13662 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
13663
13664 if (!this._startTime) {
13665 this._startTime = +new Date();
13666 }
13667
13668 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
13669
13670 clearTimeout(this._timer);
13671 this._timer = setTimeout(bind(this._performZoom, this), left);
13672
13673 stop(e);
13674 },
13675
13676 _performZoom: function () {
13677 var map = this._map,
13678 zoom = map.getZoom(),
13679 snap = this._map.options.zoomSnap || 0;
13680
13681 map._stop(); // stop panning and fly animations if any
13682
13683 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
13684 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
13685 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
13686 d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
13687 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
13688
13689 this._delta = 0;
13690 this._startTime = null;
13691
13692 if (!delta) { return; }
13693
13694 if (map.options.scrollWheelZoom === 'center') {
13695 map.setZoom(zoom + delta);
13696 } else {
13697 map.setZoomAround(this._lastMousePos, zoom + delta);
13698 }
13699 }
13700});
13701
13702// @section Handlers
13703// @property scrollWheelZoom: Handler
13704// Scroll wheel zoom handler.
13705Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
13706
13707/*
13708 * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
13709 */
13710
13711// @namespace Map
13712// @section Interaction Options
13713Map.mergeOptions({
13714 // @section Touch interaction options
13715 // @option tap: Boolean = true
13716 // Enables mobile hacks for supporting instant taps (fixing 200ms click
13717 // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
13718 tap: true,
13719
13720 // @option tapTolerance: Number = 15
13721 // The max number of pixels a user can shift his finger during touch
13722 // for it to be considered a valid tap.
13723 tapTolerance: 15
13724});
13725
13726var Tap = Handler.extend({
13727 addHooks: function () {
13728 on(this._map._container, 'touchstart', this._onDown, this);
13729 },
13730
13731 removeHooks: function () {
13732 off(this._map._container, 'touchstart', this._onDown, this);
13733 },
13734
13735 _onDown: function (e) {
13736 if (!e.touches) { return; }
13737
13738 preventDefault(e);
13739
13740 this._fireClick = true;
13741
13742 // don't simulate click or track longpress if more than 1 touch
13743 if (e.touches.length > 1) {
13744 this._fireClick = false;
13745 clearTimeout(this._holdTimeout);
13746 return;
13747 }
13748
13749 var first = e.touches[0],
13750 el = first.target;
13751
13752 this._startPos = this._newPos = new Point(first.clientX, first.clientY);
13753
13754 // if touching a link, highlight it
13755 if (el.tagName && el.tagName.toLowerCase() === 'a') {
13756 addClass(el, 'leaflet-active');
13757 }
13758
13759 // simulate long hold but setting a timeout
13760 this._holdTimeout = setTimeout(bind(function () {
13761 if (this._isTapValid()) {
13762 this._fireClick = false;
13763 this._onUp();
13764 this._simulateEvent('contextmenu', first);
13765 }
13766 }, this), 1000);
13767
13768 this._simulateEvent('mousedown', first);
13769
13770 on(document, {
13771 touchmove: this._onMove,
13772 touchend: this._onUp
13773 }, this);
13774 },
13775
13776 _onUp: function (e) {
13777 clearTimeout(this._holdTimeout);
13778
13779 off(document, {
13780 touchmove: this._onMove,
13781 touchend: this._onUp
13782 }, this);
13783
13784 if (this._fireClick && e && e.changedTouches) {
13785
13786 var first = e.changedTouches[0],
13787 el = first.target;
13788
13789 if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
13790 removeClass(el, 'leaflet-active');
13791 }
13792
13793 this._simulateEvent('mouseup', first);
13794
13795 // simulate click if the touch didn't move too much
13796 if (this._isTapValid()) {
13797 this._simulateEvent('click', first);
13798 }
13799 }
13800 },
13801
13802 _isTapValid: function () {
13803 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
13804 },
13805
13806 _onMove: function (e) {
13807 var first = e.touches[0];
13808 this._newPos = new Point(first.clientX, first.clientY);
13809 this._simulateEvent('mousemove', first);
13810 },
13811
13812 _simulateEvent: function (type, e) {
13813 var simulatedEvent = document.createEvent('MouseEvents');
13814
13815 simulatedEvent._simulated = true;
13816 e.target._simulatedClick = true;
13817
13818 simulatedEvent.initMouseEvent(
13819 type, true, true, window, 1,
13820 e.screenX, e.screenY,
13821 e.clientX, e.clientY,
13822 false, false, false, false, 0, null);
13823
13824 e.target.dispatchEvent(simulatedEvent);
13825 }
13826});
13827
13828// @section Handlers
13829// @property tap: Handler
13830// Mobile touch hacks (quick tap and touch hold) handler.
13831if (touch && (!pointer || safari)) {
13832 Map.addInitHook('addHandler', 'tap', Tap);
13833}
13834
13835/*
13836 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
13837 */
13838
13839// @namespace Map
13840// @section Interaction Options
13841Map.mergeOptions({
13842 // @section Touch interaction options
13843 // @option touchZoom: Boolean|String = *
13844 // Whether the map can be zoomed by touch-dragging with two fingers. If
13845 // passed `'center'`, it will zoom to the center of the view regardless of
13846 // where the touch events (fingers) were. Enabled for touch-capable web
13847 // browsers except for old Androids.
13848 touchZoom: touch && !android23,
13849
13850 // @option bounceAtZoomLimits: Boolean = true
13851 // Set it to false if you don't want the map to zoom beyond min/max zoom
13852 // and then bounce back when pinch-zooming.
13853 bounceAtZoomLimits: true
13854});
13855
13856var TouchZoom = Handler.extend({
13857 addHooks: function () {
13858 addClass(this._map._container, 'leaflet-touch-zoom');
13859 on(this._map._container, 'touchstart', this._onTouchStart, this);
13860 },
13861
13862 removeHooks: function () {
13863 removeClass(this._map._container, 'leaflet-touch-zoom');
13864 off(this._map._container, 'touchstart', this._onTouchStart, this);
13865 },
13866
13867 _onTouchStart: function (e) {
13868 var map = this._map;
13869 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
13870
13871 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
13872 p2 = map.mouseEventToContainerPoint(e.touches[1]);
13873
13874 this._centerPoint = map.getSize()._divideBy(2);
13875 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
13876 if (map.options.touchZoom !== 'center') {
13877 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
13878 }
13879
13880 this._startDist = p1.distanceTo(p2);
13881 this._startZoom = map.getZoom();
13882
13883 this._moved = false;
13884 this._zooming = true;
13885
13886 map._stop();
13887
13888 on(document, 'touchmove', this._onTouchMove, this);
13889 on(document, 'touchend', this._onTouchEnd, this);
13890
13891 preventDefault(e);
13892 },
13893
13894 _onTouchMove: function (e) {
13895 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
13896
13897 var map = this._map,
13898 p1 = map.mouseEventToContainerPoint(e.touches[0]),
13899 p2 = map.mouseEventToContainerPoint(e.touches[1]),
13900 scale = p1.distanceTo(p2) / this._startDist;
13901
13902 this._zoom = map.getScaleZoom(scale, this._startZoom);
13903
13904 if (!map.options.bounceAtZoomLimits && (
13905 (this._zoom < map.getMinZoom() && scale < 1) ||
13906 (this._zoom > map.getMaxZoom() && scale > 1))) {
13907 this._zoom = map._limitZoom(this._zoom);
13908 }
13909
13910 if (map.options.touchZoom === 'center') {
13911 this._center = this._startLatLng;
13912 if (scale === 1) { return; }
13913 } else {
13914 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
13915 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
13916 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
13917 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
13918 }
13919
13920 if (!this._moved) {
13921 map._moveStart(true, false);
13922 this._moved = true;
13923 }
13924
13925 cancelAnimFrame(this._animRequest);
13926
13927 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
13928 this._animRequest = requestAnimFrame(moveFn, this, true);
13929
13930 preventDefault(e);
13931 },
13932
13933 _onTouchEnd: function () {
13934 if (!this._moved || !this._zooming) {
13935 this._zooming = false;
13936 return;
13937 }
13938
13939 this._zooming = false;
13940 cancelAnimFrame(this._animRequest);
13941
13942 off(document, 'touchmove', this._onTouchMove, this);
13943 off(document, 'touchend', this._onTouchEnd, this);
13944
13945 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
13946 if (this._map.options.zoomAnimation) {
13947 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
13948 } else {
13949 this._map._resetView(this._center, this._map._limitZoom(this._zoom));
13950 }
13951 }
13952});
13953
13954// @section Handlers
13955// @property touchZoom: Handler
13956// Touch zoom handler.
13957Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
13958
13959Map.BoxZoom = BoxZoom;
13960Map.DoubleClickZoom = DoubleClickZoom;
13961Map.Drag = Drag;
13962Map.Keyboard = Keyboard;
13963Map.ScrollWheelZoom = ScrollWheelZoom;
13964Map.Tap = Tap;
13965Map.TouchZoom = TouchZoom;
13966
13967export { version, Control, control, Browser, Evented, Mixin, Util, Class, Handler, extend, bind, stamp, setOptions, DomEvent, DomUtil, PosAnimation, Draggable, LineUtil, PolyUtil, Point, toPoint as point, Bounds, toBounds as bounds, Transformation, toTransformation as transformation, index as Projection, LatLng, toLatLng as latLng, LatLngBounds, toLatLngBounds as latLngBounds, CRS, GeoJSON, geoJSON, geoJson, Layer, LayerGroup, layerGroup, FeatureGroup, featureGroup, ImageOverlay, imageOverlay, VideoOverlay, videoOverlay, SVGOverlay, svgOverlay, DivOverlay, Popup, popup, Tooltip, tooltip, Icon, icon, DivIcon, divIcon, Marker, marker, TileLayer, tileLayer, GridLayer, gridLayer, SVG, svg$1 as svg, Renderer, Canvas, canvas$1 as canvas, Path, CircleMarker, circleMarker, Circle, circle, Polyline, polyline, Polygon, polygon, Rectangle, rectangle, Map, createMap as map };
13968//# sourceMappingURL=leaflet-src.esm.js.map