UNPKG

409 kBJavaScriptView Raw
1/* @preserve
2 * Leaflet 1.6.0, a JS library for interactive maps. http://leafletjs.com
3 * (c) 2010-2019 Vladimir Agafonkin, (c) 2010-2011 CloudMade
4 */
5
6(function (global, factory) {
7 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
8 typeof define === 'function' && define.amd ? define(['exports'], factory) :
9 (factory((global.L = {})));
10}(this, (function (exports) { 'use strict';
11
12var version = "1.6.0";
13
14/*
15 * @namespace Util
16 *
17 * Various utility functions, used by Leaflet internally.
18 */
19
20var freeze = Object.freeze;
21Object.freeze = function (obj) { return obj; };
22
23// @function extend(dest: Object, src?: Object): Object
24// Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
25function extend(dest) {
26 var i, j, len, src;
27
28 for (j = 1, len = arguments.length; j < len; j++) {
29 src = arguments[j];
30 for (i in src) {
31 dest[i] = src[i];
32 }
33 }
34 return dest;
35}
36
37// @function create(proto: Object, properties?: Object): Object
38// Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
39var create = Object.create || (function () {
40 function F() {}
41 return function (proto) {
42 F.prototype = proto;
43 return new F();
44 };
45})();
46
47// @function bind(fn: Function, …): Function
48// 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).
49// Has a `L.bind()` shortcut.
50function bind(fn, obj) {
51 var slice = Array.prototype.slice;
52
53 if (fn.bind) {
54 return fn.bind.apply(fn, slice.call(arguments, 1));
55 }
56
57 var args = slice.call(arguments, 2);
58
59 return function () {
60 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
61 };
62}
63
64// @property lastId: Number
65// Last unique ID used by [`stamp()`](#util-stamp)
66var lastId = 0;
67
68// @function stamp(obj: Object): Number
69// Returns the unique ID of an object, assigning it one if it doesn't have it.
70function stamp(obj) {
71 /*eslint-disable */
72 obj._leaflet_id = obj._leaflet_id || ++lastId;
73 return obj._leaflet_id;
74 /* eslint-enable */
75}
76
77// @function throttle(fn: Function, time: Number, context: Object): Function
78// Returns a function which executes function `fn` with the given scope `context`
79// (so that the `this` keyword refers to `context` inside `fn`'s code). The function
80// `fn` will be called no more than one time per given amount of `time`. The arguments
81// received by the bound function will be any arguments passed when binding the
82// function, followed by any arguments passed when invoking the bound function.
83// Has an `L.throttle` shortcut.
84function throttle(fn, time, context) {
85 var lock, args, wrapperFn, later;
86
87 later = function () {
88 // reset lock and call if queued
89 lock = false;
90 if (args) {
91 wrapperFn.apply(context, args);
92 args = false;
93 }
94 };
95
96 wrapperFn = function () {
97 if (lock) {
98 // called too soon, queue to call later
99 args = arguments;
100
101 } else {
102 // call and lock until later
103 fn.apply(context, arguments);
104 setTimeout(later, time);
105 lock = true;
106 }
107 };
108
109 return wrapperFn;
110}
111
112// @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
113// Returns the number `num` modulo `range` in such a way so it lies within
114// `range[0]` and `range[1]`. The returned value will be always smaller than
115// `range[1]` unless `includeMax` is set to `true`.
116function wrapNum(x, range, includeMax) {
117 var max = range[1],
118 min = range[0],
119 d = max - min;
120 return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
121}
122
123// @function falseFn(): Function
124// Returns a function which always returns `false`.
125function falseFn() { return false; }
126
127// @function formatNum(num: Number, digits?: Number): Number
128// Returns the number `num` rounded to `digits` decimals, or to 6 decimals by default.
129function formatNum(num, digits) {
130 var pow = Math.pow(10, (digits === undefined ? 6 : digits));
131 return Math.round(num * pow) / pow;
132}
133
134// @function trim(str: String): String
135// Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
136function trim(str) {
137 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
138}
139
140// @function splitWords(str: String): String[]
141// Trims and splits the string on whitespace and returns the array of parts.
142function splitWords(str) {
143 return trim(str).split(/\s+/);
144}
145
146// @function setOptions(obj: Object, options: Object): Object
147// Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
148function setOptions(obj, options) {
149 if (!obj.hasOwnProperty('options')) {
150 obj.options = obj.options ? create(obj.options) : {};
151 }
152 for (var i in options) {
153 obj.options[i] = options[i];
154 }
155 return obj.options;
156}
157
158// @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
159// Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
160// translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
161// be appended at the end. If `uppercase` is `true`, the parameter names will
162// be uppercased (e.g. `'?A=foo&B=bar'`)
163function getParamString(obj, existingUrl, uppercase) {
164 var params = [];
165 for (var i in obj) {
166 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
167 }
168 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
169}
170
171var templateRe = /\{ *([\w_-]+) *\}/g;
172
173// @function template(str: String, data: Object): String
174// Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
175// and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
176// `('Hello foo, bar')`. You can also specify functions instead of strings for
177// data values — they will be evaluated passing `data` as an argument.
178function template(str, data) {
179 return str.replace(templateRe, function (str, key) {
180 var value = data[key];
181
182 if (value === undefined) {
183 throw new Error('No value provided for variable ' + str);
184
185 } else if (typeof value === 'function') {
186 value = value(data);
187 }
188 return value;
189 });
190}
191
192// @function isArray(obj): Boolean
193// Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
194var isArray = Array.isArray || function (obj) {
195 return (Object.prototype.toString.call(obj) === '[object Array]');
196};
197
198// @function indexOf(array: Array, el: Object): Number
199// Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
200function indexOf(array, el) {
201 for (var i = 0; i < array.length; i++) {
202 if (array[i] === el) { return i; }
203 }
204 return -1;
205}
206
207// @property emptyImageUrl: String
208// Data URI string containing a base64-encoded empty GIF image.
209// Used as a hack to free memory from unused images on WebKit-powered
210// mobile devices (by setting image `src` to this string).
211var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
212
213// inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
214
215function getPrefixed(name) {
216 return window['webkit' + name] || window['moz' + name] || window['ms' + name];
217}
218
219var lastTime = 0;
220
221// fallback for IE 7-8
222function timeoutDefer(fn) {
223 var time = +new Date(),
224 timeToCall = Math.max(0, 16 - (time - lastTime));
225
226 lastTime = time + timeToCall;
227 return window.setTimeout(fn, timeToCall);
228}
229
230var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
231var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
232 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
233
234// @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
235// Schedules `fn` to be executed when the browser repaints. `fn` is bound to
236// `context` if given. When `immediate` is set, `fn` is called immediately if
237// the browser doesn't have native support for
238// [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
239// otherwise it's delayed. Returns a request ID that can be used to cancel the request.
240function requestAnimFrame(fn, context, immediate) {
241 if (immediate && requestFn === timeoutDefer) {
242 fn.call(context);
243 } else {
244 return requestFn.call(window, bind(fn, context));
245 }
246}
247
248// @function cancelAnimFrame(id: Number): undefined
249// Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
250function cancelAnimFrame(id) {
251 if (id) {
252 cancelFn.call(window, id);
253 }
254}
255
256
257var Util = (Object.freeze || Object)({
258 freeze: freeze,
259 extend: extend,
260 create: create,
261 bind: bind,
262 lastId: lastId,
263 stamp: stamp,
264 throttle: throttle,
265 wrapNum: wrapNum,
266 falseFn: falseFn,
267 formatNum: formatNum,
268 trim: trim,
269 splitWords: splitWords,
270 setOptions: setOptions,
271 getParamString: getParamString,
272 template: template,
273 isArray: isArray,
274 indexOf: indexOf,
275 emptyImageUrl: emptyImageUrl,
276 requestFn: requestFn,
277 cancelFn: cancelFn,
278 requestAnimFrame: requestAnimFrame,
279 cancelAnimFrame: cancelAnimFrame
280});
281
282// @class Class
283// @aka L.Class
284
285// @section
286// @uninheritable
287
288// Thanks to John Resig and Dean Edwards for inspiration!
289
290function Class() {}
291
292Class.extend = function (props) {
293
294 // @function extend(props: Object): Function
295 // [Extends the current class](#class-inheritance) given the properties to be included.
296 // Returns a Javascript function that is a class constructor (to be called with `new`).
297 var NewClass = function () {
298
299 // call the constructor
300 if (this.initialize) {
301 this.initialize.apply(this, arguments);
302 }
303
304 // call all constructor hooks
305 this.callInitHooks();
306 };
307
308 var parentProto = NewClass.__super__ = this.prototype;
309
310 var proto = create(parentProto);
311 proto.constructor = NewClass;
312
313 NewClass.prototype = proto;
314
315 // inherit parent's statics
316 for (var i in this) {
317 if (this.hasOwnProperty(i) && i !== 'prototype' && i !== '__super__') {
318 NewClass[i] = this[i];
319 }
320 }
321
322 // mix static properties into the class
323 if (props.statics) {
324 extend(NewClass, props.statics);
325 delete props.statics;
326 }
327
328 // mix includes into the prototype
329 if (props.includes) {
330 checkDeprecatedMixinEvents(props.includes);
331 extend.apply(null, [proto].concat(props.includes));
332 delete props.includes;
333 }
334
335 // merge options
336 if (proto.options) {
337 props.options = extend(create(proto.options), props.options);
338 }
339
340 // mix given properties into the prototype
341 extend(proto, props);
342
343 proto._initHooks = [];
344
345 // add method for calling all hooks
346 proto.callInitHooks = function () {
347
348 if (this._initHooksCalled) { return; }
349
350 if (parentProto.callInitHooks) {
351 parentProto.callInitHooks.call(this);
352 }
353
354 this._initHooksCalled = true;
355
356 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
357 proto._initHooks[i].call(this);
358 }
359 };
360
361 return NewClass;
362};
363
364
365// @function include(properties: Object): this
366// [Includes a mixin](#class-includes) into the current class.
367Class.include = function (props) {
368 extend(this.prototype, props);
369 return this;
370};
371
372// @function mergeOptions(options: Object): this
373// [Merges `options`](#class-options) into the defaults of the class.
374Class.mergeOptions = function (options) {
375 extend(this.prototype.options, options);
376 return this;
377};
378
379// @function addInitHook(fn: Function): this
380// Adds a [constructor hook](#class-constructor-hooks) to the class.
381Class.addInitHook = function (fn) { // (Function) || (String, args...)
382 var args = Array.prototype.slice.call(arguments, 1);
383
384 var init = typeof fn === 'function' ? fn : function () {
385 this[fn].apply(this, args);
386 };
387
388 this.prototype._initHooks = this.prototype._initHooks || [];
389 this.prototype._initHooks.push(init);
390 return this;
391};
392
393function checkDeprecatedMixinEvents(includes) {
394 if (typeof L === 'undefined' || !L || !L.Mixin) { return; }
395
396 includes = isArray(includes) ? includes : [includes];
397
398 for (var i = 0; i < includes.length; i++) {
399 if (includes[i] === L.Mixin.Events) {
400 console.warn('Deprecated include of L.Mixin.Events: ' +
401 'this property will be removed in future releases, ' +
402 'please inherit from L.Evented instead.', new Error().stack);
403 }
404 }
405}
406
407/*
408 * @class Evented
409 * @aka L.Evented
410 * @inherits Class
411 *
412 * 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).
413 *
414 * @example
415 *
416 * ```js
417 * map.on('click', function(e) {
418 * alert(e.latlng);
419 * } );
420 * ```
421 *
422 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
423 *
424 * ```js
425 * function onClick(e) { ... }
426 *
427 * map.on('click', onClick);
428 * map.off('click', onClick);
429 * ```
430 */
431
432var Events = {
433 /* @method on(type: String, fn: Function, context?: Object): this
434 * 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'`).
435 *
436 * @alternative
437 * @method on(eventMap: Object): this
438 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
439 */
440 on: function (types, fn, context) {
441
442 // types can be a map of types/handlers
443 if (typeof types === 'object') {
444 for (var type in types) {
445 // we don't process space-separated events here for performance;
446 // it's a hot path since Layer uses the on(obj) syntax
447 this._on(type, types[type], fn);
448 }
449
450 } else {
451 // types can be a string of space-separated words
452 types = splitWords(types);
453
454 for (var i = 0, len = types.length; i < len; i++) {
455 this._on(types[i], fn, context);
456 }
457 }
458
459 return this;
460 },
461
462 /* @method off(type: String, fn?: Function, context?: Object): this
463 * 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.
464 *
465 * @alternative
466 * @method off(eventMap: Object): this
467 * Removes a set of type/listener pairs.
468 *
469 * @alternative
470 * @method off: this
471 * Removes all listeners to all events on the object. This includes implicitly attached events.
472 */
473 off: function (types, fn, context) {
474
475 if (!types) {
476 // clear all listeners if called without arguments
477 delete this._events;
478
479 } else if (typeof types === 'object') {
480 for (var type in types) {
481 this._off(type, types[type], fn);
482 }
483
484 } else {
485 types = splitWords(types);
486
487 for (var i = 0, len = types.length; i < len; i++) {
488 this._off(types[i], fn, context);
489 }
490 }
491
492 return this;
493 },
494
495 // attach listener (without syntactic sugar now)
496 _on: function (type, fn, context) {
497 this._events = this._events || {};
498
499 /* get/init listeners for type */
500 var typeListeners = this._events[type];
501 if (!typeListeners) {
502 typeListeners = [];
503 this._events[type] = typeListeners;
504 }
505
506 if (context === this) {
507 // Less memory footprint.
508 context = undefined;
509 }
510 var newListener = {fn: fn, ctx: context},
511 listeners = typeListeners;
512
513 // check if fn already there
514 for (var i = 0, len = listeners.length; i < len; i++) {
515 if (listeners[i].fn === fn && listeners[i].ctx === context) {
516 return;
517 }
518 }
519
520 listeners.push(newListener);
521 },
522
523 _off: function (type, fn, context) {
524 var listeners,
525 i,
526 len;
527
528 if (!this._events) { return; }
529
530 listeners = this._events[type];
531
532 if (!listeners) {
533 return;
534 }
535
536 if (!fn) {
537 // Set all removed listeners to noop so they are not called if remove happens in fire
538 for (i = 0, len = listeners.length; i < len; i++) {
539 listeners[i].fn = falseFn;
540 }
541 // clear all listeners for a type if function isn't specified
542 delete this._events[type];
543 return;
544 }
545
546 if (context === this) {
547 context = undefined;
548 }
549
550 if (listeners) {
551
552 // find fn and remove it
553 for (i = 0, len = listeners.length; i < len; i++) {
554 var l = listeners[i];
555 if (l.ctx !== context) { continue; }
556 if (l.fn === fn) {
557
558 // set the removed listener to noop so that's not called if remove happens in fire
559 l.fn = falseFn;
560
561 if (this._firingCount) {
562 /* copy array in case events are being fired */
563 this._events[type] = listeners = listeners.slice();
564 }
565 listeners.splice(i, 1);
566
567 return;
568 }
569 }
570 }
571 },
572
573 // @method fire(type: String, data?: Object, propagate?: Boolean): this
574 // Fires an event of the specified type. You can optionally provide an data
575 // object — the first argument of the listener function will contain its
576 // properties. The event can optionally be propagated to event parents.
577 fire: function (type, data, propagate) {
578 if (!this.listens(type, propagate)) { return this; }
579
580 var event = extend({}, data, {
581 type: type,
582 target: this,
583 sourceTarget: data && data.sourceTarget || this
584 });
585
586 if (this._events) {
587 var listeners = this._events[type];
588
589 if (listeners) {
590 this._firingCount = (this._firingCount + 1) || 1;
591 for (var i = 0, len = listeners.length; i < len; i++) {
592 var l = listeners[i];
593 l.fn.call(l.ctx || this, event);
594 }
595
596 this._firingCount--;
597 }
598 }
599
600 if (propagate) {
601 // propagate the event to parents (set with addEventParent)
602 this._propagateEvent(event);
603 }
604
605 return this;
606 },
607
608 // @method listens(type: String): Boolean
609 // Returns `true` if a particular event type has any listeners attached to it.
610 listens: function (type, propagate) {
611 var listeners = this._events && this._events[type];
612 if (listeners && listeners.length) { return true; }
613
614 if (propagate) {
615 // also check parents for listeners if event propagates
616 for (var id in this._eventParents) {
617 if (this._eventParents[id].listens(type, propagate)) { return true; }
618 }
619 }
620 return false;
621 },
622
623 // @method once(…): this
624 // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
625 once: function (types, fn, context) {
626
627 if (typeof types === 'object') {
628 for (var type in types) {
629 this.once(type, types[type], fn);
630 }
631 return this;
632 }
633
634 var handler = bind(function () {
635 this
636 .off(types, fn, context)
637 .off(types, handler, context);
638 }, this);
639
640 // add a listener that's executed once and removed after that
641 return this
642 .on(types, fn, context)
643 .on(types, handler, context);
644 },
645
646 // @method addEventParent(obj: Evented): this
647 // Adds an event parent - an `Evented` that will receive propagated events
648 addEventParent: function (obj) {
649 this._eventParents = this._eventParents || {};
650 this._eventParents[stamp(obj)] = obj;
651 return this;
652 },
653
654 // @method removeEventParent(obj: Evented): this
655 // Removes an event parent, so it will stop receiving propagated events
656 removeEventParent: function (obj) {
657 if (this._eventParents) {
658 delete this._eventParents[stamp(obj)];
659 }
660 return this;
661 },
662
663 _propagateEvent: function (e) {
664 for (var id in this._eventParents) {
665 this._eventParents[id].fire(e.type, extend({
666 layer: e.target,
667 propagatedFrom: e.target
668 }, e), true);
669 }
670 }
671};
672
673// aliases; we should ditch those eventually
674
675// @method addEventListener(…): this
676// Alias to [`on(…)`](#evented-on)
677Events.addEventListener = Events.on;
678
679// @method removeEventListener(…): this
680// Alias to [`off(…)`](#evented-off)
681
682// @method clearAllEventListeners(…): this
683// Alias to [`off()`](#evented-off)
684Events.removeEventListener = Events.clearAllEventListeners = Events.off;
685
686// @method addOneTimeEventListener(…): this
687// Alias to [`once(…)`](#evented-once)
688Events.addOneTimeEventListener = Events.once;
689
690// @method fireEvent(…): this
691// Alias to [`fire(…)`](#evented-fire)
692Events.fireEvent = Events.fire;
693
694// @method hasEventListeners(…): Boolean
695// Alias to [`listens(…)`](#evented-listens)
696Events.hasEventListeners = Events.listens;
697
698var Evented = Class.extend(Events);
699
700/*
701 * @class Point
702 * @aka L.Point
703 *
704 * Represents a point with `x` and `y` coordinates in pixels.
705 *
706 * @example
707 *
708 * ```js
709 * var point = L.point(200, 300);
710 * ```
711 *
712 * 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:
713 *
714 * ```js
715 * map.panBy([200, 300]);
716 * map.panBy(L.point(200, 300));
717 * ```
718 *
719 * Note that `Point` does not inherit from Leafet's `Class` object,
720 * which means new classes can't inherit from it, and new methods
721 * can't be added to it with the `include` function.
722 */
723
724function Point(x, y, round) {
725 // @property x: Number; The `x` coordinate of the point
726 this.x = (round ? Math.round(x) : x);
727 // @property y: Number; The `y` coordinate of the point
728 this.y = (round ? Math.round(y) : y);
729}
730
731var trunc = Math.trunc || function (v) {
732 return v > 0 ? Math.floor(v) : Math.ceil(v);
733};
734
735Point.prototype = {
736
737 // @method clone(): Point
738 // Returns a copy of the current point.
739 clone: function () {
740 return new Point(this.x, this.y);
741 },
742
743 // @method add(otherPoint: Point): Point
744 // Returns the result of addition of the current and the given points.
745 add: function (point) {
746 // non-destructive, returns a new point
747 return this.clone()._add(toPoint(point));
748 },
749
750 _add: function (point) {
751 // destructive, used directly for performance in situations where it's safe to modify existing point
752 this.x += point.x;
753 this.y += point.y;
754 return this;
755 },
756
757 // @method subtract(otherPoint: Point): Point
758 // Returns the result of subtraction of the given point from the current.
759 subtract: function (point) {
760 return this.clone()._subtract(toPoint(point));
761 },
762
763 _subtract: function (point) {
764 this.x -= point.x;
765 this.y -= point.y;
766 return this;
767 },
768
769 // @method divideBy(num: Number): Point
770 // Returns the result of division of the current point by the given number.
771 divideBy: function (num) {
772 return this.clone()._divideBy(num);
773 },
774
775 _divideBy: function (num) {
776 this.x /= num;
777 this.y /= num;
778 return this;
779 },
780
781 // @method multiplyBy(num: Number): Point
782 // Returns the result of multiplication of the current point by the given number.
783 multiplyBy: function (num) {
784 return this.clone()._multiplyBy(num);
785 },
786
787 _multiplyBy: function (num) {
788 this.x *= num;
789 this.y *= num;
790 return this;
791 },
792
793 // @method scaleBy(scale: Point): Point
794 // Multiply each coordinate of the current point by each coordinate of
795 // `scale`. In linear algebra terms, multiply the point by the
796 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
797 // defined by `scale`.
798 scaleBy: function (point) {
799 return new Point(this.x * point.x, this.y * point.y);
800 },
801
802 // @method unscaleBy(scale: Point): Point
803 // Inverse of `scaleBy`. Divide each coordinate of the current point by
804 // each coordinate of `scale`.
805 unscaleBy: function (point) {
806 return new Point(this.x / point.x, this.y / point.y);
807 },
808
809 // @method round(): Point
810 // Returns a copy of the current point with rounded coordinates.
811 round: function () {
812 return this.clone()._round();
813 },
814
815 _round: function () {
816 this.x = Math.round(this.x);
817 this.y = Math.round(this.y);
818 return this;
819 },
820
821 // @method floor(): Point
822 // Returns a copy of the current point with floored coordinates (rounded down).
823 floor: function () {
824 return this.clone()._floor();
825 },
826
827 _floor: function () {
828 this.x = Math.floor(this.x);
829 this.y = Math.floor(this.y);
830 return this;
831 },
832
833 // @method ceil(): Point
834 // Returns a copy of the current point with ceiled coordinates (rounded up).
835 ceil: function () {
836 return this.clone()._ceil();
837 },
838
839 _ceil: function () {
840 this.x = Math.ceil(this.x);
841 this.y = Math.ceil(this.y);
842 return this;
843 },
844
845 // @method trunc(): Point
846 // Returns a copy of the current point with truncated coordinates (rounded towards zero).
847 trunc: function () {
848 return this.clone()._trunc();
849 },
850
851 _trunc: function () {
852 this.x = trunc(this.x);
853 this.y = trunc(this.y);
854 return this;
855 },
856
857 // @method distanceTo(otherPoint: Point): Number
858 // Returns the cartesian distance between the current and the given points.
859 distanceTo: function (point) {
860 point = toPoint(point);
861
862 var x = point.x - this.x,
863 y = point.y - this.y;
864
865 return Math.sqrt(x * x + y * y);
866 },
867
868 // @method equals(otherPoint: Point): Boolean
869 // Returns `true` if the given point has the same coordinates.
870 equals: function (point) {
871 point = toPoint(point);
872
873 return point.x === this.x &&
874 point.y === this.y;
875 },
876
877 // @method contains(otherPoint: Point): Boolean
878 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
879 contains: function (point) {
880 point = toPoint(point);
881
882 return Math.abs(point.x) <= Math.abs(this.x) &&
883 Math.abs(point.y) <= Math.abs(this.y);
884 },
885
886 // @method toString(): String
887 // Returns a string representation of the point for debugging purposes.
888 toString: function () {
889 return 'Point(' +
890 formatNum(this.x) + ', ' +
891 formatNum(this.y) + ')';
892 }
893};
894
895// @factory L.point(x: Number, y: Number, round?: Boolean)
896// Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
897
898// @alternative
899// @factory L.point(coords: Number[])
900// Expects an array of the form `[x, y]` instead.
901
902// @alternative
903// @factory L.point(coords: Object)
904// Expects a plain object of the form `{x: Number, y: Number}` instead.
905function toPoint(x, y, round) {
906 if (x instanceof Point) {
907 return x;
908 }
909 if (isArray(x)) {
910 return new Point(x[0], x[1]);
911 }
912 if (x === undefined || x === null) {
913 return x;
914 }
915 if (typeof x === 'object' && 'x' in x && 'y' in x) {
916 return new Point(x.x, x.y);
917 }
918 return new Point(x, y, round);
919}
920
921/*
922 * @class Bounds
923 * @aka L.Bounds
924 *
925 * Represents a rectangular area in pixel coordinates.
926 *
927 * @example
928 *
929 * ```js
930 * var p1 = L.point(10, 10),
931 * p2 = L.point(40, 60),
932 * bounds = L.bounds(p1, p2);
933 * ```
934 *
935 * 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:
936 *
937 * ```js
938 * otherBounds.intersects([[10, 10], [40, 60]]);
939 * ```
940 *
941 * Note that `Bounds` does not inherit from Leafet's `Class` object,
942 * which means new classes can't inherit from it, and new methods
943 * can't be added to it with the `include` function.
944 */
945
946function Bounds(a, b) {
947 if (!a) { return; }
948
949 var points = b ? [a, b] : a;
950
951 for (var i = 0, len = points.length; i < len; i++) {
952 this.extend(points[i]);
953 }
954}
955
956Bounds.prototype = {
957 // @method extend(point: Point): this
958 // Extends the bounds to contain the given point.
959 extend: function (point) { // (Point)
960 point = toPoint(point);
961
962 // @property min: Point
963 // The top left corner of the rectangle.
964 // @property max: Point
965 // The bottom right corner of the rectangle.
966 if (!this.min && !this.max) {
967 this.min = point.clone();
968 this.max = point.clone();
969 } else {
970 this.min.x = Math.min(point.x, this.min.x);
971 this.max.x = Math.max(point.x, this.max.x);
972 this.min.y = Math.min(point.y, this.min.y);
973 this.max.y = Math.max(point.y, this.max.y);
974 }
975 return this;
976 },
977
978 // @method getCenter(round?: Boolean): Point
979 // Returns the center point of the bounds.
980 getCenter: function (round) {
981 return new Point(
982 (this.min.x + this.max.x) / 2,
983 (this.min.y + this.max.y) / 2, round);
984 },
985
986 // @method getBottomLeft(): Point
987 // Returns the bottom-left point of the bounds.
988 getBottomLeft: function () {
989 return new Point(this.min.x, this.max.y);
990 },
991
992 // @method getTopRight(): Point
993 // Returns the top-right point of the bounds.
994 getTopRight: function () { // -> Point
995 return new Point(this.max.x, this.min.y);
996 },
997
998 // @method getTopLeft(): Point
999 // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).
1000 getTopLeft: function () {
1001 return this.min; // left, top
1002 },
1003
1004 // @method getBottomRight(): Point
1005 // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).
1006 getBottomRight: function () {
1007 return this.max; // right, bottom
1008 },
1009
1010 // @method getSize(): Point
1011 // Returns the size of the given bounds
1012 getSize: function () {
1013 return this.max.subtract(this.min);
1014 },
1015
1016 // @method contains(otherBounds: Bounds): Boolean
1017 // Returns `true` if the rectangle contains the given one.
1018 // @alternative
1019 // @method contains(point: Point): Boolean
1020 // Returns `true` if the rectangle contains the given point.
1021 contains: function (obj) {
1022 var min, max;
1023
1024 if (typeof obj[0] === 'number' || obj instanceof Point) {
1025 obj = toPoint(obj);
1026 } else {
1027 obj = toBounds(obj);
1028 }
1029
1030 if (obj instanceof Bounds) {
1031 min = obj.min;
1032 max = obj.max;
1033 } else {
1034 min = max = obj;
1035 }
1036
1037 return (min.x >= this.min.x) &&
1038 (max.x <= this.max.x) &&
1039 (min.y >= this.min.y) &&
1040 (max.y <= this.max.y);
1041 },
1042
1043 // @method intersects(otherBounds: Bounds): Boolean
1044 // Returns `true` if the rectangle intersects the given bounds. Two bounds
1045 // intersect if they have at least one point in common.
1046 intersects: function (bounds) { // (Bounds) -> Boolean
1047 bounds = toBounds(bounds);
1048
1049 var min = this.min,
1050 max = this.max,
1051 min2 = bounds.min,
1052 max2 = bounds.max,
1053 xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1054 yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1055
1056 return xIntersects && yIntersects;
1057 },
1058
1059 // @method overlaps(otherBounds: Bounds): Boolean
1060 // Returns `true` if the rectangle overlaps the given bounds. Two bounds
1061 // overlap if their intersection is an area.
1062 overlaps: function (bounds) { // (Bounds) -> Boolean
1063 bounds = toBounds(bounds);
1064
1065 var min = this.min,
1066 max = this.max,
1067 min2 = bounds.min,
1068 max2 = bounds.max,
1069 xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1070 yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1071
1072 return xOverlaps && yOverlaps;
1073 },
1074
1075 isValid: function () {
1076 return !!(this.min && this.max);
1077 }
1078};
1079
1080
1081// @factory L.bounds(corner1: Point, corner2: Point)
1082// Creates a Bounds object from two corners coordinate pairs.
1083// @alternative
1084// @factory L.bounds(points: Point[])
1085// Creates a Bounds object from the given array of points.
1086function toBounds(a, b) {
1087 if (!a || a instanceof Bounds) {
1088 return a;
1089 }
1090 return new Bounds(a, b);
1091}
1092
1093/*
1094 * @class LatLngBounds
1095 * @aka L.LatLngBounds
1096 *
1097 * Represents a rectangular geographical area on a map.
1098 *
1099 * @example
1100 *
1101 * ```js
1102 * var corner1 = L.latLng(40.712, -74.227),
1103 * corner2 = L.latLng(40.774, -74.125),
1104 * bounds = L.latLngBounds(corner1, corner2);
1105 * ```
1106 *
1107 * 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:
1108 *
1109 * ```js
1110 * map.fitBounds([
1111 * [40.712, -74.227],
1112 * [40.774, -74.125]
1113 * ]);
1114 * ```
1115 *
1116 * 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.
1117 *
1118 * Note that `LatLngBounds` does not inherit from Leafet's `Class` object,
1119 * which means new classes can't inherit from it, and new methods
1120 * can't be added to it with the `include` function.
1121 */
1122
1123function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
1124 if (!corner1) { return; }
1125
1126 var latlngs = corner2 ? [corner1, corner2] : corner1;
1127
1128 for (var i = 0, len = latlngs.length; i < len; i++) {
1129 this.extend(latlngs[i]);
1130 }
1131}
1132
1133LatLngBounds.prototype = {
1134
1135 // @method extend(latlng: LatLng): this
1136 // Extend the bounds to contain the given point
1137
1138 // @alternative
1139 // @method extend(otherBounds: LatLngBounds): this
1140 // Extend the bounds to contain the given bounds
1141 extend: function (obj) {
1142 var sw = this._southWest,
1143 ne = this._northEast,
1144 sw2, ne2;
1145
1146 if (obj instanceof LatLng) {
1147 sw2 = obj;
1148 ne2 = obj;
1149
1150 } else if (obj instanceof LatLngBounds) {
1151 sw2 = obj._southWest;
1152 ne2 = obj._northEast;
1153
1154 if (!sw2 || !ne2) { return this; }
1155
1156 } else {
1157 return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
1158 }
1159
1160 if (!sw && !ne) {
1161 this._southWest = new LatLng(sw2.lat, sw2.lng);
1162 this._northEast = new LatLng(ne2.lat, ne2.lng);
1163 } else {
1164 sw.lat = Math.min(sw2.lat, sw.lat);
1165 sw.lng = Math.min(sw2.lng, sw.lng);
1166 ne.lat = Math.max(ne2.lat, ne.lat);
1167 ne.lng = Math.max(ne2.lng, ne.lng);
1168 }
1169
1170 return this;
1171 },
1172
1173 // @method pad(bufferRatio: Number): LatLngBounds
1174 // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
1175 // For example, a ratio of 0.5 extends the bounds by 50% in each direction.
1176 // Negative values will retract the bounds.
1177 pad: function (bufferRatio) {
1178 var sw = this._southWest,
1179 ne = this._northEast,
1180 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1181 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1182
1183 return new LatLngBounds(
1184 new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1185 new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1186 },
1187
1188 // @method getCenter(): LatLng
1189 // Returns the center point of the bounds.
1190 getCenter: function () {
1191 return new LatLng(
1192 (this._southWest.lat + this._northEast.lat) / 2,
1193 (this._southWest.lng + this._northEast.lng) / 2);
1194 },
1195
1196 // @method getSouthWest(): LatLng
1197 // Returns the south-west point of the bounds.
1198 getSouthWest: function () {
1199 return this._southWest;
1200 },
1201
1202 // @method getNorthEast(): LatLng
1203 // Returns the north-east point of the bounds.
1204 getNorthEast: function () {
1205 return this._northEast;
1206 },
1207
1208 // @method getNorthWest(): LatLng
1209 // Returns the north-west point of the bounds.
1210 getNorthWest: function () {
1211 return new LatLng(this.getNorth(), this.getWest());
1212 },
1213
1214 // @method getSouthEast(): LatLng
1215 // Returns the south-east point of the bounds.
1216 getSouthEast: function () {
1217 return new LatLng(this.getSouth(), this.getEast());
1218 },
1219
1220 // @method getWest(): Number
1221 // Returns the west longitude of the bounds
1222 getWest: function () {
1223 return this._southWest.lng;
1224 },
1225
1226 // @method getSouth(): Number
1227 // Returns the south latitude of the bounds
1228 getSouth: function () {
1229 return this._southWest.lat;
1230 },
1231
1232 // @method getEast(): Number
1233 // Returns the east longitude of the bounds
1234 getEast: function () {
1235 return this._northEast.lng;
1236 },
1237
1238 // @method getNorth(): Number
1239 // Returns the north latitude of the bounds
1240 getNorth: function () {
1241 return this._northEast.lat;
1242 },
1243
1244 // @method contains(otherBounds: LatLngBounds): Boolean
1245 // Returns `true` if the rectangle contains the given one.
1246
1247 // @alternative
1248 // @method contains (latlng: LatLng): Boolean
1249 // Returns `true` if the rectangle contains the given point.
1250 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1251 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
1252 obj = toLatLng(obj);
1253 } else {
1254 obj = toLatLngBounds(obj);
1255 }
1256
1257 var sw = this._southWest,
1258 ne = this._northEast,
1259 sw2, ne2;
1260
1261 if (obj instanceof LatLngBounds) {
1262 sw2 = obj.getSouthWest();
1263 ne2 = obj.getNorthEast();
1264 } else {
1265 sw2 = ne2 = obj;
1266 }
1267
1268 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1269 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1270 },
1271
1272 // @method intersects(otherBounds: LatLngBounds): Boolean
1273 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1274 intersects: function (bounds) {
1275 bounds = toLatLngBounds(bounds);
1276
1277 var sw = this._southWest,
1278 ne = this._northEast,
1279 sw2 = bounds.getSouthWest(),
1280 ne2 = bounds.getNorthEast(),
1281
1282 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1283 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1284
1285 return latIntersects && lngIntersects;
1286 },
1287
1288 // @method overlaps(otherBounds: Bounds): Boolean
1289 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1290 overlaps: function (bounds) {
1291 bounds = toLatLngBounds(bounds);
1292
1293 var sw = this._southWest,
1294 ne = this._northEast,
1295 sw2 = bounds.getSouthWest(),
1296 ne2 = bounds.getNorthEast(),
1297
1298 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1299 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1300
1301 return latOverlaps && lngOverlaps;
1302 },
1303
1304 // @method toBBoxString(): String
1305 // 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.
1306 toBBoxString: function () {
1307 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1308 },
1309
1310 // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
1311 // 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.
1312 equals: function (bounds, maxMargin) {
1313 if (!bounds) { return false; }
1314
1315 bounds = toLatLngBounds(bounds);
1316
1317 return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
1318 this._northEast.equals(bounds.getNorthEast(), maxMargin);
1319 },
1320
1321 // @method isValid(): Boolean
1322 // Returns `true` if the bounds are properly initialized.
1323 isValid: function () {
1324 return !!(this._southWest && this._northEast);
1325 }
1326};
1327
1328// TODO International date line?
1329
1330// @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
1331// Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
1332
1333// @alternative
1334// @factory L.latLngBounds(latlngs: LatLng[])
1335// 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).
1336function toLatLngBounds(a, b) {
1337 if (a instanceof LatLngBounds) {
1338 return a;
1339 }
1340 return new LatLngBounds(a, b);
1341}
1342
1343/* @class LatLng
1344 * @aka L.LatLng
1345 *
1346 * Represents a geographical point with a certain latitude and longitude.
1347 *
1348 * @example
1349 *
1350 * ```
1351 * var latlng = L.latLng(50.5, 30.5);
1352 * ```
1353 *
1354 * 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:
1355 *
1356 * ```
1357 * map.panTo([50, 30]);
1358 * map.panTo({lon: 30, lat: 50});
1359 * map.panTo({lat: 50, lng: 30});
1360 * map.panTo(L.latLng(50, 30));
1361 * ```
1362 *
1363 * Note that `LatLng` does not inherit from Leaflet's `Class` object,
1364 * which means new classes can't inherit from it, and new methods
1365 * can't be added to it with the `include` function.
1366 */
1367
1368function LatLng(lat, lng, alt) {
1369 if (isNaN(lat) || isNaN(lng)) {
1370 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1371 }
1372
1373 // @property lat: Number
1374 // Latitude in degrees
1375 this.lat = +lat;
1376
1377 // @property lng: Number
1378 // Longitude in degrees
1379 this.lng = +lng;
1380
1381 // @property alt: Number
1382 // Altitude in meters (optional)
1383 if (alt !== undefined) {
1384 this.alt = +alt;
1385 }
1386}
1387
1388LatLng.prototype = {
1389 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1390 // 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.
1391 equals: function (obj, maxMargin) {
1392 if (!obj) { return false; }
1393
1394 obj = toLatLng(obj);
1395
1396 var margin = Math.max(
1397 Math.abs(this.lat - obj.lat),
1398 Math.abs(this.lng - obj.lng));
1399
1400 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1401 },
1402
1403 // @method toString(): String
1404 // Returns a string representation of the point (for debugging purposes).
1405 toString: function (precision) {
1406 return 'LatLng(' +
1407 formatNum(this.lat, precision) + ', ' +
1408 formatNum(this.lng, precision) + ')';
1409 },
1410
1411 // @method distanceTo(otherLatLng: LatLng): Number
1412 // 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).
1413 distanceTo: function (other) {
1414 return Earth.distance(this, toLatLng(other));
1415 },
1416
1417 // @method wrap(): LatLng
1418 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1419 wrap: function () {
1420 return Earth.wrapLatLng(this);
1421 },
1422
1423 // @method toBounds(sizeInMeters: Number): LatLngBounds
1424 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
1425 toBounds: function (sizeInMeters) {
1426 var latAccuracy = 180 * sizeInMeters / 40075017,
1427 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1428
1429 return toLatLngBounds(
1430 [this.lat - latAccuracy, this.lng - lngAccuracy],
1431 [this.lat + latAccuracy, this.lng + lngAccuracy]);
1432 },
1433
1434 clone: function () {
1435 return new LatLng(this.lat, this.lng, this.alt);
1436 }
1437};
1438
1439
1440
1441// @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1442// Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1443
1444// @alternative
1445// @factory L.latLng(coords: Array): LatLng
1446// Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1447
1448// @alternative
1449// @factory L.latLng(coords: Object): LatLng
1450// Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1451
1452function toLatLng(a, b, c) {
1453 if (a instanceof LatLng) {
1454 return a;
1455 }
1456 if (isArray(a) && typeof a[0] !== 'object') {
1457 if (a.length === 3) {
1458 return new LatLng(a[0], a[1], a[2]);
1459 }
1460 if (a.length === 2) {
1461 return new LatLng(a[0], a[1]);
1462 }
1463 return null;
1464 }
1465 if (a === undefined || a === null) {
1466 return a;
1467 }
1468 if (typeof a === 'object' && 'lat' in a) {
1469 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1470 }
1471 if (b === undefined) {
1472 return null;
1473 }
1474 return new LatLng(a, b, c);
1475}
1476
1477/*
1478 * @namespace CRS
1479 * @crs L.CRS.Base
1480 * Object that defines coordinate reference systems for projecting
1481 * geographical points into pixel (screen) coordinates and back (and to
1482 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
1483 * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
1484 *
1485 * Leaflet defines the most usual CRSs by default. If you want to use a
1486 * CRS not defined by default, take a look at the
1487 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
1488 *
1489 * Note that the CRS instances do not inherit from Leafet's `Class` object,
1490 * and can't be instantiated. Also, new classes can't inherit from them,
1491 * and methods can't be added to them with the `include` function.
1492 */
1493
1494var CRS = {
1495 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
1496 // Projects geographical coordinates into pixel coordinates for a given zoom.
1497 latLngToPoint: function (latlng, zoom) {
1498 var projectedPoint = this.projection.project(latlng),
1499 scale = this.scale(zoom);
1500
1501 return this.transformation._transform(projectedPoint, scale);
1502 },
1503
1504 // @method pointToLatLng(point: Point, zoom: Number): LatLng
1505 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
1506 // zoom into geographical coordinates.
1507 pointToLatLng: function (point, zoom) {
1508 var scale = this.scale(zoom),
1509 untransformedPoint = this.transformation.untransform(point, scale);
1510
1511 return this.projection.unproject(untransformedPoint);
1512 },
1513
1514 // @method project(latlng: LatLng): Point
1515 // Projects geographical coordinates into coordinates in units accepted for
1516 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
1517 project: function (latlng) {
1518 return this.projection.project(latlng);
1519 },
1520
1521 // @method unproject(point: Point): LatLng
1522 // Given a projected coordinate returns the corresponding LatLng.
1523 // The inverse of `project`.
1524 unproject: function (point) {
1525 return this.projection.unproject(point);
1526 },
1527
1528 // @method scale(zoom: Number): Number
1529 // Returns the scale used when transforming projected coordinates into
1530 // pixel coordinates for a particular zoom. For example, it returns
1531 // `256 * 2^zoom` for Mercator-based CRS.
1532 scale: function (zoom) {
1533 return 256 * Math.pow(2, zoom);
1534 },
1535
1536 // @method zoom(scale: Number): Number
1537 // Inverse of `scale()`, returns the zoom level corresponding to a scale
1538 // factor of `scale`.
1539 zoom: function (scale) {
1540 return Math.log(scale / 256) / Math.LN2;
1541 },
1542
1543 // @method getProjectedBounds(zoom: Number): Bounds
1544 // Returns the projection's bounds scaled and transformed for the provided `zoom`.
1545 getProjectedBounds: function (zoom) {
1546 if (this.infinite) { return null; }
1547
1548 var b = this.projection.bounds,
1549 s = this.scale(zoom),
1550 min = this.transformation.transform(b.min, s),
1551 max = this.transformation.transform(b.max, s);
1552
1553 return new Bounds(min, max);
1554 },
1555
1556 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
1557 // Returns the distance between two geographical coordinates.
1558
1559 // @property code: String
1560 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
1561 //
1562 // @property wrapLng: Number[]
1563 // An array of two numbers defining whether the longitude (horizontal) coordinate
1564 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
1565 // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
1566 //
1567 // @property wrapLat: Number[]
1568 // Like `wrapLng`, but for the latitude (vertical) axis.
1569
1570 // wrapLng: [min, max],
1571 // wrapLat: [min, max],
1572
1573 // @property infinite: Boolean
1574 // If true, the coordinate space will be unbounded (infinite in both axes)
1575 infinite: false,
1576
1577 // @method wrapLatLng(latlng: LatLng): LatLng
1578 // Returns a `LatLng` where lat and lng has been wrapped according to the
1579 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
1580 wrapLatLng: function (latlng) {
1581 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
1582 lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
1583 alt = latlng.alt;
1584
1585 return new LatLng(lat, lng, alt);
1586 },
1587
1588 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
1589 // Returns a `LatLngBounds` with the same size as the given one, ensuring
1590 // that its center is within the CRS's bounds.
1591 // Only accepts actual `L.LatLngBounds` instances, not arrays.
1592 wrapLatLngBounds: function (bounds) {
1593 var center = bounds.getCenter(),
1594 newCenter = this.wrapLatLng(center),
1595 latShift = center.lat - newCenter.lat,
1596 lngShift = center.lng - newCenter.lng;
1597
1598 if (latShift === 0 && lngShift === 0) {
1599 return bounds;
1600 }
1601
1602 var sw = bounds.getSouthWest(),
1603 ne = bounds.getNorthEast(),
1604 newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),
1605 newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);
1606
1607 return new LatLngBounds(newSw, newNe);
1608 }
1609};
1610
1611/*
1612 * @namespace CRS
1613 * @crs L.CRS.Earth
1614 *
1615 * Serves as the base for CRS that are global such that they cover the earth.
1616 * Can only be used as the base for other CRS and cannot be used directly,
1617 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
1618 * meters.
1619 */
1620
1621var Earth = extend({}, CRS, {
1622 wrapLng: [-180, 180],
1623
1624 // Mean Earth Radius, as recommended for use by
1625 // the International Union of Geodesy and Geophysics,
1626 // see http://rosettacode.org/wiki/Haversine_formula
1627 R: 6371000,
1628
1629 // distance between two geographical points using spherical law of cosines approximation
1630 distance: function (latlng1, latlng2) {
1631 var rad = Math.PI / 180,
1632 lat1 = latlng1.lat * rad,
1633 lat2 = latlng2.lat * rad,
1634 sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2),
1635 sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2),
1636 a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon,
1637 c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1638 return this.R * c;
1639 }
1640});
1641
1642/*
1643 * @namespace Projection
1644 * @projection L.Projection.SphericalMercator
1645 *
1646 * Spherical Mercator projection — the most common projection for online maps,
1647 * used by almost all free and commercial tile providers. Assumes that Earth is
1648 * a sphere. Used by the `EPSG:3857` CRS.
1649 */
1650
1651var earthRadius = 6378137;
1652
1653var SphericalMercator = {
1654
1655 R: earthRadius,
1656 MAX_LATITUDE: 85.0511287798,
1657
1658 project: function (latlng) {
1659 var d = Math.PI / 180,
1660 max = this.MAX_LATITUDE,
1661 lat = Math.max(Math.min(max, latlng.lat), -max),
1662 sin = Math.sin(lat * d);
1663
1664 return new Point(
1665 this.R * latlng.lng * d,
1666 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
1667 },
1668
1669 unproject: function (point) {
1670 var d = 180 / Math.PI;
1671
1672 return new LatLng(
1673 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
1674 point.x * d / this.R);
1675 },
1676
1677 bounds: (function () {
1678 var d = earthRadius * Math.PI;
1679 return new Bounds([-d, -d], [d, d]);
1680 })()
1681};
1682
1683/*
1684 * @class Transformation
1685 * @aka L.Transformation
1686 *
1687 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1688 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1689 * the reverse. Used by Leaflet in its projections code.
1690 *
1691 * @example
1692 *
1693 * ```js
1694 * var transformation = L.transformation(2, 5, -1, 10),
1695 * p = L.point(1, 2),
1696 * p2 = transformation.transform(p), // L.point(7, 8)
1697 * p3 = transformation.untransform(p2); // L.point(1, 2)
1698 * ```
1699 */
1700
1701
1702// factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1703// Creates a `Transformation` object with the given coefficients.
1704function Transformation(a, b, c, d) {
1705 if (isArray(a)) {
1706 // use array properties
1707 this._a = a[0];
1708 this._b = a[1];
1709 this._c = a[2];
1710 this._d = a[3];
1711 return;
1712 }
1713 this._a = a;
1714 this._b = b;
1715 this._c = c;
1716 this._d = d;
1717}
1718
1719Transformation.prototype = {
1720 // @method transform(point: Point, scale?: Number): Point
1721 // Returns a transformed point, optionally multiplied by the given scale.
1722 // Only accepts actual `L.Point` instances, not arrays.
1723 transform: function (point, scale) { // (Point, Number) -> Point
1724 return this._transform(point.clone(), scale);
1725 },
1726
1727 // destructive transform (faster)
1728 _transform: function (point, scale) {
1729 scale = scale || 1;
1730 point.x = scale * (this._a * point.x + this._b);
1731 point.y = scale * (this._c * point.y + this._d);
1732 return point;
1733 },
1734
1735 // @method untransform(point: Point, scale?: Number): Point
1736 // Returns the reverse transformation of the given point, optionally divided
1737 // by the given scale. Only accepts actual `L.Point` instances, not arrays.
1738 untransform: function (point, scale) {
1739 scale = scale || 1;
1740 return new Point(
1741 (point.x / scale - this._b) / this._a,
1742 (point.y / scale - this._d) / this._c);
1743 }
1744};
1745
1746// factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1747
1748// @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1749// Instantiates a Transformation object with the given coefficients.
1750
1751// @alternative
1752// @factory L.transformation(coefficients: Array): Transformation
1753// Expects an coefficients array of the form
1754// `[a: Number, b: Number, c: Number, d: Number]`.
1755
1756function toTransformation(a, b, c, d) {
1757 return new Transformation(a, b, c, d);
1758}
1759
1760/*
1761 * @namespace CRS
1762 * @crs L.CRS.EPSG3857
1763 *
1764 * The most common CRS for online maps, used by almost all free and commercial
1765 * tile providers. Uses Spherical Mercator projection. Set in by default in
1766 * Map's `crs` option.
1767 */
1768
1769var EPSG3857 = extend({}, Earth, {
1770 code: 'EPSG:3857',
1771 projection: SphericalMercator,
1772
1773 transformation: (function () {
1774 var scale = 0.5 / (Math.PI * SphericalMercator.R);
1775 return toTransformation(scale, 0.5, -scale, 0.5);
1776 }())
1777});
1778
1779var EPSG900913 = extend({}, EPSG3857, {
1780 code: 'EPSG:900913'
1781});
1782
1783// @namespace SVG; @section
1784// There are several static functions which can be called without instantiating L.SVG:
1785
1786// @function create(name: String): SVGElement
1787// Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
1788// corresponding to the class name passed. For example, using 'line' will return
1789// an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
1790function svgCreate(name) {
1791 return document.createElementNS('http://www.w3.org/2000/svg', name);
1792}
1793
1794// @function pointsToPath(rings: Point[], closed: Boolean): String
1795// Generates a SVG path string for multiple rings, with each ring turning
1796// into "M..L..L.." instructions
1797function pointsToPath(rings, closed) {
1798 var str = '',
1799 i, j, len, len2, points, p;
1800
1801 for (i = 0, len = rings.length; i < len; i++) {
1802 points = rings[i];
1803
1804 for (j = 0, len2 = points.length; j < len2; j++) {
1805 p = points[j];
1806 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
1807 }
1808
1809 // closes the ring for polygons; "x" is VML syntax
1810 str += closed ? (svg ? 'z' : 'x') : '';
1811 }
1812
1813 // SVG complains about empty path strings
1814 return str || 'M0 0';
1815}
1816
1817/*
1818 * @namespace Browser
1819 * @aka L.Browser
1820 *
1821 * A namespace with static properties for browser/feature detection used by Leaflet internally.
1822 *
1823 * @example
1824 *
1825 * ```js
1826 * if (L.Browser.ielt9) {
1827 * alert('Upgrade your browser, dude!');
1828 * }
1829 * ```
1830 */
1831
1832var style$1 = document.documentElement.style;
1833
1834// @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
1835var ie = 'ActiveXObject' in window;
1836
1837// @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
1838var ielt9 = ie && !document.addEventListener;
1839
1840// @property edge: Boolean; `true` for the Edge web browser.
1841var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
1842
1843// @property webkit: Boolean;
1844// `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
1845var webkit = userAgentContains('webkit');
1846
1847// @property android: Boolean
1848// `true` for any browser running on an Android platform.
1849var android = userAgentContains('android');
1850
1851// @property android23: Boolean; `true` for browsers running on Android 2 or Android 3.
1852var android23 = userAgentContains('android 2') || userAgentContains('android 3');
1853
1854/* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */
1855var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit
1856// @property androidStock: Boolean; `true` for the Android stock browser (i.e. not Chrome)
1857var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);
1858
1859// @property opera: Boolean; `true` for the Opera browser
1860var opera = !!window.opera;
1861
1862// @property chrome: Boolean; `true` for the Chrome browser.
1863var chrome = userAgentContains('chrome');
1864
1865// @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
1866var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
1867
1868// @property safari: Boolean; `true` for the Safari browser.
1869var safari = !chrome && userAgentContains('safari');
1870
1871var phantom = userAgentContains('phantom');
1872
1873// @property opera12: Boolean
1874// `true` for the Opera browser supporting CSS transforms (version 12 or later).
1875var opera12 = 'OTransition' in style$1;
1876
1877// @property win: Boolean; `true` when the browser is running in a Windows platform
1878var win = navigator.platform.indexOf('Win') === 0;
1879
1880// @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
1881var ie3d = ie && ('transition' in style$1);
1882
1883// @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
1884var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;
1885
1886// @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
1887var gecko3d = 'MozPerspective' in style$1;
1888
1889// @property any3d: Boolean
1890// `true` for all browsers supporting CSS transforms.
1891var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
1892
1893// @property mobile: Boolean; `true` for all browsers running in a mobile device.
1894var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
1895
1896// @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
1897var mobileWebkit = mobile && webkit;
1898
1899// @property mobileWebkit3d: Boolean
1900// `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
1901var mobileWebkit3d = mobile && webkit3d;
1902
1903// @property msPointer: Boolean
1904// `true` for browsers implementing the Microsoft touch events model (notably IE10).
1905var msPointer = !window.PointerEvent && window.MSPointerEvent;
1906
1907// @property pointer: Boolean
1908// `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
1909var pointer = !webkit && !!(window.PointerEvent || msPointer);
1910
1911// @property touch: Boolean
1912// `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
1913// This does not necessarily mean that the browser is running in a computer with
1914// a touchscreen, it only means that the browser is capable of understanding
1915// touch events.
1916var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
1917 (window.DocumentTouch && document instanceof window.DocumentTouch));
1918
1919// @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
1920var mobileOpera = mobile && opera;
1921
1922// @property mobileGecko: Boolean
1923// `true` for gecko-based browsers running in a mobile device.
1924var mobileGecko = mobile && gecko;
1925
1926// @property retina: Boolean
1927// `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%.
1928var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
1929
1930// @property passiveEvents: Boolean
1931// `true` for browsers that support passive events.
1932var passiveEvents = (function () {
1933 var supportsPassiveOption = false;
1934 try {
1935 var opts = Object.defineProperty({}, 'passive', {
1936 get: function () {
1937 supportsPassiveOption = true;
1938 }
1939 });
1940 window.addEventListener('testPassiveEventSupport', falseFn, opts);
1941 window.removeEventListener('testPassiveEventSupport', falseFn, opts);
1942 } catch (e) {
1943 // Errors can safely be ignored since this is only a browser support test.
1944 }
1945 return supportsPassiveOption;
1946});
1947
1948// @property canvas: Boolean
1949// `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
1950var canvas = (function () {
1951 return !!document.createElement('canvas').getContext;
1952}());
1953
1954// @property svg: Boolean
1955// `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
1956var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect);
1957
1958// @property vml: Boolean
1959// `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
1960var vml = !svg && (function () {
1961 try {
1962 var div = document.createElement('div');
1963 div.innerHTML = '<v:shape adj="1"/>';
1964
1965 var shape = div.firstChild;
1966 shape.style.behavior = 'url(#default#VML)';
1967
1968 return shape && (typeof shape.adj === 'object');
1969
1970 } catch (e) {
1971 return false;
1972 }
1973}());
1974
1975
1976function userAgentContains(str) {
1977 return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
1978}
1979
1980
1981var Browser = (Object.freeze || Object)({
1982 ie: ie,
1983 ielt9: ielt9,
1984 edge: edge,
1985 webkit: webkit,
1986 android: android,
1987 android23: android23,
1988 androidStock: androidStock,
1989 opera: opera,
1990 chrome: chrome,
1991 gecko: gecko,
1992 safari: safari,
1993 phantom: phantom,
1994 opera12: opera12,
1995 win: win,
1996 ie3d: ie3d,
1997 webkit3d: webkit3d,
1998 gecko3d: gecko3d,
1999 any3d: any3d,
2000 mobile: mobile,
2001 mobileWebkit: mobileWebkit,
2002 mobileWebkit3d: mobileWebkit3d,
2003 msPointer: msPointer,
2004 pointer: pointer,
2005 touch: touch,
2006 mobileOpera: mobileOpera,
2007 mobileGecko: mobileGecko,
2008 retina: retina,
2009 passiveEvents: passiveEvents,
2010 canvas: canvas,
2011 svg: svg,
2012 vml: vml
2013});
2014
2015/*
2016 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
2017 */
2018
2019
2020var POINTER_DOWN = msPointer ? 'MSPointerDown' : 'pointerdown';
2021var POINTER_MOVE = msPointer ? 'MSPointerMove' : 'pointermove';
2022var POINTER_UP = msPointer ? 'MSPointerUp' : 'pointerup';
2023var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel';
2024var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION'];
2025
2026var _pointers = {};
2027var _pointerDocListener = false;
2028
2029// DomEvent.DoubleTap needs to know about this
2030var _pointersCount = 0;
2031
2032// Provides a touch events wrapper for (ms)pointer events.
2033// ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
2034
2035function addPointerListener(obj, type, handler, id) {
2036 if (type === 'touchstart') {
2037 _addPointerStart(obj, handler, id);
2038
2039 } else if (type === 'touchmove') {
2040 _addPointerMove(obj, handler, id);
2041
2042 } else if (type === 'touchend') {
2043 _addPointerEnd(obj, handler, id);
2044 }
2045
2046 return this;
2047}
2048
2049function removePointerListener(obj, type, id) {
2050 var handler = obj['_leaflet_' + type + id];
2051
2052 if (type === 'touchstart') {
2053 obj.removeEventListener(POINTER_DOWN, handler, false);
2054
2055 } else if (type === 'touchmove') {
2056 obj.removeEventListener(POINTER_MOVE, handler, false);
2057
2058 } else if (type === 'touchend') {
2059 obj.removeEventListener(POINTER_UP, handler, false);
2060 obj.removeEventListener(POINTER_CANCEL, handler, false);
2061 }
2062
2063 return this;
2064}
2065
2066function _addPointerStart(obj, handler, id) {
2067 var onDown = bind(function (e) {
2068 if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
2069 // In IE11, some touch events needs to fire for form controls, or
2070 // the controls will stop working. We keep a whitelist of tag names that
2071 // need these events. For other target tags, we prevent default on the event.
2072 if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
2073 preventDefault(e);
2074 } else {
2075 return;
2076 }
2077 }
2078
2079 _handlePointer(e, handler);
2080 });
2081
2082 obj['_leaflet_touchstart' + id] = onDown;
2083 obj.addEventListener(POINTER_DOWN, onDown, false);
2084
2085 // need to keep track of what pointers and how many are active to provide e.touches emulation
2086 if (!_pointerDocListener) {
2087 // we listen documentElement as any drags that end by moving the touch off the screen get fired there
2088 document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true);
2089 document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true);
2090 document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true);
2091 document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
2092
2093 _pointerDocListener = true;
2094 }
2095}
2096
2097function _globalPointerDown(e) {
2098 _pointers[e.pointerId] = e;
2099 _pointersCount++;
2100}
2101
2102function _globalPointerMove(e) {
2103 if (_pointers[e.pointerId]) {
2104 _pointers[e.pointerId] = e;
2105 }
2106}
2107
2108function _globalPointerUp(e) {
2109 delete _pointers[e.pointerId];
2110 _pointersCount--;
2111}
2112
2113function _handlePointer(e, handler) {
2114 e.touches = [];
2115 for (var i in _pointers) {
2116 e.touches.push(_pointers[i]);
2117 }
2118 e.changedTouches = [e];
2119
2120 handler(e);
2121}
2122
2123function _addPointerMove(obj, handler, id) {
2124 var onMove = function (e) {
2125 // don't fire touch moves when mouse isn't down
2126 if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
2127
2128 _handlePointer(e, handler);
2129 };
2130
2131 obj['_leaflet_touchmove' + id] = onMove;
2132 obj.addEventListener(POINTER_MOVE, onMove, false);
2133}
2134
2135function _addPointerEnd(obj, handler, id) {
2136 var onUp = function (e) {
2137 _handlePointer(e, handler);
2138 };
2139
2140 obj['_leaflet_touchend' + id] = onUp;
2141 obj.addEventListener(POINTER_UP, onUp, false);
2142 obj.addEventListener(POINTER_CANCEL, onUp, false);
2143}
2144
2145/*
2146 * Extends the event handling code with double tap support for mobile browsers.
2147 */
2148
2149var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart';
2150var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend';
2151var _pre = '_leaflet_';
2152
2153// inspired by Zepto touch code by Thomas Fuchs
2154function addDoubleTapListener(obj, handler, id) {
2155 var last, touch$$1,
2156 doubleTap = false,
2157 delay = 250;
2158
2159 function onTouchStart(e) {
2160 var count;
2161
2162 if (pointer) {
2163 if ((!edge) || e.pointerType === 'mouse') { return; }
2164 count = _pointersCount;
2165 } else {
2166 count = e.touches.length;
2167 }
2168
2169 if (count > 1) { return; }
2170
2171 var now = Date.now(),
2172 delta = now - (last || now);
2173
2174 touch$$1 = e.touches ? e.touches[0] : e;
2175 doubleTap = (delta > 0 && delta <= delay);
2176 last = now;
2177 }
2178
2179 function onTouchEnd(e) {
2180 if (doubleTap && !touch$$1.cancelBubble) {
2181 if (pointer) {
2182 if ((!edge) || e.pointerType === 'mouse') { return; }
2183 // work around .type being readonly with MSPointer* events
2184 var newTouch = {},
2185 prop, i;
2186
2187 for (i in touch$$1) {
2188 prop = touch$$1[i];
2189 newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop;
2190 }
2191 touch$$1 = newTouch;
2192 }
2193 touch$$1.type = 'dblclick';
2194 touch$$1.button = 0;
2195 handler(touch$$1);
2196 last = null;
2197 }
2198 }
2199
2200 obj[_pre + _touchstart + id] = onTouchStart;
2201 obj[_pre + _touchend + id] = onTouchEnd;
2202 obj[_pre + 'dblclick' + id] = handler;
2203
2204 obj.addEventListener(_touchstart, onTouchStart, passiveEvents ? {passive: false} : false);
2205 obj.addEventListener(_touchend, onTouchEnd, passiveEvents ? {passive: false} : false);
2206
2207 // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
2208 // the browser doesn't fire touchend/pointerup events but does fire
2209 // native dblclicks. See #4127.
2210 // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
2211 obj.addEventListener('dblclick', handler, false);
2212
2213 return this;
2214}
2215
2216function removeDoubleTapListener(obj, id) {
2217 var touchstart = obj[_pre + _touchstart + id],
2218 touchend = obj[_pre + _touchend + id],
2219 dblclick = obj[_pre + 'dblclick' + id];
2220
2221 obj.removeEventListener(_touchstart, touchstart, passiveEvents ? {passive: false} : false);
2222 obj.removeEventListener(_touchend, touchend, passiveEvents ? {passive: false} : false);
2223 if (!edge) {
2224 obj.removeEventListener('dblclick', dblclick, false);
2225 }
2226
2227 return this;
2228}
2229
2230/*
2231 * @namespace DomUtil
2232 *
2233 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
2234 * tree, used by Leaflet internally.
2235 *
2236 * Most functions expecting or returning a `HTMLElement` also work for
2237 * SVG elements. The only difference is that classes refer to CSS classes
2238 * in HTML and SVG classes in SVG.
2239 */
2240
2241
2242// @property TRANSFORM: String
2243// Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
2244var TRANSFORM = testProp(
2245 ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
2246
2247// webkitTransition comes first because some browser versions that drop vendor prefix don't do
2248// the same for the transitionend event, in particular the Android 4.1 stock browser
2249
2250// @property TRANSITION: String
2251// Vendor-prefixed transition style name.
2252var TRANSITION = testProp(
2253 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
2254
2255// @property TRANSITION_END: String
2256// Vendor-prefixed transitionend event name.
2257var TRANSITION_END =
2258 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
2259
2260
2261// @function get(id: String|HTMLElement): HTMLElement
2262// Returns an element given its DOM id, or returns the element itself
2263// if it was passed directly.
2264function get(id) {
2265 return typeof id === 'string' ? document.getElementById(id) : id;
2266}
2267
2268// @function getStyle(el: HTMLElement, styleAttrib: String): String
2269// Returns the value for a certain style attribute on an element,
2270// including computed values or values set through CSS.
2271function getStyle(el, style) {
2272 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
2273
2274 if ((!value || value === 'auto') && document.defaultView) {
2275 var css = document.defaultView.getComputedStyle(el, null);
2276 value = css ? css[style] : null;
2277 }
2278 return value === 'auto' ? null : value;
2279}
2280
2281// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
2282// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
2283function create$1(tagName, className, container) {
2284 var el = document.createElement(tagName);
2285 el.className = className || '';
2286
2287 if (container) {
2288 container.appendChild(el);
2289 }
2290 return el;
2291}
2292
2293// @function remove(el: HTMLElement)
2294// Removes `el` from its parent element
2295function remove(el) {
2296 var parent = el.parentNode;
2297 if (parent) {
2298 parent.removeChild(el);
2299 }
2300}
2301
2302// @function empty(el: HTMLElement)
2303// Removes all of `el`'s children elements from `el`
2304function empty(el) {
2305 while (el.firstChild) {
2306 el.removeChild(el.firstChild);
2307 }
2308}
2309
2310// @function toFront(el: HTMLElement)
2311// Makes `el` the last child of its parent, so it renders in front of the other children.
2312function toFront(el) {
2313 var parent = el.parentNode;
2314 if (parent && parent.lastChild !== el) {
2315 parent.appendChild(el);
2316 }
2317}
2318
2319// @function toBack(el: HTMLElement)
2320// Makes `el` the first child of its parent, so it renders behind the other children.
2321function toBack(el) {
2322 var parent = el.parentNode;
2323 if (parent && parent.firstChild !== el) {
2324 parent.insertBefore(el, parent.firstChild);
2325 }
2326}
2327
2328// @function hasClass(el: HTMLElement, name: String): Boolean
2329// Returns `true` if the element's class attribute contains `name`.
2330function hasClass(el, name) {
2331 if (el.classList !== undefined) {
2332 return el.classList.contains(name);
2333 }
2334 var className = getClass(el);
2335 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
2336}
2337
2338// @function addClass(el: HTMLElement, name: String)
2339// Adds `name` to the element's class attribute.
2340function addClass(el, name) {
2341 if (el.classList !== undefined) {
2342 var classes = splitWords(name);
2343 for (var i = 0, len = classes.length; i < len; i++) {
2344 el.classList.add(classes[i]);
2345 }
2346 } else if (!hasClass(el, name)) {
2347 var className = getClass(el);
2348 setClass(el, (className ? className + ' ' : '') + name);
2349 }
2350}
2351
2352// @function removeClass(el: HTMLElement, name: String)
2353// Removes `name` from the element's class attribute.
2354function removeClass(el, name) {
2355 if (el.classList !== undefined) {
2356 el.classList.remove(name);
2357 } else {
2358 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
2359 }
2360}
2361
2362// @function setClass(el: HTMLElement, name: String)
2363// Sets the element's class.
2364function setClass(el, name) {
2365 if (el.className.baseVal === undefined) {
2366 el.className = name;
2367 } else {
2368 // in case of SVG element
2369 el.className.baseVal = name;
2370 }
2371}
2372
2373// @function getClass(el: HTMLElement): String
2374// Returns the element's class.
2375function getClass(el) {
2376 // Check if the element is an SVGElementInstance and use the correspondingElement instead
2377 // (Required for linked SVG elements in IE11.)
2378 if (el.correspondingElement) {
2379 el = el.correspondingElement;
2380 }
2381 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
2382}
2383
2384// @function setOpacity(el: HTMLElement, opacity: Number)
2385// Set the opacity of an element (including old IE support).
2386// `opacity` must be a number from `0` to `1`.
2387function setOpacity(el, value) {
2388 if ('opacity' in el.style) {
2389 el.style.opacity = value;
2390 } else if ('filter' in el.style) {
2391 _setOpacityIE(el, value);
2392 }
2393}
2394
2395function _setOpacityIE(el, value) {
2396 var filter = false,
2397 filterName = 'DXImageTransform.Microsoft.Alpha';
2398
2399 // filters collection throws an error if we try to retrieve a filter that doesn't exist
2400 try {
2401 filter = el.filters.item(filterName);
2402 } catch (e) {
2403 // don't set opacity to 1 if we haven't already set an opacity,
2404 // it isn't needed and breaks transparent pngs.
2405 if (value === 1) { return; }
2406 }
2407
2408 value = Math.round(value * 100);
2409
2410 if (filter) {
2411 filter.Enabled = (value !== 100);
2412 filter.Opacity = value;
2413 } else {
2414 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
2415 }
2416}
2417
2418// @function testProp(props: String[]): String|false
2419// Goes through the array of style names and returns the first name
2420// that is a valid style name for an element. If no such name is found,
2421// it returns false. Useful for vendor-prefixed styles like `transform`.
2422function testProp(props) {
2423 var style = document.documentElement.style;
2424
2425 for (var i = 0; i < props.length; i++) {
2426 if (props[i] in style) {
2427 return props[i];
2428 }
2429 }
2430 return false;
2431}
2432
2433// @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
2434// Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
2435// and optionally scaled by `scale`. Does not have an effect if the
2436// browser doesn't support 3D CSS transforms.
2437function setTransform(el, offset, scale) {
2438 var pos = offset || new Point(0, 0);
2439
2440 el.style[TRANSFORM] =
2441 (ie3d ?
2442 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
2443 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
2444 (scale ? ' scale(' + scale + ')' : '');
2445}
2446
2447// @function setPosition(el: HTMLElement, position: Point)
2448// Sets the position of `el` to coordinates specified by `position`,
2449// using CSS translate or top/left positioning depending on the browser
2450// (used by Leaflet internally to position its layers).
2451function setPosition(el, point) {
2452
2453 /*eslint-disable */
2454 el._leaflet_pos = point;
2455 /* eslint-enable */
2456
2457 if (any3d) {
2458 setTransform(el, point);
2459 } else {
2460 el.style.left = point.x + 'px';
2461 el.style.top = point.y + 'px';
2462 }
2463}
2464
2465// @function getPosition(el: HTMLElement): Point
2466// Returns the coordinates of an element previously positioned with setPosition.
2467function getPosition(el) {
2468 // this method is only used for elements previously positioned using setPosition,
2469 // so it's safe to cache the position for performance
2470
2471 return el._leaflet_pos || new Point(0, 0);
2472}
2473
2474// @function disableTextSelection()
2475// Prevents the user from generating `selectstart` DOM events, usually generated
2476// when the user drags the mouse through a page with text. Used internally
2477// by Leaflet to override the behaviour of any click-and-drag interaction on
2478// the map. Affects drag interactions on the whole document.
2479
2480// @function enableTextSelection()
2481// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
2482var disableTextSelection;
2483var enableTextSelection;
2484var _userSelect;
2485if ('onselectstart' in document) {
2486 disableTextSelection = function () {
2487 on(window, 'selectstart', preventDefault);
2488 };
2489 enableTextSelection = function () {
2490 off(window, 'selectstart', preventDefault);
2491 };
2492} else {
2493 var userSelectProperty = testProp(
2494 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
2495
2496 disableTextSelection = function () {
2497 if (userSelectProperty) {
2498 var style = document.documentElement.style;
2499 _userSelect = style[userSelectProperty];
2500 style[userSelectProperty] = 'none';
2501 }
2502 };
2503 enableTextSelection = function () {
2504 if (userSelectProperty) {
2505 document.documentElement.style[userSelectProperty] = _userSelect;
2506 _userSelect = undefined;
2507 }
2508 };
2509}
2510
2511// @function disableImageDrag()
2512// As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
2513// for `dragstart` DOM events, usually generated when the user drags an image.
2514function disableImageDrag() {
2515 on(window, 'dragstart', preventDefault);
2516}
2517
2518// @function enableImageDrag()
2519// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
2520function enableImageDrag() {
2521 off(window, 'dragstart', preventDefault);
2522}
2523
2524var _outlineElement;
2525var _outlineStyle;
2526// @function preventOutline(el: HTMLElement)
2527// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
2528// of the element `el` invisible. Used internally by Leaflet to prevent
2529// focusable elements from displaying an outline when the user performs a
2530// drag interaction on them.
2531function preventOutline(element) {
2532 while (element.tabIndex === -1) {
2533 element = element.parentNode;
2534 }
2535 if (!element.style) { return; }
2536 restoreOutline();
2537 _outlineElement = element;
2538 _outlineStyle = element.style.outline;
2539 element.style.outline = 'none';
2540 on(window, 'keydown', restoreOutline);
2541}
2542
2543// @function restoreOutline()
2544// Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
2545function restoreOutline() {
2546 if (!_outlineElement) { return; }
2547 _outlineElement.style.outline = _outlineStyle;
2548 _outlineElement = undefined;
2549 _outlineStyle = undefined;
2550 off(window, 'keydown', restoreOutline);
2551}
2552
2553// @function getSizedParentNode(el: HTMLElement): HTMLElement
2554// Finds the closest parent node which size (width and height) is not null.
2555function getSizedParentNode(element) {
2556 do {
2557 element = element.parentNode;
2558 } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
2559 return element;
2560}
2561
2562// @function getScale(el: HTMLElement): Object
2563// Computes the CSS scale currently applied on the element.
2564// Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
2565// and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
2566function getScale(element) {
2567 var rect = element.getBoundingClientRect(); // Read-only in old browsers.
2568
2569 return {
2570 x: rect.width / element.offsetWidth || 1,
2571 y: rect.height / element.offsetHeight || 1,
2572 boundingClientRect: rect
2573 };
2574}
2575
2576
2577var DomUtil = (Object.freeze || Object)({
2578 TRANSFORM: TRANSFORM,
2579 TRANSITION: TRANSITION,
2580 TRANSITION_END: TRANSITION_END,
2581 get: get,
2582 getStyle: getStyle,
2583 create: create$1,
2584 remove: remove,
2585 empty: empty,
2586 toFront: toFront,
2587 toBack: toBack,
2588 hasClass: hasClass,
2589 addClass: addClass,
2590 removeClass: removeClass,
2591 setClass: setClass,
2592 getClass: getClass,
2593 setOpacity: setOpacity,
2594 testProp: testProp,
2595 setTransform: setTransform,
2596 setPosition: setPosition,
2597 getPosition: getPosition,
2598 disableTextSelection: disableTextSelection,
2599 enableTextSelection: enableTextSelection,
2600 disableImageDrag: disableImageDrag,
2601 enableImageDrag: enableImageDrag,
2602 preventOutline: preventOutline,
2603 restoreOutline: restoreOutline,
2604 getSizedParentNode: getSizedParentNode,
2605 getScale: getScale
2606});
2607
2608/*
2609 * @namespace DomEvent
2610 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
2611 */
2612
2613// Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
2614
2615// @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
2616// Adds a listener function (`fn`) to a particular DOM event type of the
2617// element `el`. You can optionally specify the context of the listener
2618// (object the `this` keyword will point to). You can also pass several
2619// space-separated types (e.g. `'click dblclick'`).
2620
2621// @alternative
2622// @function on(el: HTMLElement, eventMap: Object, context?: Object): this
2623// Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2624function on(obj, types, fn, context) {
2625
2626 if (typeof types === 'object') {
2627 for (var type in types) {
2628 addOne(obj, type, types[type], fn);
2629 }
2630 } else {
2631 types = splitWords(types);
2632
2633 for (var i = 0, len = types.length; i < len; i++) {
2634 addOne(obj, types[i], fn, context);
2635 }
2636 }
2637
2638 return this;
2639}
2640
2641var eventsKey = '_leaflet_events';
2642
2643// @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
2644// Removes a previously added listener function.
2645// Note that if you passed a custom context to on, you must pass the same
2646// context to `off` in order to remove the listener.
2647
2648// @alternative
2649// @function off(el: HTMLElement, eventMap: Object, context?: Object): this
2650// Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2651function off(obj, types, fn, context) {
2652
2653 if (typeof types === 'object') {
2654 for (var type in types) {
2655 removeOne(obj, type, types[type], fn);
2656 }
2657 } else if (types) {
2658 types = splitWords(types);
2659
2660 for (var i = 0, len = types.length; i < len; i++) {
2661 removeOne(obj, types[i], fn, context);
2662 }
2663 } else {
2664 for (var j in obj[eventsKey]) {
2665 removeOne(obj, j, obj[eventsKey][j]);
2666 }
2667 delete obj[eventsKey];
2668 }
2669
2670 return this;
2671}
2672
2673function addOne(obj, type, fn, context) {
2674 var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
2675
2676 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
2677
2678 var handler = function (e) {
2679 return fn.call(context || obj, e || window.event);
2680 };
2681
2682 var originalHandler = handler;
2683
2684 if (pointer && type.indexOf('touch') === 0) {
2685 // Needs DomEvent.Pointer.js
2686 addPointerListener(obj, type, handler, id);
2687
2688 } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
2689 !(pointer && chrome)) {
2690 // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
2691 // See #5180
2692 addDoubleTapListener(obj, handler, id);
2693
2694 } else if ('addEventListener' in obj) {
2695
2696 if (type === 'mousewheel') {
2697 obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, passiveEvents ? {passive: false} : false);
2698
2699 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
2700 handler = function (e) {
2701 e = e || window.event;
2702 if (isExternalTarget(obj, e)) {
2703 originalHandler(e);
2704 }
2705 };
2706 obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
2707
2708 } else {
2709 if (type === 'click' && android) {
2710 handler = function (e) {
2711 filterClick(e, originalHandler);
2712 };
2713 }
2714 obj.addEventListener(type, handler, false);
2715 }
2716
2717 } else if ('attachEvent' in obj) {
2718 obj.attachEvent('on' + type, handler);
2719 }
2720
2721 obj[eventsKey] = obj[eventsKey] || {};
2722 obj[eventsKey][id] = handler;
2723}
2724
2725function removeOne(obj, type, fn, context) {
2726
2727 var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
2728 handler = obj[eventsKey] && obj[eventsKey][id];
2729
2730 if (!handler) { return this; }
2731
2732 if (pointer && type.indexOf('touch') === 0) {
2733 removePointerListener(obj, type, id);
2734
2735 } else if (touch && (type === 'dblclick') && removeDoubleTapListener &&
2736 !(pointer && chrome)) {
2737 removeDoubleTapListener(obj, id);
2738
2739 } else if ('removeEventListener' in obj) {
2740
2741 if (type === 'mousewheel') {
2742 obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, passiveEvents ? {passive: false} : false);
2743
2744 } else {
2745 obj.removeEventListener(
2746 type === 'mouseenter' ? 'mouseover' :
2747 type === 'mouseleave' ? 'mouseout' : type, handler, false);
2748 }
2749
2750 } else if ('detachEvent' in obj) {
2751 obj.detachEvent('on' + type, handler);
2752 }
2753
2754 obj[eventsKey][id] = null;
2755}
2756
2757// @function stopPropagation(ev: DOMEvent): this
2758// Stop the given event from propagation to parent elements. Used inside the listener functions:
2759// ```js
2760// L.DomEvent.on(div, 'click', function (ev) {
2761// L.DomEvent.stopPropagation(ev);
2762// });
2763// ```
2764function stopPropagation(e) {
2765
2766 if (e.stopPropagation) {
2767 e.stopPropagation();
2768 } else if (e.originalEvent) { // In case of Leaflet event.
2769 e.originalEvent._stopped = true;
2770 } else {
2771 e.cancelBubble = true;
2772 }
2773 skipped(e);
2774
2775 return this;
2776}
2777
2778// @function disableScrollPropagation(el: HTMLElement): this
2779// Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
2780function disableScrollPropagation(el) {
2781 addOne(el, 'mousewheel', stopPropagation);
2782 return this;
2783}
2784
2785// @function disableClickPropagation(el: HTMLElement): this
2786// Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
2787// `'mousedown'` and `'touchstart'` events (plus browser variants).
2788function disableClickPropagation(el) {
2789 on(el, 'mousedown touchstart dblclick', stopPropagation);
2790 addOne(el, 'click', fakeStop);
2791 return this;
2792}
2793
2794// @function preventDefault(ev: DOMEvent): this
2795// Prevents the default action of the DOM Event `ev` from happening (such as
2796// following a link in the href of the a element, or doing a POST request
2797// with page reload when a `<form>` is submitted).
2798// Use it inside listener functions.
2799function preventDefault(e) {
2800 if (e.preventDefault) {
2801 e.preventDefault();
2802 } else {
2803 e.returnValue = false;
2804 }
2805 return this;
2806}
2807
2808// @function stop(ev: DOMEvent): this
2809// Does `stopPropagation` and `preventDefault` at the same time.
2810function stop(e) {
2811 preventDefault(e);
2812 stopPropagation(e);
2813 return this;
2814}
2815
2816// @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
2817// Gets normalized mouse position from a DOM event relative to the
2818// `container` (border excluded) or to the whole page if not specified.
2819function getMousePosition(e, container) {
2820 if (!container) {
2821 return new Point(e.clientX, e.clientY);
2822 }
2823
2824 var scale = getScale(container),
2825 offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y)
2826
2827 return new Point(
2828 // offset.left/top values are in page scale (like clientX/Y),
2829 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
2830 (e.clientX - offset.left) / scale.x - container.clientLeft,
2831 (e.clientY - offset.top) / scale.y - container.clientTop
2832 );
2833}
2834
2835// Chrome on Win scrolls double the pixels as in other platforms (see #4538),
2836// and Firefox scrolls device pixels, not CSS pixels
2837var wheelPxFactor =
2838 (win && chrome) ? 2 * window.devicePixelRatio :
2839 gecko ? window.devicePixelRatio : 1;
2840
2841// @function getWheelDelta(ev: DOMEvent): Number
2842// Gets normalized wheel delta from a mousewheel DOM event, in vertical
2843// pixels scrolled (negative if scrolling down).
2844// Events from pointing devices without precise scrolling are mapped to
2845// a best guess of 60 pixels.
2846function getWheelDelta(e) {
2847 return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
2848 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
2849 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
2850 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
2851 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
2852 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
2853 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
2854 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
2855 0;
2856}
2857
2858var skipEvents = {};
2859
2860function fakeStop(e) {
2861 // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
2862 skipEvents[e.type] = true;
2863}
2864
2865function skipped(e) {
2866 var events = skipEvents[e.type];
2867 // reset when checking, as it's only used in map container and propagates outside of the map
2868 skipEvents[e.type] = false;
2869 return events;
2870}
2871
2872// check if element really left/entered the event target (for mouseenter/mouseleave)
2873function isExternalTarget(el, e) {
2874
2875 var related = e.relatedTarget;
2876
2877 if (!related) { return true; }
2878
2879 try {
2880 while (related && (related !== el)) {
2881 related = related.parentNode;
2882 }
2883 } catch (err) {
2884 return false;
2885 }
2886 return (related !== el);
2887}
2888
2889var lastClick;
2890
2891// this is a horrible workaround for a bug in Android where a single touch triggers two click events
2892function filterClick(e, handler) {
2893 var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
2894 elapsed = lastClick && (timeStamp - lastClick);
2895
2896 // are they closer together than 500ms yet more than 100ms?
2897 // Android typically triggers them ~300ms apart while multiple listeners
2898 // on the same event should be triggered far faster;
2899 // or check if click is simulated on the element, and if it is, reject any non-simulated events
2900
2901 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
2902 stop(e);
2903 return;
2904 }
2905 lastClick = timeStamp;
2906
2907 handler(e);
2908}
2909
2910
2911
2912
2913var DomEvent = (Object.freeze || Object)({
2914 on: on,
2915 off: off,
2916 stopPropagation: stopPropagation,
2917 disableScrollPropagation: disableScrollPropagation,
2918 disableClickPropagation: disableClickPropagation,
2919 preventDefault: preventDefault,
2920 stop: stop,
2921 getMousePosition: getMousePosition,
2922 getWheelDelta: getWheelDelta,
2923 fakeStop: fakeStop,
2924 skipped: skipped,
2925 isExternalTarget: isExternalTarget,
2926 addListener: on,
2927 removeListener: off
2928});
2929
2930/*
2931 * @class PosAnimation
2932 * @aka L.PosAnimation
2933 * @inherits Evented
2934 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
2935 *
2936 * @example
2937 * ```js
2938 * var fx = new L.PosAnimation();
2939 * fx.run(el, [300, 500], 0.5);
2940 * ```
2941 *
2942 * @constructor L.PosAnimation()
2943 * Creates a `PosAnimation` object.
2944 *
2945 */
2946
2947var PosAnimation = Evented.extend({
2948
2949 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
2950 // Run an animation of a given element to a new position, optionally setting
2951 // duration in seconds (`0.25` by default) and easing linearity factor (3rd
2952 // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
2953 // `0.5` by default).
2954 run: function (el, newPos, duration, easeLinearity) {
2955 this.stop();
2956
2957 this._el = el;
2958 this._inProgress = true;
2959 this._duration = duration || 0.25;
2960 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
2961
2962 this._startPos = getPosition(el);
2963 this._offset = newPos.subtract(this._startPos);
2964 this._startTime = +new Date();
2965
2966 // @event start: Event
2967 // Fired when the animation starts
2968 this.fire('start');
2969
2970 this._animate();
2971 },
2972
2973 // @method stop()
2974 // Stops the animation (if currently running).
2975 stop: function () {
2976 if (!this._inProgress) { return; }
2977
2978 this._step(true);
2979 this._complete();
2980 },
2981
2982 _animate: function () {
2983 // animation loop
2984 this._animId = requestAnimFrame(this._animate, this);
2985 this._step();
2986 },
2987
2988 _step: function (round) {
2989 var elapsed = (+new Date()) - this._startTime,
2990 duration = this._duration * 1000;
2991
2992 if (elapsed < duration) {
2993 this._runFrame(this._easeOut(elapsed / duration), round);
2994 } else {
2995 this._runFrame(1);
2996 this._complete();
2997 }
2998 },
2999
3000 _runFrame: function (progress, round) {
3001 var pos = this._startPos.add(this._offset.multiplyBy(progress));
3002 if (round) {
3003 pos._round();
3004 }
3005 setPosition(this._el, pos);
3006
3007 // @event step: Event
3008 // Fired continuously during the animation.
3009 this.fire('step');
3010 },
3011
3012 _complete: function () {
3013 cancelAnimFrame(this._animId);
3014
3015 this._inProgress = false;
3016 // @event end: Event
3017 // Fired when the animation ends.
3018 this.fire('end');
3019 },
3020
3021 _easeOut: function (t) {
3022 return 1 - Math.pow(1 - t, this._easeOutPower);
3023 }
3024});
3025
3026/*
3027 * @class Map
3028 * @aka L.Map
3029 * @inherits Evented
3030 *
3031 * The central class of the API — it is used to create a map on a page and manipulate it.
3032 *
3033 * @example
3034 *
3035 * ```js
3036 * // initialize the map on the "map" div with a given center and zoom
3037 * var map = L.map('map', {
3038 * center: [51.505, -0.09],
3039 * zoom: 13
3040 * });
3041 * ```
3042 *
3043 */
3044
3045var Map = Evented.extend({
3046
3047 options: {
3048 // @section Map State Options
3049 // @option crs: CRS = L.CRS.EPSG3857
3050 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
3051 // sure what it means.
3052 crs: EPSG3857,
3053
3054 // @option center: LatLng = undefined
3055 // Initial geographic center of the map
3056 center: undefined,
3057
3058 // @option zoom: Number = undefined
3059 // Initial map zoom level
3060 zoom: undefined,
3061
3062 // @option minZoom: Number = *
3063 // Minimum zoom level of the map.
3064 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3065 // the lowest of their `minZoom` options will be used instead.
3066 minZoom: undefined,
3067
3068 // @option maxZoom: Number = *
3069 // Maximum zoom level of the map.
3070 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3071 // the highest of their `maxZoom` options will be used instead.
3072 maxZoom: undefined,
3073
3074 // @option layers: Layer[] = []
3075 // Array of layers that will be added to the map initially
3076 layers: [],
3077
3078 // @option maxBounds: LatLngBounds = null
3079 // When this option is set, the map restricts the view to the given
3080 // geographical bounds, bouncing the user back if the user tries to pan
3081 // outside the view. To set the restriction dynamically, use
3082 // [`setMaxBounds`](#map-setmaxbounds) method.
3083 maxBounds: undefined,
3084
3085 // @option renderer: Renderer = *
3086 // The default method for drawing vector layers on the map. `L.SVG`
3087 // or `L.Canvas` by default depending on browser support.
3088 renderer: undefined,
3089
3090
3091 // @section Animation Options
3092 // @option zoomAnimation: Boolean = true
3093 // Whether the map zoom animation is enabled. By default it's enabled
3094 // in all browsers that support CSS3 Transitions except Android.
3095 zoomAnimation: true,
3096
3097 // @option zoomAnimationThreshold: Number = 4
3098 // Won't animate zoom if the zoom difference exceeds this value.
3099 zoomAnimationThreshold: 4,
3100
3101 // @option fadeAnimation: Boolean = true
3102 // Whether the tile fade animation is enabled. By default it's enabled
3103 // in all browsers that support CSS3 Transitions except Android.
3104 fadeAnimation: true,
3105
3106 // @option markerZoomAnimation: Boolean = true
3107 // Whether markers animate their zoom with the zoom animation, if disabled
3108 // they will disappear for the length of the animation. By default it's
3109 // enabled in all browsers that support CSS3 Transitions except Android.
3110 markerZoomAnimation: true,
3111
3112 // @option transform3DLimit: Number = 2^23
3113 // Defines the maximum size of a CSS translation transform. The default
3114 // value should not be changed unless a web browser positions layers in
3115 // the wrong place after doing a large `panBy`.
3116 transform3DLimit: 8388608, // Precision limit of a 32-bit float
3117
3118 // @section Interaction Options
3119 // @option zoomSnap: Number = 1
3120 // Forces the map's zoom level to always be a multiple of this, particularly
3121 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
3122 // By default, the zoom level snaps to the nearest integer; lower values
3123 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
3124 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
3125 zoomSnap: 1,
3126
3127 // @option zoomDelta: Number = 1
3128 // Controls how much the map's zoom level will change after a
3129 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
3130 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
3131 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
3132 zoomDelta: 1,
3133
3134 // @option trackResize: Boolean = true
3135 // Whether the map automatically handles browser window resize to update itself.
3136 trackResize: true
3137 },
3138
3139 initialize: function (id, options) { // (HTMLElement or String, Object)
3140 options = setOptions(this, options);
3141
3142 // Make sure to assign internal flags at the beginning,
3143 // to avoid inconsistent state in some edge cases.
3144 this._handlers = [];
3145 this._layers = {};
3146 this._zoomBoundLayers = {};
3147 this._sizeChanged = true;
3148
3149 this._initContainer(id);
3150 this._initLayout();
3151
3152 // hack for https://github.com/Leaflet/Leaflet/issues/1980
3153 this._onResize = bind(this._onResize, this);
3154
3155 this._initEvents();
3156
3157 if (options.maxBounds) {
3158 this.setMaxBounds(options.maxBounds);
3159 }
3160
3161 if (options.zoom !== undefined) {
3162 this._zoom = this._limitZoom(options.zoom);
3163 }
3164
3165 if (options.center && options.zoom !== undefined) {
3166 this.setView(toLatLng(options.center), options.zoom, {reset: true});
3167 }
3168
3169 this.callInitHooks();
3170
3171 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
3172 this._zoomAnimated = TRANSITION && any3d && !mobileOpera &&
3173 this.options.zoomAnimation;
3174
3175 // zoom transitions run with the same duration for all layers, so if one of transitionend events
3176 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
3177 if (this._zoomAnimated) {
3178 this._createAnimProxy();
3179 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
3180 }
3181
3182 this._addLayers(this.options.layers);
3183 },
3184
3185
3186 // @section Methods for modifying map state
3187
3188 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
3189 // Sets the view of the map (geographical center and zoom) with the given
3190 // animation options.
3191 setView: function (center, zoom, options) {
3192
3193 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
3194 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
3195 options = options || {};
3196
3197 this._stop();
3198
3199 if (this._loaded && !options.reset && options !== true) {
3200
3201 if (options.animate !== undefined) {
3202 options.zoom = extend({animate: options.animate}, options.zoom);
3203 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
3204 }
3205
3206 // try animating pan or zoom
3207 var moved = (this._zoom !== zoom) ?
3208 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
3209 this._tryAnimatedPan(center, options.pan);
3210
3211 if (moved) {
3212 // prevent resize handler call, the view will refresh after animation anyway
3213 clearTimeout(this._sizeTimer);
3214 return this;
3215 }
3216 }
3217
3218 // animation didn't start, just reset the map view
3219 this._resetView(center, zoom);
3220
3221 return this;
3222 },
3223
3224 // @method setZoom(zoom: Number, options?: Zoom/pan options): this
3225 // Sets the zoom of the map.
3226 setZoom: function (zoom, options) {
3227 if (!this._loaded) {
3228 this._zoom = zoom;
3229 return this;
3230 }
3231 return this.setView(this.getCenter(), zoom, {zoom: options});
3232 },
3233
3234 // @method zoomIn(delta?: Number, options?: Zoom options): this
3235 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3236 zoomIn: function (delta, options) {
3237 delta = delta || (any3d ? this.options.zoomDelta : 1);
3238 return this.setZoom(this._zoom + delta, options);
3239 },
3240
3241 // @method zoomOut(delta?: Number, options?: Zoom options): this
3242 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3243 zoomOut: function (delta, options) {
3244 delta = delta || (any3d ? this.options.zoomDelta : 1);
3245 return this.setZoom(this._zoom - delta, options);
3246 },
3247
3248 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
3249 // Zooms the map while keeping a specified geographical point on the map
3250 // stationary (e.g. used internally for scroll zoom and double-click zoom).
3251 // @alternative
3252 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
3253 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
3254 setZoomAround: function (latlng, zoom, options) {
3255 var scale = this.getZoomScale(zoom),
3256 viewHalf = this.getSize().divideBy(2),
3257 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
3258
3259 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
3260 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
3261
3262 return this.setView(newCenter, zoom, {zoom: options});
3263 },
3264
3265 _getBoundsCenterZoom: function (bounds, options) {
3266
3267 options = options || {};
3268 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
3269
3270 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3271 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3272
3273 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
3274
3275 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
3276
3277 if (zoom === Infinity) {
3278 return {
3279 center: bounds.getCenter(),
3280 zoom: zoom
3281 };
3282 }
3283
3284 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
3285
3286 swPoint = this.project(bounds.getSouthWest(), zoom),
3287 nePoint = this.project(bounds.getNorthEast(), zoom),
3288 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
3289
3290 return {
3291 center: center,
3292 zoom: zoom
3293 };
3294 },
3295
3296 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
3297 // Sets a map view that contains the given geographical bounds with the
3298 // maximum zoom level possible.
3299 fitBounds: function (bounds, options) {
3300
3301 bounds = toLatLngBounds(bounds);
3302
3303 if (!bounds.isValid()) {
3304 throw new Error('Bounds are not valid.');
3305 }
3306
3307 var target = this._getBoundsCenterZoom(bounds, options);
3308 return this.setView(target.center, target.zoom, options);
3309 },
3310
3311 // @method fitWorld(options?: fitBounds options): this
3312 // Sets a map view that mostly contains the whole world with the maximum
3313 // zoom level possible.
3314 fitWorld: function (options) {
3315 return this.fitBounds([[-90, -180], [90, 180]], options);
3316 },
3317
3318 // @method panTo(latlng: LatLng, options?: Pan options): this
3319 // Pans the map to a given center.
3320 panTo: function (center, options) { // (LatLng)
3321 return this.setView(center, this._zoom, {pan: options});
3322 },
3323
3324 // @method panBy(offset: Point, options?: Pan options): this
3325 // Pans the map by a given number of pixels (animated).
3326 panBy: function (offset, options) {
3327 offset = toPoint(offset).round();
3328 options = options || {};
3329
3330 if (!offset.x && !offset.y) {
3331 return this.fire('moveend');
3332 }
3333 // If we pan too far, Chrome gets issues with tiles
3334 // and makes them disappear or appear in the wrong place (slightly offset) #2602
3335 if (options.animate !== true && !this.getSize().contains(offset)) {
3336 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
3337 return this;
3338 }
3339
3340 if (!this._panAnim) {
3341 this._panAnim = new PosAnimation();
3342
3343 this._panAnim.on({
3344 'step': this._onPanTransitionStep,
3345 'end': this._onPanTransitionEnd
3346 }, this);
3347 }
3348
3349 // don't fire movestart if animating inertia
3350 if (!options.noMoveStart) {
3351 this.fire('movestart');
3352 }
3353
3354 // animate pan unless animate: false specified
3355 if (options.animate !== false) {
3356 addClass(this._mapPane, 'leaflet-pan-anim');
3357
3358 var newPos = this._getMapPanePos().subtract(offset).round();
3359 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
3360 } else {
3361 this._rawPanBy(offset);
3362 this.fire('move').fire('moveend');
3363 }
3364
3365 return this;
3366 },
3367
3368 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
3369 // Sets the view of the map (geographical center and zoom) performing a smooth
3370 // pan-zoom animation.
3371 flyTo: function (targetCenter, targetZoom, options) {
3372
3373 options = options || {};
3374 if (options.animate === false || !any3d) {
3375 return this.setView(targetCenter, targetZoom, options);
3376 }
3377
3378 this._stop();
3379
3380 var from = this.project(this.getCenter()),
3381 to = this.project(targetCenter),
3382 size = this.getSize(),
3383 startZoom = this._zoom;
3384
3385 targetCenter = toLatLng(targetCenter);
3386 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
3387
3388 var w0 = Math.max(size.x, size.y),
3389 w1 = w0 * this.getZoomScale(startZoom, targetZoom),
3390 u1 = (to.distanceTo(from)) || 1,
3391 rho = 1.42,
3392 rho2 = rho * rho;
3393
3394 function r(i) {
3395 var s1 = i ? -1 : 1,
3396 s2 = i ? w1 : w0,
3397 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
3398 b1 = 2 * s2 * rho2 * u1,
3399 b = t1 / b1,
3400 sq = Math.sqrt(b * b + 1) - b;
3401
3402 // workaround for floating point precision bug when sq = 0, log = -Infinite,
3403 // thus triggering an infinite loop in flyTo
3404 var log = sq < 0.000000001 ? -18 : Math.log(sq);
3405
3406 return log;
3407 }
3408
3409 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
3410 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
3411 function tanh(n) { return sinh(n) / cosh(n); }
3412
3413 var r0 = r(0);
3414
3415 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
3416 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
3417
3418 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
3419
3420 var start = Date.now(),
3421 S = (r(1) - r0) / rho,
3422 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
3423
3424 function frame() {
3425 var t = (Date.now() - start) / duration,
3426 s = easeOut(t) * S;
3427
3428 if (t <= 1) {
3429 this._flyToFrame = requestAnimFrame(frame, this);
3430
3431 this._move(
3432 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
3433 this.getScaleZoom(w0 / w(s), startZoom),
3434 {flyTo: true});
3435
3436 } else {
3437 this
3438 ._move(targetCenter, targetZoom)
3439 ._moveEnd(true);
3440 }
3441 }
3442
3443 this._moveStart(true, options.noMoveStart);
3444
3445 frame.call(this);
3446 return this;
3447 },
3448
3449 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
3450 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
3451 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
3452 flyToBounds: function (bounds, options) {
3453 var target = this._getBoundsCenterZoom(bounds, options);
3454 return this.flyTo(target.center, target.zoom, options);
3455 },
3456
3457 // @method setMaxBounds(bounds: Bounds): this
3458 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
3459 setMaxBounds: function (bounds) {
3460 bounds = toLatLngBounds(bounds);
3461
3462 if (!bounds.isValid()) {
3463 this.options.maxBounds = null;
3464 return this.off('moveend', this._panInsideMaxBounds);
3465 } else if (this.options.maxBounds) {
3466 this.off('moveend', this._panInsideMaxBounds);
3467 }
3468
3469 this.options.maxBounds = bounds;
3470
3471 if (this._loaded) {
3472 this._panInsideMaxBounds();
3473 }
3474
3475 return this.on('moveend', this._panInsideMaxBounds);
3476 },
3477
3478 // @method setMinZoom(zoom: Number): this
3479 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
3480 setMinZoom: function (zoom) {
3481 var oldZoom = this.options.minZoom;
3482 this.options.minZoom = zoom;
3483
3484 if (this._loaded && oldZoom !== zoom) {
3485 this.fire('zoomlevelschange');
3486
3487 if (this.getZoom() < this.options.minZoom) {
3488 return this.setZoom(zoom);
3489 }
3490 }
3491
3492 return this;
3493 },
3494
3495 // @method setMaxZoom(zoom: Number): this
3496 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
3497 setMaxZoom: function (zoom) {
3498 var oldZoom = this.options.maxZoom;
3499 this.options.maxZoom = zoom;
3500
3501 if (this._loaded && oldZoom !== zoom) {
3502 this.fire('zoomlevelschange');
3503
3504 if (this.getZoom() > this.options.maxZoom) {
3505 return this.setZoom(zoom);
3506 }
3507 }
3508
3509 return this;
3510 },
3511
3512 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
3513 // 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.
3514 panInsideBounds: function (bounds, options) {
3515 this._enforcingBounds = true;
3516 var center = this.getCenter(),
3517 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
3518
3519 if (!center.equals(newCenter)) {
3520 this.panTo(newCenter, options);
3521 }
3522
3523 this._enforcingBounds = false;
3524 return this;
3525 },
3526
3527 // @method panInside(latlng: LatLng, options?: options): this
3528 // Pans the map the minimum amount to make the `latlng` visible. Use
3529 // `padding`, `paddingTopLeft` and `paddingTopRight` options to fit
3530 // the display to more restricted bounds, like [`fitBounds`](#map-fitbounds).
3531 // If `latlng` is already within the (optionally padded) display bounds,
3532 // the map will not be panned.
3533 panInside: function (latlng, options) {
3534 options = options || {};
3535
3536 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3537 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3538 center = this.getCenter(),
3539 pixelCenter = this.project(center),
3540 pixelPoint = this.project(latlng),
3541 pixelBounds = this.getPixelBounds(),
3542 halfPixelBounds = pixelBounds.getSize().divideBy(2),
3543 paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]);
3544
3545 if (!paddedBounds.contains(pixelPoint)) {
3546 this._enforcingBounds = true;
3547 var diff = pixelCenter.subtract(pixelPoint),
3548 newCenter = toPoint(pixelPoint.x + diff.x, pixelPoint.y + diff.y);
3549
3550 if (pixelPoint.x < paddedBounds.min.x || pixelPoint.x > paddedBounds.max.x) {
3551 newCenter.x = pixelCenter.x - diff.x;
3552 if (diff.x > 0) {
3553 newCenter.x += halfPixelBounds.x - paddingTL.x;
3554 } else {
3555 newCenter.x -= halfPixelBounds.x - paddingBR.x;
3556 }
3557 }
3558 if (pixelPoint.y < paddedBounds.min.y || pixelPoint.y > paddedBounds.max.y) {
3559 newCenter.y = pixelCenter.y - diff.y;
3560 if (diff.y > 0) {
3561 newCenter.y += halfPixelBounds.y - paddingTL.y;
3562 } else {
3563 newCenter.y -= halfPixelBounds.y - paddingBR.y;
3564 }
3565 }
3566 this.panTo(this.unproject(newCenter), options);
3567 this._enforcingBounds = false;
3568 }
3569 return this;
3570 },
3571
3572 // @method invalidateSize(options: Zoom/pan options): this
3573 // Checks if the map container size changed and updates the map if so —
3574 // call it after you've changed the map size dynamically, also animating
3575 // pan by default. If `options.pan` is `false`, panning will not occur.
3576 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
3577 // that it doesn't happen often even if the method is called many
3578 // times in a row.
3579
3580 // @alternative
3581 // @method invalidateSize(animate: Boolean): this
3582 // Checks if the map container size changed and updates the map if so —
3583 // call it after you've changed the map size dynamically, also animating
3584 // pan by default.
3585 invalidateSize: function (options) {
3586 if (!this._loaded) { return this; }
3587
3588 options = extend({
3589 animate: false,
3590 pan: true
3591 }, options === true ? {animate: true} : options);
3592
3593 var oldSize = this.getSize();
3594 this._sizeChanged = true;
3595 this._lastCenter = null;
3596
3597 var newSize = this.getSize(),
3598 oldCenter = oldSize.divideBy(2).round(),
3599 newCenter = newSize.divideBy(2).round(),
3600 offset = oldCenter.subtract(newCenter);
3601
3602 if (!offset.x && !offset.y) { return this; }
3603
3604 if (options.animate && options.pan) {
3605 this.panBy(offset);
3606
3607 } else {
3608 if (options.pan) {
3609 this._rawPanBy(offset);
3610 }
3611
3612 this.fire('move');
3613
3614 if (options.debounceMoveend) {
3615 clearTimeout(this._sizeTimer);
3616 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
3617 } else {
3618 this.fire('moveend');
3619 }
3620 }
3621
3622 // @section Map state change events
3623 // @event resize: ResizeEvent
3624 // Fired when the map is resized.
3625 return this.fire('resize', {
3626 oldSize: oldSize,
3627 newSize: newSize
3628 });
3629 },
3630
3631 // @section Methods for modifying map state
3632 // @method stop(): this
3633 // Stops the currently running `panTo` or `flyTo` animation, if any.
3634 stop: function () {
3635 this.setZoom(this._limitZoom(this._zoom));
3636 if (!this.options.zoomSnap) {
3637 this.fire('viewreset');
3638 }
3639 return this._stop();
3640 },
3641
3642 // @section Geolocation methods
3643 // @method locate(options?: Locate options): this
3644 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
3645 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
3646 // and optionally sets the map view to the user's location with respect to
3647 // detection accuracy (or to the world view if geolocation failed).
3648 // Note that, if your page doesn't use HTTPS, this method will fail in
3649 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
3650 // See `Locate options` for more details.
3651 locate: function (options) {
3652
3653 options = this._locateOptions = extend({
3654 timeout: 10000,
3655 watch: false
3656 // setView: false
3657 // maxZoom: <Number>
3658 // maximumAge: 0
3659 // enableHighAccuracy: false
3660 }, options);
3661
3662 if (!('geolocation' in navigator)) {
3663 this._handleGeolocationError({
3664 code: 0,
3665 message: 'Geolocation not supported.'
3666 });
3667 return this;
3668 }
3669
3670 var onResponse = bind(this._handleGeolocationResponse, this),
3671 onError = bind(this._handleGeolocationError, this);
3672
3673 if (options.watch) {
3674 this._locationWatchId =
3675 navigator.geolocation.watchPosition(onResponse, onError, options);
3676 } else {
3677 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
3678 }
3679 return this;
3680 },
3681
3682 // @method stopLocate(): this
3683 // Stops watching location previously initiated by `map.locate({watch: true})`
3684 // and aborts resetting the map view if map.locate was called with
3685 // `{setView: true}`.
3686 stopLocate: function () {
3687 if (navigator.geolocation && navigator.geolocation.clearWatch) {
3688 navigator.geolocation.clearWatch(this._locationWatchId);
3689 }
3690 if (this._locateOptions) {
3691 this._locateOptions.setView = false;
3692 }
3693 return this;
3694 },
3695
3696 _handleGeolocationError: function (error) {
3697 var c = error.code,
3698 message = error.message ||
3699 (c === 1 ? 'permission denied' :
3700 (c === 2 ? 'position unavailable' : 'timeout'));
3701
3702 if (this._locateOptions.setView && !this._loaded) {
3703 this.fitWorld();
3704 }
3705
3706 // @section Location events
3707 // @event locationerror: ErrorEvent
3708 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
3709 this.fire('locationerror', {
3710 code: c,
3711 message: 'Geolocation error: ' + message + '.'
3712 });
3713 },
3714
3715 _handleGeolocationResponse: function (pos) {
3716 var lat = pos.coords.latitude,
3717 lng = pos.coords.longitude,
3718 latlng = new LatLng(lat, lng),
3719 bounds = latlng.toBounds(pos.coords.accuracy * 2),
3720 options = this._locateOptions;
3721
3722 if (options.setView) {
3723 var zoom = this.getBoundsZoom(bounds);
3724 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
3725 }
3726
3727 var data = {
3728 latlng: latlng,
3729 bounds: bounds,
3730 timestamp: pos.timestamp
3731 };
3732
3733 for (var i in pos.coords) {
3734 if (typeof pos.coords[i] === 'number') {
3735 data[i] = pos.coords[i];
3736 }
3737 }
3738
3739 // @event locationfound: LocationEvent
3740 // Fired when geolocation (using the [`locate`](#map-locate) method)
3741 // went successfully.
3742 this.fire('locationfound', data);
3743 },
3744
3745 // TODO Appropriate docs section?
3746 // @section Other Methods
3747 // @method addHandler(name: String, HandlerClass: Function): this
3748 // Adds a new `Handler` to the map, given its name and constructor function.
3749 addHandler: function (name, HandlerClass) {
3750 if (!HandlerClass) { return this; }
3751
3752 var handler = this[name] = new HandlerClass(this);
3753
3754 this._handlers.push(handler);
3755
3756 if (this.options[name]) {
3757 handler.enable();
3758 }
3759
3760 return this;
3761 },
3762
3763 // @method remove(): this
3764 // Destroys the map and clears all related event listeners.
3765 remove: function () {
3766
3767 this._initEvents(true);
3768
3769 if (this._containerId !== this._container._leaflet_id) {
3770 throw new Error('Map container is being reused by another instance');
3771 }
3772
3773 try {
3774 // throws error in IE6-8
3775 delete this._container._leaflet_id;
3776 delete this._containerId;
3777 } catch (e) {
3778 /*eslint-disable */
3779 this._container._leaflet_id = undefined;
3780 /* eslint-enable */
3781 this._containerId = undefined;
3782 }
3783
3784 if (this._locationWatchId !== undefined) {
3785 this.stopLocate();
3786 }
3787
3788 this._stop();
3789
3790 remove(this._mapPane);
3791
3792 if (this._clearControlPos) {
3793 this._clearControlPos();
3794 }
3795 if (this._resizeRequest) {
3796 cancelAnimFrame(this._resizeRequest);
3797 this._resizeRequest = null;
3798 }
3799
3800 this._clearHandlers();
3801
3802 if (this._loaded) {
3803 // @section Map state change events
3804 // @event unload: Event
3805 // Fired when the map is destroyed with [remove](#map-remove) method.
3806 this.fire('unload');
3807 }
3808
3809 var i;
3810 for (i in this._layers) {
3811 this._layers[i].remove();
3812 }
3813 for (i in this._panes) {
3814 remove(this._panes[i]);
3815 }
3816
3817 this._layers = [];
3818 this._panes = [];
3819 delete this._mapPane;
3820 delete this._renderer;
3821
3822 return this;
3823 },
3824
3825 // @section Other Methods
3826 // @method createPane(name: String, container?: HTMLElement): HTMLElement
3827 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
3828 // then returns it. The pane is created as a child of `container`, or
3829 // as a child of the main map pane if not set.
3830 createPane: function (name, container) {
3831 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3832 pane = create$1('div', className, container || this._mapPane);
3833
3834 if (name) {
3835 this._panes[name] = pane;
3836 }
3837 return pane;
3838 },
3839
3840 // @section Methods for Getting Map State
3841
3842 // @method getCenter(): LatLng
3843 // Returns the geographical center of the map view
3844 getCenter: function () {
3845 this._checkIfLoaded();
3846
3847 if (this._lastCenter && !this._moved()) {
3848 return this._lastCenter;
3849 }
3850 return this.layerPointToLatLng(this._getCenterLayerPoint());
3851 },
3852
3853 // @method getZoom(): Number
3854 // Returns the current zoom level of the map view
3855 getZoom: function () {
3856 return this._zoom;
3857 },
3858
3859 // @method getBounds(): LatLngBounds
3860 // Returns the geographical bounds visible in the current map view
3861 getBounds: function () {
3862 var bounds = this.getPixelBounds(),
3863 sw = this.unproject(bounds.getBottomLeft()),
3864 ne = this.unproject(bounds.getTopRight());
3865
3866 return new LatLngBounds(sw, ne);
3867 },
3868
3869 // @method getMinZoom(): Number
3870 // 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.
3871 getMinZoom: function () {
3872 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3873 },
3874
3875 // @method getMaxZoom(): Number
3876 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3877 getMaxZoom: function () {
3878 return this.options.maxZoom === undefined ?
3879 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3880 this.options.maxZoom;
3881 },
3882
3883 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
3884 // Returns the maximum zoom level on which the given bounds fit to the map
3885 // view in its entirety. If `inside` (optional) is set to `true`, the method
3886 // instead returns the minimum zoom level on which the map view fits into
3887 // the given bounds in its entirety.
3888 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3889 bounds = toLatLngBounds(bounds);
3890 padding = toPoint(padding || [0, 0]);
3891
3892 var zoom = this.getZoom() || 0,
3893 min = this.getMinZoom(),
3894 max = this.getMaxZoom(),
3895 nw = bounds.getNorthWest(),
3896 se = bounds.getSouthEast(),
3897 size = this.getSize().subtract(padding),
3898 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3899 snap = any3d ? this.options.zoomSnap : 1,
3900 scalex = size.x / boundsSize.x,
3901 scaley = size.y / boundsSize.y,
3902 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
3903
3904 zoom = this.getScaleZoom(scale, zoom);
3905
3906 if (snap) {
3907 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3908 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3909 }
3910
3911 return Math.max(min, Math.min(max, zoom));
3912 },
3913
3914 // @method getSize(): Point
3915 // Returns the current size of the map container (in pixels).
3916 getSize: function () {
3917 if (!this._size || this._sizeChanged) {
3918 this._size = new Point(
3919 this._container.clientWidth || 0,
3920 this._container.clientHeight || 0);
3921
3922 this._sizeChanged = false;
3923 }
3924 return this._size.clone();
3925 },
3926
3927 // @method getPixelBounds(): Bounds
3928 // Returns the bounds of the current map view in projected pixel
3929 // coordinates (sometimes useful in layer and overlay implementations).
3930 getPixelBounds: function (center, zoom) {
3931 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3932 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3933 },
3934
3935 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3936 // the map pane? "left point of the map layer" can be confusing, specially
3937 // since there can be negative offsets.
3938 // @method getPixelOrigin(): Point
3939 // Returns the projected pixel coordinates of the top left point of
3940 // the map layer (useful in custom layer and overlay implementations).
3941 getPixelOrigin: function () {
3942 this._checkIfLoaded();
3943 return this._pixelOrigin;
3944 },
3945
3946 // @method getPixelWorldBounds(zoom?: Number): Bounds
3947 // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3948 // If `zoom` is omitted, the map's current zoom level is used.
3949 getPixelWorldBounds: function (zoom) {
3950 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3951 },
3952
3953 // @section Other Methods
3954
3955 // @method getPane(pane: String|HTMLElement): HTMLElement
3956 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3957 getPane: function (pane) {
3958 return typeof pane === 'string' ? this._panes[pane] : pane;
3959 },
3960
3961 // @method getPanes(): Object
3962 // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3963 // the panes as values.
3964 getPanes: function () {
3965 return this._panes;
3966 },
3967
3968 // @method getContainer: HTMLElement
3969 // Returns the HTML element that contains the map.
3970 getContainer: function () {
3971 return this._container;
3972 },
3973
3974
3975 // @section Conversion Methods
3976
3977 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3978 // Returns the scale factor to be applied to a map transition from zoom level
3979 // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3980 getZoomScale: function (toZoom, fromZoom) {
3981 // TODO replace with universal implementation after refactoring projections
3982 var crs = this.options.crs;
3983 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3984 return crs.scale(toZoom) / crs.scale(fromZoom);
3985 },
3986
3987 // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3988 // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3989 // level and everything is scaled by a factor of `scale`. Inverse of
3990 // [`getZoomScale`](#map-getZoomScale).
3991 getScaleZoom: function (scale, fromZoom) {
3992 var crs = this.options.crs;
3993 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3994 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3995 return isNaN(zoom) ? Infinity : zoom;
3996 },
3997
3998 // @method project(latlng: LatLng, zoom: Number): Point
3999 // Projects a geographical coordinate `LatLng` according to the projection
4000 // of the map's CRS, then scales it according to `zoom` and the CRS's
4001 // `Transformation`. The result is pixel coordinate relative to
4002 // the CRS origin.
4003 project: function (latlng, zoom) {
4004 zoom = zoom === undefined ? this._zoom : zoom;
4005 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
4006 },
4007
4008 // @method unproject(point: Point, zoom: Number): LatLng
4009 // Inverse of [`project`](#map-project).
4010 unproject: function (point, zoom) {
4011 zoom = zoom === undefined ? this._zoom : zoom;
4012 return this.options.crs.pointToLatLng(toPoint(point), zoom);
4013 },
4014
4015 // @method layerPointToLatLng(point: Point): LatLng
4016 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
4017 // returns the corresponding geographical coordinate (for the current zoom level).
4018 layerPointToLatLng: function (point) {
4019 var projectedPoint = toPoint(point).add(this.getPixelOrigin());
4020 return this.unproject(projectedPoint);
4021 },
4022
4023 // @method latLngToLayerPoint(latlng: LatLng): Point
4024 // Given a geographical coordinate, returns the corresponding pixel coordinate
4025 // relative to the [origin pixel](#map-getpixelorigin).
4026 latLngToLayerPoint: function (latlng) {
4027 var projectedPoint = this.project(toLatLng(latlng))._round();
4028 return projectedPoint._subtract(this.getPixelOrigin());
4029 },
4030
4031 // @method wrapLatLng(latlng: LatLng): LatLng
4032 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
4033 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
4034 // CRS's bounds.
4035 // By default this means longitude is wrapped around the dateline so its
4036 // value is between -180 and +180 degrees.
4037 wrapLatLng: function (latlng) {
4038 return this.options.crs.wrapLatLng(toLatLng(latlng));
4039 },
4040
4041 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
4042 // Returns a `LatLngBounds` with the same size as the given one, ensuring that
4043 // its center is within the CRS's bounds.
4044 // By default this means the center longitude is wrapped around the dateline so its
4045 // value is between -180 and +180 degrees, and the majority of the bounds
4046 // overlaps the CRS's bounds.
4047 wrapLatLngBounds: function (latlng) {
4048 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
4049 },
4050
4051 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
4052 // Returns the distance between two geographical coordinates according to
4053 // the map's CRS. By default this measures distance in meters.
4054 distance: function (latlng1, latlng2) {
4055 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
4056 },
4057
4058 // @method containerPointToLayerPoint(point: Point): Point
4059 // Given a pixel coordinate relative to the map container, returns the corresponding
4060 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
4061 containerPointToLayerPoint: function (point) { // (Point)
4062 return toPoint(point).subtract(this._getMapPanePos());
4063 },
4064
4065 // @method layerPointToContainerPoint(point: Point): Point
4066 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
4067 // returns the corresponding pixel coordinate relative to the map container.
4068 layerPointToContainerPoint: function (point) { // (Point)
4069 return toPoint(point).add(this._getMapPanePos());
4070 },
4071
4072 // @method containerPointToLatLng(point: Point): LatLng
4073 // Given a pixel coordinate relative to the map container, returns
4074 // the corresponding geographical coordinate (for the current zoom level).
4075 containerPointToLatLng: function (point) {
4076 var layerPoint = this.containerPointToLayerPoint(toPoint(point));
4077 return this.layerPointToLatLng(layerPoint);
4078 },
4079
4080 // @method latLngToContainerPoint(latlng: LatLng): Point
4081 // Given a geographical coordinate, returns the corresponding pixel coordinate
4082 // relative to the map container.
4083 latLngToContainerPoint: function (latlng) {
4084 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
4085 },
4086
4087 // @method mouseEventToContainerPoint(ev: MouseEvent): Point
4088 // Given a MouseEvent object, returns the pixel coordinate relative to the
4089 // map container where the event took place.
4090 mouseEventToContainerPoint: function (e) {
4091 return getMousePosition(e, this._container);
4092 },
4093
4094 // @method mouseEventToLayerPoint(ev: MouseEvent): Point
4095 // Given a MouseEvent object, returns the pixel coordinate relative to
4096 // the [origin pixel](#map-getpixelorigin) where the event took place.
4097 mouseEventToLayerPoint: function (e) {
4098 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
4099 },
4100
4101 // @method mouseEventToLatLng(ev: MouseEvent): LatLng
4102 // Given a MouseEvent object, returns geographical coordinate where the
4103 // event took place.
4104 mouseEventToLatLng: function (e) { // (MouseEvent)
4105 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
4106 },
4107
4108
4109 // map initialization methods
4110
4111 _initContainer: function (id) {
4112 var container = this._container = get(id);
4113
4114 if (!container) {
4115 throw new Error('Map container not found.');
4116 } else if (container._leaflet_id) {
4117 throw new Error('Map container is already initialized.');
4118 }
4119
4120 on(container, 'scroll', this._onScroll, this);
4121 this._containerId = stamp(container);
4122 },
4123
4124 _initLayout: function () {
4125 var container = this._container;
4126
4127 this._fadeAnimated = this.options.fadeAnimation && any3d;
4128
4129 addClass(container, 'leaflet-container' +
4130 (touch ? ' leaflet-touch' : '') +
4131 (retina ? ' leaflet-retina' : '') +
4132 (ielt9 ? ' leaflet-oldie' : '') +
4133 (safari ? ' leaflet-safari' : '') +
4134 (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
4135
4136 var position = getStyle(container, 'position');
4137
4138 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
4139 container.style.position = 'relative';
4140 }
4141
4142 this._initPanes();
4143
4144 if (this._initControlPos) {
4145 this._initControlPos();
4146 }
4147 },
4148
4149 _initPanes: function () {
4150 var panes = this._panes = {};
4151 this._paneRenderers = {};
4152
4153 // @section
4154 //
4155 // Panes are DOM elements used to control the ordering of layers on the map. You
4156 // can access panes with [`map.getPane`](#map-getpane) or
4157 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
4158 // [`map.createPane`](#map-createpane) method.
4159 //
4160 // Every map has the following default panes that differ only in zIndex.
4161 //
4162 // @pane mapPane: HTMLElement = 'auto'
4163 // Pane that contains all other map panes
4164
4165 this._mapPane = this.createPane('mapPane', this._container);
4166 setPosition(this._mapPane, new Point(0, 0));
4167
4168 // @pane tilePane: HTMLElement = 200
4169 // Pane for `GridLayer`s and `TileLayer`s
4170 this.createPane('tilePane');
4171 // @pane overlayPane: HTMLElement = 400
4172 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
4173 this.createPane('shadowPane');
4174 // @pane shadowPane: HTMLElement = 500
4175 // Pane for overlay shadows (e.g. `Marker` shadows)
4176 this.createPane('overlayPane');
4177 // @pane markerPane: HTMLElement = 600
4178 // Pane for `Icon`s of `Marker`s
4179 this.createPane('markerPane');
4180 // @pane tooltipPane: HTMLElement = 650
4181 // Pane for `Tooltip`s.
4182 this.createPane('tooltipPane');
4183 // @pane popupPane: HTMLElement = 700
4184 // Pane for `Popup`s.
4185 this.createPane('popupPane');
4186
4187 if (!this.options.markerZoomAnimation) {
4188 addClass(panes.markerPane, 'leaflet-zoom-hide');
4189 addClass(panes.shadowPane, 'leaflet-zoom-hide');
4190 }
4191 },
4192
4193
4194 // private methods that modify map state
4195
4196 // @section Map state change events
4197 _resetView: function (center, zoom) {
4198 setPosition(this._mapPane, new Point(0, 0));
4199
4200 var loading = !this._loaded;
4201 this._loaded = true;
4202 zoom = this._limitZoom(zoom);
4203
4204 this.fire('viewprereset');
4205
4206 var zoomChanged = this._zoom !== zoom;
4207 this
4208 ._moveStart(zoomChanged, false)
4209 ._move(center, zoom)
4210 ._moveEnd(zoomChanged);
4211
4212 // @event viewreset: Event
4213 // Fired when the map needs to redraw its content (this usually happens
4214 // on map zoom or load). Very useful for creating custom overlays.
4215 this.fire('viewreset');
4216
4217 // @event load: Event
4218 // Fired when the map is initialized (when its center and zoom are set
4219 // for the first time).
4220 if (loading) {
4221 this.fire('load');
4222 }
4223 },
4224
4225 _moveStart: function (zoomChanged, noMoveStart) {
4226 // @event zoomstart: Event
4227 // Fired when the map zoom is about to change (e.g. before zoom animation).
4228 // @event movestart: Event
4229 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
4230 if (zoomChanged) {
4231 this.fire('zoomstart');
4232 }
4233 if (!noMoveStart) {
4234 this.fire('movestart');
4235 }
4236 return this;
4237 },
4238
4239 _move: function (center, zoom, data) {
4240 if (zoom === undefined) {
4241 zoom = this._zoom;
4242 }
4243 var zoomChanged = this._zoom !== zoom;
4244
4245 this._zoom = zoom;
4246 this._lastCenter = center;
4247 this._pixelOrigin = this._getNewPixelOrigin(center);
4248
4249 // @event zoom: Event
4250 // Fired repeatedly during any change in zoom level, including zoom
4251 // and fly animations.
4252 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530
4253 this.fire('zoom', data);
4254 }
4255
4256 // @event move: Event
4257 // Fired repeatedly during any movement of the map, including pan and
4258 // fly animations.
4259 return this.fire('move', data);
4260 },
4261
4262 _moveEnd: function (zoomChanged) {
4263 // @event zoomend: Event
4264 // Fired when the map has changed, after any animations.
4265 if (zoomChanged) {
4266 this.fire('zoomend');
4267 }
4268
4269 // @event moveend: Event
4270 // Fired when the center of the map stops changing (e.g. user stopped
4271 // dragging the map).
4272 return this.fire('moveend');
4273 },
4274
4275 _stop: function () {
4276 cancelAnimFrame(this._flyToFrame);
4277 if (this._panAnim) {
4278 this._panAnim.stop();
4279 }
4280 return this;
4281 },
4282
4283 _rawPanBy: function (offset) {
4284 setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
4285 },
4286
4287 _getZoomSpan: function () {
4288 return this.getMaxZoom() - this.getMinZoom();
4289 },
4290
4291 _panInsideMaxBounds: function () {
4292 if (!this._enforcingBounds) {
4293 this.panInsideBounds(this.options.maxBounds);
4294 }
4295 },
4296
4297 _checkIfLoaded: function () {
4298 if (!this._loaded) {
4299 throw new Error('Set map center and zoom first.');
4300 }
4301 },
4302
4303 // DOM event handling
4304
4305 // @section Interaction events
4306 _initEvents: function (remove$$1) {
4307 this._targets = {};
4308 this._targets[stamp(this._container)] = this;
4309
4310 var onOff = remove$$1 ? off : on;
4311
4312 // @event click: MouseEvent
4313 // Fired when the user clicks (or taps) the map.
4314 // @event dblclick: MouseEvent
4315 // Fired when the user double-clicks (or double-taps) the map.
4316 // @event mousedown: MouseEvent
4317 // Fired when the user pushes the mouse button on the map.
4318 // @event mouseup: MouseEvent
4319 // Fired when the user releases the mouse button on the map.
4320 // @event mouseover: MouseEvent
4321 // Fired when the mouse enters the map.
4322 // @event mouseout: MouseEvent
4323 // Fired when the mouse leaves the map.
4324 // @event mousemove: MouseEvent
4325 // Fired while the mouse moves over the map.
4326 // @event contextmenu: MouseEvent
4327 // Fired when the user pushes the right mouse button on the map, prevents
4328 // default browser context menu from showing if there are listeners on
4329 // this event. Also fired on mobile when the user holds a single touch
4330 // for a second (also called long press).
4331 // @event keypress: KeyboardEvent
4332 // Fired when the user presses a key from the keyboard that produces a character value while the map is focused.
4333 // @event keydown: KeyboardEvent
4334 // Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event,
4335 // the `keydown` event is fired for keys that produce a character value and for keys
4336 // that do not produce a character value.
4337 // @event keyup: KeyboardEvent
4338 // Fired when the user releases a key from the keyboard while the map is focused.
4339 onOff(this._container, 'click dblclick mousedown mouseup ' +
4340 'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this);
4341
4342 if (this.options.trackResize) {
4343 onOff(window, 'resize', this._onResize, this);
4344 }
4345
4346 if (any3d && this.options.transform3DLimit) {
4347 (remove$$1 ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
4348 }
4349 },
4350
4351 _onResize: function () {
4352 cancelAnimFrame(this._resizeRequest);
4353 this._resizeRequest = requestAnimFrame(
4354 function () { this.invalidateSize({debounceMoveend: true}); }, this);
4355 },
4356
4357 _onScroll: function () {
4358 this._container.scrollTop = 0;
4359 this._container.scrollLeft = 0;
4360 },
4361
4362 _onMoveEnd: function () {
4363 var pos = this._getMapPanePos();
4364 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
4365 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
4366 // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
4367 this._resetView(this.getCenter(), this.getZoom());
4368 }
4369 },
4370
4371 _findEventTargets: function (e, type) {
4372 var targets = [],
4373 target,
4374 isHover = type === 'mouseout' || type === 'mouseover',
4375 src = e.target || e.srcElement,
4376 dragging = false;
4377
4378 while (src) {
4379 target = this._targets[stamp(src)];
4380 if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
4381 // Prevent firing click after you just dragged an object.
4382 dragging = true;
4383 break;
4384 }
4385 if (target && target.listens(type, true)) {
4386 if (isHover && !isExternalTarget(src, e)) { break; }
4387 targets.push(target);
4388 if (isHover) { break; }
4389 }
4390 if (src === this._container) { break; }
4391 src = src.parentNode;
4392 }
4393 if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) {
4394 targets = [this];
4395 }
4396 return targets;
4397 },
4398
4399 _handleDOMEvent: function (e) {
4400 if (!this._loaded || skipped(e)) { return; }
4401
4402 var type = e.type;
4403
4404 if (type === 'mousedown' || type === 'keypress' || type === 'keyup' || type === 'keydown') {
4405 // prevents outline when clicking on keyboard-focusable element
4406 preventOutline(e.target || e.srcElement);
4407 }
4408
4409 this._fireDOMEvent(e, type);
4410 },
4411
4412 _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
4413
4414 _fireDOMEvent: function (e, type, targets) {
4415
4416 if (e.type === 'click') {
4417 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
4418 // @event preclick: MouseEvent
4419 // Fired before mouse click on the map (sometimes useful when you
4420 // want something to happen on click before any existing click
4421 // handlers start running).
4422 var synth = extend({}, e);
4423 synth.type = 'preclick';
4424 this._fireDOMEvent(synth, synth.type, targets);
4425 }
4426
4427 if (e._stopped) { return; }
4428
4429 // Find the layer the event is propagating from and its parents.
4430 targets = (targets || []).concat(this._findEventTargets(e, type));
4431
4432 if (!targets.length) { return; }
4433
4434 var target = targets[0];
4435 if (type === 'contextmenu' && target.listens(type, true)) {
4436 preventDefault(e);
4437 }
4438
4439 var data = {
4440 originalEvent: e
4441 };
4442
4443 if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') {
4444 var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
4445 data.containerPoint = isMarker ?
4446 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
4447 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
4448 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
4449 }
4450
4451 for (var i = 0; i < targets.length; i++) {
4452 targets[i].fire(type, data, true);
4453 if (data.originalEvent._stopped ||
4454 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
4455 }
4456 },
4457
4458 _draggableMoved: function (obj) {
4459 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
4460 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
4461 },
4462
4463 _clearHandlers: function () {
4464 for (var i = 0, len = this._handlers.length; i < len; i++) {
4465 this._handlers[i].disable();
4466 }
4467 },
4468
4469 // @section Other Methods
4470
4471 // @method whenReady(fn: Function, context?: Object): this
4472 // Runs the given function `fn` when the map gets initialized with
4473 // a view (center and zoom) and at least one layer, or immediately
4474 // if it's already initialized, optionally passing a function context.
4475 whenReady: function (callback, context) {
4476 if (this._loaded) {
4477 callback.call(context || this, {target: this});
4478 } else {
4479 this.on('load', callback, context);
4480 }
4481 return this;
4482 },
4483
4484
4485 // private methods for getting map state
4486
4487 _getMapPanePos: function () {
4488 return getPosition(this._mapPane) || new Point(0, 0);
4489 },
4490
4491 _moved: function () {
4492 var pos = this._getMapPanePos();
4493 return pos && !pos.equals([0, 0]);
4494 },
4495
4496 _getTopLeftPoint: function (center, zoom) {
4497 var pixelOrigin = center && zoom !== undefined ?
4498 this._getNewPixelOrigin(center, zoom) :
4499 this.getPixelOrigin();
4500 return pixelOrigin.subtract(this._getMapPanePos());
4501 },
4502
4503 _getNewPixelOrigin: function (center, zoom) {
4504 var viewHalf = this.getSize()._divideBy(2);
4505 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
4506 },
4507
4508 _latLngToNewLayerPoint: function (latlng, zoom, center) {
4509 var topLeft = this._getNewPixelOrigin(center, zoom);
4510 return this.project(latlng, zoom)._subtract(topLeft);
4511 },
4512
4513 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
4514 var topLeft = this._getNewPixelOrigin(center, zoom);
4515 return toBounds([
4516 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
4517 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
4518 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
4519 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
4520 ]);
4521 },
4522
4523 // layer point of the current center
4524 _getCenterLayerPoint: function () {
4525 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
4526 },
4527
4528 // offset of the specified place to the current center in pixels
4529 _getCenterOffset: function (latlng) {
4530 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
4531 },
4532
4533 // adjust center for view to get inside bounds
4534 _limitCenter: function (center, zoom, bounds) {
4535
4536 if (!bounds) { return center; }
4537
4538 var centerPoint = this.project(center, zoom),
4539 viewHalf = this.getSize().divideBy(2),
4540 viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
4541 offset = this._getBoundsOffset(viewBounds, bounds, zoom);
4542
4543 // If offset is less than a pixel, ignore.
4544 // This prevents unstable projections from getting into
4545 // an infinite loop of tiny offsets.
4546 if (offset.round().equals([0, 0])) {
4547 return center;
4548 }
4549
4550 return this.unproject(centerPoint.add(offset), zoom);
4551 },
4552
4553 // adjust offset for view to get inside bounds
4554 _limitOffset: function (offset, bounds) {
4555 if (!bounds) { return offset; }
4556
4557 var viewBounds = this.getPixelBounds(),
4558 newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
4559
4560 return offset.add(this._getBoundsOffset(newBounds, bounds));
4561 },
4562
4563 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
4564 _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
4565 var projectedMaxBounds = toBounds(
4566 this.project(maxBounds.getNorthEast(), zoom),
4567 this.project(maxBounds.getSouthWest(), zoom)
4568 ),
4569 minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
4570 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
4571
4572 dx = this._rebound(minOffset.x, -maxOffset.x),
4573 dy = this._rebound(minOffset.y, -maxOffset.y);
4574
4575 return new Point(dx, dy);
4576 },
4577
4578 _rebound: function (left, right) {
4579 return left + right > 0 ?
4580 Math.round(left - right) / 2 :
4581 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
4582 },
4583
4584 _limitZoom: function (zoom) {
4585 var min = this.getMinZoom(),
4586 max = this.getMaxZoom(),
4587 snap = any3d ? this.options.zoomSnap : 1;
4588 if (snap) {
4589 zoom = Math.round(zoom / snap) * snap;
4590 }
4591 return Math.max(min, Math.min(max, zoom));
4592 },
4593
4594 _onPanTransitionStep: function () {
4595 this.fire('move');
4596 },
4597
4598 _onPanTransitionEnd: function () {
4599 removeClass(this._mapPane, 'leaflet-pan-anim');
4600 this.fire('moveend');
4601 },
4602
4603 _tryAnimatedPan: function (center, options) {
4604 // difference between the new and current centers in pixels
4605 var offset = this._getCenterOffset(center)._trunc();
4606
4607 // don't animate too far unless animate: true specified in options
4608 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
4609
4610 this.panBy(offset, options);
4611
4612 return true;
4613 },
4614
4615 _createAnimProxy: function () {
4616
4617 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
4618 this._panes.mapPane.appendChild(proxy);
4619
4620 this.on('zoomanim', function (e) {
4621 var prop = TRANSFORM,
4622 transform = this._proxy.style[prop];
4623
4624 setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
4625
4626 // workaround for case when transform is the same and so transitionend event is not fired
4627 if (transform === this._proxy.style[prop] && this._animatingZoom) {
4628 this._onZoomTransitionEnd();
4629 }
4630 }, this);
4631
4632 this.on('load moveend', this._animMoveEnd, this);
4633
4634 this._on('unload', this._destroyAnimProxy, this);
4635 },
4636
4637 _destroyAnimProxy: function () {
4638 remove(this._proxy);
4639 this.off('load moveend', this._animMoveEnd, this);
4640 delete this._proxy;
4641 },
4642
4643 _animMoveEnd: function () {
4644 var c = this.getCenter(),
4645 z = this.getZoom();
4646 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
4647 },
4648
4649 _catchTransitionEnd: function (e) {
4650 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
4651 this._onZoomTransitionEnd();
4652 }
4653 },
4654
4655 _nothingToAnimate: function () {
4656 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
4657 },
4658
4659 _tryAnimatedZoom: function (center, zoom, options) {
4660
4661 if (this._animatingZoom) { return true; }
4662
4663 options = options || {};
4664
4665 // don't animate if disabled, not supported or zoom difference is too large
4666 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
4667 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
4668
4669 // offset is the pixel coords of the zoom origin relative to the current center
4670 var scale = this.getZoomScale(zoom),
4671 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
4672
4673 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
4674 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
4675
4676 requestAnimFrame(function () {
4677 this
4678 ._moveStart(true, false)
4679 ._animateZoom(center, zoom, true);
4680 }, this);
4681
4682 return true;
4683 },
4684
4685 _animateZoom: function (center, zoom, startAnim, noUpdate) {
4686 if (!this._mapPane) { return; }
4687
4688 if (startAnim) {
4689 this._animatingZoom = true;
4690
4691 // remember what center/zoom to set after animation
4692 this._animateToCenter = center;
4693 this._animateToZoom = zoom;
4694
4695 addClass(this._mapPane, 'leaflet-zoom-anim');
4696 }
4697
4698 // @section Other Events
4699 // @event zoomanim: ZoomAnimEvent
4700 // Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom.
4701 this.fire('zoomanim', {
4702 center: center,
4703 zoom: zoom,
4704 noUpdate: noUpdate
4705 });
4706
4707 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
4708 setTimeout(bind(this._onZoomTransitionEnd, this), 250);
4709 },
4710
4711 _onZoomTransitionEnd: function () {
4712 if (!this._animatingZoom) { return; }
4713
4714 if (this._mapPane) {
4715 removeClass(this._mapPane, 'leaflet-zoom-anim');
4716 }
4717
4718 this._animatingZoom = false;
4719
4720 this._move(this._animateToCenter, this._animateToZoom);
4721
4722 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
4723 requestAnimFrame(function () {
4724 this._moveEnd(true);
4725 }, this);
4726 }
4727});
4728
4729// @section
4730
4731// @factory L.map(id: String, options?: Map options)
4732// Instantiates a map object given the DOM ID of a `<div>` element
4733// and optionally an object literal with `Map options`.
4734//
4735// @alternative
4736// @factory L.map(el: HTMLElement, options?: Map options)
4737// Instantiates a map object given an instance of a `<div>` HTML element
4738// and optionally an object literal with `Map options`.
4739function createMap(id, options) {
4740 return new Map(id, options);
4741}
4742
4743/*
4744 * @class Control
4745 * @aka L.Control
4746 * @inherits Class
4747 *
4748 * L.Control is a base class for implementing map controls. Handles positioning.
4749 * All other controls extend from this class.
4750 */
4751
4752var Control = Class.extend({
4753 // @section
4754 // @aka Control options
4755 options: {
4756 // @option position: String = 'topright'
4757 // The position of the control (one of the map corners). Possible values are `'topleft'`,
4758 // `'topright'`, `'bottomleft'` or `'bottomright'`
4759 position: 'topright'
4760 },
4761
4762 initialize: function (options) {
4763 setOptions(this, options);
4764 },
4765
4766 /* @section
4767 * Classes extending L.Control will inherit the following methods:
4768 *
4769 * @method getPosition: string
4770 * Returns the position of the control.
4771 */
4772 getPosition: function () {
4773 return this.options.position;
4774 },
4775
4776 // @method setPosition(position: string): this
4777 // Sets the position of the control.
4778 setPosition: function (position) {
4779 var map = this._map;
4780
4781 if (map) {
4782 map.removeControl(this);
4783 }
4784
4785 this.options.position = position;
4786
4787 if (map) {
4788 map.addControl(this);
4789 }
4790
4791 return this;
4792 },
4793
4794 // @method getContainer: HTMLElement
4795 // Returns the HTMLElement that contains the control.
4796 getContainer: function () {
4797 return this._container;
4798 },
4799
4800 // @method addTo(map: Map): this
4801 // Adds the control to the given map.
4802 addTo: function (map) {
4803 this.remove();
4804 this._map = map;
4805
4806 var container = this._container = this.onAdd(map),
4807 pos = this.getPosition(),
4808 corner = map._controlCorners[pos];
4809
4810 addClass(container, 'leaflet-control');
4811
4812 if (pos.indexOf('bottom') !== -1) {
4813 corner.insertBefore(container, corner.firstChild);
4814 } else {
4815 corner.appendChild(container);
4816 }
4817
4818 this._map.on('unload', this.remove, this);
4819
4820 return this;
4821 },
4822
4823 // @method remove: this
4824 // Removes the control from the map it is currently active on.
4825 remove: function () {
4826 if (!this._map) {
4827 return this;
4828 }
4829
4830 remove(this._container);
4831
4832 if (this.onRemove) {
4833 this.onRemove(this._map);
4834 }
4835
4836 this._map.off('unload', this.remove, this);
4837 this._map = null;
4838
4839 return this;
4840 },
4841
4842 _refocusOnMap: function (e) {
4843 // if map exists and event is not a keyboard event
4844 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
4845 this._map.getContainer().focus();
4846 }
4847 }
4848});
4849
4850var control = function (options) {
4851 return new Control(options);
4852};
4853
4854/* @section Extension methods
4855 * @uninheritable
4856 *
4857 * Every control should extend from `L.Control` and (re-)implement the following methods.
4858 *
4859 * @method onAdd(map: Map): HTMLElement
4860 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
4861 *
4862 * @method onRemove(map: Map)
4863 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
4864 */
4865
4866/* @namespace Map
4867 * @section Methods for Layers and Controls
4868 */
4869Map.include({
4870 // @method addControl(control: Control): this
4871 // Adds the given control to the map
4872 addControl: function (control) {
4873 control.addTo(this);
4874 return this;
4875 },
4876
4877 // @method removeControl(control: Control): this
4878 // Removes the given control from the map
4879 removeControl: function (control) {
4880 control.remove();
4881 return this;
4882 },
4883
4884 _initControlPos: function () {
4885 var corners = this._controlCorners = {},
4886 l = 'leaflet-',
4887 container = this._controlContainer =
4888 create$1('div', l + 'control-container', this._container);
4889
4890 function createCorner(vSide, hSide) {
4891 var className = l + vSide + ' ' + l + hSide;
4892
4893 corners[vSide + hSide] = create$1('div', className, container);
4894 }
4895
4896 createCorner('top', 'left');
4897 createCorner('top', 'right');
4898 createCorner('bottom', 'left');
4899 createCorner('bottom', 'right');
4900 },
4901
4902 _clearControlPos: function () {
4903 for (var i in this._controlCorners) {
4904 remove(this._controlCorners[i]);
4905 }
4906 remove(this._controlContainer);
4907 delete this._controlCorners;
4908 delete this._controlContainer;
4909 }
4910});
4911
4912/*
4913 * @class Control.Layers
4914 * @aka L.Control.Layers
4915 * @inherits Control
4916 *
4917 * 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`.
4918 *
4919 * @example
4920 *
4921 * ```js
4922 * var baseLayers = {
4923 * "Mapbox": mapbox,
4924 * "OpenStreetMap": osm
4925 * };
4926 *
4927 * var overlays = {
4928 * "Marker": marker,
4929 * "Roads": roadsLayer
4930 * };
4931 *
4932 * L.control.layers(baseLayers, overlays).addTo(map);
4933 * ```
4934 *
4935 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
4936 *
4937 * ```js
4938 * {
4939 * "<someName1>": layer1,
4940 * "<someName2>": layer2
4941 * }
4942 * ```
4943 *
4944 * The layer names can contain HTML, which allows you to add additional styling to the items:
4945 *
4946 * ```js
4947 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
4948 * ```
4949 */
4950
4951var Layers = Control.extend({
4952 // @section
4953 // @aka Control.Layers options
4954 options: {
4955 // @option collapsed: Boolean = true
4956 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
4957 collapsed: true,
4958 position: 'topright',
4959
4960 // @option autoZIndex: Boolean = true
4961 // 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.
4962 autoZIndex: true,
4963
4964 // @option hideSingleBase: Boolean = false
4965 // If `true`, the base layers in the control will be hidden when there is only one.
4966 hideSingleBase: false,
4967
4968 // @option sortLayers: Boolean = false
4969 // Whether to sort the layers. When `false`, layers will keep the order
4970 // in which they were added to the control.
4971 sortLayers: false,
4972
4973 // @option sortFunction: Function = *
4974 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
4975 // that will be used for sorting the layers, when `sortLayers` is `true`.
4976 // The function receives both the `L.Layer` instances and their names, as in
4977 // `sortFunction(layerA, layerB, nameA, nameB)`.
4978 // By default, it sorts layers alphabetically by their name.
4979 sortFunction: function (layerA, layerB, nameA, nameB) {
4980 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
4981 }
4982 },
4983
4984 initialize: function (baseLayers, overlays, options) {
4985 setOptions(this, options);
4986
4987 this._layerControlInputs = [];
4988 this._layers = [];
4989 this._lastZIndex = 0;
4990 this._handlingClick = false;
4991
4992 for (var i in baseLayers) {
4993 this._addLayer(baseLayers[i], i);
4994 }
4995
4996 for (i in overlays) {
4997 this._addLayer(overlays[i], i, true);
4998 }
4999 },
5000
5001 onAdd: function (map) {
5002 this._initLayout();
5003 this._update();
5004
5005 this._map = map;
5006 map.on('zoomend', this._checkDisabledLayers, this);
5007
5008 for (var i = 0; i < this._layers.length; i++) {
5009 this._layers[i].layer.on('add remove', this._onLayerChange, this);
5010 }
5011
5012 return this._container;
5013 },
5014
5015 addTo: function (map) {
5016 Control.prototype.addTo.call(this, map);
5017 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
5018 return this._expandIfNotCollapsed();
5019 },
5020
5021 onRemove: function () {
5022 this._map.off('zoomend', this._checkDisabledLayers, this);
5023
5024 for (var i = 0; i < this._layers.length; i++) {
5025 this._layers[i].layer.off('add remove', this._onLayerChange, this);
5026 }
5027 },
5028
5029 // @method addBaseLayer(layer: Layer, name: String): this
5030 // Adds a base layer (radio button entry) with the given name to the control.
5031 addBaseLayer: function (layer, name) {
5032 this._addLayer(layer, name);
5033 return (this._map) ? this._update() : this;
5034 },
5035
5036 // @method addOverlay(layer: Layer, name: String): this
5037 // Adds an overlay (checkbox entry) with the given name to the control.
5038 addOverlay: function (layer, name) {
5039 this._addLayer(layer, name, true);
5040 return (this._map) ? this._update() : this;
5041 },
5042
5043 // @method removeLayer(layer: Layer): this
5044 // Remove the given layer from the control.
5045 removeLayer: function (layer) {
5046 layer.off('add remove', this._onLayerChange, this);
5047
5048 var obj = this._getLayer(stamp(layer));
5049 if (obj) {
5050 this._layers.splice(this._layers.indexOf(obj), 1);
5051 }
5052 return (this._map) ? this._update() : this;
5053 },
5054
5055 // @method expand(): this
5056 // Expand the control container if collapsed.
5057 expand: function () {
5058 addClass(this._container, 'leaflet-control-layers-expanded');
5059 this._section.style.height = null;
5060 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
5061 if (acceptableHeight < this._section.clientHeight) {
5062 addClass(this._section, 'leaflet-control-layers-scrollbar');
5063 this._section.style.height = acceptableHeight + 'px';
5064 } else {
5065 removeClass(this._section, 'leaflet-control-layers-scrollbar');
5066 }
5067 this._checkDisabledLayers();
5068 return this;
5069 },
5070
5071 // @method collapse(): this
5072 // Collapse the control container if expanded.
5073 collapse: function () {
5074 removeClass(this._container, 'leaflet-control-layers-expanded');
5075 return this;
5076 },
5077
5078 _initLayout: function () {
5079 var className = 'leaflet-control-layers',
5080 container = this._container = create$1('div', className),
5081 collapsed = this.options.collapsed;
5082
5083 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
5084 container.setAttribute('aria-haspopup', true);
5085
5086 disableClickPropagation(container);
5087 disableScrollPropagation(container);
5088
5089 var section = this._section = create$1('section', className + '-list');
5090
5091 if (collapsed) {
5092 this._map.on('click', this.collapse, this);
5093
5094 if (!android) {
5095 on(container, {
5096 mouseenter: this.expand,
5097 mouseleave: this.collapse
5098 }, this);
5099 }
5100 }
5101
5102 var link = this._layersLink = create$1('a', className + '-toggle', container);
5103 link.href = '#';
5104 link.title = 'Layers';
5105
5106 if (touch) {
5107 on(link, 'click', stop);
5108 on(link, 'click', this.expand, this);
5109 } else {
5110 on(link, 'focus', this.expand, this);
5111 }
5112
5113 if (!collapsed) {
5114 this.expand();
5115 }
5116
5117 this._baseLayersList = create$1('div', className + '-base', section);
5118 this._separator = create$1('div', className + '-separator', section);
5119 this._overlaysList = create$1('div', className + '-overlays', section);
5120
5121 container.appendChild(section);
5122 },
5123
5124 _getLayer: function (id) {
5125 for (var i = 0; i < this._layers.length; i++) {
5126
5127 if (this._layers[i] && stamp(this._layers[i].layer) === id) {
5128 return this._layers[i];
5129 }
5130 }
5131 },
5132
5133 _addLayer: function (layer, name, overlay) {
5134 if (this._map) {
5135 layer.on('add remove', this._onLayerChange, this);
5136 }
5137
5138 this._layers.push({
5139 layer: layer,
5140 name: name,
5141 overlay: overlay
5142 });
5143
5144 if (this.options.sortLayers) {
5145 this._layers.sort(bind(function (a, b) {
5146 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
5147 }, this));
5148 }
5149
5150 if (this.options.autoZIndex && layer.setZIndex) {
5151 this._lastZIndex++;
5152 layer.setZIndex(this._lastZIndex);
5153 }
5154
5155 this._expandIfNotCollapsed();
5156 },
5157
5158 _update: function () {
5159 if (!this._container) { return this; }
5160
5161 empty(this._baseLayersList);
5162 empty(this._overlaysList);
5163
5164 this._layerControlInputs = [];
5165 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
5166
5167 for (i = 0; i < this._layers.length; i++) {
5168 obj = this._layers[i];
5169 this._addItem(obj);
5170 overlaysPresent = overlaysPresent || obj.overlay;
5171 baseLayersPresent = baseLayersPresent || !obj.overlay;
5172 baseLayersCount += !obj.overlay ? 1 : 0;
5173 }
5174
5175 // Hide base layers section if there's only one layer.
5176 if (this.options.hideSingleBase) {
5177 baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
5178 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
5179 }
5180
5181 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
5182
5183 return this;
5184 },
5185
5186 _onLayerChange: function (e) {
5187 if (!this._handlingClick) {
5188 this._update();
5189 }
5190
5191 var obj = this._getLayer(stamp(e.target));
5192
5193 // @namespace Map
5194 // @section Layer events
5195 // @event baselayerchange: LayersControlEvent
5196 // Fired when the base layer is changed through the [layer control](#control-layers).
5197 // @event overlayadd: LayersControlEvent
5198 // Fired when an overlay is selected through the [layer control](#control-layers).
5199 // @event overlayremove: LayersControlEvent
5200 // Fired when an overlay is deselected through the [layer control](#control-layers).
5201 // @namespace Control.Layers
5202 var type = obj.overlay ?
5203 (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
5204 (e.type === 'add' ? 'baselayerchange' : null);
5205
5206 if (type) {
5207 this._map.fire(type, obj);
5208 }
5209 },
5210
5211 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
5212 _createRadioElement: function (name, checked) {
5213
5214 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
5215 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
5216
5217 var radioFragment = document.createElement('div');
5218 radioFragment.innerHTML = radioHtml;
5219
5220 return radioFragment.firstChild;
5221 },
5222
5223 _addItem: function (obj) {
5224 var label = document.createElement('label'),
5225 checked = this._map.hasLayer(obj.layer),
5226 input;
5227
5228 if (obj.overlay) {
5229 input = document.createElement('input');
5230 input.type = 'checkbox';
5231 input.className = 'leaflet-control-layers-selector';
5232 input.defaultChecked = checked;
5233 } else {
5234 input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked);
5235 }
5236
5237 this._layerControlInputs.push(input);
5238 input.layerId = stamp(obj.layer);
5239
5240 on(input, 'click', this._onInputClick, this);
5241
5242 var name = document.createElement('span');
5243 name.innerHTML = ' ' + obj.name;
5244
5245 // Helps from preventing layer control flicker when checkboxes are disabled
5246 // https://github.com/Leaflet/Leaflet/issues/2771
5247 var holder = document.createElement('div');
5248
5249 label.appendChild(holder);
5250 holder.appendChild(input);
5251 holder.appendChild(name);
5252
5253 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
5254 container.appendChild(label);
5255
5256 this._checkDisabledLayers();
5257 return label;
5258 },
5259
5260 _onInputClick: function () {
5261 var inputs = this._layerControlInputs,
5262 input, layer;
5263 var addedLayers = [],
5264 removedLayers = [];
5265
5266 this._handlingClick = true;
5267
5268 for (var i = inputs.length - 1; i >= 0; i--) {
5269 input = inputs[i];
5270 layer = this._getLayer(input.layerId).layer;
5271
5272 if (input.checked) {
5273 addedLayers.push(layer);
5274 } else if (!input.checked) {
5275 removedLayers.push(layer);
5276 }
5277 }
5278
5279 // Bugfix issue 2318: Should remove all old layers before readding new ones
5280 for (i = 0; i < removedLayers.length; i++) {
5281 if (this._map.hasLayer(removedLayers[i])) {
5282 this._map.removeLayer(removedLayers[i]);
5283 }
5284 }
5285 for (i = 0; i < addedLayers.length; i++) {
5286 if (!this._map.hasLayer(addedLayers[i])) {
5287 this._map.addLayer(addedLayers[i]);
5288 }
5289 }
5290
5291 this._handlingClick = false;
5292
5293 this._refocusOnMap();
5294 },
5295
5296 _checkDisabledLayers: function () {
5297 var inputs = this._layerControlInputs,
5298 input,
5299 layer,
5300 zoom = this._map.getZoom();
5301
5302 for (var i = inputs.length - 1; i >= 0; i--) {
5303 input = inputs[i];
5304 layer = this._getLayer(input.layerId).layer;
5305 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
5306 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
5307
5308 }
5309 },
5310
5311 _expandIfNotCollapsed: function () {
5312 if (this._map && !this.options.collapsed) {
5313 this.expand();
5314 }
5315 return this;
5316 },
5317
5318 _expand: function () {
5319 // Backward compatibility, remove me in 1.1.
5320 return this.expand();
5321 },
5322
5323 _collapse: function () {
5324 // Backward compatibility, remove me in 1.1.
5325 return this.collapse();
5326 }
5327
5328});
5329
5330
5331// @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
5332// 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.
5333var layers = function (baseLayers, overlays, options) {
5334 return new Layers(baseLayers, overlays, options);
5335};
5336
5337/*
5338 * @class Control.Zoom
5339 * @aka L.Control.Zoom
5340 * @inherits Control
5341 *
5342 * 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`.
5343 */
5344
5345var Zoom = Control.extend({
5346 // @section
5347 // @aka Control.Zoom options
5348 options: {
5349 position: 'topleft',
5350
5351 // @option zoomInText: String = '+'
5352 // The text set on the 'zoom in' button.
5353 zoomInText: '+',
5354
5355 // @option zoomInTitle: String = 'Zoom in'
5356 // The title set on the 'zoom in' button.
5357 zoomInTitle: 'Zoom in',
5358
5359 // @option zoomOutText: String = '&#x2212;'
5360 // The text set on the 'zoom out' button.
5361 zoomOutText: '&#x2212;',
5362
5363 // @option zoomOutTitle: String = 'Zoom out'
5364 // The title set on the 'zoom out' button.
5365 zoomOutTitle: 'Zoom out'
5366 },
5367
5368 onAdd: function (map) {
5369 var zoomName = 'leaflet-control-zoom',
5370 container = create$1('div', zoomName + ' leaflet-bar'),
5371 options = this.options;
5372
5373 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle,
5374 zoomName + '-in', container, this._zoomIn);
5375 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
5376 zoomName + '-out', container, this._zoomOut);
5377
5378 this._updateDisabled();
5379 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
5380
5381 return container;
5382 },
5383
5384 onRemove: function (map) {
5385 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
5386 },
5387
5388 disable: function () {
5389 this._disabled = true;
5390 this._updateDisabled();
5391 return this;
5392 },
5393
5394 enable: function () {
5395 this._disabled = false;
5396 this._updateDisabled();
5397 return this;
5398 },
5399
5400 _zoomIn: function (e) {
5401 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
5402 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5403 }
5404 },
5405
5406 _zoomOut: function (e) {
5407 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
5408 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5409 }
5410 },
5411
5412 _createButton: function (html, title, className, container, fn) {
5413 var link = create$1('a', className, container);
5414 link.innerHTML = html;
5415 link.href = '#';
5416 link.title = title;
5417
5418 /*
5419 * Will force screen readers like VoiceOver to read this as "Zoom in - button"
5420 */
5421 link.setAttribute('role', 'button');
5422 link.setAttribute('aria-label', title);
5423
5424 disableClickPropagation(link);
5425 on(link, 'click', stop);
5426 on(link, 'click', fn, this);
5427 on(link, 'click', this._refocusOnMap, this);
5428
5429 return link;
5430 },
5431
5432 _updateDisabled: function () {
5433 var map = this._map,
5434 className = 'leaflet-disabled';
5435
5436 removeClass(this._zoomInButton, className);
5437 removeClass(this._zoomOutButton, className);
5438
5439 if (this._disabled || map._zoom === map.getMinZoom()) {
5440 addClass(this._zoomOutButton, className);
5441 }
5442 if (this._disabled || map._zoom === map.getMaxZoom()) {
5443 addClass(this._zoomInButton, className);
5444 }
5445 }
5446});
5447
5448// @namespace Map
5449// @section Control options
5450// @option zoomControl: Boolean = true
5451// Whether a [zoom control](#control-zoom) is added to the map by default.
5452Map.mergeOptions({
5453 zoomControl: true
5454});
5455
5456Map.addInitHook(function () {
5457 if (this.options.zoomControl) {
5458 // @section Controls
5459 // @property zoomControl: Control.Zoom
5460 // The default zoom control (only available if the
5461 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
5462 this.zoomControl = new Zoom();
5463 this.addControl(this.zoomControl);
5464 }
5465});
5466
5467// @namespace Control.Zoom
5468// @factory L.control.zoom(options: Control.Zoom options)
5469// Creates a zoom control
5470var zoom = function (options) {
5471 return new Zoom(options);
5472};
5473
5474/*
5475 * @class Control.Scale
5476 * @aka L.Control.Scale
5477 * @inherits Control
5478 *
5479 * 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`.
5480 *
5481 * @example
5482 *
5483 * ```js
5484 * L.control.scale().addTo(map);
5485 * ```
5486 */
5487
5488var Scale = Control.extend({
5489 // @section
5490 // @aka Control.Scale options
5491 options: {
5492 position: 'bottomleft',
5493
5494 // @option maxWidth: Number = 100
5495 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
5496 maxWidth: 100,
5497
5498 // @option metric: Boolean = True
5499 // Whether to show the metric scale line (m/km).
5500 metric: true,
5501
5502 // @option imperial: Boolean = True
5503 // Whether to show the imperial scale line (mi/ft).
5504 imperial: true
5505
5506 // @option updateWhenIdle: Boolean = false
5507 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
5508 },
5509
5510 onAdd: function (map) {
5511 var className = 'leaflet-control-scale',
5512 container = create$1('div', className),
5513 options = this.options;
5514
5515 this._addScales(options, className + '-line', container);
5516
5517 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5518 map.whenReady(this._update, this);
5519
5520 return container;
5521 },
5522
5523 onRemove: function (map) {
5524 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5525 },
5526
5527 _addScales: function (options, className, container) {
5528 if (options.metric) {
5529 this._mScale = create$1('div', className, container);
5530 }
5531 if (options.imperial) {
5532 this._iScale = create$1('div', className, container);
5533 }
5534 },
5535
5536 _update: function () {
5537 var map = this._map,
5538 y = map.getSize().y / 2;
5539
5540 var maxMeters = map.distance(
5541 map.containerPointToLatLng([0, y]),
5542 map.containerPointToLatLng([this.options.maxWidth, y]));
5543
5544 this._updateScales(maxMeters);
5545 },
5546
5547 _updateScales: function (maxMeters) {
5548 if (this.options.metric && maxMeters) {
5549 this._updateMetric(maxMeters);
5550 }
5551 if (this.options.imperial && maxMeters) {
5552 this._updateImperial(maxMeters);
5553 }
5554 },
5555
5556 _updateMetric: function (maxMeters) {
5557 var meters = this._getRoundNum(maxMeters),
5558 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
5559
5560 this._updateScale(this._mScale, label, meters / maxMeters);
5561 },
5562
5563 _updateImperial: function (maxMeters) {
5564 var maxFeet = maxMeters * 3.2808399,
5565 maxMiles, miles, feet;
5566
5567 if (maxFeet > 5280) {
5568 maxMiles = maxFeet / 5280;
5569 miles = this._getRoundNum(maxMiles);
5570 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
5571
5572 } else {
5573 feet = this._getRoundNum(maxFeet);
5574 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
5575 }
5576 },
5577
5578 _updateScale: function (scale, text, ratio) {
5579 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
5580 scale.innerHTML = text;
5581 },
5582
5583 _getRoundNum: function (num) {
5584 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5585 d = num / pow10;
5586
5587 d = d >= 10 ? 10 :
5588 d >= 5 ? 5 :
5589 d >= 3 ? 3 :
5590 d >= 2 ? 2 : 1;
5591
5592 return pow10 * d;
5593 }
5594});
5595
5596
5597// @factory L.control.scale(options?: Control.Scale options)
5598// Creates an scale control with the given options.
5599var scale = function (options) {
5600 return new Scale(options);
5601};
5602
5603/*
5604 * @class Control.Attribution
5605 * @aka L.Control.Attribution
5606 * @inherits Control
5607 *
5608 * 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.
5609 */
5610
5611var Attribution = Control.extend({
5612 // @section
5613 // @aka Control.Attribution options
5614 options: {
5615 position: 'bottomright',
5616
5617 // @option prefix: String = 'Leaflet'
5618 // The HTML text shown before the attributions. Pass `false` to disable.
5619 prefix: '<a href="https://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
5620 },
5621
5622 initialize: function (options) {
5623 setOptions(this, options);
5624
5625 this._attributions = {};
5626 },
5627
5628 onAdd: function (map) {
5629 map.attributionControl = this;
5630 this._container = create$1('div', 'leaflet-control-attribution');
5631 disableClickPropagation(this._container);
5632
5633 // TODO ugly, refactor
5634 for (var i in map._layers) {
5635 if (map._layers[i].getAttribution) {
5636 this.addAttribution(map._layers[i].getAttribution());
5637 }
5638 }
5639
5640 this._update();
5641
5642 return this._container;
5643 },
5644
5645 // @method setPrefix(prefix: String): this
5646 // Sets the text before the attributions.
5647 setPrefix: function (prefix) {
5648 this.options.prefix = prefix;
5649 this._update();
5650 return this;
5651 },
5652
5653 // @method addAttribution(text: String): this
5654 // Adds an attribution text (e.g. `'Vector data &copy; Mapbox'`).
5655 addAttribution: function (text) {
5656 if (!text) { return this; }
5657
5658 if (!this._attributions[text]) {
5659 this._attributions[text] = 0;
5660 }
5661 this._attributions[text]++;
5662
5663 this._update();
5664
5665 return this;
5666 },
5667
5668 // @method removeAttribution(text: String): this
5669 // Removes an attribution text.
5670 removeAttribution: function (text) {
5671 if (!text) { return this; }
5672
5673 if (this._attributions[text]) {
5674 this._attributions[text]--;
5675 this._update();
5676 }
5677
5678 return this;
5679 },
5680
5681 _update: function () {
5682 if (!this._map) { return; }
5683
5684 var attribs = [];
5685
5686 for (var i in this._attributions) {
5687 if (this._attributions[i]) {
5688 attribs.push(i);
5689 }
5690 }
5691
5692 var prefixAndAttribs = [];
5693
5694 if (this.options.prefix) {
5695 prefixAndAttribs.push(this.options.prefix);
5696 }
5697 if (attribs.length) {
5698 prefixAndAttribs.push(attribs.join(', '));
5699 }
5700
5701 this._container.innerHTML = prefixAndAttribs.join(' | ');
5702 }
5703});
5704
5705// @namespace Map
5706// @section Control options
5707// @option attributionControl: Boolean = true
5708// Whether a [attribution control](#control-attribution) is added to the map by default.
5709Map.mergeOptions({
5710 attributionControl: true
5711});
5712
5713Map.addInitHook(function () {
5714 if (this.options.attributionControl) {
5715 new Attribution().addTo(this);
5716 }
5717});
5718
5719// @namespace Control.Attribution
5720// @factory L.control.attribution(options: Control.Attribution options)
5721// Creates an attribution control.
5722var attribution = function (options) {
5723 return new Attribution(options);
5724};
5725
5726Control.Layers = Layers;
5727Control.Zoom = Zoom;
5728Control.Scale = Scale;
5729Control.Attribution = Attribution;
5730
5731control.layers = layers;
5732control.zoom = zoom;
5733control.scale = scale;
5734control.attribution = attribution;
5735
5736/*
5737 L.Handler is a base class for handler classes that are used internally to inject
5738 interaction features like dragging to classes like Map and Marker.
5739*/
5740
5741// @class Handler
5742// @aka L.Handler
5743// Abstract class for map interaction handlers
5744
5745var Handler = Class.extend({
5746 initialize: function (map) {
5747 this._map = map;
5748 },
5749
5750 // @method enable(): this
5751 // Enables the handler
5752 enable: function () {
5753 if (this._enabled) { return this; }
5754
5755 this._enabled = true;
5756 this.addHooks();
5757 return this;
5758 },
5759
5760 // @method disable(): this
5761 // Disables the handler
5762 disable: function () {
5763 if (!this._enabled) { return this; }
5764
5765 this._enabled = false;
5766 this.removeHooks();
5767 return this;
5768 },
5769
5770 // @method enabled(): Boolean
5771 // Returns `true` if the handler is enabled
5772 enabled: function () {
5773 return !!this._enabled;
5774 }
5775
5776 // @section Extension methods
5777 // Classes inheriting from `Handler` must implement the two following methods:
5778 // @method addHooks()
5779 // Called when the handler is enabled, should add event hooks.
5780 // @method removeHooks()
5781 // Called when the handler is disabled, should remove the event hooks added previously.
5782});
5783
5784// @section There is static function which can be called without instantiating L.Handler:
5785// @function addTo(map: Map, name: String): this
5786// Adds a new Handler to the given map with the given name.
5787Handler.addTo = function (map, name) {
5788 map.addHandler(name, this);
5789 return this;
5790};
5791
5792var Mixin = {Events: Events};
5793
5794/*
5795 * @class Draggable
5796 * @aka L.Draggable
5797 * @inherits Evented
5798 *
5799 * A class for making DOM elements draggable (including touch support).
5800 * Used internally for map and marker dragging. Only works for elements
5801 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
5802 *
5803 * @example
5804 * ```js
5805 * var draggable = new L.Draggable(elementToDrag);
5806 * draggable.enable();
5807 * ```
5808 */
5809
5810var START = touch ? 'touchstart mousedown' : 'mousedown';
5811var END = {
5812 mousedown: 'mouseup',
5813 touchstart: 'touchend',
5814 pointerdown: 'touchend',
5815 MSPointerDown: 'touchend'
5816};
5817var MOVE = {
5818 mousedown: 'mousemove',
5819 touchstart: 'touchmove',
5820 pointerdown: 'touchmove',
5821 MSPointerDown: 'touchmove'
5822};
5823
5824
5825var Draggable = Evented.extend({
5826
5827 options: {
5828 // @section
5829 // @aka Draggable options
5830 // @option clickTolerance: Number = 3
5831 // The max number of pixels a user can shift the mouse pointer during a click
5832 // for it to be considered a valid click (as opposed to a mouse drag).
5833 clickTolerance: 3
5834 },
5835
5836 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
5837 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
5838 initialize: function (element, dragStartTarget, preventOutline$$1, options) {
5839 setOptions(this, options);
5840
5841 this._element = element;
5842 this._dragStartTarget = dragStartTarget || element;
5843 this._preventOutline = preventOutline$$1;
5844 },
5845
5846 // @method enable()
5847 // Enables the dragging ability
5848 enable: function () {
5849 if (this._enabled) { return; }
5850
5851 on(this._dragStartTarget, START, this._onDown, this);
5852
5853 this._enabled = true;
5854 },
5855
5856 // @method disable()
5857 // Disables the dragging ability
5858 disable: function () {
5859 if (!this._enabled) { return; }
5860
5861 // If we're currently dragging this draggable,
5862 // disabling it counts as first ending the drag.
5863 if (Draggable._dragging === this) {
5864 this.finishDrag();
5865 }
5866
5867 off(this._dragStartTarget, START, this._onDown, this);
5868
5869 this._enabled = false;
5870 this._moved = false;
5871 },
5872
5873 _onDown: function (e) {
5874 // Ignore simulated events, since we handle both touch and
5875 // mouse explicitly; otherwise we risk getting duplicates of
5876 // touch events, see #4315.
5877 // Also ignore the event if disabled; this happens in IE11
5878 // under some circumstances, see #3666.
5879 if (e._simulated || !this._enabled) { return; }
5880
5881 this._moved = false;
5882
5883 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
5884
5885 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
5886 Draggable._dragging = this; // Prevent dragging multiple objects at once.
5887
5888 if (this._preventOutline) {
5889 preventOutline(this._element);
5890 }
5891
5892 disableImageDrag();
5893 disableTextSelection();
5894
5895 if (this._moving) { return; }
5896
5897 // @event down: Event
5898 // Fired when a drag is about to start.
5899 this.fire('down');
5900
5901 var first = e.touches ? e.touches[0] : e,
5902 sizedParent = getSizedParentNode(this._element);
5903
5904 this._startPoint = new Point(first.clientX, first.clientY);
5905
5906 // Cache the scale, so that we can continuously compensate for it during drag (_onMove).
5907 this._parentScale = getScale(sizedParent);
5908
5909 on(document, MOVE[e.type], this._onMove, this);
5910 on(document, END[e.type], this._onUp, this);
5911 },
5912
5913 _onMove: function (e) {
5914 // Ignore simulated events, since we handle both touch and
5915 // mouse explicitly; otherwise we risk getting duplicates of
5916 // touch events, see #4315.
5917 // Also ignore the event if disabled; this happens in IE11
5918 // under some circumstances, see #3666.
5919 if (e._simulated || !this._enabled) { return; }
5920
5921 if (e.touches && e.touches.length > 1) {
5922 this._moved = true;
5923 return;
5924 }
5925
5926 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
5927 offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
5928
5929 if (!offset.x && !offset.y) { return; }
5930 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
5931
5932 // We assume that the parent container's position, border and scale do not change for the duration of the drag.
5933 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
5934 // and we can use the cached value for the scale.
5935 offset.x /= this._parentScale.x;
5936 offset.y /= this._parentScale.y;
5937
5938 preventDefault(e);
5939
5940 if (!this._moved) {
5941 // @event dragstart: Event
5942 // Fired when a drag starts
5943 this.fire('dragstart');
5944
5945 this._moved = true;
5946 this._startPos = getPosition(this._element).subtract(offset);
5947
5948 addClass(document.body, 'leaflet-dragging');
5949
5950 this._lastTarget = e.target || e.srcElement;
5951 // IE and Edge do not give the <use> element, so fetch it
5952 // if necessary
5953 if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
5954 this._lastTarget = this._lastTarget.correspondingUseElement;
5955 }
5956 addClass(this._lastTarget, 'leaflet-drag-target');
5957 }
5958
5959 this._newPos = this._startPos.add(offset);
5960 this._moving = true;
5961
5962 cancelAnimFrame(this._animRequest);
5963 this._lastEvent = e;
5964 this._animRequest = requestAnimFrame(this._updatePosition, this, true);
5965 },
5966
5967 _updatePosition: function () {
5968 var e = {originalEvent: this._lastEvent};
5969
5970 // @event predrag: Event
5971 // Fired continuously during dragging *before* each corresponding
5972 // update of the element's position.
5973 this.fire('predrag', e);
5974 setPosition(this._element, this._newPos);
5975
5976 // @event drag: Event
5977 // Fired continuously during dragging.
5978 this.fire('drag', e);
5979 },
5980
5981 _onUp: function (e) {
5982 // Ignore simulated events, since we handle both touch and
5983 // mouse explicitly; otherwise we risk getting duplicates of
5984 // touch events, see #4315.
5985 // Also ignore the event if disabled; this happens in IE11
5986 // under some circumstances, see #3666.
5987 if (e._simulated || !this._enabled) { return; }
5988 this.finishDrag();
5989 },
5990
5991 finishDrag: function () {
5992 removeClass(document.body, 'leaflet-dragging');
5993
5994 if (this._lastTarget) {
5995 removeClass(this._lastTarget, 'leaflet-drag-target');
5996 this._lastTarget = null;
5997 }
5998
5999 for (var i in MOVE) {
6000 off(document, MOVE[i], this._onMove, this);
6001 off(document, END[i], this._onUp, this);
6002 }
6003
6004 enableImageDrag();
6005 enableTextSelection();
6006
6007 if (this._moved && this._moving) {
6008 // ensure drag is not fired after dragend
6009 cancelAnimFrame(this._animRequest);
6010
6011 // @event dragend: DragEndEvent
6012 // Fired when the drag ends.
6013 this.fire('dragend', {
6014 distance: this._newPos.distanceTo(this._startPos)
6015 });
6016 }
6017
6018 this._moving = false;
6019 Draggable._dragging = false;
6020 }
6021
6022});
6023
6024/*
6025 * @namespace LineUtil
6026 *
6027 * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
6028 */
6029
6030// Simplify polyline with vertex reduction and Douglas-Peucker simplification.
6031// Improves rendering performance dramatically by lessening the number of points to draw.
6032
6033// @function simplify(points: Point[], tolerance: Number): Point[]
6034// Dramatically reduces the number of points in a polyline while retaining
6035// its shape and returns a new array of simplified points, using the
6036// [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
6037// Used for a huge performance boost when processing/displaying Leaflet polylines for
6038// each zoom level and also reducing visual noise. tolerance affects the amount of
6039// simplification (lesser value means higher quality but slower and with more points).
6040// Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
6041function simplify(points, tolerance) {
6042 if (!tolerance || !points.length) {
6043 return points.slice();
6044 }
6045
6046 var sqTolerance = tolerance * tolerance;
6047
6048 // stage 1: vertex reduction
6049 points = _reducePoints(points, sqTolerance);
6050
6051 // stage 2: Douglas-Peucker simplification
6052 points = _simplifyDP(points, sqTolerance);
6053
6054 return points;
6055}
6056
6057// @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
6058// Returns the distance between point `p` and segment `p1` to `p2`.
6059function pointToSegmentDistance(p, p1, p2) {
6060 return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
6061}
6062
6063// @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
6064// Returns the closest point from a point `p` on a segment `p1` to `p2`.
6065function closestPointOnSegment(p, p1, p2) {
6066 return _sqClosestPointOnSegment(p, p1, p2);
6067}
6068
6069// Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
6070function _simplifyDP(points, sqTolerance) {
6071
6072 var len = points.length,
6073 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
6074 markers = new ArrayConstructor(len);
6075
6076 markers[0] = markers[len - 1] = 1;
6077
6078 _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
6079
6080 var i,
6081 newPoints = [];
6082
6083 for (i = 0; i < len; i++) {
6084 if (markers[i]) {
6085 newPoints.push(points[i]);
6086 }
6087 }
6088
6089 return newPoints;
6090}
6091
6092function _simplifyDPStep(points, markers, sqTolerance, first, last) {
6093
6094 var maxSqDist = 0,
6095 index, i, sqDist;
6096
6097 for (i = first + 1; i <= last - 1; i++) {
6098 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
6099
6100 if (sqDist > maxSqDist) {
6101 index = i;
6102 maxSqDist = sqDist;
6103 }
6104 }
6105
6106 if (maxSqDist > sqTolerance) {
6107 markers[index] = 1;
6108
6109 _simplifyDPStep(points, markers, sqTolerance, first, index);
6110 _simplifyDPStep(points, markers, sqTolerance, index, last);
6111 }
6112}
6113
6114// reduce points that are too close to each other to a single point
6115function _reducePoints(points, sqTolerance) {
6116 var reducedPoints = [points[0]];
6117
6118 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
6119 if (_sqDist(points[i], points[prev]) > sqTolerance) {
6120 reducedPoints.push(points[i]);
6121 prev = i;
6122 }
6123 }
6124 if (prev < len - 1) {
6125 reducedPoints.push(points[len - 1]);
6126 }
6127 return reducedPoints;
6128}
6129
6130var _lastCode;
6131
6132// @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
6133// Clips the segment a to b by rectangular bounds with the
6134// [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
6135// (modifying the segment points directly!). Used by Leaflet to only show polyline
6136// points that are on the screen or near, increasing performance.
6137function clipSegment(a, b, bounds, useLastCode, round) {
6138 var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
6139 codeB = _getBitCode(b, bounds),
6140
6141 codeOut, p, newCode;
6142
6143 // save 2nd code to avoid calculating it on the next segment
6144 _lastCode = codeB;
6145
6146 while (true) {
6147 // if a,b is inside the clip window (trivial accept)
6148 if (!(codeA | codeB)) {
6149 return [a, b];
6150 }
6151
6152 // if a,b is outside the clip window (trivial reject)
6153 if (codeA & codeB) {
6154 return false;
6155 }
6156
6157 // other cases
6158 codeOut = codeA || codeB;
6159 p = _getEdgeIntersection(a, b, codeOut, bounds, round);
6160 newCode = _getBitCode(p, bounds);
6161
6162 if (codeOut === codeA) {
6163 a = p;
6164 codeA = newCode;
6165 } else {
6166 b = p;
6167 codeB = newCode;
6168 }
6169 }
6170}
6171
6172function _getEdgeIntersection(a, b, code, bounds, round) {
6173 var dx = b.x - a.x,
6174 dy = b.y - a.y,
6175 min = bounds.min,
6176 max = bounds.max,
6177 x, y;
6178
6179 if (code & 8) { // top
6180 x = a.x + dx * (max.y - a.y) / dy;
6181 y = max.y;
6182
6183 } else if (code & 4) { // bottom
6184 x = a.x + dx * (min.y - a.y) / dy;
6185 y = min.y;
6186
6187 } else if (code & 2) { // right
6188 x = max.x;
6189 y = a.y + dy * (max.x - a.x) / dx;
6190
6191 } else if (code & 1) { // left
6192 x = min.x;
6193 y = a.y + dy * (min.x - a.x) / dx;
6194 }
6195
6196 return new Point(x, y, round);
6197}
6198
6199function _getBitCode(p, bounds) {
6200 var code = 0;
6201
6202 if (p.x < bounds.min.x) { // left
6203 code |= 1;
6204 } else if (p.x > bounds.max.x) { // right
6205 code |= 2;
6206 }
6207
6208 if (p.y < bounds.min.y) { // bottom
6209 code |= 4;
6210 } else if (p.y > bounds.max.y) { // top
6211 code |= 8;
6212 }
6213
6214 return code;
6215}
6216
6217// square distance (to avoid unnecessary Math.sqrt calls)
6218function _sqDist(p1, p2) {
6219 var dx = p2.x - p1.x,
6220 dy = p2.y - p1.y;
6221 return dx * dx + dy * dy;
6222}
6223
6224// return closest point on segment or distance to that point
6225function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
6226 var x = p1.x,
6227 y = p1.y,
6228 dx = p2.x - x,
6229 dy = p2.y - y,
6230 dot = dx * dx + dy * dy,
6231 t;
6232
6233 if (dot > 0) {
6234 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
6235
6236 if (t > 1) {
6237 x = p2.x;
6238 y = p2.y;
6239 } else if (t > 0) {
6240 x += dx * t;
6241 y += dy * t;
6242 }
6243 }
6244
6245 dx = p.x - x;
6246 dy = p.y - y;
6247
6248 return sqDist ? dx * dx + dy * dy : new Point(x, y);
6249}
6250
6251
6252// @function isFlat(latlngs: LatLng[]): Boolean
6253// Returns true if `latlngs` is a flat array, false is nested.
6254function isFlat(latlngs) {
6255 return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
6256}
6257
6258function _flat(latlngs) {
6259 console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
6260 return isFlat(latlngs);
6261}
6262
6263
6264var LineUtil = (Object.freeze || Object)({
6265 simplify: simplify,
6266 pointToSegmentDistance: pointToSegmentDistance,
6267 closestPointOnSegment: closestPointOnSegment,
6268 clipSegment: clipSegment,
6269 _getEdgeIntersection: _getEdgeIntersection,
6270 _getBitCode: _getBitCode,
6271 _sqClosestPointOnSegment: _sqClosestPointOnSegment,
6272 isFlat: isFlat,
6273 _flat: _flat
6274});
6275
6276/*
6277 * @namespace PolyUtil
6278 * Various utility functions for polygon geometries.
6279 */
6280
6281/* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
6282 * 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)).
6283 * Used by Leaflet to only show polygon points that are on the screen or near, increasing
6284 * performance. Note that polygon points needs different algorithm for clipping
6285 * than polyline, so there's a separate method for it.
6286 */
6287function clipPolygon(points, bounds, round) {
6288 var clippedPoints,
6289 edges = [1, 4, 2, 8],
6290 i, j, k,
6291 a, b,
6292 len, edge, p;
6293
6294 for (i = 0, len = points.length; i < len; i++) {
6295 points[i]._code = _getBitCode(points[i], bounds);
6296 }
6297
6298 // for each edge (left, bottom, right, top)
6299 for (k = 0; k < 4; k++) {
6300 edge = edges[k];
6301 clippedPoints = [];
6302
6303 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
6304 a = points[i];
6305 b = points[j];
6306
6307 // if a is inside the clip window
6308 if (!(a._code & edge)) {
6309 // if b is outside the clip window (a->b goes out of screen)
6310 if (b._code & edge) {
6311 p = _getEdgeIntersection(b, a, edge, bounds, round);
6312 p._code = _getBitCode(p, bounds);
6313 clippedPoints.push(p);
6314 }
6315 clippedPoints.push(a);
6316
6317 // else if b is inside the clip window (a->b enters the screen)
6318 } else if (!(b._code & edge)) {
6319 p = _getEdgeIntersection(b, a, edge, bounds, round);
6320 p._code = _getBitCode(p, bounds);
6321 clippedPoints.push(p);
6322 }
6323 }
6324 points = clippedPoints;
6325 }
6326
6327 return points;
6328}
6329
6330
6331var PolyUtil = (Object.freeze || Object)({
6332 clipPolygon: clipPolygon
6333});
6334
6335/*
6336 * @namespace Projection
6337 * @section
6338 * Leaflet comes with a set of already defined Projections out of the box:
6339 *
6340 * @projection L.Projection.LonLat
6341 *
6342 * Equirectangular, or Plate Carree projection — the most simple projection,
6343 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
6344 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
6345 * `EPSG:4326` and `Simple` CRS.
6346 */
6347
6348var LonLat = {
6349 project: function (latlng) {
6350 return new Point(latlng.lng, latlng.lat);
6351 },
6352
6353 unproject: function (point) {
6354 return new LatLng(point.y, point.x);
6355 },
6356
6357 bounds: new Bounds([-180, -90], [180, 90])
6358};
6359
6360/*
6361 * @namespace Projection
6362 * @projection L.Projection.Mercator
6363 *
6364 * Elliptical Mercator projection — more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS.
6365 */
6366
6367var Mercator = {
6368 R: 6378137,
6369 R_MINOR: 6356752.314245179,
6370
6371 bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
6372
6373 project: function (latlng) {
6374 var d = Math.PI / 180,
6375 r = this.R,
6376 y = latlng.lat * d,
6377 tmp = this.R_MINOR / r,
6378 e = Math.sqrt(1 - tmp * tmp),
6379 con = e * Math.sin(y);
6380
6381 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
6382 y = -r * Math.log(Math.max(ts, 1E-10));
6383
6384 return new Point(latlng.lng * d * r, y);
6385 },
6386
6387 unproject: function (point) {
6388 var d = 180 / Math.PI,
6389 r = this.R,
6390 tmp = this.R_MINOR / r,
6391 e = Math.sqrt(1 - tmp * tmp),
6392 ts = Math.exp(-point.y / r),
6393 phi = Math.PI / 2 - 2 * Math.atan(ts);
6394
6395 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
6396 con = e * Math.sin(phi);
6397 con = Math.pow((1 - con) / (1 + con), e / 2);
6398 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
6399 phi += dphi;
6400 }
6401
6402 return new LatLng(phi * d, point.x * d / r);
6403 }
6404};
6405
6406/*
6407 * @class Projection
6408
6409 * An object with methods for projecting geographical coordinates of the world onto
6410 * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection).
6411
6412 * @property bounds: Bounds
6413 * The bounds (specified in CRS units) where the projection is valid
6414
6415 * @method project(latlng: LatLng): Point
6416 * Projects geographical coordinates into a 2D point.
6417 * Only accepts actual `L.LatLng` instances, not arrays.
6418
6419 * @method unproject(point: Point): LatLng
6420 * The inverse of `project`. Projects a 2D point into a geographical location.
6421 * Only accepts actual `L.Point` instances, not arrays.
6422
6423 * Note that the projection instances do not inherit from Leafet's `Class` object,
6424 * and can't be instantiated. Also, new classes can't inherit from them,
6425 * and methods can't be added to them with the `include` function.
6426
6427 */
6428
6429
6430
6431
6432var index = (Object.freeze || Object)({
6433 LonLat: LonLat,
6434 Mercator: Mercator,
6435 SphericalMercator: SphericalMercator
6436});
6437
6438/*
6439 * @namespace CRS
6440 * @crs L.CRS.EPSG3395
6441 *
6442 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
6443 */
6444var EPSG3395 = extend({}, Earth, {
6445 code: 'EPSG:3395',
6446 projection: Mercator,
6447
6448 transformation: (function () {
6449 var scale = 0.5 / (Math.PI * Mercator.R);
6450 return toTransformation(scale, 0.5, -scale, 0.5);
6451 }())
6452});
6453
6454/*
6455 * @namespace CRS
6456 * @crs L.CRS.EPSG4326
6457 *
6458 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
6459 *
6460 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
6461 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer`
6462 * with this CRS, ensure that there are two 256x256 pixel tiles covering the
6463 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
6464 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
6465 */
6466
6467var EPSG4326 = extend({}, Earth, {
6468 code: 'EPSG:4326',
6469 projection: LonLat,
6470 transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
6471});
6472
6473/*
6474 * @namespace CRS
6475 * @crs L.CRS.Simple
6476 *
6477 * A simple CRS that maps longitude and latitude into `x` and `y` directly.
6478 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
6479 * axis should still be inverted (going from bottom to top). `distance()` returns
6480 * simple euclidean distance.
6481 */
6482
6483var Simple = extend({}, CRS, {
6484 projection: LonLat,
6485 transformation: toTransformation(1, 0, -1, 0),
6486
6487 scale: function (zoom) {
6488 return Math.pow(2, zoom);
6489 },
6490
6491 zoom: function (scale) {
6492 return Math.log(scale) / Math.LN2;
6493 },
6494
6495 distance: function (latlng1, latlng2) {
6496 var dx = latlng2.lng - latlng1.lng,
6497 dy = latlng2.lat - latlng1.lat;
6498
6499 return Math.sqrt(dx * dx + dy * dy);
6500 },
6501
6502 infinite: true
6503});
6504
6505CRS.Earth = Earth;
6506CRS.EPSG3395 = EPSG3395;
6507CRS.EPSG3857 = EPSG3857;
6508CRS.EPSG900913 = EPSG900913;
6509CRS.EPSG4326 = EPSG4326;
6510CRS.Simple = Simple;
6511
6512/*
6513 * @class Layer
6514 * @inherits Evented
6515 * @aka L.Layer
6516 * @aka ILayer
6517 *
6518 * A set of methods from the Layer base class that all Leaflet layers use.
6519 * Inherits all methods, options and events from `L.Evented`.
6520 *
6521 * @example
6522 *
6523 * ```js
6524 * var layer = L.marker(latlng).addTo(map);
6525 * layer.addTo(map);
6526 * layer.remove();
6527 * ```
6528 *
6529 * @event add: Event
6530 * Fired after the layer is added to a map
6531 *
6532 * @event remove: Event
6533 * Fired after the layer is removed from a map
6534 */
6535
6536
6537var Layer = Evented.extend({
6538
6539 // Classes extending `L.Layer` will inherit the following options:
6540 options: {
6541 // @option pane: String = 'overlayPane'
6542 // 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.
6543 pane: 'overlayPane',
6544
6545 // @option attribution: String = null
6546 // 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.
6547 attribution: null,
6548
6549 bubblingMouseEvents: true
6550 },
6551
6552 /* @section
6553 * Classes extending `L.Layer` will inherit the following methods:
6554 *
6555 * @method addTo(map: Map|LayerGroup): this
6556 * Adds the layer to the given map or layer group.
6557 */
6558 addTo: function (map) {
6559 map.addLayer(this);
6560 return this;
6561 },
6562
6563 // @method remove: this
6564 // Removes the layer from the map it is currently active on.
6565 remove: function () {
6566 return this.removeFrom(this._map || this._mapToAdd);
6567 },
6568
6569 // @method removeFrom(map: Map): this
6570 // Removes the layer from the given map
6571 removeFrom: function (obj) {
6572 if (obj) {
6573 obj.removeLayer(this);
6574 }
6575 return this;
6576 },
6577
6578 // @method getPane(name? : String): HTMLElement
6579 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
6580 getPane: function (name) {
6581 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
6582 },
6583
6584 addInteractiveTarget: function (targetEl) {
6585 this._map._targets[stamp(targetEl)] = this;
6586 return this;
6587 },
6588
6589 removeInteractiveTarget: function (targetEl) {
6590 delete this._map._targets[stamp(targetEl)];
6591 return this;
6592 },
6593
6594 // @method getAttribution: String
6595 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
6596 getAttribution: function () {
6597 return this.options.attribution;
6598 },
6599
6600 _layerAdd: function (e) {
6601 var map = e.target;
6602
6603 // check in case layer gets added and then removed before the map is ready
6604 if (!map.hasLayer(this)) { return; }
6605
6606 this._map = map;
6607 this._zoomAnimated = map._zoomAnimated;
6608
6609 if (this.getEvents) {
6610 var events = this.getEvents();
6611 map.on(events, this);
6612 this.once('remove', function () {
6613 map.off(events, this);
6614 }, this);
6615 }
6616
6617 this.onAdd(map);
6618
6619 if (this.getAttribution && map.attributionControl) {
6620 map.attributionControl.addAttribution(this.getAttribution());
6621 }
6622
6623 this.fire('add');
6624 map.fire('layeradd', {layer: this});
6625 }
6626});
6627
6628/* @section Extension methods
6629 * @uninheritable
6630 *
6631 * Every layer should extend from `L.Layer` and (re-)implement the following methods.
6632 *
6633 * @method onAdd(map: Map): this
6634 * 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).
6635 *
6636 * @method onRemove(map: Map): this
6637 * 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).
6638 *
6639 * @method getEvents(): Object
6640 * 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.
6641 *
6642 * @method getAttribution(): String
6643 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
6644 *
6645 * @method beforeAdd(map: Map): this
6646 * 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.
6647 */
6648
6649
6650/* @namespace Map
6651 * @section Layer events
6652 *
6653 * @event layeradd: LayerEvent
6654 * Fired when a new layer is added to the map.
6655 *
6656 * @event layerremove: LayerEvent
6657 * Fired when some layer is removed from the map
6658 *
6659 * @section Methods for Layers and Controls
6660 */
6661Map.include({
6662 // @method addLayer(layer: Layer): this
6663 // Adds the given layer to the map
6664 addLayer: function (layer) {
6665 if (!layer._layerAdd) {
6666 throw new Error('The provided object is not a Layer.');
6667 }
6668
6669 var id = stamp(layer);
6670 if (this._layers[id]) { return this; }
6671 this._layers[id] = layer;
6672
6673 layer._mapToAdd = this;
6674
6675 if (layer.beforeAdd) {
6676 layer.beforeAdd(this);
6677 }
6678
6679 this.whenReady(layer._layerAdd, layer);
6680
6681 return this;
6682 },
6683
6684 // @method removeLayer(layer: Layer): this
6685 // Removes the given layer from the map.
6686 removeLayer: function (layer) {
6687 var id = stamp(layer);
6688
6689 if (!this._layers[id]) { return this; }
6690
6691 if (this._loaded) {
6692 layer.onRemove(this);
6693 }
6694
6695 if (layer.getAttribution && this.attributionControl) {
6696 this.attributionControl.removeAttribution(layer.getAttribution());
6697 }
6698
6699 delete this._layers[id];
6700
6701 if (this._loaded) {
6702 this.fire('layerremove', {layer: layer});
6703 layer.fire('remove');
6704 }
6705
6706 layer._map = layer._mapToAdd = null;
6707
6708 return this;
6709 },
6710
6711 // @method hasLayer(layer: Layer): Boolean
6712 // Returns `true` if the given layer is currently added to the map
6713 hasLayer: function (layer) {
6714 return !!layer && (stamp(layer) in this._layers);
6715 },
6716
6717 /* @method eachLayer(fn: Function, context?: Object): this
6718 * Iterates over the layers of the map, optionally specifying context of the iterator function.
6719 * ```
6720 * map.eachLayer(function(layer){
6721 * layer.bindPopup('Hello');
6722 * });
6723 * ```
6724 */
6725 eachLayer: function (method, context) {
6726 for (var i in this._layers) {
6727 method.call(context, this._layers[i]);
6728 }
6729 return this;
6730 },
6731
6732 _addLayers: function (layers) {
6733 layers = layers ? (isArray(layers) ? layers : [layers]) : [];
6734
6735 for (var i = 0, len = layers.length; i < len; i++) {
6736 this.addLayer(layers[i]);
6737 }
6738 },
6739
6740 _addZoomLimit: function (layer) {
6741 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
6742 this._zoomBoundLayers[stamp(layer)] = layer;
6743 this._updateZoomLevels();
6744 }
6745 },
6746
6747 _removeZoomLimit: function (layer) {
6748 var id = stamp(layer);
6749
6750 if (this._zoomBoundLayers[id]) {
6751 delete this._zoomBoundLayers[id];
6752 this._updateZoomLevels();
6753 }
6754 },
6755
6756 _updateZoomLevels: function () {
6757 var minZoom = Infinity,
6758 maxZoom = -Infinity,
6759 oldZoomSpan = this._getZoomSpan();
6760
6761 for (var i in this._zoomBoundLayers) {
6762 var options = this._zoomBoundLayers[i].options;
6763
6764 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
6765 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
6766 }
6767
6768 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
6769 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
6770
6771 // @section Map state change events
6772 // @event zoomlevelschange: Event
6773 // Fired when the number of zoomlevels on the map is changed due
6774 // to adding or removing a layer.
6775 if (oldZoomSpan !== this._getZoomSpan()) {
6776 this.fire('zoomlevelschange');
6777 }
6778
6779 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
6780 this.setZoom(this._layersMaxZoom);
6781 }
6782 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
6783 this.setZoom(this._layersMinZoom);
6784 }
6785 }
6786});
6787
6788/*
6789 * @class LayerGroup
6790 * @aka L.LayerGroup
6791 * @inherits Layer
6792 *
6793 * Used to group several layers and handle them as one. If you add it to the map,
6794 * any layers added or removed from the group will be added/removed on the map as
6795 * well. Extends `Layer`.
6796 *
6797 * @example
6798 *
6799 * ```js
6800 * L.layerGroup([marker1, marker2])
6801 * .addLayer(polyline)
6802 * .addTo(map);
6803 * ```
6804 */
6805
6806var LayerGroup = Layer.extend({
6807
6808 initialize: function (layers, options) {
6809 setOptions(this, options);
6810
6811 this._layers = {};
6812
6813 var i, len;
6814
6815 if (layers) {
6816 for (i = 0, len = layers.length; i < len; i++) {
6817 this.addLayer(layers[i]);
6818 }
6819 }
6820 },
6821
6822 // @method addLayer(layer: Layer): this
6823 // Adds the given layer to the group.
6824 addLayer: function (layer) {
6825 var id = this.getLayerId(layer);
6826
6827 this._layers[id] = layer;
6828
6829 if (this._map) {
6830 this._map.addLayer(layer);
6831 }
6832
6833 return this;
6834 },
6835
6836 // @method removeLayer(layer: Layer): this
6837 // Removes the given layer from the group.
6838 // @alternative
6839 // @method removeLayer(id: Number): this
6840 // Removes the layer with the given internal ID from the group.
6841 removeLayer: function (layer) {
6842 var id = layer in this._layers ? layer : this.getLayerId(layer);
6843
6844 if (this._map && this._layers[id]) {
6845 this._map.removeLayer(this._layers[id]);
6846 }
6847
6848 delete this._layers[id];
6849
6850 return this;
6851 },
6852
6853 // @method hasLayer(layer: Layer): Boolean
6854 // Returns `true` if the given layer is currently added to the group.
6855 // @alternative
6856 // @method hasLayer(id: Number): Boolean
6857 // Returns `true` if the given internal ID is currently added to the group.
6858 hasLayer: function (layer) {
6859 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
6860 },
6861
6862 // @method clearLayers(): this
6863 // Removes all the layers from the group.
6864 clearLayers: function () {
6865 return this.eachLayer(this.removeLayer, this);
6866 },
6867
6868 // @method invoke(methodName: String, …): this
6869 // Calls `methodName` on every layer contained in this group, passing any
6870 // additional parameters. Has no effect if the layers contained do not
6871 // implement `methodName`.
6872 invoke: function (methodName) {
6873 var args = Array.prototype.slice.call(arguments, 1),
6874 i, layer;
6875
6876 for (i in this._layers) {
6877 layer = this._layers[i];
6878
6879 if (layer[methodName]) {
6880 layer[methodName].apply(layer, args);
6881 }
6882 }
6883
6884 return this;
6885 },
6886
6887 onAdd: function (map) {
6888 this.eachLayer(map.addLayer, map);
6889 },
6890
6891 onRemove: function (map) {
6892 this.eachLayer(map.removeLayer, map);
6893 },
6894
6895 // @method eachLayer(fn: Function, context?: Object): this
6896 // Iterates over the layers of the group, optionally specifying context of the iterator function.
6897 // ```js
6898 // group.eachLayer(function (layer) {
6899 // layer.bindPopup('Hello');
6900 // });
6901 // ```
6902 eachLayer: function (method, context) {
6903 for (var i in this._layers) {
6904 method.call(context, this._layers[i]);
6905 }
6906 return this;
6907 },
6908
6909 // @method getLayer(id: Number): Layer
6910 // Returns the layer with the given internal ID.
6911 getLayer: function (id) {
6912 return this._layers[id];
6913 },
6914
6915 // @method getLayers(): Layer[]
6916 // Returns an array of all the layers added to the group.
6917 getLayers: function () {
6918 var layers = [];
6919 this.eachLayer(layers.push, layers);
6920 return layers;
6921 },
6922
6923 // @method setZIndex(zIndex: Number): this
6924 // Calls `setZIndex` on every layer contained in this group, passing the z-index.
6925 setZIndex: function (zIndex) {
6926 return this.invoke('setZIndex', zIndex);
6927 },
6928
6929 // @method getLayerId(layer: Layer): Number
6930 // Returns the internal ID for a layer
6931 getLayerId: function (layer) {
6932 return stamp(layer);
6933 }
6934});
6935
6936
6937// @factory L.layerGroup(layers?: Layer[], options?: Object)
6938// Create a layer group, optionally given an initial set of layers and an `options` object.
6939var layerGroup = function (layers, options) {
6940 return new LayerGroup(layers, options);
6941};
6942
6943/*
6944 * @class FeatureGroup
6945 * @aka L.FeatureGroup
6946 * @inherits LayerGroup
6947 *
6948 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
6949 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
6950 * * Events are propagated to the `FeatureGroup`, so if the group has an event
6951 * handler, it will handle events from any of the layers. This includes mouse events
6952 * and custom events.
6953 * * Has `layeradd` and `layerremove` events
6954 *
6955 * @example
6956 *
6957 * ```js
6958 * L.featureGroup([marker1, marker2, polyline])
6959 * .bindPopup('Hello world!')
6960 * .on('click', function() { alert('Clicked on a member of the group!'); })
6961 * .addTo(map);
6962 * ```
6963 */
6964
6965var FeatureGroup = LayerGroup.extend({
6966
6967 addLayer: function (layer) {
6968 if (this.hasLayer(layer)) {
6969 return this;
6970 }
6971
6972 layer.addEventParent(this);
6973
6974 LayerGroup.prototype.addLayer.call(this, layer);
6975
6976 // @event layeradd: LayerEvent
6977 // Fired when a layer is added to this `FeatureGroup`
6978 return this.fire('layeradd', {layer: layer});
6979 },
6980
6981 removeLayer: function (layer) {
6982 if (!this.hasLayer(layer)) {
6983 return this;
6984 }
6985 if (layer in this._layers) {
6986 layer = this._layers[layer];
6987 }
6988
6989 layer.removeEventParent(this);
6990
6991 LayerGroup.prototype.removeLayer.call(this, layer);
6992
6993 // @event layerremove: LayerEvent
6994 // Fired when a layer is removed from this `FeatureGroup`
6995 return this.fire('layerremove', {layer: layer});
6996 },
6997
6998 // @method setStyle(style: Path options): this
6999 // Sets the given path options to each layer of the group that has a `setStyle` method.
7000 setStyle: function (style) {
7001 return this.invoke('setStyle', style);
7002 },
7003
7004 // @method bringToFront(): this
7005 // Brings the layer group to the top of all other layers
7006 bringToFront: function () {
7007 return this.invoke('bringToFront');
7008 },
7009
7010 // @method bringToBack(): this
7011 // Brings the layer group to the back of all other layers
7012 bringToBack: function () {
7013 return this.invoke('bringToBack');
7014 },
7015
7016 // @method getBounds(): LatLngBounds
7017 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
7018 getBounds: function () {
7019 var bounds = new LatLngBounds();
7020
7021 for (var id in this._layers) {
7022 var layer = this._layers[id];
7023 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
7024 }
7025 return bounds;
7026 }
7027});
7028
7029// @factory L.featureGroup(layers: Layer[])
7030// Create a feature group, optionally given an initial set of layers.
7031var featureGroup = function (layers) {
7032 return new FeatureGroup(layers);
7033};
7034
7035/*
7036 * @class Icon
7037 * @aka L.Icon
7038 *
7039 * Represents an icon to provide when creating a marker.
7040 *
7041 * @example
7042 *
7043 * ```js
7044 * var myIcon = L.icon({
7045 * iconUrl: 'my-icon.png',
7046 * iconRetinaUrl: 'my-icon@2x.png',
7047 * iconSize: [38, 95],
7048 * iconAnchor: [22, 94],
7049 * popupAnchor: [-3, -76],
7050 * shadowUrl: 'my-icon-shadow.png',
7051 * shadowRetinaUrl: 'my-icon-shadow@2x.png',
7052 * shadowSize: [68, 95],
7053 * shadowAnchor: [22, 94]
7054 * });
7055 *
7056 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
7057 * ```
7058 *
7059 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
7060 *
7061 */
7062
7063var Icon = Class.extend({
7064
7065 /* @section
7066 * @aka Icon options
7067 *
7068 * @option iconUrl: String = null
7069 * **(required)** The URL to the icon image (absolute or relative to your script path).
7070 *
7071 * @option iconRetinaUrl: String = null
7072 * The URL to a retina sized version of the icon image (absolute or relative to your
7073 * script path). Used for Retina screen devices.
7074 *
7075 * @option iconSize: Point = null
7076 * Size of the icon image in pixels.
7077 *
7078 * @option iconAnchor: Point = null
7079 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
7080 * will be aligned so that this point is at the marker's geographical location. Centered
7081 * by default if size is specified, also can be set in CSS with negative margins.
7082 *
7083 * @option popupAnchor: Point = [0, 0]
7084 * The coordinates of the point from which popups will "open", relative to the icon anchor.
7085 *
7086 * @option tooltipAnchor: Point = [0, 0]
7087 * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
7088 *
7089 * @option shadowUrl: String = null
7090 * The URL to the icon shadow image. If not specified, no shadow image will be created.
7091 *
7092 * @option shadowRetinaUrl: String = null
7093 *
7094 * @option shadowSize: Point = null
7095 * Size of the shadow image in pixels.
7096 *
7097 * @option shadowAnchor: Point = null
7098 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
7099 * as iconAnchor if not specified).
7100 *
7101 * @option className: String = ''
7102 * A custom class name to assign to both icon and shadow images. Empty by default.
7103 */
7104
7105 options: {
7106 popupAnchor: [0, 0],
7107 tooltipAnchor: [0, 0]
7108 },
7109
7110 initialize: function (options) {
7111 setOptions(this, options);
7112 },
7113
7114 // @method createIcon(oldIcon?: HTMLElement): HTMLElement
7115 // Called internally when the icon has to be shown, returns a `<img>` HTML element
7116 // styled according to the options.
7117 createIcon: function (oldIcon) {
7118 return this._createIcon('icon', oldIcon);
7119 },
7120
7121 // @method createShadow(oldIcon?: HTMLElement): HTMLElement
7122 // As `createIcon`, but for the shadow beneath it.
7123 createShadow: function (oldIcon) {
7124 return this._createIcon('shadow', oldIcon);
7125 },
7126
7127 _createIcon: function (name, oldIcon) {
7128 var src = this._getIconUrl(name);
7129
7130 if (!src) {
7131 if (name === 'icon') {
7132 throw new Error('iconUrl not set in Icon options (see the docs).');
7133 }
7134 return null;
7135 }
7136
7137 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
7138 this._setIconStyles(img, name);
7139
7140 return img;
7141 },
7142
7143 _setIconStyles: function (img, name) {
7144 var options = this.options;
7145 var sizeOption = options[name + 'Size'];
7146
7147 if (typeof sizeOption === 'number') {
7148 sizeOption = [sizeOption, sizeOption];
7149 }
7150
7151 var size = toPoint(sizeOption),
7152 anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
7153 size && size.divideBy(2, true));
7154
7155 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
7156
7157 if (anchor) {
7158 img.style.marginLeft = (-anchor.x) + 'px';
7159 img.style.marginTop = (-anchor.y) + 'px';
7160 }
7161
7162 if (size) {
7163 img.style.width = size.x + 'px';
7164 img.style.height = size.y + 'px';
7165 }
7166 },
7167
7168 _createImg: function (src, el) {
7169 el = el || document.createElement('img');
7170 el.src = src;
7171 return el;
7172 },
7173
7174 _getIconUrl: function (name) {
7175 return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
7176 }
7177});
7178
7179
7180// @factory L.icon(options: Icon options)
7181// Creates an icon instance with the given options.
7182function icon(options) {
7183 return new Icon(options);
7184}
7185
7186/*
7187 * @miniclass Icon.Default (Icon)
7188 * @aka L.Icon.Default
7189 * @section
7190 *
7191 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
7192 * no icon is specified. Points to the blue marker image distributed with Leaflet
7193 * releases.
7194 *
7195 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
7196 * (which is a set of `Icon options`).
7197 *
7198 * If you want to _completely_ replace the default icon, override the
7199 * `L.Marker.prototype.options.icon` with your own icon instead.
7200 */
7201
7202var IconDefault = Icon.extend({
7203
7204 options: {
7205 iconUrl: 'marker-icon.png',
7206 iconRetinaUrl: 'marker-icon-2x.png',
7207 shadowUrl: 'marker-shadow.png',
7208 iconSize: [25, 41],
7209 iconAnchor: [12, 41],
7210 popupAnchor: [1, -34],
7211 tooltipAnchor: [16, -28],
7212 shadowSize: [41, 41]
7213 },
7214
7215 _getIconUrl: function (name) {
7216 if (!IconDefault.imagePath) { // Deprecated, backwards-compatibility only
7217 IconDefault.imagePath = this._detectIconPath();
7218 }
7219
7220 // @option imagePath: String
7221 // `Icon.Default` will try to auto-detect the location of the
7222 // blue icon images. If you are placing these images in a non-standard
7223 // way, set this option to point to the right path.
7224 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
7225 },
7226
7227 _detectIconPath: function () {
7228 var el = create$1('div', 'leaflet-default-icon-path', document.body);
7229 var path = getStyle(el, 'background-image') ||
7230 getStyle(el, 'backgroundImage'); // IE8
7231
7232 document.body.removeChild(el);
7233
7234 if (path === null || path.indexOf('url') !== 0) {
7235 path = '';
7236 } else {
7237 path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, '');
7238 }
7239
7240 return path;
7241 }
7242});
7243
7244/*
7245 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7246 */
7247
7248
7249/* @namespace Marker
7250 * @section Interaction handlers
7251 *
7252 * 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:
7253 *
7254 * ```js
7255 * marker.dragging.disable();
7256 * ```
7257 *
7258 * @property dragging: Handler
7259 * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
7260 */
7261
7262var MarkerDrag = Handler.extend({
7263 initialize: function (marker) {
7264 this._marker = marker;
7265 },
7266
7267 addHooks: function () {
7268 var icon = this._marker._icon;
7269
7270 if (!this._draggable) {
7271 this._draggable = new Draggable(icon, icon, true);
7272 }
7273
7274 this._draggable.on({
7275 dragstart: this._onDragStart,
7276 predrag: this._onPreDrag,
7277 drag: this._onDrag,
7278 dragend: this._onDragEnd
7279 }, this).enable();
7280
7281 addClass(icon, 'leaflet-marker-draggable');
7282 },
7283
7284 removeHooks: function () {
7285 this._draggable.off({
7286 dragstart: this._onDragStart,
7287 predrag: this._onPreDrag,
7288 drag: this._onDrag,
7289 dragend: this._onDragEnd
7290 }, this).disable();
7291
7292 if (this._marker._icon) {
7293 removeClass(this._marker._icon, 'leaflet-marker-draggable');
7294 }
7295 },
7296
7297 moved: function () {
7298 return this._draggable && this._draggable._moved;
7299 },
7300
7301 _adjustPan: function (e) {
7302 var marker = this._marker,
7303 map = marker._map,
7304 speed = this._marker.options.autoPanSpeed,
7305 padding = this._marker.options.autoPanPadding,
7306 iconPos = getPosition(marker._icon),
7307 bounds = map.getPixelBounds(),
7308 origin = map.getPixelOrigin();
7309
7310 var panBounds = toBounds(
7311 bounds.min._subtract(origin).add(padding),
7312 bounds.max._subtract(origin).subtract(padding)
7313 );
7314
7315 if (!panBounds.contains(iconPos)) {
7316 // Compute incremental movement
7317 var movement = toPoint(
7318 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
7319 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
7320
7321 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
7322 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
7323 ).multiplyBy(speed);
7324
7325 map.panBy(movement, {animate: false});
7326
7327 this._draggable._newPos._add(movement);
7328 this._draggable._startPos._add(movement);
7329
7330 setPosition(marker._icon, this._draggable._newPos);
7331 this._onDrag(e);
7332
7333 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7334 }
7335 },
7336
7337 _onDragStart: function () {
7338 // @section Dragging events
7339 // @event dragstart: Event
7340 // Fired when the user starts dragging the marker.
7341
7342 // @event movestart: Event
7343 // Fired when the marker starts moving (because of dragging).
7344
7345 this._oldLatLng = this._marker.getLatLng();
7346 this._marker
7347 .closePopup()
7348 .fire('movestart')
7349 .fire('dragstart');
7350 },
7351
7352 _onPreDrag: function (e) {
7353 if (this._marker.options.autoPan) {
7354 cancelAnimFrame(this._panRequest);
7355 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7356 }
7357 },
7358
7359 _onDrag: function (e) {
7360 var marker = this._marker,
7361 shadow = marker._shadow,
7362 iconPos = getPosition(marker._icon),
7363 latlng = marker._map.layerPointToLatLng(iconPos);
7364
7365 // update shadow position
7366 if (shadow) {
7367 setPosition(shadow, iconPos);
7368 }
7369
7370 marker._latlng = latlng;
7371 e.latlng = latlng;
7372 e.oldLatLng = this._oldLatLng;
7373
7374 // @event drag: Event
7375 // Fired repeatedly while the user drags the marker.
7376 marker
7377 .fire('move', e)
7378 .fire('drag', e);
7379 },
7380
7381 _onDragEnd: function (e) {
7382 // @event dragend: DragEndEvent
7383 // Fired when the user stops dragging the marker.
7384
7385 cancelAnimFrame(this._panRequest);
7386
7387 // @event moveend: Event
7388 // Fired when the marker stops moving (because of dragging).
7389 delete this._oldLatLng;
7390 this._marker
7391 .fire('moveend')
7392 .fire('dragend', e);
7393 }
7394});
7395
7396/*
7397 * @class Marker
7398 * @inherits Interactive layer
7399 * @aka L.Marker
7400 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
7401 *
7402 * @example
7403 *
7404 * ```js
7405 * L.marker([50.5, 30.5]).addTo(map);
7406 * ```
7407 */
7408
7409var Marker = Layer.extend({
7410
7411 // @section
7412 // @aka Marker options
7413 options: {
7414 // @option icon: Icon = *
7415 // Icon instance to use for rendering the marker.
7416 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
7417 // If not specified, a common instance of `L.Icon.Default` is used.
7418 icon: new IconDefault(),
7419
7420 // Option inherited from "Interactive layer" abstract class
7421 interactive: true,
7422
7423 // @option keyboard: Boolean = true
7424 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
7425 keyboard: true,
7426
7427 // @option title: String = ''
7428 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
7429 title: '',
7430
7431 // @option alt: String = ''
7432 // Text for the `alt` attribute of the icon image (useful for accessibility).
7433 alt: '',
7434
7435 // @option zIndexOffset: Number = 0
7436 // 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).
7437 zIndexOffset: 0,
7438
7439 // @option opacity: Number = 1.0
7440 // The opacity of the marker.
7441 opacity: 1,
7442
7443 // @option riseOnHover: Boolean = false
7444 // If `true`, the marker will get on top of others when you hover the mouse over it.
7445 riseOnHover: false,
7446
7447 // @option riseOffset: Number = 250
7448 // The z-index offset used for the `riseOnHover` feature.
7449 riseOffset: 250,
7450
7451 // @option pane: String = 'markerPane'
7452 // `Map pane` where the markers icon will be added.
7453 pane: 'markerPane',
7454
7455 // @option pane: String = 'shadowPane'
7456 // `Map pane` where the markers shadow will be added.
7457 shadowPane: 'shadowPane',
7458
7459 // @option bubblingMouseEvents: Boolean = false
7460 // When `true`, a mouse event on this marker will trigger the same event on the map
7461 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7462 bubblingMouseEvents: false,
7463
7464 // @section Draggable marker options
7465 // @option draggable: Boolean = false
7466 // Whether the marker is draggable with mouse/touch or not.
7467 draggable: false,
7468
7469 // @option autoPan: Boolean = false
7470 // Whether to pan the map when dragging this marker near its edge or not.
7471 autoPan: false,
7472
7473 // @option autoPanPadding: Point = Point(50, 50)
7474 // Distance (in pixels to the left/right and to the top/bottom) of the
7475 // map edge to start panning the map.
7476 autoPanPadding: [50, 50],
7477
7478 // @option autoPanSpeed: Number = 10
7479 // Number of pixels the map should pan by.
7480 autoPanSpeed: 10
7481 },
7482
7483 /* @section
7484 *
7485 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
7486 */
7487
7488 initialize: function (latlng, options) {
7489 setOptions(this, options);
7490 this._latlng = toLatLng(latlng);
7491 },
7492
7493 onAdd: function (map) {
7494 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
7495
7496 if (this._zoomAnimated) {
7497 map.on('zoomanim', this._animateZoom, this);
7498 }
7499
7500 this._initIcon();
7501 this.update();
7502 },
7503
7504 onRemove: function (map) {
7505 if (this.dragging && this.dragging.enabled()) {
7506 this.options.draggable = true;
7507 this.dragging.removeHooks();
7508 }
7509 delete this.dragging;
7510
7511 if (this._zoomAnimated) {
7512 map.off('zoomanim', this._animateZoom, this);
7513 }
7514
7515 this._removeIcon();
7516 this._removeShadow();
7517 },
7518
7519 getEvents: function () {
7520 return {
7521 zoom: this.update,
7522 viewreset: this.update
7523 };
7524 },
7525
7526 // @method getLatLng: LatLng
7527 // Returns the current geographical position of the marker.
7528 getLatLng: function () {
7529 return this._latlng;
7530 },
7531
7532 // @method setLatLng(latlng: LatLng): this
7533 // Changes the marker position to the given point.
7534 setLatLng: function (latlng) {
7535 var oldLatLng = this._latlng;
7536 this._latlng = toLatLng(latlng);
7537 this.update();
7538
7539 // @event move: Event
7540 // 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`.
7541 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7542 },
7543
7544 // @method setZIndexOffset(offset: Number): this
7545 // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
7546 setZIndexOffset: function (offset) {
7547 this.options.zIndexOffset = offset;
7548 return this.update();
7549 },
7550
7551 // @method getIcon: Icon
7552 // Returns the current icon used by the marker
7553 getIcon: function () {
7554 return this.options.icon;
7555 },
7556
7557 // @method setIcon(icon: Icon): this
7558 // Changes the marker icon.
7559 setIcon: function (icon) {
7560
7561 this.options.icon = icon;
7562
7563 if (this._map) {
7564 this._initIcon();
7565 this.update();
7566 }
7567
7568 if (this._popup) {
7569 this.bindPopup(this._popup, this._popup.options);
7570 }
7571
7572 return this;
7573 },
7574
7575 getElement: function () {
7576 return this._icon;
7577 },
7578
7579 update: function () {
7580
7581 if (this._icon && this._map) {
7582 var pos = this._map.latLngToLayerPoint(this._latlng).round();
7583 this._setPos(pos);
7584 }
7585
7586 return this;
7587 },
7588
7589 _initIcon: function () {
7590 var options = this.options,
7591 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
7592
7593 var icon = options.icon.createIcon(this._icon),
7594 addIcon = false;
7595
7596 // if we're not reusing the icon, remove the old one and init new one
7597 if (icon !== this._icon) {
7598 if (this._icon) {
7599 this._removeIcon();
7600 }
7601 addIcon = true;
7602
7603 if (options.title) {
7604 icon.title = options.title;
7605 }
7606
7607 if (icon.tagName === 'IMG') {
7608 icon.alt = options.alt || '';
7609 }
7610 }
7611
7612 addClass(icon, classToAdd);
7613
7614 if (options.keyboard) {
7615 icon.tabIndex = '0';
7616 }
7617
7618 this._icon = icon;
7619
7620 if (options.riseOnHover) {
7621 this.on({
7622 mouseover: this._bringToFront,
7623 mouseout: this._resetZIndex
7624 });
7625 }
7626
7627 var newShadow = options.icon.createShadow(this._shadow),
7628 addShadow = false;
7629
7630 if (newShadow !== this._shadow) {
7631 this._removeShadow();
7632 addShadow = true;
7633 }
7634
7635 if (newShadow) {
7636 addClass(newShadow, classToAdd);
7637 newShadow.alt = '';
7638 }
7639 this._shadow = newShadow;
7640
7641
7642 if (options.opacity < 1) {
7643 this._updateOpacity();
7644 }
7645
7646
7647 if (addIcon) {
7648 this.getPane().appendChild(this._icon);
7649 }
7650 this._initInteraction();
7651 if (newShadow && addShadow) {
7652 this.getPane(options.shadowPane).appendChild(this._shadow);
7653 }
7654 },
7655
7656 _removeIcon: function () {
7657 if (this.options.riseOnHover) {
7658 this.off({
7659 mouseover: this._bringToFront,
7660 mouseout: this._resetZIndex
7661 });
7662 }
7663
7664 remove(this._icon);
7665 this.removeInteractiveTarget(this._icon);
7666
7667 this._icon = null;
7668 },
7669
7670 _removeShadow: function () {
7671 if (this._shadow) {
7672 remove(this._shadow);
7673 }
7674 this._shadow = null;
7675 },
7676
7677 _setPos: function (pos) {
7678
7679 if (this._icon) {
7680 setPosition(this._icon, pos);
7681 }
7682
7683 if (this._shadow) {
7684 setPosition(this._shadow, pos);
7685 }
7686
7687 this._zIndex = pos.y + this.options.zIndexOffset;
7688
7689 this._resetZIndex();
7690 },
7691
7692 _updateZIndex: function (offset) {
7693 if (this._icon) {
7694 this._icon.style.zIndex = this._zIndex + offset;
7695 }
7696 },
7697
7698 _animateZoom: function (opt) {
7699 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
7700
7701 this._setPos(pos);
7702 },
7703
7704 _initInteraction: function () {
7705
7706 if (!this.options.interactive) { return; }
7707
7708 addClass(this._icon, 'leaflet-interactive');
7709
7710 this.addInteractiveTarget(this._icon);
7711
7712 if (MarkerDrag) {
7713 var draggable = this.options.draggable;
7714 if (this.dragging) {
7715 draggable = this.dragging.enabled();
7716 this.dragging.disable();
7717 }
7718
7719 this.dragging = new MarkerDrag(this);
7720
7721 if (draggable) {
7722 this.dragging.enable();
7723 }
7724 }
7725 },
7726
7727 // @method setOpacity(opacity: Number): this
7728 // Changes the opacity of the marker.
7729 setOpacity: function (opacity) {
7730 this.options.opacity = opacity;
7731 if (this._map) {
7732 this._updateOpacity();
7733 }
7734
7735 return this;
7736 },
7737
7738 _updateOpacity: function () {
7739 var opacity = this.options.opacity;
7740
7741 if (this._icon) {
7742 setOpacity(this._icon, opacity);
7743 }
7744
7745 if (this._shadow) {
7746 setOpacity(this._shadow, opacity);
7747 }
7748 },
7749
7750 _bringToFront: function () {
7751 this._updateZIndex(this.options.riseOffset);
7752 },
7753
7754 _resetZIndex: function () {
7755 this._updateZIndex(0);
7756 },
7757
7758 _getPopupAnchor: function () {
7759 return this.options.icon.options.popupAnchor;
7760 },
7761
7762 _getTooltipAnchor: function () {
7763 return this.options.icon.options.tooltipAnchor;
7764 }
7765});
7766
7767
7768// factory L.marker(latlng: LatLng, options? : Marker options)
7769
7770// @factory L.marker(latlng: LatLng, options? : Marker options)
7771// Instantiates a Marker object given a geographical point and optionally an options object.
7772function marker(latlng, options) {
7773 return new Marker(latlng, options);
7774}
7775
7776/*
7777 * @class Path
7778 * @aka L.Path
7779 * @inherits Interactive layer
7780 *
7781 * An abstract class that contains options and constants shared between vector
7782 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
7783 */
7784
7785var Path = Layer.extend({
7786
7787 // @section
7788 // @aka Path options
7789 options: {
7790 // @option stroke: Boolean = true
7791 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
7792 stroke: true,
7793
7794 // @option color: String = '#3388ff'
7795 // Stroke color
7796 color: '#3388ff',
7797
7798 // @option weight: Number = 3
7799 // Stroke width in pixels
7800 weight: 3,
7801
7802 // @option opacity: Number = 1.0
7803 // Stroke opacity
7804 opacity: 1,
7805
7806 // @option lineCap: String= 'round'
7807 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
7808 lineCap: 'round',
7809
7810 // @option lineJoin: String = 'round'
7811 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
7812 lineJoin: 'round',
7813
7814 // @option dashArray: String = null
7815 // 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).
7816 dashArray: null,
7817
7818 // @option dashOffset: String = null
7819 // 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).
7820 dashOffset: null,
7821
7822 // @option fill: Boolean = depends
7823 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
7824 fill: false,
7825
7826 // @option fillColor: String = *
7827 // Fill color. Defaults to the value of the [`color`](#path-color) option
7828 fillColor: null,
7829
7830 // @option fillOpacity: Number = 0.2
7831 // Fill opacity.
7832 fillOpacity: 0.2,
7833
7834 // @option fillRule: String = 'evenodd'
7835 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
7836 fillRule: 'evenodd',
7837
7838 // className: '',
7839
7840 // Option inherited from "Interactive layer" abstract class
7841 interactive: true,
7842
7843 // @option bubblingMouseEvents: Boolean = true
7844 // When `true`, a mouse event on this path will trigger the same event on the map
7845 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7846 bubblingMouseEvents: true
7847 },
7848
7849 beforeAdd: function (map) {
7850 // Renderer is set here because we need to call renderer.getEvents
7851 // before this.getEvents.
7852 this._renderer = map.getRenderer(this);
7853 },
7854
7855 onAdd: function () {
7856 this._renderer._initPath(this);
7857 this._reset();
7858 this._renderer._addPath(this);
7859 },
7860
7861 onRemove: function () {
7862 this._renderer._removePath(this);
7863 },
7864
7865 // @method redraw(): this
7866 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
7867 redraw: function () {
7868 if (this._map) {
7869 this._renderer._updatePath(this);
7870 }
7871 return this;
7872 },
7873
7874 // @method setStyle(style: Path options): this
7875 // Changes the appearance of a Path based on the options in the `Path options` object.
7876 setStyle: function (style) {
7877 setOptions(this, style);
7878 if (this._renderer) {
7879 this._renderer._updateStyle(this);
7880 if (this.options.stroke && style && style.hasOwnProperty('weight')) {
7881 this._updateBounds();
7882 }
7883 }
7884 return this;
7885 },
7886
7887 // @method bringToFront(): this
7888 // Brings the layer to the top of all path layers.
7889 bringToFront: function () {
7890 if (this._renderer) {
7891 this._renderer._bringToFront(this);
7892 }
7893 return this;
7894 },
7895
7896 // @method bringToBack(): this
7897 // Brings the layer to the bottom of all path layers.
7898 bringToBack: function () {
7899 if (this._renderer) {
7900 this._renderer._bringToBack(this);
7901 }
7902 return this;
7903 },
7904
7905 getElement: function () {
7906 return this._path;
7907 },
7908
7909 _reset: function () {
7910 // defined in child classes
7911 this._project();
7912 this._update();
7913 },
7914
7915 _clickTolerance: function () {
7916 // used when doing hit detection for Canvas layers
7917 return (this.options.stroke ? this.options.weight / 2 : 0) + this._renderer.options.tolerance;
7918 }
7919});
7920
7921/*
7922 * @class CircleMarker
7923 * @aka L.CircleMarker
7924 * @inherits Path
7925 *
7926 * A circle of a fixed size with radius specified in pixels. Extends `Path`.
7927 */
7928
7929var CircleMarker = Path.extend({
7930
7931 // @section
7932 // @aka CircleMarker options
7933 options: {
7934 fill: true,
7935
7936 // @option radius: Number = 10
7937 // Radius of the circle marker, in pixels
7938 radius: 10
7939 },
7940
7941 initialize: function (latlng, options) {
7942 setOptions(this, options);
7943 this._latlng = toLatLng(latlng);
7944 this._radius = this.options.radius;
7945 },
7946
7947 // @method setLatLng(latLng: LatLng): this
7948 // Sets the position of a circle marker to a new location.
7949 setLatLng: function (latlng) {
7950 var oldLatLng = this._latlng;
7951 this._latlng = toLatLng(latlng);
7952 this.redraw();
7953
7954 // @event move: Event
7955 // Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
7956 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7957 },
7958
7959 // @method getLatLng(): LatLng
7960 // Returns the current geographical position of the circle marker
7961 getLatLng: function () {
7962 return this._latlng;
7963 },
7964
7965 // @method setRadius(radius: Number): this
7966 // Sets the radius of a circle marker. Units are in pixels.
7967 setRadius: function (radius) {
7968 this.options.radius = this._radius = radius;
7969 return this.redraw();
7970 },
7971
7972 // @method getRadius(): Number
7973 // Returns the current radius of the circle
7974 getRadius: function () {
7975 return this._radius;
7976 },
7977
7978 setStyle : function (options) {
7979 var radius = options && options.radius || this._radius;
7980 Path.prototype.setStyle.call(this, options);
7981 this.setRadius(radius);
7982 return this;
7983 },
7984
7985 _project: function () {
7986 this._point = this._map.latLngToLayerPoint(this._latlng);
7987 this._updateBounds();
7988 },
7989
7990 _updateBounds: function () {
7991 var r = this._radius,
7992 r2 = this._radiusY || r,
7993 w = this._clickTolerance(),
7994 p = [r + w, r2 + w];
7995 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
7996 },
7997
7998 _update: function () {
7999 if (this._map) {
8000 this._updatePath();
8001 }
8002 },
8003
8004 _updatePath: function () {
8005 this._renderer._updateCircle(this);
8006 },
8007
8008 _empty: function () {
8009 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
8010 },
8011
8012 // Needed by the `Canvas` renderer for interactivity
8013 _containsPoint: function (p) {
8014 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
8015 }
8016});
8017
8018
8019// @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
8020// Instantiates a circle marker object given a geographical point, and an optional options object.
8021function circleMarker(latlng, options) {
8022 return new CircleMarker(latlng, options);
8023}
8024
8025/*
8026 * @class Circle
8027 * @aka L.Circle
8028 * @inherits CircleMarker
8029 *
8030 * A class for drawing circle overlays on a map. Extends `CircleMarker`.
8031 *
8032 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
8033 *
8034 * @example
8035 *
8036 * ```js
8037 * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
8038 * ```
8039 */
8040
8041var Circle = CircleMarker.extend({
8042
8043 initialize: function (latlng, options, legacyOptions) {
8044 if (typeof options === 'number') {
8045 // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
8046 options = extend({}, legacyOptions, {radius: options});
8047 }
8048 setOptions(this, options);
8049 this._latlng = toLatLng(latlng);
8050
8051 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
8052
8053 // @section
8054 // @aka Circle options
8055 // @option radius: Number; Radius of the circle, in meters.
8056 this._mRadius = this.options.radius;
8057 },
8058
8059 // @method setRadius(radius: Number): this
8060 // Sets the radius of a circle. Units are in meters.
8061 setRadius: function (radius) {
8062 this._mRadius = radius;
8063 return this.redraw();
8064 },
8065
8066 // @method getRadius(): Number
8067 // Returns the current radius of a circle. Units are in meters.
8068 getRadius: function () {
8069 return this._mRadius;
8070 },
8071
8072 // @method getBounds(): LatLngBounds
8073 // Returns the `LatLngBounds` of the path.
8074 getBounds: function () {
8075 var half = [this._radius, this._radiusY || this._radius];
8076
8077 return new LatLngBounds(
8078 this._map.layerPointToLatLng(this._point.subtract(half)),
8079 this._map.layerPointToLatLng(this._point.add(half)));
8080 },
8081
8082 setStyle: Path.prototype.setStyle,
8083
8084 _project: function () {
8085
8086 var lng = this._latlng.lng,
8087 lat = this._latlng.lat,
8088 map = this._map,
8089 crs = map.options.crs;
8090
8091 if (crs.distance === Earth.distance) {
8092 var d = Math.PI / 180,
8093 latR = (this._mRadius / Earth.R) / d,
8094 top = map.project([lat + latR, lng]),
8095 bottom = map.project([lat - latR, lng]),
8096 p = top.add(bottom).divideBy(2),
8097 lat2 = map.unproject(p).lat,
8098 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
8099 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
8100
8101 if (isNaN(lngR) || lngR === 0) {
8102 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
8103 }
8104
8105 this._point = p.subtract(map.getPixelOrigin());
8106 this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
8107 this._radiusY = p.y - top.y;
8108
8109 } else {
8110 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
8111
8112 this._point = map.latLngToLayerPoint(this._latlng);
8113 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
8114 }
8115
8116 this._updateBounds();
8117 }
8118});
8119
8120// @factory L.circle(latlng: LatLng, options?: Circle options)
8121// Instantiates a circle object given a geographical point, and an options object
8122// which contains the circle radius.
8123// @alternative
8124// @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
8125// Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
8126// Do not use in new applications or plugins.
8127function circle(latlng, options, legacyOptions) {
8128 return new Circle(latlng, options, legacyOptions);
8129}
8130
8131/*
8132 * @class Polyline
8133 * @aka L.Polyline
8134 * @inherits Path
8135 *
8136 * A class for drawing polyline overlays on a map. Extends `Path`.
8137 *
8138 * @example
8139 *
8140 * ```js
8141 * // create a red polyline from an array of LatLng points
8142 * var latlngs = [
8143 * [45.51, -122.68],
8144 * [37.77, -122.43],
8145 * [34.04, -118.2]
8146 * ];
8147 *
8148 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
8149 *
8150 * // zoom the map to the polyline
8151 * map.fitBounds(polyline.getBounds());
8152 * ```
8153 *
8154 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
8155 *
8156 * ```js
8157 * // create a red polyline from an array of arrays of LatLng points
8158 * var latlngs = [
8159 * [[45.51, -122.68],
8160 * [37.77, -122.43],
8161 * [34.04, -118.2]],
8162 * [[40.78, -73.91],
8163 * [41.83, -87.62],
8164 * [32.76, -96.72]]
8165 * ];
8166 * ```
8167 */
8168
8169
8170var Polyline = Path.extend({
8171
8172 // @section
8173 // @aka Polyline options
8174 options: {
8175 // @option smoothFactor: Number = 1.0
8176 // How much to simplify the polyline on each zoom level. More means
8177 // better performance and smoother look, and less means more accurate representation.
8178 smoothFactor: 1.0,
8179
8180 // @option noClip: Boolean = false
8181 // Disable polyline clipping.
8182 noClip: false
8183 },
8184
8185 initialize: function (latlngs, options) {
8186 setOptions(this, options);
8187 this._setLatLngs(latlngs);
8188 },
8189
8190 // @method getLatLngs(): LatLng[]
8191 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8192 getLatLngs: function () {
8193 return this._latlngs;
8194 },
8195
8196 // @method setLatLngs(latlngs: LatLng[]): this
8197 // Replaces all the points in the polyline with the given array of geographical points.
8198 setLatLngs: function (latlngs) {
8199 this._setLatLngs(latlngs);
8200 return this.redraw();
8201 },
8202
8203 // @method isEmpty(): Boolean
8204 // Returns `true` if the Polyline has no LatLngs.
8205 isEmpty: function () {
8206 return !this._latlngs.length;
8207 },
8208
8209 // @method closestLayerPoint(p: Point): Point
8210 // Returns the point closest to `p` on the Polyline.
8211 closestLayerPoint: function (p) {
8212 var minDistance = Infinity,
8213 minPoint = null,
8214 closest = _sqClosestPointOnSegment,
8215 p1, p2;
8216
8217 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8218 var points = this._parts[j];
8219
8220 for (var i = 1, len = points.length; i < len; i++) {
8221 p1 = points[i - 1];
8222 p2 = points[i];
8223
8224 var sqDist = closest(p, p1, p2, true);
8225
8226 if (sqDist < minDistance) {
8227 minDistance = sqDist;
8228 minPoint = closest(p, p1, p2);
8229 }
8230 }
8231 }
8232 if (minPoint) {
8233 minPoint.distance = Math.sqrt(minDistance);
8234 }
8235 return minPoint;
8236 },
8237
8238 // @method getCenter(): LatLng
8239 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
8240 getCenter: function () {
8241 // throws error when not yet added to map as this center calculation requires projected coordinates
8242 if (!this._map) {
8243 throw new Error('Must add layer to map before using getCenter()');
8244 }
8245
8246 var i, halfDist, segDist, dist, p1, p2, ratio,
8247 points = this._rings[0],
8248 len = points.length;
8249
8250 if (!len) { return null; }
8251
8252 // polyline centroid algorithm; only uses the first ring if there are multiple
8253
8254 for (i = 0, halfDist = 0; i < len - 1; i++) {
8255 halfDist += points[i].distanceTo(points[i + 1]) / 2;
8256 }
8257
8258 // The line is so small in the current view that all points are on the same pixel.
8259 if (halfDist === 0) {
8260 return this._map.layerPointToLatLng(points[0]);
8261 }
8262
8263 for (i = 0, dist = 0; i < len - 1; i++) {
8264 p1 = points[i];
8265 p2 = points[i + 1];
8266 segDist = p1.distanceTo(p2);
8267 dist += segDist;
8268
8269 if (dist > halfDist) {
8270 ratio = (dist - halfDist) / segDist;
8271 return this._map.layerPointToLatLng([
8272 p2.x - ratio * (p2.x - p1.x),
8273 p2.y - ratio * (p2.y - p1.y)
8274 ]);
8275 }
8276 }
8277 },
8278
8279 // @method getBounds(): LatLngBounds
8280 // Returns the `LatLngBounds` of the path.
8281 getBounds: function () {
8282 return this._bounds;
8283 },
8284
8285 // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
8286 // Adds a given point to the polyline. By default, adds to the first ring of
8287 // the polyline in case of a multi-polyline, but can be overridden by passing
8288 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8289 addLatLng: function (latlng, latlngs) {
8290 latlngs = latlngs || this._defaultShape();
8291 latlng = toLatLng(latlng);
8292 latlngs.push(latlng);
8293 this._bounds.extend(latlng);
8294 return this.redraw();
8295 },
8296
8297 _setLatLngs: function (latlngs) {
8298 this._bounds = new LatLngBounds();
8299 this._latlngs = this._convertLatLngs(latlngs);
8300 },
8301
8302 _defaultShape: function () {
8303 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
8304 },
8305
8306 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8307 _convertLatLngs: function (latlngs) {
8308 var result = [],
8309 flat = isFlat(latlngs);
8310
8311 for (var i = 0, len = latlngs.length; i < len; i++) {
8312 if (flat) {
8313 result[i] = toLatLng(latlngs[i]);
8314 this._bounds.extend(result[i]);
8315 } else {
8316 result[i] = this._convertLatLngs(latlngs[i]);
8317 }
8318 }
8319
8320 return result;
8321 },
8322
8323 _project: function () {
8324 var pxBounds = new Bounds();
8325 this._rings = [];
8326 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8327
8328 if (this._bounds.isValid() && pxBounds.isValid()) {
8329 this._rawPxBounds = pxBounds;
8330 this._updateBounds();
8331 }
8332 },
8333
8334 _updateBounds: function () {
8335 var w = this._clickTolerance(),
8336 p = new Point(w, w);
8337 this._pxBounds = new Bounds([
8338 this._rawPxBounds.min.subtract(p),
8339 this._rawPxBounds.max.add(p)
8340 ]);
8341 },
8342
8343 // recursively turns latlngs into a set of rings with projected coordinates
8344 _projectLatlngs: function (latlngs, result, projectedBounds) {
8345 var flat = latlngs[0] instanceof LatLng,
8346 len = latlngs.length,
8347 i, ring;
8348
8349 if (flat) {
8350 ring = [];
8351 for (i = 0; i < len; i++) {
8352 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8353 projectedBounds.extend(ring[i]);
8354 }
8355 result.push(ring);
8356 } else {
8357 for (i = 0; i < len; i++) {
8358 this._projectLatlngs(latlngs[i], result, projectedBounds);
8359 }
8360 }
8361 },
8362
8363 // clip polyline by renderer bounds so that we have less to render for performance
8364 _clipPoints: function () {
8365 var bounds = this._renderer._bounds;
8366
8367 this._parts = [];
8368 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8369 return;
8370 }
8371
8372 if (this.options.noClip) {
8373 this._parts = this._rings;
8374 return;
8375 }
8376
8377 var parts = this._parts,
8378 i, j, k, len, len2, segment, points;
8379
8380 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8381 points = this._rings[i];
8382
8383 for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8384 segment = clipSegment(points[j], points[j + 1], bounds, j, true);
8385
8386 if (!segment) { continue; }
8387
8388 parts[k] = parts[k] || [];
8389 parts[k].push(segment[0]);
8390
8391 // if segment goes out of screen, or it's the last one, it's the end of the line part
8392 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8393 parts[k].push(segment[1]);
8394 k++;
8395 }
8396 }
8397 }
8398 },
8399
8400 // simplify each clipped part of the polyline for performance
8401 _simplifyPoints: function () {
8402 var parts = this._parts,
8403 tolerance = this.options.smoothFactor;
8404
8405 for (var i = 0, len = parts.length; i < len; i++) {
8406 parts[i] = simplify(parts[i], tolerance);
8407 }
8408 },
8409
8410 _update: function () {
8411 if (!this._map) { return; }
8412
8413 this._clipPoints();
8414 this._simplifyPoints();
8415 this._updatePath();
8416 },
8417
8418 _updatePath: function () {
8419 this._renderer._updatePoly(this);
8420 },
8421
8422 // Needed by the `Canvas` renderer for interactivity
8423 _containsPoint: function (p, closed) {
8424 var i, j, k, len, len2, part,
8425 w = this._clickTolerance();
8426
8427 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8428
8429 // hit detection for polylines
8430 for (i = 0, len = this._parts.length; i < len; i++) {
8431 part = this._parts[i];
8432
8433 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8434 if (!closed && (j === 0)) { continue; }
8435
8436 if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
8437 return true;
8438 }
8439 }
8440 }
8441 return false;
8442 }
8443});
8444
8445// @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8446// Instantiates a polyline object given an array of geographical points and
8447// optionally an options object. You can create a `Polyline` object with
8448// multiple separate lines (`MultiPolyline`) by passing an array of arrays
8449// of geographic points.
8450function polyline(latlngs, options) {
8451 return new Polyline(latlngs, options);
8452}
8453
8454// Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
8455Polyline._flat = _flat;
8456
8457/*
8458 * @class Polygon
8459 * @aka L.Polygon
8460 * @inherits Polyline
8461 *
8462 * A class for drawing polygon overlays on a map. Extends `Polyline`.
8463 *
8464 * 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.
8465 *
8466 *
8467 * @example
8468 *
8469 * ```js
8470 * // create a red polygon from an array of LatLng points
8471 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
8472 *
8473 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8474 *
8475 * // zoom the map to the polygon
8476 * map.fitBounds(polygon.getBounds());
8477 * ```
8478 *
8479 * 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:
8480 *
8481 * ```js
8482 * var latlngs = [
8483 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8484 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8485 * ];
8486 * ```
8487 *
8488 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8489 *
8490 * ```js
8491 * var latlngs = [
8492 * [ // first polygon
8493 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8494 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8495 * ],
8496 * [ // second polygon
8497 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
8498 * ]
8499 * ];
8500 * ```
8501 */
8502
8503var Polygon = Polyline.extend({
8504
8505 options: {
8506 fill: true
8507 },
8508
8509 isEmpty: function () {
8510 return !this._latlngs.length || !this._latlngs[0].length;
8511 },
8512
8513 getCenter: function () {
8514 // throws error when not yet added to map as this center calculation requires projected coordinates
8515 if (!this._map) {
8516 throw new Error('Must add layer to map before using getCenter()');
8517 }
8518
8519 var i, j, p1, p2, f, area, x, y, center,
8520 points = this._rings[0],
8521 len = points.length;
8522
8523 if (!len) { return null; }
8524
8525 // polygon centroid algorithm; only uses the first ring if there are multiple
8526
8527 area = x = y = 0;
8528
8529 for (i = 0, j = len - 1; i < len; j = i++) {
8530 p1 = points[i];
8531 p2 = points[j];
8532
8533 f = p1.y * p2.x - p2.y * p1.x;
8534 x += (p1.x + p2.x) * f;
8535 y += (p1.y + p2.y) * f;
8536 area += f * 3;
8537 }
8538
8539 if (area === 0) {
8540 // Polygon is so small that all points are on same pixel.
8541 center = points[0];
8542 } else {
8543 center = [x / area, y / area];
8544 }
8545 return this._map.layerPointToLatLng(center);
8546 },
8547
8548 _convertLatLngs: function (latlngs) {
8549 var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
8550 len = result.length;
8551
8552 // remove last point if it equals first one
8553 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
8554 result.pop();
8555 }
8556 return result;
8557 },
8558
8559 _setLatLngs: function (latlngs) {
8560 Polyline.prototype._setLatLngs.call(this, latlngs);
8561 if (isFlat(this._latlngs)) {
8562 this._latlngs = [this._latlngs];
8563 }
8564 },
8565
8566 _defaultShape: function () {
8567 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8568 },
8569
8570 _clipPoints: function () {
8571 // polygons need a different clipping algorithm so we redefine that
8572
8573 var bounds = this._renderer._bounds,
8574 w = this.options.weight,
8575 p = new Point(w, w);
8576
8577 // increase clip padding by stroke width to avoid stroke on clip edges
8578 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
8579
8580 this._parts = [];
8581 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8582 return;
8583 }
8584
8585 if (this.options.noClip) {
8586 this._parts = this._rings;
8587 return;
8588 }
8589
8590 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8591 clipped = clipPolygon(this._rings[i], bounds, true);
8592 if (clipped.length) {
8593 this._parts.push(clipped);
8594 }
8595 }
8596 },
8597
8598 _updatePath: function () {
8599 this._renderer._updatePoly(this, true);
8600 },
8601
8602 // Needed by the `Canvas` renderer for interactivity
8603 _containsPoint: function (p) {
8604 var inside = false,
8605 part, p1, p2, i, j, k, len, len2;
8606
8607 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8608
8609 // ray casting algorithm for detecting if point is in polygon
8610 for (i = 0, len = this._parts.length; i < len; i++) {
8611 part = this._parts[i];
8612
8613 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8614 p1 = part[j];
8615 p2 = part[k];
8616
8617 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)) {
8618 inside = !inside;
8619 }
8620 }
8621 }
8622
8623 // also check if it's on polygon stroke
8624 return inside || Polyline.prototype._containsPoint.call(this, p, true);
8625 }
8626
8627});
8628
8629
8630// @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8631function polygon(latlngs, options) {
8632 return new Polygon(latlngs, options);
8633}
8634
8635/*
8636 * @class GeoJSON
8637 * @aka L.GeoJSON
8638 * @inherits FeatureGroup
8639 *
8640 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
8641 * GeoJSON data and display it on the map. Extends `FeatureGroup`.
8642 *
8643 * @example
8644 *
8645 * ```js
8646 * L.geoJSON(data, {
8647 * style: function (feature) {
8648 * return {color: feature.properties.color};
8649 * }
8650 * }).bindPopup(function (layer) {
8651 * return layer.feature.properties.description;
8652 * }).addTo(map);
8653 * ```
8654 */
8655
8656var GeoJSON = FeatureGroup.extend({
8657
8658 /* @section
8659 * @aka GeoJSON options
8660 *
8661 * @option pointToLayer: Function = *
8662 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
8663 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
8664 * The default is to spawn a default `Marker`:
8665 * ```js
8666 * function(geoJsonPoint, latlng) {
8667 * return L.marker(latlng);
8668 * }
8669 * ```
8670 *
8671 * @option style: Function = *
8672 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
8673 * called internally when data is added.
8674 * The default value is to not override any defaults:
8675 * ```js
8676 * function (geoJsonFeature) {
8677 * return {}
8678 * }
8679 * ```
8680 *
8681 * @option onEachFeature: Function = *
8682 * A `Function` that will be called once for each created `Feature`, after it has
8683 * been created and styled. Useful for attaching events and popups to features.
8684 * The default is to do nothing with the newly created layers:
8685 * ```js
8686 * function (feature, layer) {}
8687 * ```
8688 *
8689 * @option filter: Function = *
8690 * A `Function` that will be used to decide whether to include a feature or not.
8691 * The default is to include all features:
8692 * ```js
8693 * function (geoJsonFeature) {
8694 * return true;
8695 * }
8696 * ```
8697 * Note: dynamically changing the `filter` option will have effect only on newly
8698 * added data. It will _not_ re-evaluate already included features.
8699 *
8700 * @option coordsToLatLng: Function = *
8701 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
8702 * The default is the `coordsToLatLng` static method.
8703 *
8704 * @option markersInheritOptions: Boolean = false
8705 * Whether default Markers for "Point" type Features inherit from group options.
8706 */
8707
8708 initialize: function (geojson, options) {
8709 setOptions(this, options);
8710
8711 this._layers = {};
8712
8713 if (geojson) {
8714 this.addData(geojson);
8715 }
8716 },
8717
8718 // @method addData( <GeoJSON> data ): this
8719 // Adds a GeoJSON object to the layer.
8720 addData: function (geojson) {
8721 var features = isArray(geojson) ? geojson : geojson.features,
8722 i, len, feature;
8723
8724 if (features) {
8725 for (i = 0, len = features.length; i < len; i++) {
8726 // only add this if geometry or geometries are set and not null
8727 feature = features[i];
8728 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
8729 this.addData(feature);
8730 }
8731 }
8732 return this;
8733 }
8734
8735 var options = this.options;
8736
8737 if (options.filter && !options.filter(geojson)) { return this; }
8738
8739 var layer = geometryToLayer(geojson, options);
8740 if (!layer) {
8741 return this;
8742 }
8743 layer.feature = asFeature(geojson);
8744
8745 layer.defaultOptions = layer.options;
8746 this.resetStyle(layer);
8747
8748 if (options.onEachFeature) {
8749 options.onEachFeature(geojson, layer);
8750 }
8751
8752 return this.addLayer(layer);
8753 },
8754
8755 // @method resetStyle( <Path> layer? ): this
8756 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
8757 // If `layer` is omitted, the style of all features in the current layer is reset.
8758 resetStyle: function (layer) {
8759 if (layer === undefined) {
8760 return this.eachLayer(this.resetStyle, this);
8761 }
8762 // reset any custom styles
8763 layer.options = extend({}, layer.defaultOptions);
8764 this._setLayerStyle(layer, this.options.style);
8765 return this;
8766 },
8767
8768 // @method setStyle( <Function> style ): this
8769 // Changes styles of GeoJSON vector layers with the given style function.
8770 setStyle: function (style) {
8771 return this.eachLayer(function (layer) {
8772 this._setLayerStyle(layer, style);
8773 }, this);
8774 },
8775
8776 _setLayerStyle: function (layer, style) {
8777 if (layer.setStyle) {
8778 if (typeof style === 'function') {
8779 style = style(layer.feature);
8780 }
8781 layer.setStyle(style);
8782 }
8783 }
8784});
8785
8786// @section
8787// There are several static functions which can be called without instantiating L.GeoJSON:
8788
8789// @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
8790// Creates a `Layer` from a given GeoJSON feature. Can use a custom
8791// [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
8792// functions if provided as options.
8793function geometryToLayer(geojson, options) {
8794
8795 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
8796 coords = geometry ? geometry.coordinates : null,
8797 layers = [],
8798 pointToLayer = options && options.pointToLayer,
8799 _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
8800 latlng, latlngs, i, len;
8801
8802 if (!coords && !geometry) {
8803 return null;
8804 }
8805
8806 switch (geometry.type) {
8807 case 'Point':
8808 latlng = _coordsToLatLng(coords);
8809 return _pointToLayer(pointToLayer, geojson, latlng, options);
8810
8811 case 'MultiPoint':
8812 for (i = 0, len = coords.length; i < len; i++) {
8813 latlng = _coordsToLatLng(coords[i]);
8814 layers.push(_pointToLayer(pointToLayer, geojson, latlng, options));
8815 }
8816 return new FeatureGroup(layers);
8817
8818 case 'LineString':
8819 case 'MultiLineString':
8820 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
8821 return new Polyline(latlngs, options);
8822
8823 case 'Polygon':
8824 case 'MultiPolygon':
8825 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
8826 return new Polygon(latlngs, options);
8827
8828 case 'GeometryCollection':
8829 for (i = 0, len = geometry.geometries.length; i < len; i++) {
8830 var layer = geometryToLayer({
8831 geometry: geometry.geometries[i],
8832 type: 'Feature',
8833 properties: geojson.properties
8834 }, options);
8835
8836 if (layer) {
8837 layers.push(layer);
8838 }
8839 }
8840 return new FeatureGroup(layers);
8841
8842 default:
8843 throw new Error('Invalid GeoJSON object.');
8844 }
8845}
8846
8847function _pointToLayer(pointToLayerFn, geojson, latlng, options) {
8848 return pointToLayerFn ?
8849 pointToLayerFn(geojson, latlng) :
8850 new Marker(latlng, options && options.markersInheritOptions && options);
8851}
8852
8853// @function coordsToLatLng(coords: Array): LatLng
8854// Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
8855// or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
8856function coordsToLatLng(coords) {
8857 return new LatLng(coords[1], coords[0], coords[2]);
8858}
8859
8860// @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
8861// Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
8862// `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
8863// Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
8864function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
8865 var latlngs = [];
8866
8867 for (var i = 0, len = coords.length, latlng; i < len; i++) {
8868 latlng = levelsDeep ?
8869 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
8870 (_coordsToLatLng || coordsToLatLng)(coords[i]);
8871
8872 latlngs.push(latlng);
8873 }
8874
8875 return latlngs;
8876}
8877
8878// @function latLngToCoords(latlng: LatLng, precision?: Number): Array
8879// Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
8880function latLngToCoords(latlng, precision) {
8881 precision = typeof precision === 'number' ? precision : 6;
8882 return latlng.alt !== undefined ?
8883 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
8884 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
8885}
8886
8887// @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
8888// Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
8889// `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.
8890function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
8891 var coords = [];
8892
8893 for (var i = 0, len = latlngs.length; i < len; i++) {
8894 coords.push(levelsDeep ?
8895 latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) :
8896 latLngToCoords(latlngs[i], precision));
8897 }
8898
8899 if (!levelsDeep && closed) {
8900 coords.push(coords[0]);
8901 }
8902
8903 return coords;
8904}
8905
8906function getFeature(layer, newGeometry) {
8907 return layer.feature ?
8908 extend({}, layer.feature, {geometry: newGeometry}) :
8909 asFeature(newGeometry);
8910}
8911
8912// @function asFeature(geojson: Object): Object
8913// Normalize GeoJSON geometries/features into GeoJSON features.
8914function asFeature(geojson) {
8915 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
8916 return geojson;
8917 }
8918
8919 return {
8920 type: 'Feature',
8921 properties: {},
8922 geometry: geojson
8923 };
8924}
8925
8926var PointToGeoJSON = {
8927 toGeoJSON: function (precision) {
8928 return getFeature(this, {
8929 type: 'Point',
8930 coordinates: latLngToCoords(this.getLatLng(), precision)
8931 });
8932 }
8933};
8934
8935// @namespace Marker
8936// @section Other methods
8937// @method toGeoJSON(precision?: Number): Object
8938// `precision` is the number of decimal places for coordinates.
8939// The default value is 6 places.
8940// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
8941Marker.include(PointToGeoJSON);
8942
8943// @namespace CircleMarker
8944// @method toGeoJSON(precision?: Number): Object
8945// `precision` is the number of decimal places for coordinates.
8946// The default value is 6 places.
8947// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
8948Circle.include(PointToGeoJSON);
8949CircleMarker.include(PointToGeoJSON);
8950
8951
8952// @namespace Polyline
8953// @method toGeoJSON(precision?: Number): Object
8954// `precision` is the number of decimal places for coordinates.
8955// The default value is 6 places.
8956// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
8957Polyline.include({
8958 toGeoJSON: function (precision) {
8959 var multi = !isFlat(this._latlngs);
8960
8961 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
8962
8963 return getFeature(this, {
8964 type: (multi ? 'Multi' : '') + 'LineString',
8965 coordinates: coords
8966 });
8967 }
8968});
8969
8970// @namespace Polygon
8971// @method toGeoJSON(precision?: Number): Object
8972// `precision` is the number of decimal places for coordinates.
8973// The default value is 6 places.
8974// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
8975Polygon.include({
8976 toGeoJSON: function (precision) {
8977 var holes = !isFlat(this._latlngs),
8978 multi = holes && !isFlat(this._latlngs[0]);
8979
8980 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
8981
8982 if (!holes) {
8983 coords = [coords];
8984 }
8985
8986 return getFeature(this, {
8987 type: (multi ? 'Multi' : '') + 'Polygon',
8988 coordinates: coords
8989 });
8990 }
8991});
8992
8993
8994// @namespace LayerGroup
8995LayerGroup.include({
8996 toMultiPoint: function (precision) {
8997 var coords = [];
8998
8999 this.eachLayer(function (layer) {
9000 coords.push(layer.toGeoJSON(precision).geometry.coordinates);
9001 });
9002
9003 return getFeature(this, {
9004 type: 'MultiPoint',
9005 coordinates: coords
9006 });
9007 },
9008
9009 // @method toGeoJSON(precision?: Number): Object
9010 // `precision` is the number of decimal places for coordinates.
9011 // The default value is 6 places.
9012 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
9013 toGeoJSON: function (precision) {
9014
9015 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
9016
9017 if (type === 'MultiPoint') {
9018 return this.toMultiPoint(precision);
9019 }
9020
9021 var isGeometryCollection = type === 'GeometryCollection',
9022 jsons = [];
9023
9024 this.eachLayer(function (layer) {
9025 if (layer.toGeoJSON) {
9026 var json = layer.toGeoJSON(precision);
9027 if (isGeometryCollection) {
9028 jsons.push(json.geometry);
9029 } else {
9030 var feature = asFeature(json);
9031 // Squash nested feature collections
9032 if (feature.type === 'FeatureCollection') {
9033 jsons.push.apply(jsons, feature.features);
9034 } else {
9035 jsons.push(feature);
9036 }
9037 }
9038 }
9039 });
9040
9041 if (isGeometryCollection) {
9042 return getFeature(this, {
9043 geometries: jsons,
9044 type: 'GeometryCollection'
9045 });
9046 }
9047
9048 return {
9049 type: 'FeatureCollection',
9050 features: jsons
9051 };
9052 }
9053});
9054
9055// @namespace GeoJSON
9056// @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
9057// Creates a GeoJSON layer. Optionally accepts an object in
9058// [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
9059// (you can alternatively add it later with `addData` method) and an `options` object.
9060function geoJSON(geojson, options) {
9061 return new GeoJSON(geojson, options);
9062}
9063
9064// Backward compatibility.
9065var geoJson = geoJSON;
9066
9067/*
9068 * @class ImageOverlay
9069 * @aka L.ImageOverlay
9070 * @inherits Interactive layer
9071 *
9072 * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
9073 *
9074 * @example
9075 *
9076 * ```js
9077 * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
9078 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
9079 * L.imageOverlay(imageUrl, imageBounds).addTo(map);
9080 * ```
9081 */
9082
9083var ImageOverlay = Layer.extend({
9084
9085 // @section
9086 // @aka ImageOverlay options
9087 options: {
9088 // @option opacity: Number = 1.0
9089 // The opacity of the image overlay.
9090 opacity: 1,
9091
9092 // @option alt: String = ''
9093 // Text for the `alt` attribute of the image (useful for accessibility).
9094 alt: '',
9095
9096 // @option interactive: Boolean = false
9097 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
9098 interactive: false,
9099
9100 // @option crossOrigin: Boolean|String = false
9101 // Whether the crossOrigin attribute will be added to the image.
9102 // 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.
9103 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
9104 crossOrigin: false,
9105
9106 // @option errorOverlayUrl: String = ''
9107 // URL to the overlay image to show in place of the overlay that failed to load.
9108 errorOverlayUrl: '',
9109
9110 // @option zIndex: Number = 1
9111 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
9112 zIndex: 1,
9113
9114 // @option className: String = ''
9115 // A custom class name to assign to the image. Empty by default.
9116 className: ''
9117 },
9118
9119 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
9120 this._url = url;
9121 this._bounds = toLatLngBounds(bounds);
9122
9123 setOptions(this, options);
9124 },
9125
9126 onAdd: function () {
9127 if (!this._image) {
9128 this._initImage();
9129
9130 if (this.options.opacity < 1) {
9131 this._updateOpacity();
9132 }
9133 }
9134
9135 if (this.options.interactive) {
9136 addClass(this._image, 'leaflet-interactive');
9137 this.addInteractiveTarget(this._image);
9138 }
9139
9140 this.getPane().appendChild(this._image);
9141 this._reset();
9142 },
9143
9144 onRemove: function () {
9145 remove(this._image);
9146 if (this.options.interactive) {
9147 this.removeInteractiveTarget(this._image);
9148 }
9149 },
9150
9151 // @method setOpacity(opacity: Number): this
9152 // Sets the opacity of the overlay.
9153 setOpacity: function (opacity) {
9154 this.options.opacity = opacity;
9155
9156 if (this._image) {
9157 this._updateOpacity();
9158 }
9159 return this;
9160 },
9161
9162 setStyle: function (styleOpts) {
9163 if (styleOpts.opacity) {
9164 this.setOpacity(styleOpts.opacity);
9165 }
9166 return this;
9167 },
9168
9169 // @method bringToFront(): this
9170 // Brings the layer to the top of all overlays.
9171 bringToFront: function () {
9172 if (this._map) {
9173 toFront(this._image);
9174 }
9175 return this;
9176 },
9177
9178 // @method bringToBack(): this
9179 // Brings the layer to the bottom of all overlays.
9180 bringToBack: function () {
9181 if (this._map) {
9182 toBack(this._image);
9183 }
9184 return this;
9185 },
9186
9187 // @method setUrl(url: String): this
9188 // Changes the URL of the image.
9189 setUrl: function (url) {
9190 this._url = url;
9191
9192 if (this._image) {
9193 this._image.src = url;
9194 }
9195 return this;
9196 },
9197
9198 // @method setBounds(bounds: LatLngBounds): this
9199 // Update the bounds that this ImageOverlay covers
9200 setBounds: function (bounds) {
9201 this._bounds = toLatLngBounds(bounds);
9202
9203 if (this._map) {
9204 this._reset();
9205 }
9206 return this;
9207 },
9208
9209 getEvents: function () {
9210 var events = {
9211 zoom: this._reset,
9212 viewreset: this._reset
9213 };
9214
9215 if (this._zoomAnimated) {
9216 events.zoomanim = this._animateZoom;
9217 }
9218
9219 return events;
9220 },
9221
9222 // @method setZIndex(value: Number): this
9223 // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
9224 setZIndex: function (value) {
9225 this.options.zIndex = value;
9226 this._updateZIndex();
9227 return this;
9228 },
9229
9230 // @method getBounds(): LatLngBounds
9231 // Get the bounds that this ImageOverlay covers
9232 getBounds: function () {
9233 return this._bounds;
9234 },
9235
9236 // @method getElement(): HTMLElement
9237 // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
9238 // used by this overlay.
9239 getElement: function () {
9240 return this._image;
9241 },
9242
9243 _initImage: function () {
9244 var wasElementSupplied = this._url.tagName === 'IMG';
9245 var img = this._image = wasElementSupplied ? this._url : create$1('img');
9246
9247 addClass(img, 'leaflet-image-layer');
9248 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
9249 if (this.options.className) { addClass(img, this.options.className); }
9250
9251 img.onselectstart = falseFn;
9252 img.onmousemove = falseFn;
9253
9254 // @event load: Event
9255 // Fired when the ImageOverlay layer has loaded its image
9256 img.onload = bind(this.fire, this, 'load');
9257 img.onerror = bind(this._overlayOnError, this, 'error');
9258
9259 if (this.options.crossOrigin || this.options.crossOrigin === '') {
9260 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
9261 }
9262
9263 if (this.options.zIndex) {
9264 this._updateZIndex();
9265 }
9266
9267 if (wasElementSupplied) {
9268 this._url = img.src;
9269 return;
9270 }
9271
9272 img.src = this._url;
9273 img.alt = this.options.alt;
9274 },
9275
9276 _animateZoom: function (e) {
9277 var scale = this._map.getZoomScale(e.zoom),
9278 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
9279
9280 setTransform(this._image, offset, scale);
9281 },
9282
9283 _reset: function () {
9284 var image = this._image,
9285 bounds = new Bounds(
9286 this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
9287 this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
9288 size = bounds.getSize();
9289
9290 setPosition(image, bounds.min);
9291
9292 image.style.width = size.x + 'px';
9293 image.style.height = size.y + 'px';
9294 },
9295
9296 _updateOpacity: function () {
9297 setOpacity(this._image, this.options.opacity);
9298 },
9299
9300 _updateZIndex: function () {
9301 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
9302 this._image.style.zIndex = this.options.zIndex;
9303 }
9304 },
9305
9306 _overlayOnError: function () {
9307 // @event error: Event
9308 // Fired when the ImageOverlay layer fails to load its image
9309 this.fire('error');
9310
9311 var errorUrl = this.options.errorOverlayUrl;
9312 if (errorUrl && this._url !== errorUrl) {
9313 this._url = errorUrl;
9314 this._image.src = errorUrl;
9315 }
9316 }
9317});
9318
9319// @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
9320// Instantiates an image overlay object given the URL of the image and the
9321// geographical bounds it is tied to.
9322var imageOverlay = function (url, bounds, options) {
9323 return new ImageOverlay(url, bounds, options);
9324};
9325
9326/*
9327 * @class VideoOverlay
9328 * @aka L.VideoOverlay
9329 * @inherits ImageOverlay
9330 *
9331 * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
9332 *
9333 * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
9334 * HTML5 element.
9335 *
9336 * @example
9337 *
9338 * ```js
9339 * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
9340 * videoBounds = [[ 32, -130], [ 13, -100]];
9341 * L.videoOverlay(videoUrl, videoBounds ).addTo(map);
9342 * ```
9343 */
9344
9345var VideoOverlay = ImageOverlay.extend({
9346
9347 // @section
9348 // @aka VideoOverlay options
9349 options: {
9350 // @option autoplay: Boolean = true
9351 // Whether the video starts playing automatically when loaded.
9352 autoplay: true,
9353
9354 // @option loop: Boolean = true
9355 // Whether the video will loop back to the beginning when played.
9356 loop: true,
9357
9358 // @option keepAspectRatio: Boolean = true
9359 // Whether the video will save aspect ratio after the projection.
9360 // Relevant for supported browsers. Browser compatibility- https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit
9361 keepAspectRatio: true
9362 },
9363
9364 _initImage: function () {
9365 var wasElementSupplied = this._url.tagName === 'VIDEO';
9366 var vid = this._image = wasElementSupplied ? this._url : create$1('video');
9367
9368 addClass(vid, 'leaflet-image-layer');
9369 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
9370 if (this.options.className) { addClass(vid, this.options.className); }
9371
9372 vid.onselectstart = falseFn;
9373 vid.onmousemove = falseFn;
9374
9375 // @event load: Event
9376 // Fired when the video has finished loading the first frame
9377 vid.onloadeddata = bind(this.fire, this, 'load');
9378
9379 if (wasElementSupplied) {
9380 var sourceElements = vid.getElementsByTagName('source');
9381 var sources = [];
9382 for (var j = 0; j < sourceElements.length; j++) {
9383 sources.push(sourceElements[j].src);
9384 }
9385
9386 this._url = (sourceElements.length > 0) ? sources : [vid.src];
9387 return;
9388 }
9389
9390 if (!isArray(this._url)) { this._url = [this._url]; }
9391
9392 if (!this.options.keepAspectRatio && vid.style.hasOwnProperty('objectFit')) { vid.style['objectFit'] = 'fill'; }
9393 vid.autoplay = !!this.options.autoplay;
9394 vid.loop = !!this.options.loop;
9395 for (var i = 0; i < this._url.length; i++) {
9396 var source = create$1('source');
9397 source.src = this._url[i];
9398 vid.appendChild(source);
9399 }
9400 }
9401
9402 // @method getElement(): HTMLVideoElement
9403 // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
9404 // used by this overlay.
9405});
9406
9407
9408// @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
9409// Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
9410// geographical bounds it is tied to.
9411
9412function videoOverlay(video, bounds, options) {
9413 return new VideoOverlay(video, bounds, options);
9414}
9415
9416/*
9417 * @class SVGOverlay
9418 * @aka L.SVGOverlay
9419 * @inherits ImageOverlay
9420 *
9421 * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`.
9422 *
9423 * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element.
9424 *
9425 * @example
9426 *
9427 * ```js
9428 * var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
9429 * svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg");
9430 * svgElement.setAttribute('viewBox', "0 0 200 200");
9431 * 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"/>';
9432 * var svgElementBounds = [ [ 32, -130 ], [ 13, -100 ] ];
9433 * L.svgOverlay(svgElement, svgElementBounds).addTo(map);
9434 * ```
9435 */
9436
9437var SVGOverlay = ImageOverlay.extend({
9438 _initImage: function () {
9439 var el = this._image = this._url;
9440
9441 addClass(el, 'leaflet-image-layer');
9442 if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); }
9443 if (this.options.className) { addClass(el, this.options.className); }
9444
9445 el.onselectstart = falseFn;
9446 el.onmousemove = falseFn;
9447 }
9448
9449 // @method getElement(): SVGElement
9450 // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement)
9451 // used by this overlay.
9452});
9453
9454
9455// @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options)
9456// Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to.
9457// A viewBox attribute is required on the SVG element to zoom in and out properly.
9458
9459function svgOverlay(el, bounds, options) {
9460 return new SVGOverlay(el, bounds, options);
9461}
9462
9463/*
9464 * @class DivOverlay
9465 * @inherits Layer
9466 * @aka L.DivOverlay
9467 * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
9468 */
9469
9470// @namespace DivOverlay
9471var DivOverlay = Layer.extend({
9472
9473 // @section
9474 // @aka DivOverlay options
9475 options: {
9476 // @option offset: Point = Point(0, 7)
9477 // The offset of the popup position. Useful to control the anchor
9478 // of the popup when opening it on some overlays.
9479 offset: [0, 7],
9480
9481 // @option className: String = ''
9482 // A custom CSS class name to assign to the popup.
9483 className: '',
9484
9485 // @option pane: String = 'popupPane'
9486 // `Map pane` where the popup will be added.
9487 pane: 'popupPane'
9488 },
9489
9490 initialize: function (options, source) {
9491 setOptions(this, options);
9492
9493 this._source = source;
9494 },
9495
9496 onAdd: function (map) {
9497 this._zoomAnimated = map._zoomAnimated;
9498
9499 if (!this._container) {
9500 this._initLayout();
9501 }
9502
9503 if (map._fadeAnimated) {
9504 setOpacity(this._container, 0);
9505 }
9506
9507 clearTimeout(this._removeTimeout);
9508 this.getPane().appendChild(this._container);
9509 this.update();
9510
9511 if (map._fadeAnimated) {
9512 setOpacity(this._container, 1);
9513 }
9514
9515 this.bringToFront();
9516 },
9517
9518 onRemove: function (map) {
9519 if (map._fadeAnimated) {
9520 setOpacity(this._container, 0);
9521 this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
9522 } else {
9523 remove(this._container);
9524 }
9525 },
9526
9527 // @namespace Popup
9528 // @method getLatLng: LatLng
9529 // Returns the geographical point of popup.
9530 getLatLng: function () {
9531 return this._latlng;
9532 },
9533
9534 // @method setLatLng(latlng: LatLng): this
9535 // Sets the geographical point where the popup will open.
9536 setLatLng: function (latlng) {
9537 this._latlng = toLatLng(latlng);
9538 if (this._map) {
9539 this._updatePosition();
9540 this._adjustPan();
9541 }
9542 return this;
9543 },
9544
9545 // @method getContent: String|HTMLElement
9546 // Returns the content of the popup.
9547 getContent: function () {
9548 return this._content;
9549 },
9550
9551 // @method setContent(htmlContent: String|HTMLElement|Function): this
9552 // 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.
9553 setContent: function (content) {
9554 this._content = content;
9555 this.update();
9556 return this;
9557 },
9558
9559 // @method getElement: String|HTMLElement
9560 // Alias for [getContent()](#popup-getcontent)
9561 getElement: function () {
9562 return this._container;
9563 },
9564
9565 // @method update: null
9566 // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
9567 update: function () {
9568 if (!this._map) { return; }
9569
9570 this._container.style.visibility = 'hidden';
9571
9572 this._updateContent();
9573 this._updateLayout();
9574 this._updatePosition();
9575
9576 this._container.style.visibility = '';
9577
9578 this._adjustPan();
9579 },
9580
9581 getEvents: function () {
9582 var events = {
9583 zoom: this._updatePosition,
9584 viewreset: this._updatePosition
9585 };
9586
9587 if (this._zoomAnimated) {
9588 events.zoomanim = this._animateZoom;
9589 }
9590 return events;
9591 },
9592
9593 // @method isOpen: Boolean
9594 // Returns `true` when the popup is visible on the map.
9595 isOpen: function () {
9596 return !!this._map && this._map.hasLayer(this);
9597 },
9598
9599 // @method bringToFront: this
9600 // Brings this popup in front of other popups (in the same map pane).
9601 bringToFront: function () {
9602 if (this._map) {
9603 toFront(this._container);
9604 }
9605 return this;
9606 },
9607
9608 // @method bringToBack: this
9609 // Brings this popup to the back of other popups (in the same map pane).
9610 bringToBack: function () {
9611 if (this._map) {
9612 toBack(this._container);
9613 }
9614 return this;
9615 },
9616
9617 _prepareOpen: function (parent, layer, latlng) {
9618 if (!(layer instanceof Layer)) {
9619 latlng = layer;
9620 layer = parent;
9621 }
9622
9623 if (layer instanceof FeatureGroup) {
9624 for (var id in parent._layers) {
9625 layer = parent._layers[id];
9626 break;
9627 }
9628 }
9629
9630 if (!latlng) {
9631 if (layer.getCenter) {
9632 latlng = layer.getCenter();
9633 } else if (layer.getLatLng) {
9634 latlng = layer.getLatLng();
9635 } else {
9636 throw new Error('Unable to get source layer LatLng.');
9637 }
9638 }
9639
9640 // set overlay source to this layer
9641 this._source = layer;
9642
9643 // update the overlay (content, layout, ect...)
9644 this.update();
9645
9646 return latlng;
9647 },
9648
9649 _updateContent: function () {
9650 if (!this._content) { return; }
9651
9652 var node = this._contentNode;
9653 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
9654
9655 if (typeof content === 'string') {
9656 node.innerHTML = content;
9657 } else {
9658 while (node.hasChildNodes()) {
9659 node.removeChild(node.firstChild);
9660 }
9661 node.appendChild(content);
9662 }
9663 this.fire('contentupdate');
9664 },
9665
9666 _updatePosition: function () {
9667 if (!this._map) { return; }
9668
9669 var pos = this._map.latLngToLayerPoint(this._latlng),
9670 offset = toPoint(this.options.offset),
9671 anchor = this._getAnchor();
9672
9673 if (this._zoomAnimated) {
9674 setPosition(this._container, pos.add(anchor));
9675 } else {
9676 offset = offset.add(pos).add(anchor);
9677 }
9678
9679 var bottom = this._containerBottom = -offset.y,
9680 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
9681
9682 // bottom position the popup in case the height of the popup changes (images loading etc)
9683 this._container.style.bottom = bottom + 'px';
9684 this._container.style.left = left + 'px';
9685 },
9686
9687 _getAnchor: function () {
9688 return [0, 0];
9689 }
9690
9691});
9692
9693/*
9694 * @class Popup
9695 * @inherits DivOverlay
9696 * @aka L.Popup
9697 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
9698 * open popups while making sure that only one popup is open at one time
9699 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
9700 *
9701 * @example
9702 *
9703 * If you want to just bind a popup to marker click and then open it, it's really easy:
9704 *
9705 * ```js
9706 * marker.bindPopup(popupContent).openPopup();
9707 * ```
9708 * Path overlays like polylines also have a `bindPopup` method.
9709 * Here's a more complicated way to open a popup on a map:
9710 *
9711 * ```js
9712 * var popup = L.popup()
9713 * .setLatLng(latlng)
9714 * .setContent('<p>Hello world!<br />This is a nice popup.</p>')
9715 * .openOn(map);
9716 * ```
9717 */
9718
9719
9720// @namespace Popup
9721var Popup = DivOverlay.extend({
9722
9723 // @section
9724 // @aka Popup options
9725 options: {
9726 // @option maxWidth: Number = 300
9727 // Max width of the popup, in pixels.
9728 maxWidth: 300,
9729
9730 // @option minWidth: Number = 50
9731 // Min width of the popup, in pixels.
9732 minWidth: 50,
9733
9734 // @option maxHeight: Number = null
9735 // If set, creates a scrollable container of the given height
9736 // inside a popup if its content exceeds it.
9737 maxHeight: null,
9738
9739 // @option autoPan: Boolean = true
9740 // Set it to `false` if you don't want the map to do panning animation
9741 // to fit the opened popup.
9742 autoPan: true,
9743
9744 // @option autoPanPaddingTopLeft: Point = null
9745 // The margin between the popup and the top left corner of the map
9746 // view after autopanning was performed.
9747 autoPanPaddingTopLeft: null,
9748
9749 // @option autoPanPaddingBottomRight: Point = null
9750 // The margin between the popup and the bottom right corner of the map
9751 // view after autopanning was performed.
9752 autoPanPaddingBottomRight: null,
9753
9754 // @option autoPanPadding: Point = Point(5, 5)
9755 // Equivalent of setting both top left and bottom right autopan padding to the same value.
9756 autoPanPadding: [5, 5],
9757
9758 // @option keepInView: Boolean = false
9759 // Set it to `true` if you want to prevent users from panning the popup
9760 // off of the screen while it is open.
9761 keepInView: false,
9762
9763 // @option closeButton: Boolean = true
9764 // Controls the presence of a close button in the popup.
9765 closeButton: true,
9766
9767 // @option autoClose: Boolean = true
9768 // Set it to `false` if you want to override the default behavior of
9769 // the popup closing when another popup is opened.
9770 autoClose: true,
9771
9772 // @option closeOnEscapeKey: Boolean = true
9773 // Set it to `false` if you want to override the default behavior of
9774 // the ESC key for closing of the popup.
9775 closeOnEscapeKey: true,
9776
9777 // @option closeOnClick: Boolean = *
9778 // Set it if you want to override the default behavior of the popup closing when user clicks
9779 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
9780
9781 // @option className: String = ''
9782 // A custom CSS class name to assign to the popup.
9783 className: ''
9784 },
9785
9786 // @namespace Popup
9787 // @method openOn(map: Map): this
9788 // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
9789 openOn: function (map) {
9790 map.openPopup(this);
9791 return this;
9792 },
9793
9794 onAdd: function (map) {
9795 DivOverlay.prototype.onAdd.call(this, map);
9796
9797 // @namespace Map
9798 // @section Popup events
9799 // @event popupopen: PopupEvent
9800 // Fired when a popup is opened in the map
9801 map.fire('popupopen', {popup: this});
9802
9803 if (this._source) {
9804 // @namespace Layer
9805 // @section Popup events
9806 // @event popupopen: PopupEvent
9807 // Fired when a popup bound to this layer is opened
9808 this._source.fire('popupopen', {popup: this}, true);
9809 // For non-path layers, we toggle the popup when clicking
9810 // again the layer, so prevent the map to reopen it.
9811 if (!(this._source instanceof Path)) {
9812 this._source.on('preclick', stopPropagation);
9813 }
9814 }
9815 },
9816
9817 onRemove: function (map) {
9818 DivOverlay.prototype.onRemove.call(this, map);
9819
9820 // @namespace Map
9821 // @section Popup events
9822 // @event popupclose: PopupEvent
9823 // Fired when a popup in the map is closed
9824 map.fire('popupclose', {popup: this});
9825
9826 if (this._source) {
9827 // @namespace Layer
9828 // @section Popup events
9829 // @event popupclose: PopupEvent
9830 // Fired when a popup bound to this layer is closed
9831 this._source.fire('popupclose', {popup: this}, true);
9832 if (!(this._source instanceof Path)) {
9833 this._source.off('preclick', stopPropagation);
9834 }
9835 }
9836 },
9837
9838 getEvents: function () {
9839 var events = DivOverlay.prototype.getEvents.call(this);
9840
9841 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
9842 events.preclick = this._close;
9843 }
9844
9845 if (this.options.keepInView) {
9846 events.moveend = this._adjustPan;
9847 }
9848
9849 return events;
9850 },
9851
9852 _close: function () {
9853 if (this._map) {
9854 this._map.closePopup(this);
9855 }
9856 },
9857
9858 _initLayout: function () {
9859 var prefix = 'leaflet-popup',
9860 container = this._container = create$1('div',
9861 prefix + ' ' + (this.options.className || '') +
9862 ' leaflet-zoom-animated');
9863
9864 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
9865 this._contentNode = create$1('div', prefix + '-content', wrapper);
9866
9867 disableClickPropagation(wrapper);
9868 disableScrollPropagation(this._contentNode);
9869 on(wrapper, 'contextmenu', stopPropagation);
9870
9871 this._tipContainer = create$1('div', prefix + '-tip-container', container);
9872 this._tip = create$1('div', prefix + '-tip', this._tipContainer);
9873
9874 if (this.options.closeButton) {
9875 var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
9876 closeButton.href = '#close';
9877 closeButton.innerHTML = '&#215;';
9878
9879 on(closeButton, 'click', this._onCloseButtonClick, this);
9880 }
9881 },
9882
9883 _updateLayout: function () {
9884 var container = this._contentNode,
9885 style = container.style;
9886
9887 style.width = '';
9888 style.whiteSpace = 'nowrap';
9889
9890 var width = container.offsetWidth;
9891 width = Math.min(width, this.options.maxWidth);
9892 width = Math.max(width, this.options.minWidth);
9893
9894 style.width = (width + 1) + 'px';
9895 style.whiteSpace = '';
9896
9897 style.height = '';
9898
9899 var height = container.offsetHeight,
9900 maxHeight = this.options.maxHeight,
9901 scrolledClass = 'leaflet-popup-scrolled';
9902
9903 if (maxHeight && height > maxHeight) {
9904 style.height = maxHeight + 'px';
9905 addClass(container, scrolledClass);
9906 } else {
9907 removeClass(container, scrolledClass);
9908 }
9909
9910 this._containerWidth = this._container.offsetWidth;
9911 },
9912
9913 _animateZoom: function (e) {
9914 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
9915 anchor = this._getAnchor();
9916 setPosition(this._container, pos.add(anchor));
9917 },
9918
9919 _adjustPan: function () {
9920 if (!this.options.autoPan) { return; }
9921 if (this._map._panAnim) { this._map._panAnim.stop(); }
9922
9923 var map = this._map,
9924 marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
9925 containerHeight = this._container.offsetHeight + marginBottom,
9926 containerWidth = this._containerWidth,
9927 layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
9928
9929 layerPos._add(getPosition(this._container));
9930
9931 var containerPos = map.layerPointToContainerPoint(layerPos),
9932 padding = toPoint(this.options.autoPanPadding),
9933 paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
9934 paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
9935 size = map.getSize(),
9936 dx = 0,
9937 dy = 0;
9938
9939 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
9940 dx = containerPos.x + containerWidth - size.x + paddingBR.x;
9941 }
9942 if (containerPos.x - dx - paddingTL.x < 0) { // left
9943 dx = containerPos.x - paddingTL.x;
9944 }
9945 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
9946 dy = containerPos.y + containerHeight - size.y + paddingBR.y;
9947 }
9948 if (containerPos.y - dy - paddingTL.y < 0) { // top
9949 dy = containerPos.y - paddingTL.y;
9950 }
9951
9952 // @namespace Map
9953 // @section Popup events
9954 // @event autopanstart: Event
9955 // Fired when the map starts autopanning when opening a popup.
9956 if (dx || dy) {
9957 map
9958 .fire('autopanstart')
9959 .panBy([dx, dy]);
9960 }
9961 },
9962
9963 _onCloseButtonClick: function (e) {
9964 this._close();
9965 stop(e);
9966 },
9967
9968 _getAnchor: function () {
9969 // Where should we anchor the popup on the source layer?
9970 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
9971 }
9972
9973});
9974
9975// @namespace Popup
9976// @factory L.popup(options?: Popup options, source?: Layer)
9977// 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.
9978var popup = function (options, source) {
9979 return new Popup(options, source);
9980};
9981
9982
9983/* @namespace Map
9984 * @section Interaction Options
9985 * @option closePopupOnClick: Boolean = true
9986 * Set it to `false` if you don't want popups to close when user clicks the map.
9987 */
9988Map.mergeOptions({
9989 closePopupOnClick: true
9990});
9991
9992
9993// @namespace Map
9994// @section Methods for Layers and Controls
9995Map.include({
9996 // @method openPopup(popup: Popup): this
9997 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
9998 // @alternative
9999 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
10000 // Creates a popup with the specified content and options and opens it in the given point on a map.
10001 openPopup: function (popup, latlng, options) {
10002 if (!(popup instanceof Popup)) {
10003 popup = new Popup(options).setContent(popup);
10004 }
10005
10006 if (latlng) {
10007 popup.setLatLng(latlng);
10008 }
10009
10010 if (this.hasLayer(popup)) {
10011 return this;
10012 }
10013
10014 if (this._popup && this._popup.options.autoClose) {
10015 this.closePopup();
10016 }
10017
10018 this._popup = popup;
10019 return this.addLayer(popup);
10020 },
10021
10022 // @method closePopup(popup?: Popup): this
10023 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
10024 closePopup: function (popup) {
10025 if (!popup || popup === this._popup) {
10026 popup = this._popup;
10027 this._popup = null;
10028 }
10029 if (popup) {
10030 this.removeLayer(popup);
10031 }
10032 return this;
10033 }
10034});
10035
10036/*
10037 * @namespace Layer
10038 * @section Popup methods example
10039 *
10040 * All layers share a set of methods convenient for binding popups to it.
10041 *
10042 * ```js
10043 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
10044 * layer.openPopup();
10045 * layer.closePopup();
10046 * ```
10047 *
10048 * 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.
10049 */
10050
10051// @section Popup methods
10052Layer.include({
10053
10054 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
10055 // Binds a popup to the layer with the passed `content` and sets up the
10056 // necessary event listeners. If a `Function` is passed it will receive
10057 // the layer as the first argument and should return a `String` or `HTMLElement`.
10058 bindPopup: function (content, options) {
10059
10060 if (content instanceof Popup) {
10061 setOptions(content, options);
10062 this._popup = content;
10063 content._source = this;
10064 } else {
10065 if (!this._popup || options) {
10066 this._popup = new Popup(options, this);
10067 }
10068 this._popup.setContent(content);
10069 }
10070
10071 if (!this._popupHandlersAdded) {
10072 this.on({
10073 click: this._openPopup,
10074 keypress: this._onKeyPress,
10075 remove: this.closePopup,
10076 move: this._movePopup
10077 });
10078 this._popupHandlersAdded = true;
10079 }
10080
10081 return this;
10082 },
10083
10084 // @method unbindPopup(): this
10085 // Removes the popup previously bound with `bindPopup`.
10086 unbindPopup: function () {
10087 if (this._popup) {
10088 this.off({
10089 click: this._openPopup,
10090 keypress: this._onKeyPress,
10091 remove: this.closePopup,
10092 move: this._movePopup
10093 });
10094 this._popupHandlersAdded = false;
10095 this._popup = null;
10096 }
10097 return this;
10098 },
10099
10100 // @method openPopup(latlng?: LatLng): this
10101 // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
10102 openPopup: function (layer, latlng) {
10103 if (this._popup && this._map) {
10104 latlng = this._popup._prepareOpen(this, layer, latlng);
10105
10106 // open the popup on the map
10107 this._map.openPopup(this._popup, latlng);
10108 }
10109
10110 return this;
10111 },
10112
10113 // @method closePopup(): this
10114 // Closes the popup bound to this layer if it is open.
10115 closePopup: function () {
10116 if (this._popup) {
10117 this._popup._close();
10118 }
10119 return this;
10120 },
10121
10122 // @method togglePopup(): this
10123 // Opens or closes the popup bound to this layer depending on its current state.
10124 togglePopup: function (target) {
10125 if (this._popup) {
10126 if (this._popup._map) {
10127 this.closePopup();
10128 } else {
10129 this.openPopup(target);
10130 }
10131 }
10132 return this;
10133 },
10134
10135 // @method isPopupOpen(): boolean
10136 // Returns `true` if the popup bound to this layer is currently open.
10137 isPopupOpen: function () {
10138 return (this._popup ? this._popup.isOpen() : false);
10139 },
10140
10141 // @method setPopupContent(content: String|HTMLElement|Popup): this
10142 // Sets the content of the popup bound to this layer.
10143 setPopupContent: function (content) {
10144 if (this._popup) {
10145 this._popup.setContent(content);
10146 }
10147 return this;
10148 },
10149
10150 // @method getPopup(): Popup
10151 // Returns the popup bound to this layer.
10152 getPopup: function () {
10153 return this._popup;
10154 },
10155
10156 _openPopup: function (e) {
10157 var layer = e.layer || e.target;
10158
10159 if (!this._popup) {
10160 return;
10161 }
10162
10163 if (!this._map) {
10164 return;
10165 }
10166
10167 // prevent map click
10168 stop(e);
10169
10170 // if this inherits from Path its a vector and we can just
10171 // open the popup at the new location
10172 if (layer instanceof Path) {
10173 this.openPopup(e.layer || e.target, e.latlng);
10174 return;
10175 }
10176
10177 // otherwise treat it like a marker and figure out
10178 // if we should toggle it open/closed
10179 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
10180 this.closePopup();
10181 } else {
10182 this.openPopup(layer, e.latlng);
10183 }
10184 },
10185
10186 _movePopup: function (e) {
10187 this._popup.setLatLng(e.latlng);
10188 },
10189
10190 _onKeyPress: function (e) {
10191 if (e.originalEvent.keyCode === 13) {
10192 this._openPopup(e);
10193 }
10194 }
10195});
10196
10197/*
10198 * @class Tooltip
10199 * @inherits DivOverlay
10200 * @aka L.Tooltip
10201 * Used to display small texts on top of map layers.
10202 *
10203 * @example
10204 *
10205 * ```js
10206 * marker.bindTooltip("my tooltip text").openTooltip();
10207 * ```
10208 * Note about tooltip offset. Leaflet takes two options in consideration
10209 * for computing tooltip offsetting:
10210 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
10211 * Add a positive x offset to move the tooltip to the right, and a positive y offset to
10212 * move it to the bottom. Negatives will move to the left and top.
10213 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
10214 * should adapt this value if you use a custom icon.
10215 */
10216
10217
10218// @namespace Tooltip
10219var Tooltip = DivOverlay.extend({
10220
10221 // @section
10222 // @aka Tooltip options
10223 options: {
10224 // @option pane: String = 'tooltipPane'
10225 // `Map pane` where the tooltip will be added.
10226 pane: 'tooltipPane',
10227
10228 // @option offset: Point = Point(0, 0)
10229 // Optional offset of the tooltip position.
10230 offset: [0, 0],
10231
10232 // @option direction: String = 'auto'
10233 // Direction where to open the tooltip. Possible values are: `right`, `left`,
10234 // `top`, `bottom`, `center`, `auto`.
10235 // `auto` will dynamically switch between `right` and `left` according to the tooltip
10236 // position on the map.
10237 direction: 'auto',
10238
10239 // @option permanent: Boolean = false
10240 // Whether to open the tooltip permanently or only on mouseover.
10241 permanent: false,
10242
10243 // @option sticky: Boolean = false
10244 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
10245 sticky: false,
10246
10247 // @option interactive: Boolean = false
10248 // If true, the tooltip will listen to the feature events.
10249 interactive: false,
10250
10251 // @option opacity: Number = 0.9
10252 // Tooltip container opacity.
10253 opacity: 0.9
10254 },
10255
10256 onAdd: function (map) {
10257 DivOverlay.prototype.onAdd.call(this, map);
10258 this.setOpacity(this.options.opacity);
10259
10260 // @namespace Map
10261 // @section Tooltip events
10262 // @event tooltipopen: TooltipEvent
10263 // Fired when a tooltip is opened in the map.
10264 map.fire('tooltipopen', {tooltip: this});
10265
10266 if (this._source) {
10267 // @namespace Layer
10268 // @section Tooltip events
10269 // @event tooltipopen: TooltipEvent
10270 // Fired when a tooltip bound to this layer is opened.
10271 this._source.fire('tooltipopen', {tooltip: this}, true);
10272 }
10273 },
10274
10275 onRemove: function (map) {
10276 DivOverlay.prototype.onRemove.call(this, map);
10277
10278 // @namespace Map
10279 // @section Tooltip events
10280 // @event tooltipclose: TooltipEvent
10281 // Fired when a tooltip in the map is closed.
10282 map.fire('tooltipclose', {tooltip: this});
10283
10284 if (this._source) {
10285 // @namespace Layer
10286 // @section Tooltip events
10287 // @event tooltipclose: TooltipEvent
10288 // Fired when a tooltip bound to this layer is closed.
10289 this._source.fire('tooltipclose', {tooltip: this}, true);
10290 }
10291 },
10292
10293 getEvents: function () {
10294 var events = DivOverlay.prototype.getEvents.call(this);
10295
10296 if (touch && !this.options.permanent) {
10297 events.preclick = this._close;
10298 }
10299
10300 return events;
10301 },
10302
10303 _close: function () {
10304 if (this._map) {
10305 this._map.closeTooltip(this);
10306 }
10307 },
10308
10309 _initLayout: function () {
10310 var prefix = 'leaflet-tooltip',
10311 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
10312
10313 this._contentNode = this._container = create$1('div', className);
10314 },
10315
10316 _updateLayout: function () {},
10317
10318 _adjustPan: function () {},
10319
10320 _setPosition: function (pos) {
10321 var map = this._map,
10322 container = this._container,
10323 centerPoint = map.latLngToContainerPoint(map.getCenter()),
10324 tooltipPoint = map.layerPointToContainerPoint(pos),
10325 direction = this.options.direction,
10326 tooltipWidth = container.offsetWidth,
10327 tooltipHeight = container.offsetHeight,
10328 offset = toPoint(this.options.offset),
10329 anchor = this._getAnchor();
10330
10331 if (direction === 'top') {
10332 pos = pos.add(toPoint(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true));
10333 } else if (direction === 'bottom') {
10334 pos = pos.subtract(toPoint(tooltipWidth / 2 - offset.x, -offset.y, true));
10335 } else if (direction === 'center') {
10336 pos = pos.subtract(toPoint(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true));
10337 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
10338 direction = 'right';
10339 pos = pos.add(toPoint(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true));
10340 } else {
10341 direction = 'left';
10342 pos = pos.subtract(toPoint(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true));
10343 }
10344
10345 removeClass(container, 'leaflet-tooltip-right');
10346 removeClass(container, 'leaflet-tooltip-left');
10347 removeClass(container, 'leaflet-tooltip-top');
10348 removeClass(container, 'leaflet-tooltip-bottom');
10349 addClass(container, 'leaflet-tooltip-' + direction);
10350 setPosition(container, pos);
10351 },
10352
10353 _updatePosition: function () {
10354 var pos = this._map.latLngToLayerPoint(this._latlng);
10355 this._setPosition(pos);
10356 },
10357
10358 setOpacity: function (opacity) {
10359 this.options.opacity = opacity;
10360
10361 if (this._container) {
10362 setOpacity(this._container, opacity);
10363 }
10364 },
10365
10366 _animateZoom: function (e) {
10367 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
10368 this._setPosition(pos);
10369 },
10370
10371 _getAnchor: function () {
10372 // Where should we anchor the tooltip on the source layer?
10373 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
10374 }
10375
10376});
10377
10378// @namespace Tooltip
10379// @factory L.tooltip(options?: Tooltip options, source?: Layer)
10380// 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.
10381var tooltip = function (options, source) {
10382 return new Tooltip(options, source);
10383};
10384
10385// @namespace Map
10386// @section Methods for Layers and Controls
10387Map.include({
10388
10389 // @method openTooltip(tooltip: Tooltip): this
10390 // Opens the specified tooltip.
10391 // @alternative
10392 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
10393 // Creates a tooltip with the specified content and options and open it.
10394 openTooltip: function (tooltip, latlng, options) {
10395 if (!(tooltip instanceof Tooltip)) {
10396 tooltip = new Tooltip(options).setContent(tooltip);
10397 }
10398
10399 if (latlng) {
10400 tooltip.setLatLng(latlng);
10401 }
10402
10403 if (this.hasLayer(tooltip)) {
10404 return this;
10405 }
10406
10407 return this.addLayer(tooltip);
10408 },
10409
10410 // @method closeTooltip(tooltip?: Tooltip): this
10411 // Closes the tooltip given as parameter.
10412 closeTooltip: function (tooltip) {
10413 if (tooltip) {
10414 this.removeLayer(tooltip);
10415 }
10416 return this;
10417 }
10418
10419});
10420
10421/*
10422 * @namespace Layer
10423 * @section Tooltip methods example
10424 *
10425 * All layers share a set of methods convenient for binding tooltips to it.
10426 *
10427 * ```js
10428 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
10429 * layer.openTooltip();
10430 * layer.closeTooltip();
10431 * ```
10432 */
10433
10434// @section Tooltip methods
10435Layer.include({
10436
10437 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
10438 // Binds a tooltip to the layer with the passed `content` and sets up the
10439 // necessary event listeners. If a `Function` is passed it will receive
10440 // the layer as the first argument and should return a `String` or `HTMLElement`.
10441 bindTooltip: function (content, options) {
10442
10443 if (content instanceof Tooltip) {
10444 setOptions(content, options);
10445 this._tooltip = content;
10446 content._source = this;
10447 } else {
10448 if (!this._tooltip || options) {
10449 this._tooltip = new Tooltip(options, this);
10450 }
10451 this._tooltip.setContent(content);
10452
10453 }
10454
10455 this._initTooltipInteractions();
10456
10457 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
10458 this.openTooltip();
10459 }
10460
10461 return this;
10462 },
10463
10464 // @method unbindTooltip(): this
10465 // Removes the tooltip previously bound with `bindTooltip`.
10466 unbindTooltip: function () {
10467 if (this._tooltip) {
10468 this._initTooltipInteractions(true);
10469 this.closeTooltip();
10470 this._tooltip = null;
10471 }
10472 return this;
10473 },
10474
10475 _initTooltipInteractions: function (remove$$1) {
10476 if (!remove$$1 && this._tooltipHandlersAdded) { return; }
10477 var onOff = remove$$1 ? 'off' : 'on',
10478 events = {
10479 remove: this.closeTooltip,
10480 move: this._moveTooltip
10481 };
10482 if (!this._tooltip.options.permanent) {
10483 events.mouseover = this._openTooltip;
10484 events.mouseout = this.closeTooltip;
10485 if (this._tooltip.options.sticky) {
10486 events.mousemove = this._moveTooltip;
10487 }
10488 if (touch) {
10489 events.click = this._openTooltip;
10490 }
10491 } else {
10492 events.add = this._openTooltip;
10493 }
10494 this[onOff](events);
10495 this._tooltipHandlersAdded = !remove$$1;
10496 },
10497
10498 // @method openTooltip(latlng?: LatLng): this
10499 // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
10500 openTooltip: function (layer, latlng) {
10501 if (this._tooltip && this._map) {
10502 latlng = this._tooltip._prepareOpen(this, layer, latlng);
10503
10504 // open the tooltip on the map
10505 this._map.openTooltip(this._tooltip, latlng);
10506
10507 // Tooltip container may not be defined if not permanent and never
10508 // opened.
10509 if (this._tooltip.options.interactive && this._tooltip._container) {
10510 addClass(this._tooltip._container, 'leaflet-clickable');
10511 this.addInteractiveTarget(this._tooltip._container);
10512 }
10513 }
10514
10515 return this;
10516 },
10517
10518 // @method closeTooltip(): this
10519 // Closes the tooltip bound to this layer if it is open.
10520 closeTooltip: function () {
10521 if (this._tooltip) {
10522 this._tooltip._close();
10523 if (this._tooltip.options.interactive && this._tooltip._container) {
10524 removeClass(this._tooltip._container, 'leaflet-clickable');
10525 this.removeInteractiveTarget(this._tooltip._container);
10526 }
10527 }
10528 return this;
10529 },
10530
10531 // @method toggleTooltip(): this
10532 // Opens or closes the tooltip bound to this layer depending on its current state.
10533 toggleTooltip: function (target) {
10534 if (this._tooltip) {
10535 if (this._tooltip._map) {
10536 this.closeTooltip();
10537 } else {
10538 this.openTooltip(target);
10539 }
10540 }
10541 return this;
10542 },
10543
10544 // @method isTooltipOpen(): boolean
10545 // Returns `true` if the tooltip bound to this layer is currently open.
10546 isTooltipOpen: function () {
10547 return this._tooltip.isOpen();
10548 },
10549
10550 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10551 // Sets the content of the tooltip bound to this layer.
10552 setTooltipContent: function (content) {
10553 if (this._tooltip) {
10554 this._tooltip.setContent(content);
10555 }
10556 return this;
10557 },
10558
10559 // @method getTooltip(): Tooltip
10560 // Returns the tooltip bound to this layer.
10561 getTooltip: function () {
10562 return this._tooltip;
10563 },
10564
10565 _openTooltip: function (e) {
10566 var layer = e.layer || e.target;
10567
10568 if (!this._tooltip || !this._map) {
10569 return;
10570 }
10571 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
10572 },
10573
10574 _moveTooltip: function (e) {
10575 var latlng = e.latlng, containerPoint, layerPoint;
10576 if (this._tooltip.options.sticky && e.originalEvent) {
10577 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
10578 layerPoint = this._map.containerPointToLayerPoint(containerPoint);
10579 latlng = this._map.layerPointToLatLng(layerPoint);
10580 }
10581 this._tooltip.setLatLng(latlng);
10582 }
10583});
10584
10585/*
10586 * @class DivIcon
10587 * @aka L.DivIcon
10588 * @inherits Icon
10589 *
10590 * Represents a lightweight icon for markers that uses a simple `<div>`
10591 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
10592 *
10593 * @example
10594 * ```js
10595 * var myIcon = L.divIcon({className: 'my-div-icon'});
10596 * // you can set .my-div-icon styles in CSS
10597 *
10598 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
10599 * ```
10600 *
10601 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
10602 */
10603
10604var DivIcon = Icon.extend({
10605 options: {
10606 // @section
10607 // @aka DivIcon options
10608 iconSize: [12, 12], // also can be set through CSS
10609
10610 // iconAnchor: (Point),
10611 // popupAnchor: (Point),
10612
10613 // @option html: String|HTMLElement = ''
10614 // Custom HTML code to put inside the div element, empty by default. Alternatively,
10615 // an instance of `HTMLElement`.
10616 html: false,
10617
10618 // @option bgPos: Point = [0, 0]
10619 // Optional relative position of the background, in pixels
10620 bgPos: null,
10621
10622 className: 'leaflet-div-icon'
10623 },
10624
10625 createIcon: function (oldIcon) {
10626 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
10627 options = this.options;
10628
10629 if (options.html instanceof Element) {
10630 empty(div);
10631 div.appendChild(options.html);
10632 } else {
10633 div.innerHTML = options.html !== false ? options.html : '';
10634 }
10635
10636 if (options.bgPos) {
10637 var bgPos = toPoint(options.bgPos);
10638 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
10639 }
10640 this._setIconStyles(div, 'icon');
10641
10642 return div;
10643 },
10644
10645 createShadow: function () {
10646 return null;
10647 }
10648});
10649
10650// @factory L.divIcon(options: DivIcon options)
10651// Creates a `DivIcon` instance with the given options.
10652function divIcon(options) {
10653 return new DivIcon(options);
10654}
10655
10656Icon.Default = IconDefault;
10657
10658/*
10659 * @class GridLayer
10660 * @inherits Layer
10661 * @aka L.GridLayer
10662 *
10663 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
10664 * 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.
10665 *
10666 *
10667 * @section Synchronous usage
10668 * @example
10669 *
10670 * 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.
10671 *
10672 * ```js
10673 * var CanvasLayer = L.GridLayer.extend({
10674 * createTile: function(coords){
10675 * // create a <canvas> element for drawing
10676 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10677 *
10678 * // setup tile width and height according to the options
10679 * var size = this.getTileSize();
10680 * tile.width = size.x;
10681 * tile.height = size.y;
10682 *
10683 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z
10684 * var ctx = tile.getContext('2d');
10685 *
10686 * // return the tile so it can be rendered on screen
10687 * return tile;
10688 * }
10689 * });
10690 * ```
10691 *
10692 * @section Asynchronous usage
10693 * @example
10694 *
10695 * 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.
10696 *
10697 * ```js
10698 * var CanvasLayer = L.GridLayer.extend({
10699 * createTile: function(coords, done){
10700 * var error;
10701 *
10702 * // create a <canvas> element for drawing
10703 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10704 *
10705 * // setup tile width and height according to the options
10706 * var size = this.getTileSize();
10707 * tile.width = size.x;
10708 * tile.height = size.y;
10709 *
10710 * // draw something asynchronously and pass the tile to the done() callback
10711 * setTimeout(function() {
10712 * done(error, tile);
10713 * }, 1000);
10714 *
10715 * return tile;
10716 * }
10717 * });
10718 * ```
10719 *
10720 * @section
10721 */
10722
10723
10724var GridLayer = Layer.extend({
10725
10726 // @section
10727 // @aka GridLayer options
10728 options: {
10729 // @option tileSize: Number|Point = 256
10730 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
10731 tileSize: 256,
10732
10733 // @option opacity: Number = 1.0
10734 // Opacity of the tiles. Can be used in the `createTile()` function.
10735 opacity: 1,
10736
10737 // @option updateWhenIdle: Boolean = (depends)
10738 // Load new tiles only when panning ends.
10739 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
10740 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
10741 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
10742 updateWhenIdle: mobile,
10743
10744 // @option updateWhenZooming: Boolean = true
10745 // 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.
10746 updateWhenZooming: true,
10747
10748 // @option updateInterval: Number = 200
10749 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
10750 updateInterval: 200,
10751
10752 // @option zIndex: Number = 1
10753 // The explicit zIndex of the tile layer.
10754 zIndex: 1,
10755
10756 // @option bounds: LatLngBounds = undefined
10757 // If set, tiles will only be loaded inside the set `LatLngBounds`.
10758 bounds: null,
10759
10760 // @option minZoom: Number = 0
10761 // The minimum zoom level down to which this layer will be displayed (inclusive).
10762 minZoom: 0,
10763
10764 // @option maxZoom: Number = undefined
10765 // The maximum zoom level up to which this layer will be displayed (inclusive).
10766 maxZoom: undefined,
10767
10768 // @option maxNativeZoom: Number = undefined
10769 // Maximum zoom number the tile source has available. If it is specified,
10770 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
10771 // from `maxNativeZoom` level and auto-scaled.
10772 maxNativeZoom: undefined,
10773
10774 // @option minNativeZoom: Number = undefined
10775 // Minimum zoom number the tile source has available. If it is specified,
10776 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
10777 // from `minNativeZoom` level and auto-scaled.
10778 minNativeZoom: undefined,
10779
10780 // @option noWrap: Boolean = false
10781 // Whether the layer is wrapped around the antimeridian. If `true`, the
10782 // GridLayer will only be displayed once at low zoom levels. Has no
10783 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
10784 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
10785 // tiles outside the CRS limits.
10786 noWrap: false,
10787
10788 // @option pane: String = 'tilePane'
10789 // `Map pane` where the grid layer will be added.
10790 pane: 'tilePane',
10791
10792 // @option className: String = ''
10793 // A custom class name to assign to the tile layer. Empty by default.
10794 className: '',
10795
10796 // @option keepBuffer: Number = 2
10797 // When panning the map, keep this many rows and columns of tiles before unloading them.
10798 keepBuffer: 2
10799 },
10800
10801 initialize: function (options) {
10802 setOptions(this, options);
10803 },
10804
10805 onAdd: function () {
10806 this._initContainer();
10807
10808 this._levels = {};
10809 this._tiles = {};
10810
10811 this._resetView();
10812 this._update();
10813 },
10814
10815 beforeAdd: function (map) {
10816 map._addZoomLimit(this);
10817 },
10818
10819 onRemove: function (map) {
10820 this._removeAllTiles();
10821 remove(this._container);
10822 map._removeZoomLimit(this);
10823 this._container = null;
10824 this._tileZoom = undefined;
10825 },
10826
10827 // @method bringToFront: this
10828 // Brings the tile layer to the top of all tile layers.
10829 bringToFront: function () {
10830 if (this._map) {
10831 toFront(this._container);
10832 this._setAutoZIndex(Math.max);
10833 }
10834 return this;
10835 },
10836
10837 // @method bringToBack: this
10838 // Brings the tile layer to the bottom of all tile layers.
10839 bringToBack: function () {
10840 if (this._map) {
10841 toBack(this._container);
10842 this._setAutoZIndex(Math.min);
10843 }
10844 return this;
10845 },
10846
10847 // @method getContainer: HTMLElement
10848 // Returns the HTML element that contains the tiles for this layer.
10849 getContainer: function () {
10850 return this._container;
10851 },
10852
10853 // @method setOpacity(opacity: Number): this
10854 // Changes the [opacity](#gridlayer-opacity) of the grid layer.
10855 setOpacity: function (opacity) {
10856 this.options.opacity = opacity;
10857 this._updateOpacity();
10858 return this;
10859 },
10860
10861 // @method setZIndex(zIndex: Number): this
10862 // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
10863 setZIndex: function (zIndex) {
10864 this.options.zIndex = zIndex;
10865 this._updateZIndex();
10866
10867 return this;
10868 },
10869
10870 // @method isLoading: Boolean
10871 // Returns `true` if any tile in the grid layer has not finished loading.
10872 isLoading: function () {
10873 return this._loading;
10874 },
10875
10876 // @method redraw: this
10877 // Causes the layer to clear all the tiles and request them again.
10878 redraw: function () {
10879 if (this._map) {
10880 this._removeAllTiles();
10881 this._update();
10882 }
10883 return this;
10884 },
10885
10886 getEvents: function () {
10887 var events = {
10888 viewprereset: this._invalidateAll,
10889 viewreset: this._resetView,
10890 zoom: this._resetView,
10891 moveend: this._onMoveEnd
10892 };
10893
10894 if (!this.options.updateWhenIdle) {
10895 // update tiles on move, but not more often than once per given interval
10896 if (!this._onMove) {
10897 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
10898 }
10899
10900 events.move = this._onMove;
10901 }
10902
10903 if (this._zoomAnimated) {
10904 events.zoomanim = this._animateZoom;
10905 }
10906
10907 return events;
10908 },
10909
10910 // @section Extension methods
10911 // Layers extending `GridLayer` shall reimplement the following method.
10912 // @method createTile(coords: Object, done?: Function): HTMLElement
10913 // Called only internally, must be overridden by classes extending `GridLayer`.
10914 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
10915 // is specified, it must be called when the tile has finished loading and drawing.
10916 createTile: function () {
10917 return document.createElement('div');
10918 },
10919
10920 // @section
10921 // @method getTileSize: Point
10922 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
10923 getTileSize: function () {
10924 var s = this.options.tileSize;
10925 return s instanceof Point ? s : new Point(s, s);
10926 },
10927
10928 _updateZIndex: function () {
10929 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
10930 this._container.style.zIndex = this.options.zIndex;
10931 }
10932 },
10933
10934 _setAutoZIndex: function (compare) {
10935 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
10936
10937 var layers = this.getPane().children,
10938 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
10939
10940 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
10941
10942 zIndex = layers[i].style.zIndex;
10943
10944 if (layers[i] !== this._container && zIndex) {
10945 edgeZIndex = compare(edgeZIndex, +zIndex);
10946 }
10947 }
10948
10949 if (isFinite(edgeZIndex)) {
10950 this.options.zIndex = edgeZIndex + compare(-1, 1);
10951 this._updateZIndex();
10952 }
10953 },
10954
10955 _updateOpacity: function () {
10956 if (!this._map) { return; }
10957
10958 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
10959 if (ielt9) { return; }
10960
10961 setOpacity(this._container, this.options.opacity);
10962
10963 var now = +new Date(),
10964 nextFrame = false,
10965 willPrune = false;
10966
10967 for (var key in this._tiles) {
10968 var tile = this._tiles[key];
10969 if (!tile.current || !tile.loaded) { continue; }
10970
10971 var fade = Math.min(1, (now - tile.loaded) / 200);
10972
10973 setOpacity(tile.el, fade);
10974 if (fade < 1) {
10975 nextFrame = true;
10976 } else {
10977 if (tile.active) {
10978 willPrune = true;
10979 } else {
10980 this._onOpaqueTile(tile);
10981 }
10982 tile.active = true;
10983 }
10984 }
10985
10986 if (willPrune && !this._noPrune) { this._pruneTiles(); }
10987
10988 if (nextFrame) {
10989 cancelAnimFrame(this._fadeFrame);
10990 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
10991 }
10992 },
10993
10994 _onOpaqueTile: falseFn,
10995
10996 _initContainer: function () {
10997 if (this._container) { return; }
10998
10999 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
11000 this._updateZIndex();
11001
11002 if (this.options.opacity < 1) {
11003 this._updateOpacity();
11004 }
11005
11006 this.getPane().appendChild(this._container);
11007 },
11008
11009 _updateLevels: function () {
11010
11011 var zoom = this._tileZoom,
11012 maxZoom = this.options.maxZoom;
11013
11014 if (zoom === undefined) { return undefined; }
11015
11016 for (var z in this._levels) {
11017 if (this._levels[z].el.children.length || z === zoom) {
11018 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
11019 this._onUpdateLevel(z);
11020 } else {
11021 remove(this._levels[z].el);
11022 this._removeTilesAtZoom(z);
11023 this._onRemoveLevel(z);
11024 delete this._levels[z];
11025 }
11026 }
11027
11028 var level = this._levels[zoom],
11029 map = this._map;
11030
11031 if (!level) {
11032 level = this._levels[zoom] = {};
11033
11034 level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
11035 level.el.style.zIndex = maxZoom;
11036
11037 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
11038 level.zoom = zoom;
11039
11040 this._setZoomTransform(level, map.getCenter(), map.getZoom());
11041
11042 // force the browser to consider the newly added element for transition
11043 falseFn(level.el.offsetWidth);
11044
11045 this._onCreateLevel(level);
11046 }
11047
11048 this._level = level;
11049
11050 return level;
11051 },
11052
11053 _onUpdateLevel: falseFn,
11054
11055 _onRemoveLevel: falseFn,
11056
11057 _onCreateLevel: falseFn,
11058
11059 _pruneTiles: function () {
11060 if (!this._map) {
11061 return;
11062 }
11063
11064 var key, tile;
11065
11066 var zoom = this._map.getZoom();
11067 if (zoom > this.options.maxZoom ||
11068 zoom < this.options.minZoom) {
11069 this._removeAllTiles();
11070 return;
11071 }
11072
11073 for (key in this._tiles) {
11074 tile = this._tiles[key];
11075 tile.retain = tile.current;
11076 }
11077
11078 for (key in this._tiles) {
11079 tile = this._tiles[key];
11080 if (tile.current && !tile.active) {
11081 var coords = tile.coords;
11082 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
11083 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
11084 }
11085 }
11086 }
11087
11088 for (key in this._tiles) {
11089 if (!this._tiles[key].retain) {
11090 this._removeTile(key);
11091 }
11092 }
11093 },
11094
11095 _removeTilesAtZoom: function (zoom) {
11096 for (var key in this._tiles) {
11097 if (this._tiles[key].coords.z !== zoom) {
11098 continue;
11099 }
11100 this._removeTile(key);
11101 }
11102 },
11103
11104 _removeAllTiles: function () {
11105 for (var key in this._tiles) {
11106 this._removeTile(key);
11107 }
11108 },
11109
11110 _invalidateAll: function () {
11111 for (var z in this._levels) {
11112 remove(this._levels[z].el);
11113 this._onRemoveLevel(z);
11114 delete this._levels[z];
11115 }
11116 this._removeAllTiles();
11117
11118 this._tileZoom = undefined;
11119 },
11120
11121 _retainParent: function (x, y, z, minZoom) {
11122 var x2 = Math.floor(x / 2),
11123 y2 = Math.floor(y / 2),
11124 z2 = z - 1,
11125 coords2 = new Point(+x2, +y2);
11126 coords2.z = +z2;
11127
11128 var key = this._tileCoordsToKey(coords2),
11129 tile = this._tiles[key];
11130
11131 if (tile && tile.active) {
11132 tile.retain = true;
11133 return true;
11134
11135 } else if (tile && tile.loaded) {
11136 tile.retain = true;
11137 }
11138
11139 if (z2 > minZoom) {
11140 return this._retainParent(x2, y2, z2, minZoom);
11141 }
11142
11143 return false;
11144 },
11145
11146 _retainChildren: function (x, y, z, maxZoom) {
11147
11148 for (var i = 2 * x; i < 2 * x + 2; i++) {
11149 for (var j = 2 * y; j < 2 * y + 2; j++) {
11150
11151 var coords = new Point(i, j);
11152 coords.z = z + 1;
11153
11154 var key = this._tileCoordsToKey(coords),
11155 tile = this._tiles[key];
11156
11157 if (tile && tile.active) {
11158 tile.retain = true;
11159 continue;
11160
11161 } else if (tile && tile.loaded) {
11162 tile.retain = true;
11163 }
11164
11165 if (z + 1 < maxZoom) {
11166 this._retainChildren(i, j, z + 1, maxZoom);
11167 }
11168 }
11169 }
11170 },
11171
11172 _resetView: function (e) {
11173 var animating = e && (e.pinch || e.flyTo);
11174 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
11175 },
11176
11177 _animateZoom: function (e) {
11178 this._setView(e.center, e.zoom, true, e.noUpdate);
11179 },
11180
11181 _clampZoom: function (zoom) {
11182 var options = this.options;
11183
11184 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
11185 return options.minNativeZoom;
11186 }
11187
11188 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
11189 return options.maxNativeZoom;
11190 }
11191
11192 return zoom;
11193 },
11194
11195 _setView: function (center, zoom, noPrune, noUpdate) {
11196 var tileZoom = this._clampZoom(Math.round(zoom));
11197 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
11198 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
11199 tileZoom = undefined;
11200 }
11201
11202 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
11203
11204 if (!noUpdate || tileZoomChanged) {
11205
11206 this._tileZoom = tileZoom;
11207
11208 if (this._abortLoading) {
11209 this._abortLoading();
11210 }
11211
11212 this._updateLevels();
11213 this._resetGrid();
11214
11215 if (tileZoom !== undefined) {
11216 this._update(center);
11217 }
11218
11219 if (!noPrune) {
11220 this._pruneTiles();
11221 }
11222
11223 // Flag to prevent _updateOpacity from pruning tiles during
11224 // a zoom anim or a pinch gesture
11225 this._noPrune = !!noPrune;
11226 }
11227
11228 this._setZoomTransforms(center, zoom);
11229 },
11230
11231 _setZoomTransforms: function (center, zoom) {
11232 for (var i in this._levels) {
11233 this._setZoomTransform(this._levels[i], center, zoom);
11234 }
11235 },
11236
11237 _setZoomTransform: function (level, center, zoom) {
11238 var scale = this._map.getZoomScale(zoom, level.zoom),
11239 translate = level.origin.multiplyBy(scale)
11240 .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
11241
11242 if (any3d) {
11243 setTransform(level.el, translate, scale);
11244 } else {
11245 setPosition(level.el, translate);
11246 }
11247 },
11248
11249 _resetGrid: function () {
11250 var map = this._map,
11251 crs = map.options.crs,
11252 tileSize = this._tileSize = this.getTileSize(),
11253 tileZoom = this._tileZoom;
11254
11255 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
11256 if (bounds) {
11257 this._globalTileRange = this._pxBoundsToTileRange(bounds);
11258 }
11259
11260 this._wrapX = crs.wrapLng && !this.options.noWrap && [
11261 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
11262 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
11263 ];
11264 this._wrapY = crs.wrapLat && !this.options.noWrap && [
11265 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
11266 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
11267 ];
11268 },
11269
11270 _onMoveEnd: function () {
11271 if (!this._map || this._map._animatingZoom) { return; }
11272
11273 this._update();
11274 },
11275
11276 _getTiledPixelBounds: function (center) {
11277 var map = this._map,
11278 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
11279 scale = map.getZoomScale(mapZoom, this._tileZoom),
11280 pixelCenter = map.project(center, this._tileZoom).floor(),
11281 halfSize = map.getSize().divideBy(scale * 2);
11282
11283 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
11284 },
11285
11286 // Private method to load tiles in the grid's active zoom level according to map bounds
11287 _update: function (center) {
11288 var map = this._map;
11289 if (!map) { return; }
11290 var zoom = this._clampZoom(map.getZoom());
11291
11292 if (center === undefined) { center = map.getCenter(); }
11293 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom
11294
11295 var pixelBounds = this._getTiledPixelBounds(center),
11296 tileRange = this._pxBoundsToTileRange(pixelBounds),
11297 tileCenter = tileRange.getCenter(),
11298 queue = [],
11299 margin = this.options.keepBuffer,
11300 noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
11301 tileRange.getTopRight().add([margin, -margin]));
11302
11303 // Sanity check: panic if the tile range contains Infinity somewhere.
11304 if (!(isFinite(tileRange.min.x) &&
11305 isFinite(tileRange.min.y) &&
11306 isFinite(tileRange.max.x) &&
11307 isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
11308
11309 for (var key in this._tiles) {
11310 var c = this._tiles[key].coords;
11311 if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
11312 this._tiles[key].current = false;
11313 }
11314 }
11315
11316 // _update just loads more tiles. If the tile zoom level differs too much
11317 // from the map's, let _setView reset levels and prune old tiles.
11318 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
11319
11320 // create a queue of coordinates to load tiles from
11321 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
11322 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
11323 var coords = new Point(i, j);
11324 coords.z = this._tileZoom;
11325
11326 if (!this._isValidTile(coords)) { continue; }
11327
11328 var tile = this._tiles[this._tileCoordsToKey(coords)];
11329 if (tile) {
11330 tile.current = true;
11331 } else {
11332 queue.push(coords);
11333 }
11334 }
11335 }
11336
11337 // sort tile queue to load tiles in order of their distance to center
11338 queue.sort(function (a, b) {
11339 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
11340 });
11341
11342 if (queue.length !== 0) {
11343 // if it's the first batch of tiles to load
11344 if (!this._loading) {
11345 this._loading = true;
11346 // @event loading: Event
11347 // Fired when the grid layer starts loading tiles.
11348 this.fire('loading');
11349 }
11350
11351 // create DOM fragment to append tiles in one batch
11352 var fragment = document.createDocumentFragment();
11353
11354 for (i = 0; i < queue.length; i++) {
11355 this._addTile(queue[i], fragment);
11356 }
11357
11358 this._level.el.appendChild(fragment);
11359 }
11360 },
11361
11362 _isValidTile: function (coords) {
11363 var crs = this._map.options.crs;
11364
11365 if (!crs.infinite) {
11366 // don't load tile if it's out of bounds and not wrapped
11367 var bounds = this._globalTileRange;
11368 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
11369 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
11370 }
11371
11372 if (!this.options.bounds) { return true; }
11373
11374 // don't load tile if it doesn't intersect the bounds in options
11375 var tileBounds = this._tileCoordsToBounds(coords);
11376 return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
11377 },
11378
11379 _keyToBounds: function (key) {
11380 return this._tileCoordsToBounds(this._keyToTileCoords(key));
11381 },
11382
11383 _tileCoordsToNwSe: function (coords) {
11384 var map = this._map,
11385 tileSize = this.getTileSize(),
11386 nwPoint = coords.scaleBy(tileSize),
11387 sePoint = nwPoint.add(tileSize),
11388 nw = map.unproject(nwPoint, coords.z),
11389 se = map.unproject(sePoint, coords.z);
11390 return [nw, se];
11391 },
11392
11393 // converts tile coordinates to its geographical bounds
11394 _tileCoordsToBounds: function (coords) {
11395 var bp = this._tileCoordsToNwSe(coords),
11396 bounds = new LatLngBounds(bp[0], bp[1]);
11397
11398 if (!this.options.noWrap) {
11399 bounds = this._map.wrapLatLngBounds(bounds);
11400 }
11401 return bounds;
11402 },
11403 // converts tile coordinates to key for the tile cache
11404 _tileCoordsToKey: function (coords) {
11405 return coords.x + ':' + coords.y + ':' + coords.z;
11406 },
11407
11408 // converts tile cache key to coordinates
11409 _keyToTileCoords: function (key) {
11410 var k = key.split(':'),
11411 coords = new Point(+k[0], +k[1]);
11412 coords.z = +k[2];
11413 return coords;
11414 },
11415
11416 _removeTile: function (key) {
11417 var tile = this._tiles[key];
11418 if (!tile) { return; }
11419
11420 remove(tile.el);
11421
11422 delete this._tiles[key];
11423
11424 // @event tileunload: TileEvent
11425 // Fired when a tile is removed (e.g. when a tile goes off the screen).
11426 this.fire('tileunload', {
11427 tile: tile.el,
11428 coords: this._keyToTileCoords(key)
11429 });
11430 },
11431
11432 _initTile: function (tile) {
11433 addClass(tile, 'leaflet-tile');
11434
11435 var tileSize = this.getTileSize();
11436 tile.style.width = tileSize.x + 'px';
11437 tile.style.height = tileSize.y + 'px';
11438
11439 tile.onselectstart = falseFn;
11440 tile.onmousemove = falseFn;
11441
11442 // update opacity on tiles in IE7-8 because of filter inheritance problems
11443 if (ielt9 && this.options.opacity < 1) {
11444 setOpacity(tile, this.options.opacity);
11445 }
11446
11447 // without this hack, tiles disappear after zoom on Chrome for Android
11448 // https://github.com/Leaflet/Leaflet/issues/2078
11449 if (android && !android23) {
11450 tile.style.WebkitBackfaceVisibility = 'hidden';
11451 }
11452 },
11453
11454 _addTile: function (coords, container) {
11455 var tilePos = this._getTilePos(coords),
11456 key = this._tileCoordsToKey(coords);
11457
11458 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
11459
11460 this._initTile(tile);
11461
11462 // if createTile is defined with a second argument ("done" callback),
11463 // we know that tile is async and will be ready later; otherwise
11464 if (this.createTile.length < 2) {
11465 // mark tile as ready, but delay one frame for opacity animation to happen
11466 requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
11467 }
11468
11469 setPosition(tile, tilePos);
11470
11471 // save tile in cache
11472 this._tiles[key] = {
11473 el: tile,
11474 coords: coords,
11475 current: true
11476 };
11477
11478 container.appendChild(tile);
11479 // @event tileloadstart: TileEvent
11480 // Fired when a tile is requested and starts loading.
11481 this.fire('tileloadstart', {
11482 tile: tile,
11483 coords: coords
11484 });
11485 },
11486
11487 _tileReady: function (coords, err, tile) {
11488 if (err) {
11489 // @event tileerror: TileErrorEvent
11490 // Fired when there is an error loading a tile.
11491 this.fire('tileerror', {
11492 error: err,
11493 tile: tile,
11494 coords: coords
11495 });
11496 }
11497
11498 var key = this._tileCoordsToKey(coords);
11499
11500 tile = this._tiles[key];
11501 if (!tile) { return; }
11502
11503 tile.loaded = +new Date();
11504 if (this._map._fadeAnimated) {
11505 setOpacity(tile.el, 0);
11506 cancelAnimFrame(this._fadeFrame);
11507 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11508 } else {
11509 tile.active = true;
11510 this._pruneTiles();
11511 }
11512
11513 if (!err) {
11514 addClass(tile.el, 'leaflet-tile-loaded');
11515
11516 // @event tileload: TileEvent
11517 // Fired when a tile loads.
11518 this.fire('tileload', {
11519 tile: tile.el,
11520 coords: coords
11521 });
11522 }
11523
11524 if (this._noTilesToLoad()) {
11525 this._loading = false;
11526 // @event load: Event
11527 // Fired when the grid layer loaded all visible tiles.
11528 this.fire('load');
11529
11530 if (ielt9 || !this._map._fadeAnimated) {
11531 requestAnimFrame(this._pruneTiles, this);
11532 } else {
11533 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
11534 // to trigger a pruning.
11535 setTimeout(bind(this._pruneTiles, this), 250);
11536 }
11537 }
11538 },
11539
11540 _getTilePos: function (coords) {
11541 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
11542 },
11543
11544 _wrapCoords: function (coords) {
11545 var newCoords = new Point(
11546 this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
11547 this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
11548 newCoords.z = coords.z;
11549 return newCoords;
11550 },
11551
11552 _pxBoundsToTileRange: function (bounds) {
11553 var tileSize = this.getTileSize();
11554 return new Bounds(
11555 bounds.min.unscaleBy(tileSize).floor(),
11556 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
11557 },
11558
11559 _noTilesToLoad: function () {
11560 for (var key in this._tiles) {
11561 if (!this._tiles[key].loaded) { return false; }
11562 }
11563 return true;
11564 }
11565});
11566
11567// @factory L.gridLayer(options?: GridLayer options)
11568// Creates a new instance of GridLayer with the supplied options.
11569function gridLayer(options) {
11570 return new GridLayer(options);
11571}
11572
11573/*
11574 * @class TileLayer
11575 * @inherits GridLayer
11576 * @aka L.TileLayer
11577 * 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`.
11578 *
11579 * @example
11580 *
11581 * ```js
11582 * 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);
11583 * ```
11584 *
11585 * @section URL template
11586 * @example
11587 *
11588 * A string of the following form:
11589 *
11590 * ```
11591 * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
11592 * ```
11593 *
11594 * `{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.
11595 *
11596 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
11597 *
11598 * ```
11599 * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
11600 * ```
11601 */
11602
11603
11604var TileLayer = GridLayer.extend({
11605
11606 // @section
11607 // @aka TileLayer options
11608 options: {
11609 // @option minZoom: Number = 0
11610 // The minimum zoom level down to which this layer will be displayed (inclusive).
11611 minZoom: 0,
11612
11613 // @option maxZoom: Number = 18
11614 // The maximum zoom level up to which this layer will be displayed (inclusive).
11615 maxZoom: 18,
11616
11617 // @option subdomains: String|String[] = 'abc'
11618 // 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.
11619 subdomains: 'abc',
11620
11621 // @option errorTileUrl: String = ''
11622 // URL to the tile image to show in place of the tile that failed to load.
11623 errorTileUrl: '',
11624
11625 // @option zoomOffset: Number = 0
11626 // The zoom number used in tile URLs will be offset with this value.
11627 zoomOffset: 0,
11628
11629 // @option tms: Boolean = false
11630 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
11631 tms: false,
11632
11633 // @option zoomReverse: Boolean = false
11634 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
11635 zoomReverse: false,
11636
11637 // @option detectRetina: Boolean = false
11638 // 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.
11639 detectRetina: false,
11640
11641 // @option crossOrigin: Boolean|String = false
11642 // Whether the crossOrigin attribute will be added to the tiles.
11643 // 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.
11644 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
11645 crossOrigin: false
11646 },
11647
11648 initialize: function (url, options) {
11649
11650 this._url = url;
11651
11652 options = setOptions(this, options);
11653
11654 // detecting retina displays, adjusting tileSize and zoom levels
11655 if (options.detectRetina && retina && options.maxZoom > 0) {
11656
11657 options.tileSize = Math.floor(options.tileSize / 2);
11658
11659 if (!options.zoomReverse) {
11660 options.zoomOffset++;
11661 options.maxZoom--;
11662 } else {
11663 options.zoomOffset--;
11664 options.minZoom++;
11665 }
11666
11667 options.minZoom = Math.max(0, options.minZoom);
11668 }
11669
11670 if (typeof options.subdomains === 'string') {
11671 options.subdomains = options.subdomains.split('');
11672 }
11673
11674 // for https://github.com/Leaflet/Leaflet/issues/137
11675 if (!android) {
11676 this.on('tileunload', this._onTileRemove);
11677 }
11678 },
11679
11680 // @method setUrl(url: String, noRedraw?: Boolean): this
11681 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
11682 // If the URL does not change, the layer will not be redrawn unless
11683 // the noRedraw parameter is set to false.
11684 setUrl: function (url, noRedraw) {
11685 if (this._url === url && noRedraw === undefined) {
11686 noRedraw = true;
11687 }
11688
11689 this._url = url;
11690
11691 if (!noRedraw) {
11692 this.redraw();
11693 }
11694 return this;
11695 },
11696
11697 // @method createTile(coords: Object, done?: Function): HTMLElement
11698 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
11699 // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
11700 // callback is called when the tile has been loaded.
11701 createTile: function (coords, done) {
11702 var tile = document.createElement('img');
11703
11704 on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
11705 on(tile, 'error', bind(this._tileOnError, this, done, tile));
11706
11707 if (this.options.crossOrigin || this.options.crossOrigin === '') {
11708 tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
11709 }
11710
11711 /*
11712 Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
11713 http://www.w3.org/TR/WCAG20-TECHS/H67
11714 */
11715 tile.alt = '';
11716
11717 /*
11718 Set role="presentation" to force screen readers to ignore this
11719 https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
11720 */
11721 tile.setAttribute('role', 'presentation');
11722
11723 tile.src = this.getTileUrl(coords);
11724
11725 return tile;
11726 },
11727
11728 // @section Extension methods
11729 // @uninheritable
11730 // Layers extending `TileLayer` might reimplement the following method.
11731 // @method getTileUrl(coords: Object): String
11732 // Called only internally, returns the URL for a tile given its coordinates.
11733 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
11734 getTileUrl: function (coords) {
11735 var data = {
11736 r: retina ? '@2x' : '',
11737 s: this._getSubdomain(coords),
11738 x: coords.x,
11739 y: coords.y,
11740 z: this._getZoomForUrl()
11741 };
11742 if (this._map && !this._map.options.crs.infinite) {
11743 var invertedY = this._globalTileRange.max.y - coords.y;
11744 if (this.options.tms) {
11745 data['y'] = invertedY;
11746 }
11747 data['-y'] = invertedY;
11748 }
11749
11750 return template(this._url, extend(data, this.options));
11751 },
11752
11753 _tileOnLoad: function (done, tile) {
11754 // For https://github.com/Leaflet/Leaflet/issues/3332
11755 if (ielt9) {
11756 setTimeout(bind(done, this, null, tile), 0);
11757 } else {
11758 done(null, tile);
11759 }
11760 },
11761
11762 _tileOnError: function (done, tile, e) {
11763 var errorUrl = this.options.errorTileUrl;
11764 if (errorUrl && tile.getAttribute('src') !== errorUrl) {
11765 tile.src = errorUrl;
11766 }
11767 done(e, tile);
11768 },
11769
11770 _onTileRemove: function (e) {
11771 e.tile.onload = null;
11772 },
11773
11774 _getZoomForUrl: function () {
11775 var zoom = this._tileZoom,
11776 maxZoom = this.options.maxZoom,
11777 zoomReverse = this.options.zoomReverse,
11778 zoomOffset = this.options.zoomOffset;
11779
11780 if (zoomReverse) {
11781 zoom = maxZoom - zoom;
11782 }
11783
11784 return zoom + zoomOffset;
11785 },
11786
11787 _getSubdomain: function (tilePoint) {
11788 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
11789 return this.options.subdomains[index];
11790 },
11791
11792 // stops loading all tiles in the background layer
11793 _abortLoading: function () {
11794 var i, tile;
11795 for (i in this._tiles) {
11796 if (this._tiles[i].coords.z !== this._tileZoom) {
11797 tile = this._tiles[i].el;
11798
11799 tile.onload = falseFn;
11800 tile.onerror = falseFn;
11801
11802 if (!tile.complete) {
11803 tile.src = emptyImageUrl;
11804 remove(tile);
11805 delete this._tiles[i];
11806 }
11807 }
11808 }
11809 },
11810
11811 _removeTile: function (key) {
11812 var tile = this._tiles[key];
11813 if (!tile) { return; }
11814
11815 // Cancels any pending http requests associated with the tile
11816 // unless we're on Android's stock browser,
11817 // see https://github.com/Leaflet/Leaflet/issues/137
11818 if (!androidStock) {
11819 tile.el.setAttribute('src', emptyImageUrl);
11820 }
11821
11822 return GridLayer.prototype._removeTile.call(this, key);
11823 },
11824
11825 _tileReady: function (coords, err, tile) {
11826 if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) {
11827 return;
11828 }
11829
11830 return GridLayer.prototype._tileReady.call(this, coords, err, tile);
11831 }
11832});
11833
11834
11835// @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
11836// Instantiates a tile layer object given a `URL template` and optionally an options object.
11837
11838function tileLayer(url, options) {
11839 return new TileLayer(url, options);
11840}
11841
11842/*
11843 * @class TileLayer.WMS
11844 * @inherits TileLayer
11845 * @aka L.TileLayer.WMS
11846 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
11847 *
11848 * @example
11849 *
11850 * ```js
11851 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
11852 * layers: 'nexrad-n0r-900913',
11853 * format: 'image/png',
11854 * transparent: true,
11855 * attribution: "Weather data © 2012 IEM Nexrad"
11856 * });
11857 * ```
11858 */
11859
11860var TileLayerWMS = TileLayer.extend({
11861
11862 // @section
11863 // @aka TileLayer.WMS options
11864 // If any custom options not documented here are used, they will be sent to the
11865 // WMS server as extra parameters in each request URL. This can be useful for
11866 // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
11867 defaultWmsParams: {
11868 service: 'WMS',
11869 request: 'GetMap',
11870
11871 // @option layers: String = ''
11872 // **(required)** Comma-separated list of WMS layers to show.
11873 layers: '',
11874
11875 // @option styles: String = ''
11876 // Comma-separated list of WMS styles.
11877 styles: '',
11878
11879 // @option format: String = 'image/jpeg'
11880 // WMS image format (use `'image/png'` for layers with transparency).
11881 format: 'image/jpeg',
11882
11883 // @option transparent: Boolean = false
11884 // If `true`, the WMS service will return images with transparency.
11885 transparent: false,
11886
11887 // @option version: String = '1.1.1'
11888 // Version of the WMS service to use
11889 version: '1.1.1'
11890 },
11891
11892 options: {
11893 // @option crs: CRS = null
11894 // Coordinate Reference System to use for the WMS requests, defaults to
11895 // map CRS. Don't change this if you're not sure what it means.
11896 crs: null,
11897
11898 // @option uppercase: Boolean = false
11899 // If `true`, WMS request parameter keys will be uppercase.
11900 uppercase: false
11901 },
11902
11903 initialize: function (url, options) {
11904
11905 this._url = url;
11906
11907 var wmsParams = extend({}, this.defaultWmsParams);
11908
11909 // all keys that are not TileLayer options go to WMS params
11910 for (var i in options) {
11911 if (!(i in this.options)) {
11912 wmsParams[i] = options[i];
11913 }
11914 }
11915
11916 options = setOptions(this, options);
11917
11918 var realRetina = options.detectRetina && retina ? 2 : 1;
11919 var tileSize = this.getTileSize();
11920 wmsParams.width = tileSize.x * realRetina;
11921 wmsParams.height = tileSize.y * realRetina;
11922
11923 this.wmsParams = wmsParams;
11924 },
11925
11926 onAdd: function (map) {
11927
11928 this._crs = this.options.crs || map.options.crs;
11929 this._wmsVersion = parseFloat(this.wmsParams.version);
11930
11931 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
11932 this.wmsParams[projectionKey] = this._crs.code;
11933
11934 TileLayer.prototype.onAdd.call(this, map);
11935 },
11936
11937 getTileUrl: function (coords) {
11938
11939 var tileBounds = this._tileCoordsToNwSe(coords),
11940 crs = this._crs,
11941 bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
11942 min = bounds.min,
11943 max = bounds.max,
11944 bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
11945 [min.y, min.x, max.y, max.x] :
11946 [min.x, min.y, max.x, max.y]).join(','),
11947 url = TileLayer.prototype.getTileUrl.call(this, coords);
11948 return url +
11949 getParamString(this.wmsParams, url, this.options.uppercase) +
11950 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
11951 },
11952
11953 // @method setParams(params: Object, noRedraw?: Boolean): this
11954 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
11955 setParams: function (params, noRedraw) {
11956
11957 extend(this.wmsParams, params);
11958
11959 if (!noRedraw) {
11960 this.redraw();
11961 }
11962
11963 return this;
11964 }
11965});
11966
11967
11968// @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
11969// Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
11970function tileLayerWMS(url, options) {
11971 return new TileLayerWMS(url, options);
11972}
11973
11974TileLayer.WMS = TileLayerWMS;
11975tileLayer.wms = tileLayerWMS;
11976
11977/*
11978 * @class Renderer
11979 * @inherits Layer
11980 * @aka L.Renderer
11981 *
11982 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
11983 * DOM container of the renderer, its bounds, and its zoom animation.
11984 *
11985 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
11986 * itself can be added or removed to the map. All paths use a renderer, which can
11987 * be implicit (the map will decide the type of renderer and use it automatically)
11988 * or explicit (using the [`renderer`](#path-renderer) option of the path).
11989 *
11990 * Do not use this class directly, use `SVG` and `Canvas` instead.
11991 *
11992 * @event update: Event
11993 * Fired when the renderer updates its bounds, center and zoom, for example when
11994 * its map has moved
11995 */
11996
11997var Renderer = Layer.extend({
11998
11999 // @section
12000 // @aka Renderer options
12001 options: {
12002 // @option padding: Number = 0.1
12003 // How much to extend the clip area around the map view (relative to its size)
12004 // e.g. 0.1 would be 10% of map view in each direction
12005 padding: 0.1,
12006
12007 // @option tolerance: Number = 0
12008 // How much to extend click tolerance round a path/object on the map
12009 tolerance : 0
12010 },
12011
12012 initialize: function (options) {
12013 setOptions(this, options);
12014 stamp(this);
12015 this._layers = this._layers || {};
12016 },
12017
12018 onAdd: function () {
12019 if (!this._container) {
12020 this._initContainer(); // defined by renderer implementations
12021
12022 if (this._zoomAnimated) {
12023 addClass(this._container, 'leaflet-zoom-animated');
12024 }
12025 }
12026
12027 this.getPane().appendChild(this._container);
12028 this._update();
12029 this.on('update', this._updatePaths, this);
12030 },
12031
12032 onRemove: function () {
12033 this.off('update', this._updatePaths, this);
12034 this._destroyContainer();
12035 },
12036
12037 getEvents: function () {
12038 var events = {
12039 viewreset: this._reset,
12040 zoom: this._onZoom,
12041 moveend: this._update,
12042 zoomend: this._onZoomEnd
12043 };
12044 if (this._zoomAnimated) {
12045 events.zoomanim = this._onAnimZoom;
12046 }
12047 return events;
12048 },
12049
12050 _onAnimZoom: function (ev) {
12051 this._updateTransform(ev.center, ev.zoom);
12052 },
12053
12054 _onZoom: function () {
12055 this._updateTransform(this._map.getCenter(), this._map.getZoom());
12056 },
12057
12058 _updateTransform: function (center, zoom) {
12059 var scale = this._map.getZoomScale(zoom, this._zoom),
12060 position = getPosition(this._container),
12061 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
12062 currentCenterPoint = this._map.project(this._center, zoom),
12063 destCenterPoint = this._map.project(center, zoom),
12064 centerOffset = destCenterPoint.subtract(currentCenterPoint),
12065
12066 topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
12067
12068 if (any3d) {
12069 setTransform(this._container, topLeftOffset, scale);
12070 } else {
12071 setPosition(this._container, topLeftOffset);
12072 }
12073 },
12074
12075 _reset: function () {
12076 this._update();
12077 this._updateTransform(this._center, this._zoom);
12078
12079 for (var id in this._layers) {
12080 this._layers[id]._reset();
12081 }
12082 },
12083
12084 _onZoomEnd: function () {
12085 for (var id in this._layers) {
12086 this._layers[id]._project();
12087 }
12088 },
12089
12090 _updatePaths: function () {
12091 for (var id in this._layers) {
12092 this._layers[id]._update();
12093 }
12094 },
12095
12096 _update: function () {
12097 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
12098 // Subclasses are responsible of firing the 'update' event.
12099 var p = this.options.padding,
12100 size = this._map.getSize(),
12101 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
12102
12103 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
12104
12105 this._center = this._map.getCenter();
12106 this._zoom = this._map.getZoom();
12107 }
12108});
12109
12110/*
12111 * @class Canvas
12112 * @inherits Renderer
12113 * @aka L.Canvas
12114 *
12115 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
12116 * Inherits `Renderer`.
12117 *
12118 * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
12119 * available in all web browsers, notably IE8, and overlapping geometries might
12120 * not display properly in some edge cases.
12121 *
12122 * @example
12123 *
12124 * Use Canvas by default for all paths in the map:
12125 *
12126 * ```js
12127 * var map = L.map('map', {
12128 * renderer: L.canvas()
12129 * });
12130 * ```
12131 *
12132 * Use a Canvas renderer with extra padding for specific vector geometries:
12133 *
12134 * ```js
12135 * var map = L.map('map');
12136 * var myRenderer = L.canvas({ padding: 0.5 });
12137 * var line = L.polyline( coordinates, { renderer: myRenderer } );
12138 * var circle = L.circle( center, { renderer: myRenderer } );
12139 * ```
12140 */
12141
12142var Canvas = Renderer.extend({
12143 getEvents: function () {
12144 var events = Renderer.prototype.getEvents.call(this);
12145 events.viewprereset = this._onViewPreReset;
12146 return events;
12147 },
12148
12149 _onViewPreReset: function () {
12150 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
12151 this._postponeUpdatePaths = true;
12152 },
12153
12154 onAdd: function () {
12155 Renderer.prototype.onAdd.call(this);
12156
12157 // Redraw vectors since canvas is cleared upon removal,
12158 // in case of removing the renderer itself from the map.
12159 this._draw();
12160 },
12161
12162 _initContainer: function () {
12163 var container = this._container = document.createElement('canvas');
12164
12165 on(container, 'mousemove', this._onMouseMove, this);
12166 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
12167 on(container, 'mouseout', this._handleMouseOut, this);
12168
12169 this._ctx = container.getContext('2d');
12170 },
12171
12172 _destroyContainer: function () {
12173 cancelAnimFrame(this._redrawRequest);
12174 delete this._ctx;
12175 remove(this._container);
12176 off(this._container);
12177 delete this._container;
12178 },
12179
12180 _updatePaths: function () {
12181 if (this._postponeUpdatePaths) { return; }
12182
12183 var layer;
12184 this._redrawBounds = null;
12185 for (var id in this._layers) {
12186 layer = this._layers[id];
12187 layer._update();
12188 }
12189 this._redraw();
12190 },
12191
12192 _update: function () {
12193 if (this._map._animatingZoom && this._bounds) { return; }
12194
12195 Renderer.prototype._update.call(this);
12196
12197 var b = this._bounds,
12198 container = this._container,
12199 size = b.getSize(),
12200 m = retina ? 2 : 1;
12201
12202 setPosition(container, b.min);
12203
12204 // set canvas size (also clearing it); use double size on retina
12205 container.width = m * size.x;
12206 container.height = m * size.y;
12207 container.style.width = size.x + 'px';
12208 container.style.height = size.y + 'px';
12209
12210 if (retina) {
12211 this._ctx.scale(2, 2);
12212 }
12213
12214 // translate so we use the same path coordinates after canvas element moves
12215 this._ctx.translate(-b.min.x, -b.min.y);
12216
12217 // Tell paths to redraw themselves
12218 this.fire('update');
12219 },
12220
12221 _reset: function () {
12222 Renderer.prototype._reset.call(this);
12223
12224 if (this._postponeUpdatePaths) {
12225 this._postponeUpdatePaths = false;
12226 this._updatePaths();
12227 }
12228 },
12229
12230 _initPath: function (layer) {
12231 this._updateDashArray(layer);
12232 this._layers[stamp(layer)] = layer;
12233
12234 var order = layer._order = {
12235 layer: layer,
12236 prev: this._drawLast,
12237 next: null
12238 };
12239 if (this._drawLast) { this._drawLast.next = order; }
12240 this._drawLast = order;
12241 this._drawFirst = this._drawFirst || this._drawLast;
12242 },
12243
12244 _addPath: function (layer) {
12245 this._requestRedraw(layer);
12246 },
12247
12248 _removePath: function (layer) {
12249 var order = layer._order;
12250 var next = order.next;
12251 var prev = order.prev;
12252
12253 if (next) {
12254 next.prev = prev;
12255 } else {
12256 this._drawLast = prev;
12257 }
12258 if (prev) {
12259 prev.next = next;
12260 } else {
12261 this._drawFirst = next;
12262 }
12263
12264 delete layer._order;
12265
12266 delete this._layers[stamp(layer)];
12267
12268 this._requestRedraw(layer);
12269 },
12270
12271 _updatePath: function (layer) {
12272 // Redraw the union of the layer's old pixel
12273 // bounds and the new pixel bounds.
12274 this._extendRedrawBounds(layer);
12275 layer._project();
12276 layer._update();
12277 // The redraw will extend the redraw bounds
12278 // with the new pixel bounds.
12279 this._requestRedraw(layer);
12280 },
12281
12282 _updateStyle: function (layer) {
12283 this._updateDashArray(layer);
12284 this._requestRedraw(layer);
12285 },
12286
12287 _updateDashArray: function (layer) {
12288 if (typeof layer.options.dashArray === 'string') {
12289 var parts = layer.options.dashArray.split(/[, ]+/),
12290 dashArray = [],
12291 dashValue,
12292 i;
12293 for (i = 0; i < parts.length; i++) {
12294 dashValue = Number(parts[i]);
12295 // Ignore dash array containing invalid lengths
12296 if (isNaN(dashValue)) { return; }
12297 dashArray.push(dashValue);
12298 }
12299 layer.options._dashArray = dashArray;
12300 } else {
12301 layer.options._dashArray = layer.options.dashArray;
12302 }
12303 },
12304
12305 _requestRedraw: function (layer) {
12306 if (!this._map) { return; }
12307
12308 this._extendRedrawBounds(layer);
12309 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
12310 },
12311
12312 _extendRedrawBounds: function (layer) {
12313 if (layer._pxBounds) {
12314 var padding = (layer.options.weight || 0) + 1;
12315 this._redrawBounds = this._redrawBounds || new Bounds();
12316 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
12317 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
12318 }
12319 },
12320
12321 _redraw: function () {
12322 this._redrawRequest = null;
12323
12324 if (this._redrawBounds) {
12325 this._redrawBounds.min._floor();
12326 this._redrawBounds.max._ceil();
12327 }
12328
12329 this._clear(); // clear layers in redraw bounds
12330 this._draw(); // draw layers
12331
12332 this._redrawBounds = null;
12333 },
12334
12335 _clear: function () {
12336 var bounds = this._redrawBounds;
12337 if (bounds) {
12338 var size = bounds.getSize();
12339 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
12340 } else {
12341 this._ctx.clearRect(0, 0, this._container.width, this._container.height);
12342 }
12343 },
12344
12345 _draw: function () {
12346 var layer, bounds = this._redrawBounds;
12347 this._ctx.save();
12348 if (bounds) {
12349 var size = bounds.getSize();
12350 this._ctx.beginPath();
12351 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
12352 this._ctx.clip();
12353 }
12354
12355 this._drawing = true;
12356
12357 for (var order = this._drawFirst; order; order = order.next) {
12358 layer = order.layer;
12359 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
12360 layer._updatePath();
12361 }
12362 }
12363
12364 this._drawing = false;
12365
12366 this._ctx.restore(); // Restore state before clipping.
12367 },
12368
12369 _updatePoly: function (layer, closed) {
12370 if (!this._drawing) { return; }
12371
12372 var i, j, len2, p,
12373 parts = layer._parts,
12374 len = parts.length,
12375 ctx = this._ctx;
12376
12377 if (!len) { return; }
12378
12379 ctx.beginPath();
12380
12381 for (i = 0; i < len; i++) {
12382 for (j = 0, len2 = parts[i].length; j < len2; j++) {
12383 p = parts[i][j];
12384 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
12385 }
12386 if (closed) {
12387 ctx.closePath();
12388 }
12389 }
12390
12391 this._fillStroke(ctx, layer);
12392
12393 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
12394 },
12395
12396 _updateCircle: function (layer) {
12397
12398 if (!this._drawing || layer._empty()) { return; }
12399
12400 var p = layer._point,
12401 ctx = this._ctx,
12402 r = Math.max(Math.round(layer._radius), 1),
12403 s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
12404
12405 if (s !== 1) {
12406 ctx.save();
12407 ctx.scale(1, s);
12408 }
12409
12410 ctx.beginPath();
12411 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
12412
12413 if (s !== 1) {
12414 ctx.restore();
12415 }
12416
12417 this._fillStroke(ctx, layer);
12418 },
12419
12420 _fillStroke: function (ctx, layer) {
12421 var options = layer.options;
12422
12423 if (options.fill) {
12424 ctx.globalAlpha = options.fillOpacity;
12425 ctx.fillStyle = options.fillColor || options.color;
12426 ctx.fill(options.fillRule || 'evenodd');
12427 }
12428
12429 if (options.stroke && options.weight !== 0) {
12430 if (ctx.setLineDash) {
12431 ctx.setLineDash(layer.options && layer.options._dashArray || []);
12432 }
12433 ctx.globalAlpha = options.opacity;
12434 ctx.lineWidth = options.weight;
12435 ctx.strokeStyle = options.color;
12436 ctx.lineCap = options.lineCap;
12437 ctx.lineJoin = options.lineJoin;
12438 ctx.stroke();
12439 }
12440 },
12441
12442 // Canvas obviously doesn't have mouse events for individual drawn objects,
12443 // so we emulate that by calculating what's under the mouse on mousemove/click manually
12444
12445 _onClick: function (e) {
12446 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
12447
12448 for (var order = this._drawFirst; order; order = order.next) {
12449 layer = order.layer;
12450 if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
12451 clickedLayer = layer;
12452 }
12453 }
12454 if (clickedLayer) {
12455 fakeStop(e);
12456 this._fireEvent([clickedLayer], e);
12457 }
12458 },
12459
12460 _onMouseMove: function (e) {
12461 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
12462
12463 var point = this._map.mouseEventToLayerPoint(e);
12464 this._handleMouseHover(e, point);
12465 },
12466
12467
12468 _handleMouseOut: function (e) {
12469 var layer = this._hoveredLayer;
12470 if (layer) {
12471 // if we're leaving the layer, fire mouseout
12472 removeClass(this._container, 'leaflet-interactive');
12473 this._fireEvent([layer], e, 'mouseout');
12474 this._hoveredLayer = null;
12475 this._mouseHoverThrottled = false;
12476 }
12477 },
12478
12479 _handleMouseHover: function (e, point) {
12480 if (this._mouseHoverThrottled) {
12481 return;
12482 }
12483
12484 var layer, candidateHoveredLayer;
12485
12486 for (var order = this._drawFirst; order; order = order.next) {
12487 layer = order.layer;
12488 if (layer.options.interactive && layer._containsPoint(point)) {
12489 candidateHoveredLayer = layer;
12490 }
12491 }
12492
12493 if (candidateHoveredLayer !== this._hoveredLayer) {
12494 this._handleMouseOut(e);
12495
12496 if (candidateHoveredLayer) {
12497 addClass(this._container, 'leaflet-interactive'); // change cursor
12498 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
12499 this._hoveredLayer = candidateHoveredLayer;
12500 }
12501 }
12502
12503 if (this._hoveredLayer) {
12504 this._fireEvent([this._hoveredLayer], e);
12505 }
12506
12507 this._mouseHoverThrottled = true;
12508 setTimeout(L.bind(function () {
12509 this._mouseHoverThrottled = false;
12510 }, this), 32);
12511 },
12512
12513 _fireEvent: function (layers, e, type) {
12514 this._map._fireDOMEvent(e, type || e.type, layers);
12515 },
12516
12517 _bringToFront: function (layer) {
12518 var order = layer._order;
12519
12520 if (!order) { return; }
12521
12522 var next = order.next;
12523 var prev = order.prev;
12524
12525 if (next) {
12526 next.prev = prev;
12527 } else {
12528 // Already last
12529 return;
12530 }
12531 if (prev) {
12532 prev.next = next;
12533 } else if (next) {
12534 // Update first entry unless this is the
12535 // single entry
12536 this._drawFirst = next;
12537 }
12538
12539 order.prev = this._drawLast;
12540 this._drawLast.next = order;
12541
12542 order.next = null;
12543 this._drawLast = order;
12544
12545 this._requestRedraw(layer);
12546 },
12547
12548 _bringToBack: function (layer) {
12549 var order = layer._order;
12550
12551 if (!order) { return; }
12552
12553 var next = order.next;
12554 var prev = order.prev;
12555
12556 if (prev) {
12557 prev.next = next;
12558 } else {
12559 // Already first
12560 return;
12561 }
12562 if (next) {
12563 next.prev = prev;
12564 } else if (prev) {
12565 // Update last entry unless this is the
12566 // single entry
12567 this._drawLast = prev;
12568 }
12569
12570 order.prev = null;
12571
12572 order.next = this._drawFirst;
12573 this._drawFirst.prev = order;
12574 this._drawFirst = order;
12575
12576 this._requestRedraw(layer);
12577 }
12578});
12579
12580// @factory L.canvas(options?: Renderer options)
12581// Creates a Canvas renderer with the given options.
12582function canvas$1(options) {
12583 return canvas ? new Canvas(options) : null;
12584}
12585
12586/*
12587 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
12588 */
12589
12590
12591var vmlCreate = (function () {
12592 try {
12593 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
12594 return function (name) {
12595 return document.createElement('<lvml:' + name + ' class="lvml">');
12596 };
12597 } catch (e) {
12598 return function (name) {
12599 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
12600 };
12601 }
12602})();
12603
12604
12605/*
12606 * @class SVG
12607 *
12608 *
12609 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
12610 * with old versions of Internet Explorer.
12611 */
12612
12613// mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
12614var vmlMixin = {
12615
12616 _initContainer: function () {
12617 this._container = create$1('div', 'leaflet-vml-container');
12618 },
12619
12620 _update: function () {
12621 if (this._map._animatingZoom) { return; }
12622 Renderer.prototype._update.call(this);
12623 this.fire('update');
12624 },
12625
12626 _initPath: function (layer) {
12627 var container = layer._container = vmlCreate('shape');
12628
12629 addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
12630
12631 container.coordsize = '1 1';
12632
12633 layer._path = vmlCreate('path');
12634 container.appendChild(layer._path);
12635
12636 this._updateStyle(layer);
12637 this._layers[stamp(layer)] = layer;
12638 },
12639
12640 _addPath: function (layer) {
12641 var container = layer._container;
12642 this._container.appendChild(container);
12643
12644 if (layer.options.interactive) {
12645 layer.addInteractiveTarget(container);
12646 }
12647 },
12648
12649 _removePath: function (layer) {
12650 var container = layer._container;
12651 remove(container);
12652 layer.removeInteractiveTarget(container);
12653 delete this._layers[stamp(layer)];
12654 },
12655
12656 _updateStyle: function (layer) {
12657 var stroke = layer._stroke,
12658 fill = layer._fill,
12659 options = layer.options,
12660 container = layer._container;
12661
12662 container.stroked = !!options.stroke;
12663 container.filled = !!options.fill;
12664
12665 if (options.stroke) {
12666 if (!stroke) {
12667 stroke = layer._stroke = vmlCreate('stroke');
12668 }
12669 container.appendChild(stroke);
12670 stroke.weight = options.weight + 'px';
12671 stroke.color = options.color;
12672 stroke.opacity = options.opacity;
12673
12674 if (options.dashArray) {
12675 stroke.dashStyle = isArray(options.dashArray) ?
12676 options.dashArray.join(' ') :
12677 options.dashArray.replace(/( *, *)/g, ' ');
12678 } else {
12679 stroke.dashStyle = '';
12680 }
12681 stroke.endcap = options.lineCap.replace('butt', 'flat');
12682 stroke.joinstyle = options.lineJoin;
12683
12684 } else if (stroke) {
12685 container.removeChild(stroke);
12686 layer._stroke = null;
12687 }
12688
12689 if (options.fill) {
12690 if (!fill) {
12691 fill = layer._fill = vmlCreate('fill');
12692 }
12693 container.appendChild(fill);
12694 fill.color = options.fillColor || options.color;
12695 fill.opacity = options.fillOpacity;
12696
12697 } else if (fill) {
12698 container.removeChild(fill);
12699 layer._fill = null;
12700 }
12701 },
12702
12703 _updateCircle: function (layer) {
12704 var p = layer._point.round(),
12705 r = Math.round(layer._radius),
12706 r2 = Math.round(layer._radiusY || r);
12707
12708 this._setPath(layer, layer._empty() ? 'M0 0' :
12709 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
12710 },
12711
12712 _setPath: function (layer, path) {
12713 layer._path.v = path;
12714 },
12715
12716 _bringToFront: function (layer) {
12717 toFront(layer._container);
12718 },
12719
12720 _bringToBack: function (layer) {
12721 toBack(layer._container);
12722 }
12723};
12724
12725var create$2 = vml ? vmlCreate : svgCreate;
12726
12727/*
12728 * @class SVG
12729 * @inherits Renderer
12730 * @aka L.SVG
12731 *
12732 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
12733 * Inherits `Renderer`.
12734 *
12735 * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
12736 * available in all web browsers, notably Android 2.x and 3.x.
12737 *
12738 * Although SVG is not available on IE7 and IE8, these browsers support
12739 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
12740 * (a now deprecated technology), and the SVG renderer will fall back to VML in
12741 * this case.
12742 *
12743 * @example
12744 *
12745 * Use SVG by default for all paths in the map:
12746 *
12747 * ```js
12748 * var map = L.map('map', {
12749 * renderer: L.svg()
12750 * });
12751 * ```
12752 *
12753 * Use a SVG renderer with extra padding for specific vector geometries:
12754 *
12755 * ```js
12756 * var map = L.map('map');
12757 * var myRenderer = L.svg({ padding: 0.5 });
12758 * var line = L.polyline( coordinates, { renderer: myRenderer } );
12759 * var circle = L.circle( center, { renderer: myRenderer } );
12760 * ```
12761 */
12762
12763var SVG = Renderer.extend({
12764
12765 getEvents: function () {
12766 var events = Renderer.prototype.getEvents.call(this);
12767 events.zoomstart = this._onZoomStart;
12768 return events;
12769 },
12770
12771 _initContainer: function () {
12772 this._container = create$2('svg');
12773
12774 // makes it possible to click through svg root; we'll reset it back in individual paths
12775 this._container.setAttribute('pointer-events', 'none');
12776
12777 this._rootGroup = create$2('g');
12778 this._container.appendChild(this._rootGroup);
12779 },
12780
12781 _destroyContainer: function () {
12782 remove(this._container);
12783 off(this._container);
12784 delete this._container;
12785 delete this._rootGroup;
12786 delete this._svgSize;
12787 },
12788
12789 _onZoomStart: function () {
12790 // Drag-then-pinch interactions might mess up the center and zoom.
12791 // In this case, the easiest way to prevent this is re-do the renderer
12792 // bounds and padding when the zooming starts.
12793 this._update();
12794 },
12795
12796 _update: function () {
12797 if (this._map._animatingZoom && this._bounds) { return; }
12798
12799 Renderer.prototype._update.call(this);
12800
12801 var b = this._bounds,
12802 size = b.getSize(),
12803 container = this._container;
12804
12805 // set size of svg-container if changed
12806 if (!this._svgSize || !this._svgSize.equals(size)) {
12807 this._svgSize = size;
12808 container.setAttribute('width', size.x);
12809 container.setAttribute('height', size.y);
12810 }
12811
12812 // movement: update container viewBox so that we don't have to change coordinates of individual layers
12813 setPosition(container, b.min);
12814 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
12815
12816 this.fire('update');
12817 },
12818
12819 // methods below are called by vector layers implementations
12820
12821 _initPath: function (layer) {
12822 var path = layer._path = create$2('path');
12823
12824 // @namespace Path
12825 // @option className: String = null
12826 // Custom class name set on an element. Only for SVG renderer.
12827 if (layer.options.className) {
12828 addClass(path, layer.options.className);
12829 }
12830
12831 if (layer.options.interactive) {
12832 addClass(path, 'leaflet-interactive');
12833 }
12834
12835 this._updateStyle(layer);
12836 this._layers[stamp(layer)] = layer;
12837 },
12838
12839 _addPath: function (layer) {
12840 if (!this._rootGroup) { this._initContainer(); }
12841 this._rootGroup.appendChild(layer._path);
12842 layer.addInteractiveTarget(layer._path);
12843 },
12844
12845 _removePath: function (layer) {
12846 remove(layer._path);
12847 layer.removeInteractiveTarget(layer._path);
12848 delete this._layers[stamp(layer)];
12849 },
12850
12851 _updatePath: function (layer) {
12852 layer._project();
12853 layer._update();
12854 },
12855
12856 _updateStyle: function (layer) {
12857 var path = layer._path,
12858 options = layer.options;
12859
12860 if (!path) { return; }
12861
12862 if (options.stroke) {
12863 path.setAttribute('stroke', options.color);
12864 path.setAttribute('stroke-opacity', options.opacity);
12865 path.setAttribute('stroke-width', options.weight);
12866 path.setAttribute('stroke-linecap', options.lineCap);
12867 path.setAttribute('stroke-linejoin', options.lineJoin);
12868
12869 if (options.dashArray) {
12870 path.setAttribute('stroke-dasharray', options.dashArray);
12871 } else {
12872 path.removeAttribute('stroke-dasharray');
12873 }
12874
12875 if (options.dashOffset) {
12876 path.setAttribute('stroke-dashoffset', options.dashOffset);
12877 } else {
12878 path.removeAttribute('stroke-dashoffset');
12879 }
12880 } else {
12881 path.setAttribute('stroke', 'none');
12882 }
12883
12884 if (options.fill) {
12885 path.setAttribute('fill', options.fillColor || options.color);
12886 path.setAttribute('fill-opacity', options.fillOpacity);
12887 path.setAttribute('fill-rule', options.fillRule || 'evenodd');
12888 } else {
12889 path.setAttribute('fill', 'none');
12890 }
12891 },
12892
12893 _updatePoly: function (layer, closed) {
12894 this._setPath(layer, pointsToPath(layer._parts, closed));
12895 },
12896
12897 _updateCircle: function (layer) {
12898 var p = layer._point,
12899 r = Math.max(Math.round(layer._radius), 1),
12900 r2 = Math.max(Math.round(layer._radiusY), 1) || r,
12901 arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
12902
12903 // drawing a circle with two half-arcs
12904 var d = layer._empty() ? 'M0 0' :
12905 'M' + (p.x - r) + ',' + p.y +
12906 arc + (r * 2) + ',0 ' +
12907 arc + (-r * 2) + ',0 ';
12908
12909 this._setPath(layer, d);
12910 },
12911
12912 _setPath: function (layer, path) {
12913 layer._path.setAttribute('d', path);
12914 },
12915
12916 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
12917 _bringToFront: function (layer) {
12918 toFront(layer._path);
12919 },
12920
12921 _bringToBack: function (layer) {
12922 toBack(layer._path);
12923 }
12924});
12925
12926if (vml) {
12927 SVG.include(vmlMixin);
12928}
12929
12930// @namespace SVG
12931// @factory L.svg(options?: Renderer options)
12932// Creates a SVG renderer with the given options.
12933function svg$1(options) {
12934 return svg || vml ? new SVG(options) : null;
12935}
12936
12937Map.include({
12938 // @namespace Map; @method getRenderer(layer: Path): Renderer
12939 // Returns the instance of `Renderer` that should be used to render the given
12940 // `Path`. It will ensure that the `renderer` options of the map and paths
12941 // are respected, and that the renderers do exist on the map.
12942 getRenderer: function (layer) {
12943 // @namespace Path; @option renderer: Renderer
12944 // Use this specific instance of `Renderer` for this path. Takes
12945 // precedence over the map's [default renderer](#map-renderer).
12946 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
12947
12948 if (!renderer) {
12949 renderer = this._renderer = this._createRenderer();
12950 }
12951
12952 if (!this.hasLayer(renderer)) {
12953 this.addLayer(renderer);
12954 }
12955 return renderer;
12956 },
12957
12958 _getPaneRenderer: function (name) {
12959 if (name === 'overlayPane' || name === undefined) {
12960 return false;
12961 }
12962
12963 var renderer = this._paneRenderers[name];
12964 if (renderer === undefined) {
12965 renderer = this._createRenderer({pane: name});
12966 this._paneRenderers[name] = renderer;
12967 }
12968 return renderer;
12969 },
12970
12971 _createRenderer: function (options) {
12972 // @namespace Map; @option preferCanvas: Boolean = false
12973 // Whether `Path`s should be rendered on a `Canvas` renderer.
12974 // By default, all `Path`s are rendered in a `SVG` renderer.
12975 return (this.options.preferCanvas && canvas$1(options)) || svg$1(options);
12976 }
12977});
12978
12979/*
12980 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
12981 */
12982
12983/*
12984 * @class Rectangle
12985 * @aka L.Rectangle
12986 * @inherits Polygon
12987 *
12988 * A class for drawing rectangle overlays on a map. Extends `Polygon`.
12989 *
12990 * @example
12991 *
12992 * ```js
12993 * // define rectangle geographical bounds
12994 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
12995 *
12996 * // create an orange rectangle
12997 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
12998 *
12999 * // zoom the map to the rectangle bounds
13000 * map.fitBounds(bounds);
13001 * ```
13002 *
13003 */
13004
13005
13006var Rectangle = Polygon.extend({
13007 initialize: function (latLngBounds, options) {
13008 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
13009 },
13010
13011 // @method setBounds(latLngBounds: LatLngBounds): this
13012 // Redraws the rectangle with the passed bounds.
13013 setBounds: function (latLngBounds) {
13014 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
13015 },
13016
13017 _boundsToLatLngs: function (latLngBounds) {
13018 latLngBounds = toLatLngBounds(latLngBounds);
13019 return [
13020 latLngBounds.getSouthWest(),
13021 latLngBounds.getNorthWest(),
13022 latLngBounds.getNorthEast(),
13023 latLngBounds.getSouthEast()
13024 ];
13025 }
13026});
13027
13028
13029// @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
13030function rectangle(latLngBounds, options) {
13031 return new Rectangle(latLngBounds, options);
13032}
13033
13034SVG.create = create$2;
13035SVG.pointsToPath = pointsToPath;
13036
13037GeoJSON.geometryToLayer = geometryToLayer;
13038GeoJSON.coordsToLatLng = coordsToLatLng;
13039GeoJSON.coordsToLatLngs = coordsToLatLngs;
13040GeoJSON.latLngToCoords = latLngToCoords;
13041GeoJSON.latLngsToCoords = latLngsToCoords;
13042GeoJSON.getFeature = getFeature;
13043GeoJSON.asFeature = asFeature;
13044
13045/*
13046 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
13047 * (zoom to a selected bounding box), enabled by default.
13048 */
13049
13050// @namespace Map
13051// @section Interaction Options
13052Map.mergeOptions({
13053 // @option boxZoom: Boolean = true
13054 // Whether the map can be zoomed to a rectangular area specified by
13055 // dragging the mouse while pressing the shift key.
13056 boxZoom: true
13057});
13058
13059var BoxZoom = Handler.extend({
13060 initialize: function (map) {
13061 this._map = map;
13062 this._container = map._container;
13063 this._pane = map._panes.overlayPane;
13064 this._resetStateTimeout = 0;
13065 map.on('unload', this._destroy, this);
13066 },
13067
13068 addHooks: function () {
13069 on(this._container, 'mousedown', this._onMouseDown, this);
13070 },
13071
13072 removeHooks: function () {
13073 off(this._container, 'mousedown', this._onMouseDown, this);
13074 },
13075
13076 moved: function () {
13077 return this._moved;
13078 },
13079
13080 _destroy: function () {
13081 remove(this._pane);
13082 delete this._pane;
13083 },
13084
13085 _resetState: function () {
13086 this._resetStateTimeout = 0;
13087 this._moved = false;
13088 },
13089
13090 _clearDeferredResetState: function () {
13091 if (this._resetStateTimeout !== 0) {
13092 clearTimeout(this._resetStateTimeout);
13093 this._resetStateTimeout = 0;
13094 }
13095 },
13096
13097 _onMouseDown: function (e) {
13098 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
13099
13100 // Clear the deferred resetState if it hasn't executed yet, otherwise it
13101 // will interrupt the interaction and orphan a box element in the container.
13102 this._clearDeferredResetState();
13103 this._resetState();
13104
13105 disableTextSelection();
13106 disableImageDrag();
13107
13108 this._startPoint = this._map.mouseEventToContainerPoint(e);
13109
13110 on(document, {
13111 contextmenu: stop,
13112 mousemove: this._onMouseMove,
13113 mouseup: this._onMouseUp,
13114 keydown: this._onKeyDown
13115 }, this);
13116 },
13117
13118 _onMouseMove: function (e) {
13119 if (!this._moved) {
13120 this._moved = true;
13121
13122 this._box = create$1('div', 'leaflet-zoom-box', this._container);
13123 addClass(this._container, 'leaflet-crosshair');
13124
13125 this._map.fire('boxzoomstart');
13126 }
13127
13128 this._point = this._map.mouseEventToContainerPoint(e);
13129
13130 var bounds = new Bounds(this._point, this._startPoint),
13131 size = bounds.getSize();
13132
13133 setPosition(this._box, bounds.min);
13134
13135 this._box.style.width = size.x + 'px';
13136 this._box.style.height = size.y + 'px';
13137 },
13138
13139 _finish: function () {
13140 if (this._moved) {
13141 remove(this._box);
13142 removeClass(this._container, 'leaflet-crosshair');
13143 }
13144
13145 enableTextSelection();
13146 enableImageDrag();
13147
13148 off(document, {
13149 contextmenu: stop,
13150 mousemove: this._onMouseMove,
13151 mouseup: this._onMouseUp,
13152 keydown: this._onKeyDown
13153 }, this);
13154 },
13155
13156 _onMouseUp: function (e) {
13157 if ((e.which !== 1) && (e.button !== 1)) { return; }
13158
13159 this._finish();
13160
13161 if (!this._moved) { return; }
13162 // Postpone to next JS tick so internal click event handling
13163 // still see it as "moved".
13164 this._clearDeferredResetState();
13165 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
13166
13167 var bounds = new LatLngBounds(
13168 this._map.containerPointToLatLng(this._startPoint),
13169 this._map.containerPointToLatLng(this._point));
13170
13171 this._map
13172 .fitBounds(bounds)
13173 .fire('boxzoomend', {boxZoomBounds: bounds});
13174 },
13175
13176 _onKeyDown: function (e) {
13177 if (e.keyCode === 27) {
13178 this._finish();
13179 }
13180 }
13181});
13182
13183// @section Handlers
13184// @property boxZoom: Handler
13185// Box (shift-drag with mouse) zoom handler.
13186Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
13187
13188/*
13189 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
13190 */
13191
13192// @namespace Map
13193// @section Interaction Options
13194
13195Map.mergeOptions({
13196 // @option doubleClickZoom: Boolean|String = true
13197 // Whether the map can be zoomed in by double clicking on it and
13198 // zoomed out by double clicking while holding shift. If passed
13199 // `'center'`, double-click zoom will zoom to the center of the
13200 // view regardless of where the mouse was.
13201 doubleClickZoom: true
13202});
13203
13204var DoubleClickZoom = Handler.extend({
13205 addHooks: function () {
13206 this._map.on('dblclick', this._onDoubleClick, this);
13207 },
13208
13209 removeHooks: function () {
13210 this._map.off('dblclick', this._onDoubleClick, this);
13211 },
13212
13213 _onDoubleClick: function (e) {
13214 var map = this._map,
13215 oldZoom = map.getZoom(),
13216 delta = map.options.zoomDelta,
13217 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
13218
13219 if (map.options.doubleClickZoom === 'center') {
13220 map.setZoom(zoom);
13221 } else {
13222 map.setZoomAround(e.containerPoint, zoom);
13223 }
13224 }
13225});
13226
13227// @section Handlers
13228//
13229// Map properties include interaction handlers that allow you to control
13230// interaction behavior in runtime, enabling or disabling certain features such
13231// as dragging or touch zoom (see `Handler` methods). For example:
13232//
13233// ```js
13234// map.doubleClickZoom.disable();
13235// ```
13236//
13237// @property doubleClickZoom: Handler
13238// Double click zoom handler.
13239Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
13240
13241/*
13242 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
13243 */
13244
13245// @namespace Map
13246// @section Interaction Options
13247Map.mergeOptions({
13248 // @option dragging: Boolean = true
13249 // Whether the map be draggable with mouse/touch or not.
13250 dragging: true,
13251
13252 // @section Panning Inertia Options
13253 // @option inertia: Boolean = *
13254 // If enabled, panning of the map will have an inertia effect where
13255 // the map builds momentum while dragging and continues moving in
13256 // the same direction for some time. Feels especially nice on touch
13257 // devices. Enabled by default unless running on old Android devices.
13258 inertia: !android23,
13259
13260 // @option inertiaDeceleration: Number = 3000
13261 // The rate with which the inertial movement slows down, in pixels/second².
13262 inertiaDeceleration: 3400, // px/s^2
13263
13264 // @option inertiaMaxSpeed: Number = Infinity
13265 // Max speed of the inertial movement, in pixels/second.
13266 inertiaMaxSpeed: Infinity, // px/s
13267
13268 // @option easeLinearity: Number = 0.2
13269 easeLinearity: 0.2,
13270
13271 // TODO refactor, move to CRS
13272 // @option worldCopyJump: Boolean = false
13273 // With this option enabled, the map tracks when you pan to another "copy"
13274 // of the world and seamlessly jumps to the original one so that all overlays
13275 // like markers and vector layers are still visible.
13276 worldCopyJump: false,
13277
13278 // @option maxBoundsViscosity: Number = 0.0
13279 // If `maxBounds` is set, this option will control how solid the bounds
13280 // are when dragging the map around. The default value of `0.0` allows the
13281 // user to drag outside the bounds at normal speed, higher values will
13282 // slow down map dragging outside bounds, and `1.0` makes the bounds fully
13283 // solid, preventing the user from dragging outside the bounds.
13284 maxBoundsViscosity: 0.0
13285});
13286
13287var Drag = Handler.extend({
13288 addHooks: function () {
13289 if (!this._draggable) {
13290 var map = this._map;
13291
13292 this._draggable = new Draggable(map._mapPane, map._container);
13293
13294 this._draggable.on({
13295 dragstart: this._onDragStart,
13296 drag: this._onDrag,
13297 dragend: this._onDragEnd
13298 }, this);
13299
13300 this._draggable.on('predrag', this._onPreDragLimit, this);
13301 if (map.options.worldCopyJump) {
13302 this._draggable.on('predrag', this._onPreDragWrap, this);
13303 map.on('zoomend', this._onZoomEnd, this);
13304
13305 map.whenReady(this._onZoomEnd, this);
13306 }
13307 }
13308 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
13309 this._draggable.enable();
13310 this._positions = [];
13311 this._times = [];
13312 },
13313
13314 removeHooks: function () {
13315 removeClass(this._map._container, 'leaflet-grab');
13316 removeClass(this._map._container, 'leaflet-touch-drag');
13317 this._draggable.disable();
13318 },
13319
13320 moved: function () {
13321 return this._draggable && this._draggable._moved;
13322 },
13323
13324 moving: function () {
13325 return this._draggable && this._draggable._moving;
13326 },
13327
13328 _onDragStart: function () {
13329 var map = this._map;
13330
13331 map._stop();
13332 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
13333 var bounds = toLatLngBounds(this._map.options.maxBounds);
13334
13335 this._offsetLimit = toBounds(
13336 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
13337 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
13338 .add(this._map.getSize()));
13339
13340 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
13341 } else {
13342 this._offsetLimit = null;
13343 }
13344
13345 map
13346 .fire('movestart')
13347 .fire('dragstart');
13348
13349 if (map.options.inertia) {
13350 this._positions = [];
13351 this._times = [];
13352 }
13353 },
13354
13355 _onDrag: function (e) {
13356 if (this._map.options.inertia) {
13357 var time = this._lastTime = +new Date(),
13358 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
13359
13360 this._positions.push(pos);
13361 this._times.push(time);
13362
13363 this._prunePositions(time);
13364 }
13365
13366 this._map
13367 .fire('move', e)
13368 .fire('drag', e);
13369 },
13370
13371 _prunePositions: function (time) {
13372 while (this._positions.length > 1 && time - this._times[0] > 50) {
13373 this._positions.shift();
13374 this._times.shift();
13375 }
13376 },
13377
13378 _onZoomEnd: function () {
13379 var pxCenter = this._map.getSize().divideBy(2),
13380 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
13381
13382 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
13383 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
13384 },
13385
13386 _viscousLimit: function (value, threshold) {
13387 return value - (value - threshold) * this._viscosity;
13388 },
13389
13390 _onPreDragLimit: function () {
13391 if (!this._viscosity || !this._offsetLimit) { return; }
13392
13393 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
13394
13395 var limit = this._offsetLimit;
13396 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
13397 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
13398 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
13399 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
13400
13401 this._draggable._newPos = this._draggable._startPos.add(offset);
13402 },
13403
13404 _onPreDragWrap: function () {
13405 // TODO refactor to be able to adjust map pane position after zoom
13406 var worldWidth = this._worldWidth,
13407 halfWidth = Math.round(worldWidth / 2),
13408 dx = this._initialWorldOffset,
13409 x = this._draggable._newPos.x,
13410 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
13411 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
13412 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
13413
13414 this._draggable._absPos = this._draggable._newPos.clone();
13415 this._draggable._newPos.x = newX;
13416 },
13417
13418 _onDragEnd: function (e) {
13419 var map = this._map,
13420 options = map.options,
13421
13422 noInertia = !options.inertia || this._times.length < 2;
13423
13424 map.fire('dragend', e);
13425
13426 if (noInertia) {
13427 map.fire('moveend');
13428
13429 } else {
13430 this._prunePositions(+new Date());
13431
13432 var direction = this._lastPos.subtract(this._positions[0]),
13433 duration = (this._lastTime - this._times[0]) / 1000,
13434 ease = options.easeLinearity,
13435
13436 speedVector = direction.multiplyBy(ease / duration),
13437 speed = speedVector.distanceTo([0, 0]),
13438
13439 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
13440 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
13441
13442 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
13443 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
13444
13445 if (!offset.x && !offset.y) {
13446 map.fire('moveend');
13447
13448 } else {
13449 offset = map._limitOffset(offset, map.options.maxBounds);
13450
13451 requestAnimFrame(function () {
13452 map.panBy(offset, {
13453 duration: decelerationDuration,
13454 easeLinearity: ease,
13455 noMoveStart: true,
13456 animate: true
13457 });
13458 });
13459 }
13460 }
13461 }
13462});
13463
13464// @section Handlers
13465// @property dragging: Handler
13466// Map dragging handler (by both mouse and touch).
13467Map.addInitHook('addHandler', 'dragging', Drag);
13468
13469/*
13470 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
13471 */
13472
13473// @namespace Map
13474// @section Keyboard Navigation Options
13475Map.mergeOptions({
13476 // @option keyboard: Boolean = true
13477 // Makes the map focusable and allows users to navigate the map with keyboard
13478 // arrows and `+`/`-` keys.
13479 keyboard: true,
13480
13481 // @option keyboardPanDelta: Number = 80
13482 // Amount of pixels to pan when pressing an arrow key.
13483 keyboardPanDelta: 80
13484});
13485
13486var Keyboard = Handler.extend({
13487
13488 keyCodes: {
13489 left: [37],
13490 right: [39],
13491 down: [40],
13492 up: [38],
13493 zoomIn: [187, 107, 61, 171],
13494 zoomOut: [189, 109, 54, 173]
13495 },
13496
13497 initialize: function (map) {
13498 this._map = map;
13499
13500 this._setPanDelta(map.options.keyboardPanDelta);
13501 this._setZoomDelta(map.options.zoomDelta);
13502 },
13503
13504 addHooks: function () {
13505 var container = this._map._container;
13506
13507 // make the container focusable by tabbing
13508 if (container.tabIndex <= 0) {
13509 container.tabIndex = '0';
13510 }
13511
13512 on(container, {
13513 focus: this._onFocus,
13514 blur: this._onBlur,
13515 mousedown: this._onMouseDown
13516 }, this);
13517
13518 this._map.on({
13519 focus: this._addHooks,
13520 blur: this._removeHooks
13521 }, this);
13522 },
13523
13524 removeHooks: function () {
13525 this._removeHooks();
13526
13527 off(this._map._container, {
13528 focus: this._onFocus,
13529 blur: this._onBlur,
13530 mousedown: this._onMouseDown
13531 }, this);
13532
13533 this._map.off({
13534 focus: this._addHooks,
13535 blur: this._removeHooks
13536 }, this);
13537 },
13538
13539 _onMouseDown: function () {
13540 if (this._focused) { return; }
13541
13542 var body = document.body,
13543 docEl = document.documentElement,
13544 top = body.scrollTop || docEl.scrollTop,
13545 left = body.scrollLeft || docEl.scrollLeft;
13546
13547 this._map._container.focus();
13548
13549 window.scrollTo(left, top);
13550 },
13551
13552 _onFocus: function () {
13553 this._focused = true;
13554 this._map.fire('focus');
13555 },
13556
13557 _onBlur: function () {
13558 this._focused = false;
13559 this._map.fire('blur');
13560 },
13561
13562 _setPanDelta: function (panDelta) {
13563 var keys = this._panKeys = {},
13564 codes = this.keyCodes,
13565 i, len;
13566
13567 for (i = 0, len = codes.left.length; i < len; i++) {
13568 keys[codes.left[i]] = [-1 * panDelta, 0];
13569 }
13570 for (i = 0, len = codes.right.length; i < len; i++) {
13571 keys[codes.right[i]] = [panDelta, 0];
13572 }
13573 for (i = 0, len = codes.down.length; i < len; i++) {
13574 keys[codes.down[i]] = [0, panDelta];
13575 }
13576 for (i = 0, len = codes.up.length; i < len; i++) {
13577 keys[codes.up[i]] = [0, -1 * panDelta];
13578 }
13579 },
13580
13581 _setZoomDelta: function (zoomDelta) {
13582 var keys = this._zoomKeys = {},
13583 codes = this.keyCodes,
13584 i, len;
13585
13586 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
13587 keys[codes.zoomIn[i]] = zoomDelta;
13588 }
13589 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
13590 keys[codes.zoomOut[i]] = -zoomDelta;
13591 }
13592 },
13593
13594 _addHooks: function () {
13595 on(document, 'keydown', this._onKeyDown, this);
13596 },
13597
13598 _removeHooks: function () {
13599 off(document, 'keydown', this._onKeyDown, this);
13600 },
13601
13602 _onKeyDown: function (e) {
13603 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
13604
13605 var key = e.keyCode,
13606 map = this._map,
13607 offset;
13608
13609 if (key in this._panKeys) {
13610 if (!map._panAnim || !map._panAnim._inProgress) {
13611 offset = this._panKeys[key];
13612 if (e.shiftKey) {
13613 offset = toPoint(offset).multiplyBy(3);
13614 }
13615
13616 map.panBy(offset);
13617
13618 if (map.options.maxBounds) {
13619 map.panInsideBounds(map.options.maxBounds);
13620 }
13621 }
13622 } else if (key in this._zoomKeys) {
13623 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
13624
13625 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
13626 map.closePopup();
13627
13628 } else {
13629 return;
13630 }
13631
13632 stop(e);
13633 }
13634});
13635
13636// @section Handlers
13637// @section Handlers
13638// @property keyboard: Handler
13639// Keyboard navigation handler.
13640Map.addInitHook('addHandler', 'keyboard', Keyboard);
13641
13642/*
13643 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
13644 */
13645
13646// @namespace Map
13647// @section Interaction Options
13648Map.mergeOptions({
13649 // @section Mousewheel options
13650 // @option scrollWheelZoom: Boolean|String = true
13651 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
13652 // it will zoom to the center of the view regardless of where the mouse was.
13653 scrollWheelZoom: true,
13654
13655 // @option wheelDebounceTime: Number = 40
13656 // Limits the rate at which a wheel can fire (in milliseconds). By default
13657 // user can't zoom via wheel more often than once per 40 ms.
13658 wheelDebounceTime: 40,
13659
13660 // @option wheelPxPerZoomLevel: Number = 60
13661 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
13662 // mean a change of one full zoom level. Smaller values will make wheel-zooming
13663 // faster (and vice versa).
13664 wheelPxPerZoomLevel: 60
13665});
13666
13667var ScrollWheelZoom = Handler.extend({
13668 addHooks: function () {
13669 on(this._map._container, 'mousewheel', this._onWheelScroll, this);
13670
13671 this._delta = 0;
13672 },
13673
13674 removeHooks: function () {
13675 off(this._map._container, 'mousewheel', this._onWheelScroll, this);
13676 },
13677
13678 _onWheelScroll: function (e) {
13679 var delta = getWheelDelta(e);
13680
13681 var debounce = this._map.options.wheelDebounceTime;
13682
13683 this._delta += delta;
13684 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
13685
13686 if (!this._startTime) {
13687 this._startTime = +new Date();
13688 }
13689
13690 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
13691
13692 clearTimeout(this._timer);
13693 this._timer = setTimeout(bind(this._performZoom, this), left);
13694
13695 stop(e);
13696 },
13697
13698 _performZoom: function () {
13699 var map = this._map,
13700 zoom = map.getZoom(),
13701 snap = this._map.options.zoomSnap || 0;
13702
13703 map._stop(); // stop panning and fly animations if any
13704
13705 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
13706 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
13707 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
13708 d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
13709 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
13710
13711 this._delta = 0;
13712 this._startTime = null;
13713
13714 if (!delta) { return; }
13715
13716 if (map.options.scrollWheelZoom === 'center') {
13717 map.setZoom(zoom + delta);
13718 } else {
13719 map.setZoomAround(this._lastMousePos, zoom + delta);
13720 }
13721 }
13722});
13723
13724// @section Handlers
13725// @property scrollWheelZoom: Handler
13726// Scroll wheel zoom handler.
13727Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
13728
13729/*
13730 * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
13731 */
13732
13733// @namespace Map
13734// @section Interaction Options
13735Map.mergeOptions({
13736 // @section Touch interaction options
13737 // @option tap: Boolean = true
13738 // Enables mobile hacks for supporting instant taps (fixing 200ms click
13739 // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
13740 tap: true,
13741
13742 // @option tapTolerance: Number = 15
13743 // The max number of pixels a user can shift his finger during touch
13744 // for it to be considered a valid tap.
13745 tapTolerance: 15
13746});
13747
13748var Tap = Handler.extend({
13749 addHooks: function () {
13750 on(this._map._container, 'touchstart', this._onDown, this);
13751 },
13752
13753 removeHooks: function () {
13754 off(this._map._container, 'touchstart', this._onDown, this);
13755 },
13756
13757 _onDown: function (e) {
13758 if (!e.touches) { return; }
13759
13760 preventDefault(e);
13761
13762 this._fireClick = true;
13763
13764 // don't simulate click or track longpress if more than 1 touch
13765 if (e.touches.length > 1) {
13766 this._fireClick = false;
13767 clearTimeout(this._holdTimeout);
13768 return;
13769 }
13770
13771 var first = e.touches[0],
13772 el = first.target;
13773
13774 this._startPos = this._newPos = new Point(first.clientX, first.clientY);
13775
13776 // if touching a link, highlight it
13777 if (el.tagName && el.tagName.toLowerCase() === 'a') {
13778 addClass(el, 'leaflet-active');
13779 }
13780
13781 // simulate long hold but setting a timeout
13782 this._holdTimeout = setTimeout(bind(function () {
13783 if (this._isTapValid()) {
13784 this._fireClick = false;
13785 this._onUp();
13786 this._simulateEvent('contextmenu', first);
13787 }
13788 }, this), 1000);
13789
13790 this._simulateEvent('mousedown', first);
13791
13792 on(document, {
13793 touchmove: this._onMove,
13794 touchend: this._onUp
13795 }, this);
13796 },
13797
13798 _onUp: function (e) {
13799 clearTimeout(this._holdTimeout);
13800
13801 off(document, {
13802 touchmove: this._onMove,
13803 touchend: this._onUp
13804 }, this);
13805
13806 if (this._fireClick && e && e.changedTouches) {
13807
13808 var first = e.changedTouches[0],
13809 el = first.target;
13810
13811 if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
13812 removeClass(el, 'leaflet-active');
13813 }
13814
13815 this._simulateEvent('mouseup', first);
13816
13817 // simulate click if the touch didn't move too much
13818 if (this._isTapValid()) {
13819 this._simulateEvent('click', first);
13820 }
13821 }
13822 },
13823
13824 _isTapValid: function () {
13825 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
13826 },
13827
13828 _onMove: function (e) {
13829 var first = e.touches[0];
13830 this._newPos = new Point(first.clientX, first.clientY);
13831 this._simulateEvent('mousemove', first);
13832 },
13833
13834 _simulateEvent: function (type, e) {
13835 var simulatedEvent = document.createEvent('MouseEvents');
13836
13837 simulatedEvent._simulated = true;
13838 e.target._simulatedClick = true;
13839
13840 simulatedEvent.initMouseEvent(
13841 type, true, true, window, 1,
13842 e.screenX, e.screenY,
13843 e.clientX, e.clientY,
13844 false, false, false, false, 0, null);
13845
13846 e.target.dispatchEvent(simulatedEvent);
13847 }
13848});
13849
13850// @section Handlers
13851// @property tap: Handler
13852// Mobile touch hacks (quick tap and touch hold) handler.
13853if (touch && !pointer) {
13854 Map.addInitHook('addHandler', 'tap', Tap);
13855}
13856
13857/*
13858 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
13859 */
13860
13861// @namespace Map
13862// @section Interaction Options
13863Map.mergeOptions({
13864 // @section Touch interaction options
13865 // @option touchZoom: Boolean|String = *
13866 // Whether the map can be zoomed by touch-dragging with two fingers. If
13867 // passed `'center'`, it will zoom to the center of the view regardless of
13868 // where the touch events (fingers) were. Enabled for touch-capable web
13869 // browsers except for old Androids.
13870 touchZoom: touch && !android23,
13871
13872 // @option bounceAtZoomLimits: Boolean = true
13873 // Set it to false if you don't want the map to zoom beyond min/max zoom
13874 // and then bounce back when pinch-zooming.
13875 bounceAtZoomLimits: true
13876});
13877
13878var TouchZoom = Handler.extend({
13879 addHooks: function () {
13880 addClass(this._map._container, 'leaflet-touch-zoom');
13881 on(this._map._container, 'touchstart', this._onTouchStart, this);
13882 },
13883
13884 removeHooks: function () {
13885 removeClass(this._map._container, 'leaflet-touch-zoom');
13886 off(this._map._container, 'touchstart', this._onTouchStart, this);
13887 },
13888
13889 _onTouchStart: function (e) {
13890 var map = this._map;
13891 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
13892
13893 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
13894 p2 = map.mouseEventToContainerPoint(e.touches[1]);
13895
13896 this._centerPoint = map.getSize()._divideBy(2);
13897 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
13898 if (map.options.touchZoom !== 'center') {
13899 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
13900 }
13901
13902 this._startDist = p1.distanceTo(p2);
13903 this._startZoom = map.getZoom();
13904
13905 this._moved = false;
13906 this._zooming = true;
13907
13908 map._stop();
13909
13910 on(document, 'touchmove', this._onTouchMove, this);
13911 on(document, 'touchend', this._onTouchEnd, this);
13912
13913 preventDefault(e);
13914 },
13915
13916 _onTouchMove: function (e) {
13917 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
13918
13919 var map = this._map,
13920 p1 = map.mouseEventToContainerPoint(e.touches[0]),
13921 p2 = map.mouseEventToContainerPoint(e.touches[1]),
13922 scale = p1.distanceTo(p2) / this._startDist;
13923
13924 this._zoom = map.getScaleZoom(scale, this._startZoom);
13925
13926 if (!map.options.bounceAtZoomLimits && (
13927 (this._zoom < map.getMinZoom() && scale < 1) ||
13928 (this._zoom > map.getMaxZoom() && scale > 1))) {
13929 this._zoom = map._limitZoom(this._zoom);
13930 }
13931
13932 if (map.options.touchZoom === 'center') {
13933 this._center = this._startLatLng;
13934 if (scale === 1) { return; }
13935 } else {
13936 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
13937 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
13938 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
13939 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
13940 }
13941
13942 if (!this._moved) {
13943 map._moveStart(true, false);
13944 this._moved = true;
13945 }
13946
13947 cancelAnimFrame(this._animRequest);
13948
13949 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
13950 this._animRequest = requestAnimFrame(moveFn, this, true);
13951
13952 preventDefault(e);
13953 },
13954
13955 _onTouchEnd: function () {
13956 if (!this._moved || !this._zooming) {
13957 this._zooming = false;
13958 return;
13959 }
13960
13961 this._zooming = false;
13962 cancelAnimFrame(this._animRequest);
13963
13964 off(document, 'touchmove', this._onTouchMove);
13965 off(document, 'touchend', this._onTouchEnd);
13966
13967 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
13968 if (this._map.options.zoomAnimation) {
13969 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
13970 } else {
13971 this._map._resetView(this._center, this._map._limitZoom(this._zoom));
13972 }
13973 }
13974});
13975
13976// @section Handlers
13977// @property touchZoom: Handler
13978// Touch zoom handler.
13979Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
13980
13981Map.BoxZoom = BoxZoom;
13982Map.DoubleClickZoom = DoubleClickZoom;
13983Map.Drag = Drag;
13984Map.Keyboard = Keyboard;
13985Map.ScrollWheelZoom = ScrollWheelZoom;
13986Map.Tap = Tap;
13987Map.TouchZoom = TouchZoom;
13988
13989Object.freeze = freeze;
13990
13991exports.version = version;
13992exports.Control = Control;
13993exports.control = control;
13994exports.Browser = Browser;
13995exports.Evented = Evented;
13996exports.Mixin = Mixin;
13997exports.Util = Util;
13998exports.Class = Class;
13999exports.Handler = Handler;
14000exports.extend = extend;
14001exports.bind = bind;
14002exports.stamp = stamp;
14003exports.setOptions = setOptions;
14004exports.DomEvent = DomEvent;
14005exports.DomUtil = DomUtil;
14006exports.PosAnimation = PosAnimation;
14007exports.Draggable = Draggable;
14008exports.LineUtil = LineUtil;
14009exports.PolyUtil = PolyUtil;
14010exports.Point = Point;
14011exports.point = toPoint;
14012exports.Bounds = Bounds;
14013exports.bounds = toBounds;
14014exports.Transformation = Transformation;
14015exports.transformation = toTransformation;
14016exports.Projection = index;
14017exports.LatLng = LatLng;
14018exports.latLng = toLatLng;
14019exports.LatLngBounds = LatLngBounds;
14020exports.latLngBounds = toLatLngBounds;
14021exports.CRS = CRS;
14022exports.GeoJSON = GeoJSON;
14023exports.geoJSON = geoJSON;
14024exports.geoJson = geoJson;
14025exports.Layer = Layer;
14026exports.LayerGroup = LayerGroup;
14027exports.layerGroup = layerGroup;
14028exports.FeatureGroup = FeatureGroup;
14029exports.featureGroup = featureGroup;
14030exports.ImageOverlay = ImageOverlay;
14031exports.imageOverlay = imageOverlay;
14032exports.VideoOverlay = VideoOverlay;
14033exports.videoOverlay = videoOverlay;
14034exports.SVGOverlay = SVGOverlay;
14035exports.svgOverlay = svgOverlay;
14036exports.DivOverlay = DivOverlay;
14037exports.Popup = Popup;
14038exports.popup = popup;
14039exports.Tooltip = Tooltip;
14040exports.tooltip = tooltip;
14041exports.Icon = Icon;
14042exports.icon = icon;
14043exports.DivIcon = DivIcon;
14044exports.divIcon = divIcon;
14045exports.Marker = Marker;
14046exports.marker = marker;
14047exports.TileLayer = TileLayer;
14048exports.tileLayer = tileLayer;
14049exports.GridLayer = GridLayer;
14050exports.gridLayer = gridLayer;
14051exports.SVG = SVG;
14052exports.svg = svg$1;
14053exports.Renderer = Renderer;
14054exports.Canvas = Canvas;
14055exports.canvas = canvas$1;
14056exports.Path = Path;
14057exports.CircleMarker = CircleMarker;
14058exports.circleMarker = circleMarker;
14059exports.Circle = Circle;
14060exports.circle = circle;
14061exports.Polyline = Polyline;
14062exports.polyline = polyline;
14063exports.Polygon = Polygon;
14064exports.polygon = polygon;
14065exports.Rectangle = Rectangle;
14066exports.rectangle = rectangle;
14067exports.Map = Map;
14068exports.map = createMap;
14069
14070var oldL = window.L;
14071exports.noConflict = function() {
14072 window.L = oldL;
14073 return this;
14074}
14075
14076// Always export us to window global (see #2364)
14077window.L = exports;
14078
14079})));
14080//# sourceMappingURL=leaflet-src.js.map