2 Leaflet 1.0.3, a JS library for interactive maps. http://leafletjs.com
3 (c) 2010-2016 Vladimir Agafonkin, (c) 2010-2011 CloudMade
5 (function (window, document, undefined) {
13 L.noConflict = function () {
21 // define Leaflet for Node module pattern loaders, including Browserify
22 if (typeof module === 'object' && typeof module.exports === 'object') {
25 // define Leaflet as an AMD module
26 } else if (typeof define === 'function' && define.amd) {
30 // define Leaflet as a global L variable, saving the original L to restore later if needed
31 if (typeof window !== 'undefined') {
40 * Various utility functions, used by Leaflet internally.
45 // @function extend(dest: Object, src?: Object): Object
46 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
47 extend: function (dest) {
50 for (j = 1, len = arguments.length; j < len; j++) {
59 // @function create(proto: Object, properties?: Object): Object
60 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
61 create: Object.create || (function () {
63 return function (proto) {
69 // @function bind(fn: Function, …): Function
70 // 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).
71 // Has a `L.bind()` shortcut.
72 bind: function (fn, obj) {
73 var slice = Array.prototype.slice;
76 return fn.bind.apply(fn, slice.call(arguments, 1));
79 var args = slice.call(arguments, 2);
82 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
86 // @function stamp(obj: Object): Number
87 // Returns the unique ID of an object, assiging it one if it doesn't have it.
88 stamp: function (obj) {
90 obj._leaflet_id = obj._leaflet_id || ++L.Util.lastId;
91 return obj._leaflet_id;
95 // @property lastId: Number
96 // Last unique ID used by [`stamp()`](#util-stamp)
99 // @function throttle(fn: Function, time: Number, context: Object): Function
100 // Returns a function which executes function `fn` with the given scope `context`
101 // (so that the `this` keyword refers to `context` inside `fn`'s code). The function
102 // `fn` will be called no more than one time per given amount of `time`. The arguments
103 // received by the bound function will be any arguments passed when binding the
104 // function, followed by any arguments passed when invoking the bound function.
105 // Has an `L.bind` shortcut.
106 throttle: function (fn, time, context) {
107 var lock, args, wrapperFn, later;
109 later = function () {
110 // reset lock and call if queued
113 wrapperFn.apply(context, args);
118 wrapperFn = function () {
120 // called too soon, queue to call later
124 // call and lock until later
125 fn.apply(context, arguments);
126 setTimeout(later, time);
134 // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
135 // Returns the number `num` modulo `range` in such a way so it lies within
136 // `range[0]` and `range[1]`. The returned value will be always smaller than
137 // `range[1]` unless `includeMax` is set to `true`.
138 wrapNum: function (x, range, includeMax) {
142 return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
145 // @function falseFn(): Function
146 // Returns a function which always returns `false`.
147 falseFn: function () { return false; },
149 // @function formatNum(num: Number, digits?: Number): Number
150 // Returns the number `num` rounded to `digits` decimals, or to 5 decimals by default.
151 formatNum: function (num, digits) {
152 var pow = Math.pow(10, digits || 5);
153 return Math.round(num * pow) / pow;
156 // @function trim(str: String): String
157 // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
158 trim: function (str) {
159 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
162 // @function splitWords(str: String): String[]
163 // Trims and splits the string on whitespace and returns the array of parts.
164 splitWords: function (str) {
165 return L.Util.trim(str).split(/\s+/);
168 // @function setOptions(obj: Object, options: Object): Object
169 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
170 setOptions: function (obj, options) {
171 if (!obj.hasOwnProperty('options')) {
172 obj.options = obj.options ? L.Util.create(obj.options) : {};
174 for (var i in options) {
175 obj.options[i] = options[i];
180 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
181 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
182 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
183 // be appended at the end. If `uppercase` is `true`, the parameter names will
184 // be uppercased (e.g. `'?A=foo&B=bar'`)
185 getParamString: function (obj, existingUrl, uppercase) {
188 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
190 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
193 // @function template(str: String, data: Object): String
194 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
195 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
196 // `('Hello foo, bar')`. You can also specify functions instead of strings for
197 // data values — they will be evaluated passing `data` as an argument.
198 template: function (str, data) {
199 return str.replace(L.Util.templateRe, function (str, key) {
200 var value = data[key];
202 if (value === undefined) {
203 throw new Error('No value provided for variable ' + str);
205 } else if (typeof value === 'function') {
212 templateRe: /\{ *([\w_\-]+) *\}/g,
214 // @function isArray(obj): Boolean
215 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
216 isArray: Array.isArray || function (obj) {
217 return (Object.prototype.toString.call(obj) === '[object Array]');
220 // @function indexOf(array: Array, el: Object): Number
221 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
222 indexOf: function (array, el) {
223 for (var i = 0; i < array.length; i++) {
224 if (array[i] === el) { return i; }
229 // @property emptyImageUrl: String
230 // Data URI string containing a base64-encoded empty GIF image.
231 // Used as a hack to free memory from unused images on WebKit-powered
232 // mobile devices (by setting image `src` to this string).
233 emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
237 // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
239 function getPrefixed(name) {
240 return window['webkit' + name] || window['moz' + name] || window['ms' + name];
245 // fallback for IE 7-8
246 function timeoutDefer(fn) {
247 var time = +new Date(),
248 timeToCall = Math.max(0, 16 - (time - lastTime));
250 lastTime = time + timeToCall;
251 return window.setTimeout(fn, timeToCall);
254 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer,
255 cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
256 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
259 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
260 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
261 // `context` if given. When `immediate` is set, `fn` is called immediately if
262 // the browser doesn't have native support for
263 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
264 // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
265 L.Util.requestAnimFrame = function (fn, context, immediate) {
266 if (immediate && requestFn === timeoutDefer) {
269 return requestFn.call(window, L.bind(fn, context));
273 // @function cancelAnimFrame(id: Number): undefined
274 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
275 L.Util.cancelAnimFrame = function (id) {
277 cancelFn.call(window, id);
282 // shortcuts for most used utility functions
283 L.extend = L.Util.extend;
284 L.bind = L.Util.bind;
285 L.stamp = L.Util.stamp;
286 L.setOptions = L.Util.setOptions;
297 // Thanks to John Resig and Dean Edwards for inspiration!
299 L.Class = function () {};
301 L.Class.extend = function (props) {
303 // @function extend(props: Object): Function
304 // [Extends the current class](#class-inheritance) given the properties to be included.
305 // Returns a Javascript function that is a class constructor (to be called with `new`).
306 var NewClass = function () {
308 // call the constructor
309 if (this.initialize) {
310 this.initialize.apply(this, arguments);
313 // call all constructor hooks
314 this.callInitHooks();
317 var parentProto = NewClass.__super__ = this.prototype;
319 var proto = L.Util.create(parentProto);
320 proto.constructor = NewClass;
322 NewClass.prototype = proto;
324 // inherit parent's statics
325 for (var i in this) {
326 if (this.hasOwnProperty(i) && i !== 'prototype') {
327 NewClass[i] = this[i];
331 // mix static properties into the class
333 L.extend(NewClass, props.statics);
334 delete props.statics;
337 // mix includes into the prototype
338 if (props.includes) {
339 L.Util.extend.apply(null, [proto].concat(props.includes));
340 delete props.includes;
345 props.options = L.Util.extend(L.Util.create(proto.options), props.options);
348 // mix given properties into the prototype
349 L.extend(proto, props);
351 proto._initHooks = [];
353 // add method for calling all hooks
354 proto.callInitHooks = function () {
356 if (this._initHooksCalled) { return; }
358 if (parentProto.callInitHooks) {
359 parentProto.callInitHooks.call(this);
362 this._initHooksCalled = true;
364 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
365 proto._initHooks[i].call(this);
373 // @function include(properties: Object): this
374 // [Includes a mixin](#class-includes) into the current class.
375 L.Class.include = function (props) {
376 L.extend(this.prototype, props);
380 // @function mergeOptions(options: Object): this
381 // [Merges `options`](#class-options) into the defaults of the class.
382 L.Class.mergeOptions = function (options) {
383 L.extend(this.prototype.options, options);
387 // @function addInitHook(fn: Function): this
388 // Adds a [constructor hook](#class-constructor-hooks) to the class.
389 L.Class.addInitHook = function (fn) { // (Function) || (String, args...)
390 var args = Array.prototype.slice.call(arguments, 1);
392 var init = typeof fn === 'function' ? fn : function () {
393 this[fn].apply(this, args);
396 this.prototype._initHooks = this.prototype._initHooks || [];
397 this.prototype._initHooks.push(init);
408 * A set of methods shared between event-powered classes (like `Map` and `Marker`). Generally, events allow you to execute some function when something happens with an object (e.g. the user clicks on the map, causing the map to fire `'click'` event).
413 * map.on('click', function(e) {
418 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
421 * function onClick(e) { ... }
423 * map.on('click', onClick);
424 * map.off('click', onClick);
429 L.Evented = L.Class.extend({
431 /* @method on(type: String, fn: Function, context?: Object): this
432 * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`).
435 * @method on(eventMap: Object): this
436 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
438 on: function (types, fn, context) {
440 // types can be a map of types/handlers
441 if (typeof types === 'object') {
442 for (var type in types) {
443 // we don't process space-separated events here for performance;
444 // it's a hot path since Layer uses the on(obj) syntax
445 this._on(type, types[type], fn);
449 // types can be a string of space-separated words
450 types = L.Util.splitWords(types);
452 for (var i = 0, len = types.length; i < len; i++) {
453 this._on(types[i], fn, context);
460 /* @method off(type: String, fn?: Function, context?: Object): this
461 * Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener.
464 * @method off(eventMap: Object): this
465 * Removes a set of type/listener pairs.
469 * Removes all listeners to all events on the object.
471 off: function (types, fn, context) {
474 // clear all listeners if called without arguments
477 } else if (typeof types === 'object') {
478 for (var type in types) {
479 this._off(type, types[type], fn);
483 types = L.Util.splitWords(types);
485 for (var i = 0, len = types.length; i < len; i++) {
486 this._off(types[i], fn, context);
493 // attach listener (without syntactic sugar now)
494 _on: function (type, fn, context) {
495 this._events = this._events || {};
497 /* get/init listeners for type */
498 var typeListeners = this._events[type];
499 if (!typeListeners) {
501 this._events[type] = typeListeners;
504 if (context === this) {
505 // Less memory footprint.
508 var newListener = {fn: fn, ctx: context},
509 listeners = typeListeners;
511 // check if fn already there
512 for (var i = 0, len = listeners.length; i < len; i++) {
513 if (listeners[i].fn === fn && listeners[i].ctx === context) {
518 listeners.push(newListener);
521 _off: function (type, fn, context) {
526 if (!this._events) { return; }
528 listeners = this._events[type];
535 // Set all removed listeners to noop so they are not called if remove happens in fire
536 for (i = 0, len = listeners.length; i < len; i++) {
537 listeners[i].fn = L.Util.falseFn;
539 // clear all listeners for a type if function isn't specified
540 delete this._events[type];
544 if (context === this) {
550 // find fn and remove it
551 for (i = 0, len = listeners.length; i < len; i++) {
552 var l = listeners[i];
553 if (l.ctx !== context) { continue; }
556 // set the removed listener to noop so that's not called if remove happens in fire
557 l.fn = L.Util.falseFn;
559 if (this._firingCount) {
560 /* copy array in case events are being fired */
561 this._events[type] = listeners = listeners.slice();
563 listeners.splice(i, 1);
571 // @method fire(type: String, data?: Object, propagate?: Boolean): this
572 // Fires an event of the specified type. You can optionally provide an data
573 // object — the first argument of the listener function will contain its
574 // properties. The event can optionally be propagated to event parents.
575 fire: function (type, data, propagate) {
576 if (!this.listens(type, propagate)) { return this; }
578 var event = L.Util.extend({}, data, {type: type, target: this});
581 var listeners = this._events[type];
584 this._firingCount = (this._firingCount + 1) || 1;
585 for (var i = 0, len = listeners.length; i < len; i++) {
586 var l = listeners[i];
587 l.fn.call(l.ctx || this, event);
595 // propagate the event to parents (set with addEventParent)
596 this._propagateEvent(event);
602 // @method listens(type: String): Boolean
603 // Returns `true` if a particular event type has any listeners attached to it.
604 listens: function (type, propagate) {
605 var listeners = this._events && this._events[type];
606 if (listeners && listeners.length) { return true; }
609 // also check parents for listeners if event propagates
610 for (var id in this._eventParents) {
611 if (this._eventParents[id].listens(type, propagate)) { return true; }
617 // @method once(…): this
618 // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
619 once: function (types, fn, context) {
621 if (typeof types === 'object') {
622 for (var type in types) {
623 this.once(type, types[type], fn);
628 var handler = L.bind(function () {
630 .off(types, fn, context)
631 .off(types, handler, context);
634 // add a listener that's executed once and removed after that
636 .on(types, fn, context)
637 .on(types, handler, context);
640 // @method addEventParent(obj: Evented): this
641 // Adds an event parent - an `Evented` that will receive propagated events
642 addEventParent: function (obj) {
643 this._eventParents = this._eventParents || {};
644 this._eventParents[L.stamp(obj)] = obj;
648 // @method removeEventParent(obj: Evented): this
649 // Removes an event parent, so it will stop receiving propagated events
650 removeEventParent: function (obj) {
651 if (this._eventParents) {
652 delete this._eventParents[L.stamp(obj)];
657 _propagateEvent: function (e) {
658 for (var id in this._eventParents) {
659 this._eventParents[id].fire(e.type, L.extend({layer: e.target}, e), true);
664 var proto = L.Evented.prototype;
666 // aliases; we should ditch those eventually
668 // @method addEventListener(…): this
669 // Alias to [`on(…)`](#evented-on)
670 proto.addEventListener = proto.on;
672 // @method removeEventListener(…): this
673 // Alias to [`off(…)`](#evented-off)
675 // @method clearAllEventListeners(…): this
676 // Alias to [`off()`](#evented-off)
677 proto.removeEventListener = proto.clearAllEventListeners = proto.off;
679 // @method addOneTimeEventListener(…): this
680 // Alias to [`once(…)`](#evented-once)
681 proto.addOneTimeEventListener = proto.once;
683 // @method fireEvent(…): this
684 // Alias to [`fire(…)`](#evented-fire)
685 proto.fireEvent = proto.fire;
687 // @method hasEventListeners(…): Boolean
688 // Alias to [`listens(…)`](#evented-listens)
689 proto.hasEventListeners = proto.listens;
691 L.Mixin = {Events: proto};
699 * A namespace with static properties for browser/feature detection used by Leaflet internally.
704 * if (L.Browser.ielt9) {
705 * alert('Upgrade your browser, dude!');
712 var ua = navigator.userAgent.toLowerCase(),
713 doc = document.documentElement,
715 ie = 'ActiveXObject' in window,
717 webkit = ua.indexOf('webkit') !== -1,
718 phantomjs = ua.indexOf('phantom') !== -1,
719 android23 = ua.search('android [23]') !== -1,
720 chrome = ua.indexOf('chrome') !== -1,
721 gecko = ua.indexOf('gecko') !== -1 && !webkit && !window.opera && !ie,
723 win = navigator.platform.indexOf('Win') === 0,
725 mobile = typeof orientation !== 'undefined' || ua.indexOf('mobile') !== -1,
726 msPointer = !window.PointerEvent && window.MSPointerEvent,
727 pointer = window.PointerEvent || msPointer,
729 ie3d = ie && ('transition' in doc.style),
730 webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23,
731 gecko3d = 'MozPerspective' in doc.style,
732 opera12 = 'OTransition' in doc.style;
735 var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
736 (window.DocumentTouch && document instanceof window.DocumentTouch));
740 // @property ie: Boolean
741 // `true` for all Internet Explorer versions (not Edge).
744 // @property ielt9: Boolean
745 // `true` for Internet Explorer versions less than 9.
746 ielt9: ie && !document.addEventListener,
748 // @property edge: Boolean
749 // `true` for the Edge web browser.
750 edge: 'msLaunchUri' in navigator && !('documentMode' in document),
752 // @property webkit: Boolean
753 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
756 // @property gecko: Boolean
757 // `true` for gecko-based browsers like Firefox.
760 // @property android: Boolean
761 // `true` for any browser running on an Android platform.
762 android: ua.indexOf('android') !== -1,
764 // @property android23: Boolean
765 // `true` for browsers running on Android 2 or Android 3.
766 android23: android23,
768 // @property chrome: Boolean
769 // `true` for the Chrome browser.
772 // @property safari: Boolean
773 // `true` for the Safari browser.
774 safari: !chrome && ua.indexOf('safari') !== -1,
777 // @property win: Boolean
778 // `true` when the browser is running in a Windows platform
782 // @property ie3d: Boolean
783 // `true` for all Internet Explorer versions supporting CSS transforms.
786 // @property webkit3d: Boolean
787 // `true` for webkit-based browsers supporting CSS transforms.
790 // @property gecko3d: Boolean
791 // `true` for gecko-based browsers supporting CSS transforms.
794 // @property opera12: Boolean
795 // `true` for the Opera browser supporting CSS transforms (version 12 or later).
798 // @property any3d: Boolean
799 // `true` for all browsers supporting CSS transforms.
800 any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantomjs,
803 // @property mobile: Boolean
804 // `true` for all browsers running in a mobile device.
807 // @property mobileWebkit: Boolean
808 // `true` for all webkit-based browsers in a mobile device.
809 mobileWebkit: mobile && webkit,
811 // @property mobileWebkit3d: Boolean
812 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
813 mobileWebkit3d: mobile && webkit3d,
815 // @property mobileOpera: Boolean
816 // `true` for the Opera browser in a mobile device.
817 mobileOpera: mobile && window.opera,
819 // @property mobileGecko: Boolean
820 // `true` for gecko-based browsers running in a mobile device.
821 mobileGecko: mobile && gecko,
824 // @property touch: Boolean
825 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
826 // This does not necessarily mean that the browser is running in a computer with
827 // a touchscreen, it only means that the browser is capable of understanding
831 // @property msPointer: Boolean
832 // `true` for browsers implementing the Microsoft touch events model (notably IE10).
833 msPointer: !!msPointer,
835 // @property pointer: Boolean
836 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
840 // @property retina: Boolean
841 // `true` for browsers on a high-resolution "retina" screen.
842 retina: (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1
853 * Represents a point with `x` and `y` coordinates in pixels.
858 * var point = L.point(200, 300);
861 * 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:
864 * map.panBy([200, 300]);
865 * map.panBy(L.point(200, 300));
869 L.Point = function (x, y, round) {
870 // @property x: Number; The `x` coordinate of the point
871 this.x = (round ? Math.round(x) : x);
872 // @property y: Number; The `y` coordinate of the point
873 this.y = (round ? Math.round(y) : y);
876 L.Point.prototype = {
878 // @method clone(): Point
879 // Returns a copy of the current point.
881 return new L.Point(this.x, this.y);
884 // @method add(otherPoint: Point): Point
885 // Returns the result of addition of the current and the given points.
886 add: function (point) {
887 // non-destructive, returns a new point
888 return this.clone()._add(L.point(point));
891 _add: function (point) {
892 // destructive, used directly for performance in situations where it's safe to modify existing point
898 // @method subtract(otherPoint: Point): Point
899 // Returns the result of subtraction of the given point from the current.
900 subtract: function (point) {
901 return this.clone()._subtract(L.point(point));
904 _subtract: function (point) {
910 // @method divideBy(num: Number): Point
911 // Returns the result of division of the current point by the given number.
912 divideBy: function (num) {
913 return this.clone()._divideBy(num);
916 _divideBy: function (num) {
922 // @method multiplyBy(num: Number): Point
923 // Returns the result of multiplication of the current point by the given number.
924 multiplyBy: function (num) {
925 return this.clone()._multiplyBy(num);
928 _multiplyBy: function (num) {
934 // @method scaleBy(scale: Point): Point
935 // Multiply each coordinate of the current point by each coordinate of
936 // `scale`. In linear algebra terms, multiply the point by the
937 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
938 // defined by `scale`.
939 scaleBy: function (point) {
940 return new L.Point(this.x * point.x, this.y * point.y);
943 // @method unscaleBy(scale: Point): Point
944 // Inverse of `scaleBy`. Divide each coordinate of the current point by
945 // each coordinate of `scale`.
946 unscaleBy: function (point) {
947 return new L.Point(this.x / point.x, this.y / point.y);
950 // @method round(): Point
951 // Returns a copy of the current point with rounded coordinates.
953 return this.clone()._round();
956 _round: function () {
957 this.x = Math.round(this.x);
958 this.y = Math.round(this.y);
962 // @method floor(): Point
963 // Returns a copy of the current point with floored coordinates (rounded down).
965 return this.clone()._floor();
968 _floor: function () {
969 this.x = Math.floor(this.x);
970 this.y = Math.floor(this.y);
974 // @method ceil(): Point
975 // Returns a copy of the current point with ceiled coordinates (rounded up).
977 return this.clone()._ceil();
981 this.x = Math.ceil(this.x);
982 this.y = Math.ceil(this.y);
986 // @method distanceTo(otherPoint: Point): Number
987 // Returns the cartesian distance between the current and the given points.
988 distanceTo: function (point) {
989 point = L.point(point);
991 var x = point.x - this.x,
992 y = point.y - this.y;
994 return Math.sqrt(x * x + y * y);
997 // @method equals(otherPoint: Point): Boolean
998 // Returns `true` if the given point has the same coordinates.
999 equals: function (point) {
1000 point = L.point(point);
1002 return point.x === this.x &&
1006 // @method contains(otherPoint: Point): Boolean
1007 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
1008 contains: function (point) {
1009 point = L.point(point);
1011 return Math.abs(point.x) <= Math.abs(this.x) &&
1012 Math.abs(point.y) <= Math.abs(this.y);
1015 // @method toString(): String
1016 // Returns a string representation of the point for debugging purposes.
1017 toString: function () {
1019 L.Util.formatNum(this.x) + ', ' +
1020 L.Util.formatNum(this.y) + ')';
1024 // @factory L.point(x: Number, y: Number, round?: Boolean)
1025 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
1028 // @factory L.point(coords: Number[])
1029 // Expects an array of the form `[x, y]` instead.
1032 // @factory L.point(coords: Object)
1033 // Expects a plain object of the form `{x: Number, y: Number}` instead.
1034 L.point = function (x, y, round) {
1035 if (x instanceof L.Point) {
1038 if (L.Util.isArray(x)) {
1039 return new L.Point(x[0], x[1]);
1041 if (x === undefined || x === null) {
1044 if (typeof x === 'object' && 'x' in x && 'y' in x) {
1045 return new L.Point(x.x, x.y);
1047 return new L.Point(x, y, round);
1056 * Represents a rectangular area in pixel coordinates.
1061 * var p1 = L.point(10, 10),
1062 * p2 = L.point(40, 60),
1063 * bounds = L.bounds(p1, p2);
1066 * 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:
1069 * otherBounds.intersects([[10, 10], [40, 60]]);
1073 L.Bounds = function (a, b) {
1076 var points = b ? [a, b] : a;
1078 for (var i = 0, len = points.length; i < len; i++) {
1079 this.extend(points[i]);
1083 L.Bounds.prototype = {
1084 // @method extend(point: Point): this
1085 // Extends the bounds to contain the given point.
1086 extend: function (point) { // (Point)
1087 point = L.point(point);
1089 // @property min: Point
1090 // The top left corner of the rectangle.
1091 // @property max: Point
1092 // The bottom right corner of the rectangle.
1093 if (!this.min && !this.max) {
1094 this.min = point.clone();
1095 this.max = point.clone();
1097 this.min.x = Math.min(point.x, this.min.x);
1098 this.max.x = Math.max(point.x, this.max.x);
1099 this.min.y = Math.min(point.y, this.min.y);
1100 this.max.y = Math.max(point.y, this.max.y);
1105 // @method getCenter(round?: Boolean): Point
1106 // Returns the center point of the bounds.
1107 getCenter: function (round) {
1109 (this.min.x + this.max.x) / 2,
1110 (this.min.y + this.max.y) / 2, round);
1113 // @method getBottomLeft(): Point
1114 // Returns the bottom-left point of the bounds.
1115 getBottomLeft: function () {
1116 return new L.Point(this.min.x, this.max.y);
1119 // @method getTopRight(): Point
1120 // Returns the top-right point of the bounds.
1121 getTopRight: function () { // -> Point
1122 return new L.Point(this.max.x, this.min.y);
1125 // @method getSize(): Point
1126 // Returns the size of the given bounds
1127 getSize: function () {
1128 return this.max.subtract(this.min);
1131 // @method contains(otherBounds: Bounds): Boolean
1132 // Returns `true` if the rectangle contains the given one.
1134 // @method contains(point: Point): Boolean
1135 // Returns `true` if the rectangle contains the given point.
1136 contains: function (obj) {
1139 if (typeof obj[0] === 'number' || obj instanceof L.Point) {
1142 obj = L.bounds(obj);
1145 if (obj instanceof L.Bounds) {
1152 return (min.x >= this.min.x) &&
1153 (max.x <= this.max.x) &&
1154 (min.y >= this.min.y) &&
1155 (max.y <= this.max.y);
1158 // @method intersects(otherBounds: Bounds): Boolean
1159 // Returns `true` if the rectangle intersects the given bounds. Two bounds
1160 // intersect if they have at least one point in common.
1161 intersects: function (bounds) { // (Bounds) -> Boolean
1162 bounds = L.bounds(bounds);
1168 xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1169 yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1171 return xIntersects && yIntersects;
1174 // @method overlaps(otherBounds: Bounds): Boolean
1175 // Returns `true` if the rectangle overlaps the given bounds. Two bounds
1176 // overlap if their intersection is an area.
1177 overlaps: function (bounds) { // (Bounds) -> Boolean
1178 bounds = L.bounds(bounds);
1184 xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1185 yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1187 return xOverlaps && yOverlaps;
1190 isValid: function () {
1191 return !!(this.min && this.max);
1196 // @factory L.bounds(topLeft: Point, bottomRight: Point)
1197 // Creates a Bounds object from two coordinates (usually top-left and bottom-right corners).
1199 // @factory L.bounds(points: Point[])
1200 // Creates a Bounds object from the points it contains
1201 L.bounds = function (a, b) {
1202 if (!a || a instanceof L.Bounds) {
1205 return new L.Bounds(a, b);
1211 * @class Transformation
1212 * @aka L.Transformation
1214 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1215 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1216 * the reverse. Used by Leaflet in its projections code.
1221 * var transformation = new L.Transformation(2, 5, -1, 10),
1222 * p = L.point(1, 2),
1223 * p2 = transformation.transform(p), // L.point(7, 8)
1224 * p3 = transformation.untransform(p2); // L.point(1, 2)
1229 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1230 // Creates a `Transformation` object with the given coefficients.
1231 L.Transformation = function (a, b, c, d) {
1238 L.Transformation.prototype = {
1239 // @method transform(point: Point, scale?: Number): Point
1240 // Returns a transformed point, optionally multiplied by the given scale.
1241 // Only accepts actual `L.Point` instances, not arrays.
1242 transform: function (point, scale) { // (Point, Number) -> Point
1243 return this._transform(point.clone(), scale);
1246 // destructive transform (faster)
1247 _transform: function (point, scale) {
1249 point.x = scale * (this._a * point.x + this._b);
1250 point.y = scale * (this._c * point.y + this._d);
1254 // @method untransform(point: Point, scale?: Number): Point
1255 // Returns the reverse transformation of the given point, optionally divided
1256 // by the given scale. Only accepts actual `L.Point` instances, not arrays.
1257 untransform: function (point, scale) {
1260 (point.x / scale - this._b) / this._a,
1261 (point.y / scale - this._d) / this._c);
1268 * @namespace DomUtil
1270 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
1271 * tree, used by Leaflet internally.
1273 * Most functions expecting or returning a `HTMLElement` also work for
1274 * SVG elements. The only difference is that classes refer to CSS classes
1275 * in HTML and SVG classes in SVG.
1280 // @function get(id: String|HTMLElement): HTMLElement
1281 // Returns an element given its DOM id, or returns the element itself
1282 // if it was passed directly.
1283 get: function (id) {
1284 return typeof id === 'string' ? document.getElementById(id) : id;
1287 // @function getStyle(el: HTMLElement, styleAttrib: String): String
1288 // Returns the value for a certain style attribute on an element,
1289 // including computed values or values set through CSS.
1290 getStyle: function (el, style) {
1292 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
1294 if ((!value || value === 'auto') && document.defaultView) {
1295 var css = document.defaultView.getComputedStyle(el, null);
1296 value = css ? css[style] : null;
1299 return value === 'auto' ? null : value;
1302 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
1303 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
1304 create: function (tagName, className, container) {
1306 var el = document.createElement(tagName);
1307 el.className = className || '';
1310 container.appendChild(el);
1316 // @function remove(el: HTMLElement)
1317 // Removes `el` from its parent element
1318 remove: function (el) {
1319 var parent = el.parentNode;
1321 parent.removeChild(el);
1325 // @function empty(el: HTMLElement)
1326 // Removes all of `el`'s children elements from `el`
1327 empty: function (el) {
1328 while (el.firstChild) {
1329 el.removeChild(el.firstChild);
1333 // @function toFront(el: HTMLElement)
1334 // Makes `el` the last children of its parent, so it renders in front of the other children.
1335 toFront: function (el) {
1336 el.parentNode.appendChild(el);
1339 // @function toBack(el: HTMLElement)
1340 // Makes `el` the first children of its parent, so it renders back from the other children.
1341 toBack: function (el) {
1342 var parent = el.parentNode;
1343 parent.insertBefore(el, parent.firstChild);
1346 // @function hasClass(el: HTMLElement, name: String): Boolean
1347 // Returns `true` if the element's class attribute contains `name`.
1348 hasClass: function (el, name) {
1349 if (el.classList !== undefined) {
1350 return el.classList.contains(name);
1352 var className = L.DomUtil.getClass(el);
1353 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
1356 // @function addClass(el: HTMLElement, name: String)
1357 // Adds `name` to the element's class attribute.
1358 addClass: function (el, name) {
1359 if (el.classList !== undefined) {
1360 var classes = L.Util.splitWords(name);
1361 for (var i = 0, len = classes.length; i < len; i++) {
1362 el.classList.add(classes[i]);
1364 } else if (!L.DomUtil.hasClass(el, name)) {
1365 var className = L.DomUtil.getClass(el);
1366 L.DomUtil.setClass(el, (className ? className + ' ' : '') + name);
1370 // @function removeClass(el: HTMLElement, name: String)
1371 // Removes `name` from the element's class attribute.
1372 removeClass: function (el, name) {
1373 if (el.classList !== undefined) {
1374 el.classList.remove(name);
1376 L.DomUtil.setClass(el, L.Util.trim((' ' + L.DomUtil.getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
1380 // @function setClass(el: HTMLElement, name: String)
1381 // Sets the element's class.
1382 setClass: function (el, name) {
1383 if (el.className.baseVal === undefined) {
1384 el.className = name;
1386 // in case of SVG element
1387 el.className.baseVal = name;
1391 // @function getClass(el: HTMLElement): String
1392 // Returns the element's class.
1393 getClass: function (el) {
1394 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
1397 // @function setOpacity(el: HTMLElement, opacity: Number)
1398 // Set the opacity of an element (including old IE support).
1399 // `opacity` must be a number from `0` to `1`.
1400 setOpacity: function (el, value) {
1402 if ('opacity' in el.style) {
1403 el.style.opacity = value;
1405 } else if ('filter' in el.style) {
1406 L.DomUtil._setOpacityIE(el, value);
1410 _setOpacityIE: function (el, value) {
1412 filterName = 'DXImageTransform.Microsoft.Alpha';
1414 // filters collection throws an error if we try to retrieve a filter that doesn't exist
1416 filter = el.filters.item(filterName);
1418 // don't set opacity to 1 if we haven't already set an opacity,
1419 // it isn't needed and breaks transparent pngs.
1420 if (value === 1) { return; }
1423 value = Math.round(value * 100);
1426 filter.Enabled = (value !== 100);
1427 filter.Opacity = value;
1429 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
1433 // @function testProp(props: String[]): String|false
1434 // Goes through the array of style names and returns the first name
1435 // that is a valid style name for an element. If no such name is found,
1436 // it returns false. Useful for vendor-prefixed styles like `transform`.
1437 testProp: function (props) {
1439 var style = document.documentElement.style;
1441 for (var i = 0; i < props.length; i++) {
1442 if (props[i] in style) {
1449 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
1450 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
1451 // and optionally scaled by `scale`. Does not have an effect if the
1452 // browser doesn't support 3D CSS transforms.
1453 setTransform: function (el, offset, scale) {
1454 var pos = offset || new L.Point(0, 0);
1456 el.style[L.DomUtil.TRANSFORM] =
1458 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
1459 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
1460 (scale ? ' scale(' + scale + ')' : '');
1463 // @function setPosition(el: HTMLElement, position: Point)
1464 // Sets the position of `el` to coordinates specified by `position`,
1465 // using CSS translate or top/left positioning depending on the browser
1466 // (used by Leaflet internally to position its layers).
1467 setPosition: function (el, point) { // (HTMLElement, Point[, Boolean])
1470 el._leaflet_pos = point;
1473 if (L.Browser.any3d) {
1474 L.DomUtil.setTransform(el, point);
1476 el.style.left = point.x + 'px';
1477 el.style.top = point.y + 'px';
1481 // @function getPosition(el: HTMLElement): Point
1482 // Returns the coordinates of an element previously positioned with setPosition.
1483 getPosition: function (el) {
1484 // this method is only used for elements previously positioned using setPosition,
1485 // so it's safe to cache the position for performance
1487 return el._leaflet_pos || new L.Point(0, 0);
1493 // prefix style property names
1495 // @property TRANSFORM: String
1496 // Vendor-prefixed fransform style name (e.g. `'webkitTransform'` for WebKit).
1497 L.DomUtil.TRANSFORM = L.DomUtil.testProp(
1498 ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
1501 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
1502 // the same for the transitionend event, in particular the Android 4.1 stock browser
1504 // @property TRANSITION: String
1505 // Vendor-prefixed transform style name.
1506 var transition = L.DomUtil.TRANSITION = L.DomUtil.testProp(
1507 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
1509 L.DomUtil.TRANSITION_END =
1510 transition === 'webkitTransition' || transition === 'OTransition' ? transition + 'End' : 'transitionend';
1512 // @function disableTextSelection()
1513 // Prevents the user from generating `selectstart` DOM events, usually generated
1514 // when the user drags the mouse through a page with text. Used internally
1515 // by Leaflet to override the behaviour of any click-and-drag interaction on
1516 // the map. Affects drag interactions on the whole document.
1518 // @function enableTextSelection()
1519 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
1520 if ('onselectstart' in document) {
1521 L.DomUtil.disableTextSelection = function () {
1522 L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);
1524 L.DomUtil.enableTextSelection = function () {
1525 L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);
1529 var userSelectProperty = L.DomUtil.testProp(
1530 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
1532 L.DomUtil.disableTextSelection = function () {
1533 if (userSelectProperty) {
1534 var style = document.documentElement.style;
1535 this._userSelect = style[userSelectProperty];
1536 style[userSelectProperty] = 'none';
1539 L.DomUtil.enableTextSelection = function () {
1540 if (userSelectProperty) {
1541 document.documentElement.style[userSelectProperty] = this._userSelect;
1542 delete this._userSelect;
1547 // @function disableImageDrag()
1548 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
1549 // for `dragstart` DOM events, usually generated when the user drags an image.
1550 L.DomUtil.disableImageDrag = function () {
1551 L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault);
1554 // @function enableImageDrag()
1555 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
1556 L.DomUtil.enableImageDrag = function () {
1557 L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault);
1560 // @function preventOutline(el: HTMLElement)
1561 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
1562 // of the element `el` invisible. Used internally by Leaflet to prevent
1563 // focusable elements from displaying an outline when the user performs a
1564 // drag interaction on them.
1565 L.DomUtil.preventOutline = function (element) {
1566 while (element.tabIndex === -1) {
1567 element = element.parentNode;
1569 if (!element || !element.style) { return; }
1570 L.DomUtil.restoreOutline();
1571 this._outlineElement = element;
1572 this._outlineStyle = element.style.outline;
1573 element.style.outline = 'none';
1574 L.DomEvent.on(window, 'keydown', L.DomUtil.restoreOutline, this);
1577 // @function restoreOutline()
1578 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
1579 L.DomUtil.restoreOutline = function () {
1580 if (!this._outlineElement) { return; }
1581 this._outlineElement.style.outline = this._outlineStyle;
1582 delete this._outlineElement;
1583 delete this._outlineStyle;
1584 L.DomEvent.off(window, 'keydown', L.DomUtil.restoreOutline, this);
1593 * Represents a geographical point with a certain latitude and longitude.
1598 * var latlng = L.latLng(50.5, 30.5);
1601 * 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:
1604 * map.panTo([50, 30]);
1605 * map.panTo({lon: 30, lat: 50});
1606 * map.panTo({lat: 50, lng: 30});
1607 * map.panTo(L.latLng(50, 30));
1611 L.LatLng = function (lat, lng, alt) {
1612 if (isNaN(lat) || isNaN(lng)) {
1613 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1616 // @property lat: Number
1617 // Latitude in degrees
1620 // @property lng: Number
1621 // Longitude in degrees
1624 // @property alt: Number
1625 // Altitude in meters (optional)
1626 if (alt !== undefined) {
1631 L.LatLng.prototype = {
1632 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1633 // 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.
1634 equals: function (obj, maxMargin) {
1635 if (!obj) { return false; }
1637 obj = L.latLng(obj);
1639 var margin = Math.max(
1640 Math.abs(this.lat - obj.lat),
1641 Math.abs(this.lng - obj.lng));
1643 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1646 // @method toString(): String
1647 // Returns a string representation of the point (for debugging purposes).
1648 toString: function (precision) {
1650 L.Util.formatNum(this.lat, precision) + ', ' +
1651 L.Util.formatNum(this.lng, precision) + ')';
1654 // @method distanceTo(otherLatLng: LatLng): Number
1655 // Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula).
1656 distanceTo: function (other) {
1657 return L.CRS.Earth.distance(this, L.latLng(other));
1660 // @method wrap(): LatLng
1661 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1663 return L.CRS.Earth.wrapLatLng(this);
1666 // @method toBounds(sizeInMeters: Number): LatLngBounds
1667 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
1668 toBounds: function (sizeInMeters) {
1669 var latAccuracy = 180 * sizeInMeters / 40075017,
1670 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1672 return L.latLngBounds(
1673 [this.lat - latAccuracy, this.lng - lngAccuracy],
1674 [this.lat + latAccuracy, this.lng + lngAccuracy]);
1677 clone: function () {
1678 return new L.LatLng(this.lat, this.lng, this.alt);
1684 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1685 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1688 // @factory L.latLng(coords: Array): LatLng
1689 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1692 // @factory L.latLng(coords: Object): LatLng
1693 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1695 L.latLng = function (a, b, c) {
1696 if (a instanceof L.LatLng) {
1699 if (L.Util.isArray(a) && typeof a[0] !== 'object') {
1700 if (a.length === 3) {
1701 return new L.LatLng(a[0], a[1], a[2]);
1703 if (a.length === 2) {
1704 return new L.LatLng(a[0], a[1]);
1708 if (a === undefined || a === null) {
1711 if (typeof a === 'object' && 'lat' in a) {
1712 return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1714 if (b === undefined) {
1717 return new L.LatLng(a, b, c);
1723 * @class LatLngBounds
1724 * @aka L.LatLngBounds
1726 * Represents a rectangular geographical area on a map.
1731 * var corner1 = L.latLng(40.712, -74.227),
1732 * corner2 = L.latLng(40.774, -74.125),
1733 * bounds = L.latLngBounds(corner1, corner2);
1736 * 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:
1740 * [40.712, -74.227],
1745 * 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.
1748 L.LatLngBounds = function (corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
1749 if (!corner1) { return; }
1751 var latlngs = corner2 ? [corner1, corner2] : corner1;
1753 for (var i = 0, len = latlngs.length; i < len; i++) {
1754 this.extend(latlngs[i]);
1758 L.LatLngBounds.prototype = {
1760 // @method extend(latlng: LatLng): this
1761 // Extend the bounds to contain the given point
1764 // @method extend(otherBounds: LatLngBounds): this
1765 // Extend the bounds to contain the given bounds
1766 extend: function (obj) {
1767 var sw = this._southWest,
1768 ne = this._northEast,
1771 if (obj instanceof L.LatLng) {
1775 } else if (obj instanceof L.LatLngBounds) {
1776 sw2 = obj._southWest;
1777 ne2 = obj._northEast;
1779 if (!sw2 || !ne2) { return this; }
1782 return obj ? this.extend(L.latLng(obj) || L.latLngBounds(obj)) : this;
1786 this._southWest = new L.LatLng(sw2.lat, sw2.lng);
1787 this._northEast = new L.LatLng(ne2.lat, ne2.lng);
1789 sw.lat = Math.min(sw2.lat, sw.lat);
1790 sw.lng = Math.min(sw2.lng, sw.lng);
1791 ne.lat = Math.max(ne2.lat, ne.lat);
1792 ne.lng = Math.max(ne2.lng, ne.lng);
1798 // @method pad(bufferRatio: Number): LatLngBounds
1799 // Returns bigger bounds created by extending the current bounds by a given percentage in each direction.
1800 pad: function (bufferRatio) {
1801 var sw = this._southWest,
1802 ne = this._northEast,
1803 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1804 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1806 return new L.LatLngBounds(
1807 new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1808 new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1811 // @method getCenter(): LatLng
1812 // Returns the center point of the bounds.
1813 getCenter: function () {
1814 return new L.LatLng(
1815 (this._southWest.lat + this._northEast.lat) / 2,
1816 (this._southWest.lng + this._northEast.lng) / 2);
1819 // @method getSouthWest(): LatLng
1820 // Returns the south-west point of the bounds.
1821 getSouthWest: function () {
1822 return this._southWest;
1825 // @method getNorthEast(): LatLng
1826 // Returns the north-east point of the bounds.
1827 getNorthEast: function () {
1828 return this._northEast;
1831 // @method getNorthWest(): LatLng
1832 // Returns the north-west point of the bounds.
1833 getNorthWest: function () {
1834 return new L.LatLng(this.getNorth(), this.getWest());
1837 // @method getSouthEast(): LatLng
1838 // Returns the south-east point of the bounds.
1839 getSouthEast: function () {
1840 return new L.LatLng(this.getSouth(), this.getEast());
1843 // @method getWest(): Number
1844 // Returns the west longitude of the bounds
1845 getWest: function () {
1846 return this._southWest.lng;
1849 // @method getSouth(): Number
1850 // Returns the south latitude of the bounds
1851 getSouth: function () {
1852 return this._southWest.lat;
1855 // @method getEast(): Number
1856 // Returns the east longitude of the bounds
1857 getEast: function () {
1858 return this._northEast.lng;
1861 // @method getNorth(): Number
1862 // Returns the north latitude of the bounds
1863 getNorth: function () {
1864 return this._northEast.lat;
1867 // @method contains(otherBounds: LatLngBounds): Boolean
1868 // Returns `true` if the rectangle contains the given one.
1871 // @method contains (latlng: LatLng): Boolean
1872 // Returns `true` if the rectangle contains the given point.
1873 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1874 if (typeof obj[0] === 'number' || obj instanceof L.LatLng || 'lat' in obj) {
1875 obj = L.latLng(obj);
1877 obj = L.latLngBounds(obj);
1880 var sw = this._southWest,
1881 ne = this._northEast,
1884 if (obj instanceof L.LatLngBounds) {
1885 sw2 = obj.getSouthWest();
1886 ne2 = obj.getNorthEast();
1891 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1892 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1895 // @method intersects(otherBounds: LatLngBounds): Boolean
1896 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1897 intersects: function (bounds) {
1898 bounds = L.latLngBounds(bounds);
1900 var sw = this._southWest,
1901 ne = this._northEast,
1902 sw2 = bounds.getSouthWest(),
1903 ne2 = bounds.getNorthEast(),
1905 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1906 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1908 return latIntersects && lngIntersects;
1911 // @method overlaps(otherBounds: Bounds): Boolean
1912 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1913 overlaps: function (bounds) {
1914 bounds = L.latLngBounds(bounds);
1916 var sw = this._southWest,
1917 ne = this._northEast,
1918 sw2 = bounds.getSouthWest(),
1919 ne2 = bounds.getNorthEast(),
1921 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1922 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1924 return latOverlaps && lngOverlaps;
1927 // @method toBBoxString(): String
1928 // 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.
1929 toBBoxString: function () {
1930 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1933 // @method equals(otherBounds: LatLngBounds): Boolean
1934 // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds.
1935 equals: function (bounds) {
1936 if (!bounds) { return false; }
1938 bounds = L.latLngBounds(bounds);
1940 return this._southWest.equals(bounds.getSouthWest()) &&
1941 this._northEast.equals(bounds.getNorthEast());
1944 // @method isValid(): Boolean
1945 // Returns `true` if the bounds are properly initialized.
1946 isValid: function () {
1947 return !!(this._southWest && this._northEast);
1951 // TODO International date line?
1953 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
1954 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
1957 // @factory L.latLngBounds(latlngs: LatLng[])
1958 // 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).
1959 L.latLngBounds = function (a, b) {
1960 if (a instanceof L.LatLngBounds) {
1963 return new L.LatLngBounds(a, b);
1969 * @namespace Projection
1971 * Leaflet comes with a set of already defined Projections out of the box:
1973 * @projection L.Projection.LonLat
1975 * Equirectangular, or Plate Carree projection — the most simple projection,
1976 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
1977 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
1978 * `EPSG:3395` and `Simple` CRS.
1983 L.Projection.LonLat = {
1984 project: function (latlng) {
1985 return new L.Point(latlng.lng, latlng.lat);
1988 unproject: function (point) {
1989 return new L.LatLng(point.y, point.x);
1992 bounds: L.bounds([-180, -90], [180, 90])
1998 * @namespace Projection
1999 * @projection L.Projection.SphericalMercator
2001 * Spherical Mercator projection — the most common projection for online maps,
2002 * used by almost all free and commercial tile providers. Assumes that Earth is
2003 * a sphere. Used by the `EPSG:3857` CRS.
2006 L.Projection.SphericalMercator = {
2009 MAX_LATITUDE: 85.0511287798,
2011 project: function (latlng) {
2012 var d = Math.PI / 180,
2013 max = this.MAX_LATITUDE,
2014 lat = Math.max(Math.min(max, latlng.lat), -max),
2015 sin = Math.sin(lat * d);
2018 this.R * latlng.lng * d,
2019 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
2022 unproject: function (point) {
2023 var d = 180 / Math.PI;
2025 return new L.LatLng(
2026 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
2027 point.x * d / this.R);
2030 bounds: (function () {
2031 var d = 6378137 * Math.PI;
2032 return L.bounds([-d, -d], [d, d]);
2041 * Abstract class that defines coordinate reference systems for projecting
2042 * geographical points into pixel (screen) coordinates and back (and to
2043 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
2044 * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
2046 * Leaflet defines the most usual CRSs by default. If you want to use a
2047 * CRS not defined by default, take a look at the
2048 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
2052 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
2053 // Projects geographical coordinates into pixel coordinates for a given zoom.
2054 latLngToPoint: function (latlng, zoom) {
2055 var projectedPoint = this.projection.project(latlng),
2056 scale = this.scale(zoom);
2058 return this.transformation._transform(projectedPoint, scale);
2061 // @method pointToLatLng(point: Point, zoom: Number): LatLng
2062 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
2063 // zoom into geographical coordinates.
2064 pointToLatLng: function (point, zoom) {
2065 var scale = this.scale(zoom),
2066 untransformedPoint = this.transformation.untransform(point, scale);
2068 return this.projection.unproject(untransformedPoint);
2071 // @method project(latlng: LatLng): Point
2072 // Projects geographical coordinates into coordinates in units accepted for
2073 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
2074 project: function (latlng) {
2075 return this.projection.project(latlng);
2078 // @method unproject(point: Point): LatLng
2079 // Given a projected coordinate returns the corresponding LatLng.
2080 // The inverse of `project`.
2081 unproject: function (point) {
2082 return this.projection.unproject(point);
2085 // @method scale(zoom: Number): Number
2086 // Returns the scale used when transforming projected coordinates into
2087 // pixel coordinates for a particular zoom. For example, it returns
2088 // `256 * 2^zoom` for Mercator-based CRS.
2089 scale: function (zoom) {
2090 return 256 * Math.pow(2, zoom);
2093 // @method zoom(scale: Number): Number
2094 // Inverse of `scale()`, returns the zoom level corresponding to a scale
2095 // factor of `scale`.
2096 zoom: function (scale) {
2097 return Math.log(scale / 256) / Math.LN2;
2100 // @method getProjectedBounds(zoom: Number): Bounds
2101 // Returns the projection's bounds scaled and transformed for the provided `zoom`.
2102 getProjectedBounds: function (zoom) {
2103 if (this.infinite) { return null; }
2105 var b = this.projection.bounds,
2106 s = this.scale(zoom),
2107 min = this.transformation.transform(b.min, s),
2108 max = this.transformation.transform(b.max, s);
2110 return L.bounds(min, max);
2113 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
2114 // Returns the distance between two geographical coordinates.
2116 // @property code: String
2117 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
2119 // @property wrapLng: Number[]
2120 // An array of two numbers defining whether the longitude (horizontal) coordinate
2121 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
2122 // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
2124 // @property wrapLat: Number[]
2125 // Like `wrapLng`, but for the latitude (vertical) axis.
2127 // wrapLng: [min, max],
2128 // wrapLat: [min, max],
2130 // @property infinite: Boolean
2131 // If true, the coordinate space will be unbounded (infinite in both axes)
2134 // @method wrapLatLng(latlng: LatLng): LatLng
2135 // Returns a `LatLng` where lat and lng has been wrapped according to the
2136 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
2137 // Only accepts actual `L.LatLng` instances, not arrays.
2138 wrapLatLng: function (latlng) {
2139 var lng = this.wrapLng ? L.Util.wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
2140 lat = this.wrapLat ? L.Util.wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
2143 return L.latLng(lat, lng, alt);
2146 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
2147 // Returns a `LatLngBounds` with the same size as the given one, ensuring
2148 // that its center is within the CRS's bounds.
2149 // Only accepts actual `L.LatLngBounds` instances, not arrays.
2150 wrapLatLngBounds: function (bounds) {
2151 var center = bounds.getCenter(),
2152 newCenter = this.wrapLatLng(center),
2153 latShift = center.lat - newCenter.lat,
2154 lngShift = center.lng - newCenter.lng;
2156 if (latShift === 0 && lngShift === 0) {
2160 var sw = bounds.getSouthWest(),
2161 ne = bounds.getNorthEast(),
2162 newSw = L.latLng({lat: sw.lat - latShift, lng: sw.lng - lngShift}),
2163 newNe = L.latLng({lat: ne.lat - latShift, lng: ne.lng - lngShift});
2165 return new L.LatLngBounds(newSw, newNe);
2175 * A simple CRS that maps longitude and latitude into `x` and `y` directly.
2176 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
2177 * axis should still be inverted (going from bottom to top). `distance()` returns
2178 * simple euclidean distance.
2181 L.CRS.Simple = L.extend({}, L.CRS, {
2182 projection: L.Projection.LonLat,
2183 transformation: new L.Transformation(1, 0, -1, 0),
2185 scale: function (zoom) {
2186 return Math.pow(2, zoom);
2189 zoom: function (scale) {
2190 return Math.log(scale) / Math.LN2;
2193 distance: function (latlng1, latlng2) {
2194 var dx = latlng2.lng - latlng1.lng,
2195 dy = latlng2.lat - latlng1.lat;
2197 return Math.sqrt(dx * dx + dy * dy);
2209 * Serves as the base for CRS that are global such that they cover the earth.
2210 * Can only be used as the base for other CRS and cannot be used directly,
2211 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
2215 L.CRS.Earth = L.extend({}, L.CRS, {
2216 wrapLng: [-180, 180],
2218 // Mean Earth Radius, as recommended for use by
2219 // the International Union of Geodesy and Geophysics,
2220 // see http://rosettacode.org/wiki/Haversine_formula
2223 // distance between two geographical points using spherical law of cosines approximation
2224 distance: function (latlng1, latlng2) {
2225 var rad = Math.PI / 180,
2226 lat1 = latlng1.lat * rad,
2227 lat2 = latlng2.lat * rad,
2228 a = Math.sin(lat1) * Math.sin(lat2) +
2229 Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad);
2231 return this.R * Math.acos(Math.min(a, 1));
2239 * @crs L.CRS.EPSG3857
2241 * The most common CRS for online maps, used by almost all free and commercial
2242 * tile providers. Uses Spherical Mercator projection. Set in by default in
2243 * Map's `crs` option.
2246 L.CRS.EPSG3857 = L.extend({}, L.CRS.Earth, {
2248 projection: L.Projection.SphericalMercator,
2250 transformation: (function () {
2251 var scale = 0.5 / (Math.PI * L.Projection.SphericalMercator.R);
2252 return new L.Transformation(scale, 0.5, -scale, 0.5);
2256 L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
2264 * @crs L.CRS.EPSG4326
2266 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
2268 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
2269 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer`
2270 * with this CRS, ensure that there are two 256x256 pixel tiles covering the
2271 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
2272 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
2275 L.CRS.EPSG4326 = L.extend({}, L.CRS.Earth, {
2277 projection: L.Projection.LonLat,
2278 transformation: new L.Transformation(1 / 180, 1, -1 / 180, 0.5)
2288 * The central class of the API — it is used to create a map on a page and manipulate it.
2293 * // initialize the map on the "map" div with a given center and zoom
2294 * var map = L.map('map', {
2295 * center: [51.505, -0.09],
2302 L.Map = L.Evented.extend({
2305 // @section Map State Options
2306 // @option crs: CRS = L.CRS.EPSG3857
2307 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
2308 // sure what it means.
2309 crs: L.CRS.EPSG3857,
2311 // @option center: LatLng = undefined
2312 // Initial geographic center of the map
2315 // @option zoom: Number = undefined
2316 // Initial map zoom level
2319 // @option minZoom: Number = undefined
2320 // Minimum zoom level of the map. Overrides any `minZoom` option set on map layers.
2323 // @option maxZoom: Number = undefined
2324 // Maximum zoom level of the map. Overrides any `maxZoom` option set on map layers.
2327 // @option layers: Layer[] = []
2328 // Array of layers that will be added to the map initially
2331 // @option maxBounds: LatLngBounds = null
2332 // When this option is set, the map restricts the view to the given
2333 // geographical bounds, bouncing the user back if the user tries to pan
2334 // outside the view. To set the restriction dynamically, use
2335 // [`setMaxBounds`](#map-setmaxbounds) method.
2336 maxBounds: undefined,
2338 // @option renderer: Renderer = *
2339 // The default method for drawing vector layers on the map. `L.SVG`
2340 // or `L.Canvas` by default depending on browser support.
2341 renderer: undefined,
2344 // @section Animation Options
2345 // @option zoomAnimation: Boolean = true
2346 // Whether the map zoom animation is enabled. By default it's enabled
2347 // in all browsers that support CSS3 Transitions except Android.
2348 zoomAnimation: true,
2350 // @option zoomAnimationThreshold: Number = 4
2351 // Won't animate zoom if the zoom difference exceeds this value.
2352 zoomAnimationThreshold: 4,
2354 // @option fadeAnimation: Boolean = true
2355 // Whether the tile fade animation is enabled. By default it's enabled
2356 // in all browsers that support CSS3 Transitions except Android.
2357 fadeAnimation: true,
2359 // @option markerZoomAnimation: Boolean = true
2360 // Whether markers animate their zoom with the zoom animation, if disabled
2361 // they will disappear for the length of the animation. By default it's
2362 // enabled in all browsers that support CSS3 Transitions except Android.
2363 markerZoomAnimation: true,
2365 // @option transform3DLimit: Number = 2^23
2366 // Defines the maximum size of a CSS translation transform. The default
2367 // value should not be changed unless a web browser positions layers in
2368 // the wrong place after doing a large `panBy`.
2369 transform3DLimit: 8388608, // Precision limit of a 32-bit float
2371 // @section Interaction Options
2372 // @option zoomSnap: Number = 1
2373 // Forces the map's zoom level to always be a multiple of this, particularly
2374 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
2375 // By default, the zoom level snaps to the nearest integer; lower values
2376 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
2377 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
2380 // @option zoomDelta: Number = 1
2381 // Controls how much the map's zoom level will change after a
2382 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
2383 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
2384 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
2387 // @option trackResize: Boolean = true
2388 // Whether the map automatically handles browser window resize to update itself.
2392 initialize: function (id, options) { // (HTMLElement or String, Object)
2393 options = L.setOptions(this, options);
2395 this._initContainer(id);
2398 // hack for https://github.com/Leaflet/Leaflet/issues/1980
2399 this._onResize = L.bind(this._onResize, this);
2403 if (options.maxBounds) {
2404 this.setMaxBounds(options.maxBounds);
2407 if (options.zoom !== undefined) {
2408 this._zoom = this._limitZoom(options.zoom);
2411 if (options.center && options.zoom !== undefined) {
2412 this.setView(L.latLng(options.center), options.zoom, {reset: true});
2415 this._handlers = [];
2417 this._zoomBoundLayers = {};
2418 this._sizeChanged = true;
2420 this.callInitHooks();
2422 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
2423 this._zoomAnimated = L.DomUtil.TRANSITION && L.Browser.any3d && !L.Browser.mobileOpera &&
2424 this.options.zoomAnimation;
2426 // zoom transitions run with the same duration for all layers, so if one of transitionend events
2427 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
2428 if (this._zoomAnimated) {
2429 this._createAnimProxy();
2430 L.DomEvent.on(this._proxy, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
2433 this._addLayers(this.options.layers);
2437 // @section Methods for modifying map state
2439 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
2440 // Sets the view of the map (geographical center and zoom) with the given
2441 // animation options.
2442 setView: function (center, zoom, options) {
2444 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
2445 center = this._limitCenter(L.latLng(center), zoom, this.options.maxBounds);
2446 options = options || {};
2450 if (this._loaded && !options.reset && options !== true) {
2452 if (options.animate !== undefined) {
2453 options.zoom = L.extend({animate: options.animate}, options.zoom);
2454 options.pan = L.extend({animate: options.animate, duration: options.duration}, options.pan);
2457 // try animating pan or zoom
2458 var moved = (this._zoom !== zoom) ?
2459 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
2460 this._tryAnimatedPan(center, options.pan);
2463 // prevent resize handler call, the view will refresh after animation anyway
2464 clearTimeout(this._sizeTimer);
2469 // animation didn't start, just reset the map view
2470 this._resetView(center, zoom);
2475 // @method setZoom(zoom: Number, options: Zoom/pan options): this
2476 // Sets the zoom of the map.
2477 setZoom: function (zoom, options) {
2478 if (!this._loaded) {
2482 return this.setView(this.getCenter(), zoom, {zoom: options});
2485 // @method zoomIn(delta?: Number, options?: Zoom options): this
2486 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
2487 zoomIn: function (delta, options) {
2488 delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
2489 return this.setZoom(this._zoom + delta, options);
2492 // @method zoomOut(delta?: Number, options?: Zoom options): this
2493 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
2494 zoomOut: function (delta, options) {
2495 delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
2496 return this.setZoom(this._zoom - delta, options);
2499 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
2500 // Zooms the map while keeping a specified geographical point on the map
2501 // stationary (e.g. used internally for scroll zoom and double-click zoom).
2503 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
2504 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
2505 setZoomAround: function (latlng, zoom, options) {
2506 var scale = this.getZoomScale(zoom),
2507 viewHalf = this.getSize().divideBy(2),
2508 containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng),
2510 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
2511 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
2513 return this.setView(newCenter, zoom, {zoom: options});
2516 _getBoundsCenterZoom: function (bounds, options) {
2518 options = options || {};
2519 bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds);
2521 var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]),
2522 paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]),
2524 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
2526 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
2528 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
2530 swPoint = this.project(bounds.getSouthWest(), zoom),
2531 nePoint = this.project(bounds.getNorthEast(), zoom),
2532 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
2540 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
2541 // Sets a map view that contains the given geographical bounds with the
2542 // maximum zoom level possible.
2543 fitBounds: function (bounds, options) {
2545 bounds = L.latLngBounds(bounds);
2547 if (!bounds.isValid()) {
2548 throw new Error('Bounds are not valid.');
2551 var target = this._getBoundsCenterZoom(bounds, options);
2552 return this.setView(target.center, target.zoom, options);
2555 // @method fitWorld(options?: fitBounds options): this
2556 // Sets a map view that mostly contains the whole world with the maximum
2557 // zoom level possible.
2558 fitWorld: function (options) {
2559 return this.fitBounds([[-90, -180], [90, 180]], options);
2562 // @method panTo(latlng: LatLng, options?: Pan options): this
2563 // Pans the map to a given center.
2564 panTo: function (center, options) { // (LatLng)
2565 return this.setView(center, this._zoom, {pan: options});
2568 // @method panBy(offset: Point): this
2569 // Pans the map by a given number of pixels (animated).
2570 panBy: function (offset, options) {
2571 offset = L.point(offset).round();
2572 options = options || {};
2574 if (!offset.x && !offset.y) {
2575 return this.fire('moveend');
2577 // If we pan too far, Chrome gets issues with tiles
2578 // and makes them disappear or appear in the wrong place (slightly offset) #2602
2579 if (options.animate !== true && !this.getSize().contains(offset)) {
2580 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
2584 if (!this._panAnim) {
2585 this._panAnim = new L.PosAnimation();
2588 'step': this._onPanTransitionStep,
2589 'end': this._onPanTransitionEnd
2593 // don't fire movestart if animating inertia
2594 if (!options.noMoveStart) {
2595 this.fire('movestart');
2598 // animate pan unless animate: false specified
2599 if (options.animate !== false) {
2600 L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
2602 var newPos = this._getMapPanePos().subtract(offset).round();
2603 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
2605 this._rawPanBy(offset);
2606 this.fire('move').fire('moveend');
2612 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
2613 // Sets the view of the map (geographical center and zoom) performing a smooth
2614 // pan-zoom animation.
2615 flyTo: function (targetCenter, targetZoom, options) {
2617 options = options || {};
2618 if (options.animate === false || !L.Browser.any3d) {
2619 return this.setView(targetCenter, targetZoom, options);
2624 var from = this.project(this.getCenter()),
2625 to = this.project(targetCenter),
2626 size = this.getSize(),
2627 startZoom = this._zoom;
2629 targetCenter = L.latLng(targetCenter);
2630 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
2632 var w0 = Math.max(size.x, size.y),
2633 w1 = w0 * this.getZoomScale(startZoom, targetZoom),
2634 u1 = (to.distanceTo(from)) || 1,
2639 var s1 = i ? -1 : 1,
2641 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
2642 b1 = 2 * s2 * rho2 * u1,
2644 sq = Math.sqrt(b * b + 1) - b;
2646 // workaround for floating point precision bug when sq = 0, log = -Infinite,
2647 // thus triggering an infinite loop in flyTo
2648 var log = sq < 0.000000001 ? -18 : Math.log(sq);
2653 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
2654 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
2655 function tanh(n) { return sinh(n) / cosh(n); }
2659 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
2660 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
2662 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
2664 var start = Date.now(),
2665 S = (r(1) - r0) / rho,
2666 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
2669 var t = (Date.now() - start) / duration,
2673 this._flyToFrame = L.Util.requestAnimFrame(frame, this);
2676 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
2677 this.getScaleZoom(w0 / w(s), startZoom),
2682 ._move(targetCenter, targetZoom)
2687 this._moveStart(true);
2693 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
2694 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
2695 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
2696 flyToBounds: function (bounds, options) {
2697 var target = this._getBoundsCenterZoom(bounds, options);
2698 return this.flyTo(target.center, target.zoom, options);
2701 // @method setMaxBounds(bounds: Bounds): this
2702 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
2703 setMaxBounds: function (bounds) {
2704 bounds = L.latLngBounds(bounds);
2706 if (!bounds.isValid()) {
2707 this.options.maxBounds = null;
2708 return this.off('moveend', this._panInsideMaxBounds);
2709 } else if (this.options.maxBounds) {
2710 this.off('moveend', this._panInsideMaxBounds);
2713 this.options.maxBounds = bounds;
2716 this._panInsideMaxBounds();
2719 return this.on('moveend', this._panInsideMaxBounds);
2722 // @method setMinZoom(zoom: Number): this
2723 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
2724 setMinZoom: function (zoom) {
2725 this.options.minZoom = zoom;
2727 if (this._loaded && this.getZoom() < this.options.minZoom) {
2728 return this.setZoom(zoom);
2734 // @method setMaxZoom(zoom: Number): this
2735 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
2736 setMaxZoom: function (zoom) {
2737 this.options.maxZoom = zoom;
2739 if (this._loaded && (this.getZoom() > this.options.maxZoom)) {
2740 return this.setZoom(zoom);
2746 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
2747 // 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.
2748 panInsideBounds: function (bounds, options) {
2749 this._enforcingBounds = true;
2750 var center = this.getCenter(),
2751 newCenter = this._limitCenter(center, this._zoom, L.latLngBounds(bounds));
2753 if (!center.equals(newCenter)) {
2754 this.panTo(newCenter, options);
2757 this._enforcingBounds = false;
2761 // @method invalidateSize(options: Zoom/Pan options): this
2762 // Checks if the map container size changed and updates the map if so —
2763 // call it after you've changed the map size dynamically, also animating
2764 // pan by default. If `options.pan` is `false`, panning will not occur.
2765 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
2766 // that it doesn't happen often even if the method is called many
2770 // @method invalidateSize(animate: Boolean): this
2771 // Checks if the map container size changed and updates the map if so —
2772 // call it after you've changed the map size dynamically, also animating
2774 invalidateSize: function (options) {
2775 if (!this._loaded) { return this; }
2777 options = L.extend({
2780 }, options === true ? {animate: true} : options);
2782 var oldSize = this.getSize();
2783 this._sizeChanged = true;
2784 this._lastCenter = null;
2786 var newSize = this.getSize(),
2787 oldCenter = oldSize.divideBy(2).round(),
2788 newCenter = newSize.divideBy(2).round(),
2789 offset = oldCenter.subtract(newCenter);
2791 if (!offset.x && !offset.y) { return this; }
2793 if (options.animate && options.pan) {
2798 this._rawPanBy(offset);
2803 if (options.debounceMoveend) {
2804 clearTimeout(this._sizeTimer);
2805 this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
2807 this.fire('moveend');
2811 // @section Map state change events
2812 // @event resize: ResizeEvent
2813 // Fired when the map is resized.
2814 return this.fire('resize', {
2820 // @section Methods for modifying map state
2821 // @method stop(): this
2822 // Stops the currently running `panTo` or `flyTo` animation, if any.
2824 this.setZoom(this._limitZoom(this._zoom));
2825 if (!this.options.zoomSnap) {
2826 this.fire('viewreset');
2828 return this._stop();
2831 // @section Geolocation methods
2832 // @method locate(options?: Locate options): this
2833 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
2834 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
2835 // and optionally sets the map view to the user's location with respect to
2836 // detection accuracy (or to the world view if geolocation failed).
2837 // Note that, if your page doesn't use HTTPS, this method will fail in
2838 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
2839 // See `Locate options` for more details.
2840 locate: function (options) {
2842 options = this._locateOptions = L.extend({
2846 // maxZoom: <Number>
2848 // enableHighAccuracy: false
2851 if (!('geolocation' in navigator)) {
2852 this._handleGeolocationError({
2854 message: 'Geolocation not supported.'
2859 var onResponse = L.bind(this._handleGeolocationResponse, this),
2860 onError = L.bind(this._handleGeolocationError, this);
2862 if (options.watch) {
2863 this._locationWatchId =
2864 navigator.geolocation.watchPosition(onResponse, onError, options);
2866 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
2871 // @method stopLocate(): this
2872 // Stops watching location previously initiated by `map.locate({watch: true})`
2873 // and aborts resetting the map view if map.locate was called with
2874 // `{setView: true}`.
2875 stopLocate: function () {
2876 if (navigator.geolocation && navigator.geolocation.clearWatch) {
2877 navigator.geolocation.clearWatch(this._locationWatchId);
2879 if (this._locateOptions) {
2880 this._locateOptions.setView = false;
2885 _handleGeolocationError: function (error) {
2887 message = error.message ||
2888 (c === 1 ? 'permission denied' :
2889 (c === 2 ? 'position unavailable' : 'timeout'));
2891 if (this._locateOptions.setView && !this._loaded) {
2895 // @section Location events
2896 // @event locationerror: ErrorEvent
2897 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
2898 this.fire('locationerror', {
2900 message: 'Geolocation error: ' + message + '.'
2904 _handleGeolocationResponse: function (pos) {
2905 var lat = pos.coords.latitude,
2906 lng = pos.coords.longitude,
2907 latlng = new L.LatLng(lat, lng),
2908 bounds = latlng.toBounds(pos.coords.accuracy),
2909 options = this._locateOptions;
2911 if (options.setView) {
2912 var zoom = this.getBoundsZoom(bounds);
2913 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
2919 timestamp: pos.timestamp
2922 for (var i in pos.coords) {
2923 if (typeof pos.coords[i] === 'number') {
2924 data[i] = pos.coords[i];
2928 // @event locationfound: LocationEvent
2929 // Fired when geolocation (using the [`locate`](#map-locate) method)
2930 // went successfully.
2931 this.fire('locationfound', data);
2934 // TODO handler.addTo
2935 // TODO Appropiate docs section?
2936 // @section Other Methods
2937 // @method addHandler(name: String, HandlerClass: Function): this
2938 // Adds a new `Handler` to the map, given its name and constructor function.
2939 addHandler: function (name, HandlerClass) {
2940 if (!HandlerClass) { return this; }
2942 var handler = this[name] = new HandlerClass(this);
2944 this._handlers.push(handler);
2946 if (this.options[name]) {
2953 // @method remove(): this
2954 // Destroys the map and clears all related event listeners.
2955 remove: function () {
2957 this._initEvents(true);
2959 if (this._containerId !== this._container._leaflet_id) {
2960 throw new Error('Map container is being reused by another instance');
2964 // throws error in IE6-8
2965 delete this._container._leaflet_id;
2966 delete this._containerId;
2969 this._container._leaflet_id = undefined;
2971 this._containerId = undefined;
2974 L.DomUtil.remove(this._mapPane);
2976 if (this._clearControlPos) {
2977 this._clearControlPos();
2980 this._clearHandlers();
2983 // @section Map state change events
2984 // @event unload: Event
2985 // Fired when the map is destroyed with [remove](#map-remove) method.
2986 this.fire('unload');
2989 for (var i in this._layers) {
2990 this._layers[i].remove();
2996 // @section Other Methods
2997 // @method createPane(name: String, container?: HTMLElement): HTMLElement
2998 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
2999 // then returns it. The pane is created as a children of `container`, or
3000 // as a children of the main map pane if not set.
3001 createPane: function (name, container) {
3002 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3003 pane = L.DomUtil.create('div', className, container || this._mapPane);
3006 this._panes[name] = pane;
3011 // @section Methods for Getting Map State
3013 // @method getCenter(): LatLng
3014 // Returns the geographical center of the map view
3015 getCenter: function () {
3016 this._checkIfLoaded();
3018 if (this._lastCenter && !this._moved()) {
3019 return this._lastCenter;
3021 return this.layerPointToLatLng(this._getCenterLayerPoint());
3024 // @method getZoom(): Number
3025 // Returns the current zoom level of the map view
3026 getZoom: function () {
3030 // @method getBounds(): LatLngBounds
3031 // Returns the geographical bounds visible in the current map view
3032 getBounds: function () {
3033 var bounds = this.getPixelBounds(),
3034 sw = this.unproject(bounds.getBottomLeft()),
3035 ne = this.unproject(bounds.getTopRight());
3037 return new L.LatLngBounds(sw, ne);
3040 // @method getMinZoom(): Number
3041 // 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.
3042 getMinZoom: function () {
3043 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3046 // @method getMaxZoom(): Number
3047 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3048 getMaxZoom: function () {
3049 return this.options.maxZoom === undefined ?
3050 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3051 this.options.maxZoom;
3054 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number
3055 // Returns the maximum zoom level on which the given bounds fit to the map
3056 // view in its entirety. If `inside` (optional) is set to `true`, the method
3057 // instead returns the minimum zoom level on which the map view fits into
3058 // the given bounds in its entirety.
3059 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3060 bounds = L.latLngBounds(bounds);
3061 padding = L.point(padding || [0, 0]);
3063 var zoom = this.getZoom() || 0,
3064 min = this.getMinZoom(),
3065 max = this.getMaxZoom(),
3066 nw = bounds.getNorthWest(),
3067 se = bounds.getSouthEast(),
3068 size = this.getSize().subtract(padding),
3069 boundsSize = L.bounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3070 snap = L.Browser.any3d ? this.options.zoomSnap : 1;
3072 var scale = Math.min(size.x / boundsSize.x, size.y / boundsSize.y);
3073 zoom = this.getScaleZoom(scale, zoom);
3076 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3077 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3080 return Math.max(min, Math.min(max, zoom));
3083 // @method getSize(): Point
3084 // Returns the current size of the map container (in pixels).
3085 getSize: function () {
3086 if (!this._size || this._sizeChanged) {
3087 this._size = new L.Point(
3088 this._container.clientWidth || 0,
3089 this._container.clientHeight || 0);
3091 this._sizeChanged = false;
3093 return this._size.clone();
3096 // @method getPixelBounds(): Bounds
3097 // Returns the bounds of the current map view in projected pixel
3098 // coordinates (sometimes useful in layer and overlay implementations).
3099 getPixelBounds: function (center, zoom) {
3100 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3101 return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3104 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3105 // the map pane? "left point of the map layer" can be confusing, specially
3106 // since there can be negative offsets.
3107 // @method getPixelOrigin(): Point
3108 // Returns the projected pixel coordinates of the top left point of
3109 // the map layer (useful in custom layer and overlay implementations).
3110 getPixelOrigin: function () {
3111 this._checkIfLoaded();
3112 return this._pixelOrigin;
3115 // @method getPixelWorldBounds(zoom?: Number): Bounds
3116 // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3117 // If `zoom` is omitted, the map's current zoom level is used.
3118 getPixelWorldBounds: function (zoom) {
3119 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3122 // @section Other Methods
3124 // @method getPane(pane: String|HTMLElement): HTMLElement
3125 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3126 getPane: function (pane) {
3127 return typeof pane === 'string' ? this._panes[pane] : pane;
3130 // @method getPanes(): Object
3131 // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3132 // the panes as values.
3133 getPanes: function () {
3137 // @method getContainer: HTMLElement
3138 // Returns the HTML element that contains the map.
3139 getContainer: function () {
3140 return this._container;
3144 // @section Conversion Methods
3146 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3147 // Returns the scale factor to be applied to a map transition from zoom level
3148 // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3149 getZoomScale: function (toZoom, fromZoom) {
3150 // TODO replace with universal implementation after refactoring projections
3151 var crs = this.options.crs;
3152 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3153 return crs.scale(toZoom) / crs.scale(fromZoom);
3156 // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3157 // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3158 // level and everything is scaled by a factor of `scale`. Inverse of
3159 // [`getZoomScale`](#map-getZoomScale).
3160 getScaleZoom: function (scale, fromZoom) {
3161 var crs = this.options.crs;
3162 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3163 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3164 return isNaN(zoom) ? Infinity : zoom;
3167 // @method project(latlng: LatLng, zoom: Number): Point
3168 // Projects a geographical coordinate `LatLng` according to the projection
3169 // of the map's CRS, then scales it according to `zoom` and the CRS's
3170 // `Transformation`. The result is pixel coordinate relative to
3172 project: function (latlng, zoom) {
3173 zoom = zoom === undefined ? this._zoom : zoom;
3174 return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
3177 // @method unproject(point: Point, zoom: Number): LatLng
3178 // Inverse of [`project`](#map-project).
3179 unproject: function (point, zoom) {
3180 zoom = zoom === undefined ? this._zoom : zoom;
3181 return this.options.crs.pointToLatLng(L.point(point), zoom);
3184 // @method layerPointToLatLng(point: Point): LatLng
3185 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3186 // returns the corresponding geographical coordinate (for the current zoom level).
3187 layerPointToLatLng: function (point) {
3188 var projectedPoint = L.point(point).add(this.getPixelOrigin());
3189 return this.unproject(projectedPoint);
3192 // @method latLngToLayerPoint(latlng: LatLng): Point
3193 // Given a geographical coordinate, returns the corresponding pixel coordinate
3194 // relative to the [origin pixel](#map-getpixelorigin).
3195 latLngToLayerPoint: function (latlng) {
3196 var projectedPoint = this.project(L.latLng(latlng))._round();
3197 return projectedPoint._subtract(this.getPixelOrigin());
3200 // @method wrapLatLng(latlng: LatLng): LatLng
3201 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
3202 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
3204 // By default this means longitude is wrapped around the dateline so its
3205 // value is between -180 and +180 degrees.
3206 wrapLatLng: function (latlng) {
3207 return this.options.crs.wrapLatLng(L.latLng(latlng));
3210 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
3211 // Returns a `LatLngBounds` with the same size as the given one, ensuring that
3212 // its center is within the CRS's bounds.
3213 // By default this means the center longitude is wrapped around the dateline so its
3214 // value is between -180 and +180 degrees, and the majority of the bounds
3215 // overlaps the CRS's bounds.
3216 wrapLatLngBounds: function (latlng) {
3217 return this.options.crs.wrapLatLngBounds(L.latLngBounds(latlng));
3220 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
3221 // Returns the distance between two geographical coordinates according to
3222 // the map's CRS. By default this measures distance in meters.
3223 distance: function (latlng1, latlng2) {
3224 return this.options.crs.distance(L.latLng(latlng1), L.latLng(latlng2));
3227 // @method containerPointToLayerPoint(point: Point): Point
3228 // Given a pixel coordinate relative to the map container, returns the corresponding
3229 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
3230 containerPointToLayerPoint: function (point) { // (Point)
3231 return L.point(point).subtract(this._getMapPanePos());
3234 // @method layerPointToContainerPoint(point: Point): Point
3235 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3236 // returns the corresponding pixel coordinate relative to the map container.
3237 layerPointToContainerPoint: function (point) { // (Point)
3238 return L.point(point).add(this._getMapPanePos());
3241 // @method containerPointToLatLng(point: Point): LatLng
3242 // Given a pixel coordinate relative to the map container, returns
3243 // the corresponding geographical coordinate (for the current zoom level).
3244 containerPointToLatLng: function (point) {
3245 var layerPoint = this.containerPointToLayerPoint(L.point(point));
3246 return this.layerPointToLatLng(layerPoint);
3249 // @method latLngToContainerPoint(latlng: LatLng): Point
3250 // Given a geographical coordinate, returns the corresponding pixel coordinate
3251 // relative to the map container.
3252 latLngToContainerPoint: function (latlng) {
3253 return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
3256 // @method mouseEventToContainerPoint(ev: MouseEvent): Point
3257 // Given a MouseEvent object, returns the pixel coordinate relative to the
3258 // map container where the event took place.
3259 mouseEventToContainerPoint: function (e) {
3260 return L.DomEvent.getMousePosition(e, this._container);
3263 // @method mouseEventToLayerPoint(ev: MouseEvent): Point
3264 // Given a MouseEvent object, returns the pixel coordinate relative to
3265 // the [origin pixel](#map-getpixelorigin) where the event took place.
3266 mouseEventToLayerPoint: function (e) {
3267 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
3270 // @method mouseEventToLatLng(ev: MouseEvent): LatLng
3271 // Given a MouseEvent object, returns geographical coordinate where the
3272 // event took place.
3273 mouseEventToLatLng: function (e) { // (MouseEvent)
3274 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
3278 // map initialization methods
3280 _initContainer: function (id) {
3281 var container = this._container = L.DomUtil.get(id);
3284 throw new Error('Map container not found.');
3285 } else if (container._leaflet_id) {
3286 throw new Error('Map container is already initialized.');
3289 L.DomEvent.addListener(container, 'scroll', this._onScroll, this);
3290 this._containerId = L.Util.stamp(container);
3293 _initLayout: function () {
3294 var container = this._container;
3296 this._fadeAnimated = this.options.fadeAnimation && L.Browser.any3d;
3298 L.DomUtil.addClass(container, 'leaflet-container' +
3299 (L.Browser.touch ? ' leaflet-touch' : '') +
3300 (L.Browser.retina ? ' leaflet-retina' : '') +
3301 (L.Browser.ielt9 ? ' leaflet-oldie' : '') +
3302 (L.Browser.safari ? ' leaflet-safari' : '') +
3303 (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
3305 var position = L.DomUtil.getStyle(container, 'position');
3307 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
3308 container.style.position = 'relative';
3313 if (this._initControlPos) {
3314 this._initControlPos();
3318 _initPanes: function () {
3319 var panes = this._panes = {};
3320 this._paneRenderers = {};
3324 // Panes are DOM elements used to control the ordering of layers on the map. You
3325 // can access panes with [`map.getPane`](#map-getpane) or
3326 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
3327 // [`map.createPane`](#map-createpane) method.
3329 // Every map has the following default panes that differ only in zIndex.
3331 // @pane mapPane: HTMLElement = 'auto'
3332 // Pane that contains all other map panes
3334 this._mapPane = this.createPane('mapPane', this._container);
3335 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
3337 // @pane tilePane: HTMLElement = 200
3338 // Pane for `GridLayer`s and `TileLayer`s
3339 this.createPane('tilePane');
3340 // @pane overlayPane: HTMLElement = 400
3341 // Pane for vector overlays (`Path`s), like `Polyline`s and `Polygon`s
3342 this.createPane('shadowPane');
3343 // @pane shadowPane: HTMLElement = 500
3344 // Pane for overlay shadows (e.g. `Marker` shadows)
3345 this.createPane('overlayPane');
3346 // @pane markerPane: HTMLElement = 600
3347 // Pane for `Icon`s of `Marker`s
3348 this.createPane('markerPane');
3349 // @pane tooltipPane: HTMLElement = 650
3350 // Pane for tooltip.
3351 this.createPane('tooltipPane');
3352 // @pane popupPane: HTMLElement = 700
3353 // Pane for `Popup`s.
3354 this.createPane('popupPane');
3356 if (!this.options.markerZoomAnimation) {
3357 L.DomUtil.addClass(panes.markerPane, 'leaflet-zoom-hide');
3358 L.DomUtil.addClass(panes.shadowPane, 'leaflet-zoom-hide');
3363 // private methods that modify map state
3365 // @section Map state change events
3366 _resetView: function (center, zoom) {
3367 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
3369 var loading = !this._loaded;
3370 this._loaded = true;
3371 zoom = this._limitZoom(zoom);
3373 this.fire('viewprereset');
3375 var zoomChanged = this._zoom !== zoom;
3377 ._moveStart(zoomChanged)
3378 ._move(center, zoom)
3379 ._moveEnd(zoomChanged);
3381 // @event viewreset: Event
3382 // Fired when the map needs to redraw its content (this usually happens
3383 // on map zoom or load). Very useful for creating custom overlays.
3384 this.fire('viewreset');
3386 // @event load: Event
3387 // Fired when the map is initialized (when its center and zoom are set
3388 // for the first time).
3394 _moveStart: function (zoomChanged) {
3395 // @event zoomstart: Event
3396 // Fired when the map zoom is about to change (e.g. before zoom animation).
3397 // @event movestart: Event
3398 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
3400 this.fire('zoomstart');
3402 return this.fire('movestart');
3405 _move: function (center, zoom, data) {
3406 if (zoom === undefined) {
3409 var zoomChanged = this._zoom !== zoom;
3412 this._lastCenter = center;
3413 this._pixelOrigin = this._getNewPixelOrigin(center);
3415 // @event zoom: Event
3416 // Fired repeatedly during any change in zoom level, including zoom
3417 // and fly animations.
3418 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530
3419 this.fire('zoom', data);
3422 // @event move: Event
3423 // Fired repeatedly during any movement of the map, including pan and
3425 return this.fire('move', data);
3428 _moveEnd: function (zoomChanged) {
3429 // @event zoomend: Event
3430 // Fired when the map has changed, after any animations.
3432 this.fire('zoomend');
3435 // @event moveend: Event
3436 // Fired when the center of the map stops changing (e.g. user stopped
3437 // dragging the map).
3438 return this.fire('moveend');
3441 _stop: function () {
3442 L.Util.cancelAnimFrame(this._flyToFrame);
3443 if (this._panAnim) {
3444 this._panAnim.stop();
3449 _rawPanBy: function (offset) {
3450 L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
3453 _getZoomSpan: function () {
3454 return this.getMaxZoom() - this.getMinZoom();
3457 _panInsideMaxBounds: function () {
3458 if (!this._enforcingBounds) {
3459 this.panInsideBounds(this.options.maxBounds);
3463 _checkIfLoaded: function () {
3464 if (!this._loaded) {
3465 throw new Error('Set map center and zoom first.');
3469 // DOM event handling
3471 // @section Interaction events
3472 _initEvents: function (remove) {
3473 if (!L.DomEvent) { return; }
3476 this._targets[L.stamp(this._container)] = this;
3478 var onOff = remove ? 'off' : 'on';
3480 // @event click: MouseEvent
3481 // Fired when the user clicks (or taps) the map.
3482 // @event dblclick: MouseEvent
3483 // Fired when the user double-clicks (or double-taps) the map.
3484 // @event mousedown: MouseEvent
3485 // Fired when the user pushes the mouse button on the map.
3486 // @event mouseup: MouseEvent
3487 // Fired when the user releases the mouse button on the map.
3488 // @event mouseover: MouseEvent
3489 // Fired when the mouse enters the map.
3490 // @event mouseout: MouseEvent
3491 // Fired when the mouse leaves the map.
3492 // @event mousemove: MouseEvent
3493 // Fired while the mouse moves over the map.
3494 // @event contextmenu: MouseEvent
3495 // Fired when the user pushes the right mouse button on the map, prevents
3496 // default browser context menu from showing if there are listeners on
3497 // this event. Also fired on mobile when the user holds a single touch
3498 // for a second (also called long press).
3499 // @event keypress: KeyboardEvent
3500 // Fired when the user presses a key from the keyboard while the map is focused.
3501 L.DomEvent[onOff](this._container, 'click dblclick mousedown mouseup ' +
3502 'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);
3504 if (this.options.trackResize) {
3505 L.DomEvent[onOff](window, 'resize', this._onResize, this);
3508 if (L.Browser.any3d && this.options.transform3DLimit) {
3509 this[onOff]('moveend', this._onMoveEnd);
3513 _onResize: function () {
3514 L.Util.cancelAnimFrame(this._resizeRequest);
3515 this._resizeRequest = L.Util.requestAnimFrame(
3516 function () { this.invalidateSize({debounceMoveend: true}); }, this);
3519 _onScroll: function () {
3520 this._container.scrollTop = 0;
3521 this._container.scrollLeft = 0;
3524 _onMoveEnd: function () {
3525 var pos = this._getMapPanePos();
3526 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
3527 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
3528 // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
3529 this._resetView(this.getCenter(), this.getZoom());
3533 _findEventTargets: function (e, type) {
3536 isHover = type === 'mouseout' || type === 'mouseover',
3537 src = e.target || e.srcElement,
3541 target = this._targets[L.stamp(src)];
3542 if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
3543 // Prevent firing click after you just dragged an object.
3547 if (target && target.listens(type, true)) {
3548 if (isHover && !L.DomEvent._isExternalTarget(src, e)) { break; }
3549 targets.push(target);
3550 if (isHover) { break; }
3552 if (src === this._container) { break; }
3553 src = src.parentNode;
3555 if (!targets.length && !dragging && !isHover && L.DomEvent._isExternalTarget(src, e)) {
3561 _handleDOMEvent: function (e) {
3562 if (!this._loaded || L.DomEvent._skipped(e)) { return; }
3564 var type = e.type === 'keypress' && e.keyCode === 13 ? 'click' : e.type;
3566 if (type === 'mousedown') {
3567 // prevents outline when clicking on keyboard-focusable element
3568 L.DomUtil.preventOutline(e.target || e.srcElement);
3571 this._fireDOMEvent(e, type);
3574 _fireDOMEvent: function (e, type, targets) {
3576 if (e.type === 'click') {
3577 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
3578 // @event preclick: MouseEvent
3579 // Fired before mouse click on the map (sometimes useful when you
3580 // want something to happen on click before any existing click
3581 // handlers start running).
3582 var synth = L.Util.extend({}, e);
3583 synth.type = 'preclick';
3584 this._fireDOMEvent(synth, synth.type, targets);
3587 if (e._stopped) { return; }
3589 // Find the layer the event is propagating from and its parents.
3590 targets = (targets || []).concat(this._findEventTargets(e, type));
3592 if (!targets.length) { return; }
3594 var target = targets[0];
3595 if (type === 'contextmenu' && target.listens(type, true)) {
3596 L.DomEvent.preventDefault(e);
3603 if (e.type !== 'keypress') {
3604 var isMarker = target instanceof L.Marker;
3605 data.containerPoint = isMarker ?
3606 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
3607 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
3608 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
3611 for (var i = 0; i < targets.length; i++) {
3612 targets[i].fire(type, data, true);
3613 if (data.originalEvent._stopped ||
3614 (targets[i].options.nonBubblingEvents && L.Util.indexOf(targets[i].options.nonBubblingEvents, type) !== -1)) { return; }
3618 _draggableMoved: function (obj) {
3619 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
3620 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
3623 _clearHandlers: function () {
3624 for (var i = 0, len = this._handlers.length; i < len; i++) {
3625 this._handlers[i].disable();
3629 // @section Other Methods
3631 // @method whenReady(fn: Function, context?: Object): this
3632 // Runs the given function `fn` when the map gets initialized with
3633 // a view (center and zoom) and at least one layer, or immediately
3634 // if it's already initialized, optionally passing a function context.
3635 whenReady: function (callback, context) {
3637 callback.call(context || this, {target: this});
3639 this.on('load', callback, context);
3645 // private methods for getting map state
3647 _getMapPanePos: function () {
3648 return L.DomUtil.getPosition(this._mapPane) || new L.Point(0, 0);
3651 _moved: function () {
3652 var pos = this._getMapPanePos();
3653 return pos && !pos.equals([0, 0]);
3656 _getTopLeftPoint: function (center, zoom) {
3657 var pixelOrigin = center && zoom !== undefined ?
3658 this._getNewPixelOrigin(center, zoom) :
3659 this.getPixelOrigin();
3660 return pixelOrigin.subtract(this._getMapPanePos());
3663 _getNewPixelOrigin: function (center, zoom) {
3664 var viewHalf = this.getSize()._divideBy(2);
3665 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
3668 _latLngToNewLayerPoint: function (latlng, zoom, center) {
3669 var topLeft = this._getNewPixelOrigin(center, zoom);
3670 return this.project(latlng, zoom)._subtract(topLeft);
3673 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
3674 var topLeft = this._getNewPixelOrigin(center, zoom);
3676 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
3677 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
3678 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
3679 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
3683 // layer point of the current center
3684 _getCenterLayerPoint: function () {
3685 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
3688 // offset of the specified place to the current center in pixels
3689 _getCenterOffset: function (latlng) {
3690 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
3693 // adjust center for view to get inside bounds
3694 _limitCenter: function (center, zoom, bounds) {
3696 if (!bounds) { return center; }
3698 var centerPoint = this.project(center, zoom),
3699 viewHalf = this.getSize().divideBy(2),
3700 viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
3701 offset = this._getBoundsOffset(viewBounds, bounds, zoom);
3703 // If offset is less than a pixel, ignore.
3704 // This prevents unstable projections from getting into
3705 // an infinite loop of tiny offsets.
3706 if (offset.round().equals([0, 0])) {
3710 return this.unproject(centerPoint.add(offset), zoom);
3713 // adjust offset for view to get inside bounds
3714 _limitOffset: function (offset, bounds) {
3715 if (!bounds) { return offset; }
3717 var viewBounds = this.getPixelBounds(),
3718 newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
3720 return offset.add(this._getBoundsOffset(newBounds, bounds));
3723 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
3724 _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
3725 var projectedMaxBounds = L.bounds(
3726 this.project(maxBounds.getNorthEast(), zoom),
3727 this.project(maxBounds.getSouthWest(), zoom)
3729 minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
3730 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
3732 dx = this._rebound(minOffset.x, -maxOffset.x),
3733 dy = this._rebound(minOffset.y, -maxOffset.y);
3735 return new L.Point(dx, dy);
3738 _rebound: function (left, right) {
3739 return left + right > 0 ?
3740 Math.round(left - right) / 2 :
3741 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
3744 _limitZoom: function (zoom) {
3745 var min = this.getMinZoom(),
3746 max = this.getMaxZoom(),
3747 snap = L.Browser.any3d ? this.options.zoomSnap : 1;
3749 zoom = Math.round(zoom / snap) * snap;
3751 return Math.max(min, Math.min(max, zoom));
3754 _onPanTransitionStep: function () {
3758 _onPanTransitionEnd: function () {
3759 L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
3760 this.fire('moveend');
3763 _tryAnimatedPan: function (center, options) {
3764 // difference between the new and current centers in pixels
3765 var offset = this._getCenterOffset(center)._floor();
3767 // don't animate too far unless animate: true specified in options
3768 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
3770 this.panBy(offset, options);
3775 _createAnimProxy: function () {
3777 var proxy = this._proxy = L.DomUtil.create('div', 'leaflet-proxy leaflet-zoom-animated');
3778 this._panes.mapPane.appendChild(proxy);
3780 this.on('zoomanim', function (e) {
3781 var prop = L.DomUtil.TRANSFORM,
3782 transform = proxy.style[prop];
3784 L.DomUtil.setTransform(proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
3786 // workaround for case when transform is the same and so transitionend event is not fired
3787 if (transform === proxy.style[prop] && this._animatingZoom) {
3788 this._onZoomTransitionEnd();
3792 this.on('load moveend', function () {
3793 var c = this.getCenter(),
3795 L.DomUtil.setTransform(proxy, this.project(c, z), this.getZoomScale(z, 1));
3799 _catchTransitionEnd: function (e) {
3800 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
3801 this._onZoomTransitionEnd();
3805 _nothingToAnimate: function () {
3806 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
3809 _tryAnimatedZoom: function (center, zoom, options) {
3811 if (this._animatingZoom) { return true; }
3813 options = options || {};
3815 // don't animate if disabled, not supported or zoom difference is too large
3816 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
3817 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }