2 * Leaflet 1.2.0, a JS library for interactive maps. http://leafletjs.com
3 * (c) 2010-2017 Vladimir Agafonkin, (c) 2010-2011 CloudMade
5 (function (global, factory) {
6 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
7 typeof define === 'function' && define.amd ? define(['exports'], factory) :
8 (factory((global.L = {})));
9 }(this, (function (exports) { 'use strict';
11 var version = "1.2.0";
16 * Various utility functions, used by Leaflet internally.
19 var freeze = Object.freeze;
20 Object.freeze = function (obj) { return obj; };
22 // @function extend(dest: Object, src?: Object): Object
23 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
24 function extend(dest) {
27 for (j = 1, len = arguments.length; j < len; j++) {
36 // @function create(proto: Object, properties?: Object): Object
37 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
38 var create = Object.create || (function () {
40 return function (proto) {
46 // @function bind(fn: Function, …): Function
47 // 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).
48 // Has a `L.bind()` shortcut.
49 function bind(fn, obj) {
50 var slice = Array.prototype.slice;
53 return fn.bind.apply(fn, slice.call(arguments, 1));
56 var args = slice.call(arguments, 2);
59 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
63 // @property lastId: Number
64 // Last unique ID used by [`stamp()`](#util-stamp)
67 // @function stamp(obj: Object): Number
68 // Returns the unique ID of an object, assiging it one if it doesn't have it.
71 obj._leaflet_id = obj._leaflet_id || ++lastId;
72 return obj._leaflet_id;
76 // @function throttle(fn: Function, time: Number, context: Object): Function
77 // Returns a function which executes function `fn` with the given scope `context`
78 // (so that the `this` keyword refers to `context` inside `fn`'s code). The function
79 // `fn` will be called no more than one time per given amount of `time`. The arguments
80 // received by the bound function will be any arguments passed when binding the
81 // function, followed by any arguments passed when invoking the bound function.
82 // Has an `L.throttle` shortcut.
83 function throttle(fn, time, context) {
84 var lock, args, wrapperFn, later;
87 // reset lock and call if queued
90 wrapperFn.apply(context, args);
95 wrapperFn = function () {
97 // called too soon, queue to call later
101 // call and lock until later
102 fn.apply(context, arguments);
103 setTimeout(later, time);
111 // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
112 // Returns the number `num` modulo `range` in such a way so it lies within
113 // `range[0]` and `range[1]`. The returned value will be always smaller than
114 // `range[1]` unless `includeMax` is set to `true`.
115 function wrapNum(x, range, includeMax) {
119 return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
122 // @function falseFn(): Function
123 // Returns a function which always returns `false`.
124 function falseFn() { return false; }
126 // @function formatNum(num: Number, digits?: Number): Number
127 // Returns the number `num` rounded to `digits` decimals, or to 5 decimals by default.
128 function formatNum(num, digits) {
129 var pow = Math.pow(10, digits || 5);
130 return Math.round(num * pow) / pow;
133 // @function trim(str: String): String
134 // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
136 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
139 // @function splitWords(str: String): String[]
140 // Trims and splits the string on whitespace and returns the array of parts.
141 function splitWords(str) {
142 return trim(str).split(/\s+/);
145 // @function setOptions(obj: Object, options: Object): Object
146 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
147 function setOptions(obj, options) {
148 if (!obj.hasOwnProperty('options')) {
149 obj.options = obj.options ? create(obj.options) : {};
151 for (var i in options) {
152 obj.options[i] = options[i];
157 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
158 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
159 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
160 // be appended at the end. If `uppercase` is `true`, the parameter names will
161 // be uppercased (e.g. `'?A=foo&B=bar'`)
162 function getParamString(obj, existingUrl, uppercase) {
165 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
167 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
170 var templateRe = /\{ *([\w_\-]+) *\}/g;
172 // @function template(str: String, data: Object): String
173 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
174 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
175 // `('Hello foo, bar')`. You can also specify functions instead of strings for
176 // data values — they will be evaluated passing `data` as an argument.
177 function template(str, data) {
178 return str.replace(templateRe, function (str, key) {
179 var value = data[key];
181 if (value === undefined) {
182 throw new Error('No value provided for variable ' + str);
184 } else if (typeof value === 'function') {
191 // @function isArray(obj): Boolean
192 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
193 var isArray = Array.isArray || function (obj) {
194 return (Object.prototype.toString.call(obj) === '[object Array]');
197 // @function indexOf(array: Array, el: Object): Number
198 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
199 function indexOf(array, el) {
200 for (var i = 0; i < array.length; i++) {
201 if (array[i] === el) { return i; }
206 // @property emptyImageUrl: String
207 // Data URI string containing a base64-encoded empty GIF image.
208 // Used as a hack to free memory from unused images on WebKit-powered
209 // mobile devices (by setting image `src` to this string).
210 var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
212 // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
214 function getPrefixed(name) {
215 return window['webkit' + name] || window['moz' + name] || window['ms' + name];
220 // fallback for IE 7-8
221 function timeoutDefer(fn) {
222 var time = +new Date(),
223 timeToCall = Math.max(0, 16 - (time - lastTime));
225 lastTime = time + timeToCall;
226 return window.setTimeout(fn, timeToCall);
229 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
230 var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
231 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
233 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
234 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
235 // `context` if given. When `immediate` is set, `fn` is called immediately if
236 // the browser doesn't have native support for
237 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
238 // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
239 function requestAnimFrame(fn, context, immediate) {
240 if (immediate && requestFn === timeoutDefer) {
243 return requestFn.call(window, bind(fn, context));
247 // @function cancelAnimFrame(id: Number): undefined
248 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
249 function cancelAnimFrame(id) {
251 cancelFn.call(window, id);
256 var Util = (Object.freeze || Object)({
266 formatNum: formatNum,
268 splitWords: splitWords,
269 setOptions: setOptions,
270 getParamString: getParamString,
274 emptyImageUrl: emptyImageUrl,
275 requestFn: requestFn,
277 requestAnimFrame: requestAnimFrame,
278 cancelAnimFrame: cancelAnimFrame
287 // Thanks to John Resig and Dean Edwards for inspiration!
291 Class.extend = function (props) {
293 // @function extend(props: Object): Function
294 // [Extends the current class](#class-inheritance) given the properties to be included.
295 // Returns a Javascript function that is a class constructor (to be called with `new`).
296 var NewClass = function () {
298 // call the constructor
299 if (this.initialize) {
300 this.initialize.apply(this, arguments);
303 // call all constructor hooks
304 this.callInitHooks();
307 var parentProto = NewClass.__super__ = this.prototype;
309 var proto = create(parentProto);
310 proto.constructor = NewClass;
312 NewClass.prototype = proto;
314 // inherit parent's statics
315 for (var i in this) {
316 if (this.hasOwnProperty(i) && i !== 'prototype' && i !== '__super__') {
317 NewClass[i] = this[i];
321 // mix static properties into the class
323 extend(NewClass, props.statics);
324 delete props.statics;
327 // mix includes into the prototype
328 if (props.includes) {
329 checkDeprecatedMixinEvents(props.includes);
330 extend.apply(null, [proto].concat(props.includes));
331 delete props.includes;
336 props.options = extend(create(proto.options), props.options);
339 // mix given properties into the prototype
340 extend(proto, props);
342 proto._initHooks = [];
344 // add method for calling all hooks
345 proto.callInitHooks = function () {
347 if (this._initHooksCalled) { return; }
349 if (parentProto.callInitHooks) {
350 parentProto.callInitHooks.call(this);
353 this._initHooksCalled = true;
355 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
356 proto._initHooks[i].call(this);
364 // @function include(properties: Object): this
365 // [Includes a mixin](#class-includes) into the current class.
366 Class.include = function (props) {
367 extend(this.prototype, props);
371 // @function mergeOptions(options: Object): this
372 // [Merges `options`](#class-options) into the defaults of the class.
373 Class.mergeOptions = function (options) {
374 extend(this.prototype.options, options);
378 // @function addInitHook(fn: Function): this
379 // Adds a [constructor hook](#class-constructor-hooks) to the class.
380 Class.addInitHook = function (fn) { // (Function) || (String, args...)
381 var args = Array.prototype.slice.call(arguments, 1);
383 var init = typeof fn === 'function' ? fn : function () {
384 this[fn].apply(this, args);
387 this.prototype._initHooks = this.prototype._initHooks || [];
388 this.prototype._initHooks.push(init);
392 function checkDeprecatedMixinEvents(includes) {
393 if (!L || !L.Mixin) { return; }
395 includes = isArray(includes) ? includes : [includes];
397 for (var i = 0; i < includes.length; i++) {
398 if (includes[i] === L.Mixin.Events) {
399 console.warn('Deprecated include of L.Mixin.Events: ' +
400 'this property will be removed in future releases, ' +
401 'please inherit from L.Evented instead.', new Error().stack);
411 * 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).
416 * map.on('click', function(e) {
421 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
424 * function onClick(e) { ... }
426 * map.on('click', onClick);
427 * map.off('click', onClick);
432 /* @method on(type: String, fn: Function, context?: Object): this
433 * 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'`).
436 * @method on(eventMap: Object): this
437 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
439 on: function (types, fn, context) {
441 // types can be a map of types/handlers
442 if (typeof types === 'object') {
443 for (var type in types) {
444 // we don't process space-separated events here for performance;
445 // it's a hot path since Layer uses the on(obj) syntax
446 this._on(type, types[type], fn);
450 // types can be a string of space-separated words
451 types = splitWords(types);
453 for (var i = 0, len = types.length; i < len; i++) {
454 this._on(types[i], fn, context);
461 /* @method off(type: String, fn?: Function, context?: Object): this
462 * 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.
465 * @method off(eventMap: Object): this
466 * Removes a set of type/listener pairs.
470 * Removes all listeners to all events on the object.
472 off: function (types, fn, context) {
475 // clear all listeners if called without arguments
478 } else if (typeof types === 'object') {
479 for (var type in types) {
480 this._off(type, types[type], fn);
484 types = splitWords(types);
486 for (var i = 0, len = types.length; i < len; i++) {
487 this._off(types[i], fn, context);
494 // attach listener (without syntactic sugar now)
495 _on: function (type, fn, context) {
496 this._events = this._events || {};
498 /* get/init listeners for type */
499 var typeListeners = this._events[type];
500 if (!typeListeners) {
502 this._events[type] = typeListeners;
505 if (context === this) {
506 // Less memory footprint.
509 var newListener = {fn: fn, ctx: context},
510 listeners = typeListeners;
512 // check if fn already there
513 for (var i = 0, len = listeners.length; i < len; i++) {
514 if (listeners[i].fn === fn && listeners[i].ctx === context) {
519 listeners.push(newListener);
522 _off: function (type, fn, context) {
527 if (!this._events) { return; }
529 listeners = this._events[type];
536 // Set all removed listeners to noop so they are not called if remove happens in fire
537 for (i = 0, len = listeners.length; i < len; i++) {
538 listeners[i].fn = falseFn;
540 // clear all listeners for a type if function isn't specified
541 delete this._events[type];
545 if (context === this) {
551 // find fn and remove it
552 for (i = 0, len = listeners.length; i < len; i++) {
553 var l = listeners[i];
554 if (l.ctx !== context) { continue; }
557 // set the removed listener to noop so that's not called if remove happens in fire
560 if (this._firingCount) {
561 /* copy array in case events are being fired */
562 this._events[type] = listeners = listeners.slice();
564 listeners.splice(i, 1);
572 // @method fire(type: String, data?: Object, propagate?: Boolean): this
573 // Fires an event of the specified type. You can optionally provide an data
574 // object — the first argument of the listener function will contain its
575 // properties. The event can optionally be propagated to event parents.
576 fire: function (type, data, propagate) {
577 if (!this.listens(type, propagate)) { return this; }
579 var event = extend({}, data, {type: type, target: this});
582 var listeners = this._events[type];
585 this._firingCount = (this._firingCount + 1) || 1;
586 for (var i = 0, len = listeners.length; i < len; i++) {
587 var l = listeners[i];
588 l.fn.call(l.ctx || this, event);
596 // propagate the event to parents (set with addEventParent)
597 this._propagateEvent(event);
603 // @method listens(type: String): Boolean
604 // Returns `true` if a particular event type has any listeners attached to it.
605 listens: function (type, propagate) {
606 var listeners = this._events && this._events[type];
607 if (listeners && listeners.length) { return true; }
610 // also check parents for listeners if event propagates
611 for (var id in this._eventParents) {
612 if (this._eventParents[id].listens(type, propagate)) { return true; }
618 // @method once(…): this
619 // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
620 once: function (types, fn, context) {
622 if (typeof types === 'object') {
623 for (var type in types) {
624 this.once(type, types[type], fn);
629 var handler = bind(function () {
631 .off(types, fn, context)
632 .off(types, handler, context);
635 // add a listener that's executed once and removed after that
637 .on(types, fn, context)
638 .on(types, handler, context);
641 // @method addEventParent(obj: Evented): this
642 // Adds an event parent - an `Evented` that will receive propagated events
643 addEventParent: function (obj) {
644 this._eventParents = this._eventParents || {};
645 this._eventParents[stamp(obj)] = obj;
649 // @method removeEventParent(obj: Evented): this
650 // Removes an event parent, so it will stop receiving propagated events
651 removeEventParent: function (obj) {
652 if (this._eventParents) {
653 delete this._eventParents[stamp(obj)];
658 _propagateEvent: function (e) {
659 for (var id in this._eventParents) {
660 this._eventParents[id].fire(e.type, extend({layer: e.target}, e), true);
665 // aliases; we should ditch those eventually
667 // @method addEventListener(…): this
668 // Alias to [`on(…)`](#evented-on)
669 Events.addEventListener = Events.on;
671 // @method removeEventListener(…): this
672 // Alias to [`off(…)`](#evented-off)
674 // @method clearAllEventListeners(…): this
675 // Alias to [`off()`](#evented-off)
676 Events.removeEventListener = Events.clearAllEventListeners = Events.off;
678 // @method addOneTimeEventListener(…): this
679 // Alias to [`once(…)`](#evented-once)
680 Events.addOneTimeEventListener = Events.once;
682 // @method fireEvent(…): this
683 // Alias to [`fire(…)`](#evented-fire)
684 Events.fireEvent = Events.fire;
686 // @method hasEventListeners(…): Boolean
687 // Alias to [`listens(…)`](#evented-listens)
688 Events.hasEventListeners = Events.listens;
690 var Evented = Class.extend(Events);
696 * Represents a point with `x` and `y` coordinates in pixels.
701 * var point = L.point(200, 300);
704 * 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:
707 * map.panBy([200, 300]);
708 * map.panBy(L.point(200, 300));
712 function Point(x, y, round) {
713 // @property x: Number; The `x` coordinate of the point
714 this.x = (round ? Math.round(x) : x);
715 // @property y: Number; The `y` coordinate of the point
716 this.y = (round ? Math.round(y) : y);
721 // @method clone(): Point
722 // Returns a copy of the current point.
724 return new Point(this.x, this.y);
727 // @method add(otherPoint: Point): Point
728 // Returns the result of addition of the current and the given points.
729 add: function (point) {
730 // non-destructive, returns a new point
731 return this.clone()._add(toPoint(point));
734 _add: function (point) {
735 // destructive, used directly for performance in situations where it's safe to modify existing point
741 // @method subtract(otherPoint: Point): Point
742 // Returns the result of subtraction of the given point from the current.
743 subtract: function (point) {
744 return this.clone()._subtract(toPoint(point));
747 _subtract: function (point) {
753 // @method divideBy(num: Number): Point
754 // Returns the result of division of the current point by the given number.
755 divideBy: function (num) {
756 return this.clone()._divideBy(num);
759 _divideBy: function (num) {
765 // @method multiplyBy(num: Number): Point
766 // Returns the result of multiplication of the current point by the given number.
767 multiplyBy: function (num) {
768 return this.clone()._multiplyBy(num);
771 _multiplyBy: function (num) {
777 // @method scaleBy(scale: Point): Point
778 // Multiply each coordinate of the current point by each coordinate of
779 // `scale`. In linear algebra terms, multiply the point by the
780 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
781 // defined by `scale`.
782 scaleBy: function (point) {
783 return new Point(this.x * point.x, this.y * point.y);
786 // @method unscaleBy(scale: Point): Point
787 // Inverse of `scaleBy`. Divide each coordinate of the current point by
788 // each coordinate of `scale`.
789 unscaleBy: function (point) {
790 return new Point(this.x / point.x, this.y / point.y);
793 // @method round(): Point
794 // Returns a copy of the current point with rounded coordinates.
796 return this.clone()._round();
799 _round: function () {
800 this.x = Math.round(this.x);
801 this.y = Math.round(this.y);
805 // @method floor(): Point
806 // Returns a copy of the current point with floored coordinates (rounded down).
808 return this.clone()._floor();
811 _floor: function () {
812 this.x = Math.floor(this.x);
813 this.y = Math.floor(this.y);
817 // @method ceil(): Point
818 // Returns a copy of the current point with ceiled coordinates (rounded up).
820 return this.clone()._ceil();
824 this.x = Math.ceil(this.x);
825 this.y = Math.ceil(this.y);
829 // @method distanceTo(otherPoint: Point): Number
830 // Returns the cartesian distance between the current and the given points.
831 distanceTo: function (point) {
832 point = toPoint(point);
834 var x = point.x - this.x,
835 y = point.y - this.y;
837 return Math.sqrt(x * x + y * y);
840 // @method equals(otherPoint: Point): Boolean
841 // Returns `true` if the given point has the same coordinates.
842 equals: function (point) {
843 point = toPoint(point);
845 return point.x === this.x &&
849 // @method contains(otherPoint: Point): Boolean
850 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
851 contains: function (point) {
852 point = toPoint(point);
854 return Math.abs(point.x) <= Math.abs(this.x) &&
855 Math.abs(point.y) <= Math.abs(this.y);
858 // @method toString(): String
859 // Returns a string representation of the point for debugging purposes.
860 toString: function () {
862 formatNum(this.x) + ', ' +
863 formatNum(this.y) + ')';
867 // @factory L.point(x: Number, y: Number, round?: Boolean)
868 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
871 // @factory L.point(coords: Number[])
872 // Expects an array of the form `[x, y]` instead.
875 // @factory L.point(coords: Object)
876 // Expects a plain object of the form `{x: Number, y: Number}` instead.
877 function toPoint(x, y, round) {
878 if (x instanceof Point) {
882 return new Point(x[0], x[1]);
884 if (x === undefined || x === null) {
887 if (typeof x === 'object' && 'x' in x && 'y' in x) {
888 return new Point(x.x, x.y);
890 return new Point(x, y, round);
897 * Represents a rectangular area in pixel coordinates.
902 * var p1 = L.point(10, 10),
903 * p2 = L.point(40, 60),
904 * bounds = L.bounds(p1, p2);
907 * 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:
910 * otherBounds.intersects([[10, 10], [40, 60]]);
914 function Bounds(a, b) {
917 var points = b ? [a, b] : a;
919 for (var i = 0, len = points.length; i < len; i++) {
920 this.extend(points[i]);
925 // @method extend(point: Point): this
926 // Extends the bounds to contain the given point.
927 extend: function (point) { // (Point)
928 point = toPoint(point);
930 // @property min: Point
931 // The top left corner of the rectangle.
932 // @property max: Point
933 // The bottom right corner of the rectangle.
934 if (!this.min && !this.max) {
935 this.min = point.clone();
936 this.max = point.clone();
938 this.min.x = Math.min(point.x, this.min.x);
939 this.max.x = Math.max(point.x, this.max.x);
940 this.min.y = Math.min(point.y, this.min.y);
941 this.max.y = Math.max(point.y, this.max.y);
946 // @method getCenter(round?: Boolean): Point
947 // Returns the center point of the bounds.
948 getCenter: function (round) {
950 (this.min.x + this.max.x) / 2,
951 (this.min.y + this.max.y) / 2, round);
954 // @method getBottomLeft(): Point
955 // Returns the bottom-left point of the bounds.
956 getBottomLeft: function () {
957 return new Point(this.min.x, this.max.y);
960 // @method getTopRight(): Point
961 // Returns the top-right point of the bounds.
962 getTopRight: function () { // -> Point
963 return new Point(this.max.x, this.min.y);
966 // @method getTopLeft(): Point
967 // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).
968 getTopLeft: function () {
969 return this.min; // left, top
972 // @method getBottomRight(): Point
973 // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).
974 getBottomRight: function () {
975 return this.max; // right, bottom
978 // @method getSize(): Point
979 // Returns the size of the given bounds
980 getSize: function () {
981 return this.max.subtract(this.min);
984 // @method contains(otherBounds: Bounds): Boolean
985 // Returns `true` if the rectangle contains the given one.
987 // @method contains(point: Point): Boolean
988 // Returns `true` if the rectangle contains the given point.
989 contains: function (obj) {
992 if (typeof obj[0] === 'number' || obj instanceof Point) {
998 if (obj instanceof Bounds) {
1005 return (min.x >= this.min.x) &&
1006 (max.x <= this.max.x) &&
1007 (min.y >= this.min.y) &&
1008 (max.y <= this.max.y);
1011 // @method intersects(otherBounds: Bounds): Boolean
1012 // Returns `true` if the rectangle intersects the given bounds. Two bounds
1013 // intersect if they have at least one point in common.
1014 intersects: function (bounds) { // (Bounds) -> Boolean
1015 bounds = toBounds(bounds);
1021 xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1022 yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1024 return xIntersects && yIntersects;
1027 // @method overlaps(otherBounds: Bounds): Boolean
1028 // Returns `true` if the rectangle overlaps the given bounds. Two bounds
1029 // overlap if their intersection is an area.
1030 overlaps: function (bounds) { // (Bounds) -> Boolean
1031 bounds = toBounds(bounds);
1037 xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1038 yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1040 return xOverlaps && yOverlaps;
1043 isValid: function () {
1044 return !!(this.min && this.max);
1049 // @factory L.bounds(corner1: Point, corner2: Point)
1050 // Creates a Bounds object from two corners coordinate pairs.
1052 // @factory L.bounds(points: Point[])
1053 // Creates a Bounds object from the given array of points.
1054 function toBounds(a, b) {
1055 if (!a || a instanceof Bounds) {
1058 return new Bounds(a, b);
1062 * @class LatLngBounds
1063 * @aka L.LatLngBounds
1065 * Represents a rectangular geographical area on a map.
1070 * var corner1 = L.latLng(40.712, -74.227),
1071 * corner2 = L.latLng(40.774, -74.125),
1072 * bounds = L.latLngBounds(corner1, corner2);
1075 * 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:
1079 * [40.712, -74.227],
1084 * 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.
1087 function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
1088 if (!corner1) { return; }
1090 var latlngs = corner2 ? [corner1, corner2] : corner1;
1092 for (var i = 0, len = latlngs.length; i < len; i++) {
1093 this.extend(latlngs[i]);
1097 LatLngBounds.prototype = {
1099 // @method extend(latlng: LatLng): this
1100 // Extend the bounds to contain the given point
1103 // @method extend(otherBounds: LatLngBounds): this
1104 // Extend the bounds to contain the given bounds
1105 extend: function (obj) {
1106 var sw = this._southWest,
1107 ne = this._northEast,
1110 if (obj instanceof LatLng) {
1114 } else if (obj instanceof LatLngBounds) {
1115 sw2 = obj._southWest;
1116 ne2 = obj._northEast;
1118 if (!sw2 || !ne2) { return this; }
1121 return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
1125 this._southWest = new LatLng(sw2.lat, sw2.lng);
1126 this._northEast = new LatLng(ne2.lat, ne2.lng);
1128 sw.lat = Math.min(sw2.lat, sw.lat);
1129 sw.lng = Math.min(sw2.lng, sw.lng);
1130 ne.lat = Math.max(ne2.lat, ne.lat);
1131 ne.lng = Math.max(ne2.lng, ne.lng);
1137 // @method pad(bufferRatio: Number): LatLngBounds
1138 // Returns bigger bounds created by extending the current bounds by a given percentage in each direction.
1139 pad: function (bufferRatio) {
1140 var sw = this._southWest,
1141 ne = this._northEast,
1142 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1143 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1145 return new LatLngBounds(
1146 new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1147 new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1150 // @method getCenter(): LatLng
1151 // Returns the center point of the bounds.
1152 getCenter: function () {
1154 (this._southWest.lat + this._northEast.lat) / 2,
1155 (this._southWest.lng + this._northEast.lng) / 2);
1158 // @method getSouthWest(): LatLng
1159 // Returns the south-west point of the bounds.
1160 getSouthWest: function () {
1161 return this._southWest;
1164 // @method getNorthEast(): LatLng
1165 // Returns the north-east point of the bounds.
1166 getNorthEast: function () {
1167 return this._northEast;
1170 // @method getNorthWest(): LatLng
1171 // Returns the north-west point of the bounds.
1172 getNorthWest: function () {
1173 return new LatLng(this.getNorth(), this.getWest());
1176 // @method getSouthEast(): LatLng
1177 // Returns the south-east point of the bounds.
1178 getSouthEast: function () {
1179 return new LatLng(this.getSouth(), this.getEast());
1182 // @method getWest(): Number
1183 // Returns the west longitude of the bounds
1184 getWest: function () {
1185 return this._southWest.lng;
1188 // @method getSouth(): Number
1189 // Returns the south latitude of the bounds
1190 getSouth: function () {
1191 return this._southWest.lat;
1194 // @method getEast(): Number
1195 // Returns the east longitude of the bounds
1196 getEast: function () {
1197 return this._northEast.lng;
1200 // @method getNorth(): Number
1201 // Returns the north latitude of the bounds
1202 getNorth: function () {
1203 return this._northEast.lat;
1206 // @method contains(otherBounds: LatLngBounds): Boolean
1207 // Returns `true` if the rectangle contains the given one.
1210 // @method contains (latlng: LatLng): Boolean
1211 // Returns `true` if the rectangle contains the given point.
1212 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1213 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
1214 obj = toLatLng(obj);
1216 obj = toLatLngBounds(obj);
1219 var sw = this._southWest,
1220 ne = this._northEast,
1223 if (obj instanceof LatLngBounds) {
1224 sw2 = obj.getSouthWest();
1225 ne2 = obj.getNorthEast();
1230 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1231 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1234 // @method intersects(otherBounds: LatLngBounds): Boolean
1235 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1236 intersects: function (bounds) {
1237 bounds = toLatLngBounds(bounds);
1239 var sw = this._southWest,
1240 ne = this._northEast,
1241 sw2 = bounds.getSouthWest(),
1242 ne2 = bounds.getNorthEast(),
1244 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1245 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1247 return latIntersects && lngIntersects;
1250 // @method overlaps(otherBounds: Bounds): Boolean
1251 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1252 overlaps: function (bounds) {
1253 bounds = toLatLngBounds(bounds);
1255 var sw = this._southWest,
1256 ne = this._northEast,
1257 sw2 = bounds.getSouthWest(),
1258 ne2 = bounds.getNorthEast(),
1260 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1261 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1263 return latOverlaps && lngOverlaps;
1266 // @method toBBoxString(): String
1267 // 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.
1268 toBBoxString: function () {
1269 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1272 // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
1273 // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overriden by setting `maxMargin` to a small number.
1274 equals: function (bounds, maxMargin) {
1275 if (!bounds) { return false; }
1277 bounds = toLatLngBounds(bounds);
1279 return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
1280 this._northEast.equals(bounds.getNorthEast(), maxMargin);
1283 // @method isValid(): Boolean
1284 // Returns `true` if the bounds are properly initialized.
1285 isValid: function () {
1286 return !!(this._southWest && this._northEast);
1290 // TODO International date line?
1292 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
1293 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
1296 // @factory L.latLngBounds(latlngs: LatLng[])
1297 // 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).
1298 function toLatLngBounds(a, b) {
1299 if (a instanceof LatLngBounds) {
1302 return new LatLngBounds(a, b);
1308 * Represents a geographical point with a certain latitude and longitude.
1313 * var latlng = L.latLng(50.5, 30.5);
1316 * 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:
1319 * map.panTo([50, 30]);
1320 * map.panTo({lon: 30, lat: 50});
1321 * map.panTo({lat: 50, lng: 30});
1322 * map.panTo(L.latLng(50, 30));
1326 function LatLng(lat, lng, alt) {
1327 if (isNaN(lat) || isNaN(lng)) {
1328 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1331 // @property lat: Number
1332 // Latitude in degrees
1335 // @property lng: Number
1336 // Longitude in degrees
1339 // @property alt: Number
1340 // Altitude in meters (optional)
1341 if (alt !== undefined) {
1346 LatLng.prototype = {
1347 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1348 // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overriden by setting `maxMargin` to a small number.
1349 equals: function (obj, maxMargin) {
1350 if (!obj) { return false; }
1352 obj = toLatLng(obj);
1354 var margin = Math.max(
1355 Math.abs(this.lat - obj.lat),
1356 Math.abs(this.lng - obj.lng));
1358 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1361 // @method toString(): String
1362 // Returns a string representation of the point (for debugging purposes).
1363 toString: function (precision) {
1365 formatNum(this.lat, precision) + ', ' +
1366 formatNum(this.lng, precision) + ')';
1369 // @method distanceTo(otherLatLng: LatLng): Number
1370 // Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula).
1371 distanceTo: function (other) {
1372 return Earth.distance(this, toLatLng(other));
1375 // @method wrap(): LatLng
1376 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1378 return Earth.wrapLatLng(this);
1381 // @method toBounds(sizeInMeters: Number): LatLngBounds
1382 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
1383 toBounds: function (sizeInMeters) {
1384 var latAccuracy = 180 * sizeInMeters / 40075017,
1385 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1387 return toLatLngBounds(
1388 [this.lat - latAccuracy, this.lng - lngAccuracy],
1389 [this.lat + latAccuracy, this.lng + lngAccuracy]);
1392 clone: function () {
1393 return new LatLng(this.lat, this.lng, this.alt);
1399 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1400 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1403 // @factory L.latLng(coords: Array): LatLng
1404 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1407 // @factory L.latLng(coords: Object): LatLng
1408 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1410 function toLatLng(a, b, c) {
1411 if (a instanceof LatLng) {
1414 if (isArray(a) && typeof a[0] !== 'object') {
1415 if (a.length === 3) {
1416 return new LatLng(a[0], a[1], a[2]);
1418 if (a.length === 2) {
1419 return new LatLng(a[0], a[1]);
1423 if (a === undefined || a === null) {
1426 if (typeof a === 'object' && 'lat' in a) {
1427 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1429 if (b === undefined) {
1432 return new LatLng(a, b, c);
1438 * Object that defines coordinate reference systems for projecting
1439 * geographical points into pixel (screen) coordinates and back (and to
1440 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
1441 * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
1443 * Leaflet defines the most usual CRSs by default. If you want to use a
1444 * CRS not defined by default, take a look at the
1445 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
1449 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
1450 // Projects geographical coordinates into pixel coordinates for a given zoom.
1451 latLngToPoint: function (latlng, zoom) {
1452 var projectedPoint = this.projection.project(latlng),
1453 scale = this.scale(zoom);
1455 return this.transformation._transform(projectedPoint, scale);
1458 // @method pointToLatLng(point: Point, zoom: Number): LatLng
1459 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
1460 // zoom into geographical coordinates.
1461 pointToLatLng: function (point, zoom) {
1462 var scale = this.scale(zoom),
1463 untransformedPoint = this.transformation.untransform(point, scale);
1465 return this.projection.unproject(untransformedPoint);
1468 // @method project(latlng: LatLng): Point
1469 // Projects geographical coordinates into coordinates in units accepted for
1470 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
1471 project: function (latlng) {
1472 return this.projection.project(latlng);
1475 // @method unproject(point: Point): LatLng
1476 // Given a projected coordinate returns the corresponding LatLng.
1477 // The inverse of `project`.
1478 unproject: function (point) {
1479 return this.projection.unproject(point);
1482 // @method scale(zoom: Number): Number
1483 // Returns the scale used when transforming projected coordinates into
1484 // pixel coordinates for a particular zoom. For example, it returns
1485 // `256 * 2^zoom` for Mercator-based CRS.
1486 scale: function (zoom) {
1487 return 256 * Math.pow(2, zoom);
1490 // @method zoom(scale: Number): Number
1491 // Inverse of `scale()`, returns the zoom level corresponding to a scale
1492 // factor of `scale`.
1493 zoom: function (scale) {
1494 return Math.log(scale / 256) / Math.LN2;
1497 // @method getProjectedBounds(zoom: Number): Bounds
1498 // Returns the projection's bounds scaled and transformed for the provided `zoom`.
1499 getProjectedBounds: function (zoom) {
1500 if (this.infinite) { return null; }
1502 var b = this.projection.bounds,
1503 s = this.scale(zoom),
1504 min = this.transformation.transform(b.min, s),
1505 max = this.transformation.transform(b.max, s);
1507 return new Bounds(min, max);
1510 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
1511 // Returns the distance between two geographical coordinates.
1513 // @property code: String
1514 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
1516 // @property wrapLng: Number[]
1517 // An array of two numbers defining whether the longitude (horizontal) coordinate
1518 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
1519 // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
1521 // @property wrapLat: Number[]
1522 // Like `wrapLng`, but for the latitude (vertical) axis.
1524 // wrapLng: [min, max],
1525 // wrapLat: [min, max],
1527 // @property infinite: Boolean
1528 // If true, the coordinate space will be unbounded (infinite in both axes)
1531 // @method wrapLatLng(latlng: LatLng): LatLng
1532 // Returns a `LatLng` where lat and lng has been wrapped according to the
1533 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
1534 wrapLatLng: function (latlng) {
1535 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
1536 lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
1539 return new LatLng(lat, lng, alt);
1542 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
1543 // Returns a `LatLngBounds` with the same size as the given one, ensuring
1544 // that its center is within the CRS's bounds.
1545 // Only accepts actual `L.LatLngBounds` instances, not arrays.
1546 wrapLatLngBounds: function (bounds) {
1547 var center = bounds.getCenter(),
1548 newCenter = this.wrapLatLng(center),
1549 latShift = center.lat - newCenter.lat,
1550 lngShift = center.lng - newCenter.lng;
1552 if (latShift === 0 && lngShift === 0) {
1556 var sw = bounds.getSouthWest(),
1557 ne = bounds.getNorthEast(),
1558 newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),
1559 newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);
1561 return new LatLngBounds(newSw, newNe);
1569 * Serves as the base for CRS that are global such that they cover the earth.
1570 * Can only be used as the base for other CRS and cannot be used directly,
1571 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
1575 var Earth = extend({}, CRS, {
1576 wrapLng: [-180, 180],
1578 // Mean Earth Radius, as recommended for use by
1579 // the International Union of Geodesy and Geophysics,
1580 // see http://rosettacode.org/wiki/Haversine_formula
1583 // distance between two geographical points using spherical law of cosines approximation
1584 distance: function (latlng1, latlng2) {
1585 var rad = Math.PI / 180,
1586 lat1 = latlng1.lat * rad,
1587 lat2 = latlng2.lat * rad,
1588 a = Math.sin(lat1) * Math.sin(lat2) +
1589 Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad);
1591 return this.R * Math.acos(Math.min(a, 1));
1596 * @namespace Projection
1597 * @projection L.Projection.SphericalMercator
1599 * Spherical Mercator projection — the most common projection for online maps,
1600 * used by almost all free and commercial tile providers. Assumes that Earth is
1601 * a sphere. Used by the `EPSG:3857` CRS.
1604 var SphericalMercator = {
1607 MAX_LATITUDE: 85.0511287798,
1609 project: function (latlng) {
1610 var d = Math.PI / 180,
1611 max = this.MAX_LATITUDE,
1612 lat = Math.max(Math.min(max, latlng.lat), -max),
1613 sin = Math.sin(lat * d);
1616 this.R * latlng.lng * d,
1617 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
1620 unproject: function (point) {
1621 var d = 180 / Math.PI;
1624 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
1625 point.x * d / this.R);
1628 bounds: (function () {
1629 var d = 6378137 * Math.PI;
1630 return new Bounds([-d, -d], [d, d]);
1635 * @class Transformation
1636 * @aka L.Transformation
1638 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1639 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1640 * the reverse. Used by Leaflet in its projections code.
1645 * var transformation = L.transformation(2, 5, -1, 10),
1646 * p = L.point(1, 2),
1647 * p2 = transformation.transform(p), // L.point(7, 8)
1648 * p3 = transformation.untransform(p2); // L.point(1, 2)
1653 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1654 // Creates a `Transformation` object with the given coefficients.
1655 function Transformation(a, b, c, d) {
1657 // use array properties
1670 Transformation.prototype = {
1671 // @method transform(point: Point, scale?: Number): Point
1672 // Returns a transformed point, optionally multiplied by the given scale.
1673 // Only accepts actual `L.Point` instances, not arrays.
1674 transform: function (point, scale) { // (Point, Number) -> Point
1675 return this._transform(point.clone(), scale);
1678 // destructive transform (faster)
1679 _transform: function (point, scale) {
1681 point.x = scale * (this._a * point.x + this._b);
1682 point.y = scale * (this._c * point.y + this._d);
1686 // @method untransform(point: Point, scale?: Number): Point
1687 // Returns the reverse transformation of the given point, optionally divided
1688 // by the given scale. Only accepts actual `L.Point` instances, not arrays.
1689 untransform: function (point, scale) {
1692 (point.x / scale - this._b) / this._a,
1693 (point.y / scale - this._d) / this._c);
1697 // factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1699 // @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1700 // Instantiates a Transformation object with the given coefficients.
1703 // @factory L.transformation(coefficients: Array): Transformation
1704 // Expects an coeficients array of the form
1705 // `[a: Number, b: Number, c: Number, d: Number]`.
1707 function toTransformation(a, b, c, d) {
1708 return new Transformation(a, b, c, d);
1713 * @crs L.CRS.EPSG3857
1715 * The most common CRS for online maps, used by almost all free and commercial
1716 * tile providers. Uses Spherical Mercator projection. Set in by default in
1717 * Map's `crs` option.
1720 var EPSG3857 = extend({}, Earth, {
1722 projection: SphericalMercator,
1724 transformation: (function () {
1725 var scale = 0.5 / (Math.PI * SphericalMercator.R);
1726 return toTransformation(scale, 0.5, -scale, 0.5);
1730 var EPSG900913 = extend({}, EPSG3857, {
1734 // @namespace SVG; @section
1735 // There are several static functions which can be called without instantiating L.SVG:
1737 // @function create(name: String): SVGElement
1738 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
1739 // corresponding to the class name passed. For example, using 'line' will return
1740 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
1741 function svgCreate(name) {
1742 return document.createElementNS('http://www.w3.org/2000/svg', name);
1745 // @function pointsToPath(rings: Point[], closed: Boolean): String
1746 // Generates a SVG path string for multiple rings, with each ring turning
1747 // into "M..L..L.." instructions
1748 function pointsToPath(rings, closed) {
1750 i, j, len, len2, points, p;
1752 for (i = 0, len = rings.length; i < len; i++) {
1755 for (j = 0, len2 = points.length; j < len2; j++) {
1757 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
1760 // closes the ring for polygons; "x" is VML syntax
1761 str += closed ? (svg ? 'z' : 'x') : '';
1764 // SVG complains about empty path strings
1765 return str || 'M0 0';
1769 * @namespace Browser
1772 * A namespace with static properties for browser/feature detection used by Leaflet internally.
1777 * if (L.Browser.ielt9) {
1778 * alert('Upgrade your browser, dude!');
1783 var style$1 = document.documentElement.style;
1785 // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
1786 var ie = 'ActiveXObject' in window;
1788 // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
1789 var ielt9 = ie && !document.addEventListener;
1791 // @property edge: Boolean; `true` for the Edge web browser.
1792 var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
1794 // @property webkit: Boolean;
1795 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
1796 var webkit = userAgentContains('webkit');
1798 // @property android: Boolean
1799 // `true` for any browser running on an Android platform.
1800 var android = userAgentContains('android');
1802 // @property android23: Boolean; `true` for browsers running on Android 2 or Android 3.
1803 var android23 = userAgentContains('android 2') || userAgentContains('android 3');
1805 // @property opera: Boolean; `true` for the Opera browser
1806 var opera = !!window.opera;
1808 // @property chrome: Boolean; `true` for the Chrome browser.
1809 var chrome = userAgentContains('chrome');
1811 // @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
1812 var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
1814 // @property safari: Boolean; `true` for the Safari browser.
1815 var safari = !chrome && userAgentContains('safari');
1817 var phantom = userAgentContains('phantom');
1819 // @property opera12: Boolean
1820 // `true` for the Opera browser supporting CSS transforms (version 12 or later).
1821 var opera12 = 'OTransition' in style$1;
1823 // @property win: Boolean; `true` when the browser is running in a Windows platform
1824 var win = navigator.platform.indexOf('Win') === 0;
1826 // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
1827 var ie3d = ie && ('transition' in style$1);
1829 // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
1830 var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;
1832 // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
1833 var gecko3d = 'MozPerspective' in style$1;
1835 // @property any3d: Boolean
1836 // `true` for all browsers supporting CSS transforms.
1837 var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
1839 // @property mobile: Boolean; `true` for all browsers running in a mobile device.
1840 var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
1842 // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
1843 var mobileWebkit = mobile && webkit;
1845 // @property mobileWebkit3d: Boolean
1846 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
1847 var mobileWebkit3d = mobile && webkit3d;
1849 // @property msPointer: Boolean
1850 // `true` for browsers implementing the Microsoft touch events model (notably IE10).
1851 var msPointer = !window.PointerEvent && window.MSPointerEvent;
1853 // @property pointer: Boolean
1854 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
1855 var pointer = !!(window.PointerEvent || msPointer);
1857 // @property touch: Boolean
1858 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
1859 // This does not necessarily mean that the browser is running in a computer with
1860 // a touchscreen, it only means that the browser is capable of understanding
1862 var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
1863 (window.DocumentTouch && document instanceof window.DocumentTouch));
1865 // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
1866 var mobileOpera = mobile && opera;
1868 // @property mobileGecko: Boolean
1869 // `true` for gecko-based browsers running in a mobile device.
1870 var mobileGecko = mobile && gecko;
1872 // @property retina: Boolean
1873 // `true` for browsers on a high-resolution "retina" screen.
1874 var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
1877 // @property canvas: Boolean
1878 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
1879 var canvas = (function () {
1880 return !!document.createElement('canvas').getContext;
1883 // @property svg: Boolean
1884 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
1885 var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect);
1887 // @property vml: Boolean
1888 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
1889 var vml = !svg && (function () {
1891 var div = document.createElement('div');
1892 div.innerHTML = '<v:shape adj="1"/>';
1894 var shape = div.firstChild;
1895 shape.style.behavior = 'url(#default#VML)';
1897 return shape && (typeof shape.adj === 'object');
1905 function userAgentContains(str) {
1906 return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
1910 var Browser = (Object.freeze || Object)({
1916 android23: android23,
1929 mobileWebkit: mobileWebkit,
1930 mobileWebkit3d: mobileWebkit3d,
1931 msPointer: msPointer,
1934 mobileOpera: mobileOpera,
1935 mobileGecko: mobileGecko,
1943 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
1947 var POINTER_DOWN = msPointer ? 'MSPointerDown' : 'pointerdown';
1948 var POINTER_MOVE = msPointer ? 'MSPointerMove' : 'pointermove';
1949 var POINTER_UP = msPointer ? 'MSPointerUp' : 'pointerup';
1950 var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel';
1951 var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION'];
1953 var _pointerDocListener = false;
1955 // DomEvent.DoubleTap needs to know about this
1956 var _pointersCount = 0;
1958 // Provides a touch events wrapper for (ms)pointer events.
1959 // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
1961 function addPointerListener(obj, type, handler, id) {
1962 if (type === 'touchstart') {
1963 _addPointerStart(obj, handler, id);
1965 } else if (type === 'touchmove') {
1966 _addPointerMove(obj, handler, id);
1968 } else if (type === 'touchend') {
1969 _addPointerEnd(obj, handler, id);
1975 function removePointerListener(obj, type, id) {
1976 var handler = obj['_leaflet_' + type + id];
1978 if (type === 'touchstart') {
1979 obj.removeEventListener(POINTER_DOWN, handler, false);
1981 } else if (type === 'touchmove') {
1982 obj.removeEventListener(POINTER_MOVE, handler, false);
1984 } else if (type === 'touchend') {
1985 obj.removeEventListener(POINTER_UP, handler, false);
1986 obj.removeEventListener(POINTER_CANCEL, handler, false);
1992 function _addPointerStart(obj, handler, id) {
1993 var onDown = bind(function (e) {
1994 if (e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
1995 // In IE11, some touch events needs to fire for form controls, or
1996 // the controls will stop working. We keep a whitelist of tag names that
1997 // need these events. For other target tags, we prevent default on the event.
1998 if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
2005 _handlePointer(e, handler);
2008 obj['_leaflet_touchstart' + id] = onDown;
2009 obj.addEventListener(POINTER_DOWN, onDown, false);
2011 // need to keep track of what pointers and how many are active to provide e.touches emulation
2012 if (!_pointerDocListener) {
2013 // we listen documentElement as any drags that end by moving the touch off the screen get fired there
2014 document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true);
2015 document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true);
2016 document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true);
2017 document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
2019 _pointerDocListener = true;
2023 function _globalPointerDown(e) {
2024 _pointers[e.pointerId] = e;
2028 function _globalPointerMove(e) {
2029 if (_pointers[e.pointerId]) {
2030 _pointers[e.pointerId] = e;
2034 function _globalPointerUp(e) {
2035 delete _pointers[e.pointerId];
2039 function _handlePointer(e, handler) {
2041 for (var i in _pointers) {
2042 e.touches.push(_pointers[i]);
2044 e.changedTouches = [e];
2049 function _addPointerMove(obj, handler, id) {
2050 var onMove = function (e) {
2051 // don't fire touch moves when mouse isn't down
2052 if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
2054 _handlePointer(e, handler);
2057 obj['_leaflet_touchmove' + id] = onMove;
2058 obj.addEventListener(POINTER_MOVE, onMove, false);
2061 function _addPointerEnd(obj, handler, id) {
2062 var onUp = function (e) {
2063 _handlePointer(e, handler);
2066 obj['_leaflet_touchend' + id] = onUp;
2067 obj.addEventListener(POINTER_UP, onUp, false);
2068 obj.addEventListener(POINTER_CANCEL, onUp, false);
2072 * Extends the event handling code with double tap support for mobile browsers.
2075 var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart';
2076 var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend';
2077 var _pre = '_leaflet_';
2079 // inspired by Zepto touch code by Thomas Fuchs
2080 function addDoubleTapListener(obj, handler, id) {
2085 function onTouchStart(e) {
2089 if ((!edge) || e.pointerType === 'mouse') { return; }
2090 count = _pointersCount;
2092 count = e.touches.length;
2095 if (count > 1) { return; }
2097 var now = Date.now(),
2098 delta = now - (last || now);
2100 touch$$1 = e.touches ? e.touches[0] : e;
2101 doubleTap = (delta > 0 && delta <= delay);
2105 function onTouchEnd(e) {
2106 if (doubleTap && !touch$$1.cancelBubble) {
2108 if ((!edge) || e.pointerType === 'mouse') { return; }
2109 // work around .type being readonly with MSPointer* events
2113 for (i in touch$$1) {
2115 newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop;
2117 touch$$1 = newTouch;
2119 touch$$1.type = 'dblclick';
2125 obj[_pre + _touchstart + id] = onTouchStart;
2126 obj[_pre + _touchend + id] = onTouchEnd;
2127 obj[_pre + 'dblclick' + id] = handler;
2129 obj.addEventListener(_touchstart, onTouchStart, false);
2130 obj.addEventListener(_touchend, onTouchEnd, false);
2132 // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
2133 // the browser doesn't fire touchend/pointerup events but does fire
2134 // native dblclicks. See #4127.
2135 // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
2136 obj.addEventListener('dblclick', handler, false);
2141 function removeDoubleTapListener(obj, id) {
2142 var touchstart = obj[_pre + _touchstart + id],
2143 touchend = obj[_pre + _touchend + id],
2144 dblclick = obj[_pre + 'dblclick' + id];
2146 obj.removeEventListener(_touchstart, touchstart, false);
2147 obj.removeEventListener(_touchend, touchend, false);
2149 obj.removeEventListener('dblclick', dblclick, false);
2156 * @namespace DomEvent
2157 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
2160 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
2162 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
2163 // Adds a listener function (`fn`) to a particular DOM event type of the
2164 // element `el`. You can optionally specify the context of the listener
2165 // (object the `this` keyword will point to). You can also pass several
2166 // space-separated types (e.g. `'click dblclick'`).
2169 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
2170 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2171 function on(obj, types, fn, context) {
2173 if (typeof types === 'object') {
2174 for (var type in types) {
2175 addOne(obj, type, types[type], fn);
2178 types = splitWords(types);
2180 for (var i = 0, len = types.length; i < len; i++) {
2181 addOne(obj, types[i], fn, context);
2188 var eventsKey = '_leaflet_events';
2190 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
2191 // Removes a previously added listener function. If no function is specified,
2192 // it will remove all the listeners of that particular DOM event from the element.
2193 // Note that if you passed a custom context to on, you must pass the same
2194 // context to `off` in order to remove the listener.
2197 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
2198 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2201 // @function off(el: HTMLElement): this
2202 // Removes all known event listeners
2203 function off(obj, types, fn, context) {
2205 if (typeof types === 'object') {
2206 for (var type in types) {
2207 removeOne(obj, type, types[type], fn);
2210 types = splitWords(types);
2212 for (var i = 0, len = types.length; i < len; i++) {
2213 removeOne(obj, types[i], fn, context);
2216 for (var j in obj[eventsKey]) {
2217 removeOne(obj, j, obj[eventsKey][j]);
2219 delete obj[eventsKey];
2225 function addOne(obj, type, fn, context) {
2226 var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
2228 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
2230 var handler = function (e) {
2231 return fn.call(context || obj, e || window.event);
2234 var originalHandler = handler;
2236 if (pointer && type.indexOf('touch') === 0) {
2237 // Needs DomEvent.Pointer.js
2238 addPointerListener(obj, type, handler, id);
2240 } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
2241 !(pointer && chrome)) {
2242 // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
2244 addDoubleTapListener(obj, handler, id);
2246 } else if ('addEventListener' in obj) {
2248 if (type === 'mousewheel') {
2249 obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2251 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
2252 handler = function (e) {
2253 e = e || window.event;
2254 if (isExternalTarget(obj, e)) {
2258 obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
2261 if (type === 'click' && android) {
2262 handler = function (e) {
2263 filterClick(e, originalHandler);
2266 obj.addEventListener(type, handler, false);
2269 } else if ('attachEvent' in obj) {
2270 obj.attachEvent('on' + type, handler);
2273 obj[eventsKey] = obj[eventsKey] || {};
2274 obj[eventsKey][id] = handler;
2277 function removeOne(obj, type, fn, context) {
2279 var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
2280 handler = obj[eventsKey] && obj[eventsKey][id];
2282 if (!handler) { return this; }
2284 if (pointer && type.indexOf('touch') === 0) {
2285 removePointerListener(obj, type, id);
2287 } else if (touch && (type === 'dblclick') && removeDoubleTapListener) {
2288 removeDoubleTapListener(obj, id);
2290 } else if ('removeEventListener' in obj) {
2292 if (type === 'mousewheel') {
2293 obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2296 obj.removeEventListener(
2297 type === 'mouseenter' ? 'mouseover' :
2298 type === 'mouseleave' ? 'mouseout' : type, handler, false);
2301 } else if ('detachEvent' in obj) {
2302 obj.detachEvent('on' + type, handler);
2305 obj[eventsKey][id] = null;
2308 // @function stopPropagation(ev: DOMEvent): this
2309 // Stop the given event from propagation to parent elements. Used inside the listener functions:
2311 // L.DomEvent.on(div, 'click', function (ev) {
2312 // L.DomEvent.stopPropagation(ev);
2315 function stopPropagation(e) {
2317 if (e.stopPropagation) {
2318 e.stopPropagation();
2319 } else if (e.originalEvent) { // In case of Leaflet event.
2320 e.originalEvent._stopped = true;
2322 e.cancelBubble = true;
2329 // @function disableScrollPropagation(el: HTMLElement): this
2330 // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
2331 function disableScrollPropagation(el) {
2332 addOne(el, 'mousewheel', stopPropagation);
2336 // @function disableClickPropagation(el: HTMLElement): this
2337 // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
2338 // `'mousedown'` and `'touchstart'` events (plus browser variants).
2339 function disableClickPropagation(el) {
2340 on(el, 'mousedown touchstart dblclick', stopPropagation);
2341 addOne(el, 'click', fakeStop);
2345 // @function preventDefault(ev: DOMEvent): this
2346 // Prevents the default action of the DOM Event `ev` from happening (such as
2347 // following a link in the href of the a element, or doing a POST request
2348 // with page reload when a `<form>` is submitted).
2349 // Use it inside listener functions.
2350 function preventDefault(e) {
2351 if (e.preventDefault) {
2354 e.returnValue = false;
2359 // @function stop(ev): this
2360 // Does `stopPropagation` and `preventDefault` at the same time.
2367 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
2368 // Gets normalized mouse position from a DOM event relative to the
2369 // `container` or to the whole page if not specified.
2370 function getMousePosition(e, container) {
2372 return new Point(e.clientX, e.clientY);
2375 var rect = container.getBoundingClientRect();
2378 e.clientX - rect.left - container.clientLeft,
2379 e.clientY - rect.top - container.clientTop);
2382 // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
2383 // and Firefox scrolls device pixels, not CSS pixels
2385 (win && chrome) ? 2 * window.devicePixelRatio :
2386 gecko ? window.devicePixelRatio : 1;
2388 // @function getWheelDelta(ev: DOMEvent): Number
2389 // Gets normalized wheel delta from a mousewheel DOM event, in vertical
2390 // pixels scrolled (negative if scrolling down).
2391 // Events from pointing devices without precise scrolling are mapped to
2392 // a best guess of 60 pixels.
2393 function getWheelDelta(e) {
2394 return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
2395 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
2396 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
2397 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
2398 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
2399 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
2400 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
2401 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
2405 var skipEvents = {};
2407 function fakeStop(e) {
2408 // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
2409 skipEvents[e.type] = true;
2412 function skipped(e) {
2413 var events = skipEvents[e.type];
2414 // reset when checking, as it's only used in map container and propagates outside of the map
2415 skipEvents[e.type] = false;
2419 // check if element really left/entered the event target (for mouseenter/mouseleave)
2420 function isExternalTarget(el, e) {
2422 var related = e.relatedTarget;
2424 if (!related) { return true; }
2427 while (related && (related !== el)) {
2428 related = related.parentNode;
2433 return (related !== el);
2438 // this is a horrible workaround for a bug in Android where a single touch triggers two click events
2439 function filterClick(e, handler) {
2440 var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
2441 elapsed = lastClick && (timeStamp - lastClick);
2443 // are they closer together than 500ms yet more than 100ms?
2444 // Android typically triggers them ~300ms apart while multiple listeners
2445 // on the same event should be triggered far faster;
2446 // or check if click is simulated on the element, and if it is, reject any non-simulated events
2448 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
2452 lastClick = timeStamp;
2460 var DomEvent = (Object.freeze || Object)({
2463 stopPropagation: stopPropagation,
2464 disableScrollPropagation: disableScrollPropagation,
2465 disableClickPropagation: disableClickPropagation,
2466 preventDefault: preventDefault,
2468 getMousePosition: getMousePosition,
2469 getWheelDelta: getWheelDelta,
2472 isExternalTarget: isExternalTarget,
2478 * @namespace DomUtil
2480 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
2481 * tree, used by Leaflet internally.
2483 * Most functions expecting or returning a `HTMLElement` also work for
2484 * SVG elements. The only difference is that classes refer to CSS classes
2485 * in HTML and SVG classes in SVG.
2489 // @property TRANSFORM: String
2490 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
2491 var TRANSFORM = testProp(
2492 ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
2494 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
2495 // the same for the transitionend event, in particular the Android 4.1 stock browser
2497 // @property TRANSITION: String
2498 // Vendor-prefixed transition style name.
2499 var TRANSITION = testProp(
2500 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
2502 // @property TRANSITION_END: String
2503 // Vendor-prefixed transitionend event name.
2504 var TRANSITION_END =
2505 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
2508 // @function get(id: String|HTMLElement): HTMLElement
2509 // Returns an element given its DOM id, or returns the element itself
2510 // if it was passed directly.
2512 return typeof id === 'string' ? document.getElementById(id) : id;
2515 // @function getStyle(el: HTMLElement, styleAttrib: String): String
2516 // Returns the value for a certain style attribute on an element,
2517 // including computed values or values set through CSS.
2518 function getStyle(el, style) {
2519 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
2521 if ((!value || value === 'auto') && document.defaultView) {
2522 var css = document.defaultView.getComputedStyle(el, null);
2523 value = css ? css[style] : null;
2525 return value === 'auto' ? null : value;
2528 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
2529 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
2530 function create$1(tagName, className, container) {
2531 var el = document.createElement(tagName);
2532 el.className = className || '';
2535 container.appendChild(el);
2540 // @function remove(el: HTMLElement)
2541 // Removes `el` from its parent element
2542 function remove(el) {
2543 var parent = el.parentNode;
2545 parent.removeChild(el);
2549 // @function empty(el: HTMLElement)
2550 // Removes all of `el`'s children elements from `el`
2551 function empty(el) {
2552 while (el.firstChild) {
2553 el.removeChild(el.firstChild);
2557 // @function toFront(el: HTMLElement)
2558 // Makes `el` the last child of its parent, so it renders in front of the other children.
2559 function toFront(el) {
2560 var parent = el.parentNode;
2561 if (parent.lastChild !== el) {
2562 parent.appendChild(el);
2566 // @function toBack(el: HTMLElement)
2567 // Makes `el` the first child of its parent, so it renders behind the other children.
2568 function toBack(el) {
2569 var parent = el.parentNode;
2570 if (parent.firstChild !== el) {
2571 parent.insertBefore(el, parent.firstChild);
2575 // @function hasClass(el: HTMLElement, name: String): Boolean
2576 // Returns `true` if the element's class attribute contains `name`.
2577 function hasClass(el, name) {
2578 if (el.classList !== undefined) {
2579 return el.classList.contains(name);
2581 var className = getClass(el);
2582 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
2585 // @function addClass(el: HTMLElement, name: String)
2586 // Adds `name` to the element's class attribute.
2587 function addClass(el, name) {
2588 if (el.classList !== undefined) {
2589 var classes = splitWords(name);
2590 for (var i = 0, len = classes.length; i < len; i++) {
2591 el.classList.add(classes[i]);
2593 } else if (!hasClass(el, name)) {
2594 var className = getClass(el);
2595 setClass(el, (className ? className + ' ' : '') + name);
2599 // @function removeClass(el: HTMLElement, name: String)
2600 // Removes `name` from the element's class attribute.
2601 function removeClass(el, name) {
2602 if (el.classList !== undefined) {
2603 el.classList.remove(name);
2605 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
2609 // @function setClass(el: HTMLElement, name: String)
2610 // Sets the element's class.
2611 function setClass(el, name) {
2612 if (el.className.baseVal === undefined) {
2613 el.className = name;
2615 // in case of SVG element
2616 el.className.baseVal = name;
2620 // @function getClass(el: HTMLElement): String
2621 // Returns the element's class.
2622 function getClass(el) {
2623 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
2626 // @function setOpacity(el: HTMLElement, opacity: Number)
2627 // Set the opacity of an element (including old IE support).
2628 // `opacity` must be a number from `0` to `1`.
2629 function setOpacity(el, value) {
2630 if ('opacity' in el.style) {
2631 el.style.opacity = value;
2632 } else if ('filter' in el.style) {
2633 _setOpacityIE(el, value);
2637 function _setOpacityIE(el, value) {
2639 filterName = 'DXImageTransform.Microsoft.Alpha';
2641 // filters collection throws an error if we try to retrieve a filter that doesn't exist
2643 filter = el.filters.item(filterName);
2645 // don't set opacity to 1 if we haven't already set an opacity,
2646 // it isn't needed and breaks transparent pngs.
2647 if (value === 1) { return; }
2650 value = Math.round(value * 100);
2653 filter.Enabled = (value !== 100);
2654 filter.Opacity = value;
2656 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
2660 // @function testProp(props: String[]): String|false
2661 // Goes through the array of style names and returns the first name
2662 // that is a valid style name for an element. If no such name is found,
2663 // it returns false. Useful for vendor-prefixed styles like `transform`.
2664 function testProp(props) {
2665 var style = document.documentElement.style;
2667 for (var i = 0; i < props.length; i++) {
2668 if (props[i] in style) {
2675 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
2676 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
2677 // and optionally scaled by `scale`. Does not have an effect if the
2678 // browser doesn't support 3D CSS transforms.
2679 function setTransform(el, offset, scale) {
2680 var pos = offset || new Point(0, 0);
2682 el.style[TRANSFORM] =
2684 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
2685 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
2686 (scale ? ' scale(' + scale + ')' : '');
2689 // @function setPosition(el: HTMLElement, position: Point)
2690 // Sets the position of `el` to coordinates specified by `position`,
2691 // using CSS translate or top/left positioning depending on the browser
2692 // (used by Leaflet internally to position its layers).
2693 function setPosition(el, point) {
2696 el._leaflet_pos = point;
2700 setTransform(el, point);
2702 el.style.left = point.x + 'px';
2703 el.style.top = point.y + 'px';
2707 // @function getPosition(el: HTMLElement): Point
2708 // Returns the coordinates of an element previously positioned with setPosition.
2709 function getPosition(el) {
2710 // this method is only used for elements previously positioned using setPosition,
2711 // so it's safe to cache the position for performance
2713 return el._leaflet_pos || new Point(0, 0);
2716 // @function disableTextSelection()
2717 // Prevents the user from generating `selectstart` DOM events, usually generated
2718 // when the user drags the mouse through a page with text. Used internally
2719 // by Leaflet to override the behaviour of any click-and-drag interaction on
2720 // the map. Affects drag interactions on the whole document.
2722 // @function enableTextSelection()
2723 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
2724 var disableTextSelection;
2725 var enableTextSelection;
2727 if ('onselectstart' in document) {
2728 disableTextSelection = function () {
2729 on(window, 'selectstart', preventDefault);
2731 enableTextSelection = function () {
2732 off(window, 'selectstart', preventDefault);
2735 var userSelectProperty = testProp(
2736 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
2738 disableTextSelection = function () {
2739 if (userSelectProperty) {
2740 var style = document.documentElement.style;
2741 _userSelect = style[userSelectProperty];
2742 style[userSelectProperty] = 'none';
2745 enableTextSelection = function () {
2746 if (userSelectProperty) {
2747 document.documentElement.style[userSelectProperty] = _userSelect;
2748 _userSelect = undefined;
2753 // @function disableImageDrag()
2754 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
2755 // for `dragstart` DOM events, usually generated when the user drags an image.
2756 function disableImageDrag() {
2757 on(window, 'dragstart', preventDefault);
2760 // @function enableImageDrag()
2761 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
2762 function enableImageDrag() {
2763 off(window, 'dragstart', preventDefault);
2766 var _outlineElement;
2768 // @function preventOutline(el: HTMLElement)
2769 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
2770 // of the element `el` invisible. Used internally by Leaflet to prevent
2771 // focusable elements from displaying an outline when the user performs a
2772 // drag interaction on them.
2773 function preventOutline(element) {
2774 while (element.tabIndex === -1) {
2775 element = element.parentNode;
2777 if (!element.style) { return; }
2779 _outlineElement = element;
2780 _outlineStyle = element.style.outline;
2781 element.style.outline = 'none';
2782 on(window, 'keydown', restoreOutline);
2785 // @function restoreOutline()
2786 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
2787 function restoreOutline() {
2788 if (!_outlineElement) { return; }
2789 _outlineElement.style.outline = _outlineStyle;
2790 _outlineElement = undefined;
2791 _outlineStyle = undefined;
2792 off(window, 'keydown', restoreOutline);
2796 var DomUtil = (Object.freeze || Object)({
2797 TRANSFORM: TRANSFORM,
2798 TRANSITION: TRANSITION,
2799 TRANSITION_END: TRANSITION_END,
2809 removeClass: removeClass,
2812 setOpacity: setOpacity,
2814 setTransform: setTransform,
2815 setPosition: setPosition,
2816 getPosition: getPosition,
2817 disableTextSelection: disableTextSelection,
2818 enableTextSelection: enableTextSelection,
2819 disableImageDrag: disableImageDrag,
2820 enableImageDrag: enableImageDrag,
2821 preventOutline: preventOutline,
2822 restoreOutline: restoreOutline
2826 * @class PosAnimation
2827 * @aka L.PosAnimation
2829 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
2833 * var fx = new L.PosAnimation();
2834 * fx.run(el, [300, 500], 0.5);
2837 * @constructor L.PosAnimation()
2838 * Creates a `PosAnimation` object.
2842 var PosAnimation = Evented.extend({
2844 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
2845 // Run an animation of a given element to a new position, optionally setting
2846 // duration in seconds (`0.25` by default) and easing linearity factor (3rd
2847 // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
2848 // `0.5` by default).
2849 run: function (el, newPos, duration, easeLinearity) {
2853 this._inProgress = true;
2854 this._duration = duration || 0.25;
2855 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
2857 this._startPos = getPosition(el);
2858 this._offset = newPos.subtract(this._startPos);
2859 this._startTime = +new Date();
2861 // @event start: Event
2862 // Fired when the animation starts
2869 // Stops the animation (if currently running).
2871 if (!this._inProgress) { return; }
2877 _animate: function () {
2879 this._animId = requestAnimFrame(this._animate, this);
2883 _step: function (round) {
2884 var elapsed = (+new Date()) - this._startTime,
2885 duration = this._duration * 1000;
2887 if (elapsed < duration) {
2888 this._runFrame(this._easeOut(elapsed / duration), round);
2895 _runFrame: function (progress, round) {
2896 var pos = this._startPos.add(this._offset.multiplyBy(progress));
2900 setPosition(this._el, pos);
2902 // @event step: Event
2903 // Fired continuously during the animation.
2907 _complete: function () {
2908 cancelAnimFrame(this._animId);
2910 this._inProgress = false;
2911 // @event end: Event
2912 // Fired when the animation ends.
2916 _easeOut: function (t) {
2917 return 1 - Math.pow(1 - t, this._easeOutPower);
2926 * The central class of the API — it is used to create a map on a page and manipulate it.
2931 * // initialize the map on the "map" div with a given center and zoom
2932 * var map = L.map('map', {
2933 * center: [51.505, -0.09],
2940 var Map = Evented.extend({
2943 // @section Map State Options
2944 // @option crs: CRS = L.CRS.EPSG3857
2945 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
2946 // sure what it means.
2949 // @option center: LatLng = undefined
2950 // Initial geographic center of the map
2953 // @option zoom: Number = undefined
2954 // Initial map zoom level
2957 // @option minZoom: Number = *
2958 // Minimum zoom level of the map.
2959 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
2960 // the lowest of their `minZoom` options will be used instead.
2963 // @option maxZoom: Number = *
2964 // Maximum zoom level of the map.
2965 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
2966 // the highest of their `maxZoom` options will be used instead.
2969 // @option layers: Layer[] = []
2970 // Array of layers that will be added to the map initially
2973 // @option maxBounds: LatLngBounds = null
2974 // When this option is set, the map restricts the view to the given
2975 // geographical bounds, bouncing the user back if the user tries to pan
2976 // outside the view. To set the restriction dynamically, use
2977 // [`setMaxBounds`](#map-setmaxbounds) method.
2978 maxBounds: undefined,
2980 // @option renderer: Renderer = *
2981 // The default method for drawing vector layers on the map. `L.SVG`
2982 // or `L.Canvas` by default depending on browser support.
2983 renderer: undefined,
2986 // @section Animation Options
2987 // @option zoomAnimation: Boolean = true
2988 // Whether the map zoom animation is enabled. By default it's enabled
2989 // in all browsers that support CSS3 Transitions except Android.
2990 zoomAnimation: true,
2992 // @option zoomAnimationThreshold: Number = 4
2993 // Won't animate zoom if the zoom difference exceeds this value.
2994 zoomAnimationThreshold: 4,
2996 // @option fadeAnimation: Boolean = true
2997 // Whether the tile fade animation is enabled. By default it's enabled
2998 // in all browsers that support CSS3 Transitions except Android.
2999 fadeAnimation: true,
3001 // @option markerZoomAnimation: Boolean = true
3002 // Whether markers animate their zoom with the zoom animation, if disabled
3003 // they will disappear for the length of the animation. By default it's
3004 // enabled in all browsers that support CSS3 Transitions except Android.
3005 markerZoomAnimation: true,
3007 // @option transform3DLimit: Number = 2^23
3008 // Defines the maximum size of a CSS translation transform. The default
3009 // value should not be changed unless a web browser positions layers in
3010 // the wrong place after doing a large `panBy`.
3011 transform3DLimit: 8388608, // Precision limit of a 32-bit float
3013 // @section Interaction Options
3014 // @option zoomSnap: Number = 1
3015 // Forces the map's zoom level to always be a multiple of this, particularly
3016 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
3017 // By default, the zoom level snaps to the nearest integer; lower values
3018 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
3019 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
3022 // @option zoomDelta: Number = 1
3023 // Controls how much the map's zoom level will change after a
3024 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
3025 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
3026 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
3029 // @option trackResize: Boolean = true
3030 // Whether the map automatically handles browser window resize to update itself.
3034 initialize: function (id, options) { // (HTMLElement or String, Object)
3035 options = setOptions(this, options);
3037 this._initContainer(id);
3040 // hack for https://github.com/Leaflet/Leaflet/issues/1980
3041 this._onResize = bind(this._onResize, this);
3045 if (options.maxBounds) {
3046 this.setMaxBounds(options.maxBounds);
3049 if (options.zoom !== undefined) {
3050 this._zoom = this._limitZoom(options.zoom);
3053 if (options.center && options.zoom !== undefined) {
3054 this.setView(toLatLng(options.center), options.zoom, {reset: true});
3057 this._handlers = [];
3059 this._zoomBoundLayers = {};
3060 this._sizeChanged = true;
3062 this.callInitHooks();
3064 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
3065 this._zoomAnimated = TRANSITION && any3d && !mobileOpera &&
3066 this.options.zoomAnimation;
3068 // zoom transitions run with the same duration for all layers, so if one of transitionend events
3069 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
3070 if (this._zoomAnimated) {
3071 this._createAnimProxy();
3072 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
3075 this._addLayers(this.options.layers);
3079 // @section Methods for modifying map state
3081 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
3082 // Sets the view of the map (geographical center and zoom) with the given
3083 // animation options.
3084 setView: function (center, zoom, options) {
3086 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
3087 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
3088 options = options || {};
3092 if (this._loaded && !options.reset && options !== true) {
3094 if (options.animate !== undefined) {
3095 options.zoom = extend({animate: options.animate}, options.zoom);
3096 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
3099 // try animating pan or zoom
3100 var moved = (this._zoom !== zoom) ?
3101 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
3102 this._tryAnimatedPan(center, options.pan);
3105 // prevent resize handler call, the view will refresh after animation anyway
3106 clearTimeout(this._sizeTimer);
3111 // animation didn't start, just reset the map view
3112 this._resetView(center, zoom);
3117 // @method setZoom(zoom: Number, options?: Zoom/pan options): this
3118 // Sets the zoom of the map.
3119 setZoom: function (zoom, options) {
3120 if (!this._loaded) {
3124 return this.setView(this.getCenter(), zoom, {zoom: options});
3127 // @method zoomIn(delta?: Number, options?: Zoom options): this
3128 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3129 zoomIn: function (delta, options) {
3130 delta = delta || (any3d ? this.options.zoomDelta : 1);
3131 return this.setZoom(this._zoom + delta, options);
3134 // @method zoomOut(delta?: Number, options?: Zoom options): this
3135 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3136 zoomOut: function (delta, options) {
3137 delta = delta || (any3d ? this.options.zoomDelta : 1);
3138 return this.setZoom(this._zoom - delta, options);
3141 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
3142 // Zooms the map while keeping a specified geographical point on the map
3143 // stationary (e.g. used internally for scroll zoom and double-click zoom).
3145 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
3146 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
3147 setZoomAround: function (latlng, zoom, options) {
3148 var scale = this.getZoomScale(zoom),
3149 viewHalf = this.getSize().divideBy(2),
3150 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
3152 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
3153 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
3155 return this.setView(newCenter, zoom, {zoom: options});
3158 _getBoundsCenterZoom: function (bounds, options) {
3160 options = options || {};
3161 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
3163 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3164 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3166 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
3168 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
3170 if (zoom === Infinity) {
3172 center: bounds.getCenter(),
3177 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
3179 swPoint = this.project(bounds.getSouthWest(), zoom),
3180 nePoint = this.project(bounds.getNorthEast(), zoom),
3181 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
3189 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
3190 // Sets a map view that contains the given geographical bounds with the
3191 // maximum zoom level possible.
3192 fitBounds: function (bounds, options) {
3194 bounds = toLatLngBounds(bounds);
3196 if (!bounds.isValid()) {
3197 throw new Error('Bounds are not valid.');
3200 var target = this._getBoundsCenterZoom(bounds, options);
3201 return this.setView(target.center, target.zoom, options);
3204 // @method fitWorld(options?: fitBounds options): this
3205 // Sets a map view that mostly contains the whole world with the maximum
3206 // zoom level possible.
3207 fitWorld: function (options) {
3208 return this.fitBounds([[-90, -180], [90, 180]], options);
3211 // @method panTo(latlng: LatLng, options?: Pan options): this
3212 // Pans the map to a given center.
3213 panTo: function (center, options) { // (LatLng)
3214 return this.setView(center, this._zoom, {pan: options});
3217 // @method panBy(offset: Point, options?: Pan options): this
3218 // Pans the map by a given number of pixels (animated).
3219 panBy: function (offset, options) {
3220 offset = toPoint(offset).round();
3221 options = options || {};
3223 if (!offset.x && !offset.y) {
3224 return this.fire('moveend');
3226 // If we pan too far, Chrome gets issues with tiles
3227 // and makes them disappear or appear in the wrong place (slightly offset) #2602
3228 if (options.animate !== true && !this.getSize().contains(offset)) {
3229 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
3233 if (!this._panAnim) {
3234 this._panAnim = new PosAnimation();
3237 'step': this._onPanTransitionStep,
3238 'end': this._onPanTransitionEnd
3242 // don't fire movestart if animating inertia
3243 if (!options.noMoveStart) {
3244 this.fire('movestart');
3247 // animate pan unless animate: false specified
3248 if (options.animate !== false) {
3249 addClass(this._mapPane, 'leaflet-pan-anim');
3251 var newPos = this._getMapPanePos().subtract(offset).round();
3252 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
3254 this._rawPanBy(offset);
3255 this.fire('move').fire('moveend');
3261 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
3262 // Sets the view of the map (geographical center and zoom) performing a smooth
3263 // pan-zoom animation.
3264 flyTo: function (targetCenter, targetZoom, options) {
3266 options = options || {};
3267 if (options.animate === false || !any3d) {
3268 return this.setView(targetCenter, targetZoom, options);
3273 var from = this.project(this.getCenter()),
3274 to = this.project(targetCenter),
3275 size = this.getSize(),
3276 startZoom = this._zoom;
3278 targetCenter = toLatLng(targetCenter);
3279 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
3281 var w0 = Math.max(size.x, size.y),
3282 w1 = w0 * this.getZoomScale(startZoom, targetZoom),
3283 u1 = (to.distanceTo(from)) || 1,
3288 var s1 = i ? -1 : 1,
3290 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
3291 b1 = 2 * s2 * rho2 * u1,
3293 sq = Math.sqrt(b * b + 1) - b;
3295 // workaround for floating point precision bug when sq = 0, log = -Infinite,
3296 // thus triggering an infinite loop in flyTo
3297 var log = sq < 0.000000001 ? -18 : Math.log(sq);
3302 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
3303 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
3304 function tanh(n) { return sinh(n) / cosh(n); }
3308 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
3309 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
3311 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
3313 var start = Date.now(),
3314 S = (r(1) - r0) / rho,
3315 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
3318 var t = (Date.now() - start) / duration,
3322 this._flyToFrame = requestAnimFrame(frame, this);
3325 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
3326 this.getScaleZoom(w0 / w(s), startZoom),
3331 ._move(targetCenter, targetZoom)
3336 this._moveStart(true);
3342 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
3343 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
3344 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
3345 flyToBounds: function (bounds, options) {
3346 var target = this._getBoundsCenterZoom(bounds, options);
3347 return this.flyTo(target.center, target.zoom, options);
3350 // @method setMaxBounds(bounds: Bounds): this
3351 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
3352 setMaxBounds: function (bounds) {
3353 bounds = toLatLngBounds(bounds);
3355 if (!bounds.isValid()) {
3356 this.options.maxBounds = null;
3357 return this.off('moveend', this._panInsideMaxBounds);
3358 } else if (this.options.maxBounds) {
3359 this.off('moveend', this._panInsideMaxBounds);
3362 this.options.maxBounds = bounds;
3365 this._panInsideMaxBounds();
3368 return this.on('moveend', this._panInsideMaxBounds);
3371 // @method setMinZoom(zoom: Number): this
3372 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
3373 setMinZoom: function (zoom) {
3374 this.options.minZoom = zoom;
3376 if (this._loaded && this.getZoom() < this.options.minZoom) {
3377 return this.setZoom(zoom);
3383 // @method setMaxZoom(zoom: Number): this
3384 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
3385 setMaxZoom: function (zoom) {
3386 this.options.maxZoom = zoom;
3388 if (this._loaded && (this.getZoom() > this.options.maxZoom)) {
3389 return this.setZoom(zoom);
3395 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
3396 // 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.
3397 panInsideBounds: function (bounds, options) {
3398 this._enforcingBounds = true;
3399 var center = this.getCenter(),
3400 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
3402 if (!center.equals(newCenter)) {
3403 this.panTo(newCenter, options);
3406 this._enforcingBounds = false;
3410 // @method invalidateSize(options: Zoom/Pan options): this
3411 // Checks if the map container size changed and updates the map if so —
3412 // call it after you've changed the map size dynamically, also animating
3413 // pan by default. If `options.pan` is `false`, panning will not occur.
3414 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
3415 // that it doesn't happen often even if the method is called many
3419 // @method invalidateSize(animate: Boolean): this
3420 // Checks if the map container size changed and updates the map if so —
3421 // call it after you've changed the map size dynamically, also animating
3423 invalidateSize: function (options) {
3424 if (!this._loaded) { return this; }
3429 }, options === true ? {animate: true} : options);
3431 var oldSize = this.getSize();
3432 this._sizeChanged = true;
3433 this._lastCenter = null;
3435 var newSize = this.getSize(),
3436 oldCenter = oldSize.divideBy(2).round(),
3437 newCenter = newSize.divideBy(2).round(),
3438 offset = oldCenter.subtract(newCenter);
3440 if (!offset.x && !offset.y) { return this; }
3442 if (options.animate && options.pan) {
3447 this._rawPanBy(offset);
3452 if (options.debounceMoveend) {
3453 clearTimeout(this._sizeTimer);
3454 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
3456 this.fire('moveend');
3460 // @section Map state change events
3461 // @event resize: ResizeEvent
3462 // Fired when the map is resized.
3463 return this.fire('resize', {
3469 // @section Methods for modifying map state
3470 // @method stop(): this
3471 // Stops the currently running `panTo` or `flyTo` animation, if any.
3473 this.setZoom(this._limitZoom(this._zoom));
3474 if (!this.options.zoomSnap) {
3475 this.fire('viewreset');
3477 return this._stop();
3480 // @section Geolocation methods
3481 // @method locate(options?: Locate options): this
3482 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
3483 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
3484 // and optionally sets the map view to the user's location with respect to
3485 // detection accuracy (or to the world view if geolocation failed).
3486 // Note that, if your page doesn't use HTTPS, this method will fail in
3487 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
3488 // See `Locate options` for more details.
3489 locate: function (options) {
3491 options = this._locateOptions = extend({
3495 // maxZoom: <Number>
3497 // enableHighAccuracy: false
3500 if (!('geolocation' in navigator)) {
3501 this._handleGeolocationError({
3503 message: 'Geolocation not supported.'
3508 var onResponse = bind(this._handleGeolocationResponse, this),
3509 onError = bind(this._handleGeolocationError, this);
3511 if (options.watch) {
3512 this._locationWatchId =
3513 navigator.geolocation.watchPosition(onResponse, onError, options);
3515 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
3520 // @method stopLocate(): this
3521 // Stops watching location previously initiated by `map.locate({watch: true})`
3522 // and aborts resetting the map view if map.locate was called with
3523 // `{setView: true}`.
3524 stopLocate: function () {
3525 if (navigator.geolocation && navigator.geolocation.clearWatch) {
3526 navigator.geolocation.clearWatch(this._locationWatchId);
3528 if (this._locateOptions) {
3529 this._locateOptions.setView = false;
3534 _handleGeolocationError: function (error) {
3536 message = error.message ||
3537 (c === 1 ? 'permission denied' :
3538 (c === 2 ? 'position unavailable' : 'timeout'));
3540 if (this._locateOptions.setView && !this._loaded) {
3544 // @section Location events
3545 // @event locationerror: ErrorEvent
3546 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
3547 this.fire('locationerror', {
3549 message: 'Geolocation error: ' + message + '.'
3553 _handleGeolocationResponse: function (pos) {
3554 var lat = pos.coords.latitude,
3555 lng = pos.coords.longitude,
3556 latlng = new LatLng(lat, lng),
3557 bounds = latlng.toBounds(pos.coords.accuracy),
3558 options = this._locateOptions;
3560 if (options.setView) {
3561 var zoom = this.getBoundsZoom(bounds);
3562 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
3568 timestamp: pos.timestamp
3571 for (var i in pos.coords) {
3572 if (typeof pos.coords[i] === 'number') {
3573 data[i] = pos.coords[i];
3577 // @event locationfound: LocationEvent
3578 // Fired when geolocation (using the [`locate`](#map-locate) method)
3579 // went successfully.
3580 this.fire('locationfound', data);
3583 // TODO handler.addTo
3584 // TODO Appropiate docs section?
3585 // @section Other Methods
3586 // @method addHandler(name: String, HandlerClass: Function): this
3587 // Adds a new `Handler` to the map, given its name and constructor function.
3588 addHandler: function (name, HandlerClass) {
3589 if (!HandlerClass) { return this; }
3591 var handler = this[name] = new HandlerClass(this);
3593 this._handlers.push(handler);
3595 if (this.options[name]) {
3602 // @method remove(): this
3603 // Destroys the map and clears all related event listeners.
3604 remove: function () {
3606 this._initEvents(true);
3608 if (this._containerId !== this._container._leaflet_id) {
3609 throw new Error('Map container is being reused by another instance');
3613 // throws error in IE6-8
3614 delete this._container._leaflet_id;
3615 delete this._containerId;
3618 this._container._leaflet_id = undefined;
3620 this._containerId = undefined;
3623 remove(this._mapPane);
3625 if (this._clearControlPos) {
3626 this._clearControlPos();
3629 this._clearHandlers();
3632 // @section Map state change events
3633 // @event unload: Event
3634 // Fired when the map is destroyed with [remove](#map-remove) method.
3635 this.fire('unload');
3639 for (i in this._layers) {
3640 this._layers[i].remove();
3642 for (i in this._panes) {
3643 remove(this._panes[i]);
3648 delete this._mapPane;
3649 delete this._renderer;
3654 // @section Other Methods
3655 // @method createPane(name: String, container?: HTMLElement): HTMLElement
3656 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
3657 // then returns it. The pane is created as a child of `container`, or
3658 // as a child of the main map pane if not set.
3659 createPane: function (name, container) {
3660 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3661 pane = create$1('div', className, container || this._mapPane);
3664 this._panes[name] = pane;
3669 // @section Methods for Getting Map State
3671 // @method getCenter(): LatLng
3672 // Returns the geographical center of the map view
3673 getCenter: function () {
3674 this._checkIfLoaded();
3676 if (this._lastCenter && !this._moved()) {
3677 return this._lastCenter;
3679 return this.layerPointToLatLng(this._getCenterLayerPoint());
3682 // @method getZoom(): Number
3683 // Returns the current zoom level of the map view
3684 getZoom: function () {
3688 // @method getBounds(): LatLngBounds
3689 // Returns the geographical bounds visible in the current map view
3690 getBounds: function () {
3691 var bounds = this.getPixelBounds(),
3692 sw = this.unproject(bounds.getBottomLeft()),
3693 ne = this.unproject(bounds.getTopRight());
3695 return new LatLngBounds(sw, ne);
3698 // @method getMinZoom(): Number
3699 // 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.
3700 getMinZoom: function () {
3701 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3704 // @method getMaxZoom(): Number
3705 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3706 getMaxZoom: function () {
3707 return this.options.maxZoom === undefined ?
3708 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3709 this.options.maxZoom;
3712 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number
3713 // Returns the maximum zoom level on which the given bounds fit to the map
3714 // view in its entirety. If `inside` (optional) is set to `true`, the method
3715 // instead returns the minimum zoom level on which the map view fits into
3716 // the given bounds in its entirety.
3717 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3718 bounds = toLatLngBounds(bounds);
3719 padding = toPoint(padding || [0, 0]);
3721 var zoom = this.getZoom() || 0,
3722 min = this.getMinZoom(),
3723 max = this.getMaxZoom(),
3724 nw = bounds.getNorthWest(),
3725 se = bounds.getSouthEast(),
3726 size = this.getSize().subtract(padding),
3727 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3728 snap = any3d ? this.options.zoomSnap : 1,
3729 scalex = size.x / boundsSize.x,
3730 scaley = size.y / boundsSize.y,
3731 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
3733 zoom = this.getScaleZoom(scale, zoom);
3736 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3737 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3740 return Math.max(min, Math.min(max, zoom));
3743 // @method getSize(): Point
3744 // Returns the current size of the map container (in pixels).
3745 getSize: function () {
3746 if (!this._size || this._sizeChanged) {
3747 this._size = new Point(
3748 this._container.clientWidth || 0,
3749 this._container.clientHeight || 0);
3751 this._sizeChanged = false;
3753 return this._size.clone();
3756 // @method getPixelBounds(): Bounds
3757 // Returns the bounds of the current map view in projected pixel
3758 // coordinates (sometimes useful in layer and overlay implementations).
3759 getPixelBounds: function (center, zoom) {
3760 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3761 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3764 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3765 // the map pane? "left point of the map layer" can be confusing, specially
3766 // since there can be negative offsets.
3767 // @method getPixelOrigin(): Point
3768 // Returns the projected pixel coordinates of the top left point of
3769 // the map layer (useful in custom layer and overlay implementations).
3770 getPixelOrigin: function () {
3771 this._checkIfLoaded();
3772 return this._pixelOrigin;
3775 // @method getPixelWorldBounds(zoom?: Number): Bounds
3776 // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3777 // If `zoom` is omitted, the map's current zoom level is used.
3778 getPixelWorldBounds: function (zoom) {
3779 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3782 // @section Other Methods
3784 // @method getPane(pane: String|HTMLElement): HTMLElement
3785 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3786 getPane: function (pane) {
3787 return typeof pane === 'string' ? this._panes[pane] : pane;
3790 // @method getPanes(): Object
3791 // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3792 // the panes as values.
3793 getPanes: function () {
3797 // @method getContainer: HTMLElement
3798 // Returns the HTML element that contains the map.
3799 getContainer: function () {
3800 return this._container;
3804 // @section Conversion Methods
3806 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3807 // Returns the scale factor to be applied to a map transition from zoom level
3808 // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3809 getZoomScale: function (toZoom, fromZoom) {
3810 // TODO replace with universal implementation after refactoring projections
3811 var crs = this.options.crs;
3812 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3813 return crs.scale(toZoom) / crs.scale(fromZoom);
3816 // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3817 // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3818 // level and everything is scaled by a factor of `scale`. Inverse of
3819 // [`getZoomScale`](#map-getZoomScale).
3820 getScaleZoom: function (scale, fromZoom) {
3821 var crs = this.options.crs;
3822 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3823 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3824 return isNaN(zoom) ? Infinity : zoom;
3827 // @method project(latlng: LatLng, zoom: Number): Point
3828 // Projects a geographical coordinate `LatLng` according to the projection
3829 // of the map's CRS, then scales it according to `zoom` and the CRS's
3830 // `Transformation`. The result is pixel coordinate relative to
3832 project: function (latlng, zoom) {
3833 zoom = zoom === undefined ? this._zoom : zoom;
3834 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
3837 // @method unproject(point: Point, zoom: Number): LatLng
3838 // Inverse of [`project`](#map-project).
3839 unproject: function (point, zoom) {
3840 zoom = zoom === undefined ? this._zoom : zoom;
3841 return this.options.crs.pointToLatLng(toPoint(point), zoom);
3844 // @method layerPointToLatLng(point: Point): LatLng
3845 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3846 // returns the corresponding geographical coordinate (for the current zoom level).
3847 layerPointToLatLng: function (point) {
3848 var projectedPoint = toPoint(point).add(this.getPixelOrigin());
3849 return this.unproject(projectedPoint);
3852 // @method latLngToLayerPoint(latlng: LatLng): Point
3853 // Given a geographical coordinate, returns the corresponding pixel coordinate
3854 // relative to the [origin pixel](#map-getpixelorigin).
3855 latLngToLayerPoint: function (latlng) {
3856 var projectedPoint = this.project(toLatLng(latlng))._round();
3857 return projectedPoint._subtract(this.getPixelOrigin());
3860 // @method wrapLatLng(latlng: LatLng): LatLng
3861 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
3862 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
3864 // By default this means longitude is wrapped around the dateline so its
3865 // value is between -180 and +180 degrees.
3866 wrapLatLng: function (latlng) {
3867 return this.options.crs.wrapLatLng(toLatLng(latlng));
3870 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
3871 // Returns a `LatLngBounds` with the same size as the given one, ensuring that
3872 // its center is within the CRS's bounds.
3873 // By default this means the center longitude is wrapped around the dateline so its
3874 // value is between -180 and +180 degrees, and the majority of the bounds
3875 // overlaps the CRS's bounds.
3876 wrapLatLngBounds: function (latlng) {
3877 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
3880 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
3881 // Returns the distance between two geographical coordinates according to
3882 // the map's CRS. By default this measures distance in meters.
3883 distance: function (latlng1, latlng2) {
3884 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
3887 // @method containerPointToLayerPoint(point: Point): Point
3888 // Given a pixel coordinate relative to the map container, returns the corresponding
3889 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
3890 containerPointToLayerPoint: function (point) { // (Point)
3891 return toPoint(point).subtract(this._getMapPanePos());
3894 // @method layerPointToContainerPoint(point: Point): Point
3895 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3896 // returns the corresponding pixel coordinate relative to the map container.
3897 layerPointToContainerPoint: function (point) { // (Point)
3898 return toPoint(point).add(this._getMapPanePos());
3901 // @method containerPointToLatLng(point: Point): LatLng
3902 // Given a pixel coordinate relative to the map container, returns
3903 // the corresponding geographical coordinate (for the current zoom level).
3904 containerPointToLatLng: function (point) {
3905 var layerPoint = this.containerPointToLayerPoint(toPoint(point));
3906 return this.layerPointToLatLng(layerPoint);
3909 // @method latLngToContainerPoint(latlng: LatLng): Point
3910 // Given a geographical coordinate, returns the corresponding pixel coordinate
3911 // relative to the map container.
3912 latLngToContainerPoint: function (latlng) {
3913 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
3916 // @method mouseEventToContainerPoint(ev: MouseEvent): Point
3917 // Given a MouseEvent object, returns the pixel coordinate relative to the
3918 // map container where the event took place.
3919 mouseEventToContainerPoint: function (e) {
3920 return getMousePosition(e, this._container);
3923 // @method mouseEventToLayerPoint(ev: MouseEvent): Point
3924 // Given a MouseEvent object, returns the pixel coordinate relative to
3925 // the [origin pixel](#map-getpixelorigin) where the event took place.
3926 mouseEventToLayerPoint: function (e) {
3927 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
3930 // @method mouseEventToLatLng(ev: MouseEvent): LatLng
3931 // Given a MouseEvent object, returns geographical coordinate where the
3932 // event took place.
3933 mouseEventToLatLng: function (e) { // (MouseEvent)
3934 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
3938 // map initialization methods
3940 _initContainer: function (id) {
3941 var container = this._container = get(id);
3944 throw new Error('Map container not found.');
3945 } else if (container._leaflet_id) {
3946 throw new Error('Map container is already initialized.');
3949 on(container, 'scroll', this._onScroll, this);
3950 this._containerId = stamp(container);
3953 _initLayout: function () {
3954 var container = this._container;
3956 this._fadeAnimated = this.options.fadeAnimation && any3d;
3958 addClass(container, 'leaflet-container' +
3959 (touch ? ' leaflet-touch' : '') +
3960 (retina ? ' leaflet-retina' : '') +
3961 (ielt9 ? ' leaflet-oldie' : '') +
3962 (safari ? ' leaflet-safari' : '') +
3963 (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
3965 var position = getStyle(container, 'position');