2 Leaflet 1.0.0, 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);
519 typeListeners.count++;
522 _off: function (type, fn, context) {
527 if (!this._events) { return; }
529 listeners = this._events[type];
536 // Set all removed listeners to noop so they are not called if remove happens in fire
537 for (i = 0, len = listeners.length; i < len; i++) {
538 listeners[i].fn = L.Util.falseFn;
540 // clear all listeners for a type if function isn't specified
541 delete this._events[type];
545 if (context === this) {
551 // find fn and remove it
552 for (i = 0, len = listeners.length; i < len; i++) {
553 var l = listeners[i];
554 if (l.ctx !== context) { continue; }
557 // set the removed listener to noop so that's not called if remove happens in fire
558 l.fn = L.Util.falseFn;
560 if (this._firingCount) {
561 /* copy array in case events are being fired */
562 this._events[type] = listeners = listeners.slice();
564 listeners.splice(i, 1);
572 // @method fire(type: String, data?: Object, propagate?: Boolean): this
573 // Fires an event of the specified type. You can optionally provide an data
574 // object — the first argument of the listener function will contain its
575 // properties. The event might can optionally be propagated to event parents.
576 fire: function (type, data, propagate) {
577 if (!this.listens(type, propagate)) { return this; }
579 var event = L.Util.extend({}, data, {type: type, target: this});
582 var listeners = this._events[type];
585 this._firingCount = (this._firingCount + 1) || 1;
586 for (var i = 0, len = listeners.length; i < len; i++) {
587 var l = listeners[i];
588 l.fn.call(l.ctx || this, event);
596 // propagate the event to parents (set with addEventParent)
597 this._propagateEvent(event);
603 // @method listens(type: String): Boolean
604 // Returns `true` if a particular event type has any listeners attached to it.
605 listens: function (type, propagate) {
606 var listeners = this._events && this._events[type];
607 if (listeners && listeners.length) { return true; }
610 // also check parents for listeners if event propagates
611 for (var id in this._eventParents) {
612 if (this._eventParents[id].listens(type, propagate)) { return true; }
618 // @method once(…): this
619 // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
620 once: function (types, fn, context) {
622 if (typeof types === 'object') {
623 for (var type in types) {
624 this.once(type, types[type], fn);
629 var handler = L.bind(function () {
631 .off(types, fn, context)
632 .off(types, handler, context);
635 // add a listener that's executed once and removed after that
637 .on(types, fn, context)
638 .on(types, handler, context);
641 // @method addEventParent(obj: Evented): this
642 // Adds an event parent - an `Evented` that will receive propagated events
643 addEventParent: function (obj) {
644 this._eventParents = this._eventParents || {};
645 this._eventParents[L.stamp(obj)] = obj;
649 // @method removeEventParent(obj: Evented): this
650 // Removes an event parent, so it will stop receiving propagated events
651 removeEventParent: function (obj) {
652 if (this._eventParents) {
653 delete this._eventParents[L.stamp(obj)];
658 _propagateEvent: function (e) {
659 for (var id in this._eventParents) {
660 this._eventParents[id].fire(e.type, L.extend({layer: e.target}, e), true);
665 var proto = L.Evented.prototype;
667 // aliases; we should ditch those eventually
669 // @method addEventListener(…): this
670 // Alias to [`on(…)`](#evented-on)
671 proto.addEventListener = proto.on;
673 // @method removeEventListener(…): this
674 // Alias to [`off(…)`](#evented-off)
676 // @method clearAllEventListeners(…): this
677 // Alias to [`off()`](#evented-off)
678 proto.removeEventListener = proto.clearAllEventListeners = proto.off;
680 // @method addOneTimeEventListener(…): this
681 // Alias to [`once(…)`](#evented-once)
682 proto.addOneTimeEventListener = proto.once;
684 // @method fireEvent(…): this
685 // Alias to [`fire(…)`](#evented-fire)
686 proto.fireEvent = proto.fire;
688 // @method hasEventListeners(…): Boolean
689 // Alias to [`listens(…)`](#evented-listens)
690 proto.hasEventListeners = proto.listens;
692 L.Mixin = {Events: proto};
700 * A namespace with static properties for browser/feature detection used by Leaflet internally.
705 * if (L.Browser.ielt9) {
706 * alert('Upgrade your browser, dude!');
713 var ua = navigator.userAgent.toLowerCase(),
714 doc = document.documentElement,
716 ie = 'ActiveXObject' in window,
718 webkit = ua.indexOf('webkit') !== -1,
719 phantomjs = ua.indexOf('phantom') !== -1,
720 android23 = ua.search('android [23]') !== -1,
721 chrome = ua.indexOf('chrome') !== -1,
722 gecko = ua.indexOf('gecko') !== -1 && !webkit && !window.opera && !ie,
724 win = navigator.platform.indexOf('Win') === 0,
726 mobile = typeof orientation !== 'undefined' || ua.indexOf('mobile') !== -1,
727 msPointer = !window.PointerEvent && window.MSPointerEvent,
728 pointer = window.PointerEvent || msPointer,
730 ie3d = ie && ('transition' in doc.style),
731 webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23,
732 gecko3d = 'MozPerspective' in doc.style,
733 opera12 = 'OTransition' in doc.style;
736 var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
737 (window.DocumentTouch && document instanceof window.DocumentTouch));
741 // @property ie: Boolean
742 // `true` for all Internet Explorer versions (not Edge).
745 // @property ielt9: Boolean
746 // `true` for Internet Explorer versions less than 9.
747 ielt9: ie && !document.addEventListener,
749 // @property edge: Boolean
750 // `true` for the Edge web browser.
751 edge: 'msLaunchUri' in navigator && !('documentMode' in document),
753 // @property webkit: Boolean
754 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
757 // @property gecko: Boolean
758 // `true` for gecko-based browsers like Firefox.
761 // @property android: Boolean
762 // `true` for any browser running on an Android platform.
763 android: ua.indexOf('android') !== -1,
765 // @property android23: Boolean
766 // `true` for browsers running on Android 2 or Android 3.
767 android23: android23,
769 // @property chrome: Boolean
770 // `true` for the Chrome browser.
773 // @property safari: Boolean
774 // `true` for the Safari browser.
775 safari: !chrome && ua.indexOf('safari') !== -1,
778 // @property win: Boolean
779 // `true` when the browser is running in a Windows platform
783 // @property ie3d: Boolean
784 // `true` for all Internet Explorer versions supporting CSS transforms.
787 // @property webkit3d: Boolean
788 // `true` for webkit-based browsers supporting CSS transforms.
791 // @property gecko3d: Boolean
792 // `true` for gecko-based browsers supporting CSS transforms.
795 // @property opera12: Boolean
796 // `true` for the Opera browser supporting CSS transforms (version 12 or later).
799 // @property any3d: Boolean
800 // `true` for all browsers supporting CSS transforms.
801 any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantomjs,
804 // @property mobile: Boolean
805 // `true` for all browsers running in a mobile device.
808 // @property mobileWebkit: Boolean
809 // `true` for all webkit-based browsers in a mobile device.
810 mobileWebkit: mobile && webkit,
812 // @property mobileWebkit3d: Boolean
813 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
814 mobileWebkit3d: mobile && webkit3d,
816 // @property mobileOpera: Boolean
817 // `true` for the Opera browser in a mobile device.
818 mobileOpera: mobile && window.opera,
820 // @property mobileGecko: Boolean
821 // `true` for gecko-based browsers running in a mobile device.
822 mobileGecko: mobile && gecko,
825 // @property touch: Boolean
826 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
829 // @property msPointer: Boolean
830 // `true` for browsers implementing the Microsoft touch events model (notably IE10).
831 msPointer: !!msPointer,
833 // @property pointer: Boolean
834 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
838 // @property retina: Boolean
839 // `true` for browsers on a high-resolution "retina" screen.
840 retina: (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1
851 * Represents a point with `x` and `y` coordinates in pixels.
856 * var point = L.point(200, 300);
859 * 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:
862 * map.panBy([200, 300]);
863 * map.panBy(L.point(200, 300));
867 L.Point = function (x, y, round) {
868 this.x = (round ? Math.round(x) : x);
869 this.y = (round ? Math.round(y) : y);
872 L.Point.prototype = {
874 // @method clone(): Point
875 // Returns a copy of the current point.
877 return new L.Point(this.x, this.y);
880 // @method add(otherPoint: Point): Point
881 // Returns the result of addition of the current and the given points.
882 add: function (point) {
883 // non-destructive, returns a new point
884 return this.clone()._add(L.point(point));
887 _add: function (point) {
888 // destructive, used directly for performance in situations where it's safe to modify existing point
894 // @method subtract(otherPoint: Point): Point
895 // Returns the result of subtraction of the given point from the current.
896 subtract: function (point) {
897 return this.clone()._subtract(L.point(point));
900 _subtract: function (point) {
906 // @method divideBy(num: Number): Point
907 // Returns the result of division of the current point by the given number.
908 divideBy: function (num) {
909 return this.clone()._divideBy(num);
912 _divideBy: function (num) {
918 // @method multiplyBy(num: Number): Point
919 // Returns the result of multiplication of the current point by the given number.
920 multiplyBy: function (num) {
921 return this.clone()._multiplyBy(num);
924 _multiplyBy: function (num) {
930 // @method scaleBy(scale: Point): Point
931 // Multiply each coordinate of the current point by each coordinate of
932 // `scale`. In linear algebra terms, multiply the point by the
933 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
934 // defined by `scale`.
935 scaleBy: function (point) {
936 return new L.Point(this.x * point.x, this.y * point.y);
939 // @method unscaleBy(scale: Point): Point
940 // Inverse of `scaleBy`. Divide each coordinate of the current point by
941 // each coordinate of `scale`.
942 unscaleBy: function (point) {
943 return new L.Point(this.x / point.x, this.y / point.y);
946 // @method round(): Point
947 // Returns a copy of the current point with rounded coordinates.
949 return this.clone()._round();
952 _round: function () {
953 this.x = Math.round(this.x);
954 this.y = Math.round(this.y);
958 // @method floor(): Point
959 // Returns a copy of the current point with floored coordinates (rounded down).
961 return this.clone()._floor();
964 _floor: function () {
965 this.x = Math.floor(this.x);
966 this.y = Math.floor(this.y);
970 // @method ceil(): Point
971 // Returns a copy of the current point with ceiled coordinates (rounded up).
973 return this.clone()._ceil();
977 this.x = Math.ceil(this.x);
978 this.y = Math.ceil(this.y);
982 // @method distanceTo(otherPoint: Point): Number
983 // Returns the cartesian distance between the current and the given points.
984 distanceTo: function (point) {
985 point = L.point(point);
987 var x = point.x - this.x,
988 y = point.y - this.y;
990 return Math.sqrt(x * x + y * y);
993 // @method equals(otherPoint: Point): Boolean
994 // Returns `true` if the given point has the same coordinates.
995 equals: function (point) {
996 point = L.point(point);
998 return point.x === this.x &&
1002 // @method contains(otherPoint: Point): Boolean
1003 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
1004 contains: function (point) {
1005 point = L.point(point);
1007 return Math.abs(point.x) <= Math.abs(this.x) &&
1008 Math.abs(point.y) <= Math.abs(this.y);
1011 // @method toString(): String
1012 // Returns a string representation of the point for debugging purposes.
1013 toString: function () {
1015 L.Util.formatNum(this.x) + ', ' +
1016 L.Util.formatNum(this.y) + ')';
1020 // @factory L.point(x: Number, y: Number, round?: Boolean)
1021 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
1024 // @factory L.point(coords: Number[])
1025 // Expects an array of the form `[x, y]` instead.
1028 // @factory L.point(coords: Object)
1029 // Expects a plain object of the form `{x: Number, y: Number}` instead.
1030 L.point = function (x, y, round) {
1031 if (x instanceof L.Point) {
1034 if (L.Util.isArray(x)) {
1035 return new L.Point(x[0], x[1]);
1037 if (x === undefined || x === null) {
1040 if (typeof x === 'object' && 'x' in x && 'y' in x) {
1041 return new L.Point(x.x, x.y);
1043 return new L.Point(x, y, round);
1052 * Represents a rectangular area in pixel coordinates.
1057 * var p1 = L.point(10, 10),
1058 * p2 = L.point(40, 60),
1059 * bounds = L.bounds(p1, p2);
1062 * 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:
1065 * otherBounds.intersects([[10, 10], [40, 60]]);
1069 L.Bounds = function (a, b) {
1072 var points = b ? [a, b] : a;
1074 for (var i = 0, len = points.length; i < len; i++) {
1075 this.extend(points[i]);
1079 L.Bounds.prototype = {
1080 // @method extend(point: Point): this
1081 // Extends the bounds to contain the given point.
1082 extend: function (point) { // (Point)
1083 point = L.point(point);
1085 // @property min: Point
1086 // The top left corner of the rectangle.
1087 // @property max: Point
1088 // The bottom right corner of the rectangle.
1089 if (!this.min && !this.max) {
1090 this.min = point.clone();
1091 this.max = point.clone();
1093 this.min.x = Math.min(point.x, this.min.x);
1094 this.max.x = Math.max(point.x, this.max.x);
1095 this.min.y = Math.min(point.y, this.min.y);
1096 this.max.y = Math.max(point.y, this.max.y);
1101 // @method getCenter(round?: Boolean): Point
1102 // Returns the center point of the bounds.
1103 getCenter: function (round) {
1105 (this.min.x + this.max.x) / 2,
1106 (this.min.y + this.max.y) / 2, round);
1109 // @method getBottomLeft(): Point
1110 // Returns the bottom-left point of the bounds.
1111 getBottomLeft: function () {
1112 return new L.Point(this.min.x, this.max.y);
1115 // @method getTopRight(): Point
1116 // Returns the top-right point of the bounds.
1117 getTopRight: function () { // -> Point
1118 return new L.Point(this.max.x, this.min.y);
1121 // @method getSize(): Point
1122 // Returns the size of the given bounds
1123 getSize: function () {
1124 return this.max.subtract(this.min);
1127 // @method contains(otherBounds: Bounds): Boolean
1128 // Returns `true` if the rectangle contains the given one.
1130 // @method contains(point: Point): Boolean
1131 // Returns `true` if the rectangle contains the given point.
1132 contains: function (obj) {
1135 if (typeof obj[0] === 'number' || obj instanceof L.Point) {
1138 obj = L.bounds(obj);
1141 if (obj instanceof L.Bounds) {
1148 return (min.x >= this.min.x) &&
1149 (max.x <= this.max.x) &&
1150 (min.y >= this.min.y) &&
1151 (max.y <= this.max.y);
1154 // @method intersects(otherBounds: Bounds): Boolean
1155 // Returns `true` if the rectangle intersects the given bounds. Two bounds
1156 // intersect if they have at least one point in common.
1157 intersects: function (bounds) { // (Bounds) -> Boolean
1158 bounds = L.bounds(bounds);
1164 xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1165 yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1167 return xIntersects && yIntersects;
1170 // @method overlaps(otherBounds: Bounds): Boolean
1171 // Returns `true` if the rectangle overlaps the given bounds. Two bounds
1172 // overlap if their intersection is an area.
1173 overlaps: function (bounds) { // (Bounds) -> Boolean
1174 bounds = L.bounds(bounds);
1180 xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1181 yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1183 return xOverlaps && yOverlaps;
1186 isValid: function () {
1187 return !!(this.min && this.max);
1192 // @factory L.bounds(topLeft: Point, bottomRight: Point)
1193 // Creates a Bounds object from two coordinates (usually top-left and bottom-right corners).
1195 // @factory L.bounds(points: Point[])
1196 // Creates a Bounds object from the points it contains
1197 L.bounds = function (a, b) {
1198 if (!a || a instanceof L.Bounds) {
1201 return new L.Bounds(a, b);
1207 * @class Transformation
1208 * @aka L.Transformation
1210 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1211 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1212 * the reverse. Used by Leaflet in its projections code.
1217 * var transformation = new L.Transformation(2, 5, -1, 10),
1218 * p = L.point(1, 2),
1219 * p2 = transformation.transform(p), // L.point(7, 8)
1220 * p3 = transformation.untransform(p2); // L.point(1, 2)
1225 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1226 // Creates a `Transformation` object with the given coefficients.
1227 L.Transformation = function (a, b, c, d) {
1234 L.Transformation.prototype = {
1235 // @method transform(point: Point, scale?: Number): Point
1236 // Returns a transformed point, optionally multiplied by the given scale.
1237 // Only accepts real `L.Point` instances, not arrays.
1238 transform: function (point, scale) { // (Point, Number) -> Point
1239 return this._transform(point.clone(), scale);
1242 // destructive transform (faster)
1243 _transform: function (point, scale) {
1245 point.x = scale * (this._a * point.x + this._b);
1246 point.y = scale * (this._c * point.y + this._d);
1250 // @method untransform(point: Point, scale?: Number): Point
1251 // Returns the reverse transformation of the given point, optionally divided
1252 // by the given scale. Only accepts real `L.Point` instances, not arrays.
1253 untransform: function (point, scale) {
1256 (point.x / scale - this._b) / this._a,
1257 (point.y / scale - this._d) / this._c);
1264 * @namespace DomUtil
1266 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
1267 * tree, used by Leaflet internally.
1269 * Most functions expecting or returning a `HTMLElement` also work for
1270 * SVG elements. The only difference is that classes refer to CSS classes
1271 * in HTML and SVG classes in SVG.
1276 // @function get(id: String|HTMLElement): HTMLElement
1277 // Returns an element given its DOM id, or returns the element itself
1278 // if it was passed directly.
1279 get: function (id) {
1280 return typeof id === 'string' ? document.getElementById(id) : id;
1283 // @function getStyle(el: HTMLElement, styleAttrib: String): String
1284 // Returns the value for a certain style attribute on an element,
1285 // including computed values or values set through CSS.
1286 getStyle: function (el, style) {
1288 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
1290 if ((!value || value === 'auto') && document.defaultView) {
1291 var css = document.defaultView.getComputedStyle(el, null);
1292 value = css ? css[style] : null;
1295 return value === 'auto' ? null : value;
1298 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
1299 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
1300 create: function (tagName, className, container) {
1302 var el = document.createElement(tagName);
1303 el.className = className || '';
1306 container.appendChild(el);
1312 // @function remove(el: HTMLElement)
1313 // Removes `el` from its parent element
1314 remove: function (el) {
1315 var parent = el.parentNode;
1317 parent.removeChild(el);
1321 // @function empty(el: HTMLElement)
1322 // Removes all of `el`'s children elements from `el`
1323 empty: function (el) {
1324 while (el.firstChild) {
1325 el.removeChild(el.firstChild);
1329 // @function toFront(el: HTMLElement)
1330 // Makes `el` the last children of its parent, so it renders in front of the other children.
1331 toFront: function (el) {
1332 el.parentNode.appendChild(el);
1335 // @function toBack(el: HTMLElement)
1336 // Makes `el` the first children of its parent, so it renders back from the other children.
1337 toBack: function (el) {
1338 var parent = el.parentNode;
1339 parent.insertBefore(el, parent.firstChild);
1342 // @function hasClass(el: HTMLElement, name: String): Boolean
1343 // Returns `true` if the element's class attribute contains `name`.
1344 hasClass: function (el, name) {
1345 if (el.classList !== undefined) {
1346 return el.classList.contains(name);
1348 var className = L.DomUtil.getClass(el);
1349 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
1352 // @function addClass(el: HTMLElement, name: String)
1353 // Adds `name` to the element's class attribute.
1354 addClass: function (el, name) {
1355 if (el.classList !== undefined) {
1356 var classes = L.Util.splitWords(name);
1357 for (var i = 0, len = classes.length; i < len; i++) {
1358 el.classList.add(classes[i]);
1360 } else if (!L.DomUtil.hasClass(el, name)) {
1361 var className = L.DomUtil.getClass(el);
1362 L.DomUtil.setClass(el, (className ? className + ' ' : '') + name);
1366 // @function removeClass(el: HTMLElement, name: String)
1367 // Removes `name` from the element's class attribute.
1368 removeClass: function (el, name) {
1369 if (el.classList !== undefined) {
1370 el.classList.remove(name);
1372 L.DomUtil.setClass(el, L.Util.trim((' ' + L.DomUtil.getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
1376 // @function setClass(el: HTMLElement, name: String)
1377 // Sets the element's class.
1378 setClass: function (el, name) {
1379 if (el.className.baseVal === undefined) {
1380 el.className = name;
1382 // in case of SVG element
1383 el.className.baseVal = name;
1387 // @function getClass(el: HTMLElement): String
1388 // Returns the element's class.
1389 getClass: function (el) {
1390 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
1393 // @function setOpacity(el: HTMLElement, opacity: Number)
1394 // Set the opacity of an element (including old IE support).
1395 // `opacity` must be a number from `0` to `1`.
1396 setOpacity: function (el, value) {
1398 if ('opacity' in el.style) {
1399 el.style.opacity = value;
1401 } else if ('filter' in el.style) {
1402 L.DomUtil._setOpacityIE(el, value);
1406 _setOpacityIE: function (el, value) {
1408 filterName = 'DXImageTransform.Microsoft.Alpha';
1410 // filters collection throws an error if we try to retrieve a filter that doesn't exist
1412 filter = el.filters.item(filterName);
1414 // don't set opacity to 1 if we haven't already set an opacity,
1415 // it isn't needed and breaks transparent pngs.
1416 if (value === 1) { return; }
1419 value = Math.round(value * 100);
1422 filter.Enabled = (value !== 100);
1423 filter.Opacity = value;
1425 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
1429 // @function testProp(props: String[]): String|false
1430 // Goes through the array of style names and returns the first name
1431 // that is a valid style name for an element. If no such name is found,
1432 // it returns false. Useful for vendor-prefixed styles like `transform`.
1433 testProp: function (props) {
1435 var style = document.documentElement.style;
1437 for (var i = 0; i < props.length; i++) {
1438 if (props[i] in style) {
1445 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
1446 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
1447 // and optionally scaled by `scale`. Does not have an effect if the
1448 // browser doesn't support 3D CSS transforms.
1449 setTransform: function (el, offset, scale) {
1450 var pos = offset || new L.Point(0, 0);
1452 el.style[L.DomUtil.TRANSFORM] =
1454 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
1455 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
1456 (scale ? ' scale(' + scale + ')' : '');
1459 // @function setPosition(el: HTMLElement, position: Point)
1460 // Sets the position of `el` to coordinates specified by `position`,
1461 // using CSS translate or top/left positioning depending on the browser
1462 // (used by Leaflet internally to position its layers).
1463 setPosition: function (el, point) { // (HTMLElement, Point[, Boolean])
1466 el._leaflet_pos = point;
1469 if (L.Browser.any3d) {
1470 L.DomUtil.setTransform(el, point);
1472 el.style.left = point.x + 'px';
1473 el.style.top = point.y + 'px';
1477 // @function getPosition(el: HTMLElement): Point
1478 // Returns the coordinates of an element previously positioned with setPosition.
1479 getPosition: function (el) {
1480 // this method is only used for elements previously positioned using setPosition,
1481 // so it's safe to cache the position for performance
1483 return el._leaflet_pos || new L.Point(0, 0);
1489 // prefix style property names
1491 // @property TRANSFORM: String
1492 // Vendor-prefixed fransform style name (e.g. `'webkitTransform'` for WebKit).
1493 L.DomUtil.TRANSFORM = L.DomUtil.testProp(
1494 ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
1497 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
1498 // the same for the transitionend event, in particular the Android 4.1 stock browser
1500 // @property TRANSITION: String
1501 // Vendor-prefixed transform style name.
1502 var transition = L.DomUtil.TRANSITION = L.DomUtil.testProp(
1503 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
1505 L.DomUtil.TRANSITION_END =
1506 transition === 'webkitTransition' || transition === 'OTransition' ? transition + 'End' : 'transitionend';
1508 // @function disableTextSelection()
1509 // Prevents the user from generating `selectstart` DOM events, usually generated
1510 // when the user drags the mouse through a page with text. Used internally
1511 // by Leaflet to override the behaviour of any click-and-drag interaction on
1512 // the map. Affects drag interactions on the whole document.
1514 // @function enableTextSelection()
1515 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
1516 if ('onselectstart' in document) {
1517 L.DomUtil.disableTextSelection = function () {
1518 L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);
1520 L.DomUtil.enableTextSelection = function () {
1521 L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);
1525 var userSelectProperty = L.DomUtil.testProp(
1526 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
1528 L.DomUtil.disableTextSelection = function () {
1529 if (userSelectProperty) {
1530 var style = document.documentElement.style;
1531 this._userSelect = style[userSelectProperty];
1532 style[userSelectProperty] = 'none';
1535 L.DomUtil.enableTextSelection = function () {
1536 if (userSelectProperty) {
1537 document.documentElement.style[userSelectProperty] = this._userSelect;
1538 delete this._userSelect;
1543 // @function disableImageDrag()
1544 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
1545 // for `dragstart` DOM events, usually generated when the user drags an image.
1546 L.DomUtil.disableImageDrag = function () {
1547 L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault);
1550 // @function enableImageDrag()
1551 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
1552 L.DomUtil.enableImageDrag = function () {
1553 L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault);
1556 // @function preventOutline(el: HTMLElement)
1557 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
1558 // of the element `el` invisible. Used internally by Leaflet to prevent
1559 // focusable elements from displaying an outline when the user performs a
1560 // drag interaction on them.
1561 L.DomUtil.preventOutline = function (element) {
1562 while (element.tabIndex === -1) {
1563 element = element.parentNode;
1565 if (!element || !element.style) { return; }
1566 L.DomUtil.restoreOutline();
1567 this._outlineElement = element;
1568 this._outlineStyle = element.style.outline;
1569 element.style.outline = 'none';
1570 L.DomEvent.on(window, 'keydown', L.DomUtil.restoreOutline, this);
1573 // @function restoreOutline()
1574 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
1575 L.DomUtil.restoreOutline = function () {
1576 if (!this._outlineElement) { return; }
1577 this._outlineElement.style.outline = this._outlineStyle;
1578 delete this._outlineElement;
1579 delete this._outlineStyle;
1580 L.DomEvent.off(window, 'keydown', L.DomUtil.restoreOutline, this);
1589 * Represents a geographical point with a certain latitude and longitude.
1594 * var latlng = L.latLng(50.5, 30.5);
1597 * 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:
1600 * map.panTo([50, 30]);
1601 * map.panTo({lon: 30, lat: 50});
1602 * map.panTo({lat: 50, lng: 30});
1603 * map.panTo(L.latLng(50, 30));
1607 L.LatLng = function (lat, lng, alt) {
1608 if (isNaN(lat) || isNaN(lng)) {
1609 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1612 // @property lat: Number
1613 // Latitude in degrees
1616 // @property lng: Number
1617 // Longitude in degrees
1620 // @property alt: Number
1621 // Altitude in meters (optional)
1622 if (alt !== undefined) {
1627 L.LatLng.prototype = {
1628 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1629 // 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.
1630 equals: function (obj, maxMargin) {
1631 if (!obj) { return false; }
1633 obj = L.latLng(obj);
1635 var margin = Math.max(
1636 Math.abs(this.lat - obj.lat),
1637 Math.abs(this.lng - obj.lng));
1639 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1642 // @method toString(): String
1643 // Returns a string representation of the point (for debugging purposes).
1644 toString: function (precision) {
1646 L.Util.formatNum(this.lat, precision) + ', ' +
1647 L.Util.formatNum(this.lng, precision) + ')';
1650 // @method distanceTo(otherLatLng: LatLng): Number
1651 // Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula).
1652 distanceTo: function (other) {
1653 return L.CRS.Earth.distance(this, L.latLng(other));
1656 // @method wrap(): LatLng
1657 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1659 return L.CRS.Earth.wrapLatLng(this);
1662 // @method toBounds(sizeInMeters: Number): LatLngBounds
1663 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters` meters apart from the `LatLng`.
1664 toBounds: function (sizeInMeters) {
1665 var latAccuracy = 180 * sizeInMeters / 40075017,
1666 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1668 return L.latLngBounds(
1669 [this.lat - latAccuracy, this.lng - lngAccuracy],
1670 [this.lat + latAccuracy, this.lng + lngAccuracy]);
1673 clone: function () {
1674 return new L.LatLng(this.lat, this.lng, this.alt);
1680 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1681 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1684 // @factory L.latLng(coords: Array): LatLng
1685 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1688 // @factory L.latLng(coords: Object): LatLng
1689 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1691 L.latLng = function (a, b, c) {
1692 if (a instanceof L.LatLng) {
1695 if (L.Util.isArray(a) && typeof a[0] !== 'object') {
1696 if (a.length === 3) {
1697 return new L.LatLng(a[0], a[1], a[2]);
1699 if (a.length === 2) {
1700 return new L.LatLng(a[0], a[1]);
1704 if (a === undefined || a === null) {
1707 if (typeof a === 'object' && 'lat' in a) {
1708 return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1710 if (b === undefined) {
1713 return new L.LatLng(a, b, c);
1719 * @class LatLngBounds
1720 * @aka L.LatLngBounds
1722 * Represents a rectangular geographical area on a map.
1727 * var southWest = L.latLng(40.712, -74.227),
1728 * northEast = L.latLng(40.774, -74.125),
1729 * bounds = L.latLngBounds(southWest, northEast);
1732 * 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:
1736 * [40.712, -74.227],
1742 L.LatLngBounds = function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[])
1743 if (!southWest) { return; }
1745 var latlngs = northEast ? [southWest, northEast] : southWest;
1747 for (var i = 0, len = latlngs.length; i < len; i++) {
1748 this.extend(latlngs[i]);
1752 L.LatLngBounds.prototype = {
1754 // @method extend(latlng: LatLng): this
1755 // Extend the bounds to contain the given point
1758 // @method extend(otherBounds: LatLngBounds): this
1759 // Extend the bounds to contain the given bounds
1760 extend: function (obj) {
1761 var sw = this._southWest,
1762 ne = this._northEast,
1765 if (obj instanceof L.LatLng) {
1769 } else if (obj instanceof L.LatLngBounds) {
1770 sw2 = obj._southWest;
1771 ne2 = obj._northEast;
1773 if (!sw2 || !ne2) { return this; }
1776 return obj ? this.extend(L.latLng(obj) || L.latLngBounds(obj)) : this;
1780 this._southWest = new L.LatLng(sw2.lat, sw2.lng);
1781 this._northEast = new L.LatLng(ne2.lat, ne2.lng);
1783 sw.lat = Math.min(sw2.lat, sw.lat);
1784 sw.lng = Math.min(sw2.lng, sw.lng);
1785 ne.lat = Math.max(ne2.lat, ne.lat);
1786 ne.lng = Math.max(ne2.lng, ne.lng);
1792 // @method pad(bufferRatio: Number): LatLngBounds
1793 // Returns bigger bounds created by extending the current bounds by a given percentage in each direction.
1794 pad: function (bufferRatio) {
1795 var sw = this._southWest,
1796 ne = this._northEast,
1797 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1798 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1800 return new L.LatLngBounds(
1801 new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1802 new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1805 // @method getCenter(): LatLng
1806 // Returns the center point of the bounds.
1807 getCenter: function () {
1808 return new L.LatLng(
1809 (this._southWest.lat + this._northEast.lat) / 2,
1810 (this._southWest.lng + this._northEast.lng) / 2);
1813 // @method getSouthWest(): LatLng
1814 // Returns the south-west point of the bounds.
1815 getSouthWest: function () {
1816 return this._southWest;
1819 // @method getNorthEast(): LatLng
1820 // Returns the north-east point of the bounds.
1821 getNorthEast: function () {
1822 return this._northEast;
1825 // @method getNorthWest(): LatLng
1826 // Returns the north-west point of the bounds.
1827 getNorthWest: function () {
1828 return new L.LatLng(this.getNorth(), this.getWest());
1831 // @method getSouthEast(): LatLng
1832 // Returns the south-east point of the bounds.
1833 getSouthEast: function () {
1834 return new L.LatLng(this.getSouth(), this.getEast());
1837 // @method getWest(): Number
1838 // Returns the west longitude of the bounds
1839 getWest: function () {
1840 return this._southWest.lng;
1843 // @method getSouth(): Number
1844 // Returns the south latitude of the bounds
1845 getSouth: function () {
1846 return this._southWest.lat;
1849 // @method getEast(): Number
1850 // Returns the east longitude of the bounds
1851 getEast: function () {
1852 return this._northEast.lng;
1855 // @method getNorth(): Number
1856 // Returns the north latitude of the bounds
1857 getNorth: function () {
1858 return this._northEast.lat;
1861 // @method contains(otherBounds: LatLngBounds): Boolean
1862 // Returns `true` if the rectangle contains the given one.
1865 // @method contains (latlng: LatLng): Boolean
1866 // Returns `true` if the rectangle contains the given point.
1867 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1868 if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
1869 obj = L.latLng(obj);
1871 obj = L.latLngBounds(obj);
1874 var sw = this._southWest,
1875 ne = this._northEast,
1878 if (obj instanceof L.LatLngBounds) {
1879 sw2 = obj.getSouthWest();
1880 ne2 = obj.getNorthEast();
1885 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1886 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1889 // @method intersects(otherBounds: LatLngBounds): Boolean
1890 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1891 intersects: function (bounds) {
1892 bounds = L.latLngBounds(bounds);
1894 var sw = this._southWest,
1895 ne = this._northEast,
1896 sw2 = bounds.getSouthWest(),
1897 ne2 = bounds.getNorthEast(),
1899 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1900 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1902 return latIntersects && lngIntersects;
1905 // @method overlaps(otherBounds: Bounds): Boolean
1906 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1907 overlaps: function (bounds) {
1908 bounds = L.latLngBounds(bounds);
1910 var sw = this._southWest,
1911 ne = this._northEast,
1912 sw2 = bounds.getSouthWest(),
1913 ne2 = bounds.getNorthEast(),
1915 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1916 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1918 return latOverlaps && lngOverlaps;
1921 // @method toBBoxString(): String
1922 // 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.
1923 toBBoxString: function () {
1924 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1927 // @method equals(otherBounds: LatLngBounds): Boolean
1928 // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds.
1929 equals: function (bounds) {
1930 if (!bounds) { return false; }
1932 bounds = L.latLngBounds(bounds);
1934 return this._southWest.equals(bounds.getSouthWest()) &&
1935 this._northEast.equals(bounds.getNorthEast());
1938 // @method isValid(): Boolean
1939 // Returns `true` if the bounds are properly initialized.
1940 isValid: function () {
1941 return !!(this._southWest && this._northEast);
1945 // TODO International date line?
1947 // @factory L.latLngBounds(southWest: LatLng, northEast: LatLng)
1948 // Creates a `LatLngBounds` object by defining south-west and north-east corners of the rectangle.
1951 // @factory L.latLngBounds(latlngs: LatLng[])
1952 // 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).
1953 L.latLngBounds = function (a, b) {
1954 if (a instanceof L.LatLngBounds) {
1957 return new L.LatLngBounds(a, b);
1963 * @namespace Projection
1965 * Leaflet comes with a set of already defined Projections out of the box:
1967 * @projection L.Projection.LonLat
1969 * Equirectangular, or Plate Carree projection — the most simple projection,
1970 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
1971 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
1972 * `EPSG:3395` and `Simple` CRS.
1977 L.Projection.LonLat = {
1978 project: function (latlng) {
1979 return new L.Point(latlng.lng, latlng.lat);
1982 unproject: function (point) {
1983 return new L.LatLng(point.y, point.x);
1986 bounds: L.bounds([-180, -90], [180, 90])
1992 * @namespace Projection
1993 * @projection L.Projection.SphericalMercator
1995 * Spherical Mercator projection — the most common projection for online maps,
1996 * used by almost all free and commercial tile providers. Assumes that Earth is
1997 * a sphere. Used by the `EPSG:3857` CRS.
2000 L.Projection.SphericalMercator = {
2003 MAX_LATITUDE: 85.0511287798,
2005 project: function (latlng) {
2006 var d = Math.PI / 180,
2007 max = this.MAX_LATITUDE,
2008 lat = Math.max(Math.min(max, latlng.lat), -max),
2009 sin = Math.sin(lat * d);
2012 this.R * latlng.lng * d,
2013 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
2016 unproject: function (point) {
2017 var d = 180 / Math.PI;
2019 return new L.LatLng(
2020 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
2021 point.x * d / this.R);
2024 bounds: (function () {
2025 var d = 6378137 * Math.PI;
2026 return L.bounds([-d, -d], [d, d]);
2035 * Abstract class that defines coordinate reference systems for projecting
2036 * geographical points into pixel (screen) coordinates and back (and to
2037 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
2038 * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
2040 * Leaflet defines the most usual CRSs by default. If you want to use a
2041 * CRS not defined by default, take a look at the
2042 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
2046 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
2047 // Projects geographical coordinates into pixel coordinates for a given zoom.
2048 latLngToPoint: function (latlng, zoom) {
2049 var projectedPoint = this.projection.project(latlng),
2050 scale = this.scale(zoom);
2052 return this.transformation._transform(projectedPoint, scale);
2055 // @method pointToLatLng(point: Point, zoom: Number): LatLng
2056 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
2057 // zoom into geographical coordinates.
2058 pointToLatLng: function (point, zoom) {
2059 var scale = this.scale(zoom),
2060 untransformedPoint = this.transformation.untransform(point, scale);
2062 return this.projection.unproject(untransformedPoint);
2065 // @method project(latlng: LatLng): Point
2066 // Projects geographical coordinates into coordinates in units accepted for
2067 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
2068 project: function (latlng) {
2069 return this.projection.project(latlng);
2072 // @method unproject(point: Point): LatLng
2073 // Given a projected coordinate returns the corresponding LatLng.
2074 // The inverse of `project`.
2075 unproject: function (point) {
2076 return this.projection.unproject(point);
2079 // @method scale(zoom: Number): Number
2080 // Returns the scale used when transforming projected coordinates into
2081 // pixel coordinates for a particular zoom. For example, it returns
2082 // `256 * 2^zoom` for Mercator-based CRS.
2083 scale: function (zoom) {
2084 return 256 * Math.pow(2, zoom);
2087 // @method zoom(scale: Number): Number
2088 // Inverse of `scale()`, returns the zoom level corresponding to a scale
2089 // factor of `scale`.
2090 zoom: function (scale) {
2091 return Math.log(scale / 256) / Math.LN2;
2094 // @method getProjectedBounds(zoom: Number): Bounds
2095 // Returns the projection's bounds scaled and transformed for the provided `zoom`.
2096 getProjectedBounds: function (zoom) {
2097 if (this.infinite) { return null; }
2099 var b = this.projection.bounds,
2100 s = this.scale(zoom),
2101 min = this.transformation.transform(b.min, s),
2102 max = this.transformation.transform(b.max, s);
2104 return L.bounds(min, max);
2107 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
2108 // Returns the distance between two geographical coordinates.
2110 // @property code: String
2111 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
2113 // @property wrapLng: Number[]
2114 // An array of two numbers defining whether the longitude (horizontal) coordinate
2115 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
2116 // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
2118 // @property wrapLat: Number[]
2119 // Like `wrapLng`, but for the latitude (vertical) axis.
2121 // wrapLng: [min, max],
2122 // wrapLat: [min, max],
2124 // @property infinite: Boolean
2125 // If true, the coordinate space will be unbounded (infinite in both axes)
2128 // @method wrapLatLng(latlng: LatLng): LatLng
2129 // Returns a `LatLng` where lat and lng has been wrapped according to the
2130 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
2131 wrapLatLng: function (latlng) {
2132 var lng = this.wrapLng ? L.Util.wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
2133 lat = this.wrapLat ? L.Util.wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
2136 return L.latLng(lat, lng, alt);
2146 * A simple CRS that maps longitude and latitude into `x` and `y` directly.
2147 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
2148 * axis should still be inverted (going from bottom to top). `distance()` returns
2149 * simple euclidean distance.
2152 L.CRS.Simple = L.extend({}, L.CRS, {
2153 projection: L.Projection.LonLat,
2154 transformation: new L.Transformation(1, 0, -1, 0),
2156 scale: function (zoom) {
2157 return Math.pow(2, zoom);
2160 zoom: function (scale) {
2161 return Math.log(scale) / Math.LN2;
2164 distance: function (latlng1, latlng2) {
2165 var dx = latlng2.lng - latlng1.lng,
2166 dy = latlng2.lat - latlng1.lat;
2168 return Math.sqrt(dx * dx + dy * dy);
2180 * Serves as the base for CRS that are global such that they cover the earth.
2181 * Can only be used as the base for other CRS and cannot be used directly,
2182 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
2186 L.CRS.Earth = L.extend({}, L.CRS, {
2187 wrapLng: [-180, 180],
2189 // Mean Earth Radius, as recommended for use by
2190 // the International Union of Geodesy and Geophysics,
2191 // see http://rosettacode.org/wiki/Haversine_formula
2194 // distance between two geographical points using spherical law of cosines approximation
2195 distance: function (latlng1, latlng2) {
2196 var rad = Math.PI / 180,
2197 lat1 = latlng1.lat * rad,
2198 lat2 = latlng2.lat * rad,
2199 a = Math.sin(lat1) * Math.sin(lat2) +
2200 Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad);
2202 return this.R * Math.acos(Math.min(a, 1));
2210 * @crs L.CRS.EPSG3857
2212 * The most common CRS for online maps, used by almost all free and commercial
2213 * tile providers. Uses Spherical Mercator projection. Set in by default in
2214 * Map's `crs` option.
2217 L.CRS.EPSG3857 = L.extend({}, L.CRS.Earth, {
2219 projection: L.Projection.SphericalMercator,
2221 transformation: (function () {
2222 var scale = 0.5 / (Math.PI * L.Projection.SphericalMercator.R);
2223 return new L.Transformation(scale, 0.5, -scale, 0.5);
2227 L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
2235 * @crs L.CRS.EPSG4326
2237 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
2240 L.CRS.EPSG4326 = L.extend({}, L.CRS.Earth, {
2242 projection: L.Projection.LonLat,
2243 transformation: new L.Transformation(1 / 180, 1, -1 / 180, 0.5)
2253 * The central class of the API — it is used to create a map on a page and manipulate it.
2258 * // initialize the map on the "map" div with a given center and zoom
2259 * var map = L.map('map', {
2260 * center: [51.505, -0.09],
2267 L.Map = L.Evented.extend({
2270 // @section Map State Options
2271 // @option crs: CRS = L.CRS.EPSG3857
2272 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
2273 // sure what it means.
2274 crs: L.CRS.EPSG3857,
2276 // @option center: LatLng = undefined
2277 // Initial geographic center of the map
2280 // @option zoom: Number = undefined
2281 // Initial map zoom level
2284 // @option minZoom: Number = undefined
2285 // Minimum zoom level of the map. Overrides any `minZoom` option set on map layers.
2288 // @option maxZoom: Number = undefined
2289 // Maximum zoom level of the map. Overrides any `maxZoom` option set on map layers.
2292 // @option layers: Layer[] = []
2293 // Array of layers that will be added to the map initially
2296 // @option maxBounds: LatLngBounds = null
2297 // When this option is set, the map restricts the view to the given
2298 // geographical bounds, bouncing the user back when he tries to pan
2299 // outside the view. To set the restriction dynamically, use
2300 // [`setMaxBounds`](#map-setmaxbounds) method.
2301 maxBounds: undefined,
2303 // @option renderer: Renderer = *
2304 // The default method for drawing vector layers on the map. `L.SVG`
2305 // or `L.Canvas` by default depending on browser support.
2306 renderer: undefined,
2309 // @section Animation Options
2310 // @option fadeAnimation: Boolean = true
2311 // Whether the tile fade animation is enabled. By default it's enabled
2312 // in all browsers that support CSS3 Transitions except Android.
2313 fadeAnimation: true,
2315 // @option markerZoomAnimation: Boolean = true
2316 // Whether markers animate their zoom with the zoom animation, if disabled
2317 // they will disappear for the length of the animation. By default it's
2318 // enabled in all browsers that support CSS3 Transitions except Android.
2319 markerZoomAnimation: true,
2321 // @option transform3DLimit: Number = 2^23
2322 // Defines the maximum size of a CSS translation transform. The default
2323 // value should not be changed unless a web browser positions layers in
2324 // the wrong place after doing a large `panBy`.
2325 transform3DLimit: 8388608, // Precision limit of a 32-bit float
2327 // @section Interaction Options
2328 // @option zoomSnap: Number = 1
2329 // Forces the map's zoom level to always be a multiple of this, particularly
2330 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
2331 // By default, the zoom level snaps to the nearest integer; lower values
2332 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
2333 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
2336 // @option zoomDelta: Number = 1
2337 // Controls how much the map's zoom level will change after a
2338 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
2339 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
2340 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
2343 // @option trackResize: Boolean = true
2344 // Whether the map automatically handles browser window resize to update itself.
2348 initialize: function (id, options) { // (HTMLElement or String, Object)
2349 options = L.setOptions(this, options);
2351 this._initContainer(id);
2354 // hack for https://github.com/Leaflet/Leaflet/issues/1980
2355 this._onResize = L.bind(this._onResize, this);
2359 if (options.maxBounds) {
2360 this.setMaxBounds(options.maxBounds);
2363 if (options.zoom !== undefined) {
2364 this._zoom = this._limitZoom(options.zoom);
2367 if (options.center && options.zoom !== undefined) {
2368 this.setView(L.latLng(options.center), options.zoom, {reset: true});
2371 this._handlers = [];
2373 this._zoomBoundLayers = {};
2374 this._sizeChanged = true;
2376 this.callInitHooks();
2378 this._addLayers(this.options.layers);
2382 // @section Methods for modifying map state
2384 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
2385 // Sets the view of the map (geographical center and zoom) with the given
2386 // animation options.
2387 setView: function (center, zoom) {
2388 // replaced by animation-powered implementation in Map.PanAnimation.js
2389 zoom = zoom === undefined ? this.getZoom() : zoom;
2390 this._resetView(L.latLng(center), zoom);
2394 // @method setZoom(zoom: Number, options: Zoom/pan options): this
2395 // Sets the zoom of the map.
2396 setZoom: function (zoom, options) {
2397 if (!this._loaded) {
2401 return this.setView(this.getCenter(), zoom, {zoom: options});
2404 // @method zoomIn(delta?: Number, options?: Zoom options): this
2405 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
2406 zoomIn: function (delta, options) {
2407 delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
2408 return this.setZoom(this._zoom + delta, options);
2411 // @method zoomOut(delta?: Number, options?: Zoom options): this
2412 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
2413 zoomOut: function (delta, options) {
2414 delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
2415 return this.setZoom(this._zoom - delta, options);
2418 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
2419 // Zooms the map while keeping a specified geographical point on the map
2420 // stationary (e.g. used internally for scroll zoom and double-click zoom).
2422 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
2423 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
2424 setZoomAround: function (latlng, zoom, options) {
2425 var scale = this.getZoomScale(zoom),
2426 viewHalf = this.getSize().divideBy(2),
2427 containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng),
2429 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
2430 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
2432 return this.setView(newCenter, zoom, {zoom: options});
2435 _getBoundsCenterZoom: function (bounds, options) {
2437 options = options || {};
2438 bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds);
2440 var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]),
2441 paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]),
2443 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
2445 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
2447 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
2449 swPoint = this.project(bounds.getSouthWest(), zoom),
2450 nePoint = this.project(bounds.getNorthEast(), zoom),
2451 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
2459 // @method fitBounds(bounds: LatLngBounds, options: fitBounds options): this
2460 // Sets a map view that contains the given geographical bounds with the
2461 // maximum zoom level possible.
2462 fitBounds: function (bounds, options) {
2464 bounds = L.latLngBounds(bounds);
2466 if (!bounds.isValid()) {
2467 throw new Error('Bounds are not valid.');
2470 var target = this._getBoundsCenterZoom(bounds, options);
2471 return this.setView(target.center, target.zoom, options);
2474 // @method fitWorld(options?: fitBounds options): this
2475 // Sets a map view that mostly contains the whole world with the maximum
2476 // zoom level possible.
2477 fitWorld: function (options) {
2478 return this.fitBounds([[-90, -180], [90, 180]], options);
2481 // @method panTo(latlng: LatLng, options?: Pan options): this
2482 // Pans the map to a given center.
2483 panTo: function (center, options) { // (LatLng)
2484 return this.setView(center, this._zoom, {pan: options});
2487 // @method panBy(offset: Point): this
2488 // Pans the map by a given number of pixels (animated).
2489 panBy: function (offset) { // (Point)
2490 // replaced with animated panBy in Map.PanAnimation.js
2491 this.fire('movestart');
2493 this._rawPanBy(L.point(offset));
2496 return this.fire('moveend');
2499 // @method setMaxBounds(bounds: Bounds): this
2500 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
2501 setMaxBounds: function (bounds) {
2502 bounds = L.latLngBounds(bounds);
2504 if (!bounds.isValid()) {
2505 this.options.maxBounds = null;
2506 return this.off('moveend', this._panInsideMaxBounds);
2507 } else if (this.options.maxBounds) {
2508 this.off('moveend', this._panInsideMaxBounds);
2511 this.options.maxBounds = bounds;
2514 this._panInsideMaxBounds();
2517 return this.on('moveend', this._panInsideMaxBounds);
2520 // @method setMinZoom(zoom: Number): this
2521 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
2522 setMinZoom: function (zoom) {
2523 this.options.minZoom = zoom;
2525 if (this._loaded && this.getZoom() < this.options.minZoom) {
2526 return this.setZoom(zoom);
2532 // @method setMaxZoom(zoom: Number): this
2533 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
2534 setMaxZoom: function (zoom) {
2535 this.options.maxZoom = zoom;
2537 if (this._loaded && (this.getZoom() > this.options.maxZoom)) {
2538 return this.setZoom(zoom);
2544 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
2545 // 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.
2546 panInsideBounds: function (bounds, options) {
2547 this._enforcingBounds = true;
2548 var center = this.getCenter(),
2549 newCenter = this._limitCenter(center, this._zoom, L.latLngBounds(bounds));
2551 if (!center.equals(newCenter)) {
2552 this.panTo(newCenter, options);
2555 this._enforcingBounds = false;
2559 // @method invalidateSize(options: Zoom/Pan options): this
2560 // Checks if the map container size changed and updates the map if so —
2561 // call it after you've changed the map size dynamically, also animating
2562 // pan by default. If `options.pan` is `false`, panning will not occur.
2563 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
2564 // that it doesn't happen often even if the method is called many
2568 // @method invalidateSize(animate: Boolean): this
2569 // Checks if the map container size changed and updates the map if so —
2570 // call it after you've changed the map size dynamically, also animating
2572 invalidateSize: function (options) {
2573 if (!this._loaded) { return this; }
2575 options = L.extend({
2578 }, options === true ? {animate: true} : options);
2580 var oldSize = this.getSize();
2581 this._sizeChanged = true;
2582 this._lastCenter = null;
2584 var newSize = this.getSize(),
2585 oldCenter = oldSize.divideBy(2).round(),
2586 newCenter = newSize.divideBy(2).round(),
2587 offset = oldCenter.subtract(newCenter);
2589 if (!offset.x && !offset.y) { return this; }
2591 if (options.animate && options.pan) {
2596 this._rawPanBy(offset);
2601 if (options.debounceMoveend) {
2602 clearTimeout(this._sizeTimer);
2603 this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
2605 this.fire('moveend');
2609 // @section Map state change events
2610 // @event resize: ResizeEvent
2611 // Fired when the map is resized.
2612 return this.fire('resize', {
2618 // @section Methods for modifying map state
2619 // @method stop(): this
2620 // Stops the currently running `panTo` or `flyTo` animation, if any.
2622 this.setZoom(this._limitZoom(this._zoom));
2623 if (!this.options.zoomSnap) {
2624 this.fire('viewreset');
2626 return this._stop();
2630 // TODO handler.addTo
2631 // TODO Appropiate docs section?
2632 // @section Other Methods
2633 // @method addHandler(name: String, HandlerClass: Function): this
2634 // Adds a new `Handler` to the map, given its name and constructor function.
2635 addHandler: function (name, HandlerClass) {
2636 if (!HandlerClass) { return this; }
2638 var handler = this[name] = new HandlerClass(this);
2640 this._handlers.push(handler);
2642 if (this.options[name]) {
2649 // @method remove(): this
2650 // Destroys the map and clears all related event listeners.
2651 remove: function () {
2653 this._initEvents(true);
2655 if (this._containerId !== this._container._leaflet_id) {
2656 throw new Error('Map container is being reused by another instance');
2660 // throws error in IE6-8
2661 delete this._container._leaflet_id;
2662 delete this._containerId;
2665 this._container._leaflet_id = undefined;
2667 this._containerId = undefined;
2670 L.DomUtil.remove(this._mapPane);
2672 if (this._clearControlPos) {
2673 this._clearControlPos();
2676 this._clearHandlers();
2679 // @section Map state change events
2680 // @event unload: Event
2681 // Fired when the map is destroyed with [remove](#map-remove) method.
2682 this.fire('unload');
2685 for (var i in this._layers) {
2686 this._layers[i].remove();
2692 // @section Other Methods
2693 // @method createPane(name: String, container?: HTMLElement): HTMLElement
2694 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
2695 // then returns it. The pane is created as a children of `container`, or
2696 // as a children of the main map pane if not set.
2697 createPane: function (name, container) {
2698 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
2699 pane = L.DomUtil.create('div', className, container || this._mapPane);
2702 this._panes[name] = pane;
2707 // @section Methods for Getting Map State
2709 // @method getCenter(): LatLng
2710 // Returns the geographical center of the map view
2711 getCenter: function () {
2712 this._checkIfLoaded();
2714 if (this._lastCenter && !this._moved()) {
2715 return this._lastCenter;
2717 return this.layerPointToLatLng(this._getCenterLayerPoint());
2720 // @method getZoom(): Number
2721 // Returns the current zoom level of the map view
2722 getZoom: function () {
2726 // @method getBounds(): LatLngBounds
2727 // Returns the geographical bounds visible in the current map view
2728 getBounds: function () {
2729 var bounds = this.getPixelBounds(),
2730 sw = this.unproject(bounds.getBottomLeft()),
2731 ne = this.unproject(bounds.getTopRight());
2733 return new L.LatLngBounds(sw, ne);
2736 // @method getMinZoom(): Number
2737 // 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.
2738 getMinZoom: function () {
2739 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
2742 // @method getMaxZoom(): Number
2743 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
2744 getMaxZoom: function () {
2745 return this.options.maxZoom === undefined ?
2746 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
2747 this.options.maxZoom;
2750 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number
2751 // Returns the maximum zoom level on which the given bounds fit to the map
2752 // view in its entirety. If `inside` (optional) is set to `true`, the method
2753 // instead returns the minimum zoom level on which the map view fits into
2754 // the given bounds in its entirety.
2755 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
2756 bounds = L.latLngBounds(bounds);
2757 padding = L.point(padding || [0, 0]);
2759 var zoom = this.getZoom() || 0,
2760 min = this.getMinZoom(),
2761 max = this.getMaxZoom(),
2762 nw = bounds.getNorthWest(),
2763 se = bounds.getSouthEast(),
2764 size = this.getSize().subtract(padding),
2765 boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)),
2766 snap = L.Browser.any3d ? this.options.zoomSnap : 1;
2768 var scale = Math.min(size.x / boundsSize.x, size.y / boundsSize.y);
2769 zoom = this.getScaleZoom(scale, zoom);
2772 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
2773 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
2776 return Math.max(min, Math.min(max, zoom));
2779 // @method getSize(): Point
2780 // Returns the current size of the map container (in pixels).
2781 getSize: function () {
2782 if (!this._size || this._sizeChanged) {
2783 this._size = new L.Point(
2784 this._container.clientWidth,
2785 this._container.clientHeight);
2787 this._sizeChanged = false;
2789 return this._size.clone();
2792 // @method getPixelBounds(): Bounds
2793 // Returns the bounds of the current map view in projected pixel
2794 // coordinates (sometimes useful in layer and overlay implementations).
2795 getPixelBounds: function (center, zoom) {
2796 var topLeftPoint = this._getTopLeftPoint(center, zoom);
2797 return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
2800 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
2801 // the map pane? "left point of the map layer" can be confusing, specially
2802 // since there can be negative offsets.
2803 // @method getPixelOrigin(): Point
2804 // Returns the projected pixel coordinates of the top left point of
2805 // the map layer (useful in custom layer and overlay implementations).
2806 getPixelOrigin: function () {
2807 this._checkIfLoaded();
2808 return this._pixelOrigin;
2811 // @method getPixelWorldBounds(zoom?: Number): Bounds
2812 // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
2813 // If `zoom` is omitted, the map's current zoom level is used.
2814 getPixelWorldBounds: function (zoom) {
2815 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
2818 // @section Other Methods
2820 // @method getPane(pane: String|HTMLElement): HTMLElement
2821 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
2822 getPane: function (pane) {
2823 return typeof pane === 'string' ? this._panes[pane] : pane;
2826 // @method getPanes(): Object
2827 // Returns a plain object containing the names of all [panes](#map-pane) as keys and
2828 // the panes as values.
2829 getPanes: function () {
2833 // @method getContainer: HTMLElement
2834 // Returns the HTML element that contains the map.
2835 getContainer: function () {
2836 return this._container;
2840 // @section Conversion Methods
2842 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
2843 // Returns the scale factor to be applied to a map transition from zoom level
2844 // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
2845 getZoomScale: function (toZoom, fromZoom) {
2846 // TODO replace with universal implementation after refactoring projections
2847 var crs = this.options.crs;
2848 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
2849 return crs.scale(toZoom) / crs.scale(fromZoom);
2852 // @method getScaleZoom(scale: Number, fromZoom: Number): Number
2853 // Returns the zoom level that the map would end up at, if it is at `fromZoom`
2854 // level and everything is scaled by a factor of `scale`. Inverse of
2855 // [`getZoomScale`](#map-getZoomScale).
2856 getScaleZoom: function (scale, fromZoom) {
2857 var crs = this.options.crs;
2858 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
2859 var zoom = crs.zoom(scale * crs.scale(fromZoom));
2860 return isNaN(zoom) ? Infinity : zoom;
2863 // @method project(latlng: LatLng, zoom: Number): Point
2864 // Projects a geographical coordinate `LatLng` according to the projection
2865 // of the map's CRS, then scales it according to `zoom` and the CRS's
2866 // `Transformation`. The result is pixel coordinate relative to
2868 project: function (latlng, zoom) {
2869 zoom = zoom === undefined ? this._zoom : zoom;
2870 return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
2873 // @method unproject(point: Point, zoom: Number): LatLng
2874 // Inverse of [`project`](#map-project).
2875 unproject: function (point, zoom) {
2876 zoom = zoom === undefined ? this._zoom : zoom;
2877 return this.options.crs.pointToLatLng(L.point(point), zoom);
2880 // @method layerPointToLatLng(point: Point): LatLng
2881 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
2882 // returns the corresponding geographical coordinate (for the current zoom level).
2883 layerPointToLatLng: function (point) {
2884 var projectedPoint = L.point(point).add(this.getPixelOrigin());
2885 return this.unproject(projectedPoint);
2888 // @method latLngToLayerPoint(latlng: LatLng): Point
2889 // Given a geographical coordinate, returns the corresponding pixel coordinate
2890 // relative to the [origin pixel](#map-getpixelorigin).
2891 latLngToLayerPoint: function (latlng) {
2892 var projectedPoint = this.project(L.latLng(latlng))._round();
2893 return projectedPoint._subtract(this.getPixelOrigin());
2896 // @method wrapLatLng(latlng: LatLng): LatLng
2897 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
2898 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
2900 // By default this means longitude is wrapped around the dateline so its
2901 // value is between -180 and +180 degrees.
2902 wrapLatLng: function (latlng) {
2903 return this.options.crs.wrapLatLng(L.latLng(latlng));
2906 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
2907 // Returns the distance between two geographical coordinates according to
2908 // the map's CRS. By default this measures distance in meters.
2909 distance: function (latlng1, latlng2) {
2910 return this.options.crs.distance(L.latLng(latlng1), L.latLng(latlng2));
2913 // @method containerPointToLayerPoint(point: Point): Point
2914 // Given a pixel coordinate relative to the map container, returns the corresponding
2915 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
2916 containerPointToLayerPoint: function (point) { // (Point)
2917 return L.point(point).subtract(this._getMapPanePos());
2920 // @method layerPointToContainerPoint(point: Point): Point
2921 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
2922 // returns the corresponding pixel coordinate relative to the map container.
2923 layerPointToContainerPoint: function (point) { // (Point)
2924 return L.point(point).add(this._getMapPanePos());
2927 // @method containerPointToLatLng(point: Point): Point
2928 // Given a pixel coordinate relative to the map container, returns
2929 // the corresponding geographical coordinate (for the current zoom level).
2930 containerPointToLatLng: function (point) {
2931 var layerPoint = this.containerPointToLayerPoint(L.point(point));
2932 return this.layerPointToLatLng(layerPoint);
2935 // @method latLngToContainerPoint(latlng: LatLng): Point
2936 // Given a geographical coordinate, returns the corresponding pixel coordinate
2937 // relative to the map container.
2938 latLngToContainerPoint: function (latlng) {
2939 return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
2942 // @method mouseEventToContainerPoint(ev: MouseEvent): Point
2943 // Given a MouseEvent object, returns the pixel coordinate relative to the
2944 // map container where the event took place.
2945 mouseEventToContainerPoint: function (e) {
2946 return L.DomEvent.getMousePosition(e, this._container);
2949 // @method mouseEventToLayerPoint(ev: MouseEvent): Point
2950 // Given a MouseEvent object, returns the pixel coordinate relative to
2951 // the [origin pixel](#map-getpixelorigin) where the event took place.
2952 mouseEventToLayerPoint: function (e) {
2953 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
2956 // @method mouseEventToLatLng(ev: MouseEvent): LatLng
2957 // Given a MouseEvent object, returns geographical coordinate where the
2958 // event took place.
2959 mouseEventToLatLng: function (e) { // (MouseEvent)
2960 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
2964 // map initialization methods
2966 _initContainer: function (id) {
2967 var container = this._container = L.DomUtil.get(id);
2970 throw new Error('Map container not found.');
2971 } else if (container._leaflet_id) {
2972 throw new Error('Map container is already initialized.');
2975 L.DomEvent.addListener(container, 'scroll', this._onScroll, this);
2976 this._containerId = L.Util.stamp(container);
2979 _initLayout: function () {
2980 var container = this._container;
2982 this._fadeAnimated = this.options.fadeAnimation && L.Browser.any3d;
2984 L.DomUtil.addClass(container, 'leaflet-container' +
2985 (L.Browser.touch ? ' leaflet-touch' : '') +
2986 (L.Browser.retina ? ' leaflet-retina' : '') +
2987 (L.Browser.ielt9 ? ' leaflet-oldie' : '') +
2988 (L.Browser.safari ? ' leaflet-safari' : '') +
2989 (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
2991 var position = L.DomUtil.getStyle(container, 'position');
2993 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
2994 container.style.position = 'relative';
2999 if (this._initControlPos) {
3000 this._initControlPos();
3004 _initPanes: function () {
3005 var panes = this._panes = {};
3006 this._paneRenderers = {};
3010 // Panes are DOM elements used to control the ordering of layers on the map. You
3011 // can access panes with [`map.getPane`](#map-getpane) or
3012 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
3013 // [`map.createPane`](#map-createpane) method.
3015 // Every map has the following default panes that differ only in zIndex.
3017 // @pane mapPane: HTMLElement = 'auto'
3018 // Pane that contains all other map panes
3020 this._mapPane = this.createPane('mapPane', this._container);
3021 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
3023 // @pane tilePane: HTMLElement = 200
3024 // Pane for `GridLayer`s and `TileLayer`s
3025 this.createPane('tilePane');
3026 // @pane overlayPane: HTMLElement = 400
3027 // Pane for vector overlays (`Path`s), like `Polyline`s and `Polygon`s
3028 this.createPane('shadowPane');
3029 // @pane shadowPane: HTMLElement = 500
3030 // Pane for overlay shadows (e.g. `Marker` shadows)
3031 this.createPane('overlayPane');
3032 // @pane markerPane: HTMLElement = 600
3033 // Pane for `Icon`s of `Marker`s
3034 this.createPane('markerPane');
3035 // @pane tooltipPane: HTMLElement = 650
3036 // Pane for tooltip.
3037 this.createPane('tooltipPane');
3038 // @pane popupPane: HTMLElement = 700
3039 // Pane for `Popup`s.
3040 this.createPane('popupPane');
3042 if (!this.options.markerZoomAnimation) {
3043 L.DomUtil.addClass(panes.markerPane, 'leaflet-zoom-hide');
3044 L.DomUtil.addClass(panes.shadowPane, 'leaflet-zoom-hide');
3049 // private methods that modify map state
3051 // @section Map state change events
3052 _resetView: function (center, zoom) {
3053 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
3055 var loading = !this._loaded;
3056 this._loaded = true;
3057 zoom = this._limitZoom(zoom);
3059 this.fire('viewprereset');
3061 var zoomChanged = this._zoom !== zoom;
3063 ._moveStart(zoomChanged)
3064 ._move(center, zoom)
3065 ._moveEnd(zoomChanged);
3067 // @event viewreset: Event
3068 // Fired when the map needs to redraw its content (this usually happens
3069 // on map zoom or load). Very useful for creating custom overlays.
3070 this.fire('viewreset');
3072 // @event load: Event
3073 // Fired when the map is initialized (when its center and zoom are set
3074 // for the first time).
3080 _moveStart: function (zoomChanged) {
3081 // @event zoomstart: Event
3082 // Fired when the map zoom is about to change (e.g. before zoom animation).
3083 // @event movestart: Event
3084 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
3086 this.fire('zoomstart');
3088 return this.fire('movestart');
3091 _move: function (center, zoom, data) {
3092 if (zoom === undefined) {
3095 var zoomChanged = this._zoom !== zoom;
3098 this._lastCenter = center;
3099 this._pixelOrigin = this._getNewPixelOrigin(center);
3101 // @event zoom: Event
3102 // Fired repeatedly during any change in zoom level, including zoom
3103 // and fly animations.
3104 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530
3105 this.fire('zoom', data);
3108 // @event move: Event
3109 // Fired repeatedly during any movement of the map, including pan and
3111 return this.fire('move', data);
3114 _moveEnd: function (zoomChanged) {
3115 // @event zoomend: Event
3116 // Fired when the map has changed, after any animations.
3118 this.fire('zoomend');
3121 // @event moveend: Event
3122 // Fired when the center of the map stops changing (e.g. user stopped
3123 // dragging the map).
3124 return this.fire('moveend');
3127 _stop: function () {
3128 L.Util.cancelAnimFrame(this._flyToFrame);
3129 if (this._panAnim) {
3130 this._panAnim.stop();
3135 _rawPanBy: function (offset) {
3136 L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
3139 _getZoomSpan: function () {
3140 return this.getMaxZoom() - this.getMinZoom();
3143 _panInsideMaxBounds: function () {
3144 if (!this._enforcingBounds) {
3145 this.panInsideBounds(this.options.maxBounds);
3149 _checkIfLoaded: function () {
3150 if (!this._loaded) {
3151 throw new Error('Set map center and zoom first.');
3155 // DOM event handling
3157 // @section Interaction events
3158 _initEvents: function (remove) {
3159 if (!L.DomEvent) { return; }
3162 this._targets[L.stamp(this._container)] = this;
3164 var onOff = remove ? 'off' : 'on';
3166 // @event click: MouseEvent
3167 // Fired when the user clicks (or taps) the map.
3168 // @event dblclick: MouseEvent
3169 // Fired when the user double-clicks (or double-taps) the map.
3170 // @event mousedown: MouseEvent
3171 // Fired when the user pushes the mouse button on the map.
3172 // @event mouseup: MouseEvent
3173 // Fired when the user releases the mouse button on the map.
3174 // @event mouseover: MouseEvent
3175 // Fired when the mouse enters the map.
3176 // @event mouseout: MouseEvent
3177 // Fired when the mouse leaves the map.
3178 // @event mousemove: MouseEvent
3179 // Fired while the mouse moves over the map.
3180 // @event contextmenu: MouseEvent
3181 // Fired when the user pushes the right mouse button on the map, prevents
3182 // default browser context menu from showing if there are listeners on
3183 // this event. Also fired on mobile when the user holds a single touch
3184 // for a second (also called long press).
3185 // @event keypress: KeyboardEvent
3186 // Fired when the user presses a key from the keyboard while the map is focused.
3187 L.DomEvent[onOff](this._container, 'click dblclick mousedown mouseup ' +
3188 'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);
3190 if (this.options.trackResize) {
3191 L.DomEvent[onOff](window, 'resize', this._onResize, this);
3194 if (L.Browser.any3d && this.options.transform3DLimit) {
3195 this[onOff]('moveend', this._onMoveEnd);
3199 _onResize: function () {
3200 L.Util.cancelAnimFrame(this._resizeRequest);
3201 this._resizeRequest = L.Util.requestAnimFrame(
3202 function () { this.invalidateSize({debounceMoveend: true}); }, this);
3205 _onScroll: function () {
3206 this._container.scrollTop = 0;
3207 this._container.scrollLeft = 0;
3210 _onMoveEnd: function () {
3211 var pos = this._getMapPanePos();
3212 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
3213 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
3214 // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
3215 this._resetView(this.getCenter(), this.getZoom());
3219 _findEventTargets: function (e, type) {
3222 isHover = type === 'mouseout' || type === 'mouseover',
3223 src = e.target || e.srcElement,
3227 target = this._targets[L.stamp(src)];
3228 if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
3229 // Prevent firing click after you just dragged an object.
3233 if (target && target.listens(type, true)) {
3234 if (isHover && !L.DomEvent._isExternalTarget(src, e)) { break; }
3235 targets.push(target);
3236 if (isHover) { break; }
3238 if (src === this._container) { break; }
3239 src = src.parentNode;
3241 if (!targets.length && !dragging && !isHover && L.DomEvent._isExternalTarget(src, e)) {
3247 _handleDOMEvent: function (e) {
3248 if (!this._loaded || L.DomEvent._skipped(e)) { return; }
3250 var type = e.type === 'keypress' && e.keyCode === 13 ? 'click' : e.type;
3252 if (type === 'mousedown') {
3253 // prevents outline when clicking on keyboard-focusable element
3254 L.DomUtil.preventOutline(e.target || e.srcElement);
3257 this._fireDOMEvent(e, type);
3260 _fireDOMEvent: function (e, type, targets) {
3262 if (e.type === 'click') {
3263 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
3264 // @event preclick: MouseEvent
3265 // Fired before mouse click on the map (sometimes useful when you
3266 // want something to happen on click before any existing click
3267 // handlers start running).
3268 var synth = L.Util.extend({}, e);
3269 synth.type = 'preclick';
3270 this._fireDOMEvent(synth, synth.type, targets);
3273 if (e._stopped) { return; }
3275 // Find the layer the event is propagating from and its parents.
3276 targets = (targets || []).concat(this._findEventTargets(e, type));
3278 if (!targets.length) { return; }
3280 var target = targets[0];
3281 if (type === 'contextmenu' && target.listens(type, true)) {
3282 L.DomEvent.preventDefault(e);
3289 if (e.type !== 'keypress') {
3290 var isMarker = target instanceof L.Marker;
3291 data.containerPoint = isMarker ?
3292 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
3293 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
3294 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
3297 for (var i = 0; i < targets.length; i++) {
3298 targets[i].fire(type, data, true);
3299 if (data.originalEvent._stopped ||
3300 (targets[i].options.nonBubblingEvents && L.Util.indexOf(targets[i].options.nonBubblingEvents, type) !== -1)) { return; }
3304 _draggableMoved: function (obj) {
3305 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
3306 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
3309 _clearHandlers: function () {
3310 for (var i = 0, len = this._handlers.length; i < len; i++) {
3311 this._handlers[i].disable();
3315 // @section Other Methods
3317 // @method whenReady(fn: Function, context?: Object): this
3318 // Runs the given function `fn` when the map gets initialized with
3319 // a view (center and zoom) and at least one layer, or immediately
3320 // if it's already initialized, optionally passing a function context.
3321 whenReady: function (callback, context) {
3323 callback.call(context || this, {target: this});
3325 this.on('load', callback, context);
3331 // private methods for getting map state
3333 _getMapPanePos: function () {
3334 return L.DomUtil.getPosition(this._mapPane) || new L.Point(0, 0);
3337 _moved: function () {
3338 var pos = this._getMapPanePos();
3339 return pos && !pos.equals([0, 0]);
3342 _getTopLeftPoint: function (center, zoom) {
3343 var pixelOrigin = center && zoom !== undefined ?
3344 this._getNewPixelOrigin(center, zoom) :
3345 this.getPixelOrigin();
3346 return pixelOrigin.subtract(this._getMapPanePos());
3349 _getNewPixelOrigin: function (center, zoom) {
3350 var viewHalf = this.getSize()._divideBy(2);
3351 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
3354 _latLngToNewLayerPoint: function (latlng, zoom, center) {
3355 var topLeft = this._getNewPixelOrigin(center, zoom);
3356 return this.project(latlng, zoom)._subtract(topLeft);
3359 // layer point of the current center
3360 _getCenterLayerPoint: function () {
3361 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
3364 // offset of the specified place to the current center in pixels
3365 _getCenterOffset: function (latlng) {
3366 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
3369 // adjust center for view to get inside bounds
3370 _limitCenter: function (center, zoom, bounds) {
3372 if (!bounds) { return center; }
3374 var centerPoint = this.project(center, zoom),
3375 viewHalf = this.getSize().divideBy(2),
3376 viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
3377 offset = this._getBoundsOffset(viewBounds, bounds, zoom);
3379 // If offset is less than a pixel, ignore.
3380 // This prevents unstable projections from getting into
3381 // an infinite loop of tiny offsets.
3382 if (offset.round().equals([0, 0])) {
3386 return this.unproject(centerPoint.add(offset), zoom);
3389 // adjust offset for view to get inside bounds
3390 _limitOffset: function (offset, bounds) {
3391 if (!bounds) { return offset; }
3393 var viewBounds = this.getPixelBounds(),
3394 newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
3396 return offset.add(this._getBoundsOffset(newBounds, bounds));
3399 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
3400 _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
3401 var projectedMaxBounds = L.bounds(
3402 this.project(maxBounds.getNorthEast(), zoom),
3403 this.project(maxBounds.getSouthWest(), zoom)
3405 minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
3406 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
3408 dx = this._rebound(minOffset.x, -maxOffset.x),
3409 dy = this._rebound(minOffset.y, -maxOffset.y);
3411 return new L.Point(dx, dy);
3414 _rebound: function (left, right) {
3415 return left + right > 0 ?
3416 Math.round(left - right) / 2 :
3417 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
3420 _limitZoom: function (zoom) {
3421 var min = this.getMinZoom(),
3422 max = this.getMaxZoom(),
3423 snap = L.Browser.any3d ? this.options.zoomSnap : 1;
3425 zoom = Math.round(zoom / snap) * snap;
3427 return Math.max(min, Math.min(max, zoom));
3433 // @factory L.map(id: String, options?: Map options)
3434 // Instantiates a map object given the DOM ID of a `<div>` element
3435 // and optionally an object literal with `Map options`.
3438 // @factory L.map(el: HTMLElement, options?: Map options)
3439 // Instantiates a map object given an instance of a `<div>` HTML element
3440 // and optionally an object literal with `Map options`.
3441 L.map = function (id, options) {
3442 return new L.Map(id, options);
3454 * A set of methods from the Layer base class that all Leaflet layers use.
3455 * Inherits all methods, options and events from `L.Evented`.
3460 * var layer = L.Marker(latlng).addTo(map);
3466 * Fired after the layer is added to a map
3468 * @event remove: Event
3469 * Fired after the layer is removed from a map
3473 L.Layer = L.Evented.extend({
3475 // Classes extending `L.Layer` will inherit the following options:
3477 // @option pane: String = 'overlayPane'
3478 // By default the layer will be added to the map's [overlay pane](#map-overlaypane). Overriding this option will cause the layer to be placed on another pane by default.
3479 pane: 'overlayPane',
3480 nonBubblingEvents: [] // Array of events that should not be bubbled to DOM parents (like the map)
3484 * Classes extending `L.Layer` will inherit the following methods:
3486 * @method addTo(map: Map): this
3487 * Adds the layer to the given map
3489 addTo: function (map) {
3494 // @method remove: this
3495 // Removes the layer from the map it is currently active on.
3496 remove: function () {
3497 return this.removeFrom(this._map || this._mapToAdd);
3500 // @method removeFrom(map: Map): this
3501 // Removes the layer from the given map
3502 removeFrom: function (obj) {
3504 obj.removeLayer(this);
3509 // @method getPane(name? : String): HTMLElement
3510 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
3511 getPane: function (name) {
3512 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
3515 addInteractiveTarget: function (targetEl) {
3516 this._map._targets[L.stamp(targetEl)] = this;
3520 removeInteractiveTarget: function (targetEl) {
3521 delete this._map._targets[L.stamp(targetEl)];
3525 _layerAdd: function (e) {
3528 // check in case layer gets added and then removed before the map is ready
3529 if (!map.hasLayer(this)) { return; }
3532 this._zoomAnimated = map._zoomAnimated;
3534 if (this.getEvents) {
3535 var events = this.getEvents();
3536 map.on(events, this);
3537 this.once('remove', function () {
3538 map.off(events, this);
3544 if (this.getAttribution && this._map.attributionControl) {
3545 this._map.attributionControl.addAttribution(this.getAttribution());
3549 map.fire('layeradd', {layer: this});
3553 /* @section Extension methods
3556 * Every layer should extend from `L.Layer` and (re-)implement the following methods.
3558 * @method onAdd(map: Map): this
3559 * Should contain code that creates DOM elements for the layer, adds them to `map panes` where they should belong and puts listeners on relevant map events. Called on [`map.addLayer(layer)`](#map-addlayer).
3561 * @method onRemove(map: Map): this
3562 * Should contain all clean up code that removes the layer's elements from the DOM and removes listeners previously added in [`onAdd`](#layer-onadd). Called on [`map.removeLayer(layer)`](#map-removelayer).
3564 * @method getEvents(): Object
3565 * This optional method should return an object like `{ viewreset: this._reset }` for [`addEventListener`](#evented-addeventlistener). The event handlers in this object will be automatically added and removed from the map with your layer.
3567 * @method getAttribution(): String
3568 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
3570 * @method beforeAdd(map: Map): this
3571 * Optional method. Called on [`map.addLayer(layer)`](#map-addlayer), before the layer is added to the map, before events are initialized, without waiting until the map is in a usable state. Use for early initialization only.
3576 * @section Layer events
3578 * @event layeradd: LayerEvent
3579 * Fired when a new layer is added to the map.
3581 * @event layerremove: LayerEvent
3582 * Fired when some layer is removed from the map
3584 * @section Methods for Layers and Controls
3587 // @method addLayer(layer: Layer): this
3588 // Adds the given layer to the map
3589 addLayer: function (layer) {
3590 var id = L.stamp(layer);
3591 if (this._layers[id]) { return this; }
3592 this._layers[id] = layer;
3594 layer._mapToAdd = this;
3596 if (layer.beforeAdd) {
3597 layer.beforeAdd(this);
3600 this.whenReady(layer._layerAdd, layer);
3605 // @method removeLayer(layer: Layer): this
3606 // Removes the given layer from the map.
3607 removeLayer: function (layer) {
3608 var id = L.stamp(layer);
3610 if (!this._layers[id]) { return this; }
3613 layer.onRemove(this);
3616 if (layer.getAttribution && this.attributionControl) {
3617 this.attributionControl.removeAttribution(layer.getAttribution());
3620 delete this._layers[id];
3623 this.fire('layerremove', {layer: layer});
3624 layer.fire('remove');
3627 layer._map = layer._mapToAdd = null;
3632 // @method hasLayer(layer: Layer): Boolean
3633 // Returns `true` if the given layer is currently added to the map
3634 hasLayer: function (layer) {
3635 return !!layer && (L.stamp(layer) in this._layers);
3638 /* @method eachLayer(fn: Function, context?: Object): this
3639 * Iterates over the layers of the map, optionally specifying context of the iterator function.
3641 * map.eachLayer(function(layer){
3642 * layer.bindPopup('Hello');
3646 eachLayer: function (method, context) {
3647 for (var i in this._layers) {
3648 method.call(context, this._layers[i]);
3653 _addLayers: function (layers) {
3654 layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];
3656 for (var i = 0, len = layers.length; i < len; i++) {
3657 this.addLayer(layers[i]);
3661 _addZoomLimit: function (layer) {
3662 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
3663 this._zoomBoundLayers[L.stamp(layer)] = layer;
3664 this._updateZoomLevels();
3668 _removeZoomLimit: function (layer) {
3669 var id = L.stamp(layer);
3671 if (this._zoomBoundLayers[id]) {
3672 delete this._zoomBoundLayers[id];
3673 this._updateZoomLevels();
3677 _updateZoomLevels: function () {
3678 var minZoom = Infinity,
3679 maxZoom = -Infinity,
3680 oldZoomSpan = this._getZoomSpan();
3682 for (var i in this._zoomBoundLayers) {
3683 var options = this._zoomBoundLayers[i].options;
3685 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
3686 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
3689 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
3690 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
3692 // @section Map state change events
3693 // @event zoomlevelschange: Event
3694 // Fired when the number of zoomlevels on the map is changed due
3695 // to adding or removing a layer.
3696 if (oldZoomSpan !== this._getZoomSpan()) {
3697 this.fire('zoomlevelschange');
3705 * @namespace Projection
3706 * @projection L.Projection.Mercator
3708 * Elliptical Mercator projection — more complex than Spherical Mercator. Takes into account that Earth is a geoid, not a perfect sphere. Used by the EPSG:3395 CRS.
3711 L.Projection.Mercator = {
3713 R_MINOR: 6356752.314245179,
3715 bounds: L.bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
3717 project: function (latlng) {
3718 var d = Math.PI / 180,
3721 tmp = this.R_MINOR / r,
3722 e = Math.sqrt(1 - tmp * tmp),
3723 con = e * Math.sin(y);
3725 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
3726 y = -r * Math.log(Math.max(ts, 1E-10));
3728 return new L.Point(latlng.lng * d * r, y);
3731 unproject: function (point) {
3732 var d = 180 / Math.PI,
3734 tmp = this.R_MINOR / r,
3735 e = Math.sqrt(1 - tmp * tmp),
3736 ts = Math.exp(-point.y / r),
3737 phi = Math.PI / 2 - 2 * Math.atan(ts);
3739 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
3740 con = e * Math.sin(phi);
3741 con = Math.pow((1 - con) / (1 + con), e / 2);
3742 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
3746 return new L.LatLng(phi * d, point.x * d / r);
3754 * @crs L.CRS.EPSG3395
3756 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
3759 L.CRS.EPSG3395 = L.extend({}, L.CRS.Earth, {
3761 projection: L.Projection.Mercator,
3763 transformation: (function () {
3764 var scale = 0.5 / (Math.PI * L.Projection.Mercator.R);
3765 return new L.Transformation(scale, 0.5, -scale, 0.5);
3776 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
3777 * GridLayer can be extended to create a tiled grid of HTML elements like `<canvas>`, `<img>` or `<div>`. GridLayer will handle creating and animating these DOM elements for you.
3780 * @section Synchronous usage
3783 * To create a custom layer, extend GridLayer and implement the `createTile()` method, which will be passed a `Point` object with the `x`, `y`, and `z` (zoom level) coordinates to draw your tile.
3786 * var CanvasLayer = L.GridLayer.extend({
3787 * createTile: function(coords){
3788 * // create a <canvas> element for drawing
3789 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
3791 * // setup tile width and height according to the options
3792 * var size = this.getTileSize();
3793 * tile.width = size.x;
3794 * tile.height = size.y;
3796 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z
3797 * var ctx = tile.getContext('2d');
3799 * // return the tile so it can be rendered on screen
3805 * @section Asynchronous usage
3808 * Tile creation can also be asynchronous, this is useful when using a third-party drawing library. Once the tile is finished drawing it can be passed to the `done()` callback.
3811 * var CanvasLayer = L.GridLayer.extend({
3812 * createTile: function(coords, done){
3815 * // create a <canvas> element for drawing
3816 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
3818 * // setup tile width and height according to the options
3819 * var size = this.getTileSize();
3820 * tile.width = size.x;
3821 * tile.height = size.y;
3823 * // draw something asynchronously and pass the tile to the done() callback
3824 * setTimeout(function() {
3825 * done(error, tile);
3837 L.GridLayer = L.Layer.extend({
3840 // @aka GridLayer options
3842 // @option tileSize: Number|Point = 256
3843 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
3846 // @option opacity: Number = 1.0
3847 // Opacity of the tiles. Can be used in the `createTile()` function.
3850 // @option updateWhenIdle: Boolean = depends
3851 // If `false`, new tiles are loaded during panning, otherwise only after it (for better performance). `true` by default on mobile browsers, otherwise `false`.
3852 updateWhenIdle: L.Browser.mobile,
3854 // @option updateWhenZooming: Boolean = true
3855 // By default, a smooth zoom animation (during a [touch zoom](#map-touchzoom) or a [`flyTo()`](#map-flyto)) will update grid layers every integer zoom level. Setting this option to `false` will update the grid layer only when the smooth animation ends.
3856 updateWhenZooming: true,
3858 // @option updateInterval: Number = 200
3859 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
3860 updateInterval: 200,
3862 // @option attribution: String = null
3863 // String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox".
3866 // @option zIndex: Number = 1
3867 // The explicit zIndex of the tile layer.
3870 // @option bounds: LatLngBounds = undefined
3871 // If set, tiles will only be loaded inside the set `LatLngBounds`.
3874 // @option minZoom: Number = 0
3875 // The minimum zoom level that tiles will be loaded at. By default the entire map.
3878 // @option maxZoom: Number = undefined
3879 // The maximum zoom level that tiles will be loaded at.
3882 // @option noWrap: Boolean = false
3883 // Whether the layer is wrapped around the antimeridian. If `true`, the
3884 // GridLayer will only be displayed once at low zoom levels. Has no
3885 // effect when the [map CRS](#map-crs) doesn't wrap around.
3888 // @option pane: String = 'tilePane'
3889 // `Map pane` where the grid layer will be added.
3892 // @option className: String = ''
3893 // A custom class name to assign to the tile layer. Empty by default.
3896 // @option keepBuffer: Number = 2
3897 // When panning the map, keep this many rows and columns of tiles before unloading them.
3901 initialize: function (options) {
3902 L.setOptions(this, options);
3905 onAdd: function () {
3906 this._initContainer();
3915 beforeAdd: function (map) {
3916 map._addZoomLimit(this);
3919 onRemove: function (map) {
3920 this._removeAllTiles();
3921 L.DomUtil.remove(this._container);
3922 map._removeZoomLimit(this);
3923 this._container = null;
3924 this._tileZoom = null;
3927 // @method bringToFront: this
3928 // Brings the tile layer to the top of all tile layers.
3929 bringToFront: function () {
3931 L.DomUtil.toFront(this._container);
3932 this._setAutoZIndex(Math.max);
3937 // @method bringToBack: this
3938 // Brings the tile layer to the bottom of all tile layers.
3939 bringToBack: function () {
3941 L.DomUtil.toBack(this._container);
3942 this._setAutoZIndex(Math.min);
3947 // @method getAttribution: String
3948 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
3949 getAttribution: function () {
3950 return this.options.attribution;
3953 // @method getContainer: HTMLElement
3954 // Returns the HTML element that contains the tiles for this layer.
3955 getContainer: function () {
3956 return this._container;
3959 // @method setOpacity(opacity: Number): this
3960 // Changes the [opacity](#gridlayer-opacity) of the grid layer.
3961 setOpacity: function (opacity) {
3962 this.options.opacity = opacity;
3963 this._updateOpacity();
3967 // @method setZIndex(zIndex: Number): this
3968 // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
3969 setZIndex: function (zIndex) {
3970 this.options.zIndex = zIndex;
3971 this._updateZIndex();
3976 // @method isLoading: Boolean
3977 // Returns `true` if any tile in the grid layer has not finished loading.
3978 isLoading: function () {
3979 return this._loading;
3982 // @method redraw: this
3983 // Causes the layer to clear all the tiles and request them again.
3984 redraw: function () {
3986 this._removeAllTiles();
3992 getEvents: function () {
3994 viewprereset: this._invalidateAll,
3995 viewreset: this._resetView,
3996 zoom: this._resetView,
3997 moveend: this._onMoveEnd
4000 if (!this.options.updateWhenIdle) {
4001 // update tiles on move, but not more often than once per given interval
4002 if (!this._onMove) {
4003 this._onMove = L.Util.throttle(this._onMoveEnd, this.options.updateInterval, this);
4006 events.move = this._onMove;
4009 if (this._zoomAnimated) {
4010 events.zoomanim = this._animateZoom;
4016 // @section Extension methods
4017 // Layers extending `GridLayer` shall reimplement the following method.
4018 // @method createTile(coords: Object, done?: Function): HTMLElement
4019 // Called only internally, must be overriden by classes extending `GridLayer`.
4020 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
4021 // is specified, it must be called when the tile has finished loading and drawing.
4022 createTile: function () {
4023 return document.createElement('div');
4027 // @method getTileSize: Point
4028 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
4029 getTileSize: function () {
4030 var s = this.options.tileSize;
4031 return s instanceof L.Point ? s : new L.Point(s, s);
4034 _updateZIndex: function () {
4035 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
4036 this._container.style.zIndex = this.options.zIndex;
4040 _setAutoZIndex: function (compare) {
4041 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
4043 var layers = this.getPane().children,
4044 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
4046 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
4048 zIndex = layers[i].style.zIndex;
4050 if (layers[i] !== this._container && zIndex) {
4051 edgeZIndex = compare(edgeZIndex, +zIndex);
4055 if (isFinite(edgeZIndex)) {
4056 this.options.zIndex = edgeZIndex + compare(-1, 1);
4057 this._updateZIndex();
4061 _updateOpacity: function () {
4062 if (!this._map) { return; }
4064 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
4065 if (L.Browser.ielt9) { return; }
4067 L.DomUtil.setOpacity(this._container, this.options.opacity);
4069 var now = +new Date(),
4073 for (var key in this._tiles) {
4074 var tile = this._tiles[key];
4075 if (!tile.current || !tile.loaded) { continue; }
4077 var fade = Math.min(1, (now - tile.loaded) / 200);
4079 L.DomUtil.setOpacity(tile.el, fade);
4083 if (tile.active) { willPrune = true; }
4088 if (willPrune && !this._noPrune) { this._pruneTiles(); }
4091 L.Util.cancelAnimFrame(this._fadeFrame);
4092 this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this);
4096 _initContainer: function () {
4097 if (this._container) { return; }
4099 this._container = L.DomUtil.create('div', 'leaflet-layer ' + (this.options.className || ''));
4100 this._updateZIndex();
4102 if (this.options.opacity < 1) {
4103 this._updateOpacity();
4106 this.getPane().appendChild(this._container);
4109 _updateLevels: function () {
4111 var zoom = this._tileZoom,
4112 maxZoom = this.options.maxZoom;
4114 if (zoom === undefined) { return undefined; }
4116 for (var z in this._levels) {
4117 if (this._levels[z].el.children.length || z === zoom) {
4118 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
4120 L.DomUtil.remove(this._levels[z].el);
4121 this._removeTilesAtZoom(z);
4122 delete this._levels[z];
4126 var level = this._levels[zoom],
4130 level = this._levels[zoom] = {};
4132 level.el = L.DomUtil.create('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
4133 level.el.style.zIndex = maxZoom;
4135 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
4138 this._setZoomTransform(level, map.getCenter(), map.getZoom());
4140 // force the browser to consider the newly added element for transition
4141 L.Util.falseFn(level.el.offsetWidth);
4144 this._level = level;
4149 _pruneTiles: function () {
4156 var zoom = this._map.getZoom();
4157 if (zoom > this.options.maxZoom ||
4158 zoom < this.options.minZoom) {
4159 this._removeAllTiles();
4163 for (key in this._tiles) {
4164 tile = this._tiles[key];
4165 tile.retain = tile.current;
4168 for (key in this._tiles) {
4169 tile = this._tiles[key];
4170 if (tile.current && !tile.active) {
4171 var coords = tile.coords;
4172 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
4173 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
4178 for (key in this._tiles) {
4179 if (!this._tiles[key].retain) {
4180 this._removeTile(key);
4185 _removeTilesAtZoom: function (zoom) {
4186 for (var key in this._tiles) {
4187 if (this._tiles[key].coords.z !== zoom) {
4190 this._removeTile(key);
4194 _removeAllTiles: function () {
4195 for (var key in this._tiles) {
4196 this._removeTile(key);
4200 _invalidateAll: function () {
4201 for (var z in this._levels) {
4202 L.DomUtil.remove(this._levels[z].el);
4203 delete this._levels[z];
4205 this._removeAllTiles();
4207 this._tileZoom = null;
4210 _retainParent: function (x, y, z, minZoom) {
4211 var x2 = Math.floor(x / 2),
4212 y2 = Math.floor(y / 2),
4214 coords2 = new L.Point(+x2, +y2);
4217 var key = this._tileCoordsToKey(coords2),
4218 tile = this._tiles[key];
4220 if (tile && tile.active) {
4224 } else if (tile && tile.loaded) {
4229 return this._retainParent(x2, y2, z2, minZoom);
4235 _retainChildren: function (x, y, z, maxZoom) {
4237 for (var i = 2 * x; i < 2 * x + 2; i++) {
4238 for (var j = 2 * y; j < 2 * y + 2; j++) {
4240 var coords = new L.Point(i, j);
4243 var key = this._tileCoordsToKey(coords),
4244 tile = this._tiles[key];
4246 if (tile && tile.active) {
4250 } else if (tile && tile.loaded) {
4254 if (z + 1 < maxZoom) {
4255 this._retainChildren(i, j, z + 1, maxZoom);
4261 _resetView: function (e) {
4262 var animating = e && (e.pinch || e.flyTo);
4263 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
4266 _animateZoom: function (e) {
4267 this._setView(e.center, e.zoom, true, e.noUpdate);
4270 _setView: function (center, zoom, noPrune, noUpdate) {
4271 var tileZoom = Math.round(zoom);
4272 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
4273 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
4274 tileZoom = undefined;
4277 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
4279 if (!noUpdate || tileZoomChanged) {
4281 this._tileZoom = tileZoom;
4283 if (this._abortLoading) {
4284 this._abortLoading();
4287 this._updateLevels();
4290 if (tileZoom !== undefined) {
4291 this._update(center);
4298 // Flag to prevent _updateOpacity from pruning tiles during
4299 // a zoom anim or a pinch gesture
4300 this._noPrune = !!noPrune;
4303 this._setZoomTransforms(center, zoom);
4306 _setZoomTransforms: function (center, zoom) {
4307 for (var i in this._levels) {
4308 this._setZoomTransform(this._levels[i], center, zoom);
4312 _setZoomTransform: function (level, center, zoom) {
4313 var scale = this._map.getZoomScale(zoom, level.zoom),
4314 translate = level.origin.multiplyBy(scale)
4315 .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
4317 if (L.Browser.any3d) {
4318 L.DomUtil.setTransform(level.el, translate, scale);
4320 L.DomUtil.setPosition(level.el, translate);
4324 _resetGrid: function () {
4325 var map = this._map,
4326 crs = map.options.crs,
4327 tileSize = this._tileSize = this.getTileSize(),
4328 tileZoom = this._tileZoom;
4330 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
4332 this._globalTileRange = this._pxBoundsToTileRange(bounds);
4335 this._wrapX = crs.wrapLng && !this.options.noWrap && [
4336 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
4337 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
4339 this._wrapY = crs.wrapLat && !this.options.noWrap && [
4340 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
4341 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
4345 _onMoveEnd: function () {
4346 if (!this._map || this._map._animatingZoom) { return; }
4351 _getTiledPixelBounds: function (center) {
4352 var map = this._map,
4353 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
4354 scale = map.getZoomScale(mapZoom, this._tileZoom),
4355 pixelCenter = map.project(center, this._tileZoom).floor(),
4356 halfSize = map.getSize().divideBy(scale * 2);
4358 return new L.Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
4361 // Private method to load tiles in the grid's active zoom level according to map bounds
4362 _update: function (center) {
4363 var map = this._map;
4364 if (!map) { return; }
4365 var zoom = map.getZoom();
4367 if (center === undefined) { center = map.getCenter(); }
4368 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom
4370 var pixelBounds = this._getTiledPixelBounds(center),
4371 tileRange = this._pxBoundsToTileRange(pixelBounds),
4372 tileCenter = tileRange.getCenter(),
4374 margin = this.options.keepBuffer,
4375 noPruneRange = new L.Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
4376 tileRange.getTopRight().add([margin, -margin]));
4378 for (var key in this._tiles) {
4379 var c = this._tiles[key].coords;
4380 if (c.z !== this._tileZoom || !noPruneRange.contains(L.point(c.x, c.y))) {
4381 this._tiles[key].current = false;
4385 // _update just loads more tiles. If the tile zoom level differs too much
4386 // from the map's, let _setView reset levels and prune old tiles.
4387 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
4389 // create a queue of coordinates to load tiles from
4390 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
4391 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
4392 var coords = new L.Point(i, j);
4393 coords.z = this._tileZoom;
4395 if (!this._isValidTile(coords)) { continue; }
4397 var tile = this._tiles[this._tileCoordsToKey(coords)];
4399 tile.current = true;
4406 // sort tile queue to load tiles in order of their distance to center
4407 queue.sort(function (a, b) {
4408 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
4411 if (queue.length !== 0) {
4412 // if its the first batch of tiles to load
4413 if (!this._loading) {
4414 this._loading = true;
4415 // @event loading: Event
4416 // Fired when the grid layer starts loading tiles.
4417 this.fire('loading');
4420 // create DOM fragment to append tiles in one batch
4421 var fragment = document.createDocumentFragment();
4423 for (i = 0; i < queue.length; i++) {
4424 this._addTile(queue[i], fragment);
4427 this._level.el.appendChild(fragment);
4431 _isValidTile: function (coords) {
4432 var crs = this._map.options.crs;
4434 if (!crs.infinite) {
4435 // don't load tile if it's out of bounds and not wrapped
4436 var bounds = this._globalTileRange;
4437 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
4438 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
4441 if (!this.options.bounds) { return true; }
4443 // don't load tile if it doesn't intersect the bounds in options
4444 var tileBounds = this._tileCoordsToBounds(coords);
4445 return L.latLngBounds(this.options.bounds).overlaps(tileBounds);
4448 _keyToBounds: function (key) {
4449 return this._tileCoordsToBounds(this._keyToTileCoords(key));
4452 // converts tile coordinates to its geographical bounds
4453 _tileCoordsToBounds: function (coords) {
4455 var map = this._map,
4456 tileSize = this.getTileSize(),
4458 nwPoint = coords.scaleBy(tileSize),
4459 sePoint = nwPoint.add(tileSize),
4461 nw = map.unproject(nwPoint, coords.z),
4462 se = map.unproject(sePoint, coords.z);
4464 if (!this.options.noWrap) {
4465 nw = map.wrapLatLng(nw);
4466 se = map.wrapLatLng(se);
4469 return new L.LatLngBounds(nw, se);
4472 // converts tile coordinates to key for the tile cache
4473 _tileCoordsToKey: function (coords) {
4474 return coords.x + ':' + coords.y + ':' + coords.z;
4477 // converts tile cache key to coordinates
4478 _keyToTileCoords: function (key) {
4479 var k = key.split(':'),
4480 coords = new L.Point(+k[0], +k[1]);
4485 _removeTile: function (key) {
4486 var tile = this._tiles[key];
4487 if (!tile) { return; }
4489 L.DomUtil.remove(tile.el);
4491 delete this._tiles[key];
4493 // @event tileunload: TileEvent
4494 // Fired when a tile is removed (e.g. when a tile goes off the screen).
4495 this.fire('tileunload', {
4497 coords: this._keyToTileCoords(key)
4501 _initTile: function (tile) {
4502 L.DomUtil.addClass(tile, 'leaflet-tile');
4504 var tileSize = this.getTileSize();
4505 tile.style.width = tileSize.x + 'px';
4506 tile.style.height = tileSize.y + 'px';
4508 tile.onselectstart = L.Util.falseFn;
4509 tile.onmousemove = L.Util.falseFn;
4511 // update opacity on tiles in IE7-8 because of filter inheritance problems
4512 if (L.Browser.ielt9 && this.options.opacity < 1) {
4513 L.DomUtil.setOpacity(tile, this.options.opacity);
4516 // without this hack, tiles disappear after zoom on Chrome for Android
4517 // https://github.com/Leaflet/Leaflet/issues/2078
4518 if (L.Browser.android && !L.Browser.android23) {
4519 tile.style.WebkitBackfaceVisibility = 'hidden';
4523 _addTile: function (coords, container) {
4524 var tilePos = this._getTilePos(coords),
4525 key = this._tileCoordsToKey(coords);
4527 var tile = this.createTile(this._wrapCoords(coords), L.bind(this._tileReady, this, coords));
4529 this._initTile(tile);
4531 // if createTile is defined with a second argument ("done" callback),
4532 // we know that tile is async and will be ready later; otherwise
4533 if (this.createTile.length < 2) {
4534 // mark tile as ready, but delay one frame for opacity animation to happen
4535 L.Util.requestAnimFrame(L.bind(this._tileReady, this, coords, null, tile));
4538 L.DomUtil.setPosition(tile, tilePos);
4540 // save tile in cache
4541 this._tiles[key] = {
4547 container.appendChild(tile);
4548 // @event tileloadstart: TileEvent
4549 // Fired when a tile is requested and starts loading.
4550 this.fire('tileloadstart', {
4556 _tileReady: function (coords, err, tile) {
4557 if (!this._map) { return; }
4560 // @event tileerror: TileErrorEvent
4561 // Fired when there is an error loading a tile.
4562 this.fire('tileerror', {
4569 var key = this._tileCoordsToKey(coords);
4571 tile = this._tiles[key];
4572 if (!tile) { return; }
4574 tile.loaded = +new Date();
4575 if (this._map._fadeAnimated) {
4576 L.DomUtil.setOpacity(tile.el, 0);
4577 L.Util.cancelAnimFrame(this._fadeFrame);
4578 this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this);
4584 L.DomUtil.addClass(tile.el, 'leaflet-tile-loaded');
4586 // @event tileload: TileEvent
4587 // Fired when a tile loads.
4588 this.fire('tileload', {
4593 if (this._noTilesToLoad()) {
4594 this._loading = false;
4595 // @event load: Event
4596 // Fired when the grid layer loaded all visible tiles.
4599 if (L.Browser.ielt9 || !this._map._fadeAnimated) {
4600 L.Util.requestAnimFrame(this._pruneTiles, this);
4602 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
4603 // to trigger a pruning.
4604 setTimeout(L.bind(this._pruneTiles, this), 250);
4609 _getTilePos: function (coords) {
4610 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
4613 _wrapCoords: function (coords) {
4614 var newCoords = new L.Point(
4615 this._wrapX ? L.Util.wrapNum(coords.x, this._wrapX) : coords.x,
4616 this._wrapY ? L.Util.wrapNum(coords.y, this._wrapY) : coords.y);
4617 newCoords.z = coords.z;
4621 _pxBoundsToTileRange: function (bounds) {
4622 var tileSize = this.getTileSize();
4623 return new L.Bounds(
4624 bounds.min.unscaleBy(tileSize).floor(),
4625 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
4628 _noTilesToLoad: function () {
4629 for (var key in this._tiles) {
4630 if (!this._tiles[key].loaded) { return false; }
4636 // @factory L.gridLayer(options?: GridLayer options)
4637 // Creates a new instance of GridLayer with the supplied options.
4638 L.gridLayer = function (options) {
4639 return new L.GridLayer(options);
4646 * @inherits GridLayer
4648 * Used to load and display tile layers on the map. Extends `GridLayer`.
4653 * L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map);
4656 * @section URL template
4659 * A string of the following form:
4662 * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
4665 * `{s}` means one of the available subdomains (used sequentially to help with browser parallel requests per domain limitation; subdomain values are specified in options; `a`, `b` or `c` by default, can be omitted), `{z}` — zoom level, `{x}` and `{y}` — tile coordinates. `{r}` can be used to add @2x to the URL to load retina tiles.
4667 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
4670 * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
4675 L.TileLayer = L.GridLayer.extend({
4678 // @aka TileLayer options
4680 // @option minZoom: Number = 0
4681 // Minimum zoom number.
4684 // @option maxZoom: Number = 18
4685 // Maximum zoom number.
4688 // @option maxNativeZoom: Number = null
4689 // Maximum zoom number the tile source has available. If it is specified,
4690 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
4691 // from `maxNativeZoom` level and auto-scaled.
4692 maxNativeZoom: null,
4694 // @option subdomains: String|String[] = 'abc'
4695 // Subdomains of the tile service. Can be passed in the form of one string (where each letter is a subdomain name) or an array of strings.
4698 // @option errorTileUrl: String = ''
4699 // URL to the tile image to show in place of the tile that failed to load.
4702 // @option zoomOffset: Number = 0
4703 // The zoom number used in tile URLs will be offset with this value.
4706 // @option tms: Boolean = false
4707 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
4710 // @option zoomReverse: Boolean = false
4711 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
4714 // @option detectRetina: Boolean = false
4715 // If `true` and user is on a retina display, it will request four tiles of half the specified size and a bigger zoom level in place of one to utilize the high resolution.
4716 detectRetina: false,
4718 // @option crossOrigin: Boolean = false
4719 // If true, all tiles will have their crossOrigin attribute set to ''. This is needed if you want to access tile pixel data.
4723 initialize: function (url, options) {
4727 options = L.setOptions(this, options);
4729 // detecting retina displays, adjusting tileSize and zoom levels
4730 if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {
4732 options.tileSize = Math.floor(options.tileSize / 2);
4734 if (!options.zoomReverse) {
4735 options.zoomOffset++;
4738 options.zoomOffset--;
4742 options.minZoom = Math.max(0, options.minZoom);
4745 if (typeof options.subdomains === 'string') {
4746 options.subdomains = options.subdomains.split('');
4749 // for https://github.com/Leaflet/Leaflet/issues/137
4750 if (!L.Browser.android) {
4751 this.on('tileunload', this._onTileRemove);
4755 // @method setUrl(url: String, noRedraw?: Boolean): this
4756 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
4757 setUrl: function (url, noRedraw) {
4766 // @method createTile(coords: Object, done?: Function): HTMLElement
4767 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
4768 // to return an `<img>` HTML element with the appropiate image URL given `coords`. The `done`
4769 // callback is called when the tile has been loaded.
4770 createTile: function (coords, done) {
4771 var tile = document.createElement('img');
4773 L.DomEvent.on(tile, 'load', L.bind(this._tileOnLoad, this, done, tile));
4774 L.DomEvent.on(tile, 'error', L.bind(this._tileOnError, this, done, tile));
4776 if (this.options.crossOrigin) {
4777 tile.crossOrigin = '';
4781 Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
4782 http://www.w3.org/TR/WCAG20-TECHS/H67
4786 tile.src = this.getTileUrl(coords);
4791 // @section Extension methods
4793 // Layers extending `TileLayer` might reimplement the following method.
4794 // @method getTileUrl(coords: Object): String
4795 // Called only internally, returns the URL for a tile given its coordinates.
4796 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
4797 getTileUrl: function (coords) {
4799 r: L.Browser.retina ? '@2x' : '',
4800 s: this._getSubdomain(coords),
4803 z: this._getZoomForUrl()
4805 if (this._map && !this._map.options.crs.infinite) {
4806 var invertedY = this._globalTileRange.max.y - coords.y;
4807 if (this.options.tms) {
4808 data['y'] = invertedY;
4810 data['-y'] = invertedY;
4813 return L.Util.template(this._url, L.extend(data, this.options));
4816 _tileOnLoad: function (done, tile) {
4817 // For https://github.com/Leaflet/Leaflet/issues/3332
4818 if (L.Browser.ielt9) {
4819 setTimeout(L.bind(done, this, null, tile), 0);
4825 _tileOnError: function (done, tile, e) {
4826 var errorUrl = this.options.errorTileUrl;
4828 tile.src = errorUrl;
4833 getTileSize: function () {
4834 var map = this._map,
4835 tileSize = L.GridLayer.prototype.getTileSize.call(this),
4836 zoom = this._tileZoom + this.options.zoomOffset,
4837 zoomN = this.options.maxNativeZoom;
4839 // increase tile size when overscaling
4840 return zoomN !== null && zoom > zoomN ?
4841 tileSize.divideBy(map.getZoomScale(zoomN, zoom)).round() :
4845 _onTileRemove: function (e) {
4846 e.tile.onload = null;
4849 _getZoomForUrl: function () {
4851 var options = this.options,
4852 zoom = this._tileZoom;
4854 if (options.zoomReverse) {
4855 zoom = options.maxZoom - zoom;
4858 zoom += options.zoomOffset;
4860 return options.maxNativeZoom !== null ? Math.min(zoom, options.maxNativeZoom) : zoom;
4863 _getSubdomain: function (tilePoint) {
4864 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
4865 return this.options.subdomains[index];
4868 // stops loading all tiles in the background layer
4869 _abortLoading: function () {
4871 for (i in this._tiles) {
4872 if (this._tiles[i].coords.z !== this._tileZoom) {
4873 tile = this._tiles[i].el;
4875 tile.onload = L.Util.falseFn;
4876 tile.onerror = L.Util.falseFn;
4878 if (!tile.complete) {
4879 tile.src = L.Util.emptyImageUrl;
4880 L.DomUtil.remove(tile);
4888 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
4889 // Instantiates a tile layer object given a `URL template` and optionally an options object.
4891 L.tileLayer = function (url, options) {
4892 return new L.TileLayer(url, options);
4898 * @class TileLayer.WMS
4899 * @inherits TileLayer
4900 * @aka L.TileLayer.WMS
4901 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
4906 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
4907 * layers: 'nexrad-n0r-900913',
4908 * format: 'image/png',
4909 * transparent: true,
4910 * attribution: "Weather data © 2012 IEM Nexrad"
4915 L.TileLayer.WMS = L.TileLayer.extend({
4918 // @aka TileLayer.WMS options
4919 // If any custom options not documented here are used, they will be sent to the
4920 // WMS server as extra parameters in each request URL. This can be useful for
4921 // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
4926 // @option layers: String = ''
4927 // **(required)** Comma-separated list of WMS layers to show.
4930 // @option styles: String = ''
4931 // Comma-separated list of WMS styles.
4934 // @option format: String = 'image/jpeg'
4935 // WMS image format (use `'image/png'` for layers with transparency).
4936 format: 'image/jpeg',
4938 // @option transparent: Boolean = false
4939 // If `true`, the WMS service will return images with transparency.
4942 // @option version: String = '1.1.1'
4943 // Version of the WMS service to use
4948 // @option crs: CRS = null
4949 // Coordinate Reference System to use for the WMS requests, defaults to
4950 // map CRS. Don't change this if you're not sure what it means.
4953 // @option uppercase: Boolean = false
4954 // If `true`, WMS request parameter keys will be uppercase.
4958 initialize: function (url, options) {
4962 var wmsParams = L.extend({}, this.defaultWmsParams);
4964 // all keys that are not TileLayer options go to WMS params
4965 for (var i in options) {
4966 if (!(i in this.options)) {
4967 wmsParams[i] = options[i];
4971 options = L.setOptions(this, options);
4973 wmsParams.width = wmsParams.height = options.tileSize * (options.detectRetina && L.Browser.retina ? 2 : 1);
4975 this.wmsParams = wmsParams;
4978 onAdd: function (map) {
4980 this._crs = this.options.crs || map.options.crs;
4981 this._wmsVersion = parseFloat(this.wmsParams.version);
4983 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
4984 this.wmsParams[projectionKey] = this._crs.code;
4986 L.TileLayer.prototype.onAdd.call(this, map);
4989 getTileUrl: function (coords) {
4991 var tileBounds = this._tileCoordsToBounds(coords),
4992 nw = this._crs.project(tileBounds.getNorthWest()),
4993 se = this._crs.project(tileBounds.getSouthEast()),
4995 bbox = (this._wmsVersion >= 1.3 && this._crs === L.CRS.EPSG4326 ?
4996 [se.y, nw.x, nw.y, se.x] :
4997 [nw.x, se.y, se.x, nw.y]).join(','),
4999 url = L.TileLayer.prototype.getTileUrl.call(this, coords);
5002 L.Util.getParamString(this.wmsParams, url, this.options.uppercase) +
5003 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
5006 // @method setParams(params: Object, noRedraw?: Boolean): this
5007 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
5008 setParams: function (params, noRedraw) {
5010 L.extend(this.wmsParams, params);
5021 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
5022 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
5023 L.tileLayer.wms = function (url, options) {
5024 return new L.TileLayer.WMS(url, options);
5030 * @class ImageOverlay
5031 * @aka L.ImageOverlay
5032 * @inherits Interactive layer
5034 * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
5039 * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
5040 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
5041 * L.imageOverlay(imageUrl, imageBounds).addTo(map);
5045 L.ImageOverlay = L.Layer.extend({
5048 // @aka ImageOverlay options
5050 // @option opacity: Number = 1.0
5051 // The opacity of the image overlay.
5054 // @option alt: String = ''
5055 // Text for the `alt` attribute of the image (useful for accessibility).
5058 // @option interactive: Boolean = false
5059 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
5062 // @option attribution: String = null
5063 // An optional string containing HTML to be shown on the `Attribution control`
5066 // @option crossOrigin: Boolean = false
5067 // If true, the image will have its crossOrigin attribute set to ''. This is needed if you want to access image pixel data.
5071 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
5073 this._bounds = L.latLngBounds(bounds);
5075 L.setOptions(this, options);
5078 onAdd: function () {
5082 if (this.options.opacity < 1) {
5083 this._updateOpacity();
5087 if (this.options.interactive) {
5088 L.DomUtil.addClass(this._image, 'leaflet-interactive');
5089 this.addInteractiveTarget(this._image);
5092 this.getPane().appendChild(this._image);
5096 onRemove: function () {
5097 L.DomUtil.remove(this._image);
5098 if (this.options.interactive) {
5099 this.removeInteractiveTarget(this._image);
5103 // @method setOpacity(opacity: Number): this
5104 // Sets the opacity of the overlay.
5105 setOpacity: function (opacity) {
5106 this.options.opacity = opacity;
5109 this._updateOpacity();
5114 setStyle: function (styleOpts) {
5115 if (styleOpts.opacity) {
5116 this.setOpacity(styleOpts.opacity);
5121 // @method bringToFront(): this
5122 // Brings the layer to the top of all overlays.
5123 bringToFront: function () {
5125 L.DomUtil.toFront(this._image);
5130 // @method bringToBack(): this
5131 // Brings the layer to the bottom of all overlays.
5132 bringToBack: function () {
5134 L.DomUtil.toBack(this._image);
5139 // @method setUrl(url: String): this
5140 // Changes the URL of the image.
5141 setUrl: function (url) {
5145 this._image.src = url;
5150 setBounds: function (bounds) {
5151 this._bounds = bounds;
5159 getAttribution: function () {
5160 return this.options.attribution;
5163 getEvents: function () {
5166 viewreset: this._reset
5169 if (this._zoomAnimated) {
5170 events.zoomanim = this._animateZoom;
5176 getBounds: function () {
5177 return this._bounds;
5180 getElement: function () {
5184 _initImage: function () {
5185 var img = this._image = L.DomUtil.create('img',
5186 'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : ''));
5188 img.onselectstart = L.Util.falseFn;
5189 img.onmousemove = L.Util.falseFn;
5191 img.onload = L.bind(this.fire, this, 'load');
5193 if (this.options.crossOrigin) {
5194 img.crossOrigin = '';
5197 img.src = this._url;
5198 img.alt = this.options.alt;
5201 _animateZoom: function (e) {
5202 var scale = this._map.getZoomScale(e.zoom),
5203 offset = this._map._latLngToNewLayerPoint(this._bounds.getNorthWest(), e.zoom, e.center);
5205 L.DomUtil.setTransform(this._image, offset, scale);
5208 _reset: function () {
5209 var image = this._image,
5210 bounds = new L.Bounds(
5211 this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
5212 this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
5213 size = bounds.getSize();
5215 L.DomUtil.setPosition(image, bounds.min);
5217 image.style.width = size.x + 'px';
5218 image.style.height = size.y + 'px';
5221 _updateOpacity: function () {
5222 L.DomUtil.setOpacity(this._image, this.options.opacity);
5226 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
5227 // Instantiates an image overlay object given the URL of the image and the
5228 // geographical bounds it is tied to.
5229 L.imageOverlay = function (url, bounds, options) {
5230 return new L.ImageOverlay(url, bounds, options);
5240 * Represents an icon to provide when creating a marker.
5245 * var myIcon = L.icon({
5246 * iconUrl: 'my-icon.png',
5247 * iconRetinaUrl: 'my-icon@2x.png',
5248 * iconSize: [38, 95],
5249 * iconAnchor: [22, 94],
5250 * popupAnchor: [-3, -76],
5251 * shadowUrl: 'my-icon-shadow.png',
5252 * shadowRetinaUrl: 'my-icon-shadow@2x.png',
5253 * shadowSize: [68, 95],
5254 * shadowAnchor: [22, 94]
5257 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
5260 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
5264 L.Icon = L.Class.extend({
5269 * @option iconUrl: String = null
5270 * **(required)** The URL to the icon image (absolute or relative to your script path).
5272 * @option iconRetinaUrl: String = null
5273 * The URL to a retina sized version of the icon image (absolute or relative to your
5274 * script path). Used for Retina screen devices.
5276 * @option iconSize: Point = null
5277 * Size of the icon image in pixels.
5279 * @option iconAnchor: Point = null
5280 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
5281 * will be aligned so that this point is at the marker's geographical location. Centered
5282 * by default if size is specified, also can be set in CSS with negative margins.
5284 * @option popupAnchor: Point = null
5285 * The coordinates of the point from which popups will "open", relative to the icon anchor.
5287 * @option shadowUrl: String = null
5288 * The URL to the icon shadow image. If not specified, no shadow image will be created.
5290 * @option shadowRetinaUrl: String = null
5292 * @option shadowSize: Point = null
5293 * Size of the shadow image in pixels.
5295 * @option shadowAnchor: Point = null
5296 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
5297 * as iconAnchor if not specified).
5299 * @option className: String = ''
5300 * A custom class name to assign to both icon and shadow images. Empty by default.
5303 initialize: function (options) {
5304 L.setOptions(this, options);
5307 // @method createIcon(oldIcon?: HTMLElement): HTMLElement
5308 // Called internally when the icon has to be shown, returns a `<img>` HTML element
5309 // styled according to the options.
5310 createIcon: function (oldIcon) {
5311 return this._createIcon('icon', oldIcon);
5314 // @method createShadow(oldIcon?: HTMLElement): HTMLElement
5315 // As `createIcon`, but for the shadow beneath it.
5316 createShadow: function (oldIcon) {
5317 return this._createIcon('shadow', oldIcon);
5320 _createIcon: function (name, oldIcon) {
5321 var src = this._getIconUrl(name);
5324 if (name === 'icon') {
5325 throw new Error('iconUrl not set in Icon options (see the docs).');
5330 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
5331 this._setIconStyles(img, name);
5336 _setIconStyles: function (img, name) {
5337 var options = this.options;
5338 var sizeOption = options[name + 'Size'];
5340 if (typeof sizeOption === 'number') {
5341 sizeOption = [sizeOption, sizeOption];
5344 var size = L.point(sizeOption),
5345 anchor = L.point(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
5346 size && size.divideBy(2, true));
5348 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
5351 img.style.marginLeft = (-anchor.x) + 'px';
5352 img.style.marginTop = (-anchor.y) + 'px';
5356 img.style.width = size.x + 'px';
5357 img.style.height = size.y + 'px';
5361 _createImg: function (src, el) {
5362 el = el || document.createElement('img');
5367 _getIconUrl: function (name) {
5368 return L.Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
5373 // @factory L.icon(options: Icon options)
5374 // Creates an icon instance with the given options.
5375 L.icon = function (options) {
5376 return new L.Icon(options);
5382 * @miniclass Icon.Default (Icon)
5383 * @aka L.Icon.Default
5386 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
5387 * no icon is specified. Points to the blue marker image distributed with Leaflet
5390 * In order to change the default icon, just change the properties of `L.Icon.Default.prototype.options`
5391 * (which is a set of `Icon options`).
5394 L.Icon.Default = L.Icon.extend({
5397 iconUrl: 'marker-icon.png',
5398 iconRetinaUrl: 'marker-icon-2x.png',
5399 shadowUrl: 'marker-shadow.png',
5401 iconAnchor: [12, 41],
5402 popupAnchor: [1, -34],
5403 tooltipAnchor: [16, -28],
5404 shadowSize: [41, 41]
5407 _getIconUrl: function (name) {
5408 if (!L.Icon.Default.imagePath) { // Deprecated, backwards-compatibility only
5409 L.Icon.Default.imagePath = this._detectIconPath();
5412 // @option imagePath: String
5413 // `L.Icon.Default` will try to auto-detect the absolute location of the
5414 // blue icon images. If you are placing these images in a non-standard
5415 // way, set this option to point to the right absolute path.
5416 return (this.options.imagePath || L.Icon.Default.imagePath) + L.Icon.prototype._getIconUrl.call(this, name);
5419 _detectIconPath: function () {
5420 var el = L.DomUtil.create('div', 'leaflet-default-icon-path', document.body);
5421 var path = L.DomUtil.getStyle(el, 'background-image') ||
5422 L.DomUtil.getStyle(el, 'backgroundImage'); // IE8
5424 document.body.removeChild(el);
5426 return path.indexOf('url') === 0 ?
5427 path.replace(/^url\([\"\']?/, '').replace(/[\"\']?\)$/, '') : '';
5435 * @inherits Interactive layer
5437 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
5442 * L.marker([50.5, 30.5]).addTo(map);
5446 L.Marker = L.Layer.extend({
5449 // @aka Marker options
5451 // @option icon: Icon = *
5452 // Icon class to use for rendering the marker. See [Icon documentation](#L.Icon) for details on how to customize the marker icon. If not specified, a new `L.Icon.Default` is used.
5453 icon: new L.Icon.Default(),
5455 // Option inherited from "Interactive layer" abstract class
5458 // @option draggable: Boolean = false
5459 // Whether the marker is draggable with mouse/touch or not.
5462 // @option keyboard: Boolean = true
5463 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
5466 // @option title: String = ''
5467 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
5470 // @option alt: String = ''
5471 // Text for the `alt` attribute of the icon image (useful for accessibility).
5474 // @option zIndexOffset: Number = 0
5475 // By default, marker images zIndex is set automatically based on its latitude. Use this option if you want to put the marker on top of all others (or below), specifying a high value like `1000` (or high negative value, respectively).
5478 // @option opacity: Number = 1.0
5479 // The opacity of the marker.
5482 // @option riseOnHover: Boolean = false
5483 // If `true`, the marker will get on top of others when you hover the mouse over it.
5486 // @option riseOffset: Number = 250
5487 // The z-index offset used for the `riseOnHover` feature.
5490 // @option pane: String = 'markerPane'
5491 // `Map pane` where the markers icon will be added.
5494 // FIXME: shadowPane is no longer a valid option
5495 nonBubblingEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu']
5500 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
5503 initialize: function (latlng, options) {
5504 L.setOptions(this, options);
5505 this._latlng = L.latLng(latlng);
5508 onAdd: function (map) {
5509 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
5511 if (this._zoomAnimated) {
5512 map.on('zoomanim', this._animateZoom, this);
5519 onRemove: function (map) {
5520 if (this.dragging && this.dragging.enabled()) {
5521 this.options.draggable = true;
5522 this.dragging.removeHooks();
5525 if (this._zoomAnimated) {
5526 map.off('zoomanim', this._animateZoom, this);
5530 this._removeShadow();
5533 getEvents: function () {
5536 viewreset: this.update
5540 // @method getLatLng: LatLng
5541 // Returns the current geographical position of the marker.
5542 getLatLng: function () {
5543 return this._latlng;
5546 // @method setLatLng(latlng: LatLng): this
5547 // Changes the marker position to the given point.
5548 setLatLng: function (latlng) {
5549 var oldLatLng = this._latlng;
5550 this._latlng = L.latLng(latlng);
5553 // @event move: Event
5554 // Fired when the marker is moved via [`setLatLng`](#marker-setlatlng) or by [dragging](#marker-dragging). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
5555 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
5558 // @method setZIndexOffset(offset: Number): this
5559 // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
5560 setZIndexOffset: function (offset) {
5561 this.options.zIndexOffset = offset;
5562 return this.update();
5565 // @method setIcon(icon: Icon): this
5566 // Changes the marker icon.
5567 setIcon: function (icon) {
5569 this.options.icon = icon;
5577 this.bindPopup(this._popup, this._popup.options);
5583 getElement: function () {
5587 update: function () {
5590 var pos = this._map.latLngToLayerPoint(this._latlng).round();
5597 _initIcon: function () {
5598 var options = this.options,
5599 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
5601 var icon = options.icon.createIcon(this._icon),
5604 // if we're not reusing the icon, remove the old one and init new one
5605 if (icon !== this._icon) {
5611 if (options.title) {
5612 icon.title = options.title;
5615 icon.alt = options.alt;
5619 L.DomUtil.addClass(icon, classToAdd);
5621 if (options.keyboard) {
5622 icon.tabIndex = '0';
5627 if (options.riseOnHover) {
5629 mouseover: this._bringToFront,
5630 mouseout: this._resetZIndex
5634 var newShadow = options.icon.createShadow(this._shadow),
5637 if (newShadow !== this._shadow) {
5638 this._removeShadow();
5643 L.DomUtil.addClass(newShadow, classToAdd);
5645 this._shadow = newShadow;
5648 if (options.opacity < 1) {
5649 this._updateOpacity();
5654 this.getPane().appendChild(this._icon);
5656 this._initInteraction();
5657 if (newShadow && addShadow) {
5658 this.getPane('shadowPane').appendChild(this._shadow);
5662 _removeIcon: function () {
5663 if (this.options.riseOnHover) {
5665 mouseover: this._bringToFront,
5666 mouseout: this._resetZIndex
5670 L.DomUtil.remove(this._icon);
5671 this.removeInteractiveTarget(this._icon);
5676 _removeShadow: function () {
5678 L.DomUtil.remove(this._shadow);
5680 this._shadow = null;
5683 _setPos: function (pos) {
5684 L.DomUtil.setPosition(this._icon, pos);
5687 L.DomUtil.setPosition(this._shadow, pos);
5690 this._zIndex = pos.y + this.options.zIndexOffset;
5692 this._resetZIndex();
5695 _updateZIndex: function (offset) {
5696 this._icon.style.zIndex = this._zIndex + offset;
5699 _animateZoom: function (opt) {
5700 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
5705 _initInteraction: function () {
5707 if (!this.options.interactive) { return; }
5709 L.DomUtil.addClass(this._icon, 'leaflet-interactive');
5711 this.addInteractiveTarget(this._icon);
5713 if (L.Handler.MarkerDrag) {
5714 var draggable = this.options.draggable;
5715 if (this.dragging) {
5716 draggable = this.dragging.enabled();
5717 this.dragging.disable();
5720 this.dragging = new L.Handler.MarkerDrag(this);
5723 this.dragging.enable();
5728 // @method setOpacity(opacity: Number): this
5729 // Changes the opacity of the marker.
5730 setOpacity: function (opacity) {
5731 this.options.opacity = opacity;
5733 this._updateOpacity();
5739 _updateOpacity: function () {
5740 var opacity = this.options.opacity;
5742 L.DomUtil.setOpacity(this._icon, opacity);
5745 L.DomUtil.setOpacity(this._shadow, opacity);
5749 _bringToFront: function () {
5750 this._updateZIndex(this.options.riseOffset);
5753 _resetZIndex: function () {
5754 this._updateZIndex(0);
5759 // factory L.marker(latlng: LatLng, options? : Marker options)
5761 // @factory L.marker(latlng: LatLng, options? : Marker options)
5762 // Instantiates a Marker object given a geographical point and optionally an options object.
5763 L.marker = function (latlng, options) {
5764 return new L.Marker(latlng, options);
5774 * Represents a lightweight icon for markers that uses a simple `<div>`
5775 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
5779 * var myIcon = L.divIcon({className: 'my-div-icon'});
5780 * // you can set .my-div-icon styles in CSS
5782 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
5785 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
5788 L.DivIcon = L.Icon.extend({
5791 // @aka DivIcon options
5792 iconSize: [12, 12], // also can be set through CSS
5794 // iconAnchor: (Point),
5795 // popupAnchor: (Point),
5797 // @option html: String = ''
5798 // Custom HTML code to put inside the div element, empty by default.
5801 // @option bgPos: Point = [0, 0]
5802 // Optional relative position of the background, in pixels
5805 className: 'leaflet-div-icon'
5808 createIcon: function (oldIcon) {
5809 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
5810 options = this.options;
5812 div.innerHTML = options.html !== false ? options.html : '';
5814 if (options.bgPos) {
5815 var bgPos = L.point(options.bgPos);
5816 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
5818 this._setIconStyles(div, 'icon');
5823 createShadow: function () {
5828 // @factory L.divIcon(options: DivIcon options)
5829 // Creates a `DivIcon` instance with the given options.
5830 L.divIcon = function (options) {
5831 return new L.DivIcon(options);
5840 * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
5843 // @namespace DivOverlay
5844 L.DivOverlay = L.Layer.extend({
5847 // @aka DivOverlay options
5849 // @option offset: Point = Point(0, 7)
5850 // The offset of the popup position. Useful to control the anchor
5851 // of the popup when opening it on some overlays.
5854 // @option className: String = ''
5855 // A custom CSS class name to assign to the popup.
5858 // @option pane: String = 'popupPane'
5859 // `Map pane` where the popup will be added.
5863 initialize: function (options, source) {
5864 L.setOptions(this, options);
5866 this._source = source;
5869 onAdd: function (map) {
5870 this._zoomAnimated = map._zoomAnimated;
5872 if (!this._container) {
5876 if (map._fadeAnimated) {
5877 L.DomUtil.setOpacity(this._container, 0);
5880 clearTimeout(this._removeTimeout);
5881 this.getPane().appendChild(this._container);
5884 if (map._fadeAnimated) {
5885 L.DomUtil.setOpacity(this._container, 1);
5888 this.bringToFront();
5891 onRemove: function (map) {
5892 if (map._fadeAnimated) {
5893 L.DomUtil.setOpacity(this._container, 0);
5894 this._removeTimeout = setTimeout(L.bind(L.DomUtil.remove, L.DomUtil, this._container), 200);
5896 L.DomUtil.remove(this._container);
5901 // @method getLatLng: LatLng
5902 // Returns the geographical point of popup.
5903 getLatLng: function () {
5904 return this._latlng;
5907 // @method setLatLng(latlng: LatLng): this
5908 // Sets the geographical point where the popup will open.
5909 setLatLng: function (latlng) {
5910 this._latlng = L.latLng(latlng);
5912 this._updatePosition();
5918 // @method getContent: String|HTMLElement
5919 // Returns the content of the popup.
5920 getContent: function () {
5921 return this._content;
5924 // @method setContent(htmlContent: String|HTMLElement|Function): this
5925 // Sets the HTML content of the popup. If a function is passed the source layer will be passed to the function. The function should return a `String` or `HTMLElement` to be used in the popup.
5926 setContent: function (content) {
5927 this._content = content;
5932 // @method getElement: String|HTMLElement
5933 // Alias for [getContent()](#popup-getcontent)
5934 getElement: function () {
5935 return this._container;
5938 // @method update: null
5939 // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
5940 update: function () {
5941 if (!this._map) { return; }
5943 this._container.style.visibility = 'hidden';
5945 this._updateContent();
5946 this._updateLayout();
5947 this._updatePosition();
5949 this._container.style.visibility = '';
5954 getEvents: function () {
5956 zoom: this._updatePosition,
5957 viewreset: this._updatePosition
5960 if (this._zoomAnimated) {
5961 events.zoomanim = this._animateZoom;
5966 // @method isOpen: Boolean
5967 // Returns `true` when the popup is visible on the map.
5968 isOpen: function () {
5969 return !!this._map && this._map.hasLayer(this);
5972 // @method bringToFront: this
5973 // Brings this popup in front of other popups (in the same map pane).
5974 bringToFront: function () {
5976 L.DomUtil.toFront(this._container);
5981 // @method bringToBack: this
5982 // Brings this popup to the back of other popups (in the same map pane).
5983 bringToBack: function () {
5985 L.DomUtil.toBack(this._container);
5990 _updateContent: function () {
5991 if (!this._content) { return; }
5993 var node = this._contentNode;
5994 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
5996 if (typeof content === 'string') {
5997 node.innerHTML = content;
5999 while (node.hasChildNodes()) {
6000 node.removeChild(node.firstChild);
6002 node.appendChild(content);
6004 this.fire('contentupdate');
6007 _updatePosition: function () {
6008 if (!this._map) { return; }
6010 var pos = this._map.latLngToLayerPoint(this._latlng),
6011 offset = L.point(this.options.offset),
6012 anchor = this._getAnchor();
6014 if (this._zoomAnimated) {
6015 L.DomUtil.setPosition(this._container, pos.add(anchor));
6017 offset = offset.add(pos).add(anchor);
6020 var bottom = this._containerBottom = -offset.y,
6021 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
6023 // bottom position the popup in case the height of the popup changes (images loading etc)
6024 this._container.style.bottom = bottom + 'px';
6025 this._container.style.left = left + 'px';
6028 _getAnchor: function () {
6038 * @inherits DivOverlay
6040 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
6041 * open popups while making sure that only one popup is open at one time
6042 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
6046 * If you want to just bind a popup to marker click and then open it, it's really easy:
6049 * marker.bindPopup(popupContent).openPopup();
6051 * Path overlays like polylines also have a `bindPopup` method.
6052 * Here's a more complicated way to open a popup on a map:
6055 * var popup = L.popup()
6056 * .setLatLng(latlng)
6057 * .setContent('<p>Hello world!<br />This is a nice popup.</p>')
6064 L.Popup = L.DivOverlay.extend({
6067 // @aka Popup options
6069 // @option maxWidth: Number = 300
6070 // Max width of the popup, in pixels.
6073 // @option minWidth: Number = 50
6074 // Min width of the popup, in pixels.
6077 // @option maxHeight: Number = null
6078 // If set, creates a scrollable container of the given height
6079 // inside a popup if its content exceeds it.
6082 // @option autoPan: Boolean = true
6083 // Set it to `false` if you don't want the map to do panning animation
6084 // to fit the opened popup.
6087 // @option autoPanPaddingTopLeft: Point = null
6088 // The margin between the popup and the top left corner of the map
6089 // view after autopanning was performed.
6090 autoPanPaddingTopLeft: null,
6092 // @option autoPanPaddingBottomRight: Point = null
6093 // The margin between the popup and the bottom right corner of the map
6094 // view after autopanning was performed.
6095 autoPanPaddingBottomRight: null,
6097 // @option autoPanPadding: Point = Point(5, 5)
6098 // Equivalent of setting both top left and bottom right autopan padding to the same value.
6099 autoPanPadding: [5, 5],
6101 // @option keepInView: Boolean = false
6102 // Set it to `true` if you want to prevent users from panning the popup
6103 // off of the screen while it is open.
6106 // @option closeButton: Boolean = true
6107 // Controls the presence of a close button in the popup.
6110 // @option autoClose: Boolean = true
6111 // Set it to `false` if you want to override the default behavior of
6112 // the popup closing when user clicks the map (set globally by
6113 // the Map's [closePopupOnClick](#map-closepopuponclick) option).
6116 // @option className: String = ''
6117 // A custom CSS class name to assign to the popup.
6122 // @method openOn(map: Map): this
6123 // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
6124 openOn: function (map) {
6125 map.openPopup(this);
6129 onAdd: function (map) {
6130 L.DivOverlay.prototype.onAdd.call(this, map);
6133 // @section Popup events
6134 // @event popupopen: PopupEvent
6135 // Fired when a popup is opened in the map
6136 map.fire('popupopen', {popup: this});
6140 // @section Popup events
6141 // @event popupopen: PopupEvent
6142 // Fired when a popup bound to this layer is opened
6143 this._source.fire('popupopen', {popup: this}, true);
6144 // For non-path layers, we toggle the popup when clicking
6145 // again the layer, so prevent the map to reopen it.
6146 if (!(this._source instanceof L.Path)) {
6147 this._source.on('preclick', L.DomEvent.stopPropagation);
6152 onRemove: function (map) {
6153 L.DivOverlay.prototype.onRemove.call(this, map);
6156 // @section Popup events
6157 // @event popupclose: PopupEvent
6158 // Fired when a popup in the map is closed
6159 map.fire('popupclose', {popup: this});
6163 // @section Popup events
6164 // @event popupclose: PopupEvent
6165 // Fired when a popup bound to this layer is closed
6166 this._source.fire('popupclose', {popup: this}, true);
6167 if (!(this._source instanceof L.Path)) {
6168 this._source.off('preclick', L.DomEvent.stopPropagation);
6173 getEvents: function () {
6174 var events = L.DivOverlay.prototype.getEvents.call(this);
6176 if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
6177 events.preclick = this._close;
6180 if (this.options.keepInView) {
6181 events.moveend = this._adjustPan;
6187 _close: function () {
6189 this._map.closePopup(this);
6193 _initLayout: function () {
6194 var prefix = 'leaflet-popup',
6195 container = this._container = L.DomUtil.create('div',
6196 prefix + ' ' + (this.options.className || '') +
6197 ' leaflet-zoom-animated');
6199 if (this.options.closeButton) {
6200 var closeButton = this._closeButton = L.DomUtil.create('a', prefix + '-close-button', container);
6201 closeButton.href = '#close';
6202 closeButton.innerHTML = '×';
6204 L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);
6207 var wrapper = this._wrapper = L.DomUtil.create('div', prefix + '-content-wrapper', container);
6208 this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);
6211 .disableClickPropagation(wrapper)
6212 .disableScrollPropagation(this._contentNode)
6213 .on(wrapper, 'contextmenu', L.DomEvent.stopPropagation);
6215 this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container);
6216 this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);
6219 _updateLayout: function () {
6220 var container = this._contentNode,
6221 style = container.style;
6224 style.whiteSpace = 'nowrap';
6226 var width = container.offsetWidth;
6227 width = Math.min(width, this.options.maxWidth);
6228 width = Math.max(width, this.options.minWidth);
6230 style.width = (width + 1) + 'px';
6231 style.whiteSpace = '';
6235 var height = container.offsetHeight,
6236 maxHeight = this.options.maxHeight,
6237 scrolledClass = 'leaflet-popup-scrolled';
6239 if (maxHeight && height > maxHeight) {
6240 style.height = maxHeight + 'px';
6241 L.DomUtil.addClass(container, scrolledClass);
6243 L.DomUtil.removeClass(container, scrolledClass);
6246 this._containerWidth = this._container.offsetWidth;
6249 _animateZoom: function (e) {
6250 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
6251 anchor = this._getAnchor();
6252 L.DomUtil.setPosition(this._container, pos.add(anchor));
6255 _adjustPan: function () {
6256 if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; }
6258 var map = this._map,
6259 marginBottom = parseInt(L.DomUtil.getStyle(this._container, 'marginBottom'), 10) || 0,
6260 containerHeight = this._container.offsetHeight + marginBottom,
6261 containerWidth = this._containerWidth,
6262 layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);
6264 layerPos._add(L.DomUtil.getPosition(this._container));
6266 var containerPos = map.layerPointToContainerPoint(layerPos),
6267 padding = L.point(this.options.autoPanPadding),
6268 paddingTL = L.point(this.options.autoPanPaddingTopLeft || padding),
6269 paddingBR = L.point(this.options.autoPanPaddingBottomRight || padding),
6270 size = map.getSize(),
6274 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
6275 dx = containerPos.x + containerWidth - size.x + paddingBR.x;
6277 if (containerPos.x - dx - paddingTL.x < 0) { // left
6278 dx = containerPos.x - paddingTL.x;
6280 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
6281 dy = containerPos.y + containerHeight - size.y + paddingBR.y;
6283 if (containerPos.y - dy - paddingTL.y < 0) { // top
6284 dy = containerPos.y - paddingTL.y;
6288 // @section Popup events
6289 // @event autopanstart: Event
6290 // Fired when the map starts autopanning when opening a popup.
6293 .fire('autopanstart')
6298 _onCloseButtonClick: function (e) {
6303 _getAnchor: function () {
6304 // Where should we anchor the popup on the source layer?
6305 return L.point(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
6311 // @factory L.popup(options?: Popup options, source?: Layer)
6312 // Instantiates a `Popup` object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the popup with a reference to the Layer to which it refers.
6313 L.popup = function (options, source) {
6314 return new L.Popup(options, source);
6319 * @section Interaction Options
6320 * @option closePopupOnClick: Boolean = true
6321 * Set it to `false` if you don't want popups to close when user clicks the map.
6323 L.Map.mergeOptions({
6324 closePopupOnClick: true
6329 // @section Methods for Layers and Controls
6331 // @method openPopup(popup: Popup): this
6332 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
6334 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
6335 // Creates a popup with the specified content and options and opens it in the given point on a map.
6336 openPopup: function (popup, latlng, options) {
6337 if (!(popup instanceof L.Popup)) {
6338 popup = new L.Popup(options).setContent(popup);
6342 popup.setLatLng(latlng);
6345 if (this.hasLayer(popup)) {
6349 if (this._popup && this._popup.options.autoClose) {
6353 this._popup = popup;
6354 return this.addLayer(popup);
6357 // @method closePopup(popup?: Popup): this
6358 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
6359 closePopup: function (popup) {
6360 if (!popup || popup === this._popup) {
6361 popup = this._popup;
6365 this.removeLayer(popup);
6375 * @section Popup methods example
6377 * All layers share a set of methods convenient for binding popups to it.
6380 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
6381 * layer.openPopup();
6382 * layer.closePopup();
6385 * Popups will also be automatically opened when the layer is clicked on and closed when the layer is removed from the map or another popup is opened.
6388 // @section Popup methods
6391 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
6392 // Binds a popup to the layer with the passed `content` and sets up the
6393 // neccessary event listeners. If a `Function` is passed it will receive
6394 // the layer as the first argument and should return a `String` or `HTMLElement`.
6395 bindPopup: function (content, options) {
6397 if (content instanceof L.Popup) {
6398 L.setOptions(content, options);
6399 this._popup = content;
6400 content._source = this;
6402 if (!this._popup || options) {
6403 this._popup = new L.Popup(options, this);
6405 this._popup.setContent(content);
6408 if (!this._popupHandlersAdded) {
6410 click: this._openPopup,
6411 remove: this.closePopup,
6412 move: this._movePopup
6414 this._popupHandlersAdded = true;
6420 // @method unbindPopup(): this
6421 // Removes the popup previously bound with `bindPopup`.
6422 unbindPopup: function () {
6425 click: this._openPopup,
6426 remove: this.closePopup,
6427 move: this._movePopup
6429 this._popupHandlersAdded = false;
6435 // @method openPopup(latlng?: LatLng): this
6436 // Opens the bound popup at the specificed `latlng` or at the default popup anchor if no `latlng` is passed.
6437 openPopup: function (layer, latlng) {
6438 if (!(layer instanceof L.Layer)) {
6443 if (layer instanceof L.FeatureGroup) {
6444 for (var id in this._layers) {
6445 layer = this._layers[id];
6451 latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
6454 if (this._popup && this._map) {
6455 // set popup source to this layer
6456 this._popup._source = layer;
6458 // update the popup (content, layout, ect...)
6459 this._popup.update();
6461 // open the popup on the map
6462 this._map.openPopup(this._popup, latlng);
6468 // @method closePopup(): this
6469 // Closes the popup bound to this layer if it is open.
6470 closePopup: function () {
6472 this._popup._close();
6477 // @method togglePopup(): this
6478 // Opens or closes the popup bound to this layer depending on its current state.
6479 togglePopup: function (target) {
6481 if (this._popup._map) {
6484 this.openPopup(target);
6490 // @method isPopupOpen(): boolean
6491 // Returns `true` if the popup bound to this layer is currently open.
6492 isPopupOpen: function () {
6493 return this._popup.isOpen();
6496 // @method setPopupContent(content: String|HTMLElement|Popup): this
6497 // Sets the content of the popup bound to this layer.
6498 setPopupContent: function (content) {
6500 this._popup.setContent(content);
6505 // @method getPopup(): Popup
6506 // Returns the popup bound to this layer.
6507 getPopup: function () {
6511 _openPopup: function (e) {
6512 var layer = e.layer || e.target;
6522 // prevent map click
6525 // if this inherits from Path its a vector and we can just
6526 // open the popup at the new location
6527 if (layer instanceof L.Path) {
6528 this.openPopup(e.layer || e.target, e.latlng);
6532 // otherwise treat it like a marker and figure out
6533 // if we should toggle it open/closed
6534 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
6537 this.openPopup(layer, e.latlng);
6541 _movePopup: function (e) {
6542 this._popup.setLatLng(e.latlng);
6549 * Popup extension to L.Marker, adding popup-related methods.
6553 _getPopupAnchor: function () {
6554 return this.options.icon.options.popupAnchor || [0, 0];
6562 * @inherits DivOverlay
6564 * Used to display small texts on top of map layers.
6569 * marker.bindTooltip("my tooltip text").openTooltip();
6571 * Note about tooltip offset. Leaflet takes two options in consideration
6572 * for computing tooltip offseting:
6573 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
6574 * Add a positive x offset to move the tooltip to the right, and a positive y offset to
6575 * move it to the bottom. Negatives will move to the left and top.
6576 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
6577 * should adapt this value if you use a custom icon.
6581 // @namespace Tooltip
6582 L.Tooltip = L.DivOverlay.extend({
6585 // @aka Tooltip options
6587 // @option pane: String = 'tooltipPane'
6588 // `Map pane` where the tooltip will be added.
6589 pane: 'tooltipPane',
6591 // @option offset: Point = Point(0, 0)
6592 // Optional offset of the tooltip position.
6595 // @option direction: String = 'auto'
6596 // Direction where to open the tooltip. Possible values are: `right`, `left`,
6597 // `top`, `bottom`, `center`, `auto`.
6598 // `auto` will dynamicaly switch between `right` and `left` according to the tooltip
6599 // position on the map.
6602 // @option permanent: Boolean = false
6603 // Whether to open the tooltip permanently or only on mouseover.
6606 // @option sticky: Boolean = false
6607 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
6610 // @option interactive: Boolean = false
6611 // If true, the tooltip will listen to the feature events.
6614 // @option opacity: Number = 0.9
6615 // Tooltip container opacity.
6619 onAdd: function (map) {
6620 L.DivOverlay.prototype.onAdd.call(this, map);
6621 this.setOpacity(this.options.opacity);
6624 // @section Tooltip events
6625 // @event tooltipopen: TooltipEvent
6626 // Fired when a tooltip is opened in the map.
6627 map.fire('tooltipopen', {tooltip: this});
6631 // @section Tooltip events
6632 // @event tooltipopen: TooltipEvent
6633 // Fired when a tooltip bound to this layer is opened.
6634 this._source.fire('tooltipopen', {tooltip: this}, true);
6638 onRemove: function (map) {
6639 L.DivOverlay.prototype.onRemove.call(this, map);
6642 // @section Tooltip events
6643 // @event tooltipclose: TooltipEvent
6644 // Fired when a tooltip in the map is closed.
6645 map.fire('tooltipclose', {tooltip: this});
6649 // @section Tooltip events
6650 // @event tooltipclose: TooltipEvent
6651 // Fired when a tooltip bound to this layer is closed.
6652 this._source.fire('tooltipclose', {tooltip: this}, true);
6656 getEvents: function () {
6657 var events = L.DivOverlay.prototype.getEvents.call(this);
6659 if (L.Browser.touch && !this.options.permanent) {
6660 events.preclick = this._close;
6666 _close: function () {
6668 this._map.closeTooltip(this);
6672 _initLayout: function () {
6673 var prefix = 'leaflet-tooltip',
6674 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
6676 this._contentNode = this._container = L.DomUtil.create('div', className);
6679 _updateLayout: function () {},
6681 _adjustPan: function () {},
6683 _setPosition: function (pos) {
6684 var map = this._map,
6685 container = this._container,
6686 centerPoint = map.latLngToContainerPoint(map.getCenter()),
6687 tooltipPoint = map.layerPointToContainerPoint(pos),
6688 direction = this.options.direction,
6689 tooltipWidth = container.offsetWidth,
6690 tooltipHeight = container.offsetHeight,
6691 offset = L.point(this.options.offset),
6692 anchor = this._getAnchor();
6694 if (direction === 'top') {
6695 pos = pos.add(L.point(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y));
6696 } else if (direction === 'bottom') {
6697 pos = pos.subtract(L.point(tooltipWidth / 2 - offset.x, -offset.y));
6698 } else if (direction === 'center') {
6699 pos = pos.subtract(L.point(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y));
6700 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
6701 direction = 'right';
6702 pos = pos.add([offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y]);
6705 pos = pos.subtract(L.point(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y));
6708 L.DomUtil.removeClass(container, 'leaflet-tooltip-right');
6709 L.DomUtil.removeClass(container, 'leaflet-tooltip-left');
6710 L.DomUtil.removeClass(container, 'leaflet-tooltip-top');
6711 L.DomUtil.removeClass(container, 'leaflet-tooltip-bottom');
6712 L.DomUtil.addClass(container, 'leaflet-tooltip-' + direction);
6713 L.DomUtil.setPosition(container, pos);
6716 _updatePosition: function () {
6717 var pos = this._map.latLngToLayerPoint(this._latlng);
6718 this._setPosition(pos);
6721 setOpacity: function (opacity) {
6722 this.options.opacity = opacity;
6724 if (this._container) {
6725 L.DomUtil.setOpacity(this._container, opacity);
6729 _animateZoom: function (e) {
6730 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
6731 this._setPosition(pos);
6734 _getAnchor: function () {
6735 // Where should we anchor the tooltip on the source layer?
6736 return L.point(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
6741 // @namespace Tooltip
6742 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
6743 // Instantiates a Tooltip object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the tooltip with a reference to the Layer to which it refers.
6744 L.tooltip = function (options, source) {
6745 return new L.Tooltip(options, source);
6749 // @section Methods for Layers and Controls
6752 // @method openTooltip(tooltip: Tooltip): this
6753 // Opens the specified tooltip.
6755 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
6756 // Creates a tooltip with the specified content and options and open it.
6757 openTooltip: function (tooltip, latlng, options) {
6758 if (!(tooltip instanceof L.Tooltip)) {
6759 tooltip = new L.Tooltip(options).setContent(tooltip);
6763 tooltip.setLatLng(latlng);
6766 if (this.hasLayer(tooltip)) {
6770 return this.addLayer(tooltip);
6773 // @method closeTooltip(tooltip?: Tooltip): this
6774 // Closes the tooltip given as parameter.
6775 closeTooltip: function (tooltip) {
6777 this.removeLayer(tooltip);
6788 * @section Tooltip methods example
6790 * All layers share a set of methods convenient for binding tooltips to it.
6793 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
6794 * layer.openTooltip();
6795 * layer.closeTooltip();
6799 // @section Tooltip methods
6802 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
6803 // Binds a tooltip to the layer with the passed `content` and sets up the
6804 // neccessary event listeners. If a `Function` is passed it will receive
6805 // the layer as the first argument and should return a `String` or `HTMLElement`.
6806 bindTooltip: function (content, options) {
6808 if (content instanceof L.Tooltip) {
6809 L.setOptions(content, options);
6810 this._tooltip = content;
6811 content._source = this;
6813 if (!this._tooltip || options) {
6814 this._tooltip = L.tooltip(options, this);
6816 this._tooltip.setContent(content);
6820 this._initTooltipInteractions();
6822 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
6829 // @method unbindTooltip(): this
6830 // Removes the tooltip previously bound with `bindTooltip`.
6831 unbindTooltip: function () {
6832 if (this._tooltip) {
6833 this._initTooltipInteractions(true);
6834 this.closeTooltip();
6835 this._tooltip = null;
6840 _initTooltipInteractions: function (remove) {
6841 if (!remove && this._tooltipHandlersAdded) { return; }
6842 var onOff = remove ? 'off' : 'on',
6844 remove: this.closeTooltip,
6845 move: this._moveTooltip
6847 if (!this._tooltip.options.permanent) {
6848 events.mouseover = this._openTooltip;
6849 events.mouseout = this.closeTooltip;
6850 if (this._tooltip.options.sticky) {
6851 events.mousemove = this._moveTooltip;
6853 if (L.Browser.touch) {
6854 events.click = this._openTooltip;
6857 events.add = this._openTooltip;
6859 this[onOff](events);
6860 this._tooltipHandlersAdded = !remove;
6863 // @method openTooltip(latlng?: LatLng): this
6864 // Opens the bound tooltip at the specificed `latlng` or at the default tooltip anchor if no `latlng` is passed.
6865 openTooltip: function (layer, latlng) {
6866 if (!(layer instanceof L.Layer)) {
6871 if (layer instanceof L.FeatureGroup) {
6872 for (var id in this._layers) {
6873 layer = this._layers[id];
6879 latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
6882 if (this._tooltip && this._map) {
6884 // set tooltip source to this layer
6885 this._tooltip._source = layer;
6887 // update the tooltip (content, layout, ect...)
6888 this._tooltip.update();
6890 // open the tooltip on the map
6891 this._map.openTooltip(this._tooltip, latlng);
6893 // Tooltip container may not be defined if not permanent and never
6895 if (this._tooltip.options.interactive && this._tooltip._container) {
6896 L.DomUtil.addClass(this._tooltip._container, 'leaflet-clickable');
6897 this.addInteractiveTarget(this._tooltip._container);
6904 // @method closeTooltip(): this
6905 // Closes the tooltip bound to this layer if it is open.
6906 closeTooltip: function () {
6907 if (this._tooltip) {
6908 this._tooltip._close();
6909 if (this._tooltip.options.interactive && this._tooltip._container) {
6910 L.DomUtil.removeClass(this._tooltip._container, 'leaflet-clickable');
6911 this.removeInteractiveTarget(this._tooltip._container);
6917 // @method toggleTooltip(): this
6918 // Opens or closes the tooltip bound to this layer depending on its current state.
6919 toggleTooltip: function (target) {
6920 if (this._tooltip) {
6921 if (this._tooltip._map) {
6922 this.closeTooltip();
6924 this.openTooltip(target);
6930 // @method isTooltipOpen(): boolean
6931 // Returns `true` if the tooltip bound to this layer is currently open.
6932 isTooltipOpen: function () {
6933 return this._tooltip.isOpen();
6936 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
6937 // Sets the content of the tooltip bound to this layer.
6938 setTooltipContent: function (content) {
6939 if (this._tooltip) {
6940 this._tooltip.setContent(content);
6945 // @method getTooltip(): Tooltip
6946 // Returns the tooltip bound to this layer.
6947 getTooltip: function () {
6948 return this._tooltip;
6951 _openTooltip: function (e) {
6952 var layer = e.layer || e.target;
6954 if (!this._tooltip || !this._map) {
6957 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
6960 _moveTooltip: function (e) {
6961 var latlng = e.latlng, containerPoint, layerPoint;
6962 if (this._tooltip.options.sticky && e.originalEvent) {
6963 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
6964 layerPoint = this._map.containerPointToLayerPoint(containerPoint);
6965 latlng = this._map.layerPointToLatLng(layerPoint);
6967 this._tooltip.setLatLng(latlng);
6974 * Tooltip extension to L.Marker, adding tooltip-related methods.
6978 _getTooltipAnchor: function () {
6979 return this.options.icon.options.tooltipAnchor || [0, 0];
6990 * Used to group several layers and handle them as one. If you add it to the map,
6991 * any layers added or removed from the group will be added/removed on the map as
6992 * well. Extends `Layer`.
6997 * L.layerGroup([marker1, marker2])
6998 * .addLayer(polyline)
7003 L.LayerGroup = L.Layer.extend({
7005 initialize: function (layers) {
7011 for (i = 0, len = layers.length; i < len; i++) {
7012 this.addLayer(layers[i]);
7017 // @method addLayer(layer: Layer): this
7018 // Adds the given layer to the group.
7019 addLayer: function (layer) {
7020 var id = this.getLayerId(layer);
7022 this._layers[id] = layer;
7025 this._map.addLayer(layer);
7031 // @method removeLayer(layer: Layer): this
7032 // Removes the given layer from the group.
7034 // @method removeLayer(id: Number): this
7035 // Removes the layer with the given internal ID from the group.
7036 removeLayer: function (layer) {
7037 var id = layer in this._layers ? layer : this.getLayerId(layer);
7039 if (this._map && this._layers[id]) {
7040 this._map.removeLayer(this._layers[id]);
7043 delete this._layers[id];
7048 // @method hasLayer(layer: Layer): Boolean
7049 // Returns `true` if the given layer is currently added to the group.
7050 hasLayer: function (layer) {
7051 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
7054 // @method clearLayers(): this
7055 // Removes all the layers from the group.
7056 clearLayers: function () {
7057 for (var i in this._layers) {
7058 this.removeLayer(this._layers[i]);
7063 // @method invoke(methodName: String, …): this
7064 // Calls `methodName` on every layer contained in this group, passing any
7065 // additional parameters. Has no effect if the layers contained do not
7066 // implement `methodName`.
7067 invoke: function (methodName) {
7068 var args = Array.prototype.slice.call(arguments, 1),
7071 for (i in this._layers) {
7072 layer = this._layers[i];
7074 if (layer[methodName]) {
7075 layer[methodName].apply(layer, args);
7082 onAdd: function (map) {
7083 for (var i in this._layers) {
7084 map.addLayer(this._layers[i]);
7088 onRemove: function (map) {
7089 for (var i in this._layers) {
7090 map.removeLayer(this._layers[i]);
7094 // @method eachLayer(fn: Function, context?: Object): this
7095 // Iterates over the layers of the group, optionally specifying context of the iterator function.
7097 // group.eachLayer(function (layer) {
7098 // layer.bindPopup('Hello');
7101 eachLayer: function (method, context) {
7102 for (var i in this._layers) {
7103 method.call(context, this._layers[i]);
7108 // @method getLayer(id: Number): Layer
7109 // Returns the layer with the given internal ID.
7110 getLayer: function (id) {
7111 return this._layers[id];
7114 // @method getLayers(): Layer[]
7115 // Returns an array of all the layers added to the group.
7116 getLayers: function () {
7119 for (var i in this._layers) {
7120 layers.push(this._layers[i]);
7125 // @method setZIndex(zIndex: Number): this
7126 // Calls `setZIndex` on every layer contained in this group, passing the z-index.
7127 setZIndex: function (zIndex) {
7128 return this.invoke('setZIndex', zIndex);
7131 // @method getLayerId(layer: Layer): Number
7132 // Returns the internal ID for a layer
7133 getLayerId: function (layer) {
7134 return L.stamp(layer);
7139 // @factory L.layerGroup(layers: Layer[])
7140 // Create a layer group, optionally given an initial set of layers.
7141 L.layerGroup = function (layers) {
7142 return new L.LayerGroup(layers);
7148 * @class FeatureGroup
7149 * @aka L.FeatureGroup
7150 * @inherits LayerGroup
7152 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
7153 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
7154 * * Events are propagated to the `FeatureGroup`, so if the group has an event
7155 * handler, it will handle events from any of the layers. This includes mouse events
7156 * and custom events.
7157 * * Has `layeradd` and `layerremove` events
7162 * L.featureGroup([marker1, marker2, polyline])
7163 * .bindPopup('Hello world!')
7164 * .on('click', function() { alert('Clicked on a member of the group!'); })
7169 L.FeatureGroup = L.LayerGroup.extend({
7171 addLayer: function (layer) {
7172 if (this.hasLayer(layer)) {
7176 layer.addEventParent(this);
7178 L.LayerGroup.prototype.addLayer.call(this, layer);
7180 // @event layeradd: LayerEvent
7181 // Fired when a layer is added to this `FeatureGroup`
7182 return this.fire('layeradd', {layer: layer});
7185 removeLayer: function (layer) {
7186 if (!this.hasLayer(layer)) {
7189 if (layer in this._layers) {
7190 layer = this._layers[layer];
7193 layer.removeEventParent(this);
7195 L.LayerGroup.prototype.removeLayer.call(this, layer);
7197 // @event layerremove: LayerEvent
7198 // Fired when a layer is removed from this `FeatureGroup`
7199 return this.fire('layerremove', {layer: layer});
7202 // @method setStyle(style: Path options): this
7203 // Sets the given path options to each layer of the group that has a `setStyle` method.
7204 setStyle: function (style) {
7205 return this.invoke('setStyle', style);
7208 // @method bringToFront(): this
7209 // Brings the layer group to the top of all other layers
7210 bringToFront: function () {
7211 return this.invoke('bringToFront');
7214 // @method bringToBack(): this
7215 // Brings the layer group to the top of all other layers
7216 bringToBack: function () {
7217 return this.invoke('bringToBack');
7220 // @method getBounds(): LatLngBounds
7221 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
7222 getBounds: function () {
7223 var bounds = new L.LatLngBounds();
7225 for (var id in this._layers) {
7226 var layer = this._layers[id];
7227 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
7233 // @factory L.featureGroup(layers: Layer[])
7234 // Create a feature group, optionally given an initial set of layers.
7235 L.featureGroup = function (layers) {
7236 return new L.FeatureGroup(layers);
7246 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
7247 * DOM container of the renderer, its bounds, and its zoom animation.
7249 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
7250 * itself can be added or removed to the map. All paths use a renderer, which can
7251 * be implicit (the map will decide the type of renderer and use it automatically)
7252 * or explicit (using the [`renderer`](#path-renderer) option of the path).
7254 * Do not use this class directly, use `SVG` and `Canvas` instead.
7256 * @event update: Event
7257 * Fired when the renderer updates its bounds, center and zoom, for example when
7261 L.Renderer = L.Layer.extend({
7264 // @aka Renderer options
7266 // @option padding: Number = 0.1
7267 // How much to extend the clip area around the map view (relative to its size)
7268 // e.g. 0.1 would be 10% of map view in each direction
7272 initialize: function (options) {
7273 L.setOptions(this, options);
7277 onAdd: function () {
7278 if (!this._container) {
7279 this._initContainer(); // defined by renderer implementations
7281 if (this._zoomAnimated) {
7282 L.DomUtil.addClass(this._container, 'leaflet-zoom-animated');
7286 this.getPane().appendChild(this._container);
7290 onRemove: function () {
7291 L.DomUtil.remove(this._container);
7294 getEvents: function () {
7296 viewreset: this._reset,
7298 moveend: this._update
7300 if (this._zoomAnimated) {
7301 events.zoomanim = this._onAnimZoom;
7306 _onAnimZoom: function (ev) {
7307 this._updateTransform(ev.center, ev.zoom);
7310 _onZoom: function () {
7311 this._updateTransform(this._map.getCenter(), this._map.getZoom());
7314 _updateTransform: function (center, zoom) {
7315 var scale = this._map.getZoomScale(zoom, this._zoom),
7316 position = L.DomUtil.getPosition(this._container),
7317 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
7318 currentCenterPoint = this._map.project(this._center, zoom),
7319 destCenterPoint = this._map.project(center, zoom),
7320 centerOffset = destCenterPoint.subtract(currentCenterPoint),
7322 topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
7324 if (L.Browser.any3d) {
7325 L.DomUtil.setTransform(this._container, topLeftOffset, scale);
7327 L.DomUtil.setPosition(this._container, topLeftOffset);
7331 _reset: function () {
7333 this._updateTransform(this._center, this._zoom);
7336 _update: function () {
7337 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
7338 // Subclasses are responsible of firing the 'update' event.
7339 var p = this.options.padding,
7340 size = this._map.getSize(),
7341 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
7343 this._bounds = new L.Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
7345 this._center = this._map.getCenter();
7346 this._zoom = this._map.getZoom();
7352 // @namespace Map; @method getRenderer(layer: Path): Renderer
7353 // Returns the instance of `Renderer` that should be used to render the given
7354 // `Path`. It will ensure that the `renderer` options of the map and paths
7355 // are respected, and that the renderers do exist on the map.
7356 getRenderer: function (layer) {
7357 // @namespace Path; @option renderer: Renderer
7358 // Use this specific instance of `Renderer` for this path. Takes
7359 // precedence over the map's [default renderer](#map-renderer).
7360 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
7363 // @namespace Map; @option preferCanvas: Boolean = false
7364 // Whether `Path`s should be rendered on a `Canvas` renderer.
7365 // By default, all `Path`s are rendered in a `SVG` renderer.
7366 renderer = this._renderer = (this.options.preferCanvas && L.canvas()) || L.svg();
7369 if (!this.hasLayer(renderer)) {
7370 this.addLayer(renderer);
7375 _getPaneRenderer: function (name) {
7376 if (name === 'overlayPane' || name === undefined) {
7380 var renderer = this._paneRenderers[name];
7381 if (renderer === undefined) {
7382 renderer = (L.SVG && L.svg({pane: name})) || (L.Canvas && L.canvas({pane: name}));
7383 this._paneRenderers[name] = renderer;
7394 * @inherits Interactive layer
7396 * An abstract class that contains options and constants shared between vector
7397 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
7400 L.Path = L.Layer.extend({
7403 // @aka Path options
7405 // @option stroke: Boolean = true
7406 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
7409 // @option color: String = '#3388ff'
7413 // @option weight: Number = 3
7414 // Stroke width in pixels
7417 // @option opacity: Number = 1.0
7421 // @option lineCap: String= 'round'
7422 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
7425 // @option lineJoin: String = 'round'
7426 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
7429 // @option dashArray: String = null
7430 // A string that defines the stroke [dash pattern](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dasharray). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility).
7433 // @option dashOffset: String = null
7434 // A string that defines the [distance into the dash pattern to start the dash](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dashoffset). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility).
7437 // @option fill: Boolean = depends
7438 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
7441 // @option fillColor: String = *
7442 // Fill color. Defaults to the value of the [`color`](#path-color) option
7445 // @option fillOpacity: Number = 0.2
7449 // @option fillRule: String = 'evenodd'
7450 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
7451 fillRule: 'evenodd',
7455 // Option inherited from "Interactive layer" abstract class
7459 beforeAdd: function (map) {
7460 // Renderer is set here because we need to call renderer.getEvents
7461 // before this.getEvents.
7462 this._renderer = map.getRenderer(this);
7465 onAdd: function () {
7466 this._renderer._initPath(this);
7468 this._renderer._addPath(this);
7469 this._renderer.on('update', this._update, this);
7472 onRemove: function () {
7473 this._renderer._removePath(this);
7474 this._renderer.off('update', this._update, this);
7477 getEvents: function () {
7479 zoomend: this._project,
7480 viewreset: this._reset
7484 // @method redraw(): this
7485 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
7486 redraw: function () {
7488 this._renderer._updatePath(this);
7493 // @method setStyle(style: Path options): this
7494 // Changes the appearance of a Path based on the options in the `Path options` object.
7495 setStyle: function (style) {
7496 L.setOptions(this, style);
7497 if (this._renderer) {
7498 this._renderer._updateStyle(this);
7503 // @method bringToFront(): this
7504 // Brings the layer to the top of all path layers.
7505 bringToFront: function () {
7506 if (this._renderer) {
7507 this._renderer._bringToFront(this);
7512 // @method bringToBack(): this
7513 // Brings the layer to the bottom of all path layers.
7514 bringToBack: function () {
7515 if (this._renderer) {
7516 this._renderer._bringToBack(this);
7521 getElement: function () {
7525 _reset: function () {
7526 // defined in children classes
7531 _clickTolerance: function () {
7532 // used when doing hit detection for Canvas layers
7533 return (this.options.stroke ? this.options.weight / 2 : 0) + (L.Browser.touch ? 10 : 0);
7540 * @namespace LineUtil
7542 * Various utility functions for polyine points processing, used by Leaflet internally to make polylines lightning-fast.
7547 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
7548 // Improves rendering performance dramatically by lessening the number of points to draw.
7550 // @function simplify(points: Point[], tolerance: Number): Point[]
7551 // Dramatically reduces the number of points in a polyline while retaining
7552 // its shape and returns a new array of simplified points, using the
7553 // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
7554 // Used for a huge performance boost when processing/displaying Leaflet polylines for
7555 // each zoom level and also reducing visual noise. tolerance affects the amount of
7556 // simplification (lesser value means higher quality but slower and with more points).
7557 // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
7558 simplify: function (points, tolerance) {
7559 if (!tolerance || !points.length) {
7560 return points.slice();
7563 var sqTolerance = tolerance * tolerance;
7565 // stage 1: vertex reduction
7566 points = this._reducePoints(points, sqTolerance);
7568 // stage 2: Douglas-Peucker simplification
7569 points = this._simplifyDP(points, sqTolerance);
7574 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
7575 // Returns the distance between point `p` and segment `p1` to `p2`.
7576 pointToSegmentDistance: function (p, p1, p2) {
7577 return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));
7580 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
7581 // Returns the closest point from a point `p` on a segment `p1` to `p2`.
7582 closestPointOnSegment: function (p, p1, p2) {
7583 return this._sqClosestPointOnSegment(p, p1, p2);
7586 // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
7587 _simplifyDP: function (points, sqTolerance) {
7589 var len = points.length,
7590 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
7591 markers = new ArrayConstructor(len);
7593 markers[0] = markers[len - 1] = 1;
7595 this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
7600 for (i = 0; i < len; i++) {
7602 newPoints.push(points[i]);
7609 _simplifyDPStep: function (points, markers, sqTolerance, first, last) {
7614 for (i = first + 1; i <= last - 1; i++) {
7615 sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);
7617 if (sqDist > maxSqDist) {
7623 if (maxSqDist > sqTolerance) {
7626 this._simplifyDPStep(points, markers, sqTolerance, first, index);
7627 this._simplifyDPStep(points, markers, sqTolerance, index, last);
7631 // reduce points that are too close to each other to a single point
7632 _reducePoints: function (points, sqTolerance) {
7633 var reducedPoints = [points[0]];
7635 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
7636 if (this._sqDist(points[i], points[prev]) > sqTolerance) {
7637 reducedPoints.push(points[i]);
7641 if (prev < len - 1) {
7642 reducedPoints.push(points[len - 1]);
7644 return reducedPoints;
7648 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
7649 // Clips the segment a to b by rectangular bounds with the
7650 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
7651 // (modifying the segment points directly!). Used by Leaflet to only show polyline
7652 // points that are on the screen or near, increasing performance.
7653 clipSegment: function (a, b, bounds, useLastCode, round) {
7654 var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
7655 codeB = this._getBitCode(b, bounds),
7657 codeOut, p, newCode;
7659 // save 2nd code to avoid calculating it on the next segment
7660 this._lastCode = codeB;
7663 // if a,b is inside the clip window (trivial accept)
7664 if (!(codeA | codeB)) {
7668 // if a,b is outside the clip window (trivial reject)
7669 if (codeA & codeB) {
7674 codeOut = codeA || codeB;
7675 p = this._getEdgeIntersection(a, b, codeOut, bounds, round);
7676 newCode = this._getBitCode(p, bounds);
7678 if (codeOut === codeA) {
7688 _getEdgeIntersection: function (a, b, code, bounds, round) {
7695 if (code & 8) { // top
7696 x = a.x + dx * (max.y - a.y) / dy;
7699 } else if (code & 4) { // bottom
7700 x = a.x + dx * (min.y - a.y) / dy;
7703 } else if (code & 2) { // right
7705 y = a.y + dy * (max.x - a.x) / dx;
7707 } else if (code & 1) { // left
7709 y = a.y + dy * (min.x - a.x) / dx;
7712 return new L.Point(x, y, round);
7715 _getBitCode: function (p, bounds) {
7718 if (p.x < bounds.min.x) { // left
7720 } else if (p.x > bounds.max.x) { // right
7724 if (p.y < bounds.min.y) { // bottom
7726 } else if (p.y > bounds.max.y) { // top
7733 // square distance (to avoid unnecessary Math.sqrt calls)
7734 _sqDist: function (p1, p2) {
7735 var dx = p2.x - p1.x,
7737 return dx * dx + dy * dy;
7740 // return closest point on segment or distance to that point
7741 _sqClosestPointOnSegment: function (p, p1, p2, sqDist) {
7746 dot = dx * dx + dy * dy,
7750 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
7764 return sqDist ? dx * dx + dy * dy : new L.Point(x, y);
7775 * A class for drawing polyline overlays on a map. Extends `Path`.
7780 * // create a red polyline from an array of LatLng points
7787 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
7789 * // zoom the map to the polyline
7790 * map.fitBounds(polyline.getBounds());
7793 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
7796 * // create a red polyline from an array of arrays of LatLng points
7798 * [[-122.68, 45.51],
7808 L.Polyline = L.Path.extend({
7811 // @aka Polyline options
7813 // @option smoothFactor: Number = 1.0
7814 // How much to simplify the polyline on each zoom level. More means
7815 // better performance and smoother look, and less means more accurate representation.
7818 // @option noClip: Boolean = false
7819 // Disable polyline clipping.
7823 initialize: function (latlngs, options) {
7824 L.setOptions(this, options);
7825 this._setLatLngs(latlngs);
7828 // @method getLatLngs(): LatLng[]
7829 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
7830 getLatLngs: function () {
7831 return this._latlngs;
7834 // @method setLatLngs(latlngs: LatLng[]): this
7835 // Replaces all the points in the polyline with the given array of geographical points.
7836 setLatLngs: function (latlngs) {
7837 this._setLatLngs(latlngs);
7838 return this.redraw();
7841 // @method isEmpty(): Boolean
7842 // Returns `true` if the Polyline has no LatLngs.
7843 isEmpty: function () {
7844 return !this._latlngs.length;
7847 closestLayerPoint: function (p) {
7848 var minDistance = Infinity,
7850 closest = L.LineUtil._sqClosestPointOnSegment,
7853 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
7854 var points = this._parts[j];
7856 for (var i = 1, len = points.length; i < len; i++) {
7860 var sqDist = closest(p, p1, p2, true);
7862 if (sqDist < minDistance) {
7863 minDistance = sqDist;
7864 minPoint = closest(p, p1, p2);
7869 minPoint.distance = Math.sqrt(minDistance);
7874 // @method getCenter(): LatLng
7875 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
7876 getCenter: function () {
7877 // throws error when not yet added to map as this center calculation requires projected coordinates
7879 throw new Error('Must add layer to map before using getCenter()');
7882 var i, halfDist, segDist, dist, p1, p2, ratio,
7883 points = this._rings[0],
7884 len = points.length;
7886 if (!len) { return null; }
7888 // polyline centroid algorithm; only uses the first ring if there are multiple
7890 for (i = 0, halfDist = 0; i < len - 1; i++) {
7891 halfDist += points[i].distanceTo(points[i + 1]) / 2;
7894 // The line is so small in the current view that all points are on the same pixel.
7895 if (halfDist === 0) {
7896 return this._map.layerPointToLatLng(points[0]);
7899 for (i = 0, dist = 0; i < len - 1; i++) {
7902 segDist = p1.distanceTo(p2);
7905 if (dist > halfDist) {
7906 ratio = (dist - halfDist) / segDist;
7907 return this._map.layerPointToLatLng([
7908 p2.x - ratio * (p2.x - p1.x),
7909 p2.y - ratio * (p2.y - p1.y)
7915 // @method getBounds(): LatLngBounds
7916 // Returns the `LatLngBounds` of the path.
7917 getBounds: function () {
7918 return this._bounds;
7921 // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
7922 // Adds a given point to the polyline. By default, adds to the first ring of
7923 // the polyline in case of a multi-polyline, but can be overridden by passing
7924 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
7925 addLatLng: function (latlng, latlngs) {
7926 latlngs = latlngs || this._defaultShape();
7927 latlng = L.latLng(latlng);
7928 latlngs.push(latlng);
7929 this._bounds.extend(latlng);
7930 return this.redraw();
7933 _setLatLngs: function (latlngs) {
7934 this._bounds = new L.LatLngBounds();
7935 this._latlngs = this._convertLatLngs(latlngs);
7938 _defaultShape: function () {
7939 return L.Polyline._flat(this._latlngs) ? this._latlngs : this._latlngs[0];
7942 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
7943 _convertLatLngs: function (latlngs) {
7945 flat = L.Polyline._flat(latlngs);
7947 for (var i = 0, len = latlngs.length; i < len; i++) {
7949 result[i] = L.latLng(latlngs[i]);
7950 this._bounds.extend(result[i]);
7952 result[i] = this._convertLatLngs(latlngs[i]);
7959 _project: function () {
7960 var pxBounds = new L.Bounds();
7962 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
7964 var w = this._clickTolerance(),
7965 p = new L.Point(w, w);
7967 if (this._bounds.isValid() && pxBounds.isValid()) {
7968 pxBounds.min._subtract(p);
7969 pxBounds.max._add(p);
7970 this._pxBounds = pxBounds;
7974 // recursively turns latlngs into a set of rings with projected coordinates
7975 _projectLatlngs: function (latlngs, result, projectedBounds) {
7976 var flat = latlngs[0] instanceof L.LatLng,
7977 len = latlngs.length,
7982 for (i = 0; i < len; i++) {
7983 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
7984 projectedBounds.extend(ring[i]);
7988 for (i = 0; i < len; i++) {
7989 this._projectLatlngs(latlngs[i], result, projectedBounds);
7994 // clip polyline by renderer bounds so that we have less to render for performance
7995 _clipPoints: function () {
7996 var bounds = this._renderer._bounds;
7999 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8003 if (this.options.noClip) {
8004 this._parts = this._rings;
8008 var parts = this._parts,
8009 i, j, k, len, len2, segment, points;
8011 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8012 points = this._rings[i];
8014 for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8015 segment = L.LineUtil.clipSegment(points[j], points[j + 1], bounds, j, true);
8017 if (!segment) { continue; }
8019 parts[k] = parts[k] || [];
8020 parts[k].push(segment[0]);
8022 // if segment goes out of screen, or it's the last one, it's the end of the line part
8023 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8024 parts[k].push(segment[1]);
8031 // simplify each clipped part of the polyline for performance
8032 _simplifyPoints: function () {
8033 var parts = this._parts,
8034 tolerance = this.options.smoothFactor;
8036 for (var i = 0, len = parts.length; i < len; i++) {
8037 parts[i] = L.LineUtil.simplify(parts[i], tolerance);
8041 _update: function () {
8042 if (!this._map) { return; }
8045 this._simplifyPoints();
8049 _updatePath: function () {
8050 this._renderer._updatePoly(this);
8054 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8055 // Instantiates a polyline object given an array of geographical points and
8056 // optionally an options object. You can create a `Polyline` object with
8057 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8058 // of geographic points.
8059 L.polyline = function (latlngs, options) {
8060 return new L.Polyline(latlngs, options);
8063 L.Polyline._flat = function (latlngs) {
8064 // true if it's a flat array of latlngs; false if nested
8065 return !L.Util.isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
8071 * @namespace PolyUtil
8072 * Various utility functions for polygon geometries.
8077 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
8078 * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgeman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)).
8079 * Used by Leaflet to only show polygon points that are on the screen or near, increasing
8080 * performance. Note that polygon points needs different algorithm for clipping
8081 * than polyline, so there's a seperate method for it.
8083 L.PolyUtil.clipPolygon = function (points, bounds, round) {
8085 edges = [1, 4, 2, 8],
8091 for (i = 0, len = points.length; i < len; i++) {
8092 points[i]._code = lu._getBitCode(points[i], bounds);
8095 // for each edge (left, bottom, right, top)
8096 for (k = 0; k < 4; k++) {
8100 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
8104 // if a is inside the clip window
8105 if (!(a._code & edge)) {
8106 // if b is outside the clip window (a->b goes out of screen)
8107 if (b._code & edge) {
8108 p = lu._getEdgeIntersection(b, a, edge, bounds, round);
8109 p._code = lu._getBitCode(p, bounds);
8110 clippedPoints.push(p);
8112 clippedPoints.push(a);
8114 // else if b is inside the clip window (a->b enters the screen)
8115 } else if (!(b._code & edge)) {
8116 p = lu._getEdgeIntersection(b, a, edge, bounds, round);
8117 p._code = lu._getBitCode(p, bounds);
8118 clippedPoints.push(p);
8121 points = clippedPoints;
8132 * @inherits Polyline
8134 * A class for drawing polygon overlays on a map. Extends `Polyline`.
8136 * Note that points you pass when creating a polygon shouldn't have an additional last point equal to the first one — it's better to filter out such points.
8142 * // create a red polygon from an array of LatLng points
8143 * var latlngs = [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]];
8145 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8147 * // zoom the map to the polygon
8148 * map.fitBounds(polygon.getBounds());
8151 * You can also pass an array of arrays of latlngs, with the first array representing the outer shape and the other arrays representing holes in the outer shape:
8155 * [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]], // outer ring
8156 * [[-108.58,37.29],[-108.58,40.71],[-102.50,40.71],[-102.50,37.29]] // hole
8160 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8164 * [ // first polygon
8165 * [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]], // outer ring
8166 * [[-108.58,37.29],[-108.58,40.71],[-102.50,40.71],[-102.50,37.29]] // hole
8168 * [ // second polygon
8169 * [[-109.05, 37],[-109.03, 41],[-102.05, 41],[-102.04, 37],[-109.05, 38]]
8175 L.Polygon = L.Polyline.extend({
8181 isEmpty: function () {
8182 return !this._latlngs.length || !this._latlngs[0].length;
8185 getCenter: function () {
8186 // throws error when not yet added to map as this center calculation requires projected coordinates
8188 throw new Error('Must add layer to map before using getCenter()');
8191 var i, j, p1, p2, f, area, x, y, center,
8192 points = this._rings[0],
8193 len = points.length;
8195 if (!len) { return null; }
8197 // polygon centroid algorithm; only uses the first ring if there are multiple
8201 for (i = 0, j = len - 1; i < len; j = i++) {
8205 f = p1.y * p2.x - p2.y * p1.x;
8206 x += (p1.x + p2.x) * f;
8207 y += (p1.y + p2.y) * f;
8212 // Polygon is so small that all points are on same pixel.
8215 center = [x / area, y / area];
8217 return this._map.layerPointToLatLng(center);
8220 _convertLatLngs: function (latlngs) {
8221 var result = L.Polyline.prototype._convertLatLngs.call(this, latlngs),
8222 len = result.length;
8224 // remove last point if it equals first one
8225 if (len >= 2 && result[0] instanceof L.LatLng && result[0].equals(result[len - 1])) {
8231 _setLatLngs: function (latlngs) {
8232 L.Polyline.prototype._setLatLngs.call(this, latlngs);
8233 if (L.Polyline._flat(this._latlngs)) {
8234 this._latlngs = [this._latlngs];
8238 _defaultShape: function () {
8239 return L.Polyline._flat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8242 _clipPoints: function () {
8243 // polygons need a different clipping algorithm so we redefine that
8245 var bounds = this._renderer._bounds,
8246 w = this.options.weight,
8247 p = new L.Point(w, w);
8249 // increase clip padding by stroke width to avoid stroke on clip edges
8250 bounds = new L.Bounds(bounds.min.subtract(p), bounds.max.add(p));
8253 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8257 if (this.options.noClip) {
8258 this._parts = this._rings;
8262 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8263 clipped = L.PolyUtil.clipPolygon(this._rings[i], bounds, true);
8264 if (clipped.length) {
8265 this._parts.push(clipped);
8270 _updatePath: function () {
8271 this._renderer._updatePoly(this, true);
8276 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8277 L.polygon = function (latlngs, options) {
8278 return new L.Polygon(latlngs, options);
8284 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
8292 * A class for drawing rectangle overlays on a map. Extends `Polygon`.
8297 * // define rectangle geographical bounds
8298 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
8300 * // create an orange rectangle
8301 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
8303 * // zoom the map to the rectangle bounds
8304 * map.fitBounds(bounds);
8310 L.Rectangle = L.Polygon.extend({
8311 initialize: function (latLngBounds, options) {
8312 L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
8315 // @method setBounds(latLngBounds: LatLngBounds): this
8316 // Redraws the rectangle with the passed bounds.
8317 setBounds: function (latLngBounds) {
8318 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
8321 _boundsToLatLngs: function (latLngBounds) {
8322 latLngBounds = L.latLngBounds(latLngBounds);
8324 latLngBounds.getSouthWest(),
8325 latLngBounds.getNorthWest(),
8326 latLngBounds.getNorthEast(),
8327 latLngBounds.getSouthEast()
8333 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
8334 L.rectangle = function (latLngBounds, options) {
8335 return new L.Rectangle(latLngBounds, options);
8341 * @class CircleMarker
8342 * @aka L.CircleMarker
8345 * A circle of a fixed size with radius specified in pixels. Extends `Path`.
8348 L.CircleMarker = L.Path.extend({
8351 // @aka CircleMarker options
8355 // @option radius: Number = 10
8356 // Radius of the circle marker, in pixels
8360 initialize: function (latlng, options) {
8361 L.setOptions(this, options);
8362 this._latlng = L.latLng(latlng);
8363 this._radius = this.options.radius;
8366 // @method setLatLng(latLng: LatLng): this
8367 // Sets the position of a circle marker to a new location.
8368 setLatLng: function (latlng) {
8369 this._latlng = L.latLng(latlng);
8371 return this.fire('move', {latlng: this._latlng});
8374 // @method getLatLng(): LatLng
8375 // Returns the current geographical position of the circle marker
8376 getLatLng: function () {
8377 return this._latlng;
8380 // @method setRadius(radius: Number): this
8381 // Sets the radius of a circle marker. Units are in pixels.
8382 setRadius: function (radius) {
8383 this.options.radius = this._radius = radius;
8384 return this.redraw();
8387 // @method getRadius(): Number
8388 // Returns the current radius of the circle
8389 getRadius: function () {
8390 return this._radius;
8393 setStyle : function (options) {
8394 var radius = options && options.radius || this._radius;
8395 L.Path.prototype.setStyle.call(this, options);
8396 this.setRadius(radius);
8400 _project: function () {
8401 this._point = this._map.latLngToLayerPoint(this._latlng);
8402 this._updateBounds();
8405 _updateBounds: function () {
8406 var r = this._radius,
8407 r2 = this._radiusY || r,
8408 w = this._clickTolerance(),
8409 p = [r + w, r2 + w];
8410 this._pxBounds = new L.Bounds(this._point.subtract(p), this._point.add(p));
8413 _update: function () {
8419 _updatePath: function () {
8420 this._renderer._updateCircle(this);
8423 _empty: function () {
8424 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
8429 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
8430 // Instantiates a circle marker object given a geographical point, and an optional options object.
8431 L.circleMarker = function (latlng, options) {
8432 return new L.CircleMarker(latlng, options);
8440 * @inherits CircleMarker
8442 * A class for drawing circle overlays on a map. Extends `CircleMarker`.
8444 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
8449 * L.circle([50.5, 30.5], 200).addTo(map);
8453 L.Circle = L.CircleMarker.extend({
8455 initialize: function (latlng, options, legacyOptions) {
8456 if (typeof options === 'number') {
8457 // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
8458 options = L.extend({}, legacyOptions, {radius: options});
8460 L.setOptions(this, options);
8461 this._latlng = L.latLng(latlng);
8463 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
8466 // @aka Circle options
8467 // @option radius: Number; Radius of the circle, in meters.
8468 this._mRadius = this.options.radius;
8471 // @method setRadius(radius: Number): this
8472 // Sets the radius of a circle. Units are in meters.
8473 setRadius: function (radius) {
8474 this._mRadius = radius;
8475 return this.redraw();
8478 // @method getRadius(): Number
8479 // Returns the current radius of a circle. Units are in meters.
8480 getRadius: function () {
8481 return this._mRadius;
8484 // @method getBounds(): LatLngBounds
8485 // Returns the `LatLngBounds` of the path.
8486 getBounds: function () {
8487 var half = [this._radius, this._radiusY || this._radius];
8489 return new L.LatLngBounds(
8490 this._map.layerPointToLatLng(this._point.subtract(half)),
8491 this._map.layerPointToLatLng(this._point.add(half)));
8494 setStyle: L.Path.prototype.setStyle,
8496 _project: function () {
8498 var lng = this._latlng.lng,
8499 lat = this._latlng.lat,
8501 crs = map.options.crs;
8503 if (crs.distance === L.CRS.Earth.distance) {
8504 var d = Math.PI / 180,
8505 latR = (this._mRadius / L.CRS.Earth.R) / d,
8506 top = map.project([lat + latR, lng]),
8507 bottom = map.project([lat - latR, lng]),
8508 p = top.add(bottom).divideBy(2),
8509 lat2 = map.unproject(p).lat,
8510 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
8511 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
8513 if (isNaN(lngR) || lngR === 0) {
8514 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
8517 this._point = p.subtract(map.getPixelOrigin());
8518 this._radius = isNaN(lngR) ? 0 : Math.max(Math.round(p.x - map.project([lat2, lng - lngR]).x), 1);
8519 this._radiusY = Math.max(Math.round(p.y - top.y), 1);
8522 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
8524 this._point = map.latLngToLayerPoint(this._latlng);
8525 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
8528 this._updateBounds();
8532 // @factory L.circle(latlng: LatLng, options?: Circle options)
8533 // Instantiates a circle object given a geographical point, and an options object
8534 // which contains the circle radius.
8536 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
8537 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
8538 // Do not use in new applications or plugins.
8539 L.circle = function (latlng, options, legacyOptions) {
8540 return new L.Circle(latlng, options, legacyOptions);
8547 * @inherits Renderer
8550 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
8551 * Inherits `Renderer`.
8553 * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
8554 * available in all web browsers, notably Android 2.x and 3.x.
8556 * Although SVG is not available on IE7 and IE8, these browsers support
8557 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
8558 * (a now deprecated technology), and the SVG renderer will fall back to VML in
8563 * Use SVG by default for all paths in the map:
8566 * var map = L.map('map', {
8571 * Use a SVG renderer with extra padding for specific vector geometries:
8574 * var map = L.map('map');
8575 * var myRenderer = L.svg({ padding: 0.5 });
8576 * var line = L.polyline( coordinates, { renderer: myRenderer } );
8577 * var circle = L.circle( center, { renderer: myRenderer } );
8581 L.SVG = L.Renderer.extend({
8583 getEvents: function () {
8584 var events = L.Renderer.prototype.getEvents.call(this);
8585 events.zoomstart = this._onZoomStart;
8589 _initContainer: function () {
8590 this._container = L.SVG.create('svg');
8592 // makes it possible to click through svg root; we'll reset it back in individual paths
8593 this._container.setAttribute('pointer-events', 'none');
8595 this._rootGroup = L.SVG.create('g');
8596 this._container.appendChild(this._rootGroup);
8599 _onZoomStart: function () {
8600 // Drag-then-pinch interactions might mess up the center and zoom.
8601 // In this case, the easiest way to prevent this is re-do the renderer
8602 // bounds and padding when the zooming starts.
8606 _update: function () {
8607 if (this._map._animatingZoom && this._bounds) { return; }
8609 L.Renderer.prototype._update.call(this);
8611 var b = this._bounds,
8613 container = this._container;
8615 // set size of svg-container if changed
8616 if (!this._svgSize || !this._svgSize.equals(size)) {
8617 this._svgSize = size;
8618 container.setAttribute('width', size.x);
8619 container.setAttribute('height', size.y);
8622 // movement: update container viewBox so that we don't have to change coordinates of individual layers
8623 L.DomUtil.setPosition(container, b.min);
8624 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
8626 this.fire('update');
8629 // methods below are called by vector layers implementations
8631 _initPath: function (layer) {
8632 var path = layer._path = L.SVG.create('path');
8635 // @option className: String = null
8636 // Custom class name set on an element. Only for SVG renderer.
8637 if (layer.options.className) {
8638 L.DomUtil.addClass(path, layer.options.className);
8641 if (layer.options.interactive) {
8642 L.DomUtil.addClass(path, 'leaflet-interactive');
8645 this._updateStyle(layer);
8648 _addPath: function (layer) {
8649 this._rootGroup.appendChild(layer._path);
8650 layer.addInteractiveTarget(layer._path);
8653 _removePath: function (layer) {
8654 L.DomUtil.remove(layer._path);
8655 layer.removeInteractiveTarget(layer._path);
8658 _updatePath: function (layer) {
8663 _updateStyle: function (layer) {
8664 var path = layer._path,
8665 options = layer.options;
8667 if (!path) { return; }
8669 if (options.stroke) {
8670 path.setAttribute('stroke', options.color);
8671 path.setAttribute('stroke-opacity', options.opacity);
8672 path.setAttribute('stroke-width', options.weight);
8673 path.setAttribute('stroke-linecap', options.lineCap);
8674 path.setAttribute('stroke-linejoin', options.lineJoin);
8676 if (options.dashArray) {
8677 path.setAttribute('stroke-dasharray', options.dashArray);
8679 path.removeAttribute('stroke-dasharray');
8682 if (options.dashOffset) {
8683 path.setAttribute('stroke-dashoffset', options.dashOffset);
8685 path.removeAttribute('stroke-dashoffset');
8688 path.setAttribute('stroke', 'none');
8692 path.setAttribute('fill', options.fillColor || options.color);
8693 path.setAttribute('fill-opacity', options.fillOpacity);
8694 path.setAttribute('fill-rule', options.fillRule || 'evenodd');
8696 path.setAttribute('fill', 'none');
8700 _updatePoly: function (layer, closed) {
8701 this._setPath(layer, L.SVG.pointsToPath(layer._parts, closed));
8704 _updateCircle: function (layer) {
8705 var p = layer._point,
8707 r2 = layer._radiusY || r,
8708 arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
8710 // drawing a circle with two half-arcs
8711 var d = layer._empty() ? 'M0 0' :
8712 'M' + (p.x - r) + ',' + p.y +
8713 arc + (r * 2) + ',0 ' +
8714 arc + (-r * 2) + ',0 ';
8716 this._setPath(layer, d);
8719 _setPath: function (layer, path) {
8720 layer._path.setAttribute('d', path);
8723 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
8724 _bringToFront: function (layer) {
8725 L.DomUtil.toFront(layer._path);
8728 _bringToBack: function (layer) {
8729 L.DomUtil.toBack(layer._path);
8734 // @namespace SVG; @section
8735 // There are several static functions which can be called without instantiating L.SVG:
8737 // @function create(name: String): SVGElement
8738 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
8739 // corresponding to the class name passed. For example, using 'line' will return
8740 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
8741 create: function (name) {
8742 return document.createElementNS('http://www.w3.org/2000/svg', name);
8745 // @function pointsToPath(rings: Point[], closed: Boolean): String
8746 // Generates a SVG path string for multiple rings, with each ring turning
8747 // into "M..L..L.." instructions
8748 pointsToPath: function (rings, closed) {
8750 i, j, len, len2, points, p;
8752 for (i = 0, len = rings.length; i < len; i++) {
8755 for (j = 0, len2 = points.length; j < len2; j++) {
8757 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
8760 // closes the ring for polygons; "x" is VML syntax
8761 str += closed ? (L.Browser.svg ? 'z' : 'x') : '';
8764 // SVG complains about empty path strings
8765 return str || 'M0 0';
8769 // @namespace Browser; @property svg: Boolean
8770 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
8771 L.Browser.svg = !!(document.createElementNS && L.SVG.create('svg').createSVGRect);
8775 // @factory L.svg(options?: Renderer options)
8776 // Creates a SVG renderer with the given options.
8777 L.svg = function (options) {
8778 return L.Browser.svg || L.Browser.vml ? new L.SVG(options) : null;
8784 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
8790 * Although SVG is not available on IE7 and IE8, these browsers support [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language), and the SVG renderer will fall back to VML in this case.
8792 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
8793 * with old versions of Internet Explorer.
8796 // @namespace Browser; @property vml: Boolean
8797 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
8798 L.Browser.vml = !L.Browser.svg && (function () {
8800 var div = document.createElement('div');
8801 div.innerHTML = '<v:shape adj="1"/>';
8803 var shape = div.firstChild;
8804 shape.style.behavior = 'url(#default#VML)';
8806 return shape && (typeof shape.adj === 'object');
8813 // redefine some SVG methods to handle VML syntax which is similar but with some differences
8814 L.SVG.include(!L.Browser.vml ? {} : {
8816 _initContainer: function () {
8817 this._container = L.DomUtil.create('div', 'leaflet-vml-container');
8820 _update: function () {
8821 if (this._map._animatingZoom) { return; }
8822 L.Renderer.prototype._update.call(this);
8825 _initPath: function (layer) {
8826 var container = layer._container = L.SVG.create('shape');
8828 L.DomUtil.addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
8830 container.coordsize = '1 1';
8832 layer._path = L.SVG.create('path');
8833 container.appendChild(layer._path);
8835 this._updateStyle(layer);
8838 _addPath: function (layer) {
8839 var container = layer._container;
8840 this._container.appendChild(container);
8842 if (layer.options.interactive) {
8843 layer.addInteractiveTarget(container);
8847 _removePath: function (layer) {
8848 var container = layer._container;
8849 L.DomUtil.remove(container);
8850 layer.removeInteractiveTarget(container);
8853 _updateStyle: function (layer) {
8854 var stroke = layer._stroke,
8856 options = layer.options,
8857 container = layer._container;
8859 container.stroked = !!options.stroke;
8860 container.filled = !!options.fill;
8862 if (options.stroke) {
8864 stroke = layer._stroke = L.SVG.create('stroke');
8866 container.appendChild(stroke);
8867 stroke.weight = options.weight + 'px';
8868 stroke.color = options.color;
8869 stroke.opacity = options.opacity;
8871 if (options.dashArray) {
8872 stroke.dashStyle = L.Util.isArray(options.dashArray) ?
8873 options.dashArray.join(' ') :
8874 options.dashArray.replace(/( *, *)/g, ' ');
8876 stroke.dashStyle = '';
8878 stroke.endcap = options.lineCap.replace('butt', 'flat');
8879 stroke.joinstyle = options.lineJoin;
8881 } else if (stroke) {
8882 container.removeChild(stroke);
8883 layer._stroke = null;
8888 fill = layer._fill = L.SVG.create('fill');
8890 container.appendChild(fill);
8891 fill.color = options.fillColor || options.color;
8892 fill.opacity = options.fillOpacity;
8895 container.removeChild(fill);
8900 _updateCircle: function (layer) {
8901 var p = layer._point.round(),
8902 r = Math.round(layer._radius),
8903 r2 = Math.round(layer._radiusY || r);
8905 this._setPath(layer, layer._empty() ? 'M0 0' :
8906 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
8909 _setPath: function (layer, path) {
8910 layer._path.v = path;
8913 _bringToFront: function (layer) {
8914 L.DomUtil.toFront(layer._container);
8917 _bringToBack: function (layer) {
8918 L.DomUtil.toBack(layer._container);
8922 if (L.Browser.vml) {
8923 L.SVG.create = (function () {
8925 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
8926 return function (name) {
8927 return document.createElement('<lvml:' + name + ' class="lvml">');
8930 return function (name) {
8931 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
8941 * @inherits Renderer
8944 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
8945 * Inherits `Renderer`.
8947 * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
8948 * available in all web browsers, notably IE8, and overlapping geometries might
8949 * not display properly in some edge cases.
8953 * Use Canvas by default for all paths in the map:
8956 * var map = L.map('map', {
8957 * renderer: L.canvas()
8961 * Use a Canvas renderer with extra padding for specific vector geometries:
8964 * var map = L.map('map');
8965 * var myRenderer = L.canvas({ padding: 0.5 });
8966 * var line = L.polyline( coordinates, { renderer: myRenderer } );
8967 * var circle = L.circle( center, { renderer: myRenderer } );
8971 L.Canvas = L.Renderer.extend({
8973 onAdd: function () {
8974 L.Renderer.prototype.onAdd.call(this);
8976 this._layers = this._layers || {};
8978 // Redraw vectors since canvas is cleared upon removal,
8979 // in case of removing the renderer itself from the map.
8983 _initContainer: function () {
8984 var container = this._container = document.createElement('canvas');
8987 .on(container, 'mousemove', L.Util.throttle(this._onMouseMove, 32, this), this)
8988 .on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this)
8989 .on(container, 'mouseout', this._handleMouseOut, this);
8991 this._ctx = container.getContext('2d');
8994 _update: function () {
8995 if (this._map._animatingZoom && this._bounds) { return; }
8997 this._drawnLayers = {};
8999 L.Renderer.prototype._update.call(this);
9001 var b = this._bounds,
9002 container = this._container,
9004 m = L.Browser.retina ? 2 : 1;
9006 L.DomUtil.setPosition(container, b.min);
9008 // set canvas size (also clearing it); use double size on retina
9009 container.width = m * size.x;
9010 container.height = m * size.y;
9011 container.style.width = size.x + 'px';
9012 container.style.height = size.y + 'px';
9014 if (L.Browser.retina) {
9015 this._ctx.scale(2, 2);
9018 // translate so we use the same path coordinates after canvas element moves
9019 this._ctx.translate(-b.min.x, -b.min.y);
9021 // Tell paths to redraw themselves
9022 this.fire('update');
9025 _initPath: function (layer) {
9026 this._updateDashArray(layer);
9027 this._layers[L.stamp(layer)] = layer;
9030 _addPath: L.Util.falseFn,
9032 _removePath: function (layer) {
9033 layer._removed = true;
9034 this._requestRedraw(layer);
9037 _updatePath: function (layer) {
9038 this._redrawBounds = layer._pxBounds;
9043 this._redrawBounds = null;
9046 _updateStyle: function (layer) {
9047 this._updateDashArray(layer);
9048 this._requestRedraw(layer);
9051 _updateDashArray: function (layer) {
9052 if (layer.options.dashArray) {
9053 var parts = layer.options.dashArray.split(','),
9056 for (i = 0; i < parts.length; i++) {
9057 dashArray.push(Number(parts[i]));
9059 layer.options._dashArray = dashArray;
9063 _requestRedraw: function (layer) {
9064 if (!this._map) { return; }
9066 var padding = (layer.options.weight || 0) + 1;
9067 this._redrawBounds = this._redrawBounds || new L.Bounds();
9068 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
9069 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
9071 this._redrawRequest = this._redrawRequest || L.Util.requestAnimFrame(this._redraw, this);
9074 _redraw: function () {
9075 this._redrawRequest = null;
9077 this._draw(true); // clear layers in redraw bounds
9078 this._draw(); // draw layers
9080 this._redrawBounds = null;
9083 _draw: function (clear) {
9084 this._clear = clear;
9085 var layer, bounds = this._redrawBounds;
9088 this._ctx.beginPath();
9089 this._ctx.rect(bounds.min.x, bounds.min.y, bounds.max.x - bounds.min.x, bounds.max.y - bounds.min.y);
9093 for (var id in this._layers) {
9094 layer = this._layers[id];
9095 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
9096 layer._updatePath();
9098 if (clear && layer._removed) {
9099 delete layer._removed;
9100 delete this._layers[id];
9103 this._ctx.restore(); // Restore state before clipping.
9106 _updatePoly: function (layer, closed) {
9109 parts = layer._parts,
9113 if (!len) { return; }
9115 this._drawnLayers[layer._leaflet_id] = layer;
9119 if (ctx.setLineDash) {
9120 ctx.setLineDash(layer.options && layer.options._dashArray || []);
9123 for (i = 0; i < len; i++) {
9124 for (j = 0, len2 = parts[i].length; j < len2; j++) {
9126 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
9133 this._fillStroke(ctx, layer);
9135 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
9138 _updateCircle: function (layer) {
9140 if (layer._empty()) { return; }
9142 var p = layer._point,
9145 s = (layer._radiusY || r) / r;
9147 this._drawnLayers[layer._leaflet_id] = layer;
9155 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
9161 this._fillStroke(ctx, layer);
9164 _fillStroke: function (ctx, layer) {
9165 var clear = this._clear,
9166 options = layer.options;
9168 ctx.globalCompositeOperation = clear ? 'destination-out' : 'source-over';
9171 ctx.globalAlpha = clear ? 1 : options.fillOpacity;
9172 ctx.fillStyle = options.fillColor || options.color;
9173 ctx.fill(options.fillRule || 'evenodd');
9176 if (options.stroke && options.weight !== 0) {
9177 ctx.globalAlpha = clear ? 1 : options.opacity;
9179 // if clearing shape, do it with the previously drawn line width
9180 layer._prevWeight = ctx.lineWidth = clear ? layer._prevWeight + 1 : options.weight;
9182 ctx.strokeStyle = options.color;
9183 ctx.lineCap = options.lineCap;
9184 ctx.lineJoin = options.lineJoin;
9189 // Canvas obviously doesn't have mouse events for individual drawn objects,
9190 // so we emulate that by calculating what's under the mouse on mousemove/click manually
9192 _onClick: function (e) {
9193 var point = this._map.mouseEventToLayerPoint(e), layers = [], layer;
9195 for (var id in this._layers) {
9196 layer = this._layers[id];
9197 if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
9198 L.DomEvent._fakeStop(e);
9202 if (layers.length) {
9203 this._fireEvent(layers, e);
9207 _onMouseMove: function (e) {
9208 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
9210 var point = this._map.mouseEventToLayerPoint(e);
9211 this._handleMouseOut(e, point);
9212 this._handleMouseHover(e, point);
9216 _handleMouseOut: function (e, point) {
9217 var layer = this._hoveredLayer;
9218 if (layer && (e.type === 'mouseout' || !layer._containsPoint(point))) {
9219 // if we're leaving the layer, fire mouseout
9220 L.DomUtil.removeClass(this._container, 'leaflet-interactive');
9221 this._fireEvent([layer], e, 'mouseout');
9222 this._hoveredLayer = null;
9226 _handleMouseHover: function (e, point) {
9229 for (id in this._drawnLayers) {
9230 layer = this._drawnLayers[id];
9231 if (layer.options.interactive && layer._containsPoint(point)) {
9232 L.DomUtil.addClass(this._container, 'leaflet-interactive'); // change cursor
9233 this._fireEvent([layer], e, 'mouseover');
9234 this._hoveredLayer = layer;
9238 if (this._hoveredLayer) {
9239 this._fireEvent([this._hoveredLayer], e);
9243 _fireEvent: function (layers, e, type) {
9244 this._map._fireDOMEvent(e, type || e.type, layers);
9247 // TODO _bringToFront & _bringToBack, pretty tricky
9249 _bringToFront: L.Util.falseFn,
9250 _bringToBack: L.Util.falseFn
9253 // @namespace Browser; @property canvas: Boolean
9254 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
9255 L.Browser.canvas = (function () {
9256 return !!document.createElement('canvas').getContext;
9259 // @namespace Canvas
9260 // @factory L.canvas(options?: Renderer options)
9261 // Creates a Canvas renderer with the given options.
9262 L.canvas = function (options) {
9263 return L.Browser.canvas ? new L.Canvas(options) : null;
9266 L.Polyline.prototype._containsPoint = function (p, closed) {
9267 var i, j, k, len, len2, part,
9268 w = this._clickTolerance();
9270 if (!this._pxBounds.contains(p)) { return false; }
9272 // hit detection for polylines
9273 for (i = 0, len = this._parts.length; i < len; i++) {
9274 part = this._parts[i];
9276 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
9277 if (!closed && (j === 0)) { continue; }
9279 if (L.LineUtil.pointToSegmentDistance(p, part[k], part[j]) <= w) {
9287 L.Polygon.prototype._containsPoint = function (p) {
9289 part, p1, p2, i, j, k, len, len2;
9291 if (!this._pxBounds.contains(p)) { return false; }
9293 // ray casting algorithm for detecting if point is in polygon
9294 for (i = 0, len = this._parts.length; i < len; i++) {
9295 part = this._parts[i];
9297 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
9301 if (((p1.y > p.y) !== (p2.y > p.y)) && (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
9307 // also check if it's on polygon stroke
9308 return inside || L.Polyline.prototype._containsPoint.call(this, p, true);
9311 L.CircleMarker.prototype._containsPoint = function (p) {
9312 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
9320 * @inherits FeatureGroup
9322 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
9323 * GeoJSON data and display it on the map. Extends `FeatureGroup`.
9329 * style: function (feature) {
9330 * return {color: feature.properties.color};
9332 * }).bindPopup(function (layer) {
9333 * return layer.feature.properties.description;
9338 L.GeoJSON = L.FeatureGroup.extend({
9341 * @aka GeoJSON options
9343 * @option pointToLayer: Function = *
9344 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
9345 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
9346 * The default is to spawn a default `Marker`:
9348 * function(geoJsonPoint, latlng) {
9349 * return L.marker(latlng);
9353 * @option style: Function = *
9354 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
9355 * called internally when data is added.
9356 * The default value is to not override any defaults:
9358 * function (geoJsonFeature) {
9363 * @option onEachFeature: Function = *
9364 * A `Function` that will be called once for each created `Feature`, after it has
9365 * been created and styled. Useful for attaching events and popups to features.
9366 * The default is to do nothing with the newly created layers:
9368 * function (feature, layer) {}
9371 * @option filter: Function = *
9372 * A `Function` that will be used to decide whether to show a feature or not.
9373 * The default is to show all features:
9375 * function (geoJsonFeature) {
9380 * @option coordsToLatLng: Function = *
9381 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
9382 * The default is the `coordsToLatLng` static method.
9385 initialize: function (geojson, options) {
9386 L.setOptions(this, options);
9391 this.addData(geojson);
9395 // @function addData( <GeoJSON> data ): Layer
9396 // Adds a GeoJSON object to the layer.
9397 addData: function (geojson) {
9398 var features = L.Util.isArray(geojson) ? geojson : geojson.features,
9402 for (i = 0, len = features.length; i < len; i++) {
9403 // only add this if geometry or geometries are set and not null
9404 feature = features[i];
9405 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
9406 this.addData(feature);
9412 var options = this.options;
9414 if (options.filter && !options.filter(geojson)) { return this; }
9416 var layer = L.GeoJSON.geometryToLayer(geojson, options);
9420 layer.feature = L.GeoJSON.asFeature(geojson);
9422 layer.defaultOptions = layer.options;
9423 this.resetStyle(layer);
9425 if (options.onEachFeature) {
9426 options.onEachFeature(geojson, layer);
9429 return this.addLayer(layer);
9432 // @function resetStyle( <Path> layer ): Layer
9433 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
9434 resetStyle: function (layer) {
9435 // reset any custom styles
9436 layer.options = L.Util.extend({}, layer.defaultOptions);
9437 this._setLayerStyle(layer, this.options.style);
9441 // @function setStyle( <Function> style ): Layer
9442 // Changes styles of GeoJSON vector layers with the given style function.
9443 setStyle: function (style) {
9444 return this.eachLayer(function (layer) {
9445 this._setLayerStyle(layer, style);
9449 _setLayerStyle: function (layer, style) {
9450 if (typeof style === 'function') {
9451 style = style(layer.feature);
9453 if (layer.setStyle) {
9454 layer.setStyle(style);
9460 // There are several static functions which can be called without instantiating L.GeoJSON:
9461 L.extend(L.GeoJSON, {
9462 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
9463 // Creates a `Layer` from a given GeoJSON feature. Can use a custom
9464 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
9465 // functions if provided as options.
9466 geometryToLayer: function (geojson, options) {
9468 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
9469 coords = geometry ? geometry.coordinates : null,
9471 pointToLayer = options && options.pointToLayer,
9472 coordsToLatLng = options && options.coordsToLatLng || this.coordsToLatLng,
9473 latlng, latlngs, i, len;
9475 if (!coords && !geometry) {
9479 switch (geometry.type) {
9481 latlng = coordsToLatLng(coords);
9482 return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
9485 for (i = 0, len = coords.length; i < len; i++) {
9486 latlng = coordsToLatLng(coords[i]);
9487 layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng));
9489 return new L.FeatureGroup(layers);
9492 case 'MultiLineString':
9493 latlngs = this.coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, coordsToLatLng);
9494 return new L.Polyline(latlngs, options);
9497 case 'MultiPolygon':
9498 latlngs = this.coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, coordsToLatLng);
9499 return new L.Polygon(latlngs, options);
9501 case 'GeometryCollection':
9502 for (i = 0, len = geometry.geometries.length; i < len; i++) {
9503 var layer = this.geometryToLayer({
9504 geometry: geometry.geometries[i],
9506 properties: geojson.properties
9513 return new L.FeatureGroup(layers);
9516 throw new Error('Invalid GeoJSON object.');
9520 // @function coordsToLatLng(coords: Array): LatLng
9521 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
9522 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
9523 coordsToLatLng: function (coords) {
9524 return new L.LatLng(coords[1], coords[0], coords[2]);
9527 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
9528 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
9529 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
9530 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
9531 coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) {
9534 for (var i = 0, len = coords.length, latlng; i < len; i++) {
9535 latlng = levelsDeep ?
9536 this.coordsToLatLngs(coords[i], levelsDeep - 1, coordsToLatLng) :
9537 (coordsToLatLng || this.coordsToLatLng)(coords[i]);
9539 latlngs.push(latlng);
9545 // @function latLngToCoords(latlng: LatLng): Array
9546 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
9547 latLngToCoords: function (latlng) {
9548 return latlng.alt !== undefined ?
9549 [latlng.lng, latlng.lat, latlng.alt] :
9550 [latlng.lng, latlng.lat];
9553 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
9554 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
9555 // `closed` determines whether the first point should be appended to the end of the array to close the feature, only used when `levelsDeep` is 0. False by default.
9556 latLngsToCoords: function (latlngs, levelsDeep, closed) {
9559 for (var i = 0, len = latlngs.length; i < len; i++) {
9560 coords.push(levelsDeep ?
9561 L.GeoJSON.latLngsToCoords(latlngs[i], levelsDeep - 1, closed) :
9562 L.GeoJSON.latLngToCoords(latlngs[i]));
9565 if (!levelsDeep && closed) {
9566 coords.push(coords[0]);
9572 getFeature: function (layer, newGeometry) {
9573 return layer.feature ?
9574 L.extend({}, layer.feature, {geometry: newGeometry}) :
9575 L.GeoJSON.asFeature(newGeometry);
9578 // @function asFeature(geojson: Object): Object
9579 // Normalize GeoJSON geometries/features into GeoJSON features.
9580 asFeature: function (geojson) {
9581 if (geojson.type === 'Feature') {
9593 var PointToGeoJSON = {
9594 toGeoJSON: function () {
9595 return L.GeoJSON.getFeature(this, {
9597 coordinates: L.GeoJSON.latLngToCoords(this.getLatLng())
9602 L.Marker.include(PointToGeoJSON);
9604 // @namespace CircleMarker
9605 // @method toGeoJSON(): Object
9606 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
9607 L.Circle.include(PointToGeoJSON);
9608 L.CircleMarker.include(PointToGeoJSON);
9611 // @namespace Polyline
9612 // @method toGeoJSON(): Object
9613 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
9614 L.Polyline.prototype.toGeoJSON = function () {
9615 var multi = !L.Polyline._flat(this._latlngs);
9617 var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 1 : 0);
9619 return L.GeoJSON.getFeature(this, {
9620 type: (multi ? 'Multi' : '') + 'LineString',
9625 // @namespace Polygon
9626 // @method toGeoJSON(): Object
9627 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
9628 L.Polygon.prototype.toGeoJSON = function () {
9629 var holes = !L.Polyline._flat(this._latlngs),
9630 multi = holes && !L.Polyline._flat(this._latlngs[0]);
9632 var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true);
9638 return L.GeoJSON.getFeature(this, {
9639 type: (multi ? 'Multi' : '') + 'Polygon',
9645 // @namespace LayerGroup
9646 L.LayerGroup.include({
9647 toMultiPoint: function () {
9650 this.eachLayer(function (layer) {
9651 coords.push(layer.toGeoJSON().geometry.coordinates);
9654 return L.GeoJSON.getFeature(this, {
9660 // @method toGeoJSON(): Object
9661 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `GeometryCollection`).
9662 toGeoJSON: function () {
9664 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
9666 if (type === 'MultiPoint') {
9667 return this.toMultiPoint();
9670 var isGeometryCollection = type === 'GeometryCollection',
9673 this.eachLayer(function (layer) {
9674 if (layer.toGeoJSON) {
9675 var json = layer.toGeoJSON();
9676 jsons.push(isGeometryCollection ? json.geometry : L.GeoJSON.asFeature(json));
9680 if (isGeometryCollection) {
9681 return L.GeoJSON.getFeature(this, {
9683 type: 'GeometryCollection'
9688 type: 'FeatureCollection',
9694 // @namespace GeoJSON
9695 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
9696 // Creates a GeoJSON layer. Optionally accepts an object in
9697 // [GeoJSON format](http://geojson.org/geojson-spec.html) to display on the map
9698 // (you can alternatively add it later with `addData` method) and an `options` object.
9699 L.geoJSON = function (geojson, options) {
9700 return new L.GeoJSON(geojson, options);
9702 // Backward compatibility.
9703 L.geoJson = L.geoJSON;
9708 * @namespace DomEvent
9709 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
9712 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
9716 var eventsKey = '_leaflet_events';
9720 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
9721 // Adds a listener function (`fn`) to a particular DOM event type of the
9722 // element `el`. You can optionally specify the context of the listener
9723 // (object the `this` keyword will point to). You can also pass several
9724 // space-separated types (e.g. `'click dblclick'`).
9727 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
9728 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
9729 on: function (obj, types, fn, context) {
9731 if (typeof types === 'object') {
9732 for (var type in types) {
9733 this._on(obj, type, types[type], fn);
9736 types = L.Util.splitWords(types);
9738 for (var i = 0, len = types.length; i < len; i++) {
9739 this._on(obj, types[i], fn, context);
9746 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
9747 // Removes a previously added listener function. If no function is specified,
9748 // it will remove all the listeners of that particular DOM event from the element.
9749 // Note that if you passed a custom context to on, you must pass the same
9750 // context to `off` in order to remove the listener.
9753 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
9754 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
9755 off: function (obj, types, fn, context) {
9757 if (typeof types === 'object') {
9758 for (var type in types) {
9759 this._off(obj, type, types[type], fn);
9762 types = L.Util.splitWords(types);
9764 for (var i = 0, len = types.length; i < len; i++) {
9765 this._off(obj, types[i], fn, context);
9772 _on: function (obj, type, fn, context) {
9773 var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : '');
9775 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
9777 var handler = function (e) {
9778 return fn.call(context || obj, e || window.event);
9781 var originalHandler = handler;
9783 if (L.Browser.pointer && type.indexOf('touch') === 0) {
9784 this.addPointerListener(obj, type, handler, id);
9786 } else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
9787 this.addDoubleTapListener(obj, handler, id);
9789 } else if ('addEventListener' in obj) {
9791 if (type === 'mousewheel') {
9792 obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
9794 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
9795 handler = function (e) {
9796 e = e || window.event;
9797 if (L.DomEvent._isExternalTarget(obj, e)) {
9801 obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
9804 if (type === 'click' && L.Browser.android) {
9805 handler = function (e) {
9806 return L.DomEvent._filterClick(e, originalHandler);
9809 obj.addEventListener(type, handler, false);
9812 } else if ('attachEvent' in obj) {
9813 obj.attachEvent('on' + type, handler);
9816 obj[eventsKey] = obj[eventsKey] || {};
9817 obj[eventsKey][id] = handler;
9822 _off: function (obj, type, fn, context) {
9824 var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : ''),
9825 handler = obj[eventsKey] && obj[eventsKey][id];
9827 if (!handler) { return this; }
9829 if (L.Browser.pointer && type.indexOf('touch') === 0) {
9830 this.removePointerListener(obj, type, id);
9832 } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
9833 this.removeDoubleTapListener(obj, id);
9835 } else if ('removeEventListener' in obj) {
9837 if (type === 'mousewheel') {
9838 obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
9841 obj.removeEventListener(
9842 type === 'mouseenter' ? 'mouseover' :
9843 type === 'mouseleave' ? 'mouseout' : type, handler, false);
9846 } else if ('detachEvent' in obj) {
9847 obj.detachEvent('on' + type, handler);
9850 obj[eventsKey][id] = null;
9855 // @function stopPropagation(ev: DOMEvent): this
9856 // Stop the given event from propagation to parent elements. Used inside the listener functions:
9858 // L.DomEvent.on(div, 'click', function (ev) {
9859 // L.DomEvent.stopPropagation(ev);
9862 stopPropagation: function (e) {
9864 if (e.stopPropagation) {
9865 e.stopPropagation();
9866 } else if (e.originalEvent) { // In case of Leaflet event.
9867 e.originalEvent._stopped = true;
9869 e.cancelBubble = true;
9871 L.DomEvent._skipped(e);
9876 // @function disableScrollPropagation(el: HTMLElement): this
9877 // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
9878 disableScrollPropagation: function (el) {
9879 return L.DomEvent.on(el, 'mousewheel', L.DomEvent.stopPropagation);
9882 // @function disableClickPropagation(el: HTMLElement): this
9883 // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
9884 // `'mousedown'` and `'touchstart'` events (plus browser variants).
9885 disableClickPropagation: function (el) {
9886 var stop = L.DomEvent.stopPropagation;
9888 L.DomEvent.on(el, L.Draggable.START.join(' '), stop);
9890 return L.DomEvent.on(el, {
9891 click: L.DomEvent._fakeStop,
9896 // @function preventDefault(ev: DOMEvent): this
9897 // Prevents the default action of the DOM Event `ev` from happening (such as
9898 // following a link in the href of the a element, or doing a POST request
9899 // with page reload when a `<form>` is submitted).
9900 // Use it inside listener functions.
9901 preventDefault: function (e) {
9903 if (e.preventDefault) {
9906 e.returnValue = false;
9911 // @function stop(ev): this
9912 // Does `stopPropagation` and `preventDefault` at the same time.
9913 stop: function (e) {
9916 .stopPropagation(e);
9919 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
9920 // Gets normalized mouse position from a DOM event relative to the
9921 // `container` or to the whole page if not specified.
9922 getMousePosition: function (e, container) {
9924 return new L.Point(e.clientX, e.clientY);
9927 var rect = container.getBoundingClientRect();
9930 e.clientX - rect.left - container.clientLeft,
9931 e.clientY - rect.top - container.clientTop);
9934 // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
9935 // and Firefox scrolls device pixels, not CSS pixels
9936 _wheelPxFactor: (L.Browser.win && L.Browser.chrome) ? 2 :
9937 L.Browser.gecko ? window.devicePixelRatio :
9940 // @function getWheelDelta(ev: DOMEvent): Number
9941 // Gets normalized wheel delta from a mousewheel DOM event, in vertical
9942 // pixels scrolled (negative if scrolling down).
9943 // Events from pointing devices without precise scrolling are mapped to
9944 // a best guess of 60 pixels.
9945 getWheelDelta: function (e) {
9946 return (L.Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
9947 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / L.DomEvent._wheelPxFactor : // Pixels
9948 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
9949 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
9950 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
9951 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
9952 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
9953 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
9959 _fakeStop: function (e) {
9960 // fakes stopPropagation by setting a special event flag, checked/reset with L.DomEvent._skipped(e)
9961 L.DomEvent._skipEvents[e.type] = true;
9964 _skipped: function (e) {
9965 var skipped = this._skipEvents[e.type];
9966 // reset when checking, as it's only used in map container and propagates outside of the map
9967 this._skipEvents[e.type] = false;
9971 // check if element really left/entered the event target (for mouseenter/mouseleave)
9972 _isExternalTarget: function (el, e) {
9974 var related = e.relatedTarget;
9976 if (!related) { return true; }
9979 while (related && (related !== el)) {
9980 related = related.parentNode;
9985 return (related !== el);
9988 // this is a horrible workaround for a bug in Android where a single touch triggers two click events
9989 _filterClick: function (e, handler) {
9990 var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
9991 elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick);
9993 // are they closer together than 500ms yet more than 100ms?
9994 // Android typically triggers them ~300ms apart while multiple listeners
9995 // on the same event should be triggered far faster;
9996 // or check if click is simulated on the element, and if it is, reject any non-simulated events
9998 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
10002 L.DomEvent._lastClick = timeStamp;
10008 // @function addListener(…): this
10009 // Alias to [`L.DomEvent.on`](#domevent-on)
10010 L.DomEvent.addListener = L.DomEvent.on;
10012 // @function removeListener(…): this
10013 // Alias to [`L.DomEvent.off`](#domevent-off)
10014 L.DomEvent.removeListener = L.DomEvent.off;
10021 * @inherits Evented
10023 * A class for making DOM elements draggable (including touch support).
10024 * Used internally for map and marker dragging. Only works for elements
10025 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
10029 * var draggable = new L.Draggable(elementToDrag);
10030 * draggable.enable();
10034 L.Draggable = L.Evented.extend({
10037 // @option clickTolerance: Number = 3
10038 // The max number of pixels a user can shift the mouse pointer during a click
10039 // for it to be considered a valid click (as opposed to a mouse drag).
10044 START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'],
10046 mousedown: 'mouseup',
10047 touchstart: 'touchend',
10048 pointerdown: 'touchend',
10049 MSPointerDown: 'touchend'
10052 mousedown: 'mousemove',
10053 touchstart: 'touchmove',
10054 pointerdown: 'touchmove',
10055 MSPointerDown: 'touchmove'
10059 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline: Boolean)
10060 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
10061 initialize: function (element, dragStartTarget, preventOutline) {
10062 this._element = element;
10063 this._dragStartTarget = dragStartTarget || element;
10064 this._preventOutline = preventOutline;
10067 // @method enable()
10068 // Enables the dragging ability
10069 enable: function () {
10070 if (this._enabled) { return; }
10072 L.DomEvent.on(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this);
10074 this._enabled = true;
10077 // @method disable()
10078 // Disables the dragging ability
10079 disable: function () {
10080 if (!this._enabled) { return; }
10082 L.DomEvent.off(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this);
10084 this._enabled = false;
10085 this._moved = false;
10088 _onDown: function (e) {
10089 // Ignore simulated events, since we handle both touch and
10090 // mouse explicitly; otherwise we risk getting duplicates of
10091 // touch events, see #4315.
10092 // Also ignore the event if disabled; this happens in IE11
10093 // under some circumstances, see #3666.
10094 if (e._simulated || !this._enabled) { return; }
10096 this._moved = false;
10098 if (L.DomUtil.hasClass(this._element, 'leaflet-zoom-anim')) { return; }
10100 if (L.Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches) || !this._enabled) { return; }
10101 L.Draggable._dragging = true; // Prevent dragging multiple objects at once.
10103 if (this._preventOutline) {
10104 L.DomUtil.preventOutline(this._element);
10107 L.DomUtil.disableImageDrag();
10108 L.DomUtil.disableTextSelection();
10110 if (this._moving) { return; }
10112 // @event down: Event
10113 // Fired when a drag is about to start.
10116 var first = e.touches ? e.touches[0] : e;
10118 this._startPoint = new L.Point(first.clientX, first.clientY);
10121 .on(document, L.Draggable.MOVE[e.type], this._onMove, this)
10122 .on(document, L.Draggable.END[e.type], this._onUp, this);
10125 _onMove: function (e) {
10126 // Ignore simulated events, since we handle both touch and
10127 // mouse explicitly; otherwise we risk getting duplicates of
10128 // touch events, see #4315.
10129 // Also ignore the event if disabled; this happens in IE11
10130 // under some circumstances, see #3666.
10131 if (e._simulated || !this._enabled) { return; }
10133 if (e.touches && e.touches.length > 1) {
10134 this._moved = true;
10138 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
10139 newPoint = new L.Point(first.clientX, first.clientY),
10140 offset = newPoint.subtract(this._startPoint);
10142 if (!offset.x && !offset.y) { return; }
10143 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
10145 L.DomEvent.preventDefault(e);
10147 if (!this._moved) {
10148 // @event dragstart: Event
10149 // Fired when a drag starts
10150 this.fire('dragstart');
10152 this._moved = true;
10153 this._startPos = L.DomUtil.getPosition(this._element).subtract(offset);
10155 L.DomUtil.addClass(document.body, 'leaflet-dragging');
10157 this._lastTarget = e.target || e.srcElement;
10158 // IE and Edge do not give the <use> element, so fetch it
10160 if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
10161 this._lastTarget = this._lastTarget.correspondingUseElement;
10163 L.DomUtil.addClass(this._lastTarget, 'leaflet-drag-target');
10166 this._newPos = this._startPos.add(offset);
10167 this._moving = true;
10169 L.Util.cancelAnimFrame(this._animRequest);
10170 this._lastEvent = e;
10171 this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true);
10174 _updatePosition: function () {
10175 var e = {originalEvent: this._lastEvent};
10177 // @event predrag: Event
10178 // Fired continuously during dragging *before* each corresponding
10179 // update of the element's position.
10180 this.fire('predrag', e);
10181 L.DomUtil.setPosition(this._element, this._newPos);
10183 // @event drag: Event
10184 // Fired continuously during dragging.
10185 this.fire('drag', e);
10188 _onUp: function (e) {
10189 // Ignore simulated events, since we handle both touch and
10190 // mouse explicitly; otherwise we risk getting duplicates of
10191 // touch events, see #4315.
10192 // Also ignore the event if disabled; this happens in IE11
10193 // under some circumstances, see #3666.
10194 if (e._simulated || !this._enabled) { return; }
10196 L.DomUtil.removeClass(document.body, 'leaflet-dragging');
10198 if (this._lastTarget) {
10199 L.DomUtil.removeClass(this._lastTarget, 'leaflet-drag-target');
10200 this._lastTarget = null;
10203 for (var i in L.Draggable.MOVE) {
10205 .off(document, L.Draggable.MOVE[i], this._onMove, this)
10206 .off(document, L.Draggable.END[i], this._onUp, this);
10209 L.DomUtil.enableImageDrag();
10210 L.DomUtil.enableTextSelection();
10212 if (this._moved && this._moving) {
10213 // ensure drag is not fired after dragend
10214 L.Util.cancelAnimFrame(this._animRequest);
10216 // @event dragend: DragEndEvent
10217 // Fired when the drag ends.
10218 this.fire('dragend', {
10219 distance: this._newPos.distanceTo(this._startPos)
10223 this._moving = false;
10224 L.Draggable._dragging = false;
10231 L.Handler is a base class for handler classes that are used internally to inject
10232 interaction features like dragging to classes like Map and Marker.
10237 // Abstract class for map interaction handlers
10239 L.Handler = L.Class.extend({
10240 initialize: function (map) {
10244 // @method enable(): this
10245 // Enables the handler
10246 enable: function () {
10247 if (this._enabled) { return this; }
10249 this._enabled = true;
10254 // @method disable(): this
10255 // Disables the handler
10256 disable: function () {
10257 if (!this._enabled) { return this; }
10259 this._enabled = false;
10260 this.removeHooks();
10264 // @method enabled(): Boolean
10265 // Returns `true` if the handler is enabled
10266 enabled: function () {
10267 return !!this._enabled;
10270 // @section Extension methods
10271 // Classes inheriting from `Handler` must implement the two following methods:
10272 // @method addHooks()
10273 // Called when the handler is enabled, should add event hooks.
10274 // @method removeHooks()
10275 // Called when the handler is disabled, should remove the event hooks added previously.
10281 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
10285 // @section Interaction Options
10286 L.Map.mergeOptions({
10287 // @option dragging: Boolean = true
10288 // Whether the map be draggable with mouse/touch or not.
10291 // @section Panning Inertia Options
10292 // @option inertia: Boolean = *
10293 // If enabled, panning of the map will have an inertia effect where
10294 // the map builds momentum while dragging and continues moving in
10295 // the same direction for some time. Feels especially nice on touch
10296 // devices. Enabled by default unless running on old Android devices.
10297 inertia: !L.Browser.android23,
10299 // @option inertiaDeceleration: Number = 3000
10300 // The rate with which the inertial movement slows down, in pixels/second².
10301 inertiaDeceleration: 3400, // px/s^2
10303 // @option inertiaMaxSpeed: Number = Infinity
10304 // Max speed of the inertial movement, in pixels/second.
10305 inertiaMaxSpeed: Infinity, // px/s
10307 // @option easeLinearity: Number = 0.2
10308 easeLinearity: 0.2,
10310 // TODO refactor, move to CRS
10311 // @option worldCopyJump: Boolean = false
10312 // With this option enabled, the map tracks when you pan to another "copy"
10313 // of the world and seamlessly jumps to the original one so that all overlays
10314 // like markers and vector layers are still visible.
10315 worldCopyJump: false,
10317 // @option maxBoundsViscosity: Number = 0.0
10318 // If `maxBounds` is set, this option will control how solid the bounds
10319 // are when dragging the map around. The default value of `0.0` allows the
10320 // user to drag outside the bounds at normal speed, higher values will
10321 // slow down map dragging outside bounds, and `1.0` makes the bounds fully
10322 // solid, preventing the user from dragging outside the bounds.
10323 maxBoundsViscosity: 0.0
10326 L.Map.Drag = L.Handler.extend({
10327 addHooks: function () {
10328 if (!this._draggable) {
10329 var map = this._map;
10331 this._draggable = new L.Draggable(map._mapPane, map._container);
10333 this._draggable.on({
10334 down: this._onDown,
10335 dragstart: this._onDragStart,
10336 drag: this._onDrag,
10337 dragend: this._onDragEnd
10340 this._draggable.on('predrag', this._onPreDragLimit, this);
10341 if (map.options.worldCopyJump) {
10342 this._draggable.on('predrag', this._onPreDragWrap, this);
10343 map.on('zoomend', this._onZoomEnd, this);
10345 map.whenReady(this._onZoomEnd, this);
10348 L.DomUtil.addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
10349 this._draggable.enable();
10350 this._positions = [];
10354 removeHooks: function () {
10355 L.DomUtil.removeClass(this._map._container, 'leaflet-grab');
10356 L.DomUtil.removeClass(this._map._container, 'leaflet-touch-drag');
10357 this._draggable.disable();
10360 moved: function () {
10361 return this._draggable && this._draggable._moved;
10364 moving: function () {
10365 return this._draggable && this._draggable._moving;
10368 _onDown: function () {
10372 _onDragStart: function () {
10373 var map = this._map;
10375 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
10376 var bounds = L.latLngBounds(this._map.options.maxBounds);
10378 this._offsetLimit = L.bounds(
10379 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
10380 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
10381 .add(this._map.getSize()));
10383 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
10385 this._offsetLimit = null;
10390 .fire('dragstart');
10392 if (map.options.inertia) {
10393 this._positions = [];
10398 _onDrag: function (e) {
10399 if (this._map.options.inertia) {
10400 var time = this._lastTime = +new Date(),
10401 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
10403 this._positions.push(pos);
10404 this._times.push(time);
10406 if (time - this._times[0] > 50) {
10407 this._positions.shift();
10408 this._times.shift();
10417 _onZoomEnd: function () {
10418 var pxCenter = this._map.getSize().divideBy(2),
10419 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
10421 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
10422 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
10425 _viscousLimit: function (value, threshold) {
10426 return value - (value - threshold) * this._viscosity;
10429 _onPreDragLimit: function () {
10430 if (!this._viscosity || !this._offsetLimit) { return; }
10432 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
10434 var limit = this._offsetLimit;
10435 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
10436 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
10437 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
10438 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
10440 this._draggable._newPos = this._draggable._startPos.add(offset);
10443 _onPreDragWrap: function () {
10444 // TODO refactor to be able to adjust map pane position after zoom
10445 var worldWidth = this._worldWidth,
10446 halfWidth = Math.round(worldWidth / 2),
10447 dx = this._initialWorldOffset,
10448 x = this._draggable._newPos.x,
10449 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
10450 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
10451 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
10453 this._draggable._absPos = this._draggable._newPos.clone();
10454 this._draggable._newPos.x = newX;
10457 _onDragEnd: function (e) {
10458 var map = this._map,
10459 options = map.options,
10461 noInertia = !options.inertia || this._times.length < 2;
10463 map.fire('dragend', e);
10466 map.fire('moveend');
10470 var direction = this._lastPos.subtract(this._positions[0]),
10471 duration = (this._lastTime - this._times[0]) / 1000,
10472 ease = options.easeLinearity,
10474 speedVector = direction.multiplyBy(ease / duration),
10475 speed = speedVector.distanceTo([0, 0]),
10477 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
10478 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
10480 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
10481 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
10483 if (!offset.x && !offset.y) {
10484 map.fire('moveend');
10487 offset = map._limitOffset(offset, map.options.maxBounds);
10489 L.Util.requestAnimFrame(function () {
10490 map.panBy(offset, {
10491 duration: decelerationDuration,
10492 easeLinearity: ease,
10502 // @section Handlers
10503 // @property dragging: Handler
10504 // Map dragging handler (by both mouse and touch).
10505 L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag);
10510 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
10514 // @section Interaction Options
10516 L.Map.mergeOptions({
10517 // @option doubleClickZoom: Boolean|String = true
10518 // Whether the map can be zoomed in by double clicking on it and
10519 // zoomed out by double clicking while holding shift. If passed
10520 // `'center'`, double-click zoom will zoom to the center of the
10521 // view regardless of where the mouse was.
10522 doubleClickZoom: true
10525 L.Map.DoubleClickZoom = L.Handler.extend({
10526 addHooks: function () {
10527 this._map.on('dblclick', this._onDoubleClick, this);
10530 removeHooks: function () {
10531 this._map.off('dblclick', this._onDoubleClick, this);
10534 _onDoubleClick: function (e) {
10535 var map = this._map,
10536 oldZoom = map.getZoom(),
10537 delta = map.options.zoomDelta,
10538 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
10540 if (map.options.doubleClickZoom === 'center') {
10543 map.setZoomAround(e.containerPoint, zoom);
10548 // @section Handlers
10550 // Map properties include interaction handlers that allow you to control
10551 // interaction behavior in runtime, enabling or disabling certain features such
10552 // as dragging or touch zoom (see `Handler` methods). For example:
10555 // map.doubleClickZoom.disable();
10558 // @property doubleClickZoom: Handler
10559 // Double click zoom handler.
10560 L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom);
10565 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
10569 // @section Interaction Options
10570 L.Map.mergeOptions({
10571 // @section Mousewheel options
10572 // @option scrollWheelZoom: Boolean|String = true
10573 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
10574 // it will zoom to the center of the view regardless of where the mouse was.
10575 scrollWheelZoom: true,
10577 // @option wheelDebounceTime: Number = 40
10578 // Limits the rate at which a wheel can fire (in milliseconds). By default
10579 // user can't zoom via wheel more often than once per 40 ms.
10580 wheelDebounceTime: 40,
10582 // @option wheelPxPerZoomLevel: Number = 60
10583 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
10584 // mean a change of one full zoom level. Smaller values will make wheel-zooming
10585 // faster (and vice versa).
10586 wheelPxPerZoomLevel: 60
10589 L.Map.ScrollWheelZoom = L.Handler.extend({
10590 addHooks: function () {
10591 L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this);
10596 removeHooks: function () {
10597 L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll, this);
10600 _onWheelScroll: function (e) {
10601 var delta = L.DomEvent.getWheelDelta(e);
10603 var debounce = this._map.options.wheelDebounceTime;
10605 this._delta += delta;
10606 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
10608 if (!this._startTime) {
10609 this._startTime = +new Date();
10612 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
10614 clearTimeout(this._timer);
10615 this._timer = setTimeout(L.bind(this._performZoom, this), left);
10617 L.DomEvent.stop(e);
10620 _performZoom: function () {
10621 var map = this._map,
10622 zoom = map.getZoom(),
10623 snap = this._map.options.zoomSnap || 0;
10625 map._stop(); // stop panning and fly animations if any
10627 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
10628 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
10629 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
10630 d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
10631 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
10634 this._startTime = null;
10636 if (!delta) { return; }
10638 if (map.options.scrollWheelZoom === 'center') {
10639 map.setZoom(zoom + delta);
10641 map.setZoomAround(this._lastMousePos, zoom + delta);
10646 // @section Handlers
10647 // @property scrollWheelZoom: Handler
10648 // Scroll wheel zoom handler.
10649 L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);
10654 * Extends the event handling code with double tap support for mobile browsers.
10657 L.extend(L.DomEvent, {
10659 _touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart',
10660 _touchend: L.Browser.msPointer ? 'MSPointerUp' : L.Browser.pointer ? 'pointerup' : 'touchend',
10662 // inspired by Zepto touch code by Thomas Fuchs
10663 addDoubleTapListener: function (obj, handler, id) {
10668 function onTouchStart(e) {
10671 if (L.Browser.pointer) {
10672 count = L.DomEvent._pointersCount;
10674 count = e.touches.length;
10677 if (count > 1) { return; }
10679 var now = Date.now(),
10680 delta = now - (last || now);
10682 touch = e.touches ? e.touches[0] : e;
10683 doubleTap = (delta > 0 && delta <= delay);
10687 function onTouchEnd() {
10688 if (doubleTap && !touch.cancelBubble) {
10689 if (L.Browser.pointer) {
10690 // work around .type being readonly with MSPointer* events
10696 newTouch[i] = prop && prop.bind ? prop.bind(touch) : prop;
10700 touch.type = 'dblclick';
10706 var pre = '_leaflet_',
10707 touchstart = this._touchstart,
10708 touchend = this._touchend;
10710 obj[pre + touchstart + id] = onTouchStart;
10711 obj[pre + touchend + id] = onTouchEnd;
10712 obj[pre + 'dblclick' + id] = handler;
10714 obj.addEventListener(touchstart, onTouchStart, false);
10715 obj.addEventListener(touchend, onTouchEnd, false);
10717 // On some platforms (notably, chrome on win10 + touchscreen + mouse),
10718 // the browser doesn't fire touchend/pointerup events but does fire
10719 // native dblclicks. See #4127.
10720 if (!L.Browser.edge) {
10721 obj.addEventListener('dblclick', handler, false);
10727 removeDoubleTapListener: function (obj, id) {
10728 var pre = '_leaflet_',
10729 touchstart = obj[pre + this._touchstart + id],
10730 touchend = obj[pre + this._touchend + id],
10731 dblclick = obj[pre + 'dblclick' + id];
10733 obj.removeEventListener(this._touchstart, touchstart, false);
10734 obj.removeEventListener(this._touchend, touchend, false);
10735 if (!L.Browser.edge) {
10736 obj.removeEventListener('dblclick', dblclick, false);
10746 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
10749 L.extend(L.DomEvent, {
10751 POINTER_DOWN: L.Browser.msPointer ? 'MSPointerDown' : 'pointerdown',
10752 POINTER_MOVE: L.Browser.msPointer ? 'MSPointerMove' : 'pointermove',
10753 POINTER_UP: L.Browser.msPointer ? 'MSPointerUp' : 'pointerup',
10754 POINTER_CANCEL: L.Browser.msPointer ? 'MSPointerCancel' : 'pointercancel',
10755 TAG_WHITE_LIST: ['INPUT', 'SELECT', 'OPTION'],
10760 // Provides a touch events wrapper for (ms)pointer events.
10761 // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
10763 addPointerListener: function (obj, type, handler, id) {
10765 if (type === 'touchstart') {
10766 this._addPointerStart(obj, handler, id);
10768 } else if (type === 'touchmove') {
10769 this._addPointerMove(obj, handler, id);
10771 } else if (type === 'touchend') {
10772 this._addPointerEnd(obj, handler, id);
10778 removePointerListener: function (obj, type, id) {
10779 var handler = obj['_leaflet_' + type + id];
10781 if (type === 'touchstart') {
10782 obj.removeEventListener(this.POINTER_DOWN, handler, false);
10784 } else if (type === 'touchmove') {
10785 obj.removeEventListener(this.POINTER_MOVE, handler, false);
10787 } else if (type === 'touchend') {
10788 obj.removeEventListener(this.POINTER_UP, handler, false);
10789 obj.removeEventListener(this.POINTER_CANCEL, handler, false);
10795 _addPointerStart: function (obj, handler, id) {
10796 var onDown = L.bind(function (e) {
10797 if (e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
10798 // In IE11, some touch events needs to fire for form controls, or
10799 // the controls will stop working. We keep a whitelist of tag names that
10800 // need these events. For other target tags, we prevent default on the event.
10801 if (this.TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
10802 L.DomEvent.preventDefault(e);
10808 this._handlePointer(e, handler);
10811 obj['_leaflet_touchstart' + id] = onDown;
10812 obj.addEventListener(this.POINTER_DOWN, onDown, false);
10814 // need to keep track of what pointers and how many are active to provide e.touches emulation
10815 if (!this._pointerDocListener) {
10816 var pointerUp = L.bind(this._globalPointerUp, this);
10818 // we listen documentElement as any drags that end by moving the touch off the screen get fired there
10819 document.documentElement.addEventListener(this.POINTER_DOWN, L.bind(this._globalPointerDown, this), true);
10820 document.documentElement.addEventListener(this.POINTER_MOVE, L.bind(this._globalPointerMove, this), true);
10821 document.documentElement.addEventListener(this.POINTER_UP, pointerUp, true);
10822 document.documentElement.addEventListener(this.POINTER_CANCEL, pointerUp, true);
10824 this._pointerDocListener = true;
10828 _globalPointerDown: function (e) {
10829 this._pointers[e.pointerId] = e;
10830 this._pointersCount++;
10833 _globalPointerMove: function (e) {
10834 if (this._pointers[e.pointerId]) {
10835 this._pointers[e.pointerId] = e;
10839 _globalPointerUp: function (e) {
10840 delete this._pointers[e.pointerId];
10841 this._pointersCount--;
10844 _handlePointer: function (e, handler) {
10846 for (var i in this._pointers) {
10847 e.touches.push(this._pointers[i]);
10849 e.changedTouches = [e];
10854 _addPointerMove: function (obj, handler, id) {
10855 var onMove = L.bind(function (e) {
10856 // don't fire touch moves when mouse isn't down
10857 if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
10859 this._handlePointer(e, handler);
10862 obj['_leaflet_touchmove' + id] = onMove;
10863 obj.addEventListener(this.POINTER_MOVE, onMove, false);
10866 _addPointerEnd: function (obj, handler, id) {
10867 var onUp = L.bind(function (e) {
10868 this._handlePointer(e, handler);
10871 obj['_leaflet_touchend' + id] = onUp;
10872 obj.addEventListener(this.POINTER_UP, onUp, false);
10873 obj.addEventListener(this.POINTER_CANCEL, onUp, false);
10880 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
10884 // @section Interaction Options
10885 L.Map.mergeOptions({
10886 // @section Touch interaction options
10887 // @option touchZoom: Boolean|String = *
10888 // Whether the map can be zoomed by touch-dragging with two fingers. If
10889 // passed `'center'`, it will zoom to the center of the view regardless of
10890 // where the touch events (fingers) were. Enabled for touch-capable web
10891 // browsers except for old Androids.
10892 touchZoom: L.Browser.touch && !L.Browser.android23,
10894 // @option bounceAtZoomLimits: Boolean = true
10895 // Set it to false if you don't want the map to zoom beyond min/max zoom
10896 // and then bounce back when pinch-zooming.
10897 bounceAtZoomLimits: true
10900 L.Map.TouchZoom = L.Handler.extend({
10901 addHooks: function () {
10902 L.DomUtil.addClass(this._map._container, 'leaflet-touch-zoom');
10903 L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
10906 removeHooks: function () {
10907 L.DomUtil.removeClass(this._map._container, 'leaflet-touch-zoom');
10908 L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
10911 _onTouchStart: function (e) {
10912 var map = this._map;
10913 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
10915 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
10916 p2 = map.mouseEventToContainerPoint(e.touches[1]);
10918 this._centerPoint = map.getSize()._divideBy(2);
10919 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
10920 if (map.options.touchZoom !== 'center') {
10921 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
10924 this._startDist = p1.distanceTo(p2);
10925 this._startZoom = map.getZoom();
10927 this._moved = false;
10928 this._zooming = true;
10933 .on(document, 'touchmove', this._onTouchMove, this)
10934 .on(document, 'touchend', this._onTouchEnd, this);
10936 L.DomEvent.preventDefault(e);
10939 _onTouchMove: function (e) {
10940 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
10942 var map = this._map,
10943 p1 = map.mouseEventToContainerPoint(e.touches[0]),
10944 p2 = map.mouseEventToContainerPoint(e.touches[1]),
10945 scale = p1.distanceTo(p2) / this._startDist;
10948 this._zoom = map.getScaleZoom(scale, this._startZoom);
10950 if (!map.options.bounceAtZoomLimits && (
10951 (this._zoom < map.getMinZoom() && scale < 1) ||
10952 (this._zoom > map.getMaxZoom() && scale > 1))) {
10953 this._zoom = map._limitZoom(this._zoom);
10956 if (map.options.touchZoom === 'center') {
10957 this._center = this._startLatLng;
10958 if (scale === 1) { return; }
10960 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
10961 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
10962 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
10963 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
10966 if (!this._moved) {
10967 map._moveStart(true);
10968 this._moved = true;
10971 L.Util.cancelAnimFrame(this._animRequest);
10973 var moveFn = L.bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
10974 this._animRequest = L.Util.requestAnimFrame(moveFn, this, true);
10976 L.DomEvent.preventDefault(e);
10979 _onTouchEnd: function () {
10980 if (!this._moved || !this._zooming) {
10981 this._zooming = false;
10985 this._zooming = false;
10986 L.Util.cancelAnimFrame(this._animRequest);
10989 .off(document, 'touchmove', this._onTouchMove)
10990 .off(document, 'touchend', this._onTouchEnd);
10992 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
10993 if (this._map.options.zoomAnimation) {
10994 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
10996 this._map._resetView(this._center, this._map._limitZoom(this._zoom));
11001 // @section Handlers
11002 // @property touchZoom: Handler
11003 // Touch zoom handler.
11004 L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom);
11009 * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
11013 // @section Interaction Options
11014 L.Map.mergeOptions({
11015 // @section Touch interaction options
11016 // @option tap: Boolean = true
11017 // Enables mobile hacks for supporting instant taps (fixing 200ms click
11018 // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
11021 // @option tapTolerance: Number = 15
11022 // The max number of pixels a user can shift his finger during touch
11023 // for it to be considered a valid tap.
11027 L.Map.Tap = L.Handler.extend({
11028 addHooks: function () {
11029 L.DomEvent.on(this._map._container, 'touchstart', this._onDown, this);
11032 removeHooks: function () {
11033 L.DomEvent.off(this._map._container, 'touchstart', this._onDown, this);
11036 _onDown: function (e) {
11037 if (!e.touches) { return; }
11039 L.DomEvent.preventDefault(e);
11041 this._fireClick = true;
11043 // don't simulate click or track longpress if more than 1 touch
11044 if (e.touches.length > 1) {
11045 this._fireClick = false;
11046 clearTimeout(this._holdTimeout);
11050 var first = e.touches[0],
11053 this._startPos = this._newPos = new L.Point(first.clientX, first.clientY);
11055 // if touching a link, highlight it
11056 if (el.tagName && el.tagName.toLowerCase() === 'a') {
11057 L.DomUtil.addClass(el, 'leaflet-active');
11060 // simulate long hold but setting a timeout
11061 this._holdTimeout = setTimeout(L.bind(function () {
11062 if (this._isTapValid()) {
11063 this._fireClick = false;
11065 this._simulateEvent('contextmenu', first);
11069 this._simulateEvent('mousedown', first);
11071 L.DomEvent.on(document, {
11072 touchmove: this._onMove,
11073 touchend: this._onUp
11077 _onUp: function (e) {
11078 clearTimeout(this._holdTimeout);
11080 L.DomEvent.off(document, {
11081 touchmove: this._onMove,
11082 touchend: this._onUp
11085 if (this._fireClick && e && e.changedTouches) {
11087 var first = e.changedTouches[0],
11090 if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
11091 L.DomUtil.removeClass(el, 'leaflet-active');
11094 this._simulateEvent('mouseup', first);
11096 // simulate click if the touch didn't move too much
11097 if (this._isTapValid()) {
11098 this._simulateEvent('click', first);
11103 _isTapValid: function () {
11104 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
11107 _onMove: function (e) {
11108 var first = e.touches[0];
11109 this._newPos = new L.Point(first.clientX, first.clientY);
11110 this._simulateEvent('mousemove', first);
11113 _simulateEvent: function (type, e) {
11114 var simulatedEvent = document.createEvent('MouseEvents');
11116 simulatedEvent._simulated = true;
11117 e.target._simulatedClick = true;
11119 simulatedEvent.initMouseEvent(
11120 type, true, true, window, 1,
11121 e.screenX, e.screenY,
11122 e.clientX, e.clientY,
11123 false, false, false, false, 0, null);
11125 e.target.dispatchEvent(simulatedEvent);
11129 // @section Handlers
11130 // @property tap: Handler
11131 // Mobile touch hacks (quick tap and touch hold) handler.
11132 if (L.Browser.touch && !L.Browser.pointer) {
11133 L.Map.addInitHook('addHandler', 'tap', L.Map.Tap);
11139 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
11140 * (zoom to a selected bounding box), enabled by default.
11144 // @section Interaction Options
11145 L.Map.mergeOptions({
11146 // @option boxZoom: Boolean = true
11147 // Whether the map can be zoomed to a rectangular area specified by
11148 // dragging the mouse while pressing the shift key.
11152 L.Map.BoxZoom = L.Handler.extend({
11153 initialize: function (map) {
11155 this._container = map._container;
11156 this._pane = map._panes.overlayPane;
11159 addHooks: function () {
11160 L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
11163 removeHooks: function () {
11164 L.DomEvent.off(this._container, 'mousedown', this._onMouseDown, this);
11167 moved: function () {
11168 return this._moved;
11171 _resetState: function () {
11172 this._moved = false;
11175 _onMouseDown: function (e) {
11176 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
11178 this._resetState();
11180 L.DomUtil.disableTextSelection();
11181 L.DomUtil.disableImageDrag();
11183 this._startPoint = this._map.mouseEventToContainerPoint(e);
11185 L.DomEvent.on(document, {
11186 contextmenu: L.DomEvent.stop,
11187 mousemove: this._onMouseMove,
11188 mouseup: this._onMouseUp,
11189 keydown: this._onKeyDown
11193 _onMouseMove: function (e) {
11194 if (!this._moved) {
11195 this._moved = true;
11197 this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._container);
11198 L.DomUtil.addClass(this._container, 'leaflet-crosshair');
11200 this._map.fire('boxzoomstart');
11203 this._point = this._map.mouseEventToContainerPoint(e);
11205 var bounds = new L.Bounds(this._point, this._startPoint),
11206 size = bounds.getSize();
11208 L.DomUtil.setPosition(this._box, bounds.min);
11210 this._box.style.width = size.x + 'px';
11211 this._box.style.height = size.y + 'px';
11214 _finish: function () {
11216 L.DomUtil.remove(this._box);
11217 L.DomUtil.removeClass(this._container, 'leaflet-crosshair');
11220 L.DomUtil.enableTextSelection();
11221 L.DomUtil.enableImageDrag();
11223 L.DomEvent.off(document, {
11224 contextmenu: L.DomEvent.stop,
11225 mousemove: this._onMouseMove,
11226 mouseup: this._onMouseUp,
11227 keydown: this._onKeyDown
11231 _onMouseUp: function (e) {
11232 if ((e.which !== 1) && (e.button !== 1)) { return; }
11236 if (!this._moved) { return; }
11237 // Postpone to next JS tick so internal click event handling
11238 // still see it as "moved".
11239 setTimeout(L.bind(this._resetState, this), 0);
11241 var bounds = new L.LatLngBounds(
11242 this._map.containerPointToLatLng(this._startPoint),
11243 this._map.containerPointToLatLng(this._point));
11247 .fire('boxzoomend', {boxZoomBounds: bounds});
11250 _onKeyDown: function (e) {
11251 if (e.keyCode === 27) {
11257 // @section Handlers
11258 // @property boxZoom: Handler
11259 // Box (shift-drag with mouse) zoom handler.
11260 L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom);
11265 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
11269 // @section Keyboard Navigation Options
11270 L.Map.mergeOptions({
11271 // @option keyboard: Boolean = true
11272 // Makes the map focusable and allows users to navigate the map with keyboard
11273 // arrows and `+`/`-` keys.
11276 // @option keyboardPanDelta: Number = 80
11277 // Amount of pixels to pan when pressing an arrow key.
11278 keyboardPanDelta: 80
11281 L.Map.Keyboard = L.Handler.extend({
11288 zoomIn: [187, 107, 61, 171],
11289 zoomOut: [189, 109, 54, 173]
11292 initialize: function (map) {
11295 this._setPanDelta(map.options.keyboardPanDelta);
11296 this._setZoomDelta(map.options.zoomDelta);
11299 addHooks: function () {
11300 var container = this._map._container;
11302 // make the container focusable by tabbing
11303 if (container.tabIndex <= 0) {
11304 container.tabIndex = '0';
11307 L.DomEvent.on(container, {
11308 focus: this._onFocus,
11309 blur: this._onBlur,
11310 mousedown: this._onMouseDown
11314 focus: this._addHooks,
11315 blur: this._removeHooks
11319 removeHooks: function () {
11320 this._removeHooks();
11322 L.DomEvent.off(this._map._container, {
11323 focus: this._onFocus,
11324 blur: this._onBlur,
11325 mousedown: this._onMouseDown
11329 focus: this._addHooks,
11330 blur: this._removeHooks
11334 _onMouseDown: function () {
11335 if (this._focused) { return; }
11337 var body = document.body,
11338 docEl = document.documentElement,
11339 top = body.scrollTop || docEl.scrollTop,
11340 left = body.scrollLeft || docEl.scrollLeft;
11342 this._map._container.focus();
11344 window.scrollTo(left, top);
11347 _onFocus: function () {
11348 this._focused = true;
11349 this._map.fire('focus');
11352 _onBlur: function () {
11353 this._focused = false;
11354 this._map.fire('blur');
11357 _setPanDelta: function (panDelta) {
11358 var keys = this._panKeys = {},
11359 codes = this.keyCodes,
11362 for (i = 0, len = codes.left.length; i < len; i++) {
11363 keys[codes.left[i]] = [-1 * panDelta, 0];
11365 for (i = 0, len = codes.right.length; i < len; i++) {
11366 keys[codes.right[i]] = [panDelta, 0];
11368 for (i = 0, len = codes.down.length; i < len; i++) {
11369 keys[codes.down[i]] = [0, panDelta];
11371 for (i = 0, len = codes.up.length; i < len; i++) {
11372 keys[codes.up[i]] = [0, -1 * panDelta];
11376 _setZoomDelta: function (zoomDelta) {
11377 var keys = this._zoomKeys = {},
11378 codes = this.keyCodes,
11381 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
11382 keys[codes.zoomIn[i]] = zoomDelta;
11384 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
11385 keys[codes.zoomOut[i]] = -zoomDelta;
11389 _addHooks: function () {
11390 L.DomEvent.on(document, 'keydown', this._onKeyDown, this);
11393 _removeHooks: function () {
11394 L.DomEvent.off(document, 'keydown', this._onKeyDown, this);
11397 _onKeyDown: function (e) {
11398 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
11400 var key = e.keyCode,
11404 if (key in this._panKeys) {
11406 if (map._panAnim && map._panAnim._inProgress) { return; }
11408 offset = this._panKeys[key];
11410 offset = L.point(offset).multiplyBy(3);
11415 if (map.options.maxBounds) {
11416 map.panInsideBounds(map.options.maxBounds);
11419 } else if (key in this._zoomKeys) {
11420 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
11422 } else if (key === 27) {
11429 L.DomEvent.stop(e);
11433 // @section Handlers
11434 // @section Handlers
11435 // @property keyboard: Handler
11436 // Keyboard navigation handler.
11437 L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard);
11442 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
11446 /* @namespace Marker
11447 * @section Interaction handlers
11449 * Interaction handlers are properties of a marker instance that allow you to control interaction behavior in runtime, enabling or disabling certain features such as dragging (see `Handler` methods). Example:
11452 * marker.dragging.disable();
11455 * @property dragging: Handler
11456 * Marker dragging handler (by both mouse and touch).
11459 L.Handler.MarkerDrag = L.Handler.extend({
11460 initialize: function (marker) {
11461 this._marker = marker;
11464 addHooks: function () {
11465 var icon = this._marker._icon;
11467 if (!this._draggable) {
11468 this._draggable = new L.Draggable(icon, icon, true);
11471 this._draggable.on({
11472 dragstart: this._onDragStart,
11473 drag: this._onDrag,
11474 dragend: this._onDragEnd
11477 L.DomUtil.addClass(icon, 'leaflet-marker-draggable');
11480 removeHooks: function () {
11481 this._draggable.off({
11482 dragstart: this._onDragStart,
11483 drag: this._onDrag,
11484 dragend: this._onDragEnd
11485 }, this).disable();
11487 if (this._marker._icon) {
11488 L.DomUtil.removeClass(this._marker._icon, 'leaflet-marker-draggable');
11492 moved: function () {
11493 return this._draggable && this._draggable._moved;
11496 _onDragStart: function () {
11497 // @section Dragging events
11498 // @event dragstart: Event
11499 // Fired when the user starts dragging the marker.
11501 // @event movestart: Event
11502 // Fired when the marker starts moving (because of dragging).
11504 this._oldLatLng = this._marker.getLatLng();
11508 .fire('dragstart');
11511 _onDrag: function (e) {
11512 var marker = this._marker,
11513 shadow = marker._shadow,
11514 iconPos = L.DomUtil.getPosition(marker._icon),
11515 latlng = marker._map.layerPointToLatLng(iconPos);
11517 // update shadow position
11519 L.DomUtil.setPosition(shadow, iconPos);
11522 marker._latlng = latlng;
11524 e.oldLatLng = this._oldLatLng;
11526 // @event drag: Event
11527 // Fired repeatedly while the user drags the marker.
11533 _onDragEnd: function (e) {
11534 // @event dragend: DragEndEvent
11535 // Fired when the user stops dragging the marker.
11537 // @event moveend: Event
11538 // Fired when the marker stops moving (because of dragging).
11539 delete this._oldLatLng;
11542 .fire('dragend', e);
11552 * L.Control is a base class for implementing map controls. Handles positioning.
11553 * All other controls extend from this class.
11556 L.Control = L.Class.extend({
11558 // @aka Control options
11560 // @option position: String = 'topright'
11561 // The position of the control (one of the map corners). Possible values are `'topleft'`,
11562 // `'topright'`, `'bottomleft'` or `'bottomright'`
11563 position: 'topright'
11566 initialize: function (options) {
11567 L.setOptions(this, options);
11571 * Classes extending L.Control will inherit the following methods:
11573 * @method getPosition: string
11574 * Returns the position of the control.
11576 getPosition: function () {
11577 return this.options.position;
11580 // @method setPosition(position: string): this
11581 // Sets the position of the control.
11582 setPosition: function (position) {
11583 var map = this._map;
11586 map.removeControl(this);
11589 this.options.position = position;
11592 map.addControl(this);
11598 // @method getContainer: HTMLElement
11599 // Returns the HTMLElement that contains the control.
11600 getContainer: function () {
11601 return this._container;
11604 // @method addTo(map: Map): this
11605 // Adds the control to the given map.
11606 addTo: function (map) {
11610 var container = this._container = this.onAdd(map),
11611 pos = this.getPosition(),
11612 corner = map._controlCorners[pos];
11614 L.DomUtil.addClass(container, 'leaflet-control');
11616 if (pos.indexOf('bottom') !== -1) {
11617 corner.insertBefore(container, corner.firstChild);
11619 corner.appendChild(container);
11625 // @method remove: this
11626 // Removes the control from the map it is currently active on.
11627 remove: function () {
11632 L.DomUtil.remove(this._container);
11634 if (this.onRemove) {
11635 this.onRemove(this._map);
11643 _refocusOnMap: function (e) {
11644 // if map exists and event is not a keyboard event
11645 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
11646 this._map.getContainer().focus();
11651 L.control = function (options) {
11652 return new L.Control(options);
11655 /* @section Extension methods
11658 * Every control should extend from `L.Control` and (re-)implement the following methods.
11660 * @method onAdd(map: Map): HTMLElement
11661 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
11663 * @method onRemove(map: Map)
11664 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
11668 * @section Methods for Layers and Controls
11671 // @method addControl(control: Control): this
11672 // Adds the given control to the map
11673 addControl: function (control) {
11674 control.addTo(this);
11678 // @method removeControl(control: Control): this
11679 // Removes the given control from the map
11680 removeControl: function (control) {
11685 _initControlPos: function () {
11686 var corners = this._controlCorners = {},
11688 container = this._controlContainer =
11689 L.DomUtil.create('div', l + 'control-container', this._container);
11691 function createCorner(vSide, hSide) {
11692 var className = l + vSide + ' ' + l + hSide;
11694 corners[vSide + hSide] = L.DomUtil.create('div', className, container);
11697 createCorner('top', 'left');
11698 createCorner('top', 'right');
11699 createCorner('bottom', 'left');
11700 createCorner('bottom', 'right');
11703 _clearControlPos: function () {
11704 L.DomUtil.remove(this._controlContainer);
11711 * @class Control.Zoom
11712 * @aka L.Control.Zoom
11713 * @inherits Control
11715 * A basic zoom control with two buttons (zoom in and zoom out). It is put on the map by default unless you set its [`zoomControl` option](#map-zoomcontrol) to `false`. Extends `Control`.
11718 L.Control.Zoom = L.Control.extend({
11720 // @aka Control.Zoom options
11722 position: 'topleft',
11724 // @option zoomInText: String = '+'
11725 // The text set on the 'zoom in' button.
11728 // @option zoomInTitle: String = 'Zoom in'
11729 // The title set on the 'zoom in' button.
11730 zoomInTitle: 'Zoom in',
11732 // @option zoomOutText: String = '-'
11733 // The text set on the 'zoom out' button.
11736 // @option zoomOutTitle: String = 'Zoom out'
11737 // The title set on the 'zoom out' button.
11738 zoomOutTitle: 'Zoom out'
11741 onAdd: function (map) {
11742 var zoomName = 'leaflet-control-zoom',
11743 container = L.DomUtil.create('div', zoomName + ' leaflet-bar'),
11744 options = this.options;
11746 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle,
11747 zoomName + '-in', container, this._zoomIn);
11748 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
11749 zoomName + '-out', container, this._zoomOut);
11751 this._updateDisabled();
11752 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
11757 onRemove: function (map) {
11758 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
11761 disable: function () {
11762 this._disabled = true;
11763 this._updateDisabled();
11767 enable: function () {
11768 this._disabled = false;
11769 this._updateDisabled();
11773 _zoomIn: function (e) {
11774 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
11775 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
11779 _zoomOut: function (e) {
11780 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
11781 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
11785 _createButton: function (html, title, className, container, fn) {
11786 var link = L.DomUtil.create('a', className, container);
11787 link.innerHTML = html;
11789 link.title = title;
11792 .on(link, 'mousedown dblclick', L.DomEvent.stopPropagation)
11793 .on(link, 'click', L.DomEvent.stop)
11794 .on(link, 'click', fn, this)
11795 .on(link, 'click', this._refocusOnMap, this);
11800 _updateDisabled: function () {
11801 var map = this._map,
11802 className = 'leaflet-disabled';
11804 L.DomUtil.removeClass(this._zoomInButton, className);
11805 L.DomUtil.removeClass(this._zoomOutButton, className);
11807 if (this._disabled || map._zoom === map.getMinZoom()) {
11808 L.DomUtil.addClass(this._zoomOutButton, className);
11810 if (this._disabled || map._zoom === map.getMaxZoom()) {
11811 L.DomUtil.addClass(this._zoomInButton, className);
11817 // @section Control options
11818 // @option zoomControl: Boolean = true
11819 // Whether a [zoom control](#control-zoom) is added to the map by default.
11820 L.Map.mergeOptions({
11824 L.Map.addInitHook(function () {
11825 if (this.options.zoomControl) {
11826 this.zoomControl = new L.Control.Zoom();
11827 this.addControl(this.zoomControl);
11831 // @namespace Control.Zoom
11832 // @factory L.control.zoom(options: Control.Zoom options)
11833 // Creates a zoom control
11834 L.control.zoom = function (options) {
11835 return new L.Control.Zoom(options);
11841 * @class Control.Attribution
11842 * @aka L.Control.Attribution
11843 * @inherits Control
11845 * The attribution control allows you to display attribution data in a small text box on a map. It is put on the map by default unless you set its [`attributionControl` option](#map-attributioncontrol) to `false`, and it fetches attribution texts from layers with the [`getAttribution` method](#layer-getattribution) automatically. Extends Control.
11848 L.Control.Attribution = L.Control.extend({
11850 // @aka Control.Attribution options
11852 position: 'bottomright',
11854 // @option prefix: String = 'Leaflet'
11855 // The HTML text shown before the attributions. Pass `false` to disable.
11856 prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
11859 initialize: function (options) {
11860 L.setOptions(this, options);
11862 this._attributions = {};
11865 onAdd: function (map) {
11866 map.attributionControl = this;
11867 this._container = L.DomUtil.create('div', 'leaflet-control-attribution');
11869 L.DomEvent.disableClickPropagation(this._container);
11872 // TODO ugly, refactor
11873 for (var i in map._layers) {
11874 if (map._layers[i].getAttribution) {
11875 this.addAttribution(map._layers[i].getAttribution());
11881 return this._container;
11884 // @method setPrefix(prefix: String): this
11885 // Sets the text before the attributions.
11886 setPrefix: function (prefix) {
11887 this.options.prefix = prefix;
11892 // @method addAttribution(text: String): this
11893 // Adds an attribution text (e.g. `'Vector data © Mapbox'`).
11894 addAttribution: function (text) {
11895 if (!text) { return this; }
11897 if (!this._attributions[text]) {
11898 this._attributions[text] = 0;
11900 this._attributions[text]++;
11907 // @method removeAttribution(text: String): this
11908 // Removes an attribution text.
11909 removeAttribution: function (text) {
11910 if (!text) { return this; }
11912 if (this._attributions[text]) {
11913 this._attributions[text]--;
11920 _update: function () {
11921 if (!this._map) { return; }
11925 for (var i in this._attributions) {
11926 if (this._attributions[i]) {
11931 var prefixAndAttribs = [];
11933 if (this.options.prefix) {
11934 prefixAndAttribs.push(this.options.prefix);
11936 if (attribs.length) {
11937 prefixAndAttribs.push(attribs.join(', '));
11940 this._container.innerHTML = prefixAndAttribs.join(' | ');
11945 // @section Control options
11946 // @option attributionControl: Boolean = true
11947 // Whether a [attribution control](#control-attribution) is added to the map by default.
11948 L.Map.mergeOptions({
11949 attributionControl: true
11952 L.Map.addInitHook(function () {
11953 if (this.options.attributionControl) {
11954 new L.Control.Attribution().addTo(this);
11958 // @namespace Control.Attribution
11959 // @factory L.control.attribution(options: Control.Attribution options)
11960 // Creates an attribution control.
11961 L.control.attribution = function (options) {
11962 return new L.Control.Attribution(options);
11968 * @class Control.Scale
11969 * @aka L.Control.Scale
11970 * @inherits Control
11972 * A simple scale control that shows the scale of the current center of screen in metric (m/km) and imperial (mi/ft) systems. Extends `Control`.
11977 * L.control.scale().addTo(map);
11981 L.Control.Scale = L.Control.extend({
11983 // @aka Control.Scale options
11985 position: 'bottomleft',
11987 // @option maxWidth: Number = 100
11988 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
11991 // @option metric: Boolean = True
11992 // Whether to show the metric scale line (m/km).
11995 // @option imperial: Boolean = True
11996 // Whether to show the imperial scale line (mi/ft).
11999 // @option updateWhenIdle: Boolean = false
12000 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
12003 onAdd: function (map) {
12004 var className = 'leaflet-control-scale',
12005 container = L.DomUtil.create('div', className),
12006 options = this.options;
12008 this._addScales(options, className + '-line', container);
12010 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
12011 map.whenReady(this._update, this);
12016 onRemove: function (map) {
12017 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
12020 _addScales: function (options, className, container) {
12021 if (options.metric) {
12022 this._mScale = L.DomUtil.create('div', className, container);
12024 if (options.imperial) {
12025 this._iScale = L.DomUtil.create('div', className, container);
12029 _update: function () {
12030 var map = this._map,
12031 y = map.getSize().y / 2;
12033 var maxMeters = map.distance(
12034 map.containerPointToLatLng([0, y]),
12035 map.containerPointToLatLng([this.options.maxWidth, y]));
12037 this._updateScales(maxMeters);
12040 _updateScales: function (maxMeters) {
12041 if (this.options.metric && maxMeters) {
12042 this._updateMetric(maxMeters);
12044 if (this.options.imperial && maxMeters) {
12045 this._updateImperial(maxMeters);
12049 _updateMetric: function (maxMeters) {
12050 var meters = this._getRoundNum(maxMeters),
12051 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
12053 this._updateScale(this._mScale, label, meters / maxMeters);
12056 _updateImperial: function (maxMeters) {
12057 var maxFeet = maxMeters * 3.2808399,
12058 maxMiles, miles, feet;
12060 if (maxFeet > 5280) {
12061 maxMiles = maxFeet / 5280;
12062 miles = this._getRoundNum(maxMiles);
12063 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
12066 feet = this._getRoundNum(maxFeet);
12067 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
12071 _updateScale: function (scale, text, ratio) {
12072 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
12073 scale.innerHTML = text;
12076 _getRoundNum: function (num) {
12077 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
12090 // @factory L.control.scale(options?: Control.Scale options)
12091 // Creates an scale control with the given options.
12092 L.control.scale = function (options) {
12093 return new L.Control.Scale(options);
12099 * @class Control.Layers
12100 * @aka L.Control.Layers
12101 * @inherits Control
12103 * The layers control gives users the ability to switch between different base layers and switch overlays on/off (check out the [detailed example](http://leafletjs.com/examples/layers-control.html)). Extends `Control`.
12108 * var baseLayers = {
12109 * "Mapbox": mapbox,
12110 * "OpenStreetMap": osm
12114 * "Marker": marker,
12115 * "Roads": roadsLayer
12118 * L.control.layers(baseLayers, overlays).addTo(map);
12121 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
12125 * "<someName1>": layer1,
12126 * "<someName2>": layer2
12130 * The layer names can contain HTML, which allows you to add additional styling to the items:
12133 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
12138 L.Control.Layers = L.Control.extend({
12140 // @aka Control.Layers options
12142 // @option collapsed: Boolean = true
12143 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
12145 position: 'topright',
12147 // @option autoZIndex: Boolean = true
12148 // If `true`, the control will assign zIndexes in increasing order to all of its layers so that the order is preserved when switching them on/off.
12151 // @option hideSingleBase: Boolean = false
12152 // If `true`, the base layers in the control will be hidden when there is only one.
12153 hideSingleBase: false
12156 initialize: function (baseLayers, overlays, options) {
12157 L.setOptions(this, options);
12160 this._lastZIndex = 0;
12161 this._handlingClick = false;
12163 for (var i in baseLayers) {
12164 this._addLayer(baseLayers[i], i);
12167 for (i in overlays) {
12168 this._addLayer(overlays[i], i, true);
12172 onAdd: function (map) {
12173 this._initLayout();
12177 map.on('zoomend', this._checkDisabledLayers, this);
12179 return this._container;
12182 onRemove: function () {
12183 this._map.off('zoomend', this._checkDisabledLayers, this);
12185 for (var i = 0; i < this._layers.length; i++) {
12186 this._layers[i].layer.off('add remove', this._onLayerChange, this);
12190 // @method addBaseLayer(layer: Layer, name: String): this
12191 // Adds a base layer (radio button entry) with the given name to the control.
12192 addBaseLayer: function (layer, name) {
12193 this._addLayer(layer, name);
12194 return (this._map) ? this._update() : this;
12197 // @method addOverlay(layer: Layer, name: String): this
12198 // Adds an overlay (checkbox entry) with the given name to the control.
12199 addOverlay: function (layer, name) {
12200 this._addLayer(layer, name, true);
12201 return (this._map) ? this._update() : this;
12204 // @method removeLayer(layer: Layer): this
12205 // Remove the given layer from the control.
12206 removeLayer: function (layer) {
12207 layer.off('add remove', this._onLayerChange, this);
12209 var obj = this._getLayer(L.stamp(layer));
12211 this._layers.splice(this._layers.indexOf(obj), 1);
12213 return (this._map) ? this._update() : this;
12216 // @method expand(): this
12217 // Expand the control container if collapsed.
12218 expand: function () {
12219 L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');
12220 this._form.style.height = null;
12221 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
12222 if (acceptableHeight < this._form.clientHeight) {
12223 L.DomUtil.addClass(this._form, 'leaflet-control-layers-scrollbar');
12224 this._form.style.height = acceptableHeight + 'px';
12226 L.DomUtil.removeClass(this._form, 'leaflet-control-layers-scrollbar');
12228 this._checkDisabledLayers();
12232 // @method collapse(): this
12233 // Collapse the control container if expanded.
12234 collapse: function () {
12235 L.DomUtil.removeClass(this._container, 'leaflet-control-layers-expanded');
12239 _initLayout: function () {
12240 var className = 'leaflet-control-layers',
12241 container = this._container = L.DomUtil.create('div', className);
12243 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
12244 container.setAttribute('aria-haspopup', true);
12246 L.DomEvent.disableClickPropagation(container);
12247 if (!L.Browser.touch) {
12248 L.DomEvent.disableScrollPropagation(container);
12251 var form = this._form = L.DomUtil.create('form', className + '-list');
12253 if (this.options.collapsed) {
12254 if (!L.Browser.android) {
12255 L.DomEvent.on(container, {
12256 mouseenter: this.expand,
12257 mouseleave: this.collapse
12261 var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
12263 link.title = 'Layers';
12265 if (L.Browser.touch) {
12267 .on(link, 'click', L.DomEvent.stop)
12268 .on(link, 'click', this.expand, this);
12270 L.DomEvent.on(link, 'focus', this.expand, this);
12273 // work around for Firefox Android issue https://github.com/Leaflet/Leaflet/issues/2033
12274 L.DomEvent.on(form, 'click', function () {
12275 setTimeout(L.bind(this._onInputClick, this), 0);
12278 this._map.on('click', this.collapse, this);
12279 // TODO keyboard accessibility
12284 this._baseLayersList = L.DomUtil.create('div', className + '-base', form);
12285 this._separator = L.DomUtil.create('div', className + '-separator', form);
12286 this._overlaysList = L.DomUtil.create('div', className + '-overlays', form);
12288 container.appendChild(form);
12291 _getLayer: function (id) {
12292 for (var i = 0; i < this._layers.length; i++) {
12294 if (this._layers[i] && L.stamp(this._layers[i].layer) === id) {
12295 return this._layers[i];
12300 _addLayer: function (layer, name, overlay) {
12301 layer.on('add remove', this._onLayerChange, this);
12303 this._layers.push({
12309 if (this.options.autoZIndex && layer.setZIndex) {
12310 this._lastZIndex++;
12311 layer.setZIndex(this._lastZIndex);
12315 _update: function () {
12316 if (!this._container) { return this; }
12318 L.DomUtil.empty(this._baseLayersList);
12319 L.DomUtil.empty(this._overlaysList);
12321 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
12323 for (i = 0; i < this._layers.length; i++) {
12324 obj = this._layers[i];
12325 this._addItem(obj);
12326 overlaysPresent = overlaysPresent || obj.overlay;
12327 baseLayersPresent = baseLayersPresent || !obj.overlay;
12328 baseLayersCount += !obj.overlay ? 1 : 0;
12331 // Hide base layers section if there's only one layer.
12332 if (this.options.hideSingleBase) {
12333 baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
12334 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
12337 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
12342 _onLayerChange: function (e) {
12343 if (!this._handlingClick) {
12347 var obj = this._getLayer(L.stamp(e.target));
12350 // @section Layer events
12351 // @event baselayerchange: LayersControlEvent
12352 // Fired when the base layer is changed through the [layer control](#control-layers).
12353 // @event overlayadd: LayersControlEvent
12354 // Fired when an overlay is selected through the [layer control](#control-layers).
12355 // @event overlayremove: LayersControlEvent
12356 // Fired when an overlay is deselected through the [layer control](#control-layers).
12357 // @namespace Control.Layers
12358 var type = obj.overlay ?
12359 (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
12360 (e.type === 'add' ? 'baselayerchange' : null);
12363 this._map.fire(type, obj);
12367 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
12368 _createRadioElement: function (name, checked) {
12370 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
12371 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
12373 var radioFragment = document.createElement('div');
12374 radioFragment.innerHTML = radioHtml;
12376 return radioFragment.firstChild;
12379 _addItem: function (obj) {
12380 var label = document.createElement('label'),
12381 checked = this._map.hasLayer(obj.layer),
12385 input = document.createElement('input');
12386 input.type = 'checkbox';
12387 input.className = 'leaflet-control-layers-selector';
12388 input.defaultChecked = checked;
12390 input = this._createRadioElement('leaflet-base-layers', checked);
12393 input.layerId = L.stamp(obj.layer);
12395 L.DomEvent.on(input, 'click', this._onInputClick, this);
12397 var name = document.createElement('span');
12398 name.innerHTML = ' ' + obj.name;
12400 // Helps from preventing layer control flicker when checkboxes are disabled
12401 // https://github.com/Leaflet/Leaflet/issues/2771
12402 var holder = document.createElement('div');
12404 label.appendChild(holder);
12405 holder.appendChild(input);
12406 holder.appendChild(name);
12408 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
12409 container.appendChild(label);
12411 this._checkDisabledLayers();
12415 _onInputClick: function () {
12416 var inputs = this._form.getElementsByTagName('input'),
12417 input, layer, hasLayer;
12418 var addedLayers = [],
12419 removedLayers = [];
12421 this._handlingClick = true;
12423 for (var i = inputs.length - 1; i >= 0; i--) {
12425 layer = this._getLayer(input.layerId).layer;
12426 hasLayer = this._map.hasLayer(layer);
12428 if (input.checked && !hasLayer) {
12429 addedLayers.push(layer);
12431 } else if (!input.checked && hasLayer) {
12432 removedLayers.push(layer);
12436 // Bugfix issue 2318: Should remove all old layers before readding new ones
12437 for (i = 0; i < removedLayers.length; i++) {
12438 this._map.removeLayer(removedLayers[i]);
12440 for (i = 0; i < addedLayers.length; i++) {
12441 this._map.addLayer(addedLayers[i]);
12444 this._handlingClick = false;
12446 this._refocusOnMap();
12449 _checkDisabledLayers: function () {
12450 var inputs = this._form.getElementsByTagName('input'),
12453 zoom = this._map.getZoom();
12455 for (var i = inputs.length - 1; i >= 0; i--) {
12457 layer = this._getLayer(input.layerId).layer;
12458 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
12459 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
12464 _expand: function () {
12465 // Backward compatibility, remove me in 1.1.
12466 return this.expand();
12469 _collapse: function () {
12470 // Backward compatibility, remove me in 1.1.
12471 return this.collapse();
12477 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
12478 // Creates an attribution control with the given layers. Base layers will be switched with radio buttons, while overlays will be switched with checkboxes. Note that all base layers should be passed in the base layers object, but only one should be added to the map during map instantiation.
12479 L.control.layers = function (baseLayers, overlays, options) {
12480 return new L.Control.Layers(baseLayers, overlays, options);
12486 * @class PosAnimation
12487 * @aka L.PosAnimation
12488 * @inherits Evented
12489 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
12493 * var fx = new L.PosAnimation();
12494 * fx.run(el, [300, 500], 0.5);
12497 * @constructor L.PosAnimation()
12498 * Creates a `PosAnimation` object.
12502 L.PosAnimation = L.Evented.extend({
12504 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
12505 // Run an animation of a given element to a new position, optionally setting
12506 // duration in seconds (`0.25` by default) and easing linearity factor (3rd
12507 // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
12508 // `0.5` by default).
12509 run: function (el, newPos, duration, easeLinearity) {
12513 this._inProgress = true;
12514 this._duration = duration || 0.25;
12515 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
12517 this._startPos = L.DomUtil.getPosition(el);
12518 this._offset = newPos.subtract(this._startPos);
12519 this._startTime = +new Date();
12521 // @event start: Event
12522 // Fired when the animation starts
12523 this.fire('start');
12529 // Stops the animation (if currently running).
12530 stop: function () {
12531 if (!this._inProgress) { return; }
12537 _animate: function () {
12539 this._animId = L.Util.requestAnimFrame(this._animate, this);
12543 _step: function (round) {
12544 var elapsed = (+new Date()) - this._startTime,
12545 duration = this._duration * 1000;
12547 if (elapsed < duration) {
12548 this._runFrame(this._easeOut(elapsed / duration), round);
12555 _runFrame: function (progress, round) {
12556 var pos = this._startPos.add(this._offset.multiplyBy(progress));
12560 L.DomUtil.setPosition(this._el, pos);
12562 // @event step: Event
12563 // Fired continuously during the animation.
12567 _complete: function () {
12568 L.Util.cancelAnimFrame(this._animId);
12570 this._inProgress = false;
12571 // @event end: Event
12572 // Fired when the animation ends.
12576 _easeOut: function (t) {
12577 return 1 - Math.pow(1 - t, this._easeOutPower);
12584 * Extends L.Map to handle panning animations.
12589 setView: function (center, zoom, options) {
12591 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
12592 center = this._limitCenter(L.latLng(center), zoom, this.options.maxBounds);
12593 options = options || {};
12597 if (this._loaded && !options.reset && options !== true) {
12599 if (options.animate !== undefined) {
12600 options.zoom = L.extend({animate: options.animate}, options.zoom);
12601 options.pan = L.extend({animate: options.animate, duration: options.duration}, options.pan);
12604 // try animating pan or zoom
12605 var moved = (this._zoom !== zoom) ?
12606 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
12607 this._tryAnimatedPan(center, options.pan);
12610 // prevent resize handler call, the view will refresh after animation anyway
12611 clearTimeout(this._sizeTimer);
12616 // animation didn't start, just reset the map view
12617 this._resetView(center, zoom);
12622 panBy: function (offset, options) {
12623 offset = L.point(offset).round();
12624 options = options || {};
12626 if (!offset.x && !offset.y) {
12627 return this.fire('moveend');
12629 // If we pan too far, Chrome gets issues with tiles
12630 // and makes them disappear or appear in the wrong place (slightly offset) #2602
12631 if (options.animate !== true && !this.getSize().contains(offset)) {
12632 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
12636 if (!this._panAnim) {
12637 this._panAnim = new L.PosAnimation();
12640 'step': this._onPanTransitionStep,
12641 'end': this._onPanTransitionEnd
12645 // don't fire movestart if animating inertia
12646 if (!options.noMoveStart) {
12647 this.fire('movestart');
12650 // animate pan unless animate: false specified
12651 if (options.animate !== false) {
12652 L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
12654 var newPos = this._getMapPanePos().subtract(offset).round();
12655 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
12657 this._rawPanBy(offset);
12658 this.fire('move').fire('moveend');
12664 _onPanTransitionStep: function () {
12668 _onPanTransitionEnd: function () {
12669 L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
12670 this.fire('moveend');
12673 _tryAnimatedPan: function (center, options) {
12674 // difference between the new and current centers in pixels
12675 var offset = this._getCenterOffset(center)._floor();
12677 // don't animate too far unless animate: true specified in options
12678 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
12680 this.panBy(offset, options);
12689 * Extends L.Map to handle zoom animations.
12693 // @section Animation Options
12694 L.Map.mergeOptions({
12695 // @option zoomAnimation: Boolean = true
12696 // Whether the map zoom animation is enabled. By default it's enabled
12697 // in all browsers that support CSS3 Transitions except Android.
12698 zoomAnimation: true,
12700 // @option zoomAnimationThreshold: Number = 4
12701 // Won't animate zoom if the zoom difference exceeds this value.
12702 zoomAnimationThreshold: 4
12705 var zoomAnimated = L.DomUtil.TRANSITION && L.Browser.any3d && !L.Browser.mobileOpera;
12707 if (zoomAnimated) {
12709 L.Map.addInitHook(function () {
12710 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
12711 this._zoomAnimated = this.options.zoomAnimation;
12713 // zoom transitions run with the same duration for all layers, so if one of transitionend events
12714 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
12715 if (this._zoomAnimated) {
12717 this._createAnimProxy();
12719 L.DomEvent.on(this._proxy, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
12724 L.Map.include(!zoomAnimated ? {} : {
12726 _createAnimProxy: function () {
12728 var proxy = this._proxy = L.DomUtil.create('div', 'leaflet-proxy leaflet-zoom-animated');
12729 this._panes.mapPane.appendChild(proxy);
12731 this.on('zoomanim', function (e) {
12732 var prop = L.DomUtil.TRANSFORM,
12733 transform = proxy.style[prop];
12735 L.DomUtil.setTransform(proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
12737 // workaround for case when transform is the same and so transitionend event is not fired
12738 if (transform === proxy.style[prop] && this._animatingZoom) {
12739 this._onZoomTransitionEnd();
12743 this.on('load moveend', function () {
12744 var c = this.getCenter(),
12745 z = this.getZoom();
12746 L.DomUtil.setTransform(proxy, this.project(c, z), this.getZoomScale(z, 1));
12750 _catchTransitionEnd: function (e) {
12751 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
12752 this._onZoomTransitionEnd();
12756 _nothingToAnimate: function () {
12757 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
12760 _tryAnimatedZoom: function (center, zoom, options) {
12762 if (this._animatingZoom) { return true; }
12764 options = options || {};
12766 // don't animate if disabled, not supported or zoom difference is too large
12767 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
12768 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
12770 // offset is the pixel coords of the zoom origin relative to the current center
12771 var scale = this.getZoomScale(zoom),
12772 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
12774 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
12775 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
12777 L.Util.requestAnimFrame(function () {
12780 ._animateZoom(center, zoom, true);
12786 _animateZoom: function (center, zoom, startAnim, noUpdate) {
12788 this._animatingZoom = true;
12790 // remember what center/zoom to set after animation
12791 this._animateToCenter = center;
12792 this._animateToZoom = zoom;
12794 L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
12797 // @event zoomanim: ZoomAnimEvent
12798 // Fired on every frame of a zoom animation
12799 this.fire('zoomanim', {
12805 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
12806 setTimeout(L.bind(this._onZoomTransitionEnd, this), 250);
12809 _onZoomTransitionEnd: function () {
12810 if (!this._animatingZoom) { return; }
12812 L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
12814 this._animatingZoom = false;
12816 this._move(this._animateToCenter, this._animateToZoom);
12818 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
12819 L.Util.requestAnimFrame(function () {
12820 this._moveEnd(true);
12828 // @section Methods for modifying map state
12831 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
12832 // Sets the view of the map (geographical center and zoom) performing a smooth
12833 // pan-zoom animation.
12834 flyTo: function (targetCenter, targetZoom, options) {
12836 options = options || {};
12837 if (options.animate === false || !L.Browser.any3d) {
12838 return this.setView(targetCenter, targetZoom, options);
12843 var from = this.project(this.getCenter()),
12844 to = this.project(targetCenter),
12845 size = this.getSize(),
12846 startZoom = this._zoom;
12848 targetCenter = L.latLng(targetCenter);
12849 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
12851 var w0 = Math.max(size.x, size.y),
12852 w1 = w0 * this.getZoomScale(startZoom, targetZoom),
12853 u1 = (to.distanceTo(from)) || 1,
12858 var s1 = i ? -1 : 1,
12860 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
12861 b1 = 2 * s2 * rho2 * u1,
12863 sq = Math.sqrt(b * b + 1) - b;
12865 // workaround for floating point precision bug when sq = 0, log = -Infinite,
12866 // thus triggering an infinite loop in flyTo
12867 var log = sq < 0.000000001 ? -18 : Math.log(sq);
12872 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
12873 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
12874 function tanh(n) { return sinh(n) / cosh(n); }
12878 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
12879 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
12881 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
12883 var start = Date.now(),
12884 S = (r(1) - r0) / rho,
12885 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
12888 var t = (Date.now() - start) / duration,
12889 s = easeOut(t) * S;
12892 this._flyToFrame = L.Util.requestAnimFrame(frame, this);
12895 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
12896 this.getScaleZoom(w0 / w(s), startZoom),
12901 ._move(targetCenter, targetZoom)
12906 this._moveStart(true);
12912 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
12913 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
12914 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
12915 flyToBounds: function (bounds, options) {
12916 var target = this._getBoundsCenterZoom(bounds, options);
12917 return this.flyTo(target.center, target.zoom, options);
12924 * Provides L.Map with convenient shortcuts for using browser geolocation features.
12930 // @section Geolocation methods
12931 _defaultLocateOptions: {
12935 // maxZoom: <Number>
12937 // enableHighAccuracy: false
12940 // @method locate(options?: Locate options): this
12941 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
12942 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
12943 // and optionally sets the map view to the user's location with respect to
12944 // detection accuracy (or to the world view if geolocation failed).
12945 // Note that, if your page doesn't use HTTPS, this method will fail in
12946 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
12947 // See `Locate options` for more details.
12948 locate: function (options) {
12950 options = this._locateOptions = L.extend({}, this._defaultLocateOptions, options);
12952 if (!('geolocation' in navigator)) {
12953 this._handleGeolocationError({
12955 message: 'Geolocation not supported.'
12960 var onResponse = L.bind(this._handleGeolocationResponse, this),
12961 onError = L.bind(this._handleGeolocationError, this);
12963 if (options.watch) {
12964 this._locationWatchId =
12965 navigator.geolocation.watchPosition(onResponse, onError, options);
12967 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
12972 // @method stopLocate(): this
12973 // Stops watching location previously initiated by `map.locate({watch: true})`
12974 // and aborts resetting the map view if map.locate was called with
12975 // `{setView: true}`.
12976 stopLocate: function () {
12977 if (navigator.geolocation && navigator.geolocation.clearWatch) {
12978 navigator.geolocation.clearWatch(this._locationWatchId);
12980 if (this._locateOptions) {
12981 this._locateOptions.setView = false;
12986 _handleGeolocationError: function (error) {
12987 var c = error.code,
12988 message = error.message ||
12989 (c === 1 ? 'permission denied' :
12990 (c === 2 ? 'position unavailable' : 'timeout'));
12992 if (this._locateOptions.setView && !this._loaded) {
12996 // @section Location events
12997 // @event locationerror: ErrorEvent
12998 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
12999 this.fire('locationerror', {
13001 message: 'Geolocation error: ' + message + '.'
13005 _handleGeolocationResponse: function (pos) {
13006 var lat = pos.coords.latitude,
13007 lng = pos.coords.longitude,
13008 latlng = new L.LatLng(lat, lng),
13009 bounds = latlng.toBounds(pos.coords.accuracy),
13010 options = this._locateOptions;
13012 if (options.setView) {
13013 var zoom = this.getBoundsZoom(bounds);
13014 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
13020 timestamp: pos.timestamp
13023 for (var i in pos.coords) {
13024 if (typeof pos.coords[i] === 'number') {
13025 data[i] = pos.coords[i];
13029 // @event locationfound: LocationEvent
13030 // Fired when geolocation (using the [`locate`](#map-locate) method)
13031 // went successfully.
13032 this.fire('locationfound', data);
13038 }(window, document));
13039 //# sourceMappingURL=leaflet-src.map