2 * Leaflet 1.1.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 = global.L || {})));
9 }(this, (function (exports) { 'use strict';
11 var version = "1.1.0";
16 * Various utility functions, used by Leaflet internally.
19 // @function extend(dest: Object, src?: Object): Object
20 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
21 function extend(dest) {
24 for (j = 1, len = arguments.length; j < len; j++) {
33 // @function create(proto: Object, properties?: Object): Object
34 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
35 var create = Object.create || (function () {
37 return function (proto) {
43 // @function bind(fn: Function, …): Function
44 // 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).
45 // Has a `L.bind()` shortcut.
46 function bind(fn, obj) {
47 var slice = Array.prototype.slice;
50 return fn.bind.apply(fn, slice.call(arguments, 1));
53 var args = slice.call(arguments, 2);
56 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
60 // @property lastId: Number
61 // Last unique ID used by [`stamp()`](#util-stamp)
64 // @function stamp(obj: Object): Number
65 // Returns the unique ID of an object, assiging it one if it doesn't have it.
68 obj._leaflet_id = obj._leaflet_id || ++lastId;
69 return obj._leaflet_id;
73 // @function throttle(fn: Function, time: Number, context: Object): Function
74 // Returns a function which executes function `fn` with the given scope `context`
75 // (so that the `this` keyword refers to `context` inside `fn`'s code). The function
76 // `fn` will be called no more than one time per given amount of `time`. The arguments
77 // received by the bound function will be any arguments passed when binding the
78 // function, followed by any arguments passed when invoking the bound function.
79 // Has an `L.throttle` shortcut.
80 function throttle(fn, time, context) {
81 var lock, args, wrapperFn, later;
84 // reset lock and call if queued
87 wrapperFn.apply(context, args);
92 wrapperFn = function () {
94 // called too soon, queue to call later
98 // call and lock until later
99 fn.apply(context, arguments);
100 setTimeout(later, time);
108 // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
109 // Returns the number `num` modulo `range` in such a way so it lies within
110 // `range[0]` and `range[1]`. The returned value will be always smaller than
111 // `range[1]` unless `includeMax` is set to `true`.
112 function wrapNum(x, range, includeMax) {
116 return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
119 // @function falseFn(): Function
120 // Returns a function which always returns `false`.
121 function falseFn() { return false; }
123 // @function formatNum(num: Number, digits?: Number): Number
124 // Returns the number `num` rounded to `digits` decimals, or to 5 decimals by default.
125 function formatNum(num, digits) {
126 var pow = Math.pow(10, digits || 5);
127 return Math.round(num * pow) / pow;
130 // @function trim(str: String): String
131 // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
133 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
136 // @function splitWords(str: String): String[]
137 // Trims and splits the string on whitespace and returns the array of parts.
138 function splitWords(str) {
139 return trim(str).split(/\s+/);
142 // @function setOptions(obj: Object, options: Object): Object
143 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
144 function setOptions(obj, options) {
145 if (!obj.hasOwnProperty('options')) {
146 obj.options = obj.options ? create(obj.options) : {};
148 for (var i in options) {
149 obj.options[i] = options[i];
154 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
155 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
156 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
157 // be appended at the end. If `uppercase` is `true`, the parameter names will
158 // be uppercased (e.g. `'?A=foo&B=bar'`)
159 function getParamString(obj, existingUrl, uppercase) {
162 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
164 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
167 var templateRe = /\{ *([\w_\-]+) *\}/g;
169 // @function template(str: String, data: Object): String
170 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
171 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
172 // `('Hello foo, bar')`. You can also specify functions instead of strings for
173 // data values — they will be evaluated passing `data` as an argument.
174 function template(str, data) {
175 return str.replace(templateRe, function (str, key) {
176 var value = data[key];
178 if (value === undefined) {
179 throw new Error('No value provided for variable ' + str);
181 } else if (typeof value === 'function') {
188 // @function isArray(obj): Boolean
189 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
190 var isArray = Array.isArray || function (obj) {
191 return (Object.prototype.toString.call(obj) === '[object Array]');
194 // @function indexOf(array: Array, el: Object): Number
195 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
196 function indexOf(array, el) {
197 for (var i = 0; i < array.length; i++) {
198 if (array[i] === el) { return i; }
203 // @property emptyImageUrl: String
204 // Data URI string containing a base64-encoded empty GIF image.
205 // Used as a hack to free memory from unused images on WebKit-powered
206 // mobile devices (by setting image `src` to this string).
207 var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
209 // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
211 function getPrefixed(name) {
212 return window['webkit' + name] || window['moz' + name] || window['ms' + name];
217 // fallback for IE 7-8
218 function timeoutDefer(fn) {
219 var time = +new Date(),
220 timeToCall = Math.max(0, 16 - (time - lastTime));
222 lastTime = time + timeToCall;
223 return window.setTimeout(fn, timeToCall);
226 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
227 var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
228 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
230 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
231 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
232 // `context` if given. When `immediate` is set, `fn` is called immediately if
233 // the browser doesn't have native support for
234 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
235 // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
236 function requestAnimFrame(fn, context, immediate) {
237 if (immediate && requestFn === timeoutDefer) {
240 return requestFn.call(window, bind(fn, context));
244 // @function cancelAnimFrame(id: Number): undefined
245 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
246 function cancelAnimFrame(id) {
248 cancelFn.call(window, id);
253 var Util = (Object.freeze || Object)({
262 formatNum: formatNum,
264 splitWords: splitWords,
265 setOptions: setOptions,
266 getParamString: getParamString,
270 emptyImageUrl: emptyImageUrl,
271 requestFn: requestFn,
273 requestAnimFrame: requestAnimFrame,
274 cancelAnimFrame: cancelAnimFrame
283 // Thanks to John Resig and Dean Edwards for inspiration!
287 Class.extend = function (props) {
289 // @function extend(props: Object): Function
290 // [Extends the current class](#class-inheritance) given the properties to be included.
291 // Returns a Javascript function that is a class constructor (to be called with `new`).
292 var NewClass = function () {
294 // call the constructor
295 if (this.initialize) {
296 this.initialize.apply(this, arguments);
299 // call all constructor hooks
300 this.callInitHooks();
303 var parentProto = NewClass.__super__ = this.prototype;
305 var proto = create(parentProto);
306 proto.constructor = NewClass;
308 NewClass.prototype = proto;
310 // inherit parent's statics
311 for (var i in this) {
312 if (this.hasOwnProperty(i) && i !== 'prototype' && i !== '__super__') {
313 NewClass[i] = this[i];
317 // mix static properties into the class
319 extend(NewClass, props.statics);
320 delete props.statics;
323 // mix includes into the prototype
324 if (props.includes) {
325 checkDeprecatedMixinEvents(props.includes);
326 extend.apply(null, [proto].concat(props.includes));
327 delete props.includes;
332 props.options = extend(create(proto.options), props.options);
335 // mix given properties into the prototype
336 extend(proto, props);
338 proto._initHooks = [];
340 // add method for calling all hooks
341 proto.callInitHooks = function () {
343 if (this._initHooksCalled) { return; }
345 if (parentProto.callInitHooks) {
346 parentProto.callInitHooks.call(this);
349 this._initHooksCalled = true;
351 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
352 proto._initHooks[i].call(this);
360 // @function include(properties: Object): this
361 // [Includes a mixin](#class-includes) into the current class.
362 Class.include = function (props) {
363 extend(this.prototype, props);
367 // @function mergeOptions(options: Object): this
368 // [Merges `options`](#class-options) into the defaults of the class.
369 Class.mergeOptions = function (options) {
370 extend(this.prototype.options, options);
374 // @function addInitHook(fn: Function): this
375 // Adds a [constructor hook](#class-constructor-hooks) to the class.
376 Class.addInitHook = function (fn) { // (Function) || (String, args...)
377 var args = Array.prototype.slice.call(arguments, 1);
379 var init = typeof fn === 'function' ? fn : function () {
380 this[fn].apply(this, args);
383 this.prototype._initHooks = this.prototype._initHooks || [];
384 this.prototype._initHooks.push(init);
388 function checkDeprecatedMixinEvents(includes) {
389 if (!L || !L.Mixin) { return; }
391 includes = isArray(includes) ? includes : [includes];
393 for (var i = 0; i < includes.length; i++) {
394 if (includes[i] === L.Mixin.Events) {
395 console.warn('Deprecated include of L.Mixin.Events: ' +
396 'this property will be removed in future releases, ' +
397 'please inherit from L.Evented instead.', new Error().stack);
407 * 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).
412 * map.on('click', function(e) {
417 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
420 * function onClick(e) { ... }
422 * map.on('click', onClick);
423 * map.off('click', onClick);
428 /* @method on(type: String, fn: Function, context?: Object): this
429 * 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'`).
432 * @method on(eventMap: Object): this
433 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
435 on: function (types, fn, context) {
437 // types can be a map of types/handlers
438 if (typeof types === 'object') {
439 for (var type in types) {
440 // we don't process space-separated events here for performance;
441 // it's a hot path since Layer uses the on(obj) syntax
442 this._on(type, types[type], fn);
446 // types can be a string of space-separated words
447 types = splitWords(types);
449 for (var i = 0, len = types.length; i < len; i++) {
450 this._on(types[i], fn, context);
457 /* @method off(type: String, fn?: Function, context?: Object): this
458 * 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.
461 * @method off(eventMap: Object): this
462 * Removes a set of type/listener pairs.
466 * Removes all listeners to all events on the object.
468 off: function (types, fn, context) {
471 // clear all listeners if called without arguments
474 } else if (typeof types === 'object') {
475 for (var type in types) {
476 this._off(type, types[type], fn);
480 types = splitWords(types);
482 for (var i = 0, len = types.length; i < len; i++) {
483 this._off(types[i], fn, context);
490 // attach listener (without syntactic sugar now)
491 _on: function (type, fn, context) {
492 this._events = this._events || {};
494 /* get/init listeners for type */
495 var typeListeners = this._events[type];
496 if (!typeListeners) {
498 this._events[type] = typeListeners;
501 if (context === this) {
502 // Less memory footprint.
505 var newListener = {fn: fn, ctx: context},
506 listeners = typeListeners;
508 // check if fn already there
509 for (var i = 0, len = listeners.length; i < len; i++) {
510 if (listeners[i].fn === fn && listeners[i].ctx === context) {
515 listeners.push(newListener);
518 _off: function (type, fn, context) {
523 if (!this._events) { return; }
525 listeners = this._events[type];
532 // Set all removed listeners to noop so they are not called if remove happens in fire
533 for (i = 0, len = listeners.length; i < len; i++) {
534 listeners[i].fn = falseFn;
536 // clear all listeners for a type if function isn't specified
537 delete this._events[type];
541 if (context === this) {
547 // find fn and remove it
548 for (i = 0, len = listeners.length; i < len; i++) {
549 var l = listeners[i];
550 if (l.ctx !== context) { continue; }
553 // set the removed listener to noop so that's not called if remove happens in fire
556 if (this._firingCount) {
557 /* copy array in case events are being fired */
558 this._events[type] = listeners = listeners.slice();
560 listeners.splice(i, 1);
568 // @method fire(type: String, data?: Object, propagate?: Boolean): this
569 // Fires an event of the specified type. You can optionally provide an data
570 // object — the first argument of the listener function will contain its
571 // properties. The event can optionally be propagated to event parents.
572 fire: function (type, data, propagate) {
573 if (!this.listens(type, propagate)) { return this; }
575 var event = extend({}, data, {type: type, target: this});
578 var listeners = this._events[type];
581 this._firingCount = (this._firingCount + 1) || 1;
582 for (var i = 0, len = listeners.length; i < len; i++) {
583 var l = listeners[i];
584 l.fn.call(l.ctx || this, event);
592 // propagate the event to parents (set with addEventParent)
593 this._propagateEvent(event);
599 // @method listens(type: String): Boolean
600 // Returns `true` if a particular event type has any listeners attached to it.
601 listens: function (type, propagate) {
602 var listeners = this._events && this._events[type];
603 if (listeners && listeners.length) { return true; }
606 // also check parents for listeners if event propagates
607 for (var id in this._eventParents) {
608 if (this._eventParents[id].listens(type, propagate)) { return true; }
614 // @method once(…): this
615 // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
616 once: function (types, fn, context) {
618 if (typeof types === 'object') {
619 for (var type in types) {
620 this.once(type, types[type], fn);
625 var handler = bind(function () {
627 .off(types, fn, context)
628 .off(types, handler, context);
631 // add a listener that's executed once and removed after that
633 .on(types, fn, context)
634 .on(types, handler, context);
637 // @method addEventParent(obj: Evented): this
638 // Adds an event parent - an `Evented` that will receive propagated events
639 addEventParent: function (obj) {
640 this._eventParents = this._eventParents || {};
641 this._eventParents[stamp(obj)] = obj;
645 // @method removeEventParent(obj: Evented): this
646 // Removes an event parent, so it will stop receiving propagated events
647 removeEventParent: function (obj) {
648 if (this._eventParents) {
649 delete this._eventParents[stamp(obj)];
654 _propagateEvent: function (e) {
655 for (var id in this._eventParents) {
656 this._eventParents[id].fire(e.type, extend({layer: e.target}, e), true);
661 // aliases; we should ditch those eventually
663 // @method addEventListener(…): this
664 // Alias to [`on(…)`](#evented-on)
665 Events.addEventListener = Events.on;
667 // @method removeEventListener(…): this
668 // Alias to [`off(…)`](#evented-off)
670 // @method clearAllEventListeners(…): this
671 // Alias to [`off()`](#evented-off)
672 Events.removeEventListener = Events.clearAllEventListeners = Events.off;
674 // @method addOneTimeEventListener(…): this
675 // Alias to [`once(…)`](#evented-once)
676 Events.addOneTimeEventListener = Events.once;
678 // @method fireEvent(…): this
679 // Alias to [`fire(…)`](#evented-fire)
680 Events.fireEvent = Events.fire;
682 // @method hasEventListeners(…): Boolean
683 // Alias to [`listens(…)`](#evented-listens)
684 Events.hasEventListeners = Events.listens;
686 var Evented = Class.extend(Events);
692 * Represents a point with `x` and `y` coordinates in pixels.
697 * var point = L.point(200, 300);
700 * 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:
703 * map.panBy([200, 300]);
704 * map.panBy(L.point(200, 300));
708 function Point(x, y, round) {
709 // @property x: Number; The `x` coordinate of the point
710 this.x = (round ? Math.round(x) : x);
711 // @property y: Number; The `y` coordinate of the point
712 this.y = (round ? Math.round(y) : y);
717 // @method clone(): Point
718 // Returns a copy of the current point.
720 return new Point(this.x, this.y);
723 // @method add(otherPoint: Point): Point
724 // Returns the result of addition of the current and the given points.
725 add: function (point) {
726 // non-destructive, returns a new point
727 return this.clone()._add(toPoint(point));
730 _add: function (point) {
731 // destructive, used directly for performance in situations where it's safe to modify existing point
737 // @method subtract(otherPoint: Point): Point
738 // Returns the result of subtraction of the given point from the current.
739 subtract: function (point) {
740 return this.clone()._subtract(toPoint(point));
743 _subtract: function (point) {
749 // @method divideBy(num: Number): Point
750 // Returns the result of division of the current point by the given number.
751 divideBy: function (num) {
752 return this.clone()._divideBy(num);
755 _divideBy: function (num) {
761 // @method multiplyBy(num: Number): Point
762 // Returns the result of multiplication of the current point by the given number.
763 multiplyBy: function (num) {
764 return this.clone()._multiplyBy(num);
767 _multiplyBy: function (num) {
773 // @method scaleBy(scale: Point): Point
774 // Multiply each coordinate of the current point by each coordinate of
775 // `scale`. In linear algebra terms, multiply the point by the
776 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
777 // defined by `scale`.
778 scaleBy: function (point) {
779 return new Point(this.x * point.x, this.y * point.y);
782 // @method unscaleBy(scale: Point): Point
783 // Inverse of `scaleBy`. Divide each coordinate of the current point by
784 // each coordinate of `scale`.
785 unscaleBy: function (point) {
786 return new Point(this.x / point.x, this.y / point.y);
789 // @method round(): Point
790 // Returns a copy of the current point with rounded coordinates.
792 return this.clone()._round();
795 _round: function () {
796 this.x = Math.round(this.x);
797 this.y = Math.round(this.y);
801 // @method floor(): Point
802 // Returns a copy of the current point with floored coordinates (rounded down).
804 return this.clone()._floor();
807 _floor: function () {
808 this.x = Math.floor(this.x);
809 this.y = Math.floor(this.y);
813 // @method ceil(): Point
814 // Returns a copy of the current point with ceiled coordinates (rounded up).
816 return this.clone()._ceil();
820 this.x = Math.ceil(this.x);
821 this.y = Math.ceil(this.y);
825 // @method distanceTo(otherPoint: Point): Number
826 // Returns the cartesian distance between the current and the given points.
827 distanceTo: function (point) {
828 point = toPoint(point);
830 var x = point.x - this.x,
831 y = point.y - this.y;
833 return Math.sqrt(x * x + y * y);
836 // @method equals(otherPoint: Point): Boolean
837 // Returns `true` if the given point has the same coordinates.
838 equals: function (point) {
839 point = toPoint(point);
841 return point.x === this.x &&
845 // @method contains(otherPoint: Point): Boolean
846 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
847 contains: function (point) {
848 point = toPoint(point);
850 return Math.abs(point.x) <= Math.abs(this.x) &&
851 Math.abs(point.y) <= Math.abs(this.y);
854 // @method toString(): String
855 // Returns a string representation of the point for debugging purposes.
856 toString: function () {
858 formatNum(this.x) + ', ' +
859 formatNum(this.y) + ')';
863 // @factory L.point(x: Number, y: Number, round?: Boolean)
864 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
867 // @factory L.point(coords: Number[])
868 // Expects an array of the form `[x, y]` instead.
871 // @factory L.point(coords: Object)
872 // Expects a plain object of the form `{x: Number, y: Number}` instead.
873 function toPoint(x, y, round) {
874 if (x instanceof Point) {
878 return new Point(x[0], x[1]);
880 if (x === undefined || x === null) {
883 if (typeof x === 'object' && 'x' in x && 'y' in x) {
884 return new Point(x.x, x.y);
886 return new Point(x, y, round);
893 * Represents a rectangular area in pixel coordinates.
898 * var p1 = L.point(10, 10),
899 * p2 = L.point(40, 60),
900 * bounds = L.bounds(p1, p2);
903 * 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:
906 * otherBounds.intersects([[10, 10], [40, 60]]);
910 function Bounds(a, b) {
913 var points = b ? [a, b] : a;
915 for (var i = 0, len = points.length; i < len; i++) {
916 this.extend(points[i]);
921 // @method extend(point: Point): this
922 // Extends the bounds to contain the given point.
923 extend: function (point) { // (Point)
924 point = toPoint(point);
926 // @property min: Point
927 // The top left corner of the rectangle.
928 // @property max: Point
929 // The bottom right corner of the rectangle.
930 if (!this.min && !this.max) {
931 this.min = point.clone();
932 this.max = point.clone();
934 this.min.x = Math.min(point.x, this.min.x);
935 this.max.x = Math.max(point.x, this.max.x);
936 this.min.y = Math.min(point.y, this.min.y);
937 this.max.y = Math.max(point.y, this.max.y);
942 // @method getCenter(round?: Boolean): Point
943 // Returns the center point of the bounds.
944 getCenter: function (round) {
946 (this.min.x + this.max.x) / 2,
947 (this.min.y + this.max.y) / 2, round);
950 // @method getBottomLeft(): Point
951 // Returns the bottom-left point of the bounds.
952 getBottomLeft: function () {
953 return new Point(this.min.x, this.max.y);
956 // @method getTopRight(): Point
957 // Returns the top-right point of the bounds.
958 getTopRight: function () { // -> Point
959 return new Point(this.max.x, this.min.y);
962 // @method getTopLeft(): Point
963 // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).
964 getTopLeft: function () {
965 return this.min; // left, top
968 // @method getBottomRight(): Point
969 // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).
970 getBottomRight: function () {
971 return this.max; // right, bottom
974 // @method getSize(): Point
975 // Returns the size of the given bounds
976 getSize: function () {
977 return this.max.subtract(this.min);
980 // @method contains(otherBounds: Bounds): Boolean
981 // Returns `true` if the rectangle contains the given one.
983 // @method contains(point: Point): Boolean
984 // Returns `true` if the rectangle contains the given point.
985 contains: function (obj) {
988 if (typeof obj[0] === 'number' || obj instanceof Point) {
994 if (obj instanceof Bounds) {
1001 return (min.x >= this.min.x) &&
1002 (max.x <= this.max.x) &&
1003 (min.y >= this.min.y) &&
1004 (max.y <= this.max.y);
1007 // @method intersects(otherBounds: Bounds): Boolean
1008 // Returns `true` if the rectangle intersects the given bounds. Two bounds
1009 // intersect if they have at least one point in common.
1010 intersects: function (bounds) { // (Bounds) -> Boolean
1011 bounds = toBounds(bounds);
1017 xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1018 yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1020 return xIntersects && yIntersects;
1023 // @method overlaps(otherBounds: Bounds): Boolean
1024 // Returns `true` if the rectangle overlaps the given bounds. Two bounds
1025 // overlap if their intersection is an area.
1026 overlaps: function (bounds) { // (Bounds) -> Boolean
1027 bounds = toBounds(bounds);
1033 xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1034 yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1036 return xOverlaps && yOverlaps;
1039 isValid: function () {
1040 return !!(this.min && this.max);
1045 // @factory L.bounds(corner1: Point, corner2: Point)
1046 // Creates a Bounds object from two corners coordinate pairs.
1048 // @factory L.bounds(points: Point[])
1049 // Creates a Bounds object from the given array of points.
1050 function toBounds(a, b) {
1051 if (!a || a instanceof Bounds) {
1054 return new Bounds(a, b);
1058 * @class LatLngBounds
1059 * @aka L.LatLngBounds
1061 * Represents a rectangular geographical area on a map.
1066 * var corner1 = L.latLng(40.712, -74.227),
1067 * corner2 = L.latLng(40.774, -74.125),
1068 * bounds = L.latLngBounds(corner1, corner2);
1071 * 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:
1075 * [40.712, -74.227],
1080 * 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.
1083 function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
1084 if (!corner1) { return; }
1086 var latlngs = corner2 ? [corner1, corner2] : corner1;
1088 for (var i = 0, len = latlngs.length; i < len; i++) {
1089 this.extend(latlngs[i]);
1093 LatLngBounds.prototype = {
1095 // @method extend(latlng: LatLng): this
1096 // Extend the bounds to contain the given point
1099 // @method extend(otherBounds: LatLngBounds): this
1100 // Extend the bounds to contain the given bounds
1101 extend: function (obj) {
1102 var sw = this._southWest,
1103 ne = this._northEast,
1106 if (obj instanceof LatLng) {
1110 } else if (obj instanceof LatLngBounds) {
1111 sw2 = obj._southWest;
1112 ne2 = obj._northEast;
1114 if (!sw2 || !ne2) { return this; }
1117 return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
1121 this._southWest = new LatLng(sw2.lat, sw2.lng);
1122 this._northEast = new LatLng(ne2.lat, ne2.lng);
1124 sw.lat = Math.min(sw2.lat, sw.lat);
1125 sw.lng = Math.min(sw2.lng, sw.lng);
1126 ne.lat = Math.max(ne2.lat, ne.lat);
1127 ne.lng = Math.max(ne2.lng, ne.lng);
1133 // @method pad(bufferRatio: Number): LatLngBounds
1134 // Returns bigger bounds created by extending the current bounds by a given percentage in each direction.
1135 pad: function (bufferRatio) {
1136 var sw = this._southWest,
1137 ne = this._northEast,
1138 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1139 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1141 return new LatLngBounds(
1142 new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1143 new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1146 // @method getCenter(): LatLng
1147 // Returns the center point of the bounds.
1148 getCenter: function () {
1150 (this._southWest.lat + this._northEast.lat) / 2,
1151 (this._southWest.lng + this._northEast.lng) / 2);
1154 // @method getSouthWest(): LatLng
1155 // Returns the south-west point of the bounds.
1156 getSouthWest: function () {
1157 return this._southWest;
1160 // @method getNorthEast(): LatLng
1161 // Returns the north-east point of the bounds.
1162 getNorthEast: function () {
1163 return this._northEast;
1166 // @method getNorthWest(): LatLng
1167 // Returns the north-west point of the bounds.
1168 getNorthWest: function () {
1169 return new LatLng(this.getNorth(), this.getWest());
1172 // @method getSouthEast(): LatLng
1173 // Returns the south-east point of the bounds.
1174 getSouthEast: function () {
1175 return new LatLng(this.getSouth(), this.getEast());
1178 // @method getWest(): Number
1179 // Returns the west longitude of the bounds
1180 getWest: function () {
1181 return this._southWest.lng;
1184 // @method getSouth(): Number
1185 // Returns the south latitude of the bounds
1186 getSouth: function () {
1187 return this._southWest.lat;
1190 // @method getEast(): Number
1191 // Returns the east longitude of the bounds
1192 getEast: function () {
1193 return this._northEast.lng;
1196 // @method getNorth(): Number
1197 // Returns the north latitude of the bounds
1198 getNorth: function () {
1199 return this._northEast.lat;
1202 // @method contains(otherBounds: LatLngBounds): Boolean
1203 // Returns `true` if the rectangle contains the given one.
1206 // @method contains (latlng: LatLng): Boolean
1207 // Returns `true` if the rectangle contains the given point.
1208 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1209 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
1210 obj = toLatLng(obj);
1212 obj = toLatLngBounds(obj);
1215 var sw = this._southWest,
1216 ne = this._northEast,
1219 if (obj instanceof LatLngBounds) {
1220 sw2 = obj.getSouthWest();
1221 ne2 = obj.getNorthEast();
1226 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1227 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1230 // @method intersects(otherBounds: LatLngBounds): Boolean
1231 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1232 intersects: function (bounds) {
1233 bounds = toLatLngBounds(bounds);
1235 var sw = this._southWest,
1236 ne = this._northEast,
1237 sw2 = bounds.getSouthWest(),
1238 ne2 = bounds.getNorthEast(),
1240 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1241 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1243 return latIntersects && lngIntersects;
1246 // @method overlaps(otherBounds: Bounds): Boolean
1247 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1248 overlaps: function (bounds) {
1249 bounds = toLatLngBounds(bounds);
1251 var sw = this._southWest,
1252 ne = this._northEast,
1253 sw2 = bounds.getSouthWest(),
1254 ne2 = bounds.getNorthEast(),
1256 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1257 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1259 return latOverlaps && lngOverlaps;
1262 // @method toBBoxString(): String
1263 // 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.
1264 toBBoxString: function () {
1265 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1268 // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
1269 // 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.
1270 equals: function (bounds, maxMargin) {
1271 if (!bounds) { return false; }
1273 bounds = toLatLngBounds(bounds);
1275 return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
1276 this._northEast.equals(bounds.getNorthEast(), maxMargin);
1279 // @method isValid(): Boolean
1280 // Returns `true` if the bounds are properly initialized.
1281 isValid: function () {
1282 return !!(this._southWest && this._northEast);
1286 // TODO International date line?
1288 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
1289 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
1292 // @factory L.latLngBounds(latlngs: LatLng[])
1293 // 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).
1294 function toLatLngBounds(a, b) {
1295 if (a instanceof LatLngBounds) {
1298 return new LatLngBounds(a, b);
1304 * Represents a geographical point with a certain latitude and longitude.
1309 * var latlng = L.latLng(50.5, 30.5);
1312 * 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:
1315 * map.panTo([50, 30]);
1316 * map.panTo({lon: 30, lat: 50});
1317 * map.panTo({lat: 50, lng: 30});
1318 * map.panTo(L.latLng(50, 30));
1322 function LatLng(lat, lng, alt) {
1323 if (isNaN(lat) || isNaN(lng)) {
1324 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1327 // @property lat: Number
1328 // Latitude in degrees
1331 // @property lng: Number
1332 // Longitude in degrees
1335 // @property alt: Number
1336 // Altitude in meters (optional)
1337 if (alt !== undefined) {
1342 LatLng.prototype = {
1343 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1344 // 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.
1345 equals: function (obj, maxMargin) {
1346 if (!obj) { return false; }
1348 obj = toLatLng(obj);
1350 var margin = Math.max(
1351 Math.abs(this.lat - obj.lat),
1352 Math.abs(this.lng - obj.lng));
1354 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1357 // @method toString(): String
1358 // Returns a string representation of the point (for debugging purposes).
1359 toString: function (precision) {
1361 formatNum(this.lat, precision) + ', ' +
1362 formatNum(this.lng, precision) + ')';
1365 // @method distanceTo(otherLatLng: LatLng): Number
1366 // Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula).
1367 distanceTo: function (other) {
1368 return Earth.distance(this, toLatLng(other));
1371 // @method wrap(): LatLng
1372 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1374 return Earth.wrapLatLng(this);
1377 // @method toBounds(sizeInMeters: Number): LatLngBounds
1378 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
1379 toBounds: function (sizeInMeters) {
1380 var latAccuracy = 180 * sizeInMeters / 40075017,
1381 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1383 return toLatLngBounds(
1384 [this.lat - latAccuracy, this.lng - lngAccuracy],
1385 [this.lat + latAccuracy, this.lng + lngAccuracy]);
1388 clone: function () {
1389 return new LatLng(this.lat, this.lng, this.alt);
1395 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1396 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1399 // @factory L.latLng(coords: Array): LatLng
1400 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1403 // @factory L.latLng(coords: Object): LatLng
1404 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1406 function toLatLng(a, b, c) {
1407 if (a instanceof LatLng) {
1410 if (isArray(a) && typeof a[0] !== 'object') {
1411 if (a.length === 3) {
1412 return new LatLng(a[0], a[1], a[2]);
1414 if (a.length === 2) {
1415 return new LatLng(a[0], a[1]);
1419 if (a === undefined || a === null) {
1422 if (typeof a === 'object' && 'lat' in a) {
1423 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1425 if (b === undefined) {
1428 return new LatLng(a, b, c);
1434 * Object that defines coordinate reference systems for projecting
1435 * geographical points into pixel (screen) coordinates and back (and to
1436 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
1437 * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
1439 * Leaflet defines the most usual CRSs by default. If you want to use a
1440 * CRS not defined by default, take a look at the
1441 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
1445 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
1446 // Projects geographical coordinates into pixel coordinates for a given zoom.
1447 latLngToPoint: function (latlng, zoom) {
1448 var projectedPoint = this.projection.project(latlng),
1449 scale = this.scale(zoom);
1451 return this.transformation._transform(projectedPoint, scale);
1454 // @method pointToLatLng(point: Point, zoom: Number): LatLng
1455 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
1456 // zoom into geographical coordinates.
1457 pointToLatLng: function (point, zoom) {
1458 var scale = this.scale(zoom),
1459 untransformedPoint = this.transformation.untransform(point, scale);
1461 return this.projection.unproject(untransformedPoint);
1464 // @method project(latlng: LatLng): Point
1465 // Projects geographical coordinates into coordinates in units accepted for
1466 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
1467 project: function (latlng) {
1468 return this.projection.project(latlng);
1471 // @method unproject(point: Point): LatLng
1472 // Given a projected coordinate returns the corresponding LatLng.
1473 // The inverse of `project`.
1474 unproject: function (point) {
1475 return this.projection.unproject(point);
1478 // @method scale(zoom: Number): Number
1479 // Returns the scale used when transforming projected coordinates into
1480 // pixel coordinates for a particular zoom. For example, it returns
1481 // `256 * 2^zoom` for Mercator-based CRS.
1482 scale: function (zoom) {
1483 return 256 * Math.pow(2, zoom);
1486 // @method zoom(scale: Number): Number
1487 // Inverse of `scale()`, returns the zoom level corresponding to a scale
1488 // factor of `scale`.
1489 zoom: function (scale) {
1490 return Math.log(scale / 256) / Math.LN2;
1493 // @method getProjectedBounds(zoom: Number): Bounds
1494 // Returns the projection's bounds scaled and transformed for the provided `zoom`.
1495 getProjectedBounds: function (zoom) {
1496 if (this.infinite) { return null; }
1498 var b = this.projection.bounds,
1499 s = this.scale(zoom),
1500 min = this.transformation.transform(b.min, s),
1501 max = this.transformation.transform(b.max, s);
1503 return new Bounds(min, max);
1506 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
1507 // Returns the distance between two geographical coordinates.
1509 // @property code: String
1510 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
1512 // @property wrapLng: Number[]
1513 // An array of two numbers defining whether the longitude (horizontal) coordinate
1514 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
1515 // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
1517 // @property wrapLat: Number[]
1518 // Like `wrapLng`, but for the latitude (vertical) axis.
1520 // wrapLng: [min, max],
1521 // wrapLat: [min, max],
1523 // @property infinite: Boolean
1524 // If true, the coordinate space will be unbounded (infinite in both axes)
1527 // @method wrapLatLng(latlng: LatLng): LatLng
1528 // Returns a `LatLng` where lat and lng has been wrapped according to the
1529 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
1530 wrapLatLng: function (latlng) {
1531 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
1532 lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
1535 return new LatLng(lat, lng, alt);
1538 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
1539 // Returns a `LatLngBounds` with the same size as the given one, ensuring
1540 // that its center is within the CRS's bounds.
1541 // Only accepts actual `L.LatLngBounds` instances, not arrays.
1542 wrapLatLngBounds: function (bounds) {
1543 var center = bounds.getCenter(),
1544 newCenter = this.wrapLatLng(center),
1545 latShift = center.lat - newCenter.lat,
1546 lngShift = center.lng - newCenter.lng;
1548 if (latShift === 0 && lngShift === 0) {
1552 var sw = bounds.getSouthWest(),
1553 ne = bounds.getNorthEast(),
1554 newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),
1555 newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);
1557 return new LatLngBounds(newSw, newNe);
1565 * Serves as the base for CRS that are global such that they cover the earth.
1566 * Can only be used as the base for other CRS and cannot be used directly,
1567 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
1571 var Earth = extend({}, CRS, {
1572 wrapLng: [-180, 180],
1574 // Mean Earth Radius, as recommended for use by
1575 // the International Union of Geodesy and Geophysics,
1576 // see http://rosettacode.org/wiki/Haversine_formula
1579 // distance between two geographical points using spherical law of cosines approximation
1580 distance: function (latlng1, latlng2) {
1581 var rad = Math.PI / 180,
1582 lat1 = latlng1.lat * rad,
1583 lat2 = latlng2.lat * rad,
1584 a = Math.sin(lat1) * Math.sin(lat2) +
1585 Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad);
1587 return this.R * Math.acos(Math.min(a, 1));
1592 * @namespace Projection
1593 * @projection L.Projection.SphericalMercator
1595 * Spherical Mercator projection — the most common projection for online maps,
1596 * used by almost all free and commercial tile providers. Assumes that Earth is
1597 * a sphere. Used by the `EPSG:3857` CRS.
1600 var SphericalMercator = {
1603 MAX_LATITUDE: 85.0511287798,
1605 project: function (latlng) {
1606 var d = Math.PI / 180,
1607 max = this.MAX_LATITUDE,
1608 lat = Math.max(Math.min(max, latlng.lat), -max),
1609 sin = Math.sin(lat * d);
1612 this.R * latlng.lng * d,
1613 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
1616 unproject: function (point) {
1617 var d = 180 / Math.PI;
1620 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
1621 point.x * d / this.R);
1624 bounds: (function () {
1625 var d = 6378137 * Math.PI;
1626 return new Bounds([-d, -d], [d, d]);
1631 * @class Transformation
1632 * @aka L.Transformation
1634 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1635 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1636 * the reverse. Used by Leaflet in its projections code.
1641 * var transformation = L.transformation(2, 5, -1, 10),
1642 * p = L.point(1, 2),
1643 * p2 = transformation.transform(p), // L.point(7, 8)
1644 * p3 = transformation.untransform(p2); // L.point(1, 2)
1649 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1650 // Creates a `Transformation` object with the given coefficients.
1651 function Transformation(a, b, c, d) {
1653 // use array properties
1666 Transformation.prototype = {
1667 // @method transform(point: Point, scale?: Number): Point
1668 // Returns a transformed point, optionally multiplied by the given scale.
1669 // Only accepts actual `L.Point` instances, not arrays.
1670 transform: function (point, scale) { // (Point, Number) -> Point
1671 return this._transform(point.clone(), scale);
1674 // destructive transform (faster)
1675 _transform: function (point, scale) {
1677 point.x = scale * (this._a * point.x + this._b);
1678 point.y = scale * (this._c * point.y + this._d);
1682 // @method untransform(point: Point, scale?: Number): Point
1683 // Returns the reverse transformation of the given point, optionally divided
1684 // by the given scale. Only accepts actual `L.Point` instances, not arrays.
1685 untransform: function (point, scale) {
1688 (point.x / scale - this._b) / this._a,
1689 (point.y / scale - this._d) / this._c);
1693 // factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1695 // @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1696 // Instantiates a Transformation object with the given coefficients.
1699 // @factory L.transformation(coefficients: Array): Transformation
1700 // Expects an coeficients array of the form
1701 // `[a: Number, b: Number, c: Number, d: Number]`.
1703 function toTransformation(a, b, c, d) {
1704 return new Transformation(a, b, c, d);
1709 * @crs L.CRS.EPSG3857
1711 * The most common CRS for online maps, used by almost all free and commercial
1712 * tile providers. Uses Spherical Mercator projection. Set in by default in
1713 * Map's `crs` option.
1716 var EPSG3857 = extend({}, Earth, {
1718 projection: SphericalMercator,
1720 transformation: (function () {
1721 var scale = 0.5 / (Math.PI * SphericalMercator.R);
1722 return toTransformation(scale, 0.5, -scale, 0.5);
1726 var EPSG900913 = extend({}, EPSG3857, {
1730 // @namespace SVG; @section
1731 // There are several static functions which can be called without instantiating L.SVG:
1733 // @function create(name: String): SVGElement
1734 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
1735 // corresponding to the class name passed. For example, using 'line' will return
1736 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
1737 function svgCreate(name) {
1738 return document.createElementNS('http://www.w3.org/2000/svg', name);
1741 // @function pointsToPath(rings: Point[], closed: Boolean): String
1742 // Generates a SVG path string for multiple rings, with each ring turning
1743 // into "M..L..L.." instructions
1744 function pointsToPath(rings, closed) {
1746 i, j, len, len2, points, p;
1748 for (i = 0, len = rings.length; i < len; i++) {
1751 for (j = 0, len2 = points.length; j < len2; j++) {
1753 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
1756 // closes the ring for polygons; "x" is VML syntax
1757 str += closed ? (svg ? 'z' : 'x') : '';
1760 // SVG complains about empty path strings
1761 return str || 'M0 0';
1765 * @namespace Browser
1768 * A namespace with static properties for browser/feature detection used by Leaflet internally.
1773 * if (L.Browser.ielt9) {
1774 * alert('Upgrade your browser, dude!');
1779 var style$1 = document.documentElement.style;
1781 // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
1782 var ie = 'ActiveXObject' in window;
1784 // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
1785 var ielt9 = ie && !document.addEventListener;
1787 // @property edge: Boolean; `true` for the Edge web browser.
1788 var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
1790 // @property webkit: Boolean;
1791 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
1792 var webkit = userAgentContains('webkit');
1794 // @property android: Boolean
1795 // `true` for any browser running on an Android platform.
1796 var android = userAgentContains('android');
1798 // @property android23: Boolean; `true` for browsers running on Android 2 or Android 3.
1799 var android23 = userAgentContains('android 2') || userAgentContains('android 3');
1801 // @property opera: Boolean; `true` for the Opera browser
1802 var opera = !!window.opera;
1804 // @property chrome: Boolean; `true` for the Chrome browser.
1805 var chrome = userAgentContains('chrome');
1807 // @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
1808 var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
1810 // @property safari: Boolean; `true` for the Safari browser.
1811 var safari = !chrome && userAgentContains('safari');
1813 var phantom = userAgentContains('phantom');
1815 // @property opera12: Boolean
1816 // `true` for the Opera browser supporting CSS transforms (version 12 or later).
1817 var opera12 = 'OTransition' in style$1;
1819 // @property win: Boolean; `true` when the browser is running in a Windows platform
1820 var win = navigator.platform.indexOf('Win') === 0;
1822 // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
1823 var ie3d = ie && ('transition' in style$1);
1825 // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
1826 var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;
1828 // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
1829 var gecko3d = 'MozPerspective' in style$1;
1831 // @property any3d: Boolean
1832 // `true` for all browsers supporting CSS transforms.
1833 var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
1835 // @property mobile: Boolean; `true` for all browsers running in a mobile device.
1836 var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
1838 // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
1839 var mobileWebkit = mobile && webkit;
1841 // @property mobileWebkit3d: Boolean
1842 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
1843 var mobileWebkit3d = mobile && webkit3d;
1845 // @property msPointer: Boolean
1846 // `true` for browsers implementing the Microsoft touch events model (notably IE10).
1847 var msPointer = !window.PointerEvent && window.MSPointerEvent;
1849 // @property pointer: Boolean
1850 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
1851 var pointer = !!(window.PointerEvent || msPointer);
1853 // @property touch: Boolean
1854 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
1855 // This does not necessarily mean that the browser is running in a computer with
1856 // a touchscreen, it only means that the browser is capable of understanding
1858 var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
1859 (window.DocumentTouch && document instanceof window.DocumentTouch));
1861 // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
1862 var mobileOpera = mobile && opera;
1864 // @property mobileGecko: Boolean
1865 // `true` for gecko-based browsers running in a mobile device.
1866 var mobileGecko = mobile && gecko;
1868 // @property retina: Boolean
1869 // `true` for browsers on a high-resolution "retina" screen.
1870 var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
1873 // @property canvas: Boolean
1874 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
1875 var canvas = (function () {
1876 return !!document.createElement('canvas').getContext;
1879 // @property svg: Boolean
1880 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
1881 var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect);
1883 // @property vml: Boolean
1884 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
1885 var vml = !svg && (function () {
1887 var div = document.createElement('div');
1888 div.innerHTML = '<v:shape adj="1"/>';
1890 var shape = div.firstChild;
1891 shape.style.behavior = 'url(#default#VML)';
1893 return shape && (typeof shape.adj === 'object');
1901 function userAgentContains(str) {
1902 return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
1906 var Browser = (Object.freeze || Object)({
1912 android23: android23,
1925 mobileWebkit: mobileWebkit,
1926 mobileWebkit3d: mobileWebkit3d,
1927 msPointer: msPointer,
1930 mobileOpera: mobileOpera,
1931 mobileGecko: mobileGecko,
1939 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
1943 var POINTER_DOWN = msPointer ? 'MSPointerDown' : 'pointerdown';
1944 var POINTER_MOVE = msPointer ? 'MSPointerMove' : 'pointermove';
1945 var POINTER_UP = msPointer ? 'MSPointerUp' : 'pointerup';
1946 var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel';
1947 var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION'];
1949 var _pointerDocListener = false;
1951 // DomEvent.DoubleTap needs to know about this
1952 var _pointersCount = 0;
1954 // Provides a touch events wrapper for (ms)pointer events.
1955 // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
1957 function addPointerListener(obj, type, handler, id) {
1958 if (type === 'touchstart') {
1959 _addPointerStart(obj, handler, id);
1961 } else if (type === 'touchmove') {
1962 _addPointerMove(obj, handler, id);
1964 } else if (type === 'touchend') {
1965 _addPointerEnd(obj, handler, id);
1971 function removePointerListener(obj, type, id) {
1972 var handler = obj['_leaflet_' + type + id];
1974 if (type === 'touchstart') {
1975 obj.removeEventListener(POINTER_DOWN, handler, false);
1977 } else if (type === 'touchmove') {
1978 obj.removeEventListener(POINTER_MOVE, handler, false);
1980 } else if (type === 'touchend') {
1981 obj.removeEventListener(POINTER_UP, handler, false);
1982 obj.removeEventListener(POINTER_CANCEL, handler, false);
1988 function _addPointerStart(obj, handler, id) {
1989 var onDown = bind(function (e) {
1990 if (e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
1991 // In IE11, some touch events needs to fire for form controls, or
1992 // the controls will stop working. We keep a whitelist of tag names that
1993 // need these events. For other target tags, we prevent default on the event.
1994 if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
2001 _handlePointer(e, handler);
2004 obj['_leaflet_touchstart' + id] = onDown;
2005 obj.addEventListener(POINTER_DOWN, onDown, false);
2007 // need to keep track of what pointers and how many are active to provide e.touches emulation
2008 if (!_pointerDocListener) {
2009 // we listen documentElement as any drags that end by moving the touch off the screen get fired there
2010 document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true);
2011 document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true);
2012 document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true);
2013 document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
2015 _pointerDocListener = true;
2019 function _globalPointerDown(e) {
2020 _pointers[e.pointerId] = e;
2024 function _globalPointerMove(e) {
2025 if (_pointers[e.pointerId]) {
2026 _pointers[e.pointerId] = e;
2030 function _globalPointerUp(e) {
2031 delete _pointers[e.pointerId];
2035 function _handlePointer(e, handler) {
2037 for (var i in _pointers) {
2038 e.touches.push(_pointers[i]);
2040 e.changedTouches = [e];
2045 function _addPointerMove(obj, handler, id) {
2046 var onMove = function (e) {
2047 // don't fire touch moves when mouse isn't down
2048 if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
2050 _handlePointer(e, handler);
2053 obj['_leaflet_touchmove' + id] = onMove;
2054 obj.addEventListener(POINTER_MOVE, onMove, false);
2057 function _addPointerEnd(obj, handler, id) {
2058 var onUp = function (e) {
2059 _handlePointer(e, handler);
2062 obj['_leaflet_touchend' + id] = onUp;
2063 obj.addEventListener(POINTER_UP, onUp, false);
2064 obj.addEventListener(POINTER_CANCEL, onUp, false);
2068 * Extends the event handling code with double tap support for mobile browsers.
2071 var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart';
2072 var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend';
2073 var _pre = '_leaflet_';
2075 // inspired by Zepto touch code by Thomas Fuchs
2076 function addDoubleTapListener(obj, handler, id) {
2081 function onTouchStart(e) {
2085 if ((!edge) || e.pointerType === 'mouse') { return; }
2086 count = _pointersCount;
2088 count = e.touches.length;
2091 if (count > 1) { return; }
2093 var now = Date.now(),
2094 delta = now - (last || now);
2096 touch$$1 = e.touches ? e.touches[0] : e;
2097 doubleTap = (delta > 0 && delta <= delay);
2101 function onTouchEnd(e) {
2102 if (doubleTap && !touch$$1.cancelBubble) {
2104 if ((!edge) || e.pointerType === 'mouse') { return; }
2105 // work around .type being readonly with MSPointer* events
2109 for (i in touch$$1) {
2111 newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop;
2113 touch$$1 = newTouch;
2115 touch$$1.type = 'dblclick';
2121 obj[_pre + _touchstart + id] = onTouchStart;
2122 obj[_pre + _touchend + id] = onTouchEnd;
2123 obj[_pre + 'dblclick' + id] = handler;
2125 obj.addEventListener(_touchstart, onTouchStart, false);
2126 obj.addEventListener(_touchend, onTouchEnd, false);
2128 // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
2129 // the browser doesn't fire touchend/pointerup events but does fire
2130 // native dblclicks. See #4127.
2131 // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
2132 obj.addEventListener('dblclick', handler, false);
2137 function removeDoubleTapListener(obj, id) {
2138 var touchstart = obj[_pre + _touchstart + id],
2139 touchend = obj[_pre + _touchend + id],
2140 dblclick = obj[_pre + 'dblclick' + id];
2142 obj.removeEventListener(_touchstart, touchstart, false);
2143 obj.removeEventListener(_touchend, touchend, false);
2145 obj.removeEventListener('dblclick', dblclick, false);
2152 * @namespace DomEvent
2153 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
2156 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
2158 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
2159 // Adds a listener function (`fn`) to a particular DOM event type of the
2160 // element `el`. You can optionally specify the context of the listener
2161 // (object the `this` keyword will point to). You can also pass several
2162 // space-separated types (e.g. `'click dblclick'`).
2165 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
2166 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2167 function on(obj, types, fn, context) {
2169 if (typeof types === 'object') {
2170 for (var type in types) {
2171 addOne(obj, type, types[type], fn);
2174 types = splitWords(types);
2176 for (var i = 0, len = types.length; i < len; i++) {
2177 addOne(obj, types[i], fn, context);
2184 var eventsKey = '_leaflet_events';
2186 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
2187 // Removes a previously added listener function. If no function is specified,
2188 // it will remove all the listeners of that particular DOM event from the element.
2189 // Note that if you passed a custom context to on, you must pass the same
2190 // context to `off` in order to remove the listener.
2193 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
2194 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2197 // @function off(el: HTMLElement): this
2198 // Removes all known event listeners
2199 function off(obj, types, fn, context) {
2201 if (typeof types === 'object') {
2202 for (var type in types) {
2203 removeOne(obj, type, types[type], fn);
2206 types = splitWords(types);
2208 for (var i = 0, len = types.length; i < len; i++) {
2209 removeOne(obj, types[i], fn, context);
2212 for (var j in obj[eventsKey]) {
2213 removeOne(obj, j, obj[eventsKey][j]);
2215 delete obj[eventsKey];
2219 function addOne(obj, type, fn, context) {
2220 var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
2222 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
2224 var handler = function (e) {
2225 return fn.call(context || obj, e || window.event);
2228 var originalHandler = handler;
2230 if (pointer && type.indexOf('touch') === 0) {
2231 // Needs DomEvent.Pointer.js
2232 addPointerListener(obj, type, handler, id);
2234 } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
2235 !(pointer && chrome)) {
2236 // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
2238 addDoubleTapListener(obj, handler, id);
2240 } else if ('addEventListener' in obj) {
2242 if (type === 'mousewheel') {
2243 obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2245 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
2246 handler = function (e) {
2247 e = e || window.event;
2248 if (isExternalTarget(obj, e)) {
2252 obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
2255 if (type === 'click' && android) {
2256 handler = function (e) {
2257 filterClick(e, originalHandler);
2260 obj.addEventListener(type, handler, false);
2263 } else if ('attachEvent' in obj) {
2264 obj.attachEvent('on' + type, handler);
2267 obj[eventsKey] = obj[eventsKey] || {};
2268 obj[eventsKey][id] = handler;
2271 function removeOne(obj, type, fn, context) {
2273 var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
2274 handler = obj[eventsKey] && obj[eventsKey][id];
2276 if (!handler) { return this; }
2278 if (pointer && type.indexOf('touch') === 0) {
2279 removePointerListener(obj, type, id);
2281 } else if (touch && (type === 'dblclick') && removeDoubleTapListener) {
2282 removeDoubleTapListener(obj, id);
2284 } else if ('removeEventListener' in obj) {
2286 if (type === 'mousewheel') {
2287 obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2290 obj.removeEventListener(
2291 type === 'mouseenter' ? 'mouseover' :
2292 type === 'mouseleave' ? 'mouseout' : type, handler, false);
2295 } else if ('detachEvent' in obj) {
2296 obj.detachEvent('on' + type, handler);
2299 obj[eventsKey][id] = null;
2302 // @function stopPropagation(ev: DOMEvent): this
2303 // Stop the given event from propagation to parent elements. Used inside the listener functions:
2305 // L.DomEvent.on(div, 'click', function (ev) {
2306 // L.DomEvent.stopPropagation(ev);
2309 function stopPropagation(e) {
2311 if (e.stopPropagation) {
2312 e.stopPropagation();
2313 } else if (e.originalEvent) { // In case of Leaflet event.
2314 e.originalEvent._stopped = true;
2316 e.cancelBubble = true;
2323 // @function disableScrollPropagation(el: HTMLElement): this
2324 // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
2325 function disableScrollPropagation(el) {
2326 return addOne(el, 'mousewheel', stopPropagation);
2329 // @function disableClickPropagation(el: HTMLElement): this
2330 // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
2331 // `'mousedown'` and `'touchstart'` events (plus browser variants).
2332 function disableClickPropagation(el) {
2333 on(el, 'mousedown touchstart dblclick', stopPropagation);
2334 addOne(el, 'click', fakeStop);
2338 // @function preventDefault(ev: DOMEvent): this
2339 // Prevents the default action of the DOM Event `ev` from happening (such as
2340 // following a link in the href of the a element, or doing a POST request
2341 // with page reload when a `<form>` is submitted).
2342 // Use it inside listener functions.
2343 function preventDefault(e) {
2344 if (e.preventDefault) {
2347 e.returnValue = false;
2352 // @function stop(ev): this
2353 // Does `stopPropagation` and `preventDefault` at the same time.
2360 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
2361 // Gets normalized mouse position from a DOM event relative to the
2362 // `container` or to the whole page if not specified.
2363 function getMousePosition(e, container) {
2365 return new Point(e.clientX, e.clientY);
2368 var rect = container.getBoundingClientRect();
2371 e.clientX - rect.left - container.clientLeft,
2372 e.clientY - rect.top - container.clientTop);
2375 // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
2376 // and Firefox scrolls device pixels, not CSS pixels
2378 (win && chrome) ? 2 * window.devicePixelRatio :
2379 gecko ? window.devicePixelRatio : 1;
2381 // @function getWheelDelta(ev: DOMEvent): Number
2382 // Gets normalized wheel delta from a mousewheel DOM event, in vertical
2383 // pixels scrolled (negative if scrolling down).
2384 // Events from pointing devices without precise scrolling are mapped to
2385 // a best guess of 60 pixels.
2386 function getWheelDelta(e) {
2387 return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
2388 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
2389 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
2390 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
2391 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
2392 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
2393 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
2394 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
2398 var skipEvents = {};
2400 function fakeStop(e) {
2401 // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
2402 skipEvents[e.type] = true;
2405 function skipped(e) {
2406 var events = skipEvents[e.type];
2407 // reset when checking, as it's only used in map container and propagates outside of the map
2408 skipEvents[e.type] = false;
2412 // check if element really left/entered the event target (for mouseenter/mouseleave)
2413 function isExternalTarget(el, e) {
2415 var related = e.relatedTarget;
2417 if (!related) { return true; }
2420 while (related && (related !== el)) {
2421 related = related.parentNode;
2426 return (related !== el);
2431 // this is a horrible workaround for a bug in Android where a single touch triggers two click events
2432 function filterClick(e, handler) {
2433 var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
2434 elapsed = lastClick && (timeStamp - lastClick);
2436 // are they closer together than 500ms yet more than 100ms?
2437 // Android typically triggers them ~300ms apart while multiple listeners
2438 // on the same event should be triggered far faster;
2439 // or check if click is simulated on the element, and if it is, reject any non-simulated events
2441 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
2445 lastClick = timeStamp;
2453 var DomEvent = (Object.freeze || Object)({
2456 stopPropagation: stopPropagation,
2457 disableScrollPropagation: disableScrollPropagation,
2458 disableClickPropagation: disableClickPropagation,
2459 preventDefault: preventDefault,
2461 getMousePosition: getMousePosition,
2462 getWheelDelta: getWheelDelta,
2465 isExternalTarget: isExternalTarget,
2471 * @namespace DomUtil
2473 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
2474 * tree, used by Leaflet internally.
2476 * Most functions expecting or returning a `HTMLElement` also work for
2477 * SVG elements. The only difference is that classes refer to CSS classes
2478 * in HTML and SVG classes in SVG.
2482 // @property TRANSFORM: String
2483 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
2484 var TRANSFORM = testProp(
2485 ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
2487 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
2488 // the same for the transitionend event, in particular the Android 4.1 stock browser
2490 // @property TRANSITION: String
2491 // Vendor-prefixed transition style name.
2492 var TRANSITION = testProp(
2493 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
2495 // @property TRANSITION_END: String
2496 // Vendor-prefixed transitionend event name.
2497 var TRANSITION_END =
2498 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
2501 // @function get(id: String|HTMLElement): HTMLElement
2502 // Returns an element given its DOM id, or returns the element itself
2503 // if it was passed directly.
2505 return typeof id === 'string' ? document.getElementById(id) : id;
2508 // @function getStyle(el: HTMLElement, styleAttrib: String): String
2509 // Returns the value for a certain style attribute on an element,
2510 // including computed values or values set through CSS.
2511 function getStyle(el, style) {
2512 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
2514 if ((!value || value === 'auto') && document.defaultView) {
2515 var css = document.defaultView.getComputedStyle(el, null);
2516 value = css ? css[style] : null;
2518 return value === 'auto' ? null : value;
2521 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
2522 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
2523 function create$1(tagName, className, container) {
2524 var el = document.createElement(tagName);
2525 el.className = className || '';
2528 container.appendChild(el);
2533 // @function remove(el: HTMLElement)
2534 // Removes `el` from its parent element
2535 function remove(el) {
2536 var parent = el.parentNode;
2538 parent.removeChild(el);
2542 // @function empty(el: HTMLElement)
2543 // Removes all of `el`'s children elements from `el`
2544 function empty(el) {
2545 while (el.firstChild) {
2546 el.removeChild(el.firstChild);
2550 // @function toFront(el: HTMLElement)
2551 // Makes `el` the last child of its parent, so it renders in front of the other children.
2552 function toFront(el) {
2553 var parent = el.parentNode;
2554 if (parent.lastChild !== el) {
2555 parent.appendChild(el);
2559 // @function toBack(el: HTMLElement)
2560 // Makes `el` the first child of its parent, so it renders behind the other children.
2561 function toBack(el) {
2562 var parent = el.parentNode;
2563 if (parent.firstChild !== el) {
2564 parent.insertBefore(el, parent.firstChild);
2568 // @function hasClass(el: HTMLElement, name: String): Boolean
2569 // Returns `true` if the element's class attribute contains `name`.
2570 function hasClass(el, name) {
2571 if (el.classList !== undefined) {
2572 return el.classList.contains(name);
2574 var className = getClass(el);
2575 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
2578 // @function addClass(el: HTMLElement, name: String)
2579 // Adds `name` to the element's class attribute.
2580 function addClass(el, name) {
2581 if (el.classList !== undefined) {
2582 var classes = splitWords(name);
2583 for (var i = 0, len = classes.length; i < len; i++) {
2584 el.classList.add(classes[i]);
2586 } else if (!hasClass(el, name)) {
2587 var className = getClass(el);
2588 setClass(el, (className ? className + ' ' : '') + name);
2592 // @function removeClass(el: HTMLElement, name: String)
2593 // Removes `name` from the element's class attribute.
2594 function removeClass(el, name) {
2595 if (el.classList !== undefined) {
2596 el.classList.remove(name);
2598 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
2602 // @function setClass(el: HTMLElement, name: String)
2603 // Sets the element's class.
2604 function setClass(el, name) {
2605 if (el.className.baseVal === undefined) {
2606 el.className = name;
2608 // in case of SVG element
2609 el.className.baseVal = name;
2613 // @function getClass(el: HTMLElement): String
2614 // Returns the element's class.
2615 function getClass(el) {
2616 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
2619 // @function setOpacity(el: HTMLElement, opacity: Number)
2620 // Set the opacity of an element (including old IE support).
2621 // `opacity` must be a number from `0` to `1`.
2622 function setOpacity(el, value) {
2623 if ('opacity' in el.style) {
2624 el.style.opacity = value;
2625 } else if ('filter' in el.style) {
2626 _setOpacityIE(el, value);
2630 function _setOpacityIE(el, value) {
2632 filterName = 'DXImageTransform.Microsoft.Alpha';
2634 // filters collection throws an error if we try to retrieve a filter that doesn't exist
2636 filter = el.filters.item(filterName);
2638 // don't set opacity to 1 if we haven't already set an opacity,
2639 // it isn't needed and breaks transparent pngs.
2640 if (value === 1) { return; }
2643 value = Math.round(value * 100);
2646 filter.Enabled = (value !== 100);
2647 filter.Opacity = value;
2649 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
2653 // @function testProp(props: String[]): String|false
2654 // Goes through the array of style names and returns the first name
2655 // that is a valid style name for an element. If no such name is found,
2656 // it returns false. Useful for vendor-prefixed styles like `transform`.
2657 function testProp(props) {
2658 var style = document.documentElement.style;
2660 for (var i = 0; i < props.length; i++) {
2661 if (props[i] in style) {
2668 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
2669 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
2670 // and optionally scaled by `scale`. Does not have an effect if the
2671 // browser doesn't support 3D CSS transforms.
2672 function setTransform(el, offset, scale) {
2673 var pos = offset || new Point(0, 0);
2675 el.style[TRANSFORM] =
2677 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
2678 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
2679 (scale ? ' scale(' + scale + ')' : '');
2682 // @function setPosition(el: HTMLElement, position: Point)
2683 // Sets the position of `el` to coordinates specified by `position`,
2684 // using CSS translate or top/left positioning depending on the browser
2685 // (used by Leaflet internally to position its layers).
2686 function setPosition(el, point) {
2689 el._leaflet_pos = point;
2693 setTransform(el, point);
2695 el.style.left = point.x + 'px';
2696 el.style.top = point.y + 'px';
2700 // @function getPosition(el: HTMLElement): Point
2701 // Returns the coordinates of an element previously positioned with setPosition.
2702 function getPosition(el) {
2703 // this method is only used for elements previously positioned using setPosition,
2704 // so it's safe to cache the position for performance
2706 return el._leaflet_pos || new Point(0, 0);
2709 // @function disableTextSelection()
2710 // Prevents the user from generating `selectstart` DOM events, usually generated
2711 // when the user drags the mouse through a page with text. Used internally
2712 // by Leaflet to override the behaviour of any click-and-drag interaction on
2713 // the map. Affects drag interactions on the whole document.
2715 // @function enableTextSelection()
2716 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
2717 var disableTextSelection;
2718 var enableTextSelection;
2720 if ('onselectstart' in document) {
2721 disableTextSelection = function () {
2722 on(window, 'selectstart', preventDefault);
2724 enableTextSelection = function () {
2725 off(window, 'selectstart', preventDefault);
2728 var userSelectProperty = testProp(
2729 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
2731 disableTextSelection = function () {
2732 if (userSelectProperty) {
2733 var style = document.documentElement.style;
2734 _userSelect = style[userSelectProperty];
2735 style[userSelectProperty] = 'none';
2738 enableTextSelection = function () {
2739 if (userSelectProperty) {
2740 document.documentElement.style[userSelectProperty] = _userSelect;
2741 _userSelect = undefined;
2746 // @function disableImageDrag()
2747 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
2748 // for `dragstart` DOM events, usually generated when the user drags an image.
2749 function disableImageDrag() {
2750 on(window, 'dragstart', preventDefault);
2753 // @function enableImageDrag()
2754 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
2755 function enableImageDrag() {
2756 off(window, 'dragstart', preventDefault);
2759 var _outlineElement;
2761 // @function preventOutline(el: HTMLElement)
2762 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
2763 // of the element `el` invisible. Used internally by Leaflet to prevent
2764 // focusable elements from displaying an outline when the user performs a
2765 // drag interaction on them.
2766 function preventOutline(element) {
2767 while (element.tabIndex === -1) {
2768 element = element.parentNode;
2770 if (!element.style) { return; }
2772 _outlineElement = element;
2773 _outlineStyle = element.style.outline;
2774 element.style.outline = 'none';
2775 on(window, 'keydown', restoreOutline);
2778 // @function restoreOutline()
2779 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
2780 function restoreOutline() {
2781 if (!_outlineElement) { return; }
2782 _outlineElement.style.outline = _outlineStyle;
2783 _outlineElement = undefined;
2784 _outlineStyle = undefined;
2785 off(window, 'keydown', restoreOutline);
2789 var DomUtil = (Object.freeze || Object)({
2790 TRANSFORM: TRANSFORM,
2791 TRANSITION: TRANSITION,
2792 TRANSITION_END: TRANSITION_END,
2802 removeClass: removeClass,
2805 setOpacity: setOpacity,
2807 setTransform: setTransform,
2808 setPosition: setPosition,
2809 getPosition: getPosition,
2810 disableTextSelection: disableTextSelection,
2811 enableTextSelection: enableTextSelection,
2812 disableImageDrag: disableImageDrag,
2813 enableImageDrag: enableImageDrag,
2814 preventOutline: preventOutline,
2815 restoreOutline: restoreOutline
2819 * @class PosAnimation
2820 * @aka L.PosAnimation
2822 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
2826 * var fx = new L.PosAnimation();
2827 * fx.run(el, [300, 500], 0.5);
2830 * @constructor L.PosAnimation()
2831 * Creates a `PosAnimation` object.
2835 var PosAnimation = Evented.extend({
2837 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
2838 // Run an animation of a given element to a new position, optionally setting
2839 // duration in seconds (`0.25` by default) and easing linearity factor (3rd
2840 // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
2841 // `0.5` by default).
2842 run: function (el, newPos, duration, easeLinearity) {
2846 this._inProgress = true;
2847 this._duration = duration || 0.25;
2848 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
2850 this._startPos = getPosition(el);
2851 this._offset = newPos.subtract(this._startPos);
2852 this._startTime = +new Date();
2854 // @event start: Event
2855 // Fired when the animation starts
2862 // Stops the animation (if currently running).
2864 if (!this._inProgress) { return; }
2870 _animate: function () {
2872 this._animId = requestAnimFrame(this._animate, this);
2876 _step: function (round) {
2877 var elapsed = (+new Date()) - this._startTime,
2878 duration = this._duration * 1000;
2880 if (elapsed < duration) {
2881 this._runFrame(this._easeOut(elapsed / duration), round);
2888 _runFrame: function (progress, round) {
2889 var pos = this._startPos.add(this._offset.multiplyBy(progress));
2893 setPosition(this._el, pos);
2895 // @event step: Event
2896 // Fired continuously during the animation.
2900 _complete: function () {
2901 cancelAnimFrame(this._animId);
2903 this._inProgress = false;
2904 // @event end: Event
2905 // Fired when the animation ends.
2909 _easeOut: function (t) {
2910 return 1 - Math.pow(1 - t, this._easeOutPower);
2919 * The central class of the API — it is used to create a map on a page and manipulate it.
2924 * // initialize the map on the "map" div with a given center and zoom
2925 * var map = L.map('map', {
2926 * center: [51.505, -0.09],
2933 var Map = Evented.extend({
2936 // @section Map State Options
2937 // @option crs: CRS = L.CRS.EPSG3857
2938 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
2939 // sure what it means.
2942 // @option center: LatLng = undefined
2943 // Initial geographic center of the map
2946 // @option zoom: Number = undefined
2947 // Initial map zoom level
2950 // @option minZoom: Number = *
2951 // Minimum zoom level of the map.
2952 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
2953 // the lowest of their `minZoom` options will be used instead.
2956 // @option maxZoom: Number = *
2957 // Maximum zoom level of the map.
2958 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
2959 // the highest of their `maxZoom` options will be used instead.
2962 // @option layers: Layer[] = []
2963 // Array of layers that will be added to the map initially
2966 // @option maxBounds: LatLngBounds = null
2967 // When this option is set, the map restricts the view to the given
2968 // geographical bounds, bouncing the user back if the user tries to pan
2969 // outside the view. To set the restriction dynamically, use
2970 // [`setMaxBounds`](#map-setmaxbounds) method.
2971 maxBounds: undefined,
2973 // @option renderer: Renderer = *
2974 // The default method for drawing vector layers on the map. `L.SVG`
2975 // or `L.Canvas` by default depending on browser support.
2976 renderer: undefined,
2979 // @section Animation Options
2980 // @option zoomAnimation: Boolean = true
2981 // Whether the map zoom animation is enabled. By default it's enabled
2982 // in all browsers that support CSS3 Transitions except Android.
2983 zoomAnimation: true,
2985 // @option zoomAnimationThreshold: Number = 4
2986 // Won't animate zoom if the zoom difference exceeds this value.
2987 zoomAnimationThreshold: 4,
2989 // @option fadeAnimation: Boolean = true
2990 // Whether the tile fade animation is enabled. By default it's enabled
2991 // in all browsers that support CSS3 Transitions except Android.
2992 fadeAnimation: true,
2994 // @option markerZoomAnimation: Boolean = true
2995 // Whether markers animate their zoom with the zoom animation, if disabled
2996 // they will disappear for the length of the animation. By default it's
2997 // enabled in all browsers that support CSS3 Transitions except Android.
2998 markerZoomAnimation: true,
3000 // @option transform3DLimit: Number = 2^23
3001 // Defines the maximum size of a CSS translation transform. The default
3002 // value should not be changed unless a web browser positions layers in
3003 // the wrong place after doing a large `panBy`.
3004 transform3DLimit: 8388608, // Precision limit of a 32-bit float
3006 // @section Interaction Options
3007 // @option zoomSnap: Number = 1
3008 // Forces the map's zoom level to always be a multiple of this, particularly
3009 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
3010 // By default, the zoom level snaps to the nearest integer; lower values
3011 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
3012 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
3015 // @option zoomDelta: Number = 1
3016 // Controls how much the map's zoom level will change after a
3017 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
3018 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
3019 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
3022 // @option trackResize: Boolean = true
3023 // Whether the map automatically handles browser window resize to update itself.
3027 initialize: function (id, options) { // (HTMLElement or String, Object)
3028 options = setOptions(this, options);
3030 this._initContainer(id);
3033 // hack for https://github.com/Leaflet/Leaflet/issues/1980
3034 this._onResize = bind(this._onResize, this);
3038 if (options.maxBounds) {
3039 this.setMaxBounds(options.maxBounds);
3042 if (options.zoom !== undefined) {
3043 this._zoom = this._limitZoom(options.zoom);
3046 if (options.center && options.zoom !== undefined) {
3047 this.setView(toLatLng(options.center), options.zoom, {reset: true});
3050 this._handlers = [];
3052 this._zoomBoundLayers = {};
3053 this._sizeChanged = true;
3055 this.callInitHooks();
3057 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
3058 this._zoomAnimated = TRANSITION && any3d && !mobileOpera &&
3059 this.options.zoomAnimation;
3061 // zoom transitions run with the same duration for all layers, so if one of transitionend events
3062 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
3063 if (this._zoomAnimated) {
3064 this._createAnimProxy();
3065 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
3068 this._addLayers(this.options.layers);
3072 // @section Methods for modifying map state
3074 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
3075 // Sets the view of the map (geographical center and zoom) with the given
3076 // animation options.
3077 setView: function (center, zoom, options) {
3079 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
3080 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
3081 options = options || {};
3085 if (this._loaded && !options.reset && options !== true) {
3087 if (options.animate !== undefined) {
3088 options.zoom = extend({animate: options.animate}, options.zoom);
3089 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
3092 // try animating pan or zoom
3093 var moved = (this._zoom !== zoom) ?
3094 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
3095 this._tryAnimatedPan(center, options.pan);
3098 // prevent resize handler call, the view will refresh after animation anyway
3099 clearTimeout(this._sizeTimer);
3104 // animation didn't start, just reset the map view
3105 this._resetView(center, zoom);
3110 // @method setZoom(zoom: Number, options?: Zoom/pan options): this
3111 // Sets the zoom of the map.
3112 setZoom: function (zoom, options) {
3113 if (!this._loaded) {
3117 return this.setView(this.getCenter(), zoom, {zoom: options});
3120 // @method zoomIn(delta?: Number, options?: Zoom options): this
3121 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3122 zoomIn: function (delta, options) {
3123 delta = delta || (any3d ? this.options.zoomDelta : 1);
3124 return this.setZoom(this._zoom + delta, options);
3127 // @method zoomOut(delta?: Number, options?: Zoom options): this
3128 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3129 zoomOut: function (delta, options) {
3130 delta = delta || (any3d ? this.options.zoomDelta : 1);
3131 return this.setZoom(this._zoom - delta, options);
3134 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
3135 // Zooms the map while keeping a specified geographical point on the map
3136 // stationary (e.g. used internally for scroll zoom and double-click zoom).
3138 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
3139 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
3140 setZoomAround: function (latlng, zoom, options) {
3141 var scale = this.getZoomScale(zoom),
3142 viewHalf = this.getSize().divideBy(2),
3143 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
3145 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
3146 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
3148 return this.setView(newCenter, zoom, {zoom: options});
3151 _getBoundsCenterZoom: function (bounds, options) {
3153 options = options || {};
3154 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
3156 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3157 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3159 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
3161 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
3163 if (zoom === Infinity) {
3165 center: bounds.getCenter(),
3170 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
3172 swPoint = this.project(bounds.getSouthWest(), zoom),
3173 nePoint = this.project(bounds.getNorthEast(), zoom),
3174 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
3182 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
3183 // Sets a map view that contains the given geographical bounds with the
3184 // maximum zoom level possible.
3185 fitBounds: function (bounds, options) {
3187 bounds = toLatLngBounds(bounds);
3189 if (!bounds.isValid()) {
3190 throw new Error('Bounds are not valid.');
3193 var target = this._getBoundsCenterZoom(bounds, options);
3194 return this.setView(target.center, target.zoom, options);
3197 // @method fitWorld(options?: fitBounds options): this
3198 // Sets a map view that mostly contains the whole world with the maximum
3199 // zoom level possible.
3200 fitWorld: function (options) {
3201 return this.fitBounds([[-90, -180], [90, 180]], options);
3204 // @method panTo(latlng: LatLng, options?: Pan options): this
3205 // Pans the map to a given center.
3206 panTo: function (center, options) { // (LatLng)
3207 return this.setView(center, this._zoom, {pan: options});
3210 // @method panBy(offset: Point, options?: Pan options): this
3211 // Pans the map by a given number of pixels (animated).
3212 panBy: function (offset, options) {
3213 offset = toPoint(offset).round();
3214 options = options || {};
3216 if (!offset.x && !offset.y) {
3217 return this.fire('moveend');
3219 // If we pan too far, Chrome gets issues with tiles
3220 // and makes them disappear or appear in the wrong place (slightly offset) #2602
3221 if (options.animate !== true && !this.getSize().contains(offset)) {
3222 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
3226 if (!this._panAnim) {
3227 this._panAnim = new PosAnimation();
3230 'step': this._onPanTransitionStep,
3231 'end': this._onPanTransitionEnd
3235 // don't fire movestart if animating inertia
3236 if (!options.noMoveStart) {
3237 this.fire('movestart');
3240 // animate pan unless animate: false specified
3241 if (options.animate !== false) {
3242 addClass(this._mapPane, 'leaflet-pan-anim');
3244 var newPos = this._getMapPanePos().subtract(offset).round();
3245 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
3247 this._rawPanBy(offset);
3248 this.fire('move').fire('moveend');
3254 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
3255 // Sets the view of the map (geographical center and zoom) performing a smooth
3256 // pan-zoom animation.
3257 flyTo: function (targetCenter, targetZoom, options) {
3259 options = options || {};
3260 if (options.animate === false || !any3d) {
3261 return this.setView(targetCenter, targetZoom, options);
3266 var from = this.project(this.getCenter()),
3267 to = this.project(targetCenter),
3268 size = this.getSize(),
3269 startZoom = this._zoom;
3271 targetCenter = toLatLng(targetCenter);
3272 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
3274 var w0 = Math.max(size.x, size.y),
3275 w1 = w0 * this.getZoomScale(startZoom, targetZoom),
3276 u1 = (to.distanceTo(from)) || 1,
3281 var s1 = i ? -1 : 1,
3283 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
3284 b1 = 2 * s2 * rho2 * u1,
3286 sq = Math.sqrt(b * b + 1) - b;
3288 // workaround for floating point precision bug when sq = 0, log = -Infinite,
3289 // thus triggering an infinite loop in flyTo
3290 var log = sq < 0.000000001 ? -18 : Math.log(sq);
3295 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
3296 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
3297 function tanh(n) { return sinh(n) / cosh(n); }
3301 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
3302 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
3304 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
3306 var start = Date.now(),
3307 S = (r(1) - r0) / rho,
3308 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
3311 var t = (Date.now() - start) / duration,
3315 this._flyToFrame = requestAnimFrame(frame, this);
3318 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
3319 this.getScaleZoom(w0 / w(s), startZoom),
3324 ._move(targetCenter, targetZoom)
3329 this._moveStart(true);
3335 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
3336 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
3337 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
3338 flyToBounds: function (bounds, options) {
3339 var target = this._getBoundsCenterZoom(bounds, options);
3340 return this.flyTo(target.center, target.zoom, options);
3343 // @method setMaxBounds(bounds: Bounds): this
3344 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
3345 setMaxBounds: function (bounds) {
3346 bounds = toLatLngBounds(bounds);
3348 if (!bounds.isValid()) {
3349 this.options.maxBounds = null;
3350 return this.off('moveend', this._panInsideMaxBounds);
3351 } else if (this.options.maxBounds) {
3352 this.off('moveend', this._panInsideMaxBounds);
3355 this.options.maxBounds = bounds;
3358 this._panInsideMaxBounds();
3361 return this.on('moveend', this._panInsideMaxBounds);
3364 // @method setMinZoom(zoom: Number): this
3365 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
3366 setMinZoom: function (zoom) {
3367 this.options.minZoom = zoom;
3369 if (this._loaded && this.getZoom() < this.options.minZoom) {
3370 return this.setZoom(zoom);
3376 // @method setMaxZoom(zoom: Number): this
3377 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
3378 setMaxZoom: function (zoom) {
3379 this.options.maxZoom = zoom;
3381 if (this._loaded && (this.getZoom() > this.options.maxZoom)) {
3382 return this.setZoom(zoom);
3388 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
3389 // 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.
3390 panInsideBounds: function (bounds, options) {
3391 this._enforcingBounds = true;
3392 var center = this.getCenter(),
3393 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
3395 if (!center.equals(newCenter)) {
3396 this.panTo(newCenter, options);
3399 this._enforcingBounds = false;
3403 // @method invalidateSize(options: Zoom/Pan options): this
3404 // Checks if the map container size changed and updates the map if so —
3405 // call it after you've changed the map size dynamically, also animating
3406 // pan by default. If `options.pan` is `false`, panning will not occur.
3407 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
3408 // that it doesn't happen often even if the method is called many
3412 // @method invalidateSize(animate: Boolean): this
3413 // Checks if the map container size changed and updates the map if so —
3414 // call it after you've changed the map size dynamically, also animating
3416 invalidateSize: function (options) {
3417 if (!this._loaded) { return this; }
3422 }, options === true ? {animate: true} : options);
3424 var oldSize = this.getSize();
3425 this._sizeChanged = true;
3426 this._lastCenter = null;
3428 var newSize = this.getSize(),
3429 oldCenter = oldSize.divideBy(2).round(),
3430 newCenter = newSize.divideBy(2).round(),
3431 offset = oldCenter.subtract(newCenter);
3433 if (!offset.x && !offset.y) { return this; }
3435 if (options.animate && options.pan) {
3440 this._rawPanBy(offset);
3445 if (options.debounceMoveend) {
3446 clearTimeout(this._sizeTimer);
3447 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
3449 this.fire('moveend');
3453 // @section Map state change events
3454 // @event resize: ResizeEvent
3455 // Fired when the map is resized.
3456 return this.fire('resize', {
3462 // @section Methods for modifying map state
3463 // @method stop(): this
3464 // Stops the currently running `panTo` or `flyTo` animation, if any.
3466 this.setZoom(this._limitZoom(this._zoom));
3467 if (!this.options.zoomSnap) {
3468 this.fire('viewreset');
3470 return this._stop();
3473 // @section Geolocation methods
3474 // @method locate(options?: Locate options): this
3475 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
3476 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
3477 // and optionally sets the map view to the user's location with respect to
3478 // detection accuracy (or to the world view if geolocation failed).
3479 // Note that, if your page doesn't use HTTPS, this method will fail in
3480 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
3481 // See `Locate options` for more details.
3482 locate: function (options) {
3484 options = this._locateOptions = extend({
3488 // maxZoom: <Number>
3490 // enableHighAccuracy: false
3493 if (!('geolocation' in navigator)) {
3494 this._handleGeolocationError({
3496 message: 'Geolocation not supported.'
3501 var onResponse = bind(this._handleGeolocationResponse, this),
3502 onError = bind(this._handleGeolocationError, this);
3504 if (options.watch) {
3505 this._locationWatchId =
3506 navigator.geolocation.watchPosition(onResponse, onError, options);
3508 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
3513 // @method stopLocate(): this
3514 // Stops watching location previously initiated by `map.locate({watch: true})`
3515 // and aborts resetting the map view if map.locate was called with
3516 // `{setView: true}`.
3517 stopLocate: function () {
3518 if (navigator.geolocation && navigator.geolocation.clearWatch) {
3519 navigator.geolocation.clearWatch(this._locationWatchId);
3521 if (this._locateOptions) {
3522 this._locateOptions.setView = false;
3527 _handleGeolocationError: function (error) {
3529 message = error.message ||
3530 (c === 1 ? 'permission denied' :
3531 (c === 2 ? 'position unavailable' : 'timeout'));
3533 if (this._locateOptions.setView && !this._loaded) {
3537 // @section Location events
3538 // @event locationerror: ErrorEvent
3539 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
3540 this.fire('locationerror', {
3542 message: 'Geolocation error: ' + message + '.'
3546 _handleGeolocationResponse: function (pos) {
3547 var lat = pos.coords.latitude,
3548 lng = pos.coords.longitude,
3549 latlng = new LatLng(lat, lng),
3550 bounds = latlng.toBounds(pos.coords.accuracy),
3551 options = this._locateOptions;
3553 if (options.setView) {
3554 var zoom = this.getBoundsZoom(bounds);
3555 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
3561 timestamp: pos.timestamp
3564 for (var i in pos.coords) {
3565 if (typeof pos.coords[i] === 'number') {
3566 data[i] = pos.coords[i];
3570 // @event locationfound: LocationEvent
3571 // Fired when geolocation (using the [`locate`](#map-locate) method)
3572 // went successfully.
3573 this.fire('locationfound', data);
3576 // TODO handler.addTo
3577 // TODO Appropiate docs section?
3578 // @section Other Methods
3579 // @method addHandler(name: String, HandlerClass: Function): this
3580 // Adds a new `Handler` to the map, given its name and constructor function.
3581 addHandler: function (name, HandlerClass) {
3582 if (!HandlerClass) { return this; }
3584 var handler = this[name] = new HandlerClass(this);
3586 this._handlers.push(handler);
3588 if (this.options[name]) {
3595 // @method remove(): this
3596 // Destroys the map and clears all related event listeners.
3597 remove: function () {
3599 this._initEvents(true);
3601 if (this._containerId !== this._container._leaflet_id) {
3602 throw new Error('Map container is being reused by another instance');
3606 // throws error in IE6-8
3607 delete this._container._leaflet_id;
3608 delete this._containerId;
3611 this._container._leaflet_id = undefined;
3613 this._containerId = undefined;
3616 remove(this._mapPane);
3618 if (this._clearControlPos) {
3619 this._clearControlPos();
3622 this._clearHandlers();
3625 // @section Map state change events
3626 // @event unload: Event
3627 // Fired when the map is destroyed with [remove](#map-remove) method.
3628 this.fire('unload');
3632 for (i in this._layers) {
3633 this._layers[i].remove();
3635 for (i in this._panes) {
3636 remove(this._panes[i]);
3641 delete this._mapPane;
3642 delete this._renderer;
3647 // @section Other Methods
3648 // @method createPane(name: String, container?: HTMLElement): HTMLElement
3649 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
3650 // then returns it. The pane is created as a child of `container`, or
3651 // as a child of the main map pane if not set.
3652 createPane: function (name, container) {
3653 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3654 pane = create$1('div', className, container || this._mapPane);
3657 this._panes[name] = pane;
3662 // @section Methods for Getting Map State
3664 // @method getCenter(): LatLng
3665 // Returns the geographical center of the map view
3666 getCenter: function () {
3667 this._checkIfLoaded();
3669 if (this._lastCenter && !this._moved()) {
3670 return this._lastCenter;
3672 return this.layerPointToLatLng(this._getCenterLayerPoint());
3675 // @method getZoom(): Number
3676 // Returns the current zoom level of the map view
3677 getZoom: function () {
3681 // @method getBounds(): LatLngBounds
3682 // Returns the geographical bounds visible in the current map view
3683 getBounds: function () {
3684 var bounds = this.getPixelBounds(),
3685 sw = this.unproject(bounds.getBottomLeft()),
3686 ne = this.unproject(bounds.getTopRight());
3688 return new LatLngBounds(sw, ne);
3691 // @method getMinZoom(): Number
3692 // 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.
3693 getMinZoom: function () {
3694 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3697 // @method getMaxZoom(): Number
3698 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3699 getMaxZoom: function () {
3700 return this.options.maxZoom === undefined ?
3701 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3702 this.options.maxZoom;
3705 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number
3706 // Returns the maximum zoom level on which the given bounds fit to the map
3707 // view in its entirety. If `inside` (optional) is set to `true`, the method
3708 // instead returns the minimum zoom level on which the map view fits into
3709 // the given bounds in its entirety.
3710 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3711 bounds = toLatLngBounds(bounds);
3712 padding = toPoint(padding || [0, 0]);
3714 var zoom = this.getZoom() || 0,
3715 min = this.getMinZoom(),
3716 max = this.getMaxZoom(),
3717 nw = bounds.getNorthWest(),
3718 se = bounds.getSouthEast(),
3719 size = this.getSize().subtract(padding),
3720 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3721 snap = any3d ? this.options.zoomSnap : 1,
3722 scalex = size.x / boundsSize.x,
3723 scaley = size.y / boundsSize.y,
3724 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
3726 zoom = this.getScaleZoom(scale, zoom);
3729 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3730 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3733 return Math.max(min, Math.min(max, zoom));
3736 // @method getSize(): Point
3737 // Returns the current size of the map container (in pixels).
3738 getSize: function () {
3739 if (!this._size || this._sizeChanged) {
3740 this._size = new Point(
3741 this._container.clientWidth || 0,
3742 this._container.clientHeight || 0);
3744 this._sizeChanged = false;
3746 return this._size.clone();
3749 // @method getPixelBounds(): Bounds
3750 // Returns the bounds of the current map view in projected pixel
3751 // coordinates (sometimes useful in layer and overlay implementations).
3752 getPixelBounds: function (center, zoom) {
3753 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3754 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3757 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3758 // the map pane? "left point of the map layer" can be confusing, specially
3759 // since there can be negative offsets.
3760 // @method getPixelOrigin(): Point
3761 // Returns the projected pixel coordinates of the top left point of
3762 // the map layer (useful in custom layer and overlay implementations).
3763 getPixelOrigin: function () {
3764 this._checkIfLoaded();
3765 return this._pixelOrigin;
3768 // @method getPixelWorldBounds(zoom?: Number): Bounds
3769 // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3770 // If `zoom` is omitted, the map's current zoom level is used.
3771 getPixelWorldBounds: function (zoom) {
3772 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3775 // @section Other Methods
3777 // @method getPane(pane: String|HTMLElement): HTMLElement
3778 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3779 getPane: function (pane) {
3780 return typeof pane === 'string' ? this._panes[pane] : pane;
3783 // @method getPanes(): Object
3784 // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3785 // the panes as values.
3786 getPanes: function () {
3790 // @method getContainer: HTMLElement
3791 // Returns the HTML element that contains the map.
3792 getContainer: function () {
3793 return this._container;
3797 // @section Conversion Methods
3799 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3800 // Returns the scale factor to be applied to a map transition from zoom level
3801 // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3802 getZoomScale: function (toZoom, fromZoom) {
3803 // TODO replace with universal implementation after refactoring projections
3804 var crs = this.options.crs;
3805 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3806 return crs.scale(toZoom) / crs.scale(fromZoom);
3809 // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3810 // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3811 // level and everything is scaled by a factor of `scale`. Inverse of
3812 // [`getZoomScale`](#map-getZoomScale).
3813 getScaleZoom: function (scale, fromZoom) {
3814 var crs = this.options.crs;
3815 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3816 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3817 return isNaN(zoom) ? Infinity : zoom;
3820 // @method project(latlng: LatLng, zoom: Number): Point
3821 // Projects a geographical coordinate `LatLng` according to the projection
3822 // of the map's CRS, then scales it according to `zoom` and the CRS's
3823 // `Transformation`. The result is pixel coordinate relative to
3825 project: function (latlng, zoom) {
3826 zoom = zoom === undefined ? this._zoom : zoom;
3827 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
3830 // @method unproject(point: Point, zoom: Number): LatLng
3831 // Inverse of [`project`](#map-project).
3832 unproject: function (point, zoom) {
3833 zoom = zoom === undefined ? this._zoom : zoom;
3834 return this.options.crs.pointToLatLng(toPoint(point), zoom);
3837 // @method layerPointToLatLng(point: Point): LatLng
3838 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3839 // returns the corresponding geographical coordinate (for the current zoom level).
3840 layerPointToLatLng: function (point) {
3841 var projectedPoint = toPoint(point).add(this.getPixelOrigin());
3842 return this.unproject(projectedPoint);
3845 // @method latLngToLayerPoint(latlng: LatLng): Point
3846 // Given a geographical coordinate, returns the corresponding pixel coordinate
3847 // relative to the [origin pixel](#map-getpixelorigin).
3848 latLngToLayerPoint: function (latlng) {
3849 var projectedPoint = this.project(toLatLng(latlng))._round();
3850 return projectedPoint._subtract(this.getPixelOrigin());
3853 // @method wrapLatLng(latlng: LatLng): LatLng
3854 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
3855 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
3857 // By default this means longitude is wrapped around the dateline so its
3858 // value is between -180 and +180 degrees.
3859 wrapLatLng: function (latlng) {
3860 return this.options.crs.wrapLatLng(toLatLng(latlng));
3863 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
3864 // Returns a `LatLngBounds` with the same size as the given one, ensuring that
3865 // its center is within the CRS's bounds.
3866 // By default this means the center longitude is wrapped around the dateline so its
3867 // value is between -180 and +180 degrees, and the majority of the bounds
3868 // overlaps the CRS's bounds.
3869 wrapLatLngBounds: function (latlng) {
3870 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
3873 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
3874 // Returns the distance between two geographical coordinates according to
3875 // the map's CRS. By default this measures distance in meters.
3876 distance: function (latlng1, latlng2) {
3877 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
3880 // @method containerPointToLayerPoint(point: Point): Point
3881 // Given a pixel coordinate relative to the map container, returns the corresponding
3882 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
3883 containerPointToLayerPoint: function (point) { // (Point)
3884 return toPoint(point).subtract(this._getMapPanePos());
3887 // @method layerPointToContainerPoint(point: Point): Point
3888 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3889 // returns the corresponding pixel coordinate relative to the map container.
3890 layerPointToContainerPoint: function (point) { // (Point)
3891 return toPoint(point).add(this._getMapPanePos());
3894 // @method containerPointToLatLng(point: Point): LatLng
3895 // Given a pixel coordinate relative to the map container, returns
3896 // the corresponding geographical coordinate (for the current zoom level).
3897 containerPointToLatLng: function (point) {
3898 var layerPoint = this.containerPointToLayerPoint(toPoint(point));
3899 return this.layerPointToLatLng(layerPoint);
3902 // @method latLngToContainerPoint(latlng: LatLng): Point
3903 // Given a geographical coordinate, returns the corresponding pixel coordinate
3904 // relative to the map container.
3905 latLngToContainerPoint: function (latlng) {
3906 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
3909 // @method mouseEventToContainerPoint(ev: MouseEvent): Point
3910 // Given a MouseEvent object, returns the pixel coordinate relative to the
3911 // map container where the event took place.
3912 mouseEventToContainerPoint: function (e) {
3913 return getMousePosition(e, this._container);
3916 // @method mouseEventToLayerPoint(ev: MouseEvent): Point
3917 // Given a MouseEvent object, returns the pixel coordinate relative to
3918 // the [origin pixel](#map-getpixelorigin) where the event took place.
3919 mouseEventToLayerPoint: function (e) {
3920 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
3923 // @method mouseEventToLatLng(ev: MouseEvent): LatLng
3924 // Given a MouseEvent object, returns geographical coordinate where the
3925 // event took place.
3926 mouseEventToLatLng: function (e) { // (MouseEvent)
3927 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
3931 // map initialization methods
3933 _initContainer: function (id) {
3934 var container = this._container = get(id);
3937 throw new Error('Map container not found.');
3938 } else if (container._leaflet_id) {
3939 throw new Error('Map container is already initialized.');
3942 on(container, 'scroll', this._onScroll, this);
3943 this._containerId = stamp(container);
3946 _initLayout: function () {
3947 var container = this._container;
3949 this._fadeAnimated = this.options.fadeAnimation && any3d;
3951 addClass(container, 'leaflet-container' +
3952 (touch ? ' leaflet-touch' : '') +
3953 (retina ? ' leaflet-retina' : '') +
3954 (ielt9 ? ' leaflet-oldie' : '') +
3955 (safari ? ' leaflet-safari' : '') +
3956 (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
3958 var position = getStyle(container, 'position');
3960 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
3961 container.style.position = 'relative';