2 * Leaflet 1.3.3, a JS library for interactive maps. http://leafletjs.com
3 * (c) 2010-2018 Vladimir Agafonkin, (c) 2010-2011 CloudMade
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';
12 var version = "1.3.3";
17 * Various utility functions, used by Leaflet internally.
20 var freeze = Object.freeze;
21 Object.freeze = function (obj) { return obj; };
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.
25 function extend(dest) {
28 for (j = 1, len = arguments.length; j < len; j++) {
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)
39 var create = Object.create || (function () {
41 return function (proto) {
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.
50 function bind(fn, obj) {
51 var slice = Array.prototype.slice;
54 return fn.bind.apply(fn, slice.call(arguments, 1));
57 var args = slice.call(arguments, 2);
60 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
64 // @property lastId: Number
65 // Last unique ID used by [`stamp()`](#util-stamp)
68 // @function stamp(obj: Object): Number
69 // Returns the unique ID of an object, assigning it one if it doesn't have it.
72 obj._leaflet_id = obj._leaflet_id || ++lastId;
73 return obj._leaflet_id;
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.
84 function throttle(fn, time, context) {
85 var lock, args, wrapperFn, later;
88 // reset lock and call if queued
91 wrapperFn.apply(context, args);
96 wrapperFn = function () {
98 // called too soon, queue to call later
102 // call and lock until later
103 fn.apply(context, arguments);
104 setTimeout(later, time);
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`.
116 function wrapNum(x, range, includeMax) {
120 return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
123 // @function falseFn(): Function
124 // Returns a function which always returns `false`.
125 function falseFn() { return false; }
127 // @function formatNum(num: Number, digits?: Number): Number
128 // Returns the number `num` rounded to `digits` decimals, or to 6 decimals by default.
129 function formatNum(num, digits) {
130 var pow = Math.pow(10, (digits === undefined ? 6 : digits));
131 return Math.round(num * pow) / pow;
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)
137 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
140 // @function splitWords(str: String): String[]
141 // Trims and splits the string on whitespace and returns the array of parts.
142 function splitWords(str) {
143 return trim(str).split(/\s+/);
146 // @function setOptions(obj: Object, options: Object): Object
147 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
148 function setOptions(obj, options) {
149 if (!obj.hasOwnProperty('options')) {
150 obj.options = obj.options ? create(obj.options) : {};
152 for (var i in options) {
153 obj.options[i] = options[i];
158 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
159 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
160 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
161 // be appended at the end. If `uppercase` is `true`, the parameter names will
162 // be uppercased (e.g. `'?A=foo&B=bar'`)
163 function getParamString(obj, existingUrl, uppercase) {
166 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
168 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
171 var templateRe = /\{ *([\w_-]+) *\}/g;
173 // @function template(str: String, data: Object): String
174 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
175 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
176 // `('Hello foo, bar')`. You can also specify functions instead of strings for
177 // data values — they will be evaluated passing `data` as an argument.
178 function template(str, data) {
179 return str.replace(templateRe, function (str, key) {
180 var value = data[key];
182 if (value === undefined) {
183 throw new Error('No value provided for variable ' + str);
185 } else if (typeof value === 'function') {
192 // @function isArray(obj): Boolean
193 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
194 var isArray = Array.isArray || function (obj) {
195 return (Object.prototype.toString.call(obj) === '[object Array]');
198 // @function indexOf(array: Array, el: Object): Number
199 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
200 function indexOf(array, el) {
201 for (var i = 0; i < array.length; i++) {
202 if (array[i] === el) { return i; }
207 // @property emptyImageUrl: String
208 // Data URI string containing a base64-encoded empty GIF image.
209 // Used as a hack to free memory from unused images on WebKit-powered
210 // mobile devices (by setting image `src` to this string).
211 var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
213 // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
215 function getPrefixed(name) {
216 return window['webkit' + name] || window['moz' + name] || window['ms' + name];
221 // fallback for IE 7-8
222 function timeoutDefer(fn) {
223 var time = +new Date(),
224 timeToCall = Math.max(0, 16 - (time - lastTime));
226 lastTime = time + timeToCall;
227 return window.setTimeout(fn, timeToCall);
230 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
231 var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
232 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
234 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
235 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
236 // `context` if given. When `immediate` is set, `fn` is called immediately if
237 // the browser doesn't have native support for
238 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
239 // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
240 function requestAnimFrame(fn, context, immediate) {
241 if (immediate && requestFn === timeoutDefer) {
244 return requestFn.call(window, bind(fn, context));
248 // @function cancelAnimFrame(id: Number): undefined
249 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
250 function cancelAnimFrame(id) {
252 cancelFn.call(window, id);
257 var Util = (Object.freeze || Object)({
267 formatNum: formatNum,
269 splitWords: splitWords,
270 setOptions: setOptions,
271 getParamString: getParamString,
275 emptyImageUrl: emptyImageUrl,
276 requestFn: requestFn,
278 requestAnimFrame: requestAnimFrame,
279 cancelAnimFrame: cancelAnimFrame
288 // Thanks to John Resig and Dean Edwards for inspiration!
292 Class.extend = function (props) {
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 () {
299 // call the constructor
300 if (this.initialize) {
301 this.initialize.apply(this, arguments);
304 // call all constructor hooks
305 this.callInitHooks();
308 var parentProto = NewClass.__super__ = this.prototype;
310 var proto = create(parentProto);
311 proto.constructor = NewClass;
313 NewClass.prototype = proto;
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];
322 // mix static properties into the class
324 extend(NewClass, props.statics);
325 delete props.statics;
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;
337 props.options = extend(create(proto.options), props.options);
340 // mix given properties into the prototype
341 extend(proto, props);
343 proto._initHooks = [];
345 // add method for calling all hooks
346 proto.callInitHooks = function () {
348 if (this._initHooksCalled) { return; }
350 if (parentProto.callInitHooks) {
351 parentProto.callInitHooks.call(this);
354 this._initHooksCalled = true;
356 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
357 proto._initHooks[i].call(this);
365 // @function include(properties: Object): this
366 // [Includes a mixin](#class-includes) into the current class.
367 Class.include = function (props) {
368 extend(this.prototype, props);
372 // @function mergeOptions(options: Object): this
373 // [Merges `options`](#class-options) into the defaults of the class.
374 Class.mergeOptions = function (options) {
375 extend(this.prototype.options, options);
379 // @function addInitHook(fn: Function): this
380 // Adds a [constructor hook](#class-constructor-hooks) to the class.
381 Class.addInitHook = function (fn) { // (Function) || (String, args...)
382 var args = Array.prototype.slice.call(arguments, 1);
384 var init = typeof fn === 'function' ? fn : function () {
385 this[fn].apply(this, args);
388 this.prototype._initHooks = this.prototype._initHooks || [];
389 this.prototype._initHooks.push(init);
393 function checkDeprecatedMixinEvents(includes) {
394 if (typeof L === 'undefined' || !L || !L.Mixin) { return; }
396 includes = isArray(includes) ? includes : [includes];
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);
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).
417 * map.on('click', function(e) {
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:
425 * function onClick(e) { ... }
427 * map.on('click', onClick);
428 * map.off('click', onClick);
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'`).
437 * @method on(eventMap: Object): this
438 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
440 on: function (types, fn, context) {
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);
451 // types can be a string of space-separated words
452 types = splitWords(types);
454 for (var i = 0, len = types.length; i < len; i++) {
455 this._on(types[i], fn, context);
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.
466 * @method off(eventMap: Object): this
467 * Removes a set of type/listener pairs.
471 * Removes all listeners to all events on the object.
473 off: function (types, fn, context) {
476 // clear all listeners if called without arguments
479 } else if (typeof types === 'object') {
480 for (var type in types) {
481 this._off(type, types[type], fn);
485 types = splitWords(types);
487 for (var i = 0, len = types.length; i < len; i++) {
488 this._off(types[i], fn, context);
495 // attach listener (without syntactic sugar now)
496 _on: function (type, fn, context) {
497 this._events = this._events || {};
499 /* get/init listeners for type */
500 var typeListeners = this._events[type];
501 if (!typeListeners) {
503 this._events[type] = typeListeners;
506 if (context === this) {
507 // Less memory footprint.
510 var newListener = {fn: fn, ctx: context},
511 listeners = typeListeners;
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) {
520 listeners.push(newListener);
523 _off: function (type, fn, context) {
528 if (!this._events) { return; }
530 listeners = this._events[type];
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;
541 // clear all listeners for a type if function isn't specified
542 delete this._events[type];
546 if (context === this) {
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; }
558 // set the removed listener to noop so that's not called if remove happens in fire
561 if (this._firingCount) {
562 /* copy array in case events are being fired */
563 this._events[type] = listeners = listeners.slice();
565 listeners.splice(i, 1);
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; }
580 var event = extend({}, data, {
583 sourceTarget: data && data.sourceTarget || this
587 var listeners = this._events[type];
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);
601 // propagate the event to parents (set with addEventParent)
602 this._propagateEvent(event);
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; }
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; }
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) {
627 if (typeof types === 'object') {
628 for (var type in types) {
629 this.once(type, types[type], fn);
634 var handler = bind(function () {
636 .off(types, fn, context)
637 .off(types, handler, context);
640 // add a listener that's executed once and removed after that
642 .on(types, fn, context)
643 .on(types, handler, context);
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;
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)];
663 _propagateEvent: function (e) {
664 for (var id in this._eventParents) {
665 this._eventParents[id].fire(e.type, extend({
667 propagatedFrom: e.target
673 // aliases; we should ditch those eventually
675 // @method addEventListener(…): this
676 // Alias to [`on(…)`](#evented-on)
677 Events.addEventListener = Events.on;
679 // @method removeEventListener(…): this
680 // Alias to [`off(…)`](#evented-off)
682 // @method clearAllEventListeners(…): this
683 // Alias to [`off()`](#evented-off)
684 Events.removeEventListener = Events.clearAllEventListeners = Events.off;
686 // @method addOneTimeEventListener(…): this
687 // Alias to [`once(…)`](#evented-once)
688 Events.addOneTimeEventListener = Events.once;
690 // @method fireEvent(…): this
691 // Alias to [`fire(…)`](#evented-fire)
692 Events.fireEvent = Events.fire;
694 // @method hasEventListeners(…): Boolean
695 // Alias to [`listens(…)`](#evented-listens)
696 Events.hasEventListeners = Events.listens;
698 var Evented = Class.extend(Events);
704 * Represents a point with `x` and `y` coordinates in pixels.
709 * var point = L.point(200, 300);
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:
715 * map.panBy([200, 300]);
716 * map.panBy(L.point(200, 300));
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.
724 function 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);
731 var trunc = Math.trunc || function (v) {
732 return v > 0 ? Math.floor(v) : Math.ceil(v);
737 // @method clone(): Point
738 // Returns a copy of the current point.
740 return new Point(this.x, this.y);
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));
750 _add: function (point) {
751 // destructive, used directly for performance in situations where it's safe to modify existing point
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));
763 _subtract: function (point) {
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);
775 _divideBy: function (num) {
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);
787 _multiplyBy: function (num) {
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);
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);
809 // @method round(): Point
810 // Returns a copy of the current point with rounded coordinates.
812 return this.clone()._round();
815 _round: function () {
816 this.x = Math.round(this.x);
817 this.y = Math.round(this.y);
821 // @method floor(): Point
822 // Returns a copy of the current point with floored coordinates (rounded down).
824 return this.clone()._floor();
827 _floor: function () {
828 this.x = Math.floor(this.x);
829 this.y = Math.floor(this.y);
833 // @method ceil(): Point
834 // Returns a copy of the current point with ceiled coordinates (rounded up).
836 return this.clone()._ceil();
840 this.x = Math.ceil(this.x);
841 this.y = Math.ceil(this.y);
845 // @method trunc(): Point
846 // Returns a copy of the current point with truncated coordinates (rounded towards zero).
848 return this.clone()._trunc();
851 _trunc: function () {
852 this.x = trunc(this.x);
853 this.y = trunc(this.y);
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);
862 var x = point.x - this.x,
863 y = point.y - this.y;
865 return Math.sqrt(x * x + y * y);
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);
873 return point.x === this.x &&
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);
882 return Math.abs(point.x) <= Math.abs(this.x) &&
883 Math.abs(point.y) <= Math.abs(this.y);
886 // @method toString(): String
887 // Returns a string representation of the point for debugging purposes.
888 toString: function () {
890 formatNum(this.x) + ', ' +
891 formatNum(this.y) + ')';
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.
899 // @factory L.point(coords: Number[])
900 // Expects an array of the form `[x, y]` instead.
903 // @factory L.point(coords: Object)
904 // Expects a plain object of the form `{x: Number, y: Number}` instead.
905 function toPoint(x, y, round) {
906 if (x instanceof Point) {
910 return new Point(x[0], x[1]);
912 if (x === undefined || x === null) {
915 if (typeof x === 'object' && 'x' in x && 'y' in x) {
916 return new Point(x.x, x.y);
918 return new Point(x, y, round);
925 * Represents a rectangular area in pixel coordinates.
930 * var p1 = L.point(10, 10),
931 * p2 = L.point(40, 60),
932 * bounds = L.bounds(p1, p2);
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:
938 * otherBounds.intersects([[10, 10], [40, 60]]);
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.
946 function Bounds(a, b) {
949 var points = b ? [a, b] : a;
951 for (var i = 0, len = points.length; i < len; i++) {
952 this.extend(points[i]);
957 // @method extend(point: Point): this
958 // Extends the bounds to contain the given point.
959 extend: function (point) { // (Point)
960 point = toPoint(point);
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();
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);
978 // @method getCenter(round?: Boolean): Point
979 // Returns the center point of the bounds.
980 getCenter: function (round) {
982 (this.min.x + this.max.x) / 2,
983 (this.min.y + this.max.y) / 2, round);
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);
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);
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
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
1010 // @method getSize(): Point
1011 // Returns the size of the given bounds
1012 getSize: function () {
1013 return this.max.subtract(this.min);
1016 // @method contains(otherBounds: Bounds): Boolean
1017 // Returns `true` if the rectangle contains the given one.
1019 // @method contains(point: Point): Boolean
1020 // Returns `true` if the rectangle contains the given point.
1021 contains: function (obj) {
1024 if (typeof obj[0] === 'number' || obj instanceof Point) {
1027 obj = toBounds(obj);
1030 if (obj instanceof Bounds) {
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);
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);
1053 xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1054 yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1056 return xIntersects && yIntersects;
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);
1069 xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1070 yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1072 return xOverlaps && yOverlaps;
1075 isValid: function () {
1076 return !!(this.min && this.max);
1081 // @factory L.bounds(corner1: Point, corner2: Point)
1082 // Creates a Bounds object from two corners coordinate pairs.
1084 // @factory L.bounds(points: Point[])
1085 // Creates a Bounds object from the given array of points.
1086 function toBounds(a, b) {
1087 if (!a || a instanceof Bounds) {
1090 return new Bounds(a, b);
1094 * @class LatLngBounds
1095 * @aka L.LatLngBounds
1097 * Represents a rectangular geographical area on a map.
1102 * var corner1 = L.latLng(40.712, -74.227),
1103 * corner2 = L.latLng(40.774, -74.125),
1104 * bounds = L.latLngBounds(corner1, corner2);
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:
1111 * [40.712, -74.227],
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.
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.
1123 function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
1124 if (!corner1) { return; }
1126 var latlngs = corner2 ? [corner1, corner2] : corner1;
1128 for (var i = 0, len = latlngs.length; i < len; i++) {
1129 this.extend(latlngs[i]);
1133 LatLngBounds.prototype = {
1135 // @method extend(latlng: LatLng): this
1136 // Extend the bounds to contain the given point
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,
1146 if (obj instanceof LatLng) {
1150 } else if (obj instanceof LatLngBounds) {
1151 sw2 = obj._southWest;
1152 ne2 = obj._northEast;
1154 if (!sw2 || !ne2) { return this; }
1157 return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
1161 this._southWest = new LatLng(sw2.lat, sw2.lng);
1162 this._northEast = new LatLng(ne2.lat, ne2.lng);
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);
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;
1183 return new LatLngBounds(
1184 new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1185 new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1188 // @method getCenter(): LatLng
1189 // Returns the center point of the bounds.
1190 getCenter: function () {
1192 (this._southWest.lat + this._northEast.lat) / 2,
1193 (this._southWest.lng + this._northEast.lng) / 2);
1196 // @method getSouthWest(): LatLng
1197 // Returns the south-west point of the bounds.
1198 getSouthWest: function () {
1199 return this._southWest;
1202 // @method getNorthEast(): LatLng
1203 // Returns the north-east point of the bounds.
1204 getNorthEast: function () {
1205 return this._northEast;
1208 // @method getNorthWest(): LatLng
1209 // Returns the north-west point of the bounds.
1210 getNorthWest: function () {
1211 return new LatLng(this.getNorth(), this.getWest());
1214 // @method getSouthEast(): LatLng
1215 // Returns the south-east point of the bounds.
1216 getSouthEast: function () {
1217 return new LatLng(this.getSouth(), this.getEast());
1220 // @method getWest(): Number
1221 // Returns the west longitude of the bounds
1222 getWest: function () {
1223 return this._southWest.lng;
1226 // @method getSouth(): Number
1227 // Returns the south latitude of the bounds
1228 getSouth: function () {
1229 return this._southWest.lat;
1232 // @method getEast(): Number
1233 // Returns the east longitude of the bounds
1234 getEast: function () {
1235 return this._northEast.lng;
1238 // @method getNorth(): Number
1239 // Returns the north latitude of the bounds
1240 getNorth: function () {
1241 return this._northEast.lat;
1244 // @method contains(otherBounds: LatLngBounds): Boolean
1245 // Returns `true` if the rectangle contains the given one.
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);
1254 obj = toLatLngBounds(obj);
1257 var sw = this._southWest,
1258 ne = this._northEast,
1261 if (obj instanceof LatLngBounds) {
1262 sw2 = obj.getSouthWest();
1263 ne2 = obj.getNorthEast();
1268 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1269 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
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);
1277 var sw = this._southWest,
1278 ne = this._northEast,
1279 sw2 = bounds.getSouthWest(),
1280 ne2 = bounds.getNorthEast(),
1282 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1283 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1285 return latIntersects && lngIntersects;
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);
1293 var sw = this._southWest,
1294 ne = this._northEast,
1295 sw2 = bounds.getSouthWest(),
1296 ne2 = bounds.getNorthEast(),
1298 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1299 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1301 return latOverlaps && lngOverlaps;
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(',');
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; }
1315 bounds = toLatLngBounds(bounds);
1317 return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
1318 this._northEast.equals(bounds.getNorthEast(), maxMargin);
1321 // @method isValid(): Boolean
1322 // Returns `true` if the bounds are properly initialized.
1323 isValid: function () {
1324 return !!(this._southWest && this._northEast);
1328 // TODO International date line?
1330 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
1331 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
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).
1336 function toLatLngBounds(a, b) {
1337 if (a instanceof LatLngBounds) {
1340 return new LatLngBounds(a, b);
1346 * Represents a geographical point with a certain latitude and longitude.
1351 * var latlng = L.latLng(50.5, 30.5);
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:
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));
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.
1368 function LatLng(lat, lng, alt) {
1369 if (isNaN(lat) || isNaN(lng)) {
1370 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1373 // @property lat: Number
1374 // Latitude in degrees
1377 // @property lng: Number
1378 // Longitude in degrees
1381 // @property alt: Number
1382 // Altitude in meters (optional)
1383 if (alt !== undefined) {
1388 LatLng.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; }
1394 obj = toLatLng(obj);
1396 var margin = Math.max(
1397 Math.abs(this.lat - obj.lat),
1398 Math.abs(this.lng - obj.lng));
1400 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1403 // @method toString(): String
1404 // Returns a string representation of the point (for debugging purposes).
1405 toString: function (precision) {
1407 formatNum(this.lat, precision) + ', ' +
1408 formatNum(this.lng, precision) + ')';
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));
1417 // @method wrap(): LatLng
1418 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1420 return Earth.wrapLatLng(this);
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);
1429 return toLatLngBounds(
1430 [this.lat - latAccuracy, this.lng - lngAccuracy],
1431 [this.lat + latAccuracy, this.lng + lngAccuracy]);
1434 clone: function () {
1435 return new LatLng(this.lat, this.lng, this.alt);
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).
1445 // @factory L.latLng(coords: Array): LatLng
1446 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
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.
1452 function toLatLng(a, b, c) {
1453 if (a instanceof LatLng) {
1456 if (isArray(a) && typeof a[0] !== 'object') {
1457 if (a.length === 3) {
1458 return new LatLng(a[0], a[1], a[2]);
1460 if (a.length === 2) {
1461 return new LatLng(a[0], a[1]);
1465 if (a === undefined || a === null) {
1468 if (typeof a === 'object' && 'lat' in a) {
1469 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1471 if (b === undefined) {
1474 return new LatLng(a, b, c);
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).
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.
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.
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);
1501 return this.transformation._transform(projectedPoint, scale);
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);
1511 return this.projection.unproject(untransformedPoint);
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);
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);
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);
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;
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; }
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);
1553 return new Bounds(min, max);
1556 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
1557 // Returns the distance between two geographical coordinates.
1559 // @property code: String
1560 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
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.
1567 // @property wrapLat: Number[]
1568 // Like `wrapLng`, but for the latitude (vertical) axis.
1570 // wrapLng: [min, max],
1571 // wrapLat: [min, max],
1573 // @property infinite: Boolean
1574 // If true, the coordinate space will be unbounded (infinite in both axes)
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,
1585 return new LatLng(lat, lng, alt);
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;
1598 if (latShift === 0 && lngShift === 0) {
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);
1607 return new LatLngBounds(newSw, newNe);
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
1621 var Earth = extend({}, CRS, {
1622 wrapLng: [-180, 180],
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
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));
1643 * @namespace Projection
1644 * @projection L.Projection.SphericalMercator
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.
1651 var SphericalMercator = {
1654 MAX_LATITUDE: 85.0511287798,
1656 project: function (latlng) {
1657 var d = Math.PI / 180,
1658 max = this.MAX_LATITUDE,
1659 lat = Math.max(Math.min(max, latlng.lat), -max),
1660 sin = Math.sin(lat * d);
1663 this.R * latlng.lng * d,
1664 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
1667 unproject: function (point) {
1668 var d = 180 / Math.PI;
1671 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
1672 point.x * d / this.R);
1675 bounds: (function () {
1676 var d = 6378137 * Math.PI;
1677 return new Bounds([-d, -d], [d, d]);
1682 * @class Transformation
1683 * @aka L.Transformation
1685 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1686 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1687 * the reverse. Used by Leaflet in its projections code.
1692 * var transformation = L.transformation(2, 5, -1, 10),
1693 * p = L.point(1, 2),
1694 * p2 = transformation.transform(p), // L.point(7, 8)
1695 * p3 = transformation.untransform(p2); // L.point(1, 2)
1700 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1701 // Creates a `Transformation` object with the given coefficients.
1702 function Transformation(a, b, c, d) {
1704 // use array properties
1717 Transformation.prototype = {
1718 // @method transform(point: Point, scale?: Number): Point
1719 // Returns a transformed point, optionally multiplied by the given scale.
1720 // Only accepts actual `L.Point` instances, not arrays.
1721 transform: function (point, scale) { // (Point, Number) -> Point
1722 return this._transform(point.clone(), scale);
1725 // destructive transform (faster)
1726 _transform: function (point, scale) {
1728 point.x = scale * (this._a * point.x + this._b);
1729 point.y = scale * (this._c * point.y + this._d);
1733 // @method untransform(point: Point, scale?: Number): Point
1734 // Returns the reverse transformation of the given point, optionally divided
1735 // by the given scale. Only accepts actual `L.Point` instances, not arrays.
1736 untransform: function (point, scale) {
1739 (point.x / scale - this._b) / this._a,
1740 (point.y / scale - this._d) / this._c);
1744 // factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1746 // @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1747 // Instantiates a Transformation object with the given coefficients.
1750 // @factory L.transformation(coefficients: Array): Transformation
1751 // Expects an coefficients array of the form
1752 // `[a: Number, b: Number, c: Number, d: Number]`.
1754 function toTransformation(a, b, c, d) {
1755 return new Transformation(a, b, c, d);
1760 * @crs L.CRS.EPSG3857
1762 * The most common CRS for online maps, used by almost all free and commercial
1763 * tile providers. Uses Spherical Mercator projection. Set in by default in
1764 * Map's `crs` option.
1767 var EPSG3857 = extend({}, Earth, {
1769 projection: SphericalMercator,
1771 transformation: (function () {
1772 var scale = 0.5 / (Math.PI * SphericalMercator.R);
1773 return toTransformation(scale, 0.5, -scale, 0.5);
1777 var EPSG900913 = extend({}, EPSG3857, {
1781 // @namespace SVG; @section
1782 // There are several static functions which can be called without instantiating L.SVG:
1784 // @function create(name: String): SVGElement
1785 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
1786 // corresponding to the class name passed. For example, using 'line' will return
1787 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
1788 function svgCreate(name) {
1789 return document.createElementNS('http://www.w3.org/2000/svg', name);
1792 // @function pointsToPath(rings: Point[], closed: Boolean): String
1793 // Generates a SVG path string for multiple rings, with each ring turning
1794 // into "M..L..L.." instructions
1795 function pointsToPath(rings, closed) {
1797 i, j, len, len2, points, p;
1799 for (i = 0, len = rings.length; i < len; i++) {
1802 for (j = 0, len2 = points.length; j < len2; j++) {
1804 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
1807 // closes the ring for polygons; "x" is VML syntax
1808 str += closed ? (svg ? 'z' : 'x') : '';
1811 // SVG complains about empty path strings
1812 return str || 'M0 0';
1816 * @namespace Browser
1819 * A namespace with static properties for browser/feature detection used by Leaflet internally.
1824 * if (L.Browser.ielt9) {
1825 * alert('Upgrade your browser, dude!');
1830 var style$1 = document.documentElement.style;
1832 // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
1833 var ie = 'ActiveXObject' in window;
1835 // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
1836 var ielt9 = ie && !document.addEventListener;
1838 // @property edge: Boolean; `true` for the Edge web browser.
1839 var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
1841 // @property webkit: Boolean;
1842 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
1843 var webkit = userAgentContains('webkit');
1845 // @property android: Boolean
1846 // `true` for any browser running on an Android platform.
1847 var android = userAgentContains('android');
1849 // @property android23: Boolean; `true` for browsers running on Android 2 or Android 3.
1850 var android23 = userAgentContains('android 2') || userAgentContains('android 3');
1852 /* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */
1853 var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit
1854 // @property androidStock: Boolean; `true` for the Android stock browser (i.e. not Chrome)
1855 var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);
1857 // @property opera: Boolean; `true` for the Opera browser
1858 var opera = !!window.opera;
1860 // @property chrome: Boolean; `true` for the Chrome browser.
1861 var chrome = userAgentContains('chrome');
1863 // @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
1864 var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
1866 // @property safari: Boolean; `true` for the Safari browser.
1867 var safari = !chrome && userAgentContains('safari');
1869 var phantom = userAgentContains('phantom');
1871 // @property opera12: Boolean
1872 // `true` for the Opera browser supporting CSS transforms (version 12 or later).
1873 var opera12 = 'OTransition' in style$1;
1875 // @property win: Boolean; `true` when the browser is running in a Windows platform
1876 var win = navigator.platform.indexOf('Win') === 0;
1878 // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
1879 var ie3d = ie && ('transition' in style$1);
1881 // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
1882 var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;
1884 // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
1885 var gecko3d = 'MozPerspective' in style$1;
1887 // @property any3d: Boolean
1888 // `true` for all browsers supporting CSS transforms.
1889 var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
1891 // @property mobile: Boolean; `true` for all browsers running in a mobile device.
1892 var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
1894 // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
1895 var mobileWebkit = mobile && webkit;
1897 // @property mobileWebkit3d: Boolean
1898 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
1899 var mobileWebkit3d = mobile && webkit3d;
1901 // @property msPointer: Boolean
1902 // `true` for browsers implementing the Microsoft touch events model (notably IE10).
1903 var msPointer = !window.PointerEvent && window.MSPointerEvent;
1905 // @property pointer: Boolean
1906 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
1907 var pointer = !!(window.PointerEvent || msPointer);
1909 // @property touch: Boolean
1910 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
1911 // This does not necessarily mean that the browser is running in a computer with
1912 // a touchscreen, it only means that the browser is capable of understanding
1914 var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
1915 (window.DocumentTouch && document instanceof window.DocumentTouch));
1917 // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
1918 var mobileOpera = mobile && opera;
1920 // @property mobileGecko: Boolean
1921 // `true` for gecko-based browsers running in a mobile device.
1922 var mobileGecko = mobile && gecko;
1924 // @property retina: Boolean
1925 // `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%.
1926 var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
1929 // @property canvas: Boolean
1930 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
1931 var canvas = (function () {
1932 return !!document.createElement('canvas').getContext;
1935 // @property svg: Boolean
1936 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
1937 var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect);
1939 // @property vml: Boolean
1940 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
1941 var vml = !svg && (function () {
1943 var div = document.createElement('div');
1944 div.innerHTML = '<v:shape adj="1"/>';
1946 var shape = div.firstChild;
1947 shape.style.behavior = 'url(#default#VML)';
1949 return shape && (typeof shape.adj === 'object');
1957 function userAgentContains(str) {
1958 return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
1962 var Browser = (Object.freeze || Object)({
1968 android23: android23,
1969 androidStock: androidStock,
1982 mobileWebkit: mobileWebkit,
1983 mobileWebkit3d: mobileWebkit3d,
1984 msPointer: msPointer,
1987 mobileOpera: mobileOpera,
1988 mobileGecko: mobileGecko,
1996 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
2000 var POINTER_DOWN = msPointer ? 'MSPointerDown' : 'pointerdown';
2001 var POINTER_MOVE = msPointer ? 'MSPointerMove' : 'pointermove';
2002 var POINTER_UP = msPointer ? 'MSPointerUp' : 'pointerup';
2003 var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel';
2004 var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION'];
2007 var _pointerDocListener = false;
2009 // DomEvent.DoubleTap needs to know about this
2010 var _pointersCount = 0;
2012 // Provides a touch events wrapper for (ms)pointer events.
2013 // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
2015 function addPointerListener(obj, type, handler, id) {
2016 if (type === 'touchstart') {
2017 _addPointerStart(obj, handler, id);
2019 } else if (type === 'touchmove') {
2020 _addPointerMove(obj, handler, id);
2022 } else if (type === 'touchend') {
2023 _addPointerEnd(obj, handler, id);
2029 function removePointerListener(obj, type, id) {
2030 var handler = obj['_leaflet_' + type + id];
2032 if (type === 'touchstart') {
2033 obj.removeEventListener(POINTER_DOWN, handler, false);
2035 } else if (type === 'touchmove') {
2036 obj.removeEventListener(POINTER_MOVE, handler, false);
2038 } else if (type === 'touchend') {
2039 obj.removeEventListener(POINTER_UP, handler, false);
2040 obj.removeEventListener(POINTER_CANCEL, handler, false);
2046 function _addPointerStart(obj, handler, id) {
2047 var onDown = bind(function (e) {
2048 if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
2049 // In IE11, some touch events needs to fire for form controls, or
2050 // the controls will stop working. We keep a whitelist of tag names that
2051 // need these events. For other target tags, we prevent default on the event.
2052 if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
2059 _handlePointer(e, handler);
2062 obj['_leaflet_touchstart' + id] = onDown;
2063 obj.addEventListener(POINTER_DOWN, onDown, false);
2065 // need to keep track of what pointers and how many are active to provide e.touches emulation
2066 if (!_pointerDocListener) {
2067 // we listen documentElement as any drags that end by moving the touch off the screen get fired there
2068 document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true);
2069 document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true);
2070 document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true);
2071 document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
2073 _pointerDocListener = true;
2077 function _globalPointerDown(e) {
2078 _pointers[e.pointerId] = e;
2082 function _globalPointerMove(e) {
2083 if (_pointers[e.pointerId]) {
2084 _pointers[e.pointerId] = e;
2088 function _globalPointerUp(e) {
2089 delete _pointers[e.pointerId];
2093 function _handlePointer(e, handler) {
2095 for (var i in _pointers) {
2096 e.touches.push(_pointers[i]);
2098 e.changedTouches = [e];
2103 function _addPointerMove(obj, handler, id) {
2104 var onMove = function (e) {
2105 // don't fire touch moves when mouse isn't down
2106 if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
2108 _handlePointer(e, handler);
2111 obj['_leaflet_touchmove' + id] = onMove;
2112 obj.addEventListener(POINTER_MOVE, onMove, false);
2115 function _addPointerEnd(obj, handler, id) {
2116 var onUp = function (e) {
2117 _handlePointer(e, handler);
2120 obj['_leaflet_touchend' + id] = onUp;
2121 obj.addEventListener(POINTER_UP, onUp, false);
2122 obj.addEventListener(POINTER_CANCEL, onUp, false);
2126 * Extends the event handling code with double tap support for mobile browsers.
2129 var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart';
2130 var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend';
2131 var _pre = '_leaflet_';
2133 // inspired by Zepto touch code by Thomas Fuchs
2134 function addDoubleTapListener(obj, handler, id) {
2139 function onTouchStart(e) {
2143 if ((!edge) || e.pointerType === 'mouse') { return; }
2144 count = _pointersCount;
2146 count = e.touches.length;
2149 if (count > 1) { return; }
2151 var now = Date.now(),
2152 delta = now - (last || now);
2154 touch$$1 = e.touches ? e.touches[0] : e;
2155 doubleTap = (delta > 0 && delta <= delay);
2159 function onTouchEnd(e) {
2160 if (doubleTap && !touch$$1.cancelBubble) {
2162 if ((!edge) || e.pointerType === 'mouse') { return; }
2163 // work around .type being readonly with MSPointer* events
2167 for (i in touch$$1) {
2169 newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop;
2171 touch$$1 = newTouch;
2173 touch$$1.type = 'dblclick';
2179 obj[_pre + _touchstart + id] = onTouchStart;
2180 obj[_pre + _touchend + id] = onTouchEnd;
2181 obj[_pre + 'dblclick' + id] = handler;
2183 obj.addEventListener(_touchstart, onTouchStart, false);
2184 obj.addEventListener(_touchend, onTouchEnd, false);
2186 // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
2187 // the browser doesn't fire touchend/pointerup events but does fire
2188 // native dblclicks. See #4127.
2189 // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
2190 obj.addEventListener('dblclick', handler, false);
2195 function removeDoubleTapListener(obj, id) {
2196 var touchstart = obj[_pre + _touchstart + id],
2197 touchend = obj[_pre + _touchend + id],
2198 dblclick = obj[_pre + 'dblclick' + id];
2200 obj.removeEventListener(_touchstart, touchstart, false);
2201 obj.removeEventListener(_touchend, touchend, false);
2203 obj.removeEventListener('dblclick', dblclick, false);
2210 * @namespace DomUtil
2212 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
2213 * tree, used by Leaflet internally.
2215 * Most functions expecting or returning a `HTMLElement` also work for
2216 * SVG elements. The only difference is that classes refer to CSS classes
2217 * in HTML and SVG classes in SVG.
2221 // @property TRANSFORM: String
2222 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
2223 var TRANSFORM = testProp(
2224 ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
2226 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
2227 // the same for the transitionend event, in particular the Android 4.1 stock browser
2229 // @property TRANSITION: String
2230 // Vendor-prefixed transition style name.
2231 var TRANSITION = testProp(
2232 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
2234 // @property TRANSITION_END: String
2235 // Vendor-prefixed transitionend event name.
2236 var TRANSITION_END =
2237 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
2240 // @function get(id: String|HTMLElement): HTMLElement
2241 // Returns an element given its DOM id, or returns the element itself
2242 // if it was passed directly.
2244 return typeof id === 'string' ? document.getElementById(id) : id;
2247 // @function getStyle(el: HTMLElement, styleAttrib: String): String
2248 // Returns the value for a certain style attribute on an element,
2249 // including computed values or values set through CSS.
2250 function getStyle(el, style) {
2251 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
2253 if ((!value || value === 'auto') && document.defaultView) {
2254 var css = document.defaultView.getComputedStyle(el, null);
2255 value = css ? css[style] : null;
2257 return value === 'auto' ? null : value;
2260 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
2261 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
2262 function create$1(tagName, className, container) {
2263 var el = document.createElement(tagName);
2264 el.className = className || '';
2267 container.appendChild(el);
2272 // @function remove(el: HTMLElement)
2273 // Removes `el` from its parent element
2274 function remove(el) {
2275 var parent = el.parentNode;
2277 parent.removeChild(el);
2281 // @function empty(el: HTMLElement)
2282 // Removes all of `el`'s children elements from `el`
2283 function empty(el) {
2284 while (el.firstChild) {
2285 el.removeChild(el.firstChild);
2289 // @function toFront(el: HTMLElement)
2290 // Makes `el` the last child of its parent, so it renders in front of the other children.
2291 function toFront(el) {
2292 var parent = el.parentNode;
2293 if (parent.lastChild !== el) {
2294 parent.appendChild(el);
2298 // @function toBack(el: HTMLElement)
2299 // Makes `el` the first child of its parent, so it renders behind the other children.
2300 function toBack(el) {
2301 var parent = el.parentNode;
2302 if (parent.firstChild !== el) {
2303 parent.insertBefore(el, parent.firstChild);
2307 // @function hasClass(el: HTMLElement, name: String): Boolean
2308 // Returns `true` if the element's class attribute contains `name`.
2309 function hasClass(el, name) {
2310 if (el.classList !== undefined) {
2311 return el.classList.contains(name);
2313 var className = getClass(el);
2314 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
2317 // @function addClass(el: HTMLElement, name: String)
2318 // Adds `name` to the element's class attribute.
2319 function addClass(el, name) {
2320 if (el.classList !== undefined) {
2321 var classes = splitWords(name);
2322 for (var i = 0, len = classes.length; i < len; i++) {
2323 el.classList.add(classes[i]);
2325 } else if (!hasClass(el, name)) {
2326 var className = getClass(el);
2327 setClass(el, (className ? className + ' ' : '') + name);
2331 // @function removeClass(el: HTMLElement, name: String)
2332 // Removes `name` from the element's class attribute.
2333 function removeClass(el, name) {
2334 if (el.classList !== undefined) {
2335 el.classList.remove(name);
2337 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
2341 // @function setClass(el: HTMLElement, name: String)
2342 // Sets the element's class.
2343 function setClass(el, name) {
2344 if (el.className.baseVal === undefined) {
2345 el.className = name;
2347 // in case of SVG element
2348 el.className.baseVal = name;
2352 // @function getClass(el: HTMLElement): String
2353 // Returns the element's class.
2354 function getClass(el) {
2355 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
2358 // @function setOpacity(el: HTMLElement, opacity: Number)
2359 // Set the opacity of an element (including old IE support).
2360 // `opacity` must be a number from `0` to `1`.
2361 function setOpacity(el, value) {
2362 if ('opacity' in el.style) {
2363 el.style.opacity = value;
2364 } else if ('filter' in el.style) {
2365 _setOpacityIE(el, value);
2369 function _setOpacityIE(el, value) {
2371 filterName = 'DXImageTransform.Microsoft.Alpha';
2373 // filters collection throws an error if we try to retrieve a filter that doesn't exist
2375 filter = el.filters.item(filterName);
2377 // don't set opacity to 1 if we haven't already set an opacity,
2378 // it isn't needed and breaks transparent pngs.
2379 if (value === 1) { return; }
2382 value = Math.round(value * 100);
2385 filter.Enabled = (value !== 100);
2386 filter.Opacity = value;
2388 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
2392 // @function testProp(props: String[]): String|false
2393 // Goes through the array of style names and returns the first name
2394 // that is a valid style name for an element. If no such name is found,
2395 // it returns false. Useful for vendor-prefixed styles like `transform`.
2396 function testProp(props) {
2397 var style = document.documentElement.style;
2399 for (var i = 0; i < props.length; i++) {
2400 if (props[i] in style) {
2407 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
2408 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
2409 // and optionally scaled by `scale`. Does not have an effect if the
2410 // browser doesn't support 3D CSS transforms.
2411 function setTransform(el, offset, scale) {
2412 var pos = offset || new Point(0, 0);
2414 el.style[TRANSFORM] =
2416 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
2417 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
2418 (scale ? ' scale(' + scale + ')' : '');
2421 // @function setPosition(el: HTMLElement, position: Point)
2422 // Sets the position of `el` to coordinates specified by `position`,
2423 // using CSS translate or top/left positioning depending on the browser
2424 // (used by Leaflet internally to position its layers).
2425 function setPosition(el, point) {
2428 el._leaflet_pos = point;
2432 setTransform(el, point);
2434 el.style.left = point.x + 'px';
2435 el.style.top = point.y + 'px';
2439 // @function getPosition(el: HTMLElement): Point
2440 // Returns the coordinates of an element previously positioned with setPosition.
2441 function getPosition(el) {
2442 // this method is only used for elements previously positioned using setPosition,
2443 // so it's safe to cache the position for performance
2445 return el._leaflet_pos || new Point(0, 0);
2448 // @function disableTextSelection()
2449 // Prevents the user from generating `selectstart` DOM events, usually generated
2450 // when the user drags the mouse through a page with text. Used internally
2451 // by Leaflet to override the behaviour of any click-and-drag interaction on
2452 // the map. Affects drag interactions on the whole document.
2454 // @function enableTextSelection()
2455 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
2456 var disableTextSelection;
2457 var enableTextSelection;
2459 if ('onselectstart' in document) {
2460 disableTextSelection = function () {
2461 on(window, 'selectstart', preventDefault);
2463 enableTextSelection = function () {
2464 off(window, 'selectstart', preventDefault);
2467 var userSelectProperty = testProp(
2468 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
2470 disableTextSelection = function () {
2471 if (userSelectProperty) {
2472 var style = document.documentElement.style;
2473 _userSelect = style[userSelectProperty];
2474 style[userSelectProperty] = 'none';
2477 enableTextSelection = function () {
2478 if (userSelectProperty) {
2479 document.documentElement.style[userSelectProperty] = _userSelect;
2480 _userSelect = undefined;
2485 // @function disableImageDrag()
2486 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
2487 // for `dragstart` DOM events, usually generated when the user drags an image.
2488 function disableImageDrag() {
2489 on(window, 'dragstart', preventDefault);
2492 // @function enableImageDrag()
2493 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
2494 function enableImageDrag() {
2495 off(window, 'dragstart', preventDefault);
2498 var _outlineElement;
2500 // @function preventOutline(el: HTMLElement)
2501 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
2502 // of the element `el` invisible. Used internally by Leaflet to prevent
2503 // focusable elements from displaying an outline when the user performs a
2504 // drag interaction on them.
2505 function preventOutline(element) {
2506 while (element.tabIndex === -1) {
2507 element = element.parentNode;
2509 if (!element.style) { return; }
2511 _outlineElement = element;
2512 _outlineStyle = element.style.outline;
2513 element.style.outline = 'none';
2514 on(window, 'keydown', restoreOutline);
2517 // @function restoreOutline()
2518 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
2519 function restoreOutline() {
2520 if (!_outlineElement) { return; }
2521 _outlineElement.style.outline = _outlineStyle;
2522 _outlineElement = undefined;
2523 _outlineStyle = undefined;
2524 off(window, 'keydown', restoreOutline);
2527 // @function getSizedParentNode(el: HTMLElement): HTMLElement
2528 // Finds the closest parent node which size (width and height) is not null.
2529 function getSizedParentNode(element) {
2531 element = element.parentNode;
2532 } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
2536 // @function getScale(el: HTMLElement): Object
2537 // Computes the CSS scale currently applied on the element.
2538 // Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
2539 // and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
2540 function getScale(element) {
2541 var rect = element.getBoundingClientRect(); // Read-only in old browsers.
2544 x: rect.width / element.offsetWidth || 1,
2545 y: rect.height / element.offsetHeight || 1,
2546 boundingClientRect: rect
2551 var DomUtil = (Object.freeze || Object)({
2552 TRANSFORM: TRANSFORM,
2553 TRANSITION: TRANSITION,
2554 TRANSITION_END: TRANSITION_END,
2564 removeClass: removeClass,
2567 setOpacity: setOpacity,
2569 setTransform: setTransform,
2570 setPosition: setPosition,
2571 getPosition: getPosition,
2572 disableTextSelection: disableTextSelection,
2573 enableTextSelection: enableTextSelection,
2574 disableImageDrag: disableImageDrag,
2575 enableImageDrag: enableImageDrag,
2576 preventOutline: preventOutline,
2577 restoreOutline: restoreOutline,
2578 getSizedParentNode: getSizedParentNode,
2583 * @namespace DomEvent
2584 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
2587 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
2589 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
2590 // Adds a listener function (`fn`) to a particular DOM event type of the
2591 // element `el`. You can optionally specify the context of the listener
2592 // (object the `this` keyword will point to). You can also pass several
2593 // space-separated types (e.g. `'click dblclick'`).
2596 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
2597 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2598 function on(obj, types, fn, context) {
2600 if (typeof types === 'object') {
2601 for (var type in types) {
2602 addOne(obj, type, types[type], fn);
2605 types = splitWords(types);
2607 for (var i = 0, len = types.length; i < len; i++) {
2608 addOne(obj, types[i], fn, context);
2615 var eventsKey = '_leaflet_events';
2617 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
2618 // Removes a previously added listener function.
2619 // Note that if you passed a custom context to on, you must pass the same
2620 // context to `off` in order to remove the listener.
2623 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
2624 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2625 function off(obj, types, fn, context) {
2627 if (typeof types === 'object') {
2628 for (var type in types) {
2629 removeOne(obj, type, types[type], fn);
2632 types = splitWords(types);
2634 for (var i = 0, len = types.length; i < len; i++) {
2635 removeOne(obj, types[i], fn, context);
2638 for (var j in obj[eventsKey]) {
2639 removeOne(obj, j, obj[eventsKey][j]);
2641 delete obj[eventsKey];
2647 function addOne(obj, type, fn, context) {
2648 var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
2650 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
2652 var handler = function (e) {
2653 return fn.call(context || obj, e || window.event);
2656 var originalHandler = handler;
2658 if (pointer && type.indexOf('touch') === 0) {
2659 // Needs DomEvent.Pointer.js
2660 addPointerListener(obj, type, handler, id);
2662 } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
2663 !(pointer && chrome)) {
2664 // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
2666 addDoubleTapListener(obj, handler, id);
2668 } else if ('addEventListener' in obj) {
2670 if (type === 'mousewheel') {
2671 obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2673 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
2674 handler = function (e) {
2675 e = e || window.event;
2676 if (isExternalTarget(obj, e)) {
2680 obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
2683 if (type === 'click' && android) {
2684 handler = function (e) {
2685 filterClick(e, originalHandler);
2688 obj.addEventListener(type, handler, false);
2691 } else if ('attachEvent' in obj) {
2692 obj.attachEvent('on' + type, handler);
2695 obj[eventsKey] = obj[eventsKey] || {};
2696 obj[eventsKey][id] = handler;
2699 function removeOne(obj, type, fn, context) {
2701 var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
2702 handler = obj[eventsKey] && obj[eventsKey][id];
2704 if (!handler) { return this; }
2706 if (pointer && type.indexOf('touch') === 0) {
2707 removePointerListener(obj, type, id);
2709 } else if (touch && (type === 'dblclick') && removeDoubleTapListener &&
2710 !(pointer && chrome)) {
2711 removeDoubleTapListener(obj, id);
2713 } else if ('removeEventListener' in obj) {
2715 if (type === 'mousewheel') {
2716 obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2719 obj.removeEventListener(
2720 type === 'mouseenter' ? 'mouseover' :
2721 type === 'mouseleave' ? 'mouseout' : type, handler, false);
2724 } else if ('detachEvent' in obj) {
2725 obj.detachEvent('on' + type, handler);
2728 obj[eventsKey][id] = null;
2731 // @function stopPropagation(ev: DOMEvent): this
2732 // Stop the given event from propagation to parent elements. Used inside the listener functions:
2734 // L.DomEvent.on(div, 'click', function (ev) {
2735 // L.DomEvent.stopPropagation(ev);
2738 function stopPropagation(e) {
2740 if (e.stopPropagation) {
2741 e.stopPropagation();
2742 } else if (e.originalEvent) { // In case of Leaflet event.
2743 e.originalEvent._stopped = true;
2745 e.cancelBubble = true;
2752 // @function disableScrollPropagation(el: HTMLElement): this
2753 // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
2754 function disableScrollPropagation(el) {
2755 addOne(el, 'mousewheel', stopPropagation);
2759 // @function disableClickPropagation(el: HTMLElement): this
2760 // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
2761 // `'mousedown'` and `'touchstart'` events (plus browser variants).
2762 function disableClickPropagation(el) {
2763 on(el, 'mousedown touchstart dblclick', stopPropagation);
2764 addOne(el, 'click', fakeStop);
2768 // @function preventDefault(ev: DOMEvent): this
2769 // Prevents the default action of the DOM Event `ev` from happening (such as
2770 // following a link in the href of the a element, or doing a POST request
2771 // with page reload when a `<form>` is submitted).
2772 // Use it inside listener functions.
2773 function preventDefault(e) {
2774 if (e.preventDefault) {
2777 e.returnValue = false;
2782 // @function stop(ev: DOMEvent): this
2783 // Does `stopPropagation` and `preventDefault` at the same time.
2790 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
2791 // Gets normalized mouse position from a DOM event relative to the
2792 // `container` (border excluded) or to the whole page if not specified.
2793 function getMousePosition(e, container) {
2795 return new Point(e.clientX, e.clientY);
2798 var scale = getScale(container),
2799 offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y)
2802 // offset.left/top values are in page scale (like clientX/Y),
2803 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
2804 (e.clientX - offset.left) / scale.x - container.clientLeft,
2805 (e.clientY - offset.top) / scale.y - container.clientTop
2809 // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
2810 // and Firefox scrolls device pixels, not CSS pixels
2812 (win && chrome) ? 2 * window.devicePixelRatio :
2813 gecko ? window.devicePixelRatio : 1;
2815 // @function getWheelDelta(ev: DOMEvent): Number
2816 // Gets normalized wheel delta from a mousewheel DOM event, in vertical
2817 // pixels scrolled (negative if scrolling down).
2818 // Events from pointing devices without precise scrolling are mapped to
2819 // a best guess of 60 pixels.
2820 function getWheelDelta(e) {
2821 return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
2822 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
2823 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
2824 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
2825 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
2826 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
2827 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
2828 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
2832 var skipEvents = {};
2834 function fakeStop(e) {
2835 // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
2836 skipEvents[e.type] = true;
2839 function skipped(e) {
2840 var events = skipEvents[e.type];
2841 // reset when checking, as it's only used in map container and propagates outside of the map
2842 skipEvents[e.type] = false;
2846 // check if element really left/entered the event target (for mouseenter/mouseleave)
2847 function isExternalTarget(el, e) {
2849 var related = e.relatedTarget;
2851 if (!related) { return true; }
2854 while (related && (related !== el)) {
2855 related = related.parentNode;
2860 return (related !== el);
2865 // this is a horrible workaround for a bug in Android where a single touch triggers two click events
2866 function filterClick(e, handler) {
2867 var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
2868 elapsed = lastClick && (timeStamp - lastClick);
2870 // are they closer together than 500ms yet more than 100ms?
2871 // Android typically triggers them ~300ms apart while multiple listeners
2872 // on the same event should be triggered far faster;
2873 // or check if click is simulated on the element, and if it is, reject any non-simulated events
2875 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
2879 lastClick = timeStamp;
2887 var DomEvent = (Object.freeze || Object)({
2890 stopPropagation: stopPropagation,
2891 disableScrollPropagation: disableScrollPropagation,
2892 disableClickPropagation: disableClickPropagation,
2893 preventDefault: preventDefault,
2895 getMousePosition: getMousePosition,
2896 getWheelDelta: getWheelDelta,
2899 isExternalTarget: isExternalTarget,
2905 * @class PosAnimation
2906 * @aka L.PosAnimation
2908 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
2912 * var fx = new L.PosAnimation();
2913 * fx.run(el, [300, 500], 0.5);
2916 * @constructor L.PosAnimation()
2917 * Creates a `PosAnimation` object.
2921 var PosAnimation = Evented.extend({
2923 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
2924 // Run an animation of a given element to a new position, optionally setting
2925 // duration in seconds (`0.25` by default) and easing linearity factor (3rd
2926 // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
2927 // `0.5` by default).
2928 run: function (el, newPos, duration, easeLinearity) {
2932 this._inProgress = true;
2933 this._duration = duration || 0.25;
2934 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
2936 this._startPos = getPosition(el);
2937 this._offset = newPos.subtract(this._startPos);
2938 this._startTime = +new Date();
2940 // @event start: Event
2941 // Fired when the animation starts
2948 // Stops the animation (if currently running).
2950 if (!this._inProgress) { return; }
2956 _animate: function () {
2958 this._animId = requestAnimFrame(this._animate, this);
2962 _step: function (round) {
2963 var elapsed = (+new Date()) - this._startTime,
2964 duration = this._duration * 1000;
2966 if (elapsed < duration) {
2967 this._runFrame(this._easeOut(elapsed / duration), round);
2974 _runFrame: function (progress, round) {
2975 var pos = this._startPos.add(this._offset.multiplyBy(progress));
2979 setPosition(this._el, pos);
2981 // @event step: Event
2982 // Fired continuously during the animation.
2986 _complete: function () {
2987 cancelAnimFrame(this._animId);
2989 this._inProgress = false;
2990 // @event end: Event
2991 // Fired when the animation ends.
2995 _easeOut: function (t) {
2996 return 1 - Math.pow(1 - t, this._easeOutPower);
3005 * The central class of the API — it is used to create a map on a page and manipulate it.
3010 * // initialize the map on the "map" div with a given center and zoom
3011 * var map = L.map('map', {
3012 * center: [51.505, -0.09],
3019 var Map = Evented.extend({
3022 // @section Map State Options
3023 // @option crs: CRS = L.CRS.EPSG3857
3024 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
3025 // sure what it means.
3028 // @option center: LatLng = undefined
3029 // Initial geographic center of the map
3032 // @option zoom: Number = undefined
3033 // Initial map zoom level
3036 // @option minZoom: Number = *
3037 // Minimum zoom level of the map.
3038 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3039 // the lowest of their `minZoom` options will be used instead.
3042 // @option maxZoom: Number = *
3043 // Maximum zoom level of the map.
3044 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3045 // the highest of their `maxZoom` options will be used instead.
3048 // @option layers: Layer[] = []
3049 // Array of layers that will be added to the map initially
3052 // @option maxBounds: LatLngBounds = null
3053 // When this option is set, the map restricts the view to the given
3054 // geographical bounds, bouncing the user back if the user tries to pan
3055 // outside the view. To set the restriction dynamically, use
3056 // [`setMaxBounds`](#map-setmaxbounds) method.
3057 maxBounds: undefined,
3059 // @option renderer: Renderer = *
3060 // The default method for drawing vector layers on the map. `L.SVG`
3061 // or `L.Canvas` by default depending on browser support.
3062 renderer: undefined,
3065 // @section Animation Options
3066 // @option zoomAnimation: Boolean = true
3067 // Whether the map zoom animation is enabled. By default it's enabled
3068 // in all browsers that support CSS3 Transitions except Android.
3069 zoomAnimation: true,
3071 // @option zoomAnimationThreshold: Number = 4
3072 // Won't animate zoom if the zoom difference exceeds this value.
3073 zoomAnimationThreshold: 4,
3075 // @option fadeAnimation: Boolean = true
3076 // Whether the tile fade animation is enabled. By default it's enabled
3077 // in all browsers that support CSS3 Transitions except Android.
3078 fadeAnimation: true,
3080 // @option markerZoomAnimation: Boolean = true
3081 // Whether markers animate their zoom with the zoom animation, if disabled
3082 // they will disappear for the length of the animation. By default it's
3083 // enabled in all browsers that support CSS3 Transitions except Android.
3084 markerZoomAnimation: true,
3086 // @option transform3DLimit: Number = 2^23
3087 // Defines the maximum size of a CSS translation transform. The default
3088 // value should not be changed unless a web browser positions layers in
3089 // the wrong place after doing a large `panBy`.
3090 transform3DLimit: 8388608, // Precision limit of a 32-bit float
3092 // @section Interaction Options
3093 // @option zoomSnap: Number = 1
3094 // Forces the map's zoom level to always be a multiple of this, particularly
3095 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
3096 // By default, the zoom level snaps to the nearest integer; lower values
3097 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
3098 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
3101 // @option zoomDelta: Number = 1
3102 // Controls how much the map's zoom level will change after a
3103 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
3104 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
3105 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
3108 // @option trackResize: Boolean = true
3109 // Whether the map automatically handles browser window resize to update itself.
3113 initialize: function (id, options) { // (HTMLElement or String, Object)
3114 options = setOptions(this, options);
3116 this._initContainer(id);
3119 // hack for https://github.com/Leaflet/Leaflet/issues/1980
3120 this._onResize = bind(this._onResize, this);
3124 if (options.maxBounds) {
3125 this.setMaxBounds(options.maxBounds);
3128 if (options.zoom !== undefined) {
3129 this._zoom = this._limitZoom(options.zoom);
3132 if (options.center && options.zoom !== undefined) {
3133 this.setView(toLatLng(options.center), options.zoom, {reset: true});
3136 this._handlers = [];
3138 this._zoomBoundLayers = {};
3139 this._sizeChanged = true;
3141 this.callInitHooks();
3143 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
3144 this._zoomAnimated = TRANSITION && any3d && !mobileOpera &&
3145 this.options.zoomAnimation;
3147 // zoom transitions run with the same duration for all layers, so if one of transitionend events
3148 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
3149 if (this._zoomAnimated) {
3150 this._createAnimProxy();
3151 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
3154 this._addLayers(this.options.layers);
3158 // @section Methods for modifying map state
3160 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
3161 // Sets the view of the map (geographical center and zoom) with the given
3162 // animation options.
3163 setView: function (center, zoom, options) {
3165 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
3166 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
3167 options = options || {};
3171 if (this._loaded && !options.reset && options !== true) {
3173 if (options.animate !== undefined) {
3174 options.zoom = extend({animate: options.animate}, options.zoom);
3175 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
3178 // try animating pan or zoom
3179 var moved = (this._zoom !== zoom) ?
3180 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
3181 this._tryAnimatedPan(center, options.pan);
3184 // prevent resize handler call, the view will refresh after animation anyway
3185 clearTimeout(this._sizeTimer);
3190 // animation didn't start, just reset the map view
3191 this._resetView(center, zoom);
3196 // @method setZoom(zoom: Number, options?: Zoom/pan options): this
3197 // Sets the zoom of the map.
3198 setZoom: function (zoom, options) {
3199 if (!this._loaded) {
3203 return this.setView(this.getCenter(), zoom, {zoom: options});
3206 // @method zoomIn(delta?: Number, options?: Zoom options): this
3207 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3208 zoomIn: function (delta, options) {
3209 delta = delta || (any3d ? this.options.zoomDelta : 1);
3210 return this.setZoom(this._zoom + delta, options);
3213 // @method zoomOut(delta?: Number, options?: Zoom options): this
3214 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3215 zoomOut: function (delta, options) {
3216 delta = delta || (any3d ? this.options.zoomDelta : 1);
3217 return this.setZoom(this._zoom - delta, options);
3220 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
3221 // Zooms the map while keeping a specified geographical point on the map
3222 // stationary (e.g. used internally for scroll zoom and double-click zoom).
3224 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
3225 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
3226 setZoomAround: function (latlng, zoom, options) {
3227 var scale = this.getZoomScale(zoom),
3228 viewHalf = this.getSize().divideBy(2),
3229 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
3231 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
3232 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
3234 return this.setView(newCenter, zoom, {zoom: options});
3237 _getBoundsCenterZoom: function (bounds, options) {
3239 options = options || {};
3240 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
3242 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3243 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3245 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
3247 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
3249 if (zoom === Infinity) {
3251 center: bounds.getCenter(),
3256 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
3258 swPoint = this.project(bounds.getSouthWest(), zoom),
3259 nePoint = this.project(bounds.getNorthEast(), zoom),
3260 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
3268 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
3269 // Sets a map view that contains the given geographical bounds with the
3270 // maximum zoom level possible.
3271 fitBounds: function (bounds, options) {
3273 bounds = toLatLngBounds(bounds);
3275 if (!bounds.isValid()) {
3276 throw new Error('Bounds are not valid.');
3279 var target = this._getBoundsCenterZoom(bounds, options);
3280 return this.setView(target.center, target.zoom, options);
3283 // @method fitWorld(options?: fitBounds options): this
3284 // Sets a map view that mostly contains the whole world with the maximum
3285 // zoom level possible.
3286 fitWorld: function (options) {
3287 return this.fitBounds([[-90, -180], [90, 180]], options);
3290 // @method panTo(latlng: LatLng, options?: Pan options): this
3291 // Pans the map to a given center.
3292 panTo: function (center, options) { // (LatLng)
3293 return this.setView(center, this._zoom, {pan: options});
3296 // @method panBy(offset: Point, options?: Pan options): this
3297 // Pans the map by a given number of pixels (animated).
3298 panBy: function (offset, options) {
3299 offset = toPoint(offset).round();
3300 options = options || {};
3302 if (!offset.x && !offset.y) {
3303 return this.fire('moveend');
3305 // If we pan too far, Chrome gets issues with tiles
3306 // and makes them disappear or appear in the wrong place (slightly offset) #2602
3307 if (options.animate !== true && !this.getSize().contains(offset)) {
3308 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
3312 if (!this._panAnim) {
3313 this._panAnim = new PosAnimation();
3316 'step': this._onPanTransitionStep,
3317 'end': this._onPanTransitionEnd
3321 // don't fire movestart if animating inertia
3322 if (!options.noMoveStart) {
3323 this.fire('movestart');
3326 // animate pan unless animate: false specified
3327 if (options.animate !== false) {
3328 addClass(this._mapPane, 'leaflet-pan-anim');
3330 var newPos = this._getMapPanePos().subtract(offset).round();
3331 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
3333 this._rawPanBy(offset);
3334 this.fire('move').fire('moveend');
3340 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
3341 // Sets the view of the map (geographical center and zoom) performing a smooth
3342 // pan-zoom animation.
3343 flyTo: function (targetCenter, targetZoom, options) {
3345 options = options || {};
3346 if (options.animate === false || !any3d) {
3347 return this.setView(targetCenter, targetZoom, options);
3352 var from = this.project(this.getCenter()),
3353 to = this.project(targetCenter),
3354 size = this.getSize(),
3355 startZoom = this._zoom;
3357 targetCenter = toLatLng(targetCenter);
3358 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
3360 var w0 = Math.max(size.x, size.y),
3361 w1 = w0 * this.getZoomScale(startZoom, targetZoom),
3362 u1 = (to.distanceTo(from)) || 1,
3367 var s1 = i ? -1 : 1,
3369 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
3370 b1 = 2 * s2 * rho2 * u1,
3372 sq = Math.sqrt(b * b + 1) - b;
3374 // workaround for floating point precision bug when sq = 0, log = -Infinite,
3375 // thus triggering an infinite loop in flyTo
3376 var log = sq < 0.000000001 ? -18 : Math.log(sq);
3381 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
3382 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
3383 function tanh(n) { return sinh(n) / cosh(n); }
3387 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
3388 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
3390 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
3392 var start = Date.now(),
3393 S = (r(1) - r0) / rho,
3394 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
3397 var t = (Date.now() - start) / duration,
3401 this._flyToFrame = requestAnimFrame(frame, this);
3404 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
3405 this.getScaleZoom(w0 / w(s), startZoom),
3410 ._move(targetCenter, targetZoom)
3415 this._moveStart(true, options.noMoveStart);
3421 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
3422 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
3423 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
3424 flyToBounds: function (bounds, options) {
3425 var target = this._getBoundsCenterZoom(bounds, options);
3426 return this.flyTo(target.center, target.zoom, options);
3429 // @method setMaxBounds(bounds: Bounds): this
3430 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
3431 setMaxBounds: function (bounds) {
3432 bounds = toLatLngBounds(bounds);
3434 if (!bounds.isValid()) {
3435 this.options.maxBounds = null;
3436 return this.off('moveend', this._panInsideMaxBounds);
3437 } else if (this.options.maxBounds) {
3438 this.off('moveend', this._panInsideMaxBounds);
3441 this.options.maxBounds = bounds;
3444 this._panInsideMaxBounds();
3447 return this.on('moveend', this._panInsideMaxBounds);
3450 // @method setMinZoom(zoom: Number): this
3451 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
3452 setMinZoom: function (zoom) {
3453 var oldZoom = this.options.minZoom;
3454 this.options.minZoom = zoom;
3456 if (this._loaded && oldZoom !== zoom) {
3457 this.fire('zoomlevelschange');
3459 if (this.getZoom() < this.options.minZoom) {
3460 return this.setZoom(zoom);
3467 // @method setMaxZoom(zoom: Number): this
3468 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
3469 setMaxZoom: function (zoom) {
3470 var oldZoom = this.options.maxZoom;
3471 this.options.maxZoom = zoom;
3473 if (this._loaded && oldZoom !== zoom) {
3474 this.fire('zoomlevelschange');
3476 if (this.getZoom() > this.options.maxZoom) {
3477 return this.setZoom(zoom);
3484 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
3485 // 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.
3486 panInsideBounds: function (bounds, options) {
3487 this._enforcingBounds = true;
3488 var center = this.getCenter(),
3489 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
3491 if (!center.equals(newCenter)) {
3492 this.panTo(newCenter, options);
3495 this._enforcingBounds = false;
3499 // @method invalidateSize(options: Zoom/pan options): this
3500 // Checks if the map container size changed and updates the map if so —
3501 // call it after you've changed the map size dynamically, also animating
3502 // pan by default. If `options.pan` is `false`, panning will not occur.
3503 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
3504 // that it doesn't happen often even if the method is called many
3508 // @method invalidateSize(animate: Boolean): this
3509 // Checks if the map container size changed and updates the map if so —
3510 // call it after you've changed the map size dynamically, also animating
3512 invalidateSize: function (options) {
3513 if (!this._loaded) { return this; }
3518 }, options === true ? {animate: true} : options);
3520 var oldSize = this.getSize();
3521 this._sizeChanged = true;
3522 this._lastCenter = null;
3524 var newSize = this.getSize(),
3525 oldCenter = oldSize.divideBy(2).round(),
3526 newCenter = newSize.divideBy(2).round(),
3527 offset = oldCenter.subtract(newCenter);
3529 if (!offset.x && !offset.y) { return this; }
3531 if (options.animate && options.pan) {
3536 this._rawPanBy(offset);
3541 if (options.debounceMoveend) {
3542 clearTimeout(this._sizeTimer);
3543 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
3545 this.fire('moveend');
3549 // @section Map state change events
3550 // @event resize: ResizeEvent
3551 // Fired when the map is resized.
3552 return this.fire('resize', {
3558 // @section Methods for modifying map state
3559 // @method stop(): this
3560 // Stops the currently running `panTo` or `flyTo` animation, if any.
3562 this.setZoom(this._limitZoom(this._zoom));
3563 if (!this.options.zoomSnap) {
3564 this.fire('viewreset');
3566 return this._stop();
3569 // @section Geolocation methods
3570 // @method locate(options?: Locate options): this
3571 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
3572 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
3573 // and optionally sets the map view to the user's location with respect to
3574 // detection accuracy (or to the world view if geolocation failed).
3575 // Note that, if your page doesn't use HTTPS, this method will fail in
3576 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
3577 // See `Locate options` for more details.
3578 locate: function (options) {
3580 options = this._locateOptions = extend({
3584 // maxZoom: <Number>
3586 // enableHighAccuracy: false
3589 if (!('geolocation' in navigator)) {
3590 this._handleGeolocationError({
3592 message: 'Geolocation not supported.'
3597 var onResponse = bind(this._handleGeolocationResponse, this),
3598 onError = bind(this._handleGeolocationError, this);
3600 if (options.watch) {
3601 this._locationWatchId =
3602 navigator.geolocation.watchPosition(onResponse, onError, options);
3604 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
3609 // @method stopLocate(): this
3610 // Stops watching location previously initiated by `map.locate({watch: true})`
3611 // and aborts resetting the map view if map.locate was called with
3612 // `{setView: true}`.
3613 stopLocate: function () {
3614 if (navigator.geolocation && navigator.geolocation.clearWatch) {
3615 navigator.geolocation.clearWatch(this._locationWatchId);
3617 if (this._locateOptions) {
3618 this._locateOptions.setView = false;
3623 _handleGeolocationError: function (error) {
3625 message = error.message ||
3626 (c === 1 ? 'permission denied' :
3627 (c === 2 ? 'position unavailable' : 'timeout'));
3629 if (this._locateOptions.setView && !this._loaded) {
3633 // @section Location events
3634 // @event locationerror: ErrorEvent
3635 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
3636 this.fire('locationerror', {
3638 message: 'Geolocation error: ' + message + '.'
3642 _handleGeolocationResponse: function (pos) {
3643 var lat = pos.coords.latitude,
3644 lng = pos.coords.longitude,
3645 latlng = new LatLng(lat, lng),
3646 bounds = latlng.toBounds(pos.coords.accuracy * 2),
3647 options = this._locateOptions;
3649 if (options.setView) {
3650 var zoom = this.getBoundsZoom(bounds);
3651 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
3657 timestamp: pos.timestamp
3660 for (var i in pos.coords) {
3661 if (typeof pos.coords[i] === 'number') {
3662 data[i] = pos.coords[i];
3666 // @event locationfound: LocationEvent
3667 // Fired when geolocation (using the [`locate`](#map-locate) method)
3668 // went successfully.
3669 this.fire('locationfound', data);
3672 // TODO Appropriate docs section?
3673 // @section Other Methods
3674 // @method addHandler(name: String, HandlerClass: Function): this
3675 // Adds a new `Handler` to the map, given its name and constructor function.
3676 addHandler: function (name, HandlerClass) {
3677 if (!HandlerClass) { return this; }
3679 var handler = this[name] = new HandlerClass(this);
3681 this._handlers.push(handler);
3683 if (this.options[name]) {
3690 // @method remove(): this
3691 // Destroys the map and clears all related event listeners.
3692 remove: function () {
3694 this._initEvents(true);
3696 if (this._containerId !== this._container._leaflet_id) {
3697 throw new Error('Map container is being reused by another instance');
3701 // throws error in IE6-8
3702 delete this._container._leaflet_id;
3703 delete this._containerId;
3706 this._container._leaflet_id = undefined;
3708 this._containerId = undefined;
3711 if (this._locationWatchId !== undefined) {
3717 remove(this._mapPane);
3719 if (this._clearControlPos) {
3720 this._clearControlPos();
3722 if (this._resizeRequest) {
3723 cancelAnimFrame(this._resizeRequest);
3724 this._resizeRequest = null;
3727 this._clearHandlers();
3730 // @section Map state change events
3731 // @event unload: Event
3732 // Fired when the map is destroyed with [remove](#map-remove) method.
3733 this.fire('unload');
3737 for (i in this._layers) {
3738 this._layers[i].remove();
3740 for (i in this._panes) {
3741 remove(this._panes[i]);
3746 delete this._mapPane;
3747 delete this._renderer;
3752 // @section Other Methods
3753 // @method createPane(name: String, container?: HTMLElement): HTMLElement
3754 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
3755 // then returns it. The pane is created as a child of `container`, or
3756 // as a child of the main map pane if not set.
3757 createPane: function (name, container) {
3758 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3759 pane = create$1('div', className, container || this._mapPane);
3762 this._panes[name] = pane;
3767 // @section Methods for Getting Map State
3769 // @method getCenter(): LatLng
3770 // Returns the geographical center of the map view
3771 getCenter: function () {
3772 this._checkIfLoaded();
3774 if (this._lastCenter && !this._moved()) {
3775 return this._lastCenter;
3777 return this.layerPointToLatLng(this._getCenterLayerPoint());
3780 // @method getZoom(): Number
3781 // Returns the current zoom level of the map view
3782 getZoom: function () {
3786 // @method getBounds(): LatLngBounds
3787 // Returns the geographical bounds visible in the current map view
3788 getBounds: function () {
3789 var bounds = this.getPixelBounds(),
3790 sw = this.unproject(bounds.getBottomLeft()),
3791 ne = this.unproject(bounds.getTopRight());
3793 return new LatLngBounds(sw, ne);
3796 // @method getMinZoom(): Number
3797 // 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.
3798 getMinZoom: function () {
3799 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3802 // @method getMaxZoom(): Number
3803 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3804 getMaxZoom: function () {
3805 return this.options.maxZoom === undefined ?
3806 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3807 this.options.maxZoom;
3810 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
3811 // Returns the maximum zoom level on which the given bounds fit to the map
3812 // view in its entirety. If `inside` (optional) is set to `true`, the method
3813 // instead returns the minimum zoom level on which the map view fits into
3814 // the given bounds in its entirety.
3815 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3816 bounds = toLatLngBounds(bounds);
3817 padding = toPoint(padding || [0, 0]);
3819 var zoom = this.getZoom() || 0,
3820 min = this.getMinZoom(),
3821 max = this.getMaxZoom(),
3822 nw = bounds.getNorthWest(),
3823 se = bounds.getSouthEast(),
3824 size = this.getSize().subtract(padding),
3825 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3826 snap = any3d ? this.options.zoomSnap : 1,
3827 scalex = size.x / boundsSize.x,
3828 scaley = size.y / boundsSize.y,
3829 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
3831 zoom = this.getScaleZoom(scale, zoom);
3834 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3835 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3838 return Math.max(min, Math.min(max, zoom));
3841 // @method getSize(): Point
3842 // Returns the current size of the map container (in pixels).
3843 getSize: function () {
3844 if (!this._size || this._sizeChanged) {
3845 this._size = new Point(
3846 this._container.clientWidth || 0,
3847 this._container.clientHeight || 0);
3849 this._sizeChanged = false;
3851 return this._size.clone();
3854 // @method getPixelBounds(): Bounds
3855 // Returns the bounds of the current map view in projected pixel
3856 // coordinates (sometimes useful in layer and overlay implementations).
3857 getPixelBounds: function (center, zoom) {
3858 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3859 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3862 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3863 // the map pane? "left point of the map layer" can be confusing, specially
3864 // since there can be negative offsets.
3865 // @method getPixelOrigin(): Point
3866 // Returns the projected pixel coordinates of the top left point of
3867 // the map layer (useful in custom layer and overlay implementations).
3868 getPixelOrigin: function () {
3869 this._checkIfLoaded();
3870 return this._pixelOrigin;
3873 // @method getPixelWorldBounds(zoom?: Number): Bounds
3874 // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3875 // If `zoom` is omitted, the map's current zoom level is used.
3876 getPixelWorldBounds: function (zoom) {
3877 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3880 // @section Other Methods
3882 // @method getPane(pane: String|HTMLElement): HTMLElement
3883 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3884 getPane: function (pane) {
3885 return typeof pane === 'string' ? this._panes[pane] : pane;
3888 // @method getPanes(): Object
3889 // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3890 // the panes as values.
3891 getPanes: function () {
3895 // @method getContainer: HTMLElement
3896 // Returns the HTML element that contains the map.
3897 getContainer: function () {
3898 return this._container;
3902 // @section Conversion Methods
3904 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3905 // Returns the scale factor to be applied to a map transition from zoom level
3906 // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3907 getZoomScale: function (toZoom, fromZoom) {
3908 // TODO replace with universal implementation after refactoring projections
3909 var crs = this.options.crs;
3910 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3911 return crs.scale(toZoom) / crs.scale(fromZoom);
3914 // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3915 // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3916 // level and everything is scaled by a factor of `scale`. Inverse of
3917 // [`getZoomScale`](#map-getZoomScale).
3918 getScaleZoom: function (scale, fromZoom) {
3919 var crs = this.options.crs;
3920 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3921 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3922 return isNaN(zoom) ? Infinity : zoom;
3925 // @method project(latlng: LatLng, zoom: Number): Point
3926 // Projects a geographical coordinate `LatLng` according to the projection
3927 // of the map's CRS, then scales it according to `zoom` and the CRS's
3928 // `Transformation`. The result is pixel coordinate relative to
3930 project: function (latlng, zoom) {
3931 zoom = zoom === undefined ? this._zoom : zoom;
3932 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
3935 // @method unproject(point: Point, zoom: Number): LatLng
3936 // Inverse of [`project`](#map-project).
3937 unproject: function (point, zoom) {
3938 zoom = zoom === undefined ? this._zoom : zoom;
3939 return this.options.crs.pointToLatLng(toPoint(point), zoom);
3942 // @method layerPointToLatLng(point: Point): LatLng
3943 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3944 // returns the corresponding geographical coordinate (for the current zoom level).
3945 layerPointToLatLng: function (point) {
3946 var projectedPoint = toPoint(point).add(this.getPixelOrigin());
3947 return this.unproject(projectedPoint);
3950 // @method latLngToLayerPoint(latlng: LatLng): Point
3951 // Given a geographical coordinate, returns the corresponding pixel coordinate
3952 // relative to the [origin pixel](#map-getpixelorigin).
3953 latLngToLayerPoint: function (latlng) {
3954 var projectedPoint = this.project(toLatLng(latlng))._round();
3955 return projectedPoint._subtract(this.getPixelOrigin());
3958 // @method wrapLatLng(latlng: LatLng): LatLng
3959 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
3960 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
3962 // By default this means longitude is wrapped around the dateline so its
3963 // value is between -180 and +180 degrees.
3964 wrapLatLng: function (latlng) {
3965 return this.options.crs.wrapLatLng(toLatLng(latlng));