2 Leaflet 1.0.2, 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 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 // @property x: Number; The `x` coordinate of the point
869 this.x = (round ? Math.round(x) : x);
870 // @property y: Number; The `y` coordinate of the point
871 this.y = (round ? Math.round(y) : y);
874 L.Point.prototype = {
876 // @method clone(): Point
877 // Returns a copy of the current point.
879 return new L.Point(this.x, this.y);
882 // @method add(otherPoint: Point): Point
883 // Returns the result of addition of the current and the given points.
884 add: function (point) {
885 // non-destructive, returns a new point
886 return this.clone()._add(L.point(point));
889 _add: function (point) {
890 // destructive, used directly for performance in situations where it's safe to modify existing point
896 // @method subtract(otherPoint: Point): Point
897 // Returns the result of subtraction of the given point from the current.
898 subtract: function (point) {
899 return this.clone()._subtract(L.point(point));
902 _subtract: function (point) {
908 // @method divideBy(num: Number): Point
909 // Returns the result of division of the current point by the given number.
910 divideBy: function (num) {
911 return this.clone()._divideBy(num);
914 _divideBy: function (num) {
920 // @method multiplyBy(num: Number): Point
921 // Returns the result of multiplication of the current point by the given number.
922 multiplyBy: function (num) {
923 return this.clone()._multiplyBy(num);
926 _multiplyBy: function (num) {
932 // @method scaleBy(scale: Point): Point
933 // Multiply each coordinate of the current point by each coordinate of
934 // `scale`. In linear algebra terms, multiply the point by the
935 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
936 // defined by `scale`.
937 scaleBy: function (point) {
938 return new L.Point(this.x * point.x, this.y * point.y);
941 // @method unscaleBy(scale: Point): Point
942 // Inverse of `scaleBy`. Divide each coordinate of the current point by
943 // each coordinate of `scale`.
944 unscaleBy: function (point) {
945 return new L.Point(this.x / point.x, this.y / point.y);
948 // @method round(): Point
949 // Returns a copy of the current point with rounded coordinates.
951 return this.clone()._round();
954 _round: function () {
955 this.x = Math.round(this.x);
956 this.y = Math.round(this.y);
960 // @method floor(): Point
961 // Returns a copy of the current point with floored coordinates (rounded down).
963 return this.clone()._floor();
966 _floor: function () {
967 this.x = Math.floor(this.x);
968 this.y = Math.floor(this.y);
972 // @method ceil(): Point
973 // Returns a copy of the current point with ceiled coordinates (rounded up).
975 return this.clone()._ceil();
979 this.x = Math.ceil(this.x);
980 this.y = Math.ceil(this.y);
984 // @method distanceTo(otherPoint: Point): Number
985 // Returns the cartesian distance between the current and the given points.
986 distanceTo: function (point) {
987 point = L.point(point);
989 var x = point.x - this.x,
990 y = point.y - this.y;
992 return Math.sqrt(x * x + y * y);
995 // @method equals(otherPoint: Point): Boolean
996 // Returns `true` if the given point has the same coordinates.
997 equals: function (point) {
998 point = L.point(point);
1000 return point.x === this.x &&
1004 // @method contains(otherPoint: Point): Boolean
1005 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
1006 contains: function (point) {
1007 point = L.point(point);
1009 return Math.abs(point.x) <= Math.abs(this.x) &&
1010 Math.abs(point.y) <= Math.abs(this.y);
1013 // @method toString(): String
1014 // Returns a string representation of the point for debugging purposes.
1015 toString: function () {
1017 L.Util.formatNum(this.x) + ', ' +
1018 L.Util.formatNum(this.y) + ')';
1022 // @factory L.point(x: Number, y: Number, round?: Boolean)
1023 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
1026 // @factory L.point(coords: Number[])
1027 // Expects an array of the form `[x, y]` instead.
1030 // @factory L.point(coords: Object)
1031 // Expects a plain object of the form `{x: Number, y: Number}` instead.
1032 L.point = function (x, y, round) {
1033 if (x instanceof L.Point) {
1036 if (L.Util.isArray(x)) {
1037 return new L.Point(x[0], x[1]);
1039 if (x === undefined || x === null) {
1042 if (typeof x === 'object' && 'x' in x && 'y' in x) {
1043 return new L.Point(x.x, x.y);
1045 return new L.Point(x, y, round);
1054 * Represents a rectangular area in pixel coordinates.
1059 * var p1 = L.point(10, 10),
1060 * p2 = L.point(40, 60),
1061 * bounds = L.bounds(p1, p2);
1064 * 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:
1067 * otherBounds.intersects([[10, 10], [40, 60]]);
1071 L.Bounds = function (a, b) {
1074 var points = b ? [a, b] : a;
1076 for (var i = 0, len = points.length; i < len; i++) {
1077 this.extend(points[i]);
1081 L.Bounds.prototype = {
1082 // @method extend(point: Point): this
1083 // Extends the bounds to contain the given point.
1084 extend: function (point) { // (Point)
1085 point = L.point(point);
1087 // @property min: Point
1088 // The top left corner of the rectangle.
1089 // @property max: Point
1090 // The bottom right corner of the rectangle.
1091 if (!this.min && !this.max) {
1092 this.min = point.clone();
1093 this.max = point.clone();
1095 this.min.x = Math.min(point.x, this.min.x);
1096 this.max.x = Math.max(point.x, this.max.x);
1097 this.min.y = Math.min(point.y, this.min.y);
1098 this.max.y = Math.max(point.y, this.max.y);
1103 // @method getCenter(round?: Boolean): Point
1104 // Returns the center point of the bounds.
1105 getCenter: function (round) {
1107 (this.min.x + this.max.x) / 2,
1108 (this.min.y + this.max.y) / 2, round);
1111 // @method getBottomLeft(): Point
1112 // Returns the bottom-left point of the bounds.
1113 getBottomLeft: function () {
1114 return new L.Point(this.min.x, this.max.y);
1117 // @method getTopRight(): Point
1118 // Returns the top-right point of the bounds.
1119 getTopRight: function () { // -> Point
1120 return new L.Point(this.max.x, this.min.y);
1123 // @method getSize(): Point
1124 // Returns the size of the given bounds
1125 getSize: function () {
1126 return this.max.subtract(this.min);
1129 // @method contains(otherBounds: Bounds): Boolean
1130 // Returns `true` if the rectangle contains the given one.
1132 // @method contains(point: Point): Boolean
1133 // Returns `true` if the rectangle contains the given point.
1134 contains: function (obj) {
1137 if (typeof obj[0] === 'number' || obj instanceof L.Point) {
1140 obj = L.bounds(obj);
1143 if (obj instanceof L.Bounds) {
1150 return (min.x >= this.min.x) &&
1151 (max.x <= this.max.x) &&
1152 (min.y >= this.min.y) &&
1153 (max.y <= this.max.y);
1156 // @method intersects(otherBounds: Bounds): Boolean
1157 // Returns `true` if the rectangle intersects the given bounds. Two bounds
1158 // intersect if they have at least one point in common.
1159 intersects: function (bounds) { // (Bounds) -> Boolean
1160 bounds = L.bounds(bounds);
1166 xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1167 yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1169 return xIntersects && yIntersects;
1172 // @method overlaps(otherBounds: Bounds): Boolean
1173 // Returns `true` if the rectangle overlaps the given bounds. Two bounds
1174 // overlap if their intersection is an area.
1175 overlaps: function (bounds) { // (Bounds) -> Boolean
1176 bounds = L.bounds(bounds);
1182 xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1183 yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1185 return xOverlaps && yOverlaps;
1188 isValid: function () {
1189 return !!(this.min && this.max);
1194 // @factory L.bounds(topLeft: Point, bottomRight: Point)
1195 // Creates a Bounds object from two coordinates (usually top-left and bottom-right corners).
1197 // @factory L.bounds(points: Point[])
1198 // Creates a Bounds object from the points it contains
1199 L.bounds = function (a, b) {
1200 if (!a || a instanceof L.Bounds) {
1203 return new L.Bounds(a, b);
1209 * @class Transformation
1210 * @aka L.Transformation
1212 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1213 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1214 * the reverse. Used by Leaflet in its projections code.
1219 * var transformation = new L.Transformation(2, 5, -1, 10),
1220 * p = L.point(1, 2),
1221 * p2 = transformation.transform(p), // L.point(7, 8)
1222 * p3 = transformation.untransform(p2); // L.point(1, 2)
1227 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1228 // Creates a `Transformation` object with the given coefficients.
1229 L.Transformation = function (a, b, c, d) {
1236 L.Transformation.prototype = {
1237 // @method transform(point: Point, scale?: Number): Point
1238 // Returns a transformed point, optionally multiplied by the given scale.
1239 // Only accepts actual `L.Point` instances, not arrays.
1240 transform: function (point, scale) { // (Point, Number) -> Point
1241 return this._transform(point.clone(), scale);
1244 // destructive transform (faster)
1245 _transform: function (point, scale) {
1247 point.x = scale * (this._a * point.x + this._b);
1248 point.y = scale * (this._c * point.y + this._d);
1252 // @method untransform(point: Point, scale?: Number): Point
1253 // Returns the reverse transformation of the given point, optionally divided
1254 // by the given scale. Only accepts actual `L.Point` instances, not arrays.
1255 untransform: function (point, scale) {
1258 (point.x / scale - this._b) / this._a,
1259 (point.y / scale - this._d) / this._c);
1266 * @namespace DomUtil
1268 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
1269 * tree, used by Leaflet internally.
1271 * Most functions expecting or returning a `HTMLElement` also work for
1272 * SVG elements. The only difference is that classes refer to CSS classes
1273 * in HTML and SVG classes in SVG.
1278 // @function get(id: String|HTMLElement): HTMLElement
1279 // Returns an element given its DOM id, or returns the element itself
1280 // if it was passed directly.
1281 get: function (id) {
1282 return typeof id === 'string' ? document.getElementById(id) : id;
1285 // @function getStyle(el: HTMLElement, styleAttrib: String): String
1286 // Returns the value for a certain style attribute on an element,
1287 // including computed values or values set through CSS.
1288 getStyle: function (el, style) {
1290 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
1292 if ((!value || value === 'auto') && document.defaultView) {
1293 var css = document.defaultView.getComputedStyle(el, null);
1294 value = css ? css[style] : null;
1297 return value === 'auto' ? null : value;
1300 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
1301 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
1302 create: function (tagName, className, container) {
1304 var el = document.createElement(tagName);
1305 el.className = className || '';
1308 container.appendChild(el);
1314 // @function remove(el: HTMLElement)
1315 // Removes `el` from its parent element
1316 remove: function (el) {
1317 var parent = el.parentNode;
1319 parent.removeChild(el);
1323 // @function empty(el: HTMLElement)
1324 // Removes all of `el`'s children elements from `el`
1325 empty: function (el) {
1326 while (el.firstChild) {
1327 el.removeChild(el.firstChild);
1331 // @function toFront(el: HTMLElement)
1332 // Makes `el` the last children of its parent, so it renders in front of the other children.
1333 toFront: function (el) {
1334 el.parentNode.appendChild(el);
1337 // @function toBack(el: HTMLElement)
1338 // Makes `el` the first children of its parent, so it renders back from the other children.
1339 toBack: function (el) {
1340 var parent = el.parentNode;
1341 parent.insertBefore(el, parent.firstChild);
1344 // @function hasClass(el: HTMLElement, name: String): Boolean
1345 // Returns `true` if the element's class attribute contains `name`.
1346 hasClass: function (el, name) {
1347 if (el.classList !== undefined) {
1348 return el.classList.contains(name);
1350 var className = L.DomUtil.getClass(el);
1351 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
1354 // @function addClass(el: HTMLElement, name: String)
1355 // Adds `name` to the element's class attribute.
1356 addClass: function (el, name) {
1357 if (el.classList !== undefined) {
1358 var classes = L.Util.splitWords(name);
1359 for (var i = 0, len = classes.length; i < len; i++) {
1360 el.classList.add(classes[i]);
1362 } else if (!L.DomUtil.hasClass(el, name)) {
1363 var className = L.DomUtil.getClass(el);
1364 L.DomUtil.setClass(el, (className ? className + ' ' : '') + name);
1368 // @function removeClass(el: HTMLElement, name: String)
1369 // Removes `name` from the element's class attribute.
1370 removeClass: function (el, name) {
1371 if (el.classList !== undefined) {
1372 el.classList.remove(name);
1374 L.DomUtil.setClass(el, L.Util.trim((' ' + L.DomUtil.getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
1378 // @function setClass(el: HTMLElement, name: String)
1379 // Sets the element's class.
1380 setClass: function (el, name) {
1381 if (el.className.baseVal === undefined) {
1382 el.className = name;
1384 // in case of SVG element
1385 el.className.baseVal = name;
1389 // @function getClass(el: HTMLElement): String
1390 // Returns the element's class.
1391 getClass: function (el) {
1392 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
1395 // @function setOpacity(el: HTMLElement, opacity: Number)
1396 // Set the opacity of an element (including old IE support).
1397 // `opacity` must be a number from `0` to `1`.
1398 setOpacity: function (el, value) {
1400 if ('opacity' in el.style) {
1401 el.style.opacity = value;
1403 } else if ('filter' in el.style) {
1404 L.DomUtil._setOpacityIE(el, value);
1408 _setOpacityIE: function (el, value) {
1410 filterName = 'DXImageTransform.Microsoft.Alpha';
1412 // filters collection throws an error if we try to retrieve a filter that doesn't exist
1414 filter = el.filters.item(filterName);
1416 // don't set opacity to 1 if we haven't already set an opacity,
1417 // it isn't needed and breaks transparent pngs.
1418 if (value === 1) { return; }
1421 value = Math.round(value * 100);
1424 filter.Enabled = (value !== 100);
1425 filter.Opacity = value;
1427 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
1431 // @function testProp(props: String[]): String|false
1432 // Goes through the array of style names and returns the first name
1433 // that is a valid style name for an element. If no such name is found,
1434 // it returns false. Useful for vendor-prefixed styles like `transform`.
1435 testProp: function (props) {
1437 var style = document.documentElement.style;
1439 for (var i = 0; i < props.length; i++) {
1440 if (props[i] in style) {
1447 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
1448 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
1449 // and optionally scaled by `scale`. Does not have an effect if the
1450 // browser doesn't support 3D CSS transforms.
1451 setTransform: function (el, offset, scale) {
1452 var pos = offset || new L.Point(0, 0);
1454 el.style[L.DomUtil.TRANSFORM] =
1456 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
1457 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
1458 (scale ? ' scale(' + scale + ')' : '');
1461 // @function setPosition(el: HTMLElement, position: Point)
1462 // Sets the position of `el` to coordinates specified by `position`,
1463 // using CSS translate or top/left positioning depending on the browser
1464 // (used by Leaflet internally to position its layers).
1465 setPosition: function (el, point) { // (HTMLElement, Point[, Boolean])
1468 el._leaflet_pos = point;
1471 if (L.Browser.any3d) {
1472 L.DomUtil.setTransform(el, point);
1474 el.style.left = point.x + 'px';
1475 el.style.top = point.y + 'px';
1479 // @function getPosition(el: HTMLElement): Point
1480 // Returns the coordinates of an element previously positioned with setPosition.
1481 getPosition: function (el) {
1482 // this method is only used for elements previously positioned using setPosition,
1483 // so it's safe to cache the position for performance
1485 return el._leaflet_pos || new L.Point(0, 0);
1491 // prefix style property names
1493 // @property TRANSFORM: String
1494 // Vendor-prefixed fransform style name (e.g. `'webkitTransform'` for WebKit).
1495 L.DomUtil.TRANSFORM = L.DomUtil.testProp(
1496 ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
1499 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
1500 // the same for the transitionend event, in particular the Android 4.1 stock browser
1502 // @property TRANSITION: String
1503 // Vendor-prefixed transform style name.
1504 var transition = L.DomUtil.TRANSITION = L.DomUtil.testProp(
1505 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
1507 L.DomUtil.TRANSITION_END =
1508 transition === 'webkitTransition' || transition === 'OTransition' ? transition + 'End' : 'transitionend';
1510 // @function disableTextSelection()
1511 // Prevents the user from generating `selectstart` DOM events, usually generated
1512 // when the user drags the mouse through a page with text. Used internally
1513 // by Leaflet to override the behaviour of any click-and-drag interaction on
1514 // the map. Affects drag interactions on the whole document.
1516 // @function enableTextSelection()
1517 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
1518 if ('onselectstart' in document) {
1519 L.DomUtil.disableTextSelection = function () {
1520 L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);
1522 L.DomUtil.enableTextSelection = function () {
1523 L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);
1527 var userSelectProperty = L.DomUtil.testProp(
1528 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
1530 L.DomUtil.disableTextSelection = function () {
1531 if (userSelectProperty) {
1532 var style = document.documentElement.style;
1533 this._userSelect = style[userSelectProperty];
1534 style[userSelectProperty] = 'none';
1537 L.DomUtil.enableTextSelection = function () {
1538 if (userSelectProperty) {
1539 document.documentElement.style[userSelectProperty] = this._userSelect;
1540 delete this._userSelect;
1545 // @function disableImageDrag()
1546 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
1547 // for `dragstart` DOM events, usually generated when the user drags an image.
1548 L.DomUtil.disableImageDrag = function () {
1549 L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault);
1552 // @function enableImageDrag()
1553 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
1554 L.DomUtil.enableImageDrag = function () {
1555 L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault);
1558 // @function preventOutline(el: HTMLElement)
1559 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
1560 // of the element `el` invisible. Used internally by Leaflet to prevent
1561 // focusable elements from displaying an outline when the user performs a
1562 // drag interaction on them.
1563 L.DomUtil.preventOutline = function (element) {
1564 while (element.tabIndex === -1) {
1565 element = element.parentNode;
1567 if (!element || !element.style) { return; }
1568 L.DomUtil.restoreOutline();
1569 this._outlineElement = element;
1570 this._outlineStyle = element.style.outline;
1571 element.style.outline = 'none';
1572 L.DomEvent.on(window, 'keydown', L.DomUtil.restoreOutline, this);
1575 // @function restoreOutline()
1576 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
1577 L.DomUtil.restoreOutline = function () {
1578 if (!this._outlineElement) { return; }
1579 this._outlineElement.style.outline = this._outlineStyle;
1580 delete this._outlineElement;
1581 delete this._outlineStyle;
1582 L.DomEvent.off(window, 'keydown', L.DomUtil.restoreOutline, this);
1591 * Represents a geographical point with a certain latitude and longitude.
1596 * var latlng = L.latLng(50.5, 30.5);
1599 * 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:
1602 * map.panTo([50, 30]);
1603 * map.panTo({lon: 30, lat: 50});
1604 * map.panTo({lat: 50, lng: 30});
1605 * map.panTo(L.latLng(50, 30));
1609 L.LatLng = function (lat, lng, alt) {
1610 if (isNaN(lat) || isNaN(lng)) {
1611 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1614 // @property lat: Number
1615 // Latitude in degrees
1618 // @property lng: Number
1619 // Longitude in degrees
1622 // @property alt: Number
1623 // Altitude in meters (optional)
1624 if (alt !== undefined) {
1629 L.LatLng.prototype = {
1630 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1631 // 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.
1632 equals: function (obj, maxMargin) {
1633 if (!obj) { return false; }
1635 obj = L.latLng(obj);
1637 var margin = Math.max(
1638 Math.abs(this.lat - obj.lat),
1639 Math.abs(this.lng - obj.lng));
1641 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1644 // @method toString(): String
1645 // Returns a string representation of the point (for debugging purposes).
1646 toString: function (precision) {
1648 L.Util.formatNum(this.lat, precision) + ', ' +
1649 L.Util.formatNum(this.lng, precision) + ')';
1652 // @method distanceTo(otherLatLng: LatLng): Number
1653 // Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula).
1654 distanceTo: function (other) {
1655 return L.CRS.Earth.distance(this, L.latLng(other));
1658 // @method wrap(): LatLng
1659 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1661 return L.CRS.Earth.wrapLatLng(this);
1664 // @method toBounds(sizeInMeters: Number): LatLngBounds
1665 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters` meters apart from the `LatLng`.
1666 toBounds: function (sizeInMeters) {
1667 var latAccuracy = 180 * sizeInMeters / 40075017,
1668 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1670 return L.latLngBounds(
1671 [this.lat - latAccuracy, this.lng - lngAccuracy],
1672 [this.lat + latAccuracy, this.lng + lngAccuracy]);
1675 clone: function () {
1676 return new L.LatLng(this.lat, this.lng, this.alt);
1682 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1683 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1686 // @factory L.latLng(coords: Array): LatLng
1687 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1690 // @factory L.latLng(coords: Object): LatLng
1691 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1693 L.latLng = function (a, b, c) {
1694 if (a instanceof L.LatLng) {
1697 if (L.Util.isArray(a) && typeof a[0] !== 'object') {
1698 if (a.length === 3) {
1699 return new L.LatLng(a[0], a[1], a[2]);
1701 if (a.length === 2) {
1702 return new L.LatLng(a[0], a[1]);
1706 if (a === undefined || a === null) {
1709 if (typeof a === 'object' && 'lat' in a) {
1710 return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1712 if (b === undefined) {
1715 return new L.LatLng(a, b, c);
1721 * @class LatLngBounds
1722 * @aka L.LatLngBounds
1724 * Represents a rectangular geographical area on a map.
1729 * var corner1 = L.latLng(40.712, -74.227),
1730 * corner2 = L.latLng(40.774, -74.125),
1731 * bounds = L.latLngBounds(corner1, corner2);
1734 * 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:
1738 * [40.712, -74.227],
1743 * Caution: if the area crosses the antimeridian (often confused with the International Date Line), you must specify corners _outside_ the [-180, 180] degrees longitude range.
1746 L.LatLngBounds = function (corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
1747 if (!corner1) { return; }
1749 var latlngs = corner2 ? [corner1, corner2] : corner1;
1751 for (var i = 0, len = latlngs.length; i < len; i++) {
1752 this.extend(latlngs[i]);
1756 L.LatLngBounds.prototype = {
1758 // @method extend(latlng: LatLng): this
1759 // Extend the bounds to contain the given point
1762 // @method extend(otherBounds: LatLngBounds): this
1763 // Extend the bounds to contain the given bounds
1764 extend: function (obj) {
1765 var sw = this._southWest,
1766 ne = this._northEast,
1769 if (obj instanceof L.LatLng) {
1773 } else if (obj instanceof L.LatLngBounds) {
1774 sw2 = obj._southWest;
1775 ne2 = obj._northEast;
1777 if (!sw2 || !ne2) { return this; }
1780 return obj ? this.extend(L.latLng(obj) || L.latLngBounds(obj)) : this;
1784 this._southWest = new L.LatLng(sw2.lat, sw2.lng);
1785 this._northEast = new L.LatLng(ne2.lat, ne2.lng);
1787 sw.lat = Math.min(sw2.lat, sw.lat);
1788 sw.lng = Math.min(sw2.lng, sw.lng);
1789 ne.lat = Math.max(ne2.lat, ne.lat);
1790 ne.lng = Math.max(ne2.lng, ne.lng);
1796 // @method pad(bufferRatio: Number): LatLngBounds
1797 // Returns bigger bounds created by extending the current bounds by a given percentage in each direction.
1798 pad: function (bufferRatio) {
1799 var sw = this._southWest,
1800 ne = this._northEast,
1801 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1802 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1804 return new L.LatLngBounds(
1805 new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1806 new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1809 // @method getCenter(): LatLng
1810 // Returns the center point of the bounds.
1811 getCenter: function () {
1812 return new L.LatLng(
1813 (this._southWest.lat + this._northEast.lat) / 2,
1814 (this._southWest.lng + this._northEast.lng) / 2);
1817 // @method getSouthWest(): LatLng
1818 // Returns the south-west point of the bounds.
1819 getSouthWest: function () {
1820 return this._southWest;
1823 // @method getNorthEast(): LatLng
1824 // Returns the north-east point of the bounds.
1825 getNorthEast: function () {
1826 return this._northEast;
1829 // @method getNorthWest(): LatLng
1830 // Returns the north-west point of the bounds.
1831 getNorthWest: function () {
1832 return new L.LatLng(this.getNorth(), this.getWest());
1835 // @method getSouthEast(): LatLng
1836 // Returns the south-east point of the bounds.
1837 getSouthEast: function () {
1838 return new L.LatLng(this.getSouth(), this.getEast());
1841 // @method getWest(): Number
1842 // Returns the west longitude of the bounds
1843 getWest: function () {
1844 return this._southWest.lng;
1847 // @method getSouth(): Number
1848 // Returns the south latitude of the bounds
1849 getSouth: function () {
1850 return this._southWest.lat;
1853 // @method getEast(): Number
1854 // Returns the east longitude of the bounds
1855 getEast: function () {
1856 return this._northEast.lng;
1859 // @method getNorth(): Number
1860 // Returns the north latitude of the bounds
1861 getNorth: function () {
1862 return this._northEast.lat;
1865 // @method contains(otherBounds: LatLngBounds): Boolean
1866 // Returns `true` if the rectangle contains the given one.
1869 // @method contains (latlng: LatLng): Boolean
1870 // Returns `true` if the rectangle contains the given point.
1871 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1872 if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
1873 obj = L.latLng(obj);
1875 obj = L.latLngBounds(obj);
1878 var sw = this._southWest,
1879 ne = this._northEast,
1882 if (obj instanceof L.LatLngBounds) {
1883 sw2 = obj.getSouthWest();
1884 ne2 = obj.getNorthEast();
1889 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1890 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1893 // @method intersects(otherBounds: LatLngBounds): Boolean
1894 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1895 intersects: function (bounds) {
1896 bounds = L.latLngBounds(bounds);
1898 var sw = this._southWest,
1899 ne = this._northEast,
1900 sw2 = bounds.getSouthWest(),
1901 ne2 = bounds.getNorthEast(),
1903 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1904 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1906 return latIntersects && lngIntersects;
1909 // @method overlaps(otherBounds: Bounds): Boolean
1910 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1911 overlaps: function (bounds) {
1912 bounds = L.latLngBounds(bounds);
1914 var sw = this._southWest,
1915 ne = this._northEast,
1916 sw2 = bounds.getSouthWest(),
1917 ne2 = bounds.getNorthEast(),
1919 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1920 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1922 return latOverlaps && lngOverlaps;
1925 // @method toBBoxString(): String
1926 // 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.
1927 toBBoxString: function () {
1928 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1931 // @method equals(otherBounds: LatLngBounds): Boolean
1932 // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds.
1933 equals: function (bounds) {
1934 if (!bounds) { return false; }
1936 bounds = L.latLngBounds(bounds);
1938 return this._southWest.equals(bounds.getSouthWest()) &&
1939 this._northEast.equals(bounds.getNorthEast());
1942 // @method isValid(): Boolean
1943 // Returns `true` if the bounds are properly initialized.
1944 isValid: function () {
1945 return !!(this._southWest && this._northEast);
1949 // TODO International date line?
1951 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
1952 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
1955 // @factory L.latLngBounds(latlngs: LatLng[])
1956 // 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).
1957 L.latLngBounds = function (a, b) {
1958 if (a instanceof L.LatLngBounds) {
1961 return new L.LatLngBounds(a, b);
1967 * @namespace Projection
1969 * Leaflet comes with a set of already defined Projections out of the box:
1971 * @projection L.Projection.LonLat
1973 * Equirectangular, or Plate Carree projection — the most simple projection,
1974 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
1975 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
1976 * `EPSG:3395` and `Simple` CRS.
1981 L.Projection.LonLat = {
1982 project: function (latlng) {
1983 return new L.Point(latlng.lng, latlng.lat);
1986 unproject: function (point) {
1987 return new L.LatLng(point.y, point.x);
1990 bounds: L.bounds([-180, -90], [180, 90])
1996 * @namespace Projection
1997 * @projection L.Projection.SphericalMercator
1999 * Spherical Mercator projection — the most common projection for online maps,
2000 * used by almost all free and commercial tile providers. Assumes that Earth is
2001 * a sphere. Used by the `EPSG:3857` CRS.
2004 L.Projection.SphericalMercator = {
2007 MAX_LATITUDE: 85.0511287798,
2009 project: function (latlng) {
2010 var d = Math.PI / 180,
2011 max = this.MAX_LATITUDE,
2012 lat = Math.max(Math.min(max, latlng.lat), -max),
2013 sin = Math.sin(lat * d);
2016 this.R * latlng.lng * d,
2017 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
2020 unproject: function (point) {
2021 var d = 180 / Math.PI;
2023 return new L.LatLng(
2024 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
2025 point.x * d / this.R);
2028 bounds: (function () {
2029 var d = 6378137 * Math.PI;
2030 return L.bounds([-d, -d], [d, d]);
2039 * Abstract class that defines coordinate reference systems for projecting
2040 * geographical points into pixel (screen) coordinates and back (and to
2041 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
2042 * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
2044 * Leaflet defines the most usual CRSs by default. If you want to use a
2045 * CRS not defined by default, take a look at the
2046 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
2050 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
2051 // Projects geographical coordinates into pixel coordinates for a given zoom.
2052 latLngToPoint: function (latlng, zoom) {
2053 var projectedPoint = this.projection.project(latlng),
2054 scale = this.scale(zoom);
2056 return this.transformation._transform(projectedPoint, scale);
2059 // @method pointToLatLng(point: Point, zoom: Number): LatLng
2060 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
2061 // zoom into geographical coordinates.
2062 pointToLatLng: function (point, zoom) {
2063 var scale = this.scale(zoom),
2064 untransformedPoint = this.transformation.untransform(point, scale);
2066 return this.projection.unproject(untransformedPoint);
2069 // @method project(latlng: LatLng): Point
2070 // Projects geographical coordinates into coordinates in units accepted for
2071 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
2072 project: function (latlng) {
2073 return this.projection.project(latlng);
2076 // @method unproject(point: Point): LatLng
2077 // Given a projected coordinate returns the corresponding LatLng.
2078 // The inverse of `project`.
2079 unproject: function (point) {
2080 return this.projection.unproject(point);
2083 // @method scale(zoom: Number): Number
2084 // Returns the scale used when transforming projected coordinates into
2085 // pixel coordinates for a particular zoom. For example, it returns
2086 // `256 * 2^zoom` for Mercator-based CRS.
2087 scale: function (zoom) {
2088 return 256 * Math.pow(2, zoom);
2091 // @method zoom(scale: Number): Number
2092 // Inverse of `scale()`, returns the zoom level corresponding to a scale
2093 // factor of `scale`.
2094 zoom: function (scale) {
2095 return Math.log(scale / 256) / Math.LN2;
2098 // @method getProjectedBounds(zoom: Number): Bounds
2099 // Returns the projection's bounds scaled and transformed for the provided `zoom`.
2100 getProjectedBounds: function (zoom) {
2101 if (this.infinite) { return null; }
2103 var b = this.projection.bounds,
2104 s = this.scale(zoom),
2105 min = this.transformation.transform(b.min, s),
2106 max = this.transformation.transform(b.max, s);
2108 return L.bounds(min, max);
2111 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
2112 // Returns the distance between two geographical coordinates.
2114 // @property code: String
2115 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
2117 // @property wrapLng: Number[]
2118 // An array of two numbers defining whether the longitude (horizontal) coordinate
2119 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
2120 // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
2122 // @property wrapLat: Number[]
2123 // Like `wrapLng`, but for the latitude (vertical) axis.
2125 // wrapLng: [min, max],
2126 // wrapLat: [min, max],
2128 // @property infinite: Boolean
2129 // If true, the coordinate space will be unbounded (infinite in both axes)
2132 // @method wrapLatLng(latlng: LatLng): LatLng
2133 // Returns a `LatLng` where lat and lng has been wrapped according to the
2134 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
2135 wrapLatLng: function (latlng) {
2136 var lng = this.wrapLng ? L.Util.wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
2137 lat = this.wrapLat ? L.Util.wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
2140 return L.latLng(lat, lng, alt);
2150 * A simple CRS that maps longitude and latitude into `x` and `y` directly.
2151 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
2152 * axis should still be inverted (going from bottom to top). `distance()` returns
2153 * simple euclidean distance.
2156 L.CRS.Simple = L.extend({}, L.CRS, {
2157 projection: L.Projection.LonLat,
2158 transformation: new L.Transformation(1, 0, -1, 0),
2160 scale: function (zoom) {
2161 return Math.pow(2, zoom);
2164 zoom: function (scale) {
2165 return Math.log(scale) / Math.LN2;
2168 distance: function (latlng1, latlng2) {
2169 var dx = latlng2.lng - latlng1.lng,
2170 dy = latlng2.lat - latlng1.lat;
2172 return Math.sqrt(dx * dx + dy * dy);
2184 * Serves as the base for CRS that are global such that they cover the earth.
2185 * Can only be used as the base for other CRS and cannot be used directly,
2186 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
2190 L.CRS.Earth = L.extend({}, L.CRS, {
2191 wrapLng: [-180, 180],
2193 // Mean Earth Radius, as recommended for use by
2194 // the International Union of Geodesy and Geophysics,
2195 // see http://rosettacode.org/wiki/Haversine_formula
2198 // distance between two geographical points using spherical law of cosines approximation
2199 distance: function (latlng1, latlng2) {
2200 var rad = Math.PI / 180,
2201 lat1 = latlng1.lat * rad,
2202 lat2 = latlng2.lat * rad,
2203 a = Math.sin(lat1) * Math.sin(lat2) +
2204 Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad);
2206 return this.R * Math.acos(Math.min(a, 1));
2214 * @crs L.CRS.EPSG3857
2216 * The most common CRS for online maps, used by almost all free and commercial
2217 * tile providers. Uses Spherical Mercator projection. Set in by default in
2218 * Map's `crs` option.
2221 L.CRS.EPSG3857 = L.extend({}, L.CRS.Earth, {
2223 projection: L.Projection.SphericalMercator,
2225 transformation: (function () {
2226 var scale = 0.5 / (Math.PI * L.Projection.SphericalMercator.R);
2227 return new L.Transformation(scale, 0.5, -scale, 0.5);
2231 L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
2239 * @crs L.CRS.EPSG4326
2241 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
2243 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
2244 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer`
2245 * with this CRS, ensure that there are two 256x256 pixel tiles covering the
2246 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
2247 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
2250 L.CRS.EPSG4326 = L.extend({}, L.CRS.Earth, {
2252 projection: L.Projection.LonLat,
2253 transformation: new L.Transformation(1 / 180, 1, -1 / 180, 0.5)
2263 * The central class of the API — it is used to create a map on a page and manipulate it.
2268 * // initialize the map on the "map" div with a given center and zoom
2269 * var map = L.map('map', {
2270 * center: [51.505, -0.09],
2277 L.Map = L.Evented.extend({
2280 // @section Map State Options
2281 // @option crs: CRS = L.CRS.EPSG3857
2282 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
2283 // sure what it means.
2284 crs: L.CRS.EPSG3857,
2286 // @option center: LatLng = undefined
2287 // Initial geographic center of the map
2290 // @option zoom: Number = undefined
2291 // Initial map zoom level
2294 // @option minZoom: Number = undefined
2295 // Minimum zoom level of the map. Overrides any `minZoom` option set on map layers.
2298 // @option maxZoom: Number = undefined
2299 // Maximum zoom level of the map. Overrides any `maxZoom` option set on map layers.
2302 // @option layers: Layer[] = []
2303 // Array of layers that will be added to the map initially
2306 // @option maxBounds: LatLngBounds = null
2307 // When this option is set, the map restricts the view to the given
2308 // geographical bounds, bouncing the user back when he tries to pan
2309 // outside the view. To set the restriction dynamically, use
2310 // [`setMaxBounds`](#map-setmaxbounds) method.
2311 maxBounds: undefined,
2313 // @option renderer: Renderer = *
2314 // The default method for drawing vector layers on the map. `L.SVG`
2315 // or `L.Canvas` by default depending on browser support.
2316 renderer: undefined,
2319 // @section Animation Options
2320 // @option zoomAnimation: Boolean = true
2321 // Whether the map zoom animation is enabled. By default it's enabled
2322 // in all browsers that support CSS3 Transitions except Android.
2323 zoomAnimation: true,
2325 // @option zoomAnimationThreshold: Number = 4
2326 // Won't animate zoom if the zoom difference exceeds this value.
2327 zoomAnimationThreshold: 4,
2329 // @option fadeAnimation: Boolean = true
2330 // Whether the tile fade animation is enabled. By default it's enabled
2331 // in all browsers that support CSS3 Transitions except Android.
2332 fadeAnimation: true,
2334 // @option markerZoomAnimation: Boolean = true
2335 // Whether markers animate their zoom with the zoom animation, if disabled
2336 // they will disappear for the length of the animation. By default it's
2337 // enabled in all browsers that support CSS3 Transitions except Android.
2338 markerZoomAnimation: true,
2340 // @option transform3DLimit: Number = 2^23
2341 // Defines the maximum size of a CSS translation transform. The default
2342 // value should not be changed unless a web browser positions layers in
2343 // the wrong place after doing a large `panBy`.
2344 transform3DLimit: 8388608, // Precision limit of a 32-bit float
2346 // @section Interaction Options
2347 // @option zoomSnap: Number = 1
2348 // Forces the map's zoom level to always be a multiple of this, particularly
2349 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
2350 // By default, the zoom level snaps to the nearest integer; lower values
2351 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
2352 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
2355 // @option zoomDelta: Number = 1
2356 // Controls how much the map's zoom level will change after a
2357 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
2358 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
2359 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
2362 // @option trackResize: Boolean = true
2363 // Whether the map automatically handles browser window resize to update itself.
2367 initialize: function (id, options) { // (HTMLElement or String, Object)
2368 options = L.setOptions(this, options);
2370 this._initContainer(id);
2373 // hack for https://github.com/Leaflet/Leaflet/issues/1980
2374 this._onResize = L.bind(this._onResize, this);
2378 if (options.maxBounds) {
2379 this.setMaxBounds(options.maxBounds);
2382 if (options.zoom !== undefined) {
2383 this._zoom = this._limitZoom(options.zoom);
2386 if (options.center && options.zoom !== undefined) {
2387 this.setView(L.latLng(options.center), options.zoom, {reset: true});
2390 this._handlers = [];
2392 this._zoomBoundLayers = {};
2393 this._sizeChanged = true;
2395 this.callInitHooks();
2397 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
2398 this._zoomAnimated = L.DomUtil.TRANSITION && L.Browser.any3d && !L.Browser.mobileOpera &&
2399 this.options.zoomAnimation;
2401 // zoom transitions run with the same duration for all layers, so if one of transitionend events
2402 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
2403 if (this._zoomAnimated) {
2404 this._createAnimProxy();
2405 L.DomEvent.on(this._proxy, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
2408 this._addLayers(this.options.layers);
2412 // @section Methods for modifying map state
2414 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
2415 // Sets the view of the map (geographical center and zoom) with the given
2416 // animation options.
2417 setView: function (center, zoom, options) {
2419 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
2420 center = this._limitCenter(L.latLng(center), zoom, this.options.maxBounds);
2421 options = options || {};
2425 if (this._loaded && !options.reset && options !== true) {
2427 if (options.animate !== undefined) {
2428 options.zoom = L.extend({animate: options.animate}, options.zoom);
2429 options.pan = L.extend({animate: options.animate, duration: options.duration}, options.pan);
2432 // try animating pan or zoom
2433 var moved = (this._zoom !== zoom) ?
2434 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
2435 this._tryAnimatedPan(center, options.pan);
2438 // prevent resize handler call, the view will refresh after animation anyway
2439 clearTimeout(this._sizeTimer);
2444 // animation didn't start, just reset the map view
2445 this._resetView(center, zoom);
2450 // @method setZoom(zoom: Number, options: Zoom/pan options): this
2451 // Sets the zoom of the map.
2452 setZoom: function (zoom, options) {
2453 if (!this._loaded) {
2457 return this.setView(this.getCenter(), zoom, {zoom: options});
2460 // @method zoomIn(delta?: Number, options?: Zoom options): this
2461 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
2462 zoomIn: function (delta, options) {
2463 delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
2464 return this.setZoom(this._zoom + delta, options);
2467 // @method zoomOut(delta?: Number, options?: Zoom options): this
2468 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
2469 zoomOut: function (delta, options) {
2470 delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
2471 return this.setZoom(this._zoom - delta, options);
2474 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
2475 // Zooms the map while keeping a specified geographical point on the map
2476 // stationary (e.g. used internally for scroll zoom and double-click zoom).
2478 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
2479 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
2480 setZoomAround: function (latlng, zoom, options) {
2481 var scale = this.getZoomScale(zoom),
2482 viewHalf = this.getSize().divideBy(2),
2483 containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng),
2485 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
2486 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
2488 return this.setView(newCenter, zoom, {zoom: options});
2491 _getBoundsCenterZoom: function (bounds, options) {
2493 options = options || {};
2494 bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds);
2496 var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]),
2497 paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]),
2499 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
2501 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
2503 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
2505 swPoint = this.project(bounds.getSouthWest(), zoom),
2506 nePoint = this.project(bounds.getNorthEast(), zoom),
2507 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
2515 // @method fitBounds(bounds: LatLngBounds, options: fitBounds options): this
2516 // Sets a map view that contains the given geographical bounds with the
2517 // maximum zoom level possible.
2518 fitBounds: function (bounds, options) {
2520 bounds = L.latLngBounds(bounds);
2522 if (!bounds.isValid()) {
2523 throw new Error('Bounds are not valid.');
2526 var target = this._getBoundsCenterZoom(bounds, options);
2527 return this.setView(target.center, target.zoom, options);
2530 // @method fitWorld(options?: fitBounds options): this
2531 // Sets a map view that mostly contains the whole world with the maximum
2532 // zoom level possible.
2533 fitWorld: function (options) {
2534 return this.fitBounds([[-90, -180], [90, 180]], options);
2537 // @method panTo(latlng: LatLng, options?: Pan options): this
2538 // Pans the map to a given center.
2539 panTo: function (center, options) { // (LatLng)
2540 return this.setView(center, this._zoom, {pan: options});
2543 // @method panBy(offset: Point): this
2544 // Pans the map by a given number of pixels (animated).
2545 panBy: function (offset, options) {
2546 offset = L.point(offset).round();
2547 options = options || {};
2549 if (!offset.x && !offset.y) {
2550 return this.fire('moveend');
2552 // If we pan too far, Chrome gets issues with tiles
2553 // and makes them disappear or appear in the wrong place (slightly offset) #2602
2554 if (options.animate !== true && !this.getSize().contains(offset)) {
2555 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
2559 if (!this._panAnim) {
2560 this._panAnim = new L.PosAnimation();
2563 'step': this._onPanTransitionStep,
2564 'end': this._onPanTransitionEnd
2568 // don't fire movestart if animating inertia
2569 if (!options.noMoveStart) {
2570 this.fire('movestart');
2573 // animate pan unless animate: false specified
2574 if (options.animate !== false) {
2575 L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
2577 var newPos = this._getMapPanePos().subtract(offset).round();
2578 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
2580 this._rawPanBy(offset);
2581 this.fire('move').fire('moveend');
2587 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
2588 // Sets the view of the map (geographical center and zoom) performing a smooth
2589 // pan-zoom animation.
2590 flyTo: function (targetCenter, targetZoom, options) {
2592 options = options || {};
2593 if (options.animate === false || !L.Browser.any3d) {
2594 return this.setView(targetCenter, targetZoom, options);
2599 var from = this.project(this.getCenter()),
2600 to = this.project(targetCenter),
2601 size = this.getSize(),
2602 startZoom = this._zoom;
2604 targetCenter = L.latLng(targetCenter);
2605 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
2607 var w0 = Math.max(size.x, size.y),
2608 w1 = w0 * this.getZoomScale(startZoom, targetZoom),
2609 u1 = (to.distanceTo(from)) || 1,
2614 var s1 = i ? -1 : 1,
2616 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
2617 b1 = 2 * s2 * rho2 * u1,
2619 sq = Math.sqrt(b * b + 1) - b;
2621 // workaround for floating point precision bug when sq = 0, log = -Infinite,
2622 // thus triggering an infinite loop in flyTo
2623 var log = sq < 0.000000001 ? -18 : Math.log(sq);
2628 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
2629 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
2630 function tanh(n) { return sinh(n) / cosh(n); }
2634 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
2635 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
2637 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
2639 var start = Date.now(),
2640 S = (r(1) - r0) / rho,
2641 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
2644 var t = (Date.now() - start) / duration,
2648 this._flyToFrame = L.Util.requestAnimFrame(frame, this);
2651 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
2652 this.getScaleZoom(w0 / w(s), startZoom),
2657 ._move(targetCenter, targetZoom)
2662 this._moveStart(true);
2668 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
2669 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
2670 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
2671 flyToBounds: function (bounds, options) {
2672 var target = this._getBoundsCenterZoom(bounds, options);
2673 return this.flyTo(target.center, target.zoom, options);
2676 // @method setMaxBounds(bounds: Bounds): this
2677 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
2678 setMaxBounds: function (bounds) {
2679 bounds = L.latLngBounds(bounds);
2681 if (!bounds.isValid()) {
2682 this.options.maxBounds = null;
2683 return this.off('moveend', this._panInsideMaxBounds);
2684 } else if (this.options.maxBounds) {
2685 this.off('moveend', this._panInsideMaxBounds);
2688 this.options.maxBounds = bounds;
2691 this._panInsideMaxBounds();
2694 return this.on('moveend', this._panInsideMaxBounds);
2697 // @method setMinZoom(zoom: Number): this
2698 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
2699 setMinZoom: function (zoom) {
2700 this.options.minZoom = zoom;
2702 if (this._loaded && this.getZoom() < this.options.minZoom) {
2703 return this.setZoom(zoom);
2709 // @method setMaxZoom(zoom: Number): this
2710 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
2711 setMaxZoom: function (zoom) {
2712 this.options.maxZoom = zoom;
2714 if (this._loaded && (this.getZoom() > this.options.maxZoom)) {
2715 return this.setZoom(zoom);
2721 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
2722 // 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.
2723 panInsideBounds: function (bounds, options) {
2724 this._enforcingBounds = true;
2725 var center = this.getCenter(),
2726 newCenter = this._limitCenter(center, this._zoom, L.latLngBounds(bounds));
2728 if (!center.equals(newCenter)) {
2729 this.panTo(newCenter, options);
2732 this._enforcingBounds = false;
2736 // @method invalidateSize(options: Zoom/Pan options): this
2737 // Checks if the map container size changed and updates the map if so —
2738 // call it after you've changed the map size dynamically, also animating
2739 // pan by default. If `options.pan` is `false`, panning will not occur.
2740 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
2741 // that it doesn't happen often even if the method is called many
2745 // @method invalidateSize(animate: Boolean): this
2746 // Checks if the map container size changed and updates the map if so —
2747 // call it after you've changed the map size dynamically, also animating
2749 invalidateSize: function (options) {
2750 if (!this._loaded) { return this; }
2752 options = L.extend({
2755 }, options === true ? {animate: true} : options);
2757 var oldSize = this.getSize();
2758 this._sizeChanged = true;
2759 this._lastCenter = null;
2761 var newSize = this.getSize(),
2762 oldCenter = oldSize.divideBy(2).round(),
2763 newCenter = newSize.divideBy(2).round(),
2764 offset = oldCenter.subtract(newCenter);
2766 if (!offset.x && !offset.y) { return this; }
2768 if (options.animate && options.pan) {
2773 this._rawPanBy(offset);
2778 if (options.debounceMoveend) {
2779 clearTimeout(this._sizeTimer);
2780 this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
2782 this.fire('moveend');
2786 // @section Map state change events
2787 // @event resize: ResizeEvent
2788 // Fired when the map is resized.
2789 return this.fire('resize', {
2795 // @section Methods for modifying map state
2796 // @method stop(): this
2797 // Stops the currently running `panTo` or `flyTo` animation, if any.
2799 this.setZoom(this._limitZoom(this._zoom));
2800 if (!this.options.zoomSnap) {
2801 this.fire('viewreset');
2803 return this._stop();
2806 // @section Geolocation methods
2807 // @method locate(options?: Locate options): this
2808 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
2809 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
2810 // and optionally sets the map view to the user's location with respect to
2811 // detection accuracy (or to the world view if geolocation failed).
2812 // Note that, if your page doesn't use HTTPS, this method will fail in
2813 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
2814 // See `Locate options` for more details.
2815 locate: function (options) {
2817 options = this._locateOptions = L.extend({
2821 // maxZoom: <Number>
2823 // enableHighAccuracy: false
2826 if (!('geolocation' in navigator)) {
2827 this._handleGeolocationError({
2829 message: 'Geolocation not supported.'
2834 var onResponse = L.bind(this._handleGeolocationResponse, this),
2835 onError = L.bind(this._handleGeolocationError, this);
2837 if (options.watch) {
2838 this._locationWatchId =
2839 navigator.geolocation.watchPosition(onResponse, onError, options);
2841 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
2846 // @method stopLocate(): this
2847 // Stops watching location previously initiated by `map.locate({watch: true})`
2848 // and aborts resetting the map view if map.locate was called with
2849 // `{setView: true}`.
2850 stopLocate: function () {
2851 if (navigator.geolocation && navigator.geolocation.clearWatch) {
2852 navigator.geolocation.clearWatch(this._locationWatchId);
2854 if (this._locateOptions) {
2855 this._locateOptions.setView = false;
2860 _handleGeolocationError: function (error) {
2862 message = error.message ||
2863 (c === 1 ? 'permission denied' :
2864 (c === 2 ? 'position unavailable' : 'timeout'));
2866 if (this._locateOptions.setView && !this._loaded) {
2870 // @section Location events
2871 // @event locationerror: ErrorEvent
2872 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
2873 this.fire('locationerror', {
2875 message: 'Geolocation error: ' + message + '.'
2879 _handleGeolocationResponse: function (pos) {
2880 var lat = pos.coords.latitude,
2881 lng = pos.coords.longitude,
2882 latlng = new L.LatLng(lat, lng),
2883 bounds = latlng.toBounds(pos.coords.accuracy),
2884 options = this._locateOptions;
2886 if (options.setView) {
2887 var zoom = this.getBoundsZoom(bounds);
2888 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
2894 timestamp: pos.timestamp
2897 for (var i in pos.coords) {
2898 if (typeof pos.coords[i] === 'number') {
2899 data[i] = pos.coords[i];
2903 // @event locationfound: LocationEvent
2904 // Fired when geolocation (using the [`locate`](#map-locate) method)
2905 // went successfully.
2906 this.fire('locationfound', data);
2909 // TODO handler.addTo
2910 // TODO Appropiate docs section?
2911 // @section Other Methods
2912 // @method addHandler(name: String, HandlerClass: Function): this
2913 // Adds a new `Handler` to the map, given its name and constructor function.
2914 addHandler: function (name, HandlerClass) {
2915 if (!HandlerClass) { return this; }
2917 var handler = this[name] = new HandlerClass(this);
2919 this._handlers.push(handler);
2921 if (this.options[name]) {
2928 // @method remove(): this
2929 // Destroys the map and clears all related event listeners.
2930 remove: function () {
2932 this._initEvents(true);
2934 if (this._containerId !== this._container._leaflet_id) {
2935 throw new Error('Map container is being reused by another instance');
2939 // throws error in IE6-8
2940 delete this._container._leaflet_id;
2941 delete this._containerId;
2944 this._container._leaflet_id = undefined;
2946 this._containerId = undefined;
2949 L.DomUtil.remove(this._mapPane);
2951 if (this._clearControlPos) {
2952 this._clearControlPos();
2955 this._clearHandlers();
2958 // @section Map state change events
2959 // @event unload: Event
2960 // Fired when the map is destroyed with [remove](#map-remove) method.
2961 this.fire('unload');
2964 for (var i in this._layers) {
2965 this._layers[i].remove();
2971 // @section Other Methods
2972 // @method createPane(name: String, container?: HTMLElement): HTMLElement
2973 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
2974 // then returns it. The pane is created as a children of `container`, or
2975 // as a children of the main map pane if not set.
2976 createPane: function (name, container) {
2977 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
2978 pane = L.DomUtil.create('div', className, container || this._mapPane);
2981 this._panes[name] = pane;
2986 // @section Methods for Getting Map State
2988 // @method getCenter(): LatLng
2989 // Returns the geographical center of the map view
2990 getCenter: function () {
2991 this._checkIfLoaded();
2993 if (this._lastCenter && !this._moved()) {
2994 return this._lastCenter;
2996 return this.layerPointToLatLng(this._getCenterLayerPoint());
2999 // @method getZoom(): Number
3000 // Returns the current zoom level of the map view
3001 getZoom: function () {
3005 // @method getBounds(): LatLngBounds
3006 // Returns the geographical bounds visible in the current map view
3007 getBounds: function () {
3008 var bounds = this.getPixelBounds(),
3009 sw = this.unproject(bounds.getBottomLeft()),
3010 ne = this.unproject(bounds.getTopRight());
3012 return new L.LatLngBounds(sw, ne);
3015 // @method getMinZoom(): Number
3016 // 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.
3017 getMinZoom: function () {
3018 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3021 // @method getMaxZoom(): Number
3022 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3023 getMaxZoom: function () {
3024 return this.options.maxZoom === undefined ?
3025 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3026 this.options.maxZoom;
3029 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number
3030 // Returns the maximum zoom level on which the given bounds fit to the map
3031 // view in its entirety. If `inside` (optional) is set to `true`, the method
3032 // instead returns the minimum zoom level on which the map view fits into
3033 // the given bounds in its entirety.
3034 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3035 bounds = L.latLngBounds(bounds);
3036 padding = L.point(padding || [0, 0]);
3038 var zoom = this.getZoom() || 0,
3039 min = this.getMinZoom(),
3040 max = this.getMaxZoom(),
3041 nw = bounds.getNorthWest(),
3042 se = bounds.getSouthEast(),
3043 size = this.getSize().subtract(padding),
3044 boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)),
3045 snap = L.Browser.any3d ? this.options.zoomSnap : 1;
3047 var scale = Math.min(size.x / boundsSize.x, size.y / boundsSize.y);
3048 zoom = this.getScaleZoom(scale, zoom);
3051 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3052 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3055 return Math.max(min, Math.min(max, zoom));
3058 // @method getSize(): Point
3059 // Returns the current size of the map container (in pixels).
3060 getSize: function () {
3061 if (!this._size || this._sizeChanged) {
3062 this._size = new L.Point(
3063 this._container.clientWidth,
3064 this._container.clientHeight);
3066 this._sizeChanged = false;
3068 return this._size.clone();
3071 // @method getPixelBounds(): Bounds
3072 // Returns the bounds of the current map view in projected pixel
3073 // coordinates (sometimes useful in layer and overlay implementations).
3074 getPixelBounds: function (center, zoom) {
3075 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3076 return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3079 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3080 // the map pane? "left point of the map layer" can be confusing, specially
3081 // since there can be negative offsets.
3082 // @method getPixelOrigin(): Point
3083 // Returns the projected pixel coordinates of the top left point of
3084 // the map layer (useful in custom layer and overlay implementations).
3085 getPixelOrigin: function () {
3086 this._checkIfLoaded();
3087 return this._pixelOrigin;
3090 // @method getPixelWorldBounds(zoom?: Number): Bounds
3091 // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3092 // If `zoom` is omitted, the map's current zoom level is used.
3093 getPixelWorldBounds: function (zoom) {
3094 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3097 // @section Other Methods
3099 // @method getPane(pane: String|HTMLElement): HTMLElement
3100 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3101 getPane: function (pane) {
3102 return typeof pane === 'string' ? this._panes[pane] : pane;
3105 // @method getPanes(): Object
3106 // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3107 // the panes as values.
3108 getPanes: function () {
3112 // @method getContainer: HTMLElement
3113 // Returns the HTML element that contains the map.
3114 getContainer: function () {
3115 return this._container;
3119 // @section Conversion Methods
3121 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3122 // Returns the scale factor to be applied to a map transition from zoom level
3123 // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3124 getZoomScale: function (toZoom, fromZoom) {
3125 // TODO replace with universal implementation after refactoring projections
3126 var crs = this.options.crs;
3127 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3128 return crs.scale(toZoom) / crs.scale(fromZoom);
3131 // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3132 // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3133 // level and everything is scaled by a factor of `scale`. Inverse of
3134 // [`getZoomScale`](#map-getZoomScale).
3135 getScaleZoom: function (scale, fromZoom) {
3136 var crs = this.options.crs;
3137 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3138 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3139 return isNaN(zoom) ? Infinity : zoom;
3142 // @method project(latlng: LatLng, zoom: Number): Point
3143 // Projects a geographical coordinate `LatLng` according to the projection
3144 // of the map's CRS, then scales it according to `zoom` and the CRS's
3145 // `Transformation`. The result is pixel coordinate relative to
3147 project: function (latlng, zoom) {
3148 zoom = zoom === undefined ? this._zoom : zoom;
3149 return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
3152 // @method unproject(point: Point, zoom: Number): LatLng
3153 // Inverse of [`project`](#map-project).
3154 unproject: function (point, zoom) {
3155 zoom = zoom === undefined ? this._zoom : zoom;
3156 return this.options.crs.pointToLatLng(L.point(point), zoom);
3159 // @method layerPointToLatLng(point: Point): LatLng
3160 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3161 // returns the corresponding geographical coordinate (for the current zoom level).
3162 layerPointToLatLng: function (point) {
3163 var projectedPoint = L.point(point).add(this.getPixelOrigin());
3164 return this.unproject(projectedPoint);
3167 // @method latLngToLayerPoint(latlng: LatLng): Point
3168 // Given a geographical coordinate, returns the corresponding pixel coordinate
3169 // relative to the [origin pixel](#map-getpixelorigin).
3170 latLngToLayerPoint: function (latlng) {
3171 var projectedPoint = this.project(L.latLng(latlng))._round();
3172 return projectedPoint._subtract(this.getPixelOrigin());
3175 // @method wrapLatLng(latlng: LatLng): LatLng
3176 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
3177 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
3179 // By default this means longitude is wrapped around the dateline so its
3180 // value is between -180 and +180 degrees.
3181 wrapLatLng: function (latlng) {
3182 return this.options.crs.wrapLatLng(L.latLng(latlng));
3185 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
3186 // Returns the distance between two geographical coordinates according to
3187 // the map's CRS. By default this measures distance in meters.
3188 distance: function (latlng1, latlng2) {
3189 return this.options.crs.distance(L.latLng(latlng1), L.latLng(latlng2));
3192 // @method containerPointToLayerPoint(point: Point): Point
3193 // Given a pixel coordinate relative to the map container, returns the corresponding
3194 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
3195 containerPointToLayerPoint: function (point) { // (Point)
3196 return L.point(point).subtract(this._getMapPanePos());
3199 // @method layerPointToContainerPoint(point: Point): Point
3200 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3201 // returns the corresponding pixel coordinate relative to the map container.
3202 layerPointToContainerPoint: function (point) { // (Point)
3203 return L.point(point).add(this._getMapPanePos());
3206 // @method containerPointToLatLng(point: Point): Point
3207 // Given a pixel coordinate relative to the map container, returns
3208 // the corresponding geographical coordinate (for the current zoom level).
3209 containerPointToLatLng: function (point) {
3210 var layerPoint = this.containerPointToLayerPoint(L.point(point));
3211 return this.layerPointToLatLng(layerPoint);
3214 // @method latLngToContainerPoint(latlng: LatLng): Point
3215 // Given a geographical coordinate, returns the corresponding pixel coordinate
3216 // relative to the map container.
3217 latLngToContainerPoint: function (latlng) {
3218 return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
3221 // @method mouseEventToContainerPoint(ev: MouseEvent): Point
3222 // Given a MouseEvent object, returns the pixel coordinate relative to the
3223 // map container where the event took place.
3224 mouseEventToContainerPoint: function (e) {
3225 return L.DomEvent.getMousePosition(e, this._container);
3228 // @method mouseEventToLayerPoint(ev: MouseEvent): Point
3229 // Given a MouseEvent object, returns the pixel coordinate relative to
3230 // the [origin pixel](#map-getpixelorigin) where the event took place.
3231 mouseEventToLayerPoint: function (e) {
3232 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
3235 // @method mouseEventToLatLng(ev: MouseEvent): LatLng
3236 // Given a MouseEvent object, returns geographical coordinate where the
3237 // event took place.
3238 mouseEventToLatLng: function (e) { // (MouseEvent)
3239 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
3243 // map initialization methods
3245 _initContainer: function (id) {
3246 var container = this._container = L.DomUtil.get(id);
3249 throw new Error('Map container not found.');
3250 } else if (container._leaflet_id) {
3251 throw new Error('Map container is already initialized.');
3254 L.DomEvent.addListener(container, 'scroll', this._onScroll, this);
3255 this._containerId = L.Util.stamp(container);
3258 _initLayout: function () {
3259 var container = this._container;
3261 this._fadeAnimated = this.options.fadeAnimation && L.Browser.any3d;
3263 L.DomUtil.addClass(container, 'leaflet-container' +
3264 (L.Browser.touch ? ' leaflet-touch' : '') +
3265 (L.Browser.retina ? ' leaflet-retina' : '') +
3266 (L.Browser.ielt9 ? ' leaflet-oldie' : '') +
3267 (L.Browser.safari ? ' leaflet-safari' : '') +
3268 (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
3270 var position = L.DomUtil.getStyle(container, 'position');
3272 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
3273 container.style.position = 'relative';
3278 if (this._initControlPos) {
3279 this._initControlPos();
3283 _initPanes: function () {
3284 var panes = this._panes = {};
3285 this._paneRenderers = {};
3289 // Panes are DOM elements used to control the ordering of layers on the map. You
3290 // can access panes with [`map.getPane`](#map-getpane) or
3291 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
3292 // [`map.createPane`](#map-createpane) method.
3294 // Every map has the following default panes that differ only in zIndex.
3296 // @pane mapPane: HTMLElement = 'auto'
3297 // Pane that contains all other map panes
3299 this._mapPane = this.createPane('mapPane', this._container);
3300 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
3302 // @pane tilePane: HTMLElement = 200
3303 // Pane for `GridLayer`s and `TileLayer`s
3304 this.createPane('tilePane');
3305 // @pane overlayPane: HTMLElement = 400
3306 // Pane for vector overlays (`Path`s), like `Polyline`s and `Polygon`s
3307 this.createPane('shadowPane');
3308 // @pane shadowPane: HTMLElement = 500
3309 // Pane for overlay shadows (e.g. `Marker` shadows)
3310 this.createPane('overlayPane');
3311 // @pane markerPane: HTMLElement = 600
3312 // Pane for `Icon`s of `Marker`s
3313 this.createPane('markerPane');
3314 // @pane tooltipPane: HTMLElement = 650
3315 // Pane for tooltip.
3316 this.createPane('tooltipPane');
3317 // @pane popupPane: HTMLElement = 700
3318 // Pane for `Popup`s.
3319 this.createPane('popupPane');
3321 if (!this.options.markerZoomAnimation) {
3322 L.DomUtil.addClass(panes.markerPane, 'leaflet-zoom-hide');
3323 L.DomUtil.addClass(panes.shadowPane, 'leaflet-zoom-hide');
3328 // private methods that modify map state
3330 // @section Map state change events
3331 _resetView: function (center, zoom) {
3332 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
3334 var loading = !this._loaded;
3335 this._loaded = true;
3336 zoom = this._limitZoom(zoom);
3338 this.fire('viewprereset');
3340 var zoomChanged = this._zoom !== zoom;
3342 ._moveStart(zoomChanged)
3343 ._move(center, zoom)
3344 ._moveEnd(zoomChanged);
3346 // @event viewreset: Event
3347 // Fired when the map needs to redraw its content (this usually happens
3348 // on map zoom or load). Very useful for creating custom overlays.
3349 this.fire('viewreset');
3351 // @event load: Event
3352 // Fired when the map is initialized (when its center and zoom are set
3353 // for the first time).
3359 _moveStart: function (zoomChanged) {
3360 // @event zoomstart: Event
3361 // Fired when the map zoom is about to change (e.g. before zoom animation).
3362 // @event movestart: Event
3363 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
3365 this.fire('zoomstart');
3367 return this.fire('movestart');
3370 _move: function (center, zoom, data) {
3371 if (zoom === undefined) {
3374 var zoomChanged = this._zoom !== zoom;
3377 this._lastCenter = center;
3378 this._pixelOrigin = this._getNewPixelOrigin(center);
3380 // @event zoom: Event
3381 // Fired repeatedly during any change in zoom level, including zoom
3382 // and fly animations.
3383 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530
3384 this.fire('zoom', data);
3387 // @event move: Event
3388 // Fired repeatedly during any movement of the map, including pan and
3390 return this.fire('move', data);
3393 _moveEnd: function (zoomChanged) {
3394 // @event zoomend: Event
3395 // Fired when the map has changed, after any animations.
3397 this.fire('zoomend');
3400 // @event moveend: Event
3401 // Fired when the center of the map stops changing (e.g. user stopped
3402 // dragging the map).
3403 return this.fire('moveend');
3406 _stop: function () {
3407 L.Util.cancelAnimFrame(this._flyToFrame);
3408 if (this._panAnim) {
3409 this._panAnim.stop();
3414 _rawPanBy: function (offset) {
3415 L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
3418 _getZoomSpan: function () {
3419 return this.getMaxZoom() - this.getMinZoom();
3422 _panInsideMaxBounds: function () {
3423 if (!this._enforcingBounds) {
3424 this.panInsideBounds(this.options.maxBounds);
3428 _checkIfLoaded: function () {
3429 if (!this._loaded) {
3430 throw new Error('Set map center and zoom first.');
3434 // DOM event handling
3436 // @section Interaction events
3437 _initEvents: function (remove) {
3438 if (!L.DomEvent) { return; }
3441 this._targets[L.stamp(this._container)] = this;
3443 var onOff = remove ? 'off' : 'on';
3445 // @event click: MouseEvent
3446 // Fired when the user clicks (or taps) the map.
3447 // @event dblclick: MouseEvent
3448 // Fired when the user double-clicks (or double-taps) the map.
3449 // @event mousedown: MouseEvent
3450 // Fired when the user pushes the mouse button on the map.
3451 // @event mouseup: MouseEvent
3452 // Fired when the user releases the mouse button on the map.
3453 // @event mouseover: MouseEvent
3454 // Fired when the mouse enters the map.
3455 // @event mouseout: MouseEvent
3456 // Fired when the mouse leaves the map.
3457 // @event mousemove: MouseEvent
3458 // Fired while the mouse moves over the map.
3459 // @event contextmenu: MouseEvent
3460 // Fired when the user pushes the right mouse button on the map, prevents
3461 // default browser context menu from showing if there are listeners on
3462 // this event. Also fired on mobile when the user holds a single touch
3463 // for a second (also called long press).
3464 // @event keypress: KeyboardEvent
3465 // Fired when the user presses a key from the keyboard while the map is focused.
3466 L.DomEvent[onOff](this._container, 'click dblclick mousedown mouseup ' +
3467 'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);
3469 if (this.options.trackResize) {
3470 L.DomEvent[onOff](window, 'resize', this._onResize, this);
3473 if (L.Browser.any3d && this.options.transform3DLimit) {
3474 this[onOff]('moveend', this._onMoveEnd);
3478 _onResize: function () {
3479 L.Util.cancelAnimFrame(this._resizeRequest);
3480 this._resizeRequest = L.Util.requestAnimFrame(
3481 function () { this.invalidateSize({debounceMoveend: true}); }, this);
3484 _onScroll: function () {
3485 this._container.scrollTop = 0;
3486 this._container.scrollLeft = 0;
3489 _onMoveEnd: function () {
3490 var pos = this._getMapPanePos();
3491 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
3492 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
3493 // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
3494 this._resetView(this.getCenter(), this.getZoom());
3498 _findEventTargets: function (e, type) {
3501 isHover = type === 'mouseout' || type === 'mouseover',
3502 src = e.target || e.srcElement,
3506 target = this._targets[L.stamp(src)];
3507 if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
3508 // Prevent firing click after you just dragged an object.
3512 if (target && target.listens(type, true)) {
3513 if (isHover && !L.DomEvent._isExternalTarget(src, e)) { break; }
3514 targets.push(target);
3515 if (isHover) { break; }
3517 if (src === this._container) { break; }
3518 src = src.parentNode;
3520 if (!targets.length && !dragging && !isHover && L.DomEvent._isExternalTarget(src, e)) {
3526 _handleDOMEvent: function (e) {
3527 if (!this._loaded || L.DomEvent._skipped(e)) { return; }
3529 var type = e.type === 'keypress' && e.keyCode === 13 ? 'click' : e.type;
3531 if (type === 'mousedown') {
3532 // prevents outline when clicking on keyboard-focusable element
3533 L.DomUtil.preventOutline(e.target || e.srcElement);
3536 this._fireDOMEvent(e, type);
3539 _fireDOMEvent: function (e, type, targets) {
3541 if (e.type === 'click') {
3542 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
3543 // @event preclick: MouseEvent
3544 // Fired before mouse click on the map (sometimes useful when you
3545 // want something to happen on click before any existing click
3546 // handlers start running).
3547 var synth = L.Util.extend({}, e);
3548 synth.type = 'preclick';
3549 this._fireDOMEvent(synth, synth.type, targets);
3552 if (e._stopped) { return; }
3554 // Find the layer the event is propagating from and its parents.
3555 targets = (targets || []).concat(this._findEventTargets(e, type));
3557 if (!targets.length) { return; }
3559 var target = targets[0];
3560 if (type === 'contextmenu' && target.listens(type, true)) {
3561 L.DomEvent.preventDefault(e);
3568 if (e.type !== 'keypress') {
3569 var isMarker = target instanceof L.Marker;
3570 data.containerPoint = isMarker ?
3571 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
3572 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
3573 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
3576 for (var i = 0; i < targets.length; i++) {
3577 targets[i].fire(type, data, true);
3578 if (data.originalEvent._stopped ||
3579 (targets[i].options.nonBubblingEvents && L.Util.indexOf(targets[i].options.nonBubblingEvents, type) !== -1)) { return; }
3583 _draggableMoved: function (obj) {
3584 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
3585 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
3588 _clearHandlers: function () {
3589 for (var i = 0, len = this._handlers.length; i < len; i++) {
3590 this._handlers[i].disable();
3594 // @section Other Methods
3596 // @method whenReady(fn: Function, context?: Object): this
3597 // Runs the given function `fn` when the map gets initialized with
3598 // a view (center and zoom) and at least one layer, or immediately
3599 // if it's already initialized, optionally passing a function context.
3600 whenReady: function (callback, context) {
3602 callback.call(context || this, {target: this});
3604 this.on('load', callback, context);
3610 // private methods for getting map state
3612 _getMapPanePos: function () {
3613 return L.DomUtil.getPosition(this._mapPane) || new L.Point(0, 0);
3616 _moved: function () {
3617 var pos = this._getMapPanePos();
3618 return pos && !pos.equals([0, 0]);
3621 _getTopLeftPoint: function (center, zoom) {
3622 var pixelOrigin = center && zoom !== undefined ?
3623 this._getNewPixelOrigin(center, zoom) :
3624 this.getPixelOrigin();
3625 return pixelOrigin.subtract(this._getMapPanePos());
3628 _getNewPixelOrigin: function (center, zoom) {
3629 var viewHalf = this.getSize()._divideBy(2);
3630 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
3633 _latLngToNewLayerPoint: function (latlng, zoom, center) {
3634 var topLeft = this._getNewPixelOrigin(center, zoom);
3635 return this.project(latlng, zoom)._subtract(topLeft);
3638 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
3639 var topLeft = this._getNewPixelOrigin(center, zoom);
3641 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
3642 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
3643 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
3644 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
3648 // layer point of the current center
3649 _getCenterLayerPoint: function () {
3650 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
3653 // offset of the specified place to the current center in pixels
3654 _getCenterOffset: function (latlng) {
3655 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
3658 // adjust center for view to get inside bounds
3659 _limitCenter: function (center, zoom, bounds) {
3661 if (!bounds) { return center; }
3663 var centerPoint = this.project(center, zoom),
3664 viewHalf = this.getSize().divideBy(2),
3665 viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
3666 offset = this._getBoundsOffset(viewBounds, bounds, zoom);
3668 // If offset is less than a pixel, ignore.
3669 // This prevents unstable projections from getting into
3670 // an infinite loop of tiny offsets.
3671 if (offset.round().equals([0, 0])) {
3675 return this.unproject(centerPoint.add(offset), zoom);
3678 // adjust offset for view to get inside bounds
3679 _limitOffset: function (offset, bounds) {
3680 if (!bounds) { return offset; }
3682 var viewBounds = this.getPixelBounds(),
3683 newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
3685 return offset.add(this._getBoundsOffset(newBounds, bounds));
3688 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
3689 _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
3690 var projectedMaxBounds = L.bounds(
3691 this.project(maxBounds.getNorthEast(), zoom),
3692 this.project(maxBounds.getSouthWest(), zoom)
3694 minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
3695 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
3697 dx = this._rebound(minOffset.x, -maxOffset.x),
3698 dy = this._rebound(minOffset.y, -maxOffset.y);
3700 return new L.Point(dx, dy);
3703 _rebound: function (left, right) {
3704 return left + right > 0 ?
3705 Math.round(left - right) / 2 :
3706 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
3709 _limitZoom: function (zoom) {
3710 var min = this.getMinZoom(),
3711 max = this.getMaxZoom(),
3712 snap = L.Browser.any3d ? this.options.zoomSnap : 1;
3714 zoom = Math.round(zoom / snap) * snap;
3716 return Math.max(min, Math.min(max, zoom));
3719 _onPanTransitionStep: function () {
3723 _onPanTransitionEnd: function () {
3724 L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
3725 this.fire('moveend');
3728 _tryAnimatedPan: function (center, options) {
3729 // difference between the new and current centers in pixels
3730 var offset = this._getCenterOffset(center)._floor();
3732 // don't animate too far unless animate: true specified in options
3733 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
3735 this.panBy(offset, options);
3740 _createAnimProxy: function () {
3742 var proxy = this._proxy = L.DomUtil.create('div', 'leaflet-proxy leaflet-zoom-animated');
3743 this._panes.mapPane.appendChild(proxy);
3745 this.on('zoomanim', function (e) {
3746 var prop = L.DomUtil.TRANSFORM,
3747 transform = proxy.style[prop];
3749 L.DomUtil.setTransform(proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
3751 // workaround for case when transform is the same and so transitionend event is not fired
3752 if (transform === proxy.style[prop] && this._animatingZoom) {
3753 this._onZoomTransitionEnd();
3757 this.on('load moveend', function () {
3758 var c = this.getCenter(),
3760 L.DomUtil.setTransform(proxy, this.project(c, z), this.getZoomScale(z, 1));
3764 _catchTransitionEnd: function (e) {
3765 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
3766 this._onZoomTransitionEnd();
3770 _nothingToAnimate: function () {
3771 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
3774 _tryAnimatedZoom: function (center, zoom, options) {
3776 if (this._animatingZoom) { return true; }
3778 options = options || {};
3780 // don't animate if disabled, not supported or zoom difference is too large
3781 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
3782 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
3784 // offset is the pixel coords of the zoom origin relative to the current center
3785 var scale = this.getZoomScale(zoom),
3786 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
3788 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
3789 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
3791 L.Util.requestAnimFrame(function () {
3794 ._animateZoom(center, zoom, true);
3800 _animateZoom: function (center, zoom, startAnim, noUpdate) {
3802 this._animatingZoom = true;
3804 // remember what center/zoom to set after animation
3805 this._animateToCenter = center;
3806 this._animateToZoom = zoom;
3808 L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
3811 // @event zoomanim: ZoomAnimEvent
3812 // Fired on every frame of a zoom animation
3813 this.fire('zoomanim', {
3819 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
3820 setTimeout(L.bind(this._onZoomTransitionEnd, this), 250);
3823 _onZoomTransitionEnd: function () {
3824 if (!this._animatingZoom) { return; }
3826 L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
3828 this._animatingZoom = false;
3830 this._move(this._animateToCenter, this._animateToZoom);
3832 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
3833 L.Util.requestAnimFrame(function () {
3834 this._moveEnd(true);
3841 // @factory L.map(id: String, options?: Map options)
3842 // Instantiates a map object given the DOM ID of a `<div>` element
3843 // and optionally an object literal with `Map options`.
3846 // @factory L.map(el: HTMLElement, options?: Map options)
3847 // Instantiates a map object given an instance of a `<div>` HTML element
3848 // and optionally an object literal with `Map options`.
3849 L.map = function (id, options) {
3850 return new L.Map(id, options);
3862 * A set of methods from the Layer base class that all Leaflet layers use.
3863 * Inherits all methods, options and events from `L.Evented`.
3868 * var layer = L.Marker(latlng).addTo(map);
3874 * Fired after the layer is added to a map
3876 * @event remove: Event
3877 * Fired after the layer is removed from a map
3881 L.Layer = L.Evented.extend({
3883 // Classes extending `L.Layer` will inherit the following options:
3885 // @option pane: String = 'overlayPane'
3886 // 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.
3887 pane: 'overlayPane',
3888 nonBubblingEvents: [], // Array of events that should not be bubbled to DOM parents (like the map),
3890 // @option attribution: String = null
3891 // String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox".
3896 * Classes extending `L.Layer` will inherit the following methods:
3898 * @method addTo(map: Map): this
3899 * Adds the layer to the given map
3901 addTo: function (map) {
3906 // @method remove: this
3907 // Removes the layer from the map it is currently active on.
3908 remove: function () {
3909 return this.removeFrom(this._map || this._mapToAdd);
3912 // @method removeFrom(map: Map): this
3913 // Removes the layer from the given map
3914 removeFrom: function (obj) {
3916 obj.removeLayer(this);
3921 // @method getPane(name? : String): HTMLElement
3922 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
3923 getPane: function (name) {
3924 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
3927 addInteractiveTarget: function (targetEl) {
3928 this._map._targets[L.stamp(targetEl)] = this;
3932 removeInteractiveTarget: function (targetEl) {
3933 delete this._map._targets[L.stamp(targetEl)];
3937 // @method getAttribution: String
3938 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
3939 getAttribution: function () {
3940 return this.options.attribution;
3943 _layerAdd: function (e) {
3946 // check in case layer gets added and then removed before the map is ready
3947 if (!map.hasLayer(this)) { return; }
3950 this._zoomAnimated = map._zoomAnimated;
3952 if (this.getEvents) {
3953 var events = this.getEvents();
3954 map.on(events, this);
3955 this.once('remove', function () {
3956 map.off(events, this);
3962 if (this.getAttribution && this._map.attributionControl) {
3963 this._map.attributionControl.addAttribution(this.getAttribution());
3967 map.fire('layeradd', {layer: this});
3971 /* @section Extension methods
3974 * Every layer should extend from `L.Layer` and (re-)implement the following methods.
3976 * @method onAdd(map: Map): this
3977 * 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).
3979 * @method onRemove(map: Map): this
3980 * 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).
3982 * @method getEvents(): Object
3983 * 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.
3985 * @method getAttribution(): String
3986 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
3988 * @method beforeAdd(map: Map): this
3989 * 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.
3994 * @section Layer events
3996 * @event layeradd: LayerEvent
3997 * Fired when a new layer is added to the map.
3999 * @event layerremove: LayerEvent
4000 * Fired when some layer is removed from the map
4002 * @section Methods for Layers and Controls
4005 // @method addLayer(layer: Layer): this
4006 // Adds the given layer to the map
4007 addLayer: function (layer) {
4008 var id = L.stamp(layer);
4009 if (this._layers[id]) { return this; }
4010 this._layers[id] = layer;
4012 layer._mapToAdd = this;
4014 if (layer.beforeAdd) {
4015 layer.beforeAdd(this);
4018 this.whenReady(layer._layerAdd, layer);
4023 // @method removeLayer(layer: Layer): this
4024 // Removes the given layer from the map.
4025 removeLayer: function (layer) {
4026 var id = L.stamp(layer);
4028 if (!this._layers[id]) { return this; }
4031 layer.onRemove(this);
4034 if (layer.getAttribution && this.attributionControl) {
4035 this.attributionControl.removeAttribution(layer.getAttribution());
4038 delete this._layers[id];
4041 this.fire('layerremove', {layer: layer});
4042 layer.fire('remove');
4045 layer._map = layer._mapToAdd = null;
4050 // @method hasLayer(layer: Layer): Boolean
4051 // Returns `true` if the given layer is currently added to the map
4052 hasLayer: function (layer) {
4053 return !!layer && (L.stamp(layer) in this._layers);
4056 /* @method eachLayer(fn: Function, context?: Object): this
4057 * Iterates over the layers of the map, optionally specifying context of the iterator function.
4059 * map.eachLayer(function(layer){
4060 * layer.bindPopup('Hello');
4064 eachLayer: function (method, context) {
4065 for (var i in this._layers) {
4066 method.call(context, this._layers[i]);
4071 _addLayers: function (layers) {
4072 layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];
4074 for (var i = 0, len = layers.length; i < len; i++) {
4075 this.addLayer(layers[i]);
4079 _addZoomLimit: function (layer) {
4080 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
4081 this._zoomBoundLayers[L.stamp(layer)] = layer;
4082 this._updateZoomLevels();
4086 _removeZoomLimit: function (layer) {
4087 var id = L.stamp(layer);
4089 if (this._zoomBoundLayers[id]) {
4090 delete this._zoomBoundLayers[id];
4091 this._updateZoomLevels();
4095 _updateZoomLevels: function () {
4096 var minZoom = Infinity,
4097 maxZoom = -Infinity,
4098 oldZoomSpan = this._getZoomSpan();
4100 for (var i in this._zoomBoundLayers) {
4101 var options = this._zoomBoundLayers[i].options;
4103 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
4104 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
4107 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
4108 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
4110 // @section Map state change events
4111 // @event zoomlevelschange: Event
4112 // Fired when the number of zoomlevels on the map is changed due
4113 // to adding or removing a layer.
4114 if (oldZoomSpan !== this._getZoomSpan()) {
4115 this.fire('zoomlevelschange');
4118 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
4119 this.setZoom(this._layersMaxZoom);
4121 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
4122 this.setZoom(this._layersMinZoom);
4130 * @namespace DomEvent
4131 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
4134 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
4138 var eventsKey = '_leaflet_events';
4142 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
4143 // Adds a listener function (`fn`) to a particular DOM event type of the
4144 // element `el`. You can optionally specify the context of the listener
4145 // (object the `this` keyword will point to). You can also pass several
4146 // space-separated types (e.g. `'click dblclick'`).
4149 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
4150 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
4151 on: function (obj, types, fn, context) {
4153 if (typeof types === 'object') {
4154 for (var type in types) {
4155 this._on(obj, type, types[type], fn);
4158 types = L.Util.splitWords(types);
4160 for (var i = 0, len = types.length; i < len; i++) {
4161 this._on(obj, types[i], fn, context);
4168 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
4169 // Removes a previously added listener function. If no function is specified,
4170 // it will remove all the listeners of that particular DOM event from the element.
4171 // Note that if you passed a custom context to on, you must pass the same
4172 // context to `off` in order to remove the listener.
4175 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
4176 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
4177 off: function (obj, types, fn, context) {
4179 if (typeof types === 'object') {
4180 for (var type in types) {
4181 this._off(obj, type, types[type], fn);
4184 types = L.Util.splitWords(types);
4186 for (var i = 0, len = types.length; i < len; i++) {
4187 this._off(obj, types[i], fn, context);
4194 _on: function (obj, type, fn, context) {
4195 var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : '');
4197 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
4199 var handler = function (e) {
4200 return fn.call(context || obj, e || window.event);
4203 var originalHandler = handler;
4205 if (L.Browser.pointer && type.indexOf('touch') === 0) {
4206 this.addPointerListener(obj, type, handler, id);
4208 } else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
4209 this.addDoubleTapListener(obj, handler, id);
4211 } else if ('addEventListener' in obj) {
4213 if (type === 'mousewheel') {
4214 obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
4216 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
4217 handler = function (e) {
4218 e = e || window.event;
4219 if (L.DomEvent._isExternalTarget(obj, e)) {
4223 obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
4226 if (type === 'click' && L.Browser.android) {
4227 handler = function (e) {
4228 return L.DomEvent._filterClick(e, originalHandler);
4231 obj.addEventListener(type, handler, false);
4234 } else if ('attachEvent' in obj) {
4235 obj.attachEvent('on' + type, handler);
4238 obj[eventsKey] = obj[eventsKey] || {};
4239 obj[eventsKey][id] = handler;
4244 _off: function (obj, type, fn, context) {
4246 var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : ''),
4247 handler = obj[eventsKey] && obj[eventsKey][id];
4249 if (!handler) { return this; }
4251 if (L.Browser.pointer && type.indexOf('touch') === 0) {
4252 this.removePointerListener(obj, type, id);
4254 } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
4255 this.removeDoubleTapListener(obj, id);
4257 } else if ('removeEventListener' in obj) {
4259 if (type === 'mousewheel') {
4260 obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
4263 obj.removeEventListener(
4264 type === 'mouseenter' ? 'mouseover' :
4265 type === 'mouseleave' ? 'mouseout' : type, handler, false);
4268 } else if ('detachEvent' in obj) {
4269 obj.detachEvent('on' + type, handler);
4272 obj[eventsKey][id] = null;
4277 // @function stopPropagation(ev: DOMEvent): this
4278 // Stop the given event from propagation to parent elements. Used inside the listener functions:
4280 // L.DomEvent.on(div, 'click', function (ev) {
4281 // L.DomEvent.stopPropagation(ev);
4284 stopPropagation: function (e) {
4286 if (e.stopPropagation) {
4287 e.stopPropagation();
4288 } else if (e.originalEvent) { // In case of Leaflet event.
4289 e.originalEvent._stopped = true;
4291 e.cancelBubble = true;
4293 L.DomEvent._skipped(e);
4298 // @function disableScrollPropagation(el: HTMLElement): this
4299 // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
4300 disableScrollPropagation: function (el) {
4301 return L.DomEvent.on(el, 'mousewheel', L.DomEvent.stopPropagation);
4304 // @function disableClickPropagation(el: HTMLElement): this
4305 // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
4306 // `'mousedown'` and `'touchstart'` events (plus browser variants).
4307 disableClickPropagation: function (el) {
4308 var stop = L.DomEvent.stopPropagation;
4310 L.DomEvent.on(el, L.Draggable.START.join(' '), stop);
4312 return L.DomEvent.on(el, {
4313 click: L.DomEvent._fakeStop,
4318 // @function preventDefault(ev: DOMEvent): this
4319 // Prevents the default action of the DOM Event `ev` from happening (such as
4320 // following a link in the href of the a element, or doing a POST request
4321 // with page reload when a `<form>` is submitted).
4322 // Use it inside listener functions.
4323 preventDefault: function (e) {
4325 if (e.preventDefault) {
4328 e.returnValue = false;
4333 // @function stop(ev): this
4334 // Does `stopPropagation` and `preventDefault` at the same time.
4335 stop: function (e) {
4338 .stopPropagation(e);
4341 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
4342 // Gets normalized mouse position from a DOM event relative to the
4343 // `container` or to the whole page if not specified.
4344 getMousePosition: function (e, container) {
4346 return new L.Point(e.clientX, e.clientY);
4349 var rect = container.getBoundingClientRect();
4352 e.clientX - rect.left - container.clientLeft,
4353 e.clientY - rect.top - container.clientTop);
4356 // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
4357 // and Firefox scrolls device pixels, not CSS pixels
4358 _wheelPxFactor: (L.Browser.win && L.Browser.chrome) ? 2 :
4359 L.Browser.gecko ? window.devicePixelRatio :
4362 // @function getWheelDelta(ev: DOMEvent): Number
4363 // Gets normalized wheel delta from a mousewheel DOM event, in vertical
4364 // pixels scrolled (negative if scrolling down).
4365 // Events from pointing devices without precise scrolling are mapped to
4366 // a best guess of 60 pixels.
4367 getWheelDelta: function (e) {
4368 return (L.Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
4369 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / L.DomEvent._wheelPxFactor : // Pixels
4370 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
4371 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
4372 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
4373 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
4374 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
4375 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
4381 _fakeStop: function (e) {
4382 // fakes stopPropagation by setting a special event flag, checked/reset with L.DomEvent._skipped(e)
4383 L.DomEvent._skipEvents[e.type] = true;
4386 _skipped: function (e) {
4387 var skipped = this._skipEvents[e.type];
4388 // reset when checking, as it's only used in map container and propagates outside of the map
4389 this._skipEvents[e.type] = false;
4393 // check if element really left/entered the event target (for mouseenter/mouseleave)
4394 _isExternalTarget: function (el, e) {
4396 var related = e.relatedTarget;
4398 if (!related) { return true; }
4401 while (related && (related !== el)) {
4402 related = related.parentNode;
4407 return (related !== el);
4410 // this is a horrible workaround for a bug in Android where a single touch triggers two click events
4411 _filterClick: function (e, handler) {
4412 var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
4413 elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick);
4415 // are they closer together than 500ms yet more than 100ms?
4416 // Android typically triggers them ~300ms apart while multiple listeners
4417 // on the same event should be triggered far faster;
4418 // or check if click is simulated on the element, and if it is, reject any non-simulated events
4420 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
4424 L.DomEvent._lastClick = timeStamp;
4430 // @function addListener(…): this
4431 // Alias to [`L.DomEvent.on`](#domevent-on)
4432 L.DomEvent.addListener = L.DomEvent.on;
4434 // @function removeListener(…): this
4435 // Alias to [`L.DomEvent.off`](#domevent-off)
4436 L.DomEvent.removeListener = L.DomEvent.off;
4441 * @class PosAnimation
4442 * @aka L.PosAnimation
4444 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
4448 * var fx = new L.PosAnimation();
4449 * fx.run(el, [300, 500], 0.5);
4452 * @constructor L.PosAnimation()
4453 * Creates a `PosAnimation` object.
4457 L.PosAnimation = L.Evented.extend({
4459 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
4460 // Run an animation of a given element to a new position, optionally setting
4461 // duration in seconds (`0.25` by default) and easing linearity factor (3rd
4462 // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
4463 // `0.5` by default).
4464 run: function (el, newPos, duration, easeLinearity) {
4468 this._inProgress = true;
4469 this._duration = duration || 0.25;
4470 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
4472 this._startPos = L.DomUtil.getPosition(el);
4473 this._offset = newPos.subtract(this._startPos);
4474 this._startTime = +new Date();
4476 // @event start: Event
4477 // Fired when the animation starts
4484 // Stops the animation (if currently running).
4486 if (!this._inProgress) { return; }
4492 _animate: function () {
4494 this._animId = L.Util.requestAnimFrame(this._animate, this);
4498 _step: function (round) {
4499 var elapsed = (+new Date()) - this._startTime,
4500 duration = this._duration * 1000;
4502 if (elapsed < duration) {
4503 this._runFrame(this._easeOut(elapsed / duration), round);
4510 _runFrame: function (progress, round) {
4511 var pos = this._startPos.add(this._offset.multiplyBy(progress));
4515 L.DomUtil.setPosition(this._el, pos);
4517 // @event step: Event
4518 // Fired continuously during the animation.
4522 _complete: function () {
4523 L.Util.cancelAnimFrame(this._animId);
4525 this._inProgress = false;
4526 // @event end: Event
4527 // Fired when the animation ends.
4531 _easeOut: function (t) {
4532 return 1 - Math.pow(1 - t, this._easeOutPower);
4539 * @namespace Projection
4540 * @projection L.Projection.Mercator
4542 * 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.
4545 L.Projection.Mercator = {
4547 R_MINOR: 6356752.314245179,
4549 bounds: L.bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
4551 project: function (latlng) {
4552 var d = Math.PI / 180,
4555 tmp = this.R_MINOR / r,
4556 e = Math.sqrt(1 - tmp * tmp),
4557 con = e * Math.sin(y);
4559 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
4560 y = -r * Math.log(Math.max(ts, 1E-10));
4562 return new L.Point(latlng.lng * d * r, y);
4565 unproject: function (point) {
4566 var d = 180 / Math.PI,
4568 tmp = this.R_MINOR / r,
4569 e = Math.sqrt(1 - tmp * tmp),
4570 ts = Math.exp(-point.y / r),
4571 phi = Math.PI / 2 - 2 * Math.atan(ts);
4573 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
4574 con = e * Math.sin(phi);
4575 con = Math.pow((1 - con) / (1 + con), e / 2);
4576 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
4580 return new L.LatLng(phi * d, point.x * d / r);
4588 * @crs L.CRS.EPSG3395
4590 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
4593 L.CRS.EPSG3395 = L.extend({}, L.CRS.Earth, {
4595 projection: L.Projection.Mercator,
4597 transformation: (function () {
4598 var scale = 0.5 / (Math.PI * L.Projection.Mercator.R);
4599 return new L.Transformation(scale, 0.5, -scale, 0.5);
4610 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
4611 * 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.
4614 * @section Synchronous usage
4617 * 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.
4620 * var CanvasLayer = L.GridLayer.extend({
4621 * createTile: function(coords){
4622 * // create a <canvas> element for drawing
4623 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
4625 * // setup tile width and height according to the options
4626 * var size = this.getTileSize();
4627 * tile.width = size.x;
4628 * tile.height = size.y;
4630 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z
4631 * var ctx = tile.getContext('2d');
4633 * // return the tile so it can be rendered on screen
4639 * @section Asynchronous usage
4642 * 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.
4645 * var CanvasLayer = L.GridLayer.extend({
4646 * createTile: function(coords, done){
4649 * // create a <canvas> element for drawing
4650 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
4652 * // setup tile width and height according to the options
4653 * var size = this.getTileSize();
4654 * tile.width = size.x;
4655 * tile.height = size.y;
4657 * // draw something asynchronously and pass the tile to the done() callback
4658 * setTimeout(function() {
4659 * done(error, tile);
4671 L.GridLayer = L.Layer.extend({
4674 // @aka GridLayer options
4676 // @option tileSize: Number|Point = 256
4677 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
4680 // @option opacity: Number = 1.0
4681 // Opacity of the tiles. Can be used in the `createTile()` function.
4684 // @option updateWhenIdle: Boolean = depends
4685 // If `false`, new tiles are loaded during panning, otherwise only after it (for better performance). `true` by default on mobile browsers, otherwise `false`.
4686 updateWhenIdle: L.Browser.mobile,
4688 // @option updateWhenZooming: Boolean = true
4689 // 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.
4690 updateWhenZooming: true,
4692 // @option updateInterval: Number = 200
4693 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
4694 updateInterval: 200,
4696 // @option zIndex: Number = 1
4697 // The explicit zIndex of the tile layer.
4700 // @option bounds: LatLngBounds = undefined
4701 // If set, tiles will only be loaded inside the set `LatLngBounds`.
4704 // @option minZoom: Number = 0
4705 // The minimum zoom level that tiles will be loaded at. By default the entire map.
4708 // @option maxZoom: Number = undefined
4709 // The maximum zoom level that tiles will be loaded at.
4712 // @option noWrap: Boolean = false
4713 // Whether the layer is wrapped around the antimeridian. If `true`, the
4714 // GridLayer will only be displayed once at low zoom levels. Has no
4715 // effect when the [map CRS](#map-crs) doesn't wrap around.
4718 // @option pane: String = 'tilePane'
4719 // `Map pane` where the grid layer will be added.
4722 // @option className: String = ''
4723 // A custom class name to assign to the tile layer. Empty by default.
4726 // @option keepBuffer: Number = 2
4727 // When panning the map, keep this many rows and columns of tiles before unloading them.
4731 initialize: function (options) {
4732 L.setOptions(this, options);
4735 onAdd: function () {
4736 this._initContainer();
4745 beforeAdd: function (map) {
4746 map._addZoomLimit(this);
4749 onRemove: function (map) {
4750 this._removeAllTiles();
4751 L.DomUtil.remove(this._container);
4752 map._removeZoomLimit(this);
4753 this._container = null;
4754 this._tileZoom = null;
4757 // @method bringToFront: this
4758 // Brings the tile layer to the top of all tile layers.
4759 bringToFront: function () {
4761 L.DomUtil.toFront(this._container);
4762 this._setAutoZIndex(Math.max);
4767 // @method bringToBack: this
4768 // Brings the tile layer to the bottom of all tile layers.
4769 bringToBack: function () {
4771 L.DomUtil.toBack(this._container);
4772 this._setAutoZIndex(Math.min);
4777 // @method getContainer: HTMLElement
4778 // Returns the HTML element that contains the tiles for this layer.
4779 getContainer: function () {
4780 return this._container;
4783 // @method setOpacity(opacity: Number): this
4784 // Changes the [opacity](#gridlayer-opacity) of the grid layer.
4785 setOpacity: function (opacity) {
4786 this.options.opacity = opacity;
4787 this._updateOpacity();
4791 // @method setZIndex(zIndex: Number): this
4792 // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
4793 setZIndex: function (zIndex) {
4794 this.options.zIndex = zIndex;
4795 this._updateZIndex();
4800 // @method isLoading: Boolean
4801 // Returns `true` if any tile in the grid layer has not finished loading.
4802 isLoading: function () {
4803 return this._loading;
4806 // @method redraw: this
4807 // Causes the layer to clear all the tiles and request them again.
4808 redraw: function () {
4810 this._removeAllTiles();
4816 getEvents: function () {
4818 viewprereset: this._invalidateAll,
4819 viewreset: this._resetView,
4820 zoom: this._resetView,
4821 moveend: this._onMoveEnd
4824 if (!this.options.updateWhenIdle) {
4825 // update tiles on move, but not more often than once per given interval
4826 if (!this._onMove) {
4827 this._onMove = L.Util.throttle(this._onMoveEnd, this.options.updateInterval, this);
4830 events.move = this._onMove;
4833 if (this._zoomAnimated) {
4834 events.zoomanim = this._animateZoom;
4840 // @section Extension methods
4841 // Layers extending `GridLayer` shall reimplement the following method.
4842 // @method createTile(coords: Object, done?: Function): HTMLElement
4843 // Called only internally, must be overriden by classes extending `GridLayer`.
4844 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
4845 // is specified, it must be called when the tile has finished loading and drawing.
4846 createTile: function () {
4847 return document.createElement('div');
4851 // @method getTileSize: Point
4852 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
4853 getTileSize: function () {
4854 var s = this.options.tileSize;
4855 return s instanceof L.Point ? s : new L.Point(s, s);
4858 _updateZIndex: function () {
4859 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
4860 this._container.style.zIndex = this.options.zIndex;
4864 _setAutoZIndex: function (compare) {
4865 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
4867 var layers = this.getPane().children,
4868 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
4870 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
4872 zIndex = layers[i].style.zIndex;
4874 if (layers[i] !== this._container && zIndex) {
4875 edgeZIndex = compare(edgeZIndex, +zIndex);
4879 if (isFinite(edgeZIndex)) {
4880 this.options.zIndex = edgeZIndex + compare(-1, 1);
4881 this._updateZIndex();
4885 _updateOpacity: function () {
4886 if (!this._map) { return; }
4888 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
4889 if (L.Browser.ielt9) { return; }
4891 L.DomUtil.setOpacity(this._container, this.options.opacity);
4893 var now = +new Date(),
4897 for (var key in this._tiles) {
4898 var tile = this._tiles[key];
4899 if (!tile.current || !tile.loaded) { continue; }
4901 var fade = Math.min(1, (now - tile.loaded) / 200);
4903 L.DomUtil.setOpacity(tile.el, fade);
4907 if (tile.active) { willPrune = true; }
4912 if (willPrune && !this._noPrune) { this._pruneTiles(); }
4915 L.Util.cancelAnimFrame(this._fadeFrame);
4916 this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this);
4920 _initContainer: function () {
4921 if (this._container) { return; }
4923 this._container = L.DomUtil.create('div', 'leaflet-layer ' + (this.options.className || ''));
4924 this._updateZIndex();
4926 if (this.options.opacity < 1) {
4927 this._updateOpacity();
4930 this.getPane().appendChild(this._container);
4933 _updateLevels: function () {
4935 var zoom = this._tileZoom,
4936 maxZoom = this.options.maxZoom;
4938 if (zoom === undefined) { return undefined; }
4940 for (var z in this._levels) {
4941 if (this._levels[z].el.children.length || z === zoom) {
4942 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
4944 L.DomUtil.remove(this._levels[z].el);
4945 this._removeTilesAtZoom(z);
4946 delete this._levels[z];
4950 var level = this._levels[zoom],
4954 level = this._levels[zoom] = {};
4956 level.el = L.DomUtil.create('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
4957 level.el.style.zIndex = maxZoom;
4959 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
4962 this._setZoomTransform(level, map.getCenter(), map.getZoom());
4964 // force the browser to consider the newly added element for transition
4965 L.Util.falseFn(level.el.offsetWidth);
4968 this._level = level;
4973 _pruneTiles: function () {
4980 var zoom = this._map.getZoom();
4981 if (zoom > this.options.maxZoom ||
4982 zoom < this.options.minZoom) {
4983 this._removeAllTiles();
4987 for (key in this._tiles) {
4988 tile = this._tiles[key];
4989 tile.retain = tile.current;
4992 for (key in this._tiles) {
4993 tile = this._tiles[key];
4994 if (tile.current && !tile.active) {
4995 var coords = tile.coords;
4996 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
4997 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
5002 for (key in this._tiles) {
5003 if (!this._tiles[key].retain) {
5004 this._removeTile(key);
5009 _removeTilesAtZoom: function (zoom) {
5010 for (var key in this._tiles) {
5011 if (this._tiles[key].coords.z !== zoom) {
5014 this._removeTile(key);
5018 _removeAllTiles: function () {
5019 for (var key in this._tiles) {
5020 this._removeTile(key);
5024 _invalidateAll: function () {
5025 for (var z in this._levels) {
5026 L.DomUtil.remove(this._levels[z].el);
5027 delete this._levels[z];
5029 this._removeAllTiles();
5031 this._tileZoom = null;
5034 _retainParent: function (x, y, z, minZoom) {
5035 var x2 = Math.floor(x / 2),
5036 y2 = Math.floor(y / 2),
5038 coords2 = new L.Point(+x2, +y2);
5041 var key = this._tileCoordsToKey(coords2),
5042 tile = this._tiles[key];
5044 if (tile && tile.active) {
5048 } else if (tile && tile.loaded) {
5053 return this._retainParent(x2, y2, z2, minZoom);
5059 _retainChildren: function (x, y, z, maxZoom) {
5061 for (var i = 2 * x; i < 2 * x + 2; i++) {
5062 for (var j = 2 * y; j < 2 * y + 2; j++) {
5064 var coords = new L.Point(i, j);
5067 var key = this._tileCoordsToKey(coords),
5068 tile = this._tiles[key];
5070 if (tile && tile.active) {
5074 } else if (tile && tile.loaded) {
5078 if (z + 1 < maxZoom) {
5079 this._retainChildren(i, j, z + 1, maxZoom);
5085 _resetView: function (e) {
5086 var animating = e && (e.pinch || e.flyTo);
5087 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
5090 _animateZoom: function (e) {
5091 this._setView(e.center, e.zoom, true, e.noUpdate);
5094 _setView: function (center, zoom, noPrune, noUpdate) {
5095 var tileZoom = Math.round(zoom);
5096 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
5097 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
5098 tileZoom = undefined;
5101 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
5103 if (!noUpdate || tileZoomChanged) {
5105 this._tileZoom = tileZoom;
5107 if (this._abortLoading) {
5108 this._abortLoading();
5111 this._updateLevels();
5114 if (tileZoom !== undefined) {
5115 this._update(center);
5122 // Flag to prevent _updateOpacity from pruning tiles during
5123 // a zoom anim or a pinch gesture
5124 this._noPrune = !!noPrune;
5127 this._setZoomTransforms(center, zoom);
5130 _setZoomTransforms: function (center, zoom) {
5131 for (var i in this._levels) {
5132 this._setZoomTransform(this._levels[i], center, zoom);
5136 _setZoomTransform: function (level, center, zoom) {
5137 var scale = this._map.getZoomScale(zoom, level.zoom),
5138 translate = level.origin.multiplyBy(scale)
5139 .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
5141 if (L.Browser.any3d) {
5142 L.DomUtil.setTransform(level.el, translate, scale);
5144 L.DomUtil.setPosition(level.el, translate);
5148 _resetGrid: function () {
5149 var map = this._map,
5150 crs = map.options.crs,
5151 tileSize = this._tileSize = this.getTileSize(),
5152 tileZoom = this._tileZoom;
5154 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
5156 this._globalTileRange = this._pxBoundsToTileRange(bounds);
5159 this._wrapX = crs.wrapLng && !this.options.noWrap && [
5160 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
5161 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
5163 this._wrapY = crs.wrapLat && !this.options.noWrap && [
5164 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
5165 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
5169 _onMoveEnd: function () {
5170 if (!this._map || this._map._animatingZoom) { return; }
5175 _getTiledPixelBounds: function (center) {
5176 var map = this._map,
5177 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
5178 scale = map.getZoomScale(mapZoom, this._tileZoom),
5179 pixelCenter = map.project(center, this._tileZoom).floor(),
5180 halfSize = map.getSize().divideBy(scale * 2);
5182 return new L.Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
5185 // Private method to load tiles in the grid's active zoom level according to map bounds
5186 _update: function (center) {
5187 var map = this._map;
5188 if (!map) { return; }
5189 var zoom = map.getZoom();
5191 if (center === undefined) { center = map.getCenter(); }
5192 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom
5194 var pixelBounds = this._getTiledPixelBounds(center),
5195 tileRange = this._pxBoundsToTileRange(pixelBounds),
5196 tileCenter = tileRange.getCenter(),
5198 margin = this.options.keepBuffer,
5199 noPruneRange = new L.Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
5200 tileRange.getTopRight().add([margin, -margin]));
5202 for (var key in this._tiles) {
5203 var c = this._tiles[key].coords;
5204 if (c.z !== this._tileZoom || !noPruneRange.contains(L.point(c.x, c.y))) {
5205 this._tiles[key].current = false;
5209 // _update just loads more tiles. If the tile zoom level differs too much
5210 // from the map's, let _setView reset levels and prune old tiles.
5211 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
5213 // create a queue of coordinates to load tiles from
5214 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
5215 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
5216 var coords = new L.Point(i, j);
5217 coords.z = this._tileZoom;
5219 if (!this._isValidTile(coords)) { continue; }
5221 var tile = this._tiles[this._tileCoordsToKey(coords)];
5223 tile.current = true;
5230 // sort tile queue to load tiles in order of their distance to center
5231 queue.sort(function (a, b) {
5232 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
5235 if (queue.length !== 0) {
5236 // if it's the first batch of tiles to load
5237 if (!this._loading) {
5238 this._loading = true;
5239 // @event loading: Event
5240 // Fired when the grid layer starts loading tiles.
5241 this.fire('loading');
5244 // create DOM fragment to append tiles in one batch
5245 var fragment = document.createDocumentFragment();
5247 for (i = 0; i < queue.length; i++) {
5248 this._addTile(queue[i], fragment);
5251 this._level.el.appendChild(fragment);
5255 _isValidTile: function (coords) {
5256 var crs = this._map.options.crs;
5258 if (!crs.infinite) {
5259 // don't load tile if it's out of bounds and not wrapped
5260 var bounds = this._globalTileRange;
5261 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
5262 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
5265 if (!this.options.bounds) { return true; }
5267 // don't load tile if it doesn't intersect the bounds in options
5268 var tileBounds = this._tileCoordsToBounds(coords);
5269 return L.latLngBounds(this.options.bounds).overlaps(tileBounds);
5272 _keyToBounds: function (key) {
5273 return this._tileCoordsToBounds(this._keyToTileCoords(key));
5276 // converts tile coordinates to its geographical bounds
5277 _tileCoordsToBounds: function (coords) {
5279 var map = this._map,
5280 tileSize = this.getTileSize(),
5282 nwPoint = coords.scaleBy(tileSize),
5283 sePoint = nwPoint.add(tileSize),
5285 nw = map.unproject(nwPoint, coords.z),
5286 se = map.unproject(sePoint, coords.z);
5288 if (!this.options.noWrap) {
5289 nw = map.wrapLatLng(nw);
5290 se = map.wrapLatLng(se);
5293 return new L.LatLngBounds(nw, se);
5296 // converts tile coordinates to key for the tile cache
5297 _tileCoordsToKey: function (coords) {
5298 return coords.x + ':' + coords.y + ':' + coords.z;
5301 // converts tile cache key to coordinates
5302 _keyToTileCoords: function (key) {
5303 var k = key.split(':'),
5304 coords = new L.Point(+k[0], +k[1]);
5309 _removeTile: function (key) {
5310 var tile = this._tiles[key];
5311 if (!tile) { return; }
5313 L.DomUtil.remove(tile.el);
5315 delete this._tiles[key];
5317 // @event tileunload: TileEvent
5318 // Fired when a tile is removed (e.g. when a tile goes off the screen).
5319 this.fire('tileunload', {
5321 coords: this._keyToTileCoords(key)
5325 _initTile: function (tile) {
5326 L.DomUtil.addClass(tile, 'leaflet-tile');
5328 var tileSize = this.getTileSize();
5329 tile.style.width = tileSize.x + 'px';
5330 tile.style.height = tileSize.y + 'px';
5332 tile.onselectstart = L.Util.falseFn;
5333 tile.onmousemove = L.Util.falseFn;
5335 // update opacity on tiles in IE7-8 because of filter inheritance problems
5336 if (L.Browser.ielt9 && this.options.opacity < 1) {
5337 L.DomUtil.setOpacity(tile, this.options.opacity);
5340 // without this hack, tiles disappear after zoom on Chrome for Android
5341 // https://github.com/Leaflet/Leaflet/issues/2078
5342 if (L.Browser.android && !L.Browser.android23) {
5343 tile.style.WebkitBackfaceVisibility = 'hidden';
5347 _addTile: function (coords, container) {
5348 var tilePos = this._getTilePos(coords),
5349 key = this._tileCoordsToKey(coords);
5351 var tile = this.createTile(this._wrapCoords(coords), L.bind(this._tileReady, this, coords));
5353 this._initTile(tile);
5355 // if createTile is defined with a second argument ("done" callback),
5356 // we know that tile is async and will be ready later; otherwise
5357 if (this.createTile.length < 2) {
5358 // mark tile as ready, but delay one frame for opacity animation to happen
5359 L.Util.requestAnimFrame(L.bind(this._tileReady, this, coords, null, tile));
5362 L.DomUtil.setPosition(tile, tilePos);
5364 // save tile in cache
5365 this._tiles[key] = {
5371 container.appendChild(tile);
5372 // @event tileloadstart: TileEvent
5373 // Fired when a tile is requested and starts loading.
5374 this.fire('tileloadstart', {
5380 _tileReady: function (coords, err, tile) {
5381 if (!this._map) { return; }
5384 // @event tileerror: TileErrorEvent
5385 // Fired when there is an error loading a tile.
5386 this.fire('tileerror', {
5393 var key = this._tileCoordsToKey(coords);
5395 tile = this._tiles[key];
5396 if (!tile) { return; }
5398 tile.loaded = +new Date();
5399 if (this._map._fadeAnimated) {
5400 L.DomUtil.setOpacity(tile.el, 0);
5401 L.Util.cancelAnimFrame(this._fadeFrame);
5402 this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this);
5409 L.DomUtil.addClass(tile.el, 'leaflet-tile-loaded');
5411 // @event tileload: TileEvent
5412 // Fired when a tile loads.
5413 this.fire('tileload', {
5419 if (this._noTilesToLoad()) {
5420 this._loading = false;
5421 // @event load: Event
5422 // Fired when the grid layer loaded all visible tiles.
5425 if (L.Browser.ielt9 || !this._map._fadeAnimated) {
5426 L.Util.requestAnimFrame(this._pruneTiles, this);
5428 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
5429 // to trigger a pruning.
5430 setTimeout(L.bind(this._pruneTiles, this), 250);
5435 _getTilePos: function (coords) {
5436 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
5439 _wrapCoords: function (coords) {
5440 var newCoords = new L.Point(
5441 this._wrapX ? L.Util.wrapNum(coords.x, this._wrapX) : coords.x,
5442 this._wrapY ? L.Util.wrapNum(coords.y, this._wrapY) : coords.y);
5443 newCoords.z = coords.z;
5447 _pxBoundsToTileRange: function (bounds) {
5448 var tileSize = this.getTileSize();
5449 return new L.Bounds(
5450 bounds.min.unscaleBy(tileSize).floor(),
5451 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
5454 _noTilesToLoad: function () {
5455 for (var key in this._tiles) {
5456 if (!this._tiles[key].loaded) { return false; }
5462 // @factory L.gridLayer(options?: GridLayer options)
5463 // Creates a new instance of GridLayer with the supplied options.
5464 L.gridLayer = function (options) {
5465 return new L.GridLayer(options);
5472 * @inherits GridLayer
5474 * Used to load and display tile layers on the map. Extends `GridLayer`.
5479 * L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map);
5482 * @section URL template
5485 * A string of the following form:
5488 * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
5491 * `{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.
5493 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
5496 * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
5501 L.TileLayer = L.GridLayer.extend({
5504 // @aka TileLayer options
5506 // @option minZoom: Number = 0
5507 // Minimum zoom number.
5510 // @option maxZoom: Number = 18
5511 // Maximum zoom number.
5514 // @option maxNativeZoom: Number = null
5515 // Maximum zoom number the tile source has available. If it is specified,
5516 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
5517 // from `maxNativeZoom` level and auto-scaled.
5518 maxNativeZoom: null,
5520 // @option minNativeZoom: Number = null
5521 // Minimum zoom number the tile source has available. If it is specified,
5522 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
5523 // from `minNativeZoom` level and auto-scaled.
5524 minNativeZoom: null,
5526 // @option subdomains: String|String[] = 'abc'
5527 // 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.
5530 // @option errorTileUrl: String = ''
5531 // URL to the tile image to show in place of the tile that failed to load.
5534 // @option zoomOffset: Number = 0
5535 // The zoom number used in tile URLs will be offset with this value.
5538 // @option tms: Boolean = false
5539 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
5542 // @option zoomReverse: Boolean = false
5543 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
5546 // @option detectRetina: Boolean = false
5547 // 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.
5548 detectRetina: false,
5550 // @option crossOrigin: Boolean = false
5551 // If true, all tiles will have their crossOrigin attribute set to ''. This is needed if you want to access tile pixel data.
5555 initialize: function (url, options) {
5559 options = L.setOptions(this, options);
5561 // detecting retina displays, adjusting tileSize and zoom levels
5562 if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {
5564 options.tileSize = Math.floor(options.tileSize / 2);
5566 if (!options.zoomReverse) {
5567 options.zoomOffset++;
5570 options.zoomOffset--;
5574 options.minZoom = Math.max(0, options.minZoom);
5577 if (typeof options.subdomains === 'string') {
5578 options.subdomains = options.subdomains.split('');
5581 // for https://github.com/Leaflet/Leaflet/issues/137
5582 if (!L.Browser.android) {
5583 this.on('tileunload', this._onTileRemove);
5587 // @method setUrl(url: String, noRedraw?: Boolean): this
5588 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
5589 setUrl: function (url, noRedraw) {
5598 // @method createTile(coords: Object, done?: Function): HTMLElement
5599 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
5600 // to return an `<img>` HTML element with the appropiate image URL given `coords`. The `done`
5601 // callback is called when the tile has been loaded.
5602 createTile: function (coords, done) {
5603 var tile = document.createElement('img');
5605 L.DomEvent.on(tile, 'load', L.bind(this._tileOnLoad, this, done, tile));
5606 L.DomEvent.on(tile, 'error', L.bind(this._tileOnError, this, done, tile));
5608 if (this.options.crossOrigin) {
5609 tile.crossOrigin = '';
5613 Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
5614 http://www.w3.org/TR/WCAG20-TECHS/H67
5619 Set role="presentation" to force screen readers to ignore this
5620 https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
5622 tile.setAttribute('role', 'presentation');
5624 tile.src = this.getTileUrl(coords);
5629 // @section Extension methods
5631 // Layers extending `TileLayer` might reimplement the following method.
5632 // @method getTileUrl(coords: Object): String
5633 // Called only internally, returns the URL for a tile given its coordinates.
5634 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
5635 getTileUrl: function (coords) {
5637 r: L.Browser.retina ? '@2x' : '',
5638 s: this._getSubdomain(coords),
5641 z: this._getZoomForUrl()
5643 if (this._map && !this._map.options.crs.infinite) {
5644 var invertedY = this._globalTileRange.max.y - coords.y;
5645 if (this.options.tms) {
5646 data['y'] = invertedY;
5648 data['-y'] = invertedY;
5651 return L.Util.template(this._url, L.extend(data, this.options));
5654 _tileOnLoad: function (done, tile) {
5655 // For https://github.com/Leaflet/Leaflet/issues/3332
5656 if (L.Browser.ielt9) {
5657 setTimeout(L.bind(done, this, null, tile), 0);
5663 _tileOnError: function (done, tile, e) {
5664 var errorUrl = this.options.errorTileUrl;
5666 tile.src = errorUrl;
5671 getTileSize: function () {
5672 var map = this._map,
5673 tileSize = L.GridLayer.prototype.getTileSize.call(this),
5674 zoom = this._tileZoom + this.options.zoomOffset,
5675 minNativeZoom = this.options.minNativeZoom,
5676 maxNativeZoom = this.options.maxNativeZoom;
5678 // decrease tile size when scaling below minNativeZoom
5679 if (minNativeZoom !== null && zoom < minNativeZoom) {
5680 return tileSize.divideBy(map.getZoomScale(minNativeZoom, zoom)).round();
5683 // increase tile size when scaling above maxNativeZoom
5684 if (maxNativeZoom !== null && zoom > maxNativeZoom) {
5685 return tileSize.divideBy(map.getZoomScale(maxNativeZoom, zoom)).round();
5691 _onTileRemove: function (e) {
5692 e.tile.onload = null;
5695 _getZoomForUrl: function () {
5696 var zoom = this._tileZoom,
5697 maxZoom = this.options.maxZoom,
5698 zoomReverse = this.options.zoomReverse,
5699 zoomOffset = this.options.zoomOffset,
5700 minNativeZoom = this.options.minNativeZoom,
5701 maxNativeZoom = this.options.maxNativeZoom;
5704 zoom = maxZoom - zoom;
5709 if (minNativeZoom !== null && zoom < minNativeZoom) {
5710 return minNativeZoom;
5713 if (maxNativeZoom !== null && zoom > maxNativeZoom) {
5714 return maxNativeZoom;
5720 _getSubdomain: function (tilePoint) {
5721 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
5722 return this.options.subdomains[index];
5725 // stops loading all tiles in the background layer
5726 _abortLoading: function () {
5728 for (i in this._tiles) {
5729 if (this._tiles[i].coords.z !== this._tileZoom) {
5730 tile = this._tiles[i].el;
5732 tile.onload = L.Util.falseFn;
5733 tile.onerror = L.Util.falseFn;
5735 if (!tile.complete) {
5736 tile.src = L.Util.emptyImageUrl;
5737 L.DomUtil.remove(tile);
5745 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
5746 // Instantiates a tile layer object given a `URL template` and optionally an options object.
5748 L.tileLayer = function (url, options) {
5749 return new L.TileLayer(url, options);
5755 * @class TileLayer.WMS
5756 * @inherits TileLayer
5757 * @aka L.TileLayer.WMS
5758 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
5763 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
5764 * layers: 'nexrad-n0r-900913',
5765 * format: 'image/png',
5766 * transparent: true,
5767 * attribution: "Weather data © 2012 IEM Nexrad"
5772 L.TileLayer.WMS = L.TileLayer.extend({
5775 // @aka TileLayer.WMS options
5776 // If any custom options not documented here are used, they will be sent to the
5777 // WMS server as extra parameters in each request URL. This can be useful for
5778 // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
5783 // @option layers: String = ''
5784 // **(required)** Comma-separated list of WMS layers to show.
5787 // @option styles: String = ''
5788 // Comma-separated list of WMS styles.
5791 // @option format: String = 'image/jpeg'
5792 // WMS image format (use `'image/png'` for layers with transparency).
5793 format: 'image/jpeg',
5795 // @option transparent: Boolean = false
5796 // If `true`, the WMS service will return images with transparency.
5799 // @option version: String = '1.1.1'
5800 // Version of the WMS service to use
5805 // @option crs: CRS = null
5806 // Coordinate Reference System to use for the WMS requests, defaults to
5807 // map CRS. Don't change this if you're not sure what it means.
5810 // @option uppercase: Boolean = false
5811 // If `true`, WMS request parameter keys will be uppercase.
5815 initialize: function (url, options) {
5819 var wmsParams = L.extend({}, this.defaultWmsParams);
5821 // all keys that are not TileLayer options go to WMS params
5822 for (var i in options) {
5823 if (!(i in this.options)) {
5824 wmsParams[i] = options[i];
5828 options = L.setOptions(this, options);
5830 wmsParams.width = wmsParams.height = options.tileSize * (options.detectRetina && L.Browser.retina ? 2 : 1);
5832 this.wmsParams = wmsParams;
5835 onAdd: function (map) {
5837 this._crs = this.options.crs || map.options.crs;
5838 this._wmsVersion = parseFloat(this.wmsParams.version);
5840 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
5841 this.wmsParams[projectionKey] = this._crs.code;
5843 L.TileLayer.prototype.onAdd.call(this, map);
5846 getTileUrl: function (coords) {
5848 var tileBounds = this._tileCoordsToBounds(coords),
5849 nw = this._crs.project(tileBounds.getNorthWest()),
5850 se = this._crs.project(tileBounds.getSouthEast()),
5852 bbox = (this._wmsVersion >= 1.3 && this._crs === L.CRS.EPSG4326 ?
5853 [se.y, nw.x, nw.y, se.x] :
5854 [nw.x, se.y, se.x, nw.y]).join(','),
5856 url = L.TileLayer.prototype.getTileUrl.call(this, coords);
5859 L.Util.getParamString(this.wmsParams, url, this.options.uppercase) +
5860 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
5863 // @method setParams(params: Object, noRedraw?: Boolean): this
5864 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
5865 setParams: function (params, noRedraw) {
5867 L.extend(this.wmsParams, params);
5878 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
5879 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
5880 L.tileLayer.wms = function (url, options) {
5881 return new L.TileLayer.WMS(url, options);
5887 * @class ImageOverlay
5888 * @aka L.ImageOverlay
5889 * @inherits Interactive layer
5891 * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
5896 * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
5897 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
5898 * L.imageOverlay(imageUrl, imageBounds).addTo(map);
5902 L.ImageOverlay = L.Layer.extend({
5905 // @aka ImageOverlay options
5907 // @option opacity: Number = 1.0
5908 // The opacity of the image overlay.
5911 // @option alt: String = ''
5912 // Text for the `alt` attribute of the image (useful for accessibility).
5915 // @option interactive: Boolean = false
5916 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
5919 // @option crossOrigin: Boolean = false
5920 // If true, the image will have its crossOrigin attribute set to ''. This is needed if you want to access image pixel data.
5924 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
5926 this._bounds = L.latLngBounds(bounds);
5928 L.setOptions(this, options);
5931 onAdd: function () {
5935 if (this.options.opacity < 1) {
5936 this._updateOpacity();
5940 if (this.options.interactive) {
5941 L.DomUtil.addClass(this._image, 'leaflet-interactive');
5942 this.addInteractiveTarget(this._image);
5945 this.getPane().appendChild(this._image);
5949 onRemove: function () {
5950 L.DomUtil.remove(this._image);
5951 if (this.options.interactive) {
5952 this.removeInteractiveTarget(this._image);
5956 // @method setOpacity(opacity: Number): this
5957 // Sets the opacity of the overlay.
5958 setOpacity: function (opacity) {
5959 this.options.opacity = opacity;
5962 this._updateOpacity();
5967 setStyle: function (styleOpts) {
5968 if (styleOpts.opacity) {
5969 this.setOpacity(styleOpts.opacity);
5974 // @method bringToFront(): this
5975 // Brings the layer to the top of all overlays.
5976 bringToFront: function () {
5978 L.DomUtil.toFront(this._image);
5983 // @method bringToBack(): this
5984 // Brings the layer to the bottom of all overlays.
5985 bringToBack: function () {
5987 L.DomUtil.toBack(this._image);
5992 // @method setUrl(url: String): this
5993 // Changes the URL of the image.
5994 setUrl: function (url) {
5998 this._image.src = url;
6003 setBounds: function (bounds) {
6004 this._bounds = bounds;
6012 getEvents: function () {
6015 viewreset: this._reset
6018 if (this._zoomAnimated) {
6019 events.zoomanim = this._animateZoom;
6025 getBounds: function () {
6026 return this._bounds;
6029 getElement: function () {
6033 _initImage: function () {
6034 var img = this._image = L.DomUtil.create('img',
6035 'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : ''));
6037 img.onselectstart = L.Util.falseFn;
6038 img.onmousemove = L.Util.falseFn;
6040 img.onload = L.bind(this.fire, this, 'load');
6042 if (this.options.crossOrigin) {
6043 img.crossOrigin = '';
6046 img.src = this._url;
6047 img.alt = this.options.alt;
6050 _animateZoom: function (e) {
6051 var scale = this._map.getZoomScale(e.zoom),
6052 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
6054 L.DomUtil.setTransform(this._image, offset, scale);
6057 _reset: function () {
6058 var image = this._image,
6059 bounds = new L.Bounds(
6060 this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
6061 this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
6062 size = bounds.getSize();
6064 L.DomUtil.setPosition(image, bounds.min);
6066 image.style.width = size.x + 'px';
6067 image.style.height = size.y + 'px';
6070 _updateOpacity: function () {
6071 L.DomUtil.setOpacity(this._image, this.options.opacity);
6075 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
6076 // Instantiates an image overlay object given the URL of the image and the
6077 // geographical bounds it is tied to.
6078 L.imageOverlay = function (url, bounds, options) {
6079 return new L.ImageOverlay(url, bounds, options);
6089 * Represents an icon to provide when creating a marker.
6094 * var myIcon = L.icon({
6095 * iconUrl: 'my-icon.png',
6096 * iconRetinaUrl: 'my-icon@2x.png',
6097 * iconSize: [38, 95],
6098 * iconAnchor: [22, 94],
6099 * popupAnchor: [-3, -76],
6100 * shadowUrl: 'my-icon-shadow.png',
6101 * shadowRetinaUrl: 'my-icon-shadow@2x.png',
6102 * shadowSize: [68, 95],
6103 * shadowAnchor: [22, 94]
6106 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
6109 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
6113 L.Icon = L.Class.extend({
6118 * @option iconUrl: String = null
6119 * **(required)** The URL to the icon image (absolute or relative to your script path).
6121 * @option iconRetinaUrl: String = null
6122 * The URL to a retina sized version of the icon image (absolute or relative to your
6123 * script path). Used for Retina screen devices.
6125 * @option iconSize: Point = null
6126 * Size of the icon image in pixels.
6128 * @option iconAnchor: Point = null
6129 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
6130 * will be aligned so that this point is at the marker's geographical location. Centered
6131 * by default if size is specified, also can be set in CSS with negative margins.
6133 * @option popupAnchor: Point = null
6134 * The coordinates of the point from which popups will "open", relative to the icon anchor.
6136 * @option shadowUrl: String = null
6137 * The URL to the icon shadow image. If not specified, no shadow image will be created.
6139 * @option shadowRetinaUrl: String = null
6141 * @option shadowSize: Point = null
6142 * Size of the shadow image in pixels.
6144 * @option shadowAnchor: Point = null
6145 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
6146 * as iconAnchor if not specified).
6148 * @option className: String = ''
6149 * A custom class name to assign to both icon and shadow images. Empty by default.
6152 initialize: function (options) {
6153 L.setOptions(this, options);
6156 // @method createIcon(oldIcon?: HTMLElement): HTMLElement
6157 // Called internally when the icon has to be shown, returns a `<img>` HTML element
6158 // styled according to the options.
6159 createIcon: function (oldIcon) {
6160 return this._createIcon('icon', oldIcon);
6163 // @method createShadow(oldIcon?: HTMLElement): HTMLElement
6164 // As `createIcon`, but for the shadow beneath it.
6165 createShadow: function (oldIcon) {
6166 return this._createIcon('shadow', oldIcon);
6169 _createIcon: function (name, oldIcon) {
6170 var src = this._getIconUrl(name);
6173 if (name === 'icon') {
6174 throw new Error('iconUrl not set in Icon options (see the docs).');
6179 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
6180 this._setIconStyles(img, name);
6185 _setIconStyles: function (img, name) {
6186 var options = this.options;
6187 var sizeOption = options[name + 'Size'];
6189 if (typeof sizeOption === 'number') {
6190 sizeOption = [sizeOption, sizeOption];
6193 var size = L.point(sizeOption),
6194 anchor = L.point(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
6195 size && size.divideBy(2, true));
6197 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
6200 img.style.marginLeft = (-anchor.x) + 'px';
6201 img.style.marginTop = (-anchor.y) + 'px';
6205 img.style.width = size.x + 'px';
6206 img.style.height = size.y + 'px';
6210 _createImg: function (src, el) {
6211 el = el || document.createElement('img');
6216 _getIconUrl: function (name) {
6217 return L.Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
6222 // @factory L.icon(options: Icon options)
6223 // Creates an icon instance with the given options.
6224 L.icon = function (options) {
6225 return new L.Icon(options);
6231 * @miniclass Icon.Default (Icon)
6232 * @aka L.Icon.Default
6235 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
6236 * no icon is specified. Points to the blue marker image distributed with Leaflet
6239 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
6240 * (which is a set of `Icon options`).
6242 * If you want to _completely_ replace the default icon, override the
6243 * `L.Marker.prototype.options.icon` with your own icon instead.
6246 L.Icon.Default = L.Icon.extend({
6249 iconUrl: 'marker-icon.png',
6250 iconRetinaUrl: 'marker-icon-2x.png',
6251 shadowUrl: 'marker-shadow.png',
6253 iconAnchor: [12, 41],
6254 popupAnchor: [1, -34],
6255 tooltipAnchor: [16, -28],
6256 shadowSize: [41, 41]
6259 _getIconUrl: function (name) {
6260 if (!L.Icon.Default.imagePath) { // Deprecated, backwards-compatibility only
6261 L.Icon.Default.imagePath = this._detectIconPath();
6264 // @option imagePath: String
6265 // `L.Icon.Default` will try to auto-detect the absolute location of the
6266 // blue icon images. If you are placing these images in a non-standard
6267 // way, set this option to point to the right absolute path.
6268 return (this.options.imagePath || L.Icon.Default.imagePath) + L.Icon.prototype._getIconUrl.call(this, name);
6271 _detectIconPath: function () {
6272 var el = L.DomUtil.create('div', 'leaflet-default-icon-path', document.body);
6273 var path = L.DomUtil.getStyle(el, 'background-image') ||
6274 L.DomUtil.getStyle(el, 'backgroundImage'); // IE8
6276 document.body.removeChild(el);
6278 return path.indexOf('url') === 0 ?
6279 path.replace(/^url\([\"\']?/, '').replace(/marker-icon\.png[\"\']?\)$/, '') : '';
6287 * @inherits Interactive layer
6289 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
6294 * L.marker([50.5, 30.5]).addTo(map);
6298 L.Marker = L.Layer.extend({
6301 // @aka Marker options
6303 // @option icon: Icon = *
6304 // 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.
6305 icon: new L.Icon.Default(),
6307 // Option inherited from "Interactive layer" abstract class
6310 // @option draggable: Boolean = false
6311 // Whether the marker is draggable with mouse/touch or not.
6314 // @option keyboard: Boolean = true
6315 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
6318 // @option title: String = ''
6319 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
6322 // @option alt: String = ''
6323 // Text for the `alt` attribute of the icon image (useful for accessibility).
6326 // @option zIndexOffset: Number = 0
6327 // 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).
6330 // @option opacity: Number = 1.0
6331 // The opacity of the marker.
6334 // @option riseOnHover: Boolean = false
6335 // If `true`, the marker will get on top of others when you hover the mouse over it.
6338 // @option riseOffset: Number = 250
6339 // The z-index offset used for the `riseOnHover` feature.
6342 // @option pane: String = 'markerPane'
6343 // `Map pane` where the markers icon will be added.
6346 // FIXME: shadowPane is no longer a valid option
6347 nonBubblingEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu']
6352 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
6355 initialize: function (latlng, options) {
6356 L.setOptions(this, options);
6357 this._latlng = L.latLng(latlng);
6360 onAdd: function (map) {
6361 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
6363 if (this._zoomAnimated) {
6364 map.on('zoomanim', this._animateZoom, this);
6371 onRemove: function (map) {
6372 if (this.dragging && this.dragging.enabled()) {
6373 this.options.draggable = true;
6374 this.dragging.removeHooks();
6377 if (this._zoomAnimated) {
6378 map.off('zoomanim', this._animateZoom, this);
6382 this._removeShadow();
6385 getEvents: function () {
6388 viewreset: this.update
6392 // @method getLatLng: LatLng
6393 // Returns the current geographical position of the marker.
6394 getLatLng: function () {
6395 return this._latlng;
6398 // @method setLatLng(latlng: LatLng): this
6399 // Changes the marker position to the given point.
6400 setLatLng: function (latlng) {
6401 var oldLatLng = this._latlng;
6402 this._latlng = L.latLng(latlng);
6405 // @event move: Event
6406 // 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`.
6407 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
6410 // @method setZIndexOffset(offset: Number): this
6411 // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
6412 setZIndexOffset: function (offset) {
6413 this.options.zIndexOffset = offset;
6414 return this.update();
6417 // @method setIcon(icon: Icon): this
6418 // Changes the marker icon.
6419 setIcon: function (icon) {
6421 this.options.icon = icon;
6429 this.bindPopup(this._popup, this._popup.options);
6435 getElement: function () {
6439 update: function () {
6442 var pos = this._map.latLngToLayerPoint(this._latlng).round();
6449 _initIcon: function () {
6450 var options = this.options,
6451 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
6453 var icon = options.icon.createIcon(this._icon),
6456 // if we're not reusing the icon, remove the old one and init new one
6457 if (icon !== this._icon) {
6463 if (options.title) {
6464 icon.title = options.title;
6467 icon.alt = options.alt;
6471 L.DomUtil.addClass(icon, classToAdd);
6473 if (options.keyboard) {
6474 icon.tabIndex = '0';
6479 if (options.riseOnHover) {
6481 mouseover: this._bringToFront,
6482 mouseout: this._resetZIndex
6486 var newShadow = options.icon.createShadow(this._shadow),
6489 if (newShadow !== this._shadow) {
6490 this._removeShadow();
6495 L.DomUtil.addClass(newShadow, classToAdd);
6497 this._shadow = newShadow;
6500 if (options.opacity < 1) {
6501 this._updateOpacity();
6506 this.getPane().appendChild(this._icon);
6508 this._initInteraction();
6509 if (newShadow && addShadow) {
6510 this.getPane('shadowPane').appendChild(this._shadow);
6514 _removeIcon: function () {
6515 if (this.options.riseOnHover) {
6517 mouseover: this._bringToFront,
6518 mouseout: this._resetZIndex
6522 L.DomUtil.remove(this._icon);
6523 this.removeInteractiveTarget(this._icon);
6528 _removeShadow: function () {
6530 L.DomUtil.remove(this._shadow);
6532 this._shadow = null;
6535 _setPos: function (pos) {
6536 L.DomUtil.setPosition(this._icon, pos);
6539 L.DomUtil.setPosition(this._shadow, pos);
6542 this._zIndex = pos.y + this.options.zIndexOffset;
6544 this._resetZIndex();
6547 _updateZIndex: function (offset) {
6548 this._icon.style.zIndex = this._zIndex + offset;
6551 _animateZoom: function (opt) {
6552 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
6557 _initInteraction: function () {
6559 if (!this.options.interactive) { return; }
6561 L.DomUtil.addClass(this._icon, 'leaflet-interactive');
6563 this.addInteractiveTarget(this._icon);
6565 if (L.Handler.MarkerDrag) {
6566 var draggable = this.options.draggable;
6567 if (this.dragging) {
6568 draggable = this.dragging.enabled();
6569 this.dragging.disable();
6572 this.dragging = new L.Handler.MarkerDrag(this);
6575 this.dragging.enable();
6580 // @method setOpacity(opacity: Number): this
6581 // Changes the opacity of the marker.
6582 setOpacity: function (opacity) {
6583 this.options.opacity = opacity;
6585 this._updateOpacity();
6591 _updateOpacity: function () {
6592 var opacity = this.options.opacity;
6594 L.DomUtil.setOpacity(this._icon, opacity);
6597 L.DomUtil.setOpacity(this._shadow, opacity);
6601 _bringToFront: function () {
6602 this._updateZIndex(this.options.riseOffset);
6605 _resetZIndex: function () {
6606 this._updateZIndex(0);
6609 _getPopupAnchor: function () {
6610 return this.options.icon.options.popupAnchor || [0, 0];
6613 _getTooltipAnchor: function () {
6614 return this.options.icon.options.tooltipAnchor || [0, 0];
6619 // factory L.marker(latlng: LatLng, options? : Marker options)
6621 // @factory L.marker(latlng: LatLng, options? : Marker options)
6622 // Instantiates a Marker object given a geographical point and optionally an options object.
6623 L.marker = function (latlng, options) {
6624 return new L.Marker(latlng, options);
6634 * Represents a lightweight icon for markers that uses a simple `<div>`
6635 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
6639 * var myIcon = L.divIcon({className: 'my-div-icon'});
6640 * // you can set .my-div-icon styles in CSS
6642 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
6645 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
6648 L.DivIcon = L.Icon.extend({
6651 // @aka DivIcon options
6652 iconSize: [12, 12], // also can be set through CSS
6654 // iconAnchor: (Point),
6655 // popupAnchor: (Point),
6657 // @option html: String = ''
6658 // Custom HTML code to put inside the div element, empty by default.
6661 // @option bgPos: Point = [0, 0]
6662 // Optional relative position of the background, in pixels
6665 className: 'leaflet-div-icon'
6668 createIcon: function (oldIcon) {
6669 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
6670 options = this.options;
6672 div.innerHTML = options.html !== false ? options.html : '';
6674 if (options.bgPos) {
6675 var bgPos = L.point(options.bgPos);
6676 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
6678 this._setIconStyles(div, 'icon');
6683 createShadow: function () {
6688 // @factory L.divIcon(options: DivIcon options)
6689 // Creates a `DivIcon` instance with the given options.
6690 L.divIcon = function (options) {
6691 return new L.DivIcon(options);
6700 * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
6703 // @namespace DivOverlay
6704 L.DivOverlay = L.Layer.extend({
6707 // @aka DivOverlay options
6709 // @option offset: Point = Point(0, 7)
6710 // The offset of the popup position. Useful to control the anchor
6711 // of the popup when opening it on some overlays.
6714 // @option className: String = ''
6715 // A custom CSS class name to assign to the popup.
6718 // @option pane: String = 'popupPane'
6719 // `Map pane` where the popup will be added.
6723 initialize: function (options, source) {
6724 L.setOptions(this, options);
6726 this._source = source;
6729 onAdd: function (map) {
6730 this._zoomAnimated = map._zoomAnimated;
6732 if (!this._container) {
6736 if (map._fadeAnimated) {
6737 L.DomUtil.setOpacity(this._container, 0);
6740 clearTimeout(this._removeTimeout);
6741 this.getPane().appendChild(this._container);
6744 if (map._fadeAnimated) {
6745 L.DomUtil.setOpacity(this._container, 1);
6748 this.bringToFront();
6751 onRemove: function (map) {
6752 if (map._fadeAnimated) {
6753 L.DomUtil.setOpacity(this._container, 0);
6754 this._removeTimeout = setTimeout(L.bind(L.DomUtil.remove, L.DomUtil, this._container), 200);
6756 L.DomUtil.remove(this._container);
6761 // @method getLatLng: LatLng
6762 // Returns the geographical point of popup.
6763 getLatLng: function () {
6764 return this._latlng;
6767 // @method setLatLng(latlng: LatLng): this
6768 // Sets the geographical point where the popup will open.
6769 setLatLng: function (latlng) {
6770 this._latlng = L.latLng(latlng);
6772 this._updatePosition();
6778 // @method getContent: String|HTMLElement
6779 // Returns the content of the popup.
6780 getContent: function () {
6781 return this._content;
6784 // @method setContent(htmlContent: String|HTMLElement|Function): this
6785 // 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.
6786 setContent: function (content) {
6787 this._content = content;
6792 // @method getElement: String|HTMLElement
6793 // Alias for [getContent()](#popup-getcontent)
6794 getElement: function () {
6795 return this._container;
6798 // @method update: null
6799 // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
6800 update: function () {
6801 if (!this._map) { return; }
6803 this._container.style.visibility = 'hidden';
6805 this._updateContent();
6806 this._updateLayout();
6807 this._updatePosition();
6809 this._container.style.visibility = '';
6814 getEvents: function () {
6816 zoom: this._updatePosition,
6817 viewreset: this._updatePosition
6820 if (this._zoomAnimated) {
6821 events.zoomanim = this._animateZoom;
6826 // @method isOpen: Boolean
6827 // Returns `true` when the popup is visible on the map.
6828 isOpen: function () {
6829 return !!this._map && this._map.hasLayer(this);
6832 // @method bringToFront: this
6833 // Brings this popup in front of other popups (in the same map pane).
6834 bringToFront: function () {
6836 L.DomUtil.toFront(this._container);
6841 // @method bringToBack: this
6842 // Brings this popup to the back of other popups (in the same map pane).
6843 bringToBack: function () {
6845 L.DomUtil.toBack(this._container);
6850 _updateContent: function () {
6851 if (!this._content) { return; }
6853 var node = this._contentNode;
6854 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
6856 if (typeof content === 'string') {
6857 node.innerHTML = content;
6859 while (node.hasChildNodes()) {
6860 node.removeChild(node.firstChild);
6862 node.appendChild(content);
6864 this.fire('contentupdate');
6867 _updatePosition: function () {
6868 if (!this._map) { return; }
6870 var pos = this._map.latLngToLayerPoint(this._latlng),
6871 offset = L.point(this.options.offset),
6872 anchor = this._getAnchor();
6874 if (this._zoomAnimated) {
6875 L.DomUtil.setPosition(this._container, pos.add(anchor));
6877 offset = offset.add(pos).add(anchor);
6880 var bottom = this._containerBottom = -offset.y,
6881 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
6883 // bottom position the popup in case the height of the popup changes (images loading etc)
6884 this._container.style.bottom = bottom + 'px';
6885 this._container.style.left = left + 'px';
6888 _getAnchor: function () {
6898 * @inherits DivOverlay
6900 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
6901 * open popups while making sure that only one popup is open at one time
6902 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
6906 * If you want to just bind a popup to marker click and then open it, it's really easy:
6909 * marker.bindPopup(popupContent).openPopup();
6911 * Path overlays like polylines also have a `bindPopup` method.
6912 * Here's a more complicated way to open a popup on a map:
6915 * var popup = L.popup()
6916 * .setLatLng(latlng)
6917 * .setContent('<p>Hello world!<br />This is a nice popup.</p>')
6924 L.Popup = L.DivOverlay.extend({
6927 // @aka Popup options
6929 // @option maxWidth: Number = 300
6930 // Max width of the popup, in pixels.
6933 // @option minWidth: Number = 50
6934 // Min width of the popup, in pixels.
6937 // @option maxHeight: Number = null
6938 // If set, creates a scrollable container of the given height
6939 // inside a popup if its content exceeds it.
6942 // @option autoPan: Boolean = true
6943 // Set it to `false` if you don't want the map to do panning animation
6944 // to fit the opened popup.
6947 // @option autoPanPaddingTopLeft: Point = null
6948 // The margin between the popup and the top left corner of the map
6949 // view after autopanning was performed.
6950 autoPanPaddingTopLeft: null,
6952 // @option autoPanPaddingBottomRight: Point = null
6953 // The margin between the popup and the bottom right corner of the map
6954 // view after autopanning was performed.
6955 autoPanPaddingBottomRight: null,
6957 // @option autoPanPadding: Point = Point(5, 5)
6958 // Equivalent of setting both top left and bottom right autopan padding to the same value.
6959 autoPanPadding: [5, 5],
6961 // @option keepInView: Boolean = false
6962 // Set it to `true` if you want to prevent users from panning the popup
6963 // off of the screen while it is open.
6966 // @option closeButton: Boolean = true
6967 // Controls the presence of a close button in the popup.
6970 // @option autoClose: Boolean = true
6971 // Set it to `false` if you want to override the default behavior of
6972 // the popup closing when user clicks the map (set globally by
6973 // the Map's [closePopupOnClick](#map-closepopuponclick) option).
6976 // @option className: String = ''
6977 // A custom CSS class name to assign to the popup.
6982 // @method openOn(map: Map): this
6983 // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
6984 openOn: function (map) {
6985 map.openPopup(this);
6989 onAdd: function (map) {
6990 L.DivOverlay.prototype.onAdd.call(this, map);
6993 // @section Popup events
6994 // @event popupopen: PopupEvent
6995 // Fired when a popup is opened in the map
6996 map.fire('popupopen', {popup: this});
7000 // @section Popup events
7001 // @event popupopen: PopupEvent
7002 // Fired when a popup bound to this layer is opened
7003 this._source.fire('popupopen', {popup: this}, true);
7004 // For non-path layers, we toggle the popup when clicking
7005 // again the layer, so prevent the map to reopen it.
7006 if (!(this._source instanceof L.Path)) {
7007 this._source.on('preclick', L.DomEvent.stopPropagation);
7012 onRemove: function (map) {
7013 L.DivOverlay.prototype.onRemove.call(this, map);
7016 // @section Popup events
7017 // @event popupclose: PopupEvent
7018 // Fired when a popup in the map is closed
7019 map.fire('popupclose', {popup: this});
7023 // @section Popup events
7024 // @event popupclose: PopupEvent
7025 // Fired when a popup bound to this layer is closed
7026 this._source.fire('popupclose', {popup: this}, true);
7027 if (!(this._source instanceof L.Path)) {
7028 this._source.off('preclick', L.DomEvent.stopPropagation);
7033 getEvents: function () {
7034 var events = L.DivOverlay.prototype.getEvents.call(this);
7036 if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
7037 events.preclick = this._close;
7040 if (this.options.keepInView) {
7041 events.moveend = this._adjustPan;
7047 _close: function () {
7049 this._map.closePopup(this);
7053 _initLayout: function () {
7054 var prefix = 'leaflet-popup',
7055 container = this._container = L.DomUtil.create('div',
7056 prefix + ' ' + (this.options.className || '') +
7057 ' leaflet-zoom-animated');
7059 if (this.options.closeButton) {
7060 var closeButton = this._closeButton = L.DomUtil.create('a', prefix + '-close-button', container);
7061 closeButton.href = '#close';
7062 closeButton.innerHTML = '×';
7064 L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);
7067 var wrapper = this._wrapper = L.DomUtil.create('div', prefix + '-content-wrapper', container);
7068 this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);
7071 .disableClickPropagation(wrapper)
7072 .disableScrollPropagation(this._contentNode)
7073 .on(wrapper, 'contextmenu', L.DomEvent.stopPropagation);
7075 this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container);
7076 this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);
7079 _updateLayout: function () {
7080 var container = this._contentNode,
7081 style = container.style;
7084 style.whiteSpace = 'nowrap';
7086 var width = container.offsetWidth;
7087 width = Math.min(width, this.options.maxWidth);
7088 width = Math.max(width, this.options.minWidth);
7090 style.width = (width + 1) + 'px';
7091 style.whiteSpace = '';
7095 var height = container.offsetHeight,
7096 maxHeight = this.options.maxHeight,
7097 scrolledClass = 'leaflet-popup-scrolled';
7099 if (maxHeight && height > maxHeight) {
7100 style.height = maxHeight + 'px';
7101 L.DomUtil.addClass(container, scrolledClass);
7103 L.DomUtil.removeClass(container, scrolledClass);
7106 this._containerWidth = this._container.offsetWidth;
7109 _animateZoom: function (e) {
7110 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
7111 anchor = this._getAnchor();
7112 L.DomUtil.setPosition(this._container, pos.add(anchor));
7115 _adjustPan: function () {
7116 if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; }
7118 var map = this._map,
7119 marginBottom = parseInt(L.DomUtil.getStyle(this._container, 'marginBottom'), 10) || 0,
7120 containerHeight = this._container.offsetHeight + marginBottom,
7121 containerWidth = this._containerWidth,
7122 layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);
7124 layerPos._add(L.DomUtil.getPosition(this._container));
7126 var containerPos = map.layerPointToContainerPoint(layerPos),
7127 padding = L.point(this.options.autoPanPadding),
7128 paddingTL = L.point(this.options.autoPanPaddingTopLeft || padding),
7129 paddingBR = L.point(this.options.autoPanPaddingBottomRight || padding),
7130 size = map.getSize(),
7134 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
7135 dx = containerPos.x + containerWidth - size.x + paddingBR.x;
7137 if (containerPos.x - dx - paddingTL.x < 0) { // left
7138 dx = containerPos.x - paddingTL.x;
7140 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
7141 dy = containerPos.y + containerHeight - size.y + paddingBR.y;
7143 if (containerPos.y - dy - paddingTL.y < 0) { // top
7144 dy = containerPos.y - paddingTL.y;
7148 // @section Popup events
7149 // @event autopanstart: Event
7150 // Fired when the map starts autopanning when opening a popup.
7153 .fire('autopanstart')
7158 _onCloseButtonClick: function (e) {
7163 _getAnchor: function () {
7164 // Where should we anchor the popup on the source layer?
7165 return L.point(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
7171 // @factory L.popup(options?: Popup options, source?: Layer)
7172 // 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.
7173 L.popup = function (options, source) {
7174 return new L.Popup(options, source);
7179 * @section Interaction Options
7180 * @option closePopupOnClick: Boolean = true
7181 * Set it to `false` if you don't want popups to close when user clicks the map.
7183 L.Map.mergeOptions({
7184 closePopupOnClick: true
7189 // @section Methods for Layers and Controls
7191 // @method openPopup(popup: Popup): this
7192 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
7194 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
7195 // Creates a popup with the specified content and options and opens it in the given point on a map.
7196 openPopup: function (popup, latlng, options) {
7197 if (!(popup instanceof L.Popup)) {
7198 popup = new L.Popup(options).setContent(popup);
7202 popup.setLatLng(latlng);
7205 if (this.hasLayer(popup)) {
7209 if (this._popup && this._popup.options.autoClose) {
7213 this._popup = popup;
7214 return this.addLayer(popup);
7217 // @method closePopup(popup?: Popup): this
7218 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
7219 closePopup: function (popup) {
7220 if (!popup || popup === this._popup) {
7221 popup = this._popup;
7225 this.removeLayer(popup);
7233 * @section Popup methods example
7235 * All layers share a set of methods convenient for binding popups to it.
7238 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
7239 * layer.openPopup();
7240 * layer.closePopup();
7243 * 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.
7246 // @section Popup methods
7249 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
7250 // Binds a popup to the layer with the passed `content` and sets up the
7251 // neccessary event listeners. If a `Function` is passed it will receive
7252 // the layer as the first argument and should return a `String` or `HTMLElement`.
7253 bindPopup: function (content, options) {
7255 if (content instanceof L.Popup) {
7256 L.setOptions(content, options);
7257 this._popup = content;
7258 content._source = this;
7260 if (!this._popup || options) {
7261 this._popup = new L.Popup(options, this);
7263 this._popup.setContent(content);
7266 if (!this._popupHandlersAdded) {
7268 click: this._openPopup,
7269 remove: this.closePopup,
7270 move: this._movePopup
7272 this._popupHandlersAdded = true;
7278 // @method unbindPopup(): this
7279 // Removes the popup previously bound with `bindPopup`.
7280 unbindPopup: function () {
7283 click: this._openPopup,
7284 remove: this.closePopup,
7285 move: this._movePopup
7287 this._popupHandlersAdded = false;
7293 // @method openPopup(latlng?: LatLng): this
7294 // Opens the bound popup at the specificed `latlng` or at the default popup anchor if no `latlng` is passed.
7295 openPopup: function (layer, latlng) {
7296 if (!(layer instanceof L.Layer)) {
7301 if (layer instanceof L.FeatureGroup) {
7302 for (var id in this._layers) {
7303 layer = this._layers[id];
7309 latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
7312 if (this._popup && this._map) {
7313 // set popup source to this layer
7314 this._popup._source = layer;
7316 // update the popup (content, layout, ect...)
7317 this._popup.update();
7319 // open the popup on the map
7320 this._map.openPopup(this._popup, latlng);
7326 // @method closePopup(): this
7327 // Closes the popup bound to this layer if it is open.
7328 closePopup: function () {
7330 this._popup._close();
7335 // @method togglePopup(): this
7336 // Opens or closes the popup bound to this layer depending on its current state.
7337 togglePopup: function (target) {
7339 if (this._popup._map) {
7342 this.openPopup(target);
7348 // @method isPopupOpen(): boolean
7349 // Returns `true` if the popup bound to this layer is currently open.
7350 isPopupOpen: function () {
7351 return this._popup.isOpen();
7354 // @method setPopupContent(content: String|HTMLElement|Popup): this
7355 // Sets the content of the popup bound to this layer.
7356 setPopupContent: function (content) {
7358 this._popup.setContent(content);
7363 // @method getPopup(): Popup
7364 // Returns the popup bound to this layer.
7365 getPopup: function () {
7369 _openPopup: function (e) {
7370 var layer = e.layer || e.target;
7380 // prevent map click
7383 // if this inherits from Path its a vector and we can just
7384 // open the popup at the new location
7385 if (layer instanceof L.Path) {
7386 this.openPopup(e.layer || e.target, e.latlng);
7390 // otherwise treat it like a marker and figure out
7391 // if we should toggle it open/closed
7392 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
7395 this.openPopup(layer, e.latlng);
7399 _movePopup: function (e) {
7400 this._popup.setLatLng(e.latlng);
7408 * @inherits DivOverlay
7410 * Used to display small texts on top of map layers.
7415 * marker.bindTooltip("my tooltip text").openTooltip();
7417 * Note about tooltip offset. Leaflet takes two options in consideration
7418 * for computing tooltip offseting:
7419 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
7420 * Add a positive x offset to move the tooltip to the right, and a positive y offset to
7421 * move it to the bottom. Negatives will move to the left and top.
7422 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
7423 * should adapt this value if you use a custom icon.
7427 // @namespace Tooltip
7428 L.Tooltip = L.DivOverlay.extend({
7431 // @aka Tooltip options
7433 // @option pane: String = 'tooltipPane'
7434 // `Map pane` where the tooltip will be added.
7435 pane: 'tooltipPane',
7437 // @option offset: Point = Point(0, 0)
7438 // Optional offset of the tooltip position.
7441 // @option direction: String = 'auto'
7442 // Direction where to open the tooltip. Possible values are: `right`, `left`,
7443 // `top`, `bottom`, `center`, `auto`.
7444 // `auto` will dynamicaly switch between `right` and `left` according to the tooltip
7445 // position on the map.
7448 // @option permanent: Boolean = false
7449 // Whether to open the tooltip permanently or only on mouseover.
7452 // @option sticky: Boolean = false
7453 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
7456 // @option interactive: Boolean = false
7457 // If true, the tooltip will listen to the feature events.
7460 // @option opacity: Number = 0.9
7461 // Tooltip container opacity.
7465 onAdd: function (map) {
7466 L.DivOverlay.prototype.onAdd.call(this, map);
7467 this.setOpacity(this.options.opacity);
7470 // @section Tooltip events
7471 // @event tooltipopen: TooltipEvent
7472 // Fired when a tooltip is opened in the map.
7473 map.fire('tooltipopen', {tooltip: this});
7477 // @section Tooltip events
7478 // @event tooltipopen: TooltipEvent
7479 // Fired when a tooltip bound to this layer is opened.
7480 this._source.fire('tooltipopen', {tooltip: this}, true);
7484 onRemove: function (map) {
7485 L.DivOverlay.prototype.onRemove.call(this, map);
7488 // @section Tooltip events
7489 // @event tooltipclose: TooltipEvent
7490 // Fired when a tooltip in the map is closed.
7491 map.fire('tooltipclose', {tooltip: this});
7495 // @section Tooltip events
7496 // @event tooltipclose: TooltipEvent
7497 // Fired when a tooltip bound to this layer is closed.
7498 this._source.fire('tooltipclose', {tooltip: this}, true);
7502 getEvents: function () {
7503 var events = L.DivOverlay.prototype.getEvents.call(this);
7505 if (L.Browser.touch && !this.options.permanent) {
7506 events.preclick = this._close;
7512 _close: function () {
7514 this._map.closeTooltip(this);
7518 _initLayout: function () {
7519 var prefix = 'leaflet-tooltip',
7520 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
7522 this._contentNode = this._container = L.DomUtil.create('div', className);
7525 _updateLayout: function () {},
7527 _adjustPan: function () {},
7529 _setPosition: function (pos) {
7530 var map = this._map,
7531 container = this._container,
7532 centerPoint = map.latLngToContainerPoint(map.getCenter()),
7533 tooltipPoint = map.layerPointToContainerPoint(pos),
7534 direction = this.options.direction,
7535 tooltipWidth = container.offsetWidth,
7536 tooltipHeight = container.offsetHeight,
7537 offset = L.point(this.options.offset),
7538 anchor = this._getAnchor();
7540 if (direction === 'top') {
7541 pos = pos.add(L.point(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true));
7542 } else if (direction === 'bottom') {
7543 pos = pos.subtract(L.point(tooltipWidth / 2 - offset.x, -offset.y, true));
7544 } else if (direction === 'center') {
7545 pos = pos.subtract(L.point(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true));
7546 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
7547 direction = 'right';
7548 pos = pos.add(L.point(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true));
7551 pos = pos.subtract(L.point(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true));
7554 L.DomUtil.removeClass(container, 'leaflet-tooltip-right');
7555 L.DomUtil.removeClass(container, 'leaflet-tooltip-left');
7556 L.DomUtil.removeClass(container, 'leaflet-tooltip-top');
7557 L.DomUtil.removeClass(container, 'leaflet-tooltip-bottom');
7558 L.DomUtil.addClass(container, 'leaflet-tooltip-' + direction);
7559 L.DomUtil.setPosition(container, pos);
7562 _updatePosition: function () {
7563 var pos = this._map.latLngToLayerPoint(this._latlng);
7564 this._setPosition(pos);
7567 setOpacity: function (opacity) {
7568 this.options.opacity = opacity;
7570 if (this._container) {
7571 L.DomUtil.setOpacity(this._container, opacity);
7575 _animateZoom: function (e) {
7576 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
7577 this._setPosition(pos);
7580 _getAnchor: function () {
7581 // Where should we anchor the tooltip on the source layer?
7582 return L.point(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
7587 // @namespace Tooltip
7588 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
7589 // 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.
7590 L.tooltip = function (options, source) {
7591 return new L.Tooltip(options, source);
7595 // @section Methods for Layers and Controls
7598 // @method openTooltip(tooltip: Tooltip): this
7599 // Opens the specified tooltip.
7601 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
7602 // Creates a tooltip with the specified content and options and open it.
7603 openTooltip: function (tooltip, latlng, options) {
7604 if (!(tooltip instanceof L.Tooltip)) {
7605 tooltip = new L.Tooltip(options).setContent(tooltip);
7609 tooltip.setLatLng(latlng);
7612 if (this.hasLayer(tooltip)) {
7616 return this.addLayer(tooltip);
7619 // @method closeTooltip(tooltip?: Tooltip): this
7620 // Closes the tooltip given as parameter.
7621 closeTooltip: function (tooltip) {
7623 this.removeLayer(tooltip);
7632 * @section Tooltip methods example
7634 * All layers share a set of methods convenient for binding tooltips to it.
7637 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
7638 * layer.openTooltip();
7639 * layer.closeTooltip();
7643 // @section Tooltip methods
7646 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
7647 // Binds a tooltip to the layer with the passed `content` and sets up the
7648 // neccessary event listeners. If a `Function` is passed it will receive
7649 // the layer as the first argument and should return a `String` or `HTMLElement`.
7650 bindTooltip: function (content, options) {
7652 if (content instanceof L.Tooltip) {
7653 L.setOptions(content, options);
7654 this._tooltip = content;
7655 content._source = this;
7657 if (!this._tooltip || options) {
7658 this._tooltip = L.tooltip(options, this);
7660 this._tooltip.setContent(content);
7664 this._initTooltipInteractions();
7666 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
7673 // @method unbindTooltip(): this
7674 // Removes the tooltip previously bound with `bindTooltip`.
7675 unbindTooltip: function () {
7676 if (this._tooltip) {
7677 this._initTooltipInteractions(true);
7678 this.closeTooltip();
7679 this._tooltip = null;
7684 _initTooltipInteractions: function (remove) {
7685 if (!remove && this._tooltipHandlersAdded) { return; }
7686 var onOff = remove ? 'off' : 'on',
7688 remove: this.closeTooltip,
7689 move: this._moveTooltip
7691 if (!this._tooltip.options.permanent) {
7692 events.mouseover = this._openTooltip;
7693 events.mouseout = this.closeTooltip;
7694 if (this._tooltip.options.sticky) {
7695 events.mousemove = this._moveTooltip;
7697 if (L.Browser.touch) {
7698 events.click = this._openTooltip;
7701 events.add = this._openTooltip;
7703 this[onOff](events);
7704 this._tooltipHandlersAdded = !remove;
7707 // @method openTooltip(latlng?: LatLng): this
7708 // Opens the bound tooltip at the specificed `latlng` or at the default tooltip anchor if no `latlng` is passed.
7709 openTooltip: function (layer, latlng) {
7710 if (!(layer instanceof L.Layer)) {
7715 if (layer instanceof L.FeatureGroup) {
7716 for (var id in this._layers) {
7717 layer = this._layers[id];
7723 latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
7726 if (this._tooltip && this._map) {
7728 // set tooltip source to this layer
7729 this._tooltip._source = layer;
7731 // update the tooltip (content, layout, ect...)
7732 this._tooltip.update();
7734 // open the tooltip on the map
7735 this._map.openTooltip(this._tooltip, latlng);
7737 // Tooltip container may not be defined if not permanent and never
7739 if (this._tooltip.options.interactive && this._tooltip._container) {
7740 L.DomUtil.addClass(this._tooltip._container, 'leaflet-clickable');
7741 this.addInteractiveTarget(this._tooltip._container);
7748 // @method closeTooltip(): this
7749 // Closes the tooltip bound to this layer if it is open.
7750 closeTooltip: function () {
7751 if (this._tooltip) {
7752 this._tooltip._close();
7753 if (this._tooltip.options.interactive && this._tooltip._container) {
7754 L.DomUtil.removeClass(this._tooltip._container, 'leaflet-clickable');
7755 this.removeInteractiveTarget(this._tooltip._container);
7761 // @method toggleTooltip(): this
7762 // Opens or closes the tooltip bound to this layer depending on its current state.
7763 toggleTooltip: function (target) {
7764 if (this._tooltip) {
7765 if (this._tooltip._map) {
7766 this.closeTooltip();
7768 this.openTooltip(target);
7774 // @method isTooltipOpen(): boolean
7775 // Returns `true` if the tooltip bound to this layer is currently open.
7776 isTooltipOpen: function () {
7777 return this._tooltip.isOpen();
7780 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
7781 // Sets the content of the tooltip bound to this layer.
7782 setTooltipContent: function (content) {
7783 if (this._tooltip) {
7784 this._tooltip.setContent(content);
7789 // @method getTooltip(): Tooltip
7790 // Returns the tooltip bound to this layer.
7791 getTooltip: function () {
7792 return this._tooltip;
7795 _openTooltip: function (e) {
7796 var layer = e.layer || e.target;
7798 if (!this._tooltip || !this._map) {
7801 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
7804 _moveTooltip: function (e) {
7805 var latlng = e.latlng, containerPoint, layerPoint;
7806 if (this._tooltip.options.sticky && e.originalEvent) {
7807 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
7808 layerPoint = this._map.containerPointToLayerPoint(containerPoint);
7809 latlng = this._map.layerPointToLatLng(layerPoint);
7811 this._tooltip.setLatLng(latlng);
7822 * Used to group several layers and handle them as one. If you add it to the map,
7823 * any layers added or removed from the group will be added/removed on the map as
7824 * well. Extends `Layer`.
7829 * L.layerGroup([marker1, marker2])
7830 * .addLayer(polyline)
7835 L.LayerGroup = L.Layer.extend({
7837 initialize: function (layers) {
7843 for (i = 0, len = layers.length; i < len; i++) {
7844 this.addLayer(layers[i]);
7849 // @method addLayer(layer: Layer): this
7850 // Adds the given layer to the group.
7851 addLayer: function (layer) {
7852 var id = this.getLayerId(layer);
7854 this._layers[id] = layer;
7857 this._map.addLayer(layer);
7863 // @method removeLayer(layer: Layer): this
7864 // Removes the given layer from the group.
7866 // @method removeLayer(id: Number): this
7867 // Removes the layer with the given internal ID from the group.
7868 removeLayer: function (layer) {
7869 var id = layer in this._layers ? layer : this.getLayerId(layer);
7871 if (this._map && this._layers[id]) {
7872 this._map.removeLayer(this._layers[id]);
7875 delete this._layers[id];
7880 // @method hasLayer(layer: Layer): Boolean
7881 // Returns `true` if the given layer is currently added to the group.
7882 hasLayer: function (layer) {
7883 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
7886 // @method clearLayers(): this
7887 // Removes all the layers from the group.
7888 clearLayers: function () {
7889 for (var i in this._layers) {
7890 this.removeLayer(this._layers[i]);
7895 // @method invoke(methodName: String, …): this
7896 // Calls `methodName` on every layer contained in this group, passing any
7897 // additional parameters. Has no effect if the layers contained do not
7898 // implement `methodName`.
7899 invoke: function (methodName) {
7900 var args = Array.prototype.slice.call(arguments, 1),
7903 for (i in this._layers) {
7904 layer = this._layers[i];
7906 if (layer[methodName]) {
7907 layer[methodName].apply(layer, args);
7914 onAdd: function (map) {
7915 for (var i in this._layers) {
7916 map.addLayer(this._layers[i]);
7920 onRemove: function (map) {
7921 for (var i in this._layers) {
7922 map.removeLayer(this._layers[i]);
7926 // @method eachLayer(fn: Function, context?: Object): this
7927 // Iterates over the layers of the group, optionally specifying context of the iterator function.
7929 // group.eachLayer(function (layer) {
7930 // layer.bindPopup('Hello');
7933 eachLayer: function (method, context) {
7934 for (var i in this._layers) {
7935 method.call(context, this._layers[i]);
7940 // @method getLayer(id: Number): Layer
7941 // Returns the layer with the given internal ID.
7942 getLayer: function (id) {
7943 return this._layers[id];
7946 // @method getLayers(): Layer[]
7947 // Returns an array of all the layers added to the group.
7948 getLayers: function () {
7951 for (var i in this._layers) {
7952 layers.push(this._layers[i]);
7957 // @method setZIndex(zIndex: Number): this
7958 // Calls `setZIndex` on every layer contained in this group, passing the z-index.
7959 setZIndex: function (zIndex) {
7960 return this.invoke('setZIndex', zIndex);
7963 // @method getLayerId(layer: Layer): Number
7964 // Returns the internal ID for a layer
7965 getLayerId: function (layer) {
7966 return L.stamp(layer);
7971 // @factory L.layerGroup(layers: Layer[])
7972 // Create a layer group, optionally given an initial set of layers.
7973 L.layerGroup = function (layers) {
7974 return new L.LayerGroup(layers);
7980 * @class FeatureGroup
7981 * @aka L.FeatureGroup
7982 * @inherits LayerGroup
7984 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
7985 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
7986 * * Events are propagated to the `FeatureGroup`, so if the group has an event
7987 * handler, it will handle events from any of the layers. This includes mouse events
7988 * and custom events.
7989 * * Has `layeradd` and `layerremove` events
7994 * L.featureGroup([marker1, marker2, polyline])
7995 * .bindPopup('Hello world!')
7996 * .on('click', function() { alert('Clicked on a member of the group!'); })
8001 L.FeatureGroup = L.LayerGroup.extend({
8003 addLayer: function (layer) {
8004 if (this.hasLayer(layer)) {
8008 layer.addEventParent(this);
8010 L.LayerGroup.prototype.addLayer.call(this, layer);
8012 // @event layeradd: LayerEvent
8013 // Fired when a layer is added to this `FeatureGroup`
8014 return this.fire('layeradd', {layer: layer});
8017 removeLayer: function (layer) {
8018 if (!this.hasLayer(layer)) {
8021 if (layer in this._layers) {
8022 layer = this._layers[layer];
8025 layer.removeEventParent(this);
8027 L.LayerGroup.prototype.removeLayer.call(this, layer);
8029 // @event layerremove: LayerEvent
8030 // Fired when a layer is removed from this `FeatureGroup`
8031 return this.fire('layerremove', {layer: layer});
8034 // @method setStyle(style: Path options): this
8035 // Sets the given path options to each layer of the group that has a `setStyle` method.
8036 setStyle: function (style) {
8037 return this.invoke('setStyle', style);
8040 // @method bringToFront(): this
8041 // Brings the layer group to the top of all other layers
8042 bringToFront: function () {
8043 return this.invoke('bringToFront');
8046 // @method bringToBack(): this
8047 // Brings the layer group to the top of all other layers
8048 bringToBack: function () {
8049 return this.invoke('bringToBack');
8052 // @method getBounds(): LatLngBounds
8053 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
8054 getBounds: function () {
8055 var bounds = new L.LatLngBounds();
8057 for (var id in this._layers) {
8058 var layer = this._layers[id];
8059 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
8065 // @factory L.featureGroup(layers: Layer[])
8066 // Create a feature group, optionally given an initial set of layers.
8067 L.featureGroup = function (layers) {
8068 return new L.FeatureGroup(layers);
8078 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
8079 * DOM container of the renderer, its bounds, and its zoom animation.
8081 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
8082 * itself can be added or removed to the map. All paths use a renderer, which can
8083 * be implicit (the map will decide the type of renderer and use it automatically)
8084 * or explicit (using the [`renderer`](#path-renderer) option of the path).
8086 * Do not use this class directly, use `SVG` and `Canvas` instead.
8088 * @event update: Event
8089 * Fired when the renderer updates its bounds, center and zoom, for example when
8093 L.Renderer = L.Layer.extend({
8096 // @aka Renderer options
8098 // @option padding: Number = 0.1
8099 // How much to extend the clip area around the map view (relative to its size)
8100 // e.g. 0.1 would be 10% of map view in each direction
8104 initialize: function (options) {
8105 L.setOptions(this, options);
8107 this._layers = this._layers || {};
8110 onAdd: function () {
8111 if (!this._container) {
8112 this._initContainer(); // defined by renderer implementations
8114 if (this._zoomAnimated) {
8115 L.DomUtil.addClass(this._container, 'leaflet-zoom-animated');
8119 this.getPane().appendChild(this._container);
8121 this.on('update', this._updatePaths, this);
8124 onRemove: function () {
8125 L.DomUtil.remove(this._container);
8126 this.off('update', this._updatePaths, this);
8129 getEvents: function () {
8131 viewreset: this._reset,
8133 moveend: this._update,
8134 zoomend: this._onZoomEnd
8136 if (this._zoomAnimated) {
8137 events.zoomanim = this._onAnimZoom;
8142 _onAnimZoom: function (ev) {
8143 this._updateTransform(ev.center, ev.zoom);
8146 _onZoom: function () {
8147 this._updateTransform(this._map.getCenter(), this._map.getZoom());
8150 _updateTransform: function (center, zoom) {
8151 var scale = this._map.getZoomScale(zoom, this._zoom),
8152 position = L.DomUtil.getPosition(this._container),
8153 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
8154 currentCenterPoint = this._map.project(this._center, zoom),
8155 destCenterPoint = this._map.project(center, zoom),
8156 centerOffset = destCenterPoint.subtract(currentCenterPoint),
8158 topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
8160 if (L.Browser.any3d) {
8161 L.DomUtil.setTransform(this._container, topLeftOffset, scale);
8163 L.DomUtil.setPosition(this._container, topLeftOffset);
8167 _reset: function () {
8169 this._updateTransform(this._center, this._zoom);
8171 for (var id in this._layers) {
8172 this._layers[id]._reset();
8176 _onZoomEnd: function () {
8177 for (var id in this._layers) {
8178 this._layers[id]._project();
8182 _updatePaths: function () {
8183 for (var id in this._layers) {
8184 this._layers[id]._update();
8188 _update: function () {
8189 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
8190 // Subclasses are responsible of firing the 'update' event.
8191 var p = this.options.padding,
8192 size = this._map.getSize(),
8193 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
8195 this._bounds = new L.Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
8197 this._center = this._map.getCenter();
8198 this._zoom = this._map.getZoom();
8204 // @namespace Map; @method getRenderer(layer: Path): Renderer
8205 // Returns the instance of `Renderer` that should be used to render the given
8206 // `Path`. It will ensure that the `renderer` options of the map and paths
8207 // are respected, and that the renderers do exist on the map.
8208 getRenderer: function (layer) {
8209 // @namespace Path; @option renderer: Renderer
8210 // Use this specific instance of `Renderer` for this path. Takes
8211 // precedence over the map's [default renderer](#map-renderer).
8212 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
8215 // @namespace Map; @option preferCanvas: Boolean = false
8216 // Whether `Path`s should be rendered on a `Canvas` renderer.
8217 // By default, all `Path`s are rendered in a `SVG` renderer.
8218 renderer = this._renderer = (this.options.preferCanvas && L.canvas()) || L.svg();
8221 if (!this.hasLayer(renderer)) {
8222 this.addLayer(renderer);
8227 _getPaneRenderer: function (name) {
8228 if (name === 'overlayPane' || name === undefined) {
8232 var renderer = this._paneRenderers[name];
8233 if (renderer === undefined) {
8234 renderer = (L.SVG && L.svg({pane: name})) || (L.Canvas && L.canvas({pane: name}));
8235 this._paneRenderers[name] = renderer;
8246 * @inherits Interactive layer
8248 * An abstract class that contains options and constants shared between vector
8249 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
8252 L.Path = L.Layer.extend({
8255 // @aka Path options
8257 // @option stroke: Boolean = true
8258 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
8261 // @option color: String = '#3388ff'
8265 // @option weight: Number = 3
8266 // Stroke width in pixels
8269 // @option opacity: Number = 1.0
8273 // @option lineCap: String= 'round'
8274 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
8277 // @option lineJoin: String = 'round'
8278 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
8281 // @option dashArray: String = null
8282 // 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).
8285 // @option dashOffset: String = null
8286 // 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).
8289 // @option fill: Boolean = depends
8290 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
8293 // @option fillColor: String = *
8294 // Fill color. Defaults to the value of the [`color`](#path-color) option
8297 // @option fillOpacity: Number = 0.2
8301 // @option fillRule: String = 'evenodd'
8302 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
8303 fillRule: 'evenodd',
8307 // Option inherited from "Interactive layer" abstract class
8311 beforeAdd: function (map) {
8312 // Renderer is set here because we need to call renderer.getEvents
8313 // before this.getEvents.
8314 this._renderer = map.getRenderer(this);
8317 onAdd: function () {
8318 this._renderer._initPath(this);
8320 this._renderer._addPath(this);
8323 onRemove: function () {
8324 this._renderer._removePath(this);
8327 // @method redraw(): this
8328 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
8329 redraw: function () {
8331 this._renderer._updatePath(this);
8336 // @method setStyle(style: Path options): this
8337 // Changes the appearance of a Path based on the options in the `Path options` object.
8338 setStyle: function (style) {
8339 L.setOptions(this, style);
8340 if (this._renderer) {
8341 this._renderer._updateStyle(this);
8346 // @method bringToFront(): this
8347 // Brings the layer to the top of all path layers.
8348 bringToFront: function () {
8349 if (this._renderer) {
8350 this._renderer._bringToFront(this);
8355 // @method bringToBack(): this
8356 // Brings the layer to the bottom of all path layers.
8357 bringToBack: function () {
8358 if (this._renderer) {
8359 this._renderer._bringToBack(this);
8364 getElement: function () {
8368 _reset: function () {
8369 // defined in children classes
8374 _clickTolerance: function () {
8375 // used when doing hit detection for Canvas layers
8376 return (this.options.stroke ? this.options.weight / 2 : 0) + (L.Browser.touch ? 10 : 0);
8383 * @namespace LineUtil
8385 * Various utility functions for polyine points processing, used by Leaflet internally to make polylines lightning-fast.
8390 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
8391 // Improves rendering performance dramatically by lessening the number of points to draw.
8393 // @function simplify(points: Point[], tolerance: Number): Point[]
8394 // Dramatically reduces the number of points in a polyline while retaining
8395 // its shape and returns a new array of simplified points, using the
8396 // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
8397 // Used for a huge performance boost when processing/displaying Leaflet polylines for
8398 // each zoom level and also reducing visual noise. tolerance affects the amount of
8399 // simplification (lesser value means higher quality but slower and with more points).
8400 // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
8401 simplify: function (points, tolerance) {
8402 if (!tolerance || !points.length) {
8403 return points.slice();
8406 var sqTolerance = tolerance * tolerance;
8408 // stage 1: vertex reduction
8409 points = this._reducePoints(points, sqTolerance);
8411 // stage 2: Douglas-Peucker simplification
8412 points = this._simplifyDP(points, sqTolerance);
8417 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
8418 // Returns the distance between point `p` and segment `p1` to `p2`.
8419 pointToSegmentDistance: function (p, p1, p2) {
8420 return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));
8423 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
8424 // Returns the closest point from a point `p` on a segment `p1` to `p2`.
8425 closestPointOnSegment: function (p, p1, p2) {
8426 return this._sqClosestPointOnSegment(p, p1, p2);
8429 // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
8430 _simplifyDP: function (points, sqTolerance) {
8432 var len = points.length,
8433 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
8434 markers = new ArrayConstructor(len);
8436 markers[0] = markers[len - 1] = 1;
8438 this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
8443 for (i = 0; i < len; i++) {
8445 newPoints.push(points[i]);
8452 _simplifyDPStep: function (points, markers, sqTolerance, first, last) {
8457 for (i = first + 1; i <= last - 1; i++) {
8458 sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);
8460 if (sqDist > maxSqDist) {
8466 if (maxSqDist > sqTolerance) {
8469 this._simplifyDPStep(points, markers, sqTolerance, first, index);
8470 this._simplifyDPStep(points, markers, sqTolerance, index, last);
8474 // reduce points that are too close to each other to a single point
8475 _reducePoints: function (points, sqTolerance) {
8476 var reducedPoints = [points[0]];
8478 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
8479 if (this._sqDist(points[i], points[prev]) > sqTolerance) {
8480 reducedPoints.push(points[i]);
8484 if (prev < len - 1) {
8485 reducedPoints.push(points[len - 1]);
8487 return reducedPoints;
8491 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
8492 // Clips the segment a to b by rectangular bounds with the
8493 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
8494 // (modifying the segment points directly!). Used by Leaflet to only show polyline
8495 // points that are on the screen or near, increasing performance.
8496 clipSegment: function (a, b, bounds, useLastCode, round) {
8497 var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
8498 codeB = this._getBitCode(b, bounds),
8500 codeOut, p, newCode;
8502 // save 2nd code to avoid calculating it on the next segment
8503 this._lastCode = codeB;
8506 // if a,b is inside the clip window (trivial accept)
8507 if (!(codeA | codeB)) {
8511 // if a,b is outside the clip window (trivial reject)
8512 if (codeA & codeB) {
8517 codeOut = codeA || codeB;
8518 p = this._getEdgeIntersection(a, b, codeOut, bounds, round);
8519 newCode = this._getBitCode(p, bounds);
8521 if (codeOut === codeA) {
8531 _getEdgeIntersection: function (a, b, code, bounds, round) {
8538 if (code & 8) { // top
8539 x = a.x + dx * (max.y - a.y) / dy;
8542 } else if (code & 4) { // bottom
8543 x = a.x + dx * (min.y - a.y) / dy;
8546 } else if (code & 2) { // right
8548 y = a.y + dy * (max.x - a.x) / dx;
8550 } else if (code & 1) { // left
8552 y = a.y + dy * (min.x - a.x) / dx;
8555 return new L.Point(x, y, round);
8558 _getBitCode: function (p, bounds) {
8561 if (p.x < bounds.min.x) { // left
8563 } else if (p.x > bounds.max.x) { // right
8567 if (p.y < bounds.min.y) { // bottom
8569 } else if (p.y > bounds.max.y) { // top
8576 // square distance (to avoid unnecessary Math.sqrt calls)
8577 _sqDist: function (p1, p2) {
8578 var dx = p2.x - p1.x,
8580 return dx * dx + dy * dy;
8583 // return closest point on segment or distance to that point
8584 _sqClosestPointOnSegment: function (p, p1, p2, sqDist) {
8589 dot = dx * dx + dy * dy,
8593 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
8607 return sqDist ? dx * dx + dy * dy : new L.Point(x, y);
8618 * A class for drawing polyline overlays on a map. Extends `Path`.
8623 * // create a red polyline from an array of LatLng points
8630 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
8632 * // zoom the map to the polyline
8633 * map.fitBounds(polyline.getBounds());
8636 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
8639 * // create a red polyline from an array of arrays of LatLng points
8641 * [[-122.68, 45.51],
8651 L.Polyline = L.Path.extend({
8654 // @aka Polyline options
8656 // @option smoothFactor: Number = 1.0
8657 // How much to simplify the polyline on each zoom level. More means
8658 // better performance and smoother look, and less means more accurate representation.
8661 // @option noClip: Boolean = false
8662 // Disable polyline clipping.
8666 initialize: function (latlngs, options) {
8667 L.setOptions(this, options);
8668 this._setLatLngs(latlngs);
8671 // @method getLatLngs(): LatLng[]
8672 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8673 getLatLngs: function () {
8674 return this._latlngs;
8677 // @method setLatLngs(latlngs: LatLng[]): this
8678 // Replaces all the points in the polyline with the given array of geographical points.
8679 setLatLngs: function (latlngs) {
8680 this._setLatLngs(latlngs);
8681 return this.redraw();
8684 // @method isEmpty(): Boolean
8685 // Returns `true` if the Polyline has no LatLngs.
8686 isEmpty: function () {
8687 return !this._latlngs.length;
8690 closestLayerPoint: function (p) {
8691 var minDistance = Infinity,
8693 closest = L.LineUtil._sqClosestPointOnSegment,
8696 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8697 var points = this._parts[j];
8699 for (var i = 1, len = points.length; i < len; i++) {
8703 var sqDist = closest(p, p1, p2, true);
8705 if (sqDist < minDistance) {
8706 minDistance = sqDist;
8707 minPoint = closest(p, p1, p2);
8712 minPoint.distance = Math.sqrt(minDistance);
8717 // @method getCenter(): LatLng
8718 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
8719 getCenter: function () {
8720 // throws error when not yet added to map as this center calculation requires projected coordinates
8722 throw new Error('Must add layer to map before using getCenter()');
8725 var i, halfDist, segDist, dist, p1, p2, ratio,
8726 points = this._rings[0],
8727 len = points.length;
8729 if (!len) { return null; }
8731 // polyline centroid algorithm; only uses the first ring if there are multiple
8733 for (i = 0, halfDist = 0; i < len - 1; i++) {
8734 halfDist += points[i].distanceTo(points[i + 1]) / 2;
8737 // The line is so small in the current view that all points are on the same pixel.
8738 if (halfDist === 0) {
8739 return this._map.layerPointToLatLng(points[0]);
8742 for (i = 0, dist = 0; i < len - 1; i++) {
8745 segDist = p1.distanceTo(p2);
8748 if (dist > halfDist) {
8749 ratio = (dist - halfDist) / segDist;
8750 return this._map.layerPointToLatLng([
8751 p2.x - ratio * (p2.x - p1.x),
8752 p2.y - ratio * (p2.y - p1.y)
8758 // @method getBounds(): LatLngBounds
8759 // Returns the `LatLngBounds` of the path.
8760 getBounds: function () {
8761 return this._bounds;
8764 // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
8765 // Adds a given point to the polyline. By default, adds to the first ring of
8766 // the polyline in case of a multi-polyline, but can be overridden by passing
8767 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8768 addLatLng: function (latlng, latlngs) {
8769 latlngs = latlngs || this._defaultShape();
8770 latlng = L.latLng(latlng);
8771 latlngs.push(latlng);
8772 this._bounds.extend(latlng);
8773 return this.redraw();
8776 _setLatLngs: function (latlngs) {
8777 this._bounds = new L.LatLngBounds();
8778 this._latlngs = this._convertLatLngs(latlngs);
8781 _defaultShape: function () {
8782 return L.Polyline._flat(this._latlngs) ? this._latlngs : this._latlngs[0];
8785 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8786 _convertLatLngs: function (latlngs) {
8788 flat = L.Polyline._flat(latlngs);
8790 for (var i = 0, len = latlngs.length; i < len; i++) {
8792 result[i] = L.latLng(latlngs[i]);
8793 this._bounds.extend(result[i]);
8795 result[i] = this._convertLatLngs(latlngs[i]);
8802 _project: function () {
8803 var pxBounds = new L.Bounds();
8805 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8807 var w = this._clickTolerance(),
8808 p = new L.Point(w, w);
8810 if (this._bounds.isValid() && pxBounds.isValid()) {
8811 pxBounds.min._subtract(p);
8812 pxBounds.max._add(p);
8813 this._pxBounds = pxBounds;
8817 // recursively turns latlngs into a set of rings with projected coordinates
8818 _projectLatlngs: function (latlngs, result, projectedBounds) {
8819 var flat = latlngs[0] instanceof L.LatLng,
8820 len = latlngs.length,
8825 for (i = 0; i < len; i++) {
8826 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8827 projectedBounds.extend(ring[i]);
8831 for (i = 0; i < len; i++) {
8832 this._projectLatlngs(latlngs[i], result, projectedBounds);
8837 // clip polyline by renderer bounds so that we have less to render for performance
8838 _clipPoints: function () {
8839 var bounds = this._renderer._bounds;
8842 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8846 if (this.options.noClip) {
8847 this._parts = this._rings;
8851 var parts = this._parts,
8852 i, j, k, len, len2, segment, points;
8854 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8855 points = this._rings[i];
8857 for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8858 segment = L.LineUtil.clipSegment(points[j], points[j + 1], bounds, j, true);
8860 if (!segment) { continue; }
8862 parts[k] = parts[k] || [];
8863 parts[k].push(segment[0]);
8865 // if segment goes out of screen, or it's the last one, it's the end of the line part
8866 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8867 parts[k].push(segment[1]);
8874 // simplify each clipped part of the polyline for performance
8875 _simplifyPoints: function () {
8876 var parts = this._parts,
8877 tolerance = this.options.smoothFactor;
8879 for (var i = 0, len = parts.length; i < len; i++) {
8880 parts[i] = L.LineUtil.simplify(parts[i], tolerance);
8884 _update: function () {
8885 if (!this._map) { return; }
8888 this._simplifyPoints();
8892 _updatePath: function () {
8893 this._renderer._updatePoly(this);
8897 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8898 // Instantiates a polyline object given an array of geographical points and
8899 // optionally an options object. You can create a `Polyline` object with
8900 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8901 // of geographic points.
8902 L.polyline = function (latlngs, options) {
8903 return new L.Polyline(latlngs, options);
8906 L.Polyline._flat = function (latlngs) {
8907 // true if it's a flat array of latlngs; false if nested
8908 return !L.Util.isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
8914 * @namespace PolyUtil
8915 * Various utility functions for polygon geometries.
8920 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
8921 * 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)).
8922 * Used by Leaflet to only show polygon points that are on the screen or near, increasing
8923 * performance. Note that polygon points needs different algorithm for clipping
8924 * than polyline, so there's a seperate method for it.
8926 L.PolyUtil.clipPolygon = function (points, bounds, round) {
8928 edges = [1, 4, 2, 8],
8934 for (i = 0, len = points.length; i < len; i++) {
8935 points[i]._code = lu._getBitCode(points[i], bounds);
8938 // for each edge (left, bottom, right, top)
8939 for (k = 0; k < 4; k++) {
8943 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
8947 // if a is inside the clip window
8948 if (!(a._code & edge)) {
8949 // if b is outside the clip window (a->b goes out of screen)
8950 if (b._code & edge) {
8951 p = lu._getEdgeIntersection(b, a, edge, bounds, round);
8952 p._code = lu._getBitCode(p, bounds);
8953 clippedPoints.push(p);
8955 clippedPoints.push(a);
8957 // else if b is inside the clip window (a->b enters the screen)
8958 } else if (!(b._code & edge)) {
8959 p = lu._getEdgeIntersection(b, a, edge, bounds, round);
8960 p._code = lu._getBitCode(p, bounds);
8961 clippedPoints.push(p);
8964 points = clippedPoints;
8975 * @inherits Polyline
8977 * A class for drawing polygon overlays on a map. Extends `Polyline`.
8979 * 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.
8985 * // create a red polygon from an array of LatLng points
8986 * var latlngs = [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]];
8988 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8990 * // zoom the map to the polygon
8991 * map.fitBounds(polygon.getBounds());
8994 * 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:
8998 * [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]], // outer ring
8999 * [[-108.58,37.29],[-108.58,40.71],[-102.50,40.71],[-102.50,37.29]] // hole
9003 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
9007 * [ // first polygon
9008 * [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]], // outer ring
9009 * [[-108.58,37.29],[-108.58,40.71],[-102.50,40.71],[-102.50,37.29]] // hole
9011 * [ // second polygon
9012 * [[-109.05, 37],[-109.03, 41],[-102.05, 41],[-102.04, 37],[-109.05, 38]]
9018 L.Polygon = L.Polyline.extend({
9024 isEmpty: function () {
9025 return !this._latlngs.length || !this._latlngs[0].length;
9028 getCenter: function () {
9029 // throws error when not yet added to map as this center calculation requires projected coordinates
9031 throw new Error('Must add layer to map before using getCenter()');
9034 var i, j, p1, p2, f, area, x, y, center,
9035 points = this._rings[0],
9036 len = points.length;
9038 if (!len) { return null; }
9040 // polygon centroid algorithm; only uses the first ring if there are multiple
9044 for (i = 0, j = len - 1; i < len; j = i++) {
9048 f = p1.y * p2.x - p2.y * p1.x;
9049 x += (p1.x + p2.x) * f;
9050 y += (p1.y + p2.y) * f;
9055 // Polygon is so small that all points are on same pixel.
9058 center = [x / area, y / area];
9060 return this._map.layerPointToLatLng(center);
9063 _convertLatLngs: function (latlngs) {
9064 var result = L.Polyline.prototype._convertLatLngs.call(this, latlngs),
9065 len = result.length;
9067 // remove last point if it equals first one
9068 if (len >= 2 && result[0] instanceof L.LatLng && result[0].equals(result[len - 1])) {
9074 _setLatLngs: function (latlngs) {
9075 L.Polyline.prototype._setLatLngs.call(this, latlngs);
9076 if (L.Polyline._flat(this._latlngs)) {
9077 this._latlngs = [this._latlngs];
9081 _defaultShape: function () {
9082 return L.Polyline._flat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
9085 _clipPoints: function () {
9086 // polygons need a different clipping algorithm so we redefine that
9088 var bounds = this._renderer._bounds,
9089 w = this.options.weight,
9090 p = new L.Point(w, w);
9092 // increase clip padding by stroke width to avoid stroke on clip edges
9093 bounds = new L.Bounds(bounds.min.subtract(p), bounds.max.add(p));
9096 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
9100 if (this.options.noClip) {
9101 this._parts = this._rings;
9105 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
9106 clipped = L.PolyUtil.clipPolygon(this._rings[i], bounds, true);
9107 if (clipped.length) {
9108 this._parts.push(clipped);
9113 _updatePath: function () {
9114 this._renderer._updatePoly(this, true);
9119 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
9120 L.polygon = function (latlngs, options) {
9121 return new L.Polygon(latlngs, options);
9127 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
9135 * A class for drawing rectangle overlays on a map. Extends `Polygon`.
9140 * // define rectangle geographical bounds
9141 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
9143 * // create an orange rectangle
9144 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
9146 * // zoom the map to the rectangle bounds
9147 * map.fitBounds(bounds);
9153 L.Rectangle = L.Polygon.extend({
9154 initialize: function (latLngBounds, options) {
9155 L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
9158 // @method setBounds(latLngBounds: LatLngBounds): this
9159 // Redraws the rectangle with the passed bounds.
9160 setBounds: function (latLngBounds) {
9161 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
9164 _boundsToLatLngs: function (latLngBounds) {
9165 latLngBounds = L.latLngBounds(latLngBounds);
9167 latLngBounds.getSouthWest(),
9168 latLngBounds.getNorthWest(),
9169 latLngBounds.getNorthEast(),
9170 latLngBounds.getSouthEast()
9176 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
9177 L.rectangle = function (latLngBounds, options) {
9178 return new L.Rectangle(latLngBounds, options);
9184 * @class CircleMarker
9185 * @aka L.CircleMarker
9188 * A circle of a fixed size with radius specified in pixels. Extends `Path`.
9191 L.CircleMarker = L.Path.extend({
9194 // @aka CircleMarker options
9198 // @option radius: Number = 10
9199 // Radius of the circle marker, in pixels
9203 initialize: function (latlng, options) {
9204 L.setOptions(this, options);
9205 this._latlng = L.latLng(latlng);
9206 this._radius = this.options.radius;
9209 // @method setLatLng(latLng: LatLng): this
9210 // Sets the position of a circle marker to a new location.
9211 setLatLng: function (latlng) {
9212 this._latlng = L.latLng(latlng);
9214 return this.fire('move', {latlng: this._latlng});
9217 // @method getLatLng(): LatLng
9218 // Returns the current geographical position of the circle marker
9219 getLatLng: function () {
9220 return this._latlng;
9223 // @method setRadius(radius: Number): this
9224 // Sets the radius of a circle marker. Units are in pixels.
9225 setRadius: function (radius) {
9226 this.options.radius = this._radius = radius;
9227 return this.redraw();
9230 // @method getRadius(): Number
9231 // Returns the current radius of the circle
9232 getRadius: function () {
9233 return this._radius;
9236 setStyle : function (options) {
9237 var radius = options && options.radius || this._radius;
9238 L.Path.prototype.setStyle.call(this, options);
9239 this.setRadius(radius);
9243 _project: function () {
9244 this._point = this._map.latLngToLayerPoint(this._latlng);
9245 this._updateBounds();
9248 _updateBounds: function () {
9249 var r = this._radius,
9250 r2 = this._radiusY || r,
9251 w = this._clickTolerance(),
9252 p = [r + w, r2 + w];
9253 this._pxBounds = new L.Bounds(this._point.subtract(p), this._point.add(p));
9256 _update: function () {
9262 _updatePath: function () {
9263 this._renderer._updateCircle(this);
9266 _empty: function () {
9267 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
9272 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
9273 // Instantiates a circle marker object given a geographical point, and an optional options object.
9274 L.circleMarker = function (latlng, options) {
9275 return new L.CircleMarker(latlng, options);
9283 * @inherits CircleMarker
9285 * A class for drawing circle overlays on a map. Extends `CircleMarker`.
9287 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
9292 * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
9296 L.Circle = L.CircleMarker.extend({
9298 initialize: function (latlng, options, legacyOptions) {
9299 if (typeof options === 'number') {
9300 // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
9301 options = L.extend({}, legacyOptions, {radius: options});
9303 L.setOptions(this, options);
9304 this._latlng = L.latLng(latlng);
9306 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
9309 // @aka Circle options
9310 // @option radius: Number; Radius of the circle, in meters.
9311 this._mRadius = this.options.radius;
9314 // @method setRadius(radius: Number): this
9315 // Sets the radius of a circle. Units are in meters.
9316 setRadius: function (radius) {
9317 this._mRadius = radius;
9318 return this.redraw();
9321 // @method getRadius(): Number
9322 // Returns the current radius of a circle. Units are in meters.
9323 getRadius: function () {
9324 return this._mRadius;
9327 // @method getBounds(): LatLngBounds
9328 // Returns the `LatLngBounds` of the path.
9329 getBounds: function () {
9330 var half = [this._radius, this._radiusY || this._radius];
9332 return new L.LatLngBounds(
9333 this._map.layerPointToLatLng(this._point.subtract(half)),
9334 this._map.layerPointToLatLng(this._point.add(half)));
9337 setStyle: L.Path.prototype.setStyle,
9339 _project: function () {
9341 var lng = this._latlng.lng,
9342 lat = this._latlng.lat,
9344 crs = map.options.crs;
9346 if (crs.distance === L.CRS.Earth.distance) {
9347 var d = Math.PI / 180,
9348 latR = (this._mRadius / L.CRS.Earth.R) / d,
9349 top = map.project([lat + latR, lng]),
9350 bottom = map.project([lat - latR, lng]),
9351 p = top.add(bottom).divideBy(2),
9352 lat2 = map.unproject(p).lat,
9353 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
9354 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
9356 if (isNaN(lngR) || lngR === 0) {
9357 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
9360 this._point = p.subtract(map.getPixelOrigin());
9361 this._radius = isNaN(lngR) ? 0 : Math.max(Math.round(p.x - map.project([lat2, lng - lngR]).x), 1);
9362 this._radiusY = Math.max(Math.round(p.y - top.y), 1);
9365 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
9367 this._point = map.latLngToLayerPoint(this._latlng);
9368 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
9371 this._updateBounds();
9375 // @factory L.circle(latlng: LatLng, options?: Circle options)
9376 // Instantiates a circle object given a geographical point, and an options object
9377 // which contains the circle radius.
9379 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
9380 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
9381 // Do not use in new applications or plugins.
9382 L.circle = function (latlng, options, legacyOptions) {
9383 return new L.Circle(latlng, options, legacyOptions);
9390 * @inherits Renderer
9393 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
9394 * Inherits `Renderer`.
9396 * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
9397 * available in all web browsers, notably Android 2.x and 3.x.
9399 * Although SVG is not available on IE7 and IE8, these browsers support
9400 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
9401 * (a now deprecated technology), and the SVG renderer will fall back to VML in
9406 * Use SVG by default for all paths in the map:
9409 * var map = L.map('map', {
9414 * Use a SVG renderer with extra padding for specific vector geometries:
9417 * var map = L.map('map');
9418 * var myRenderer = L.svg({ padding: 0.5 });
9419 * var line = L.polyline( coordinates, { renderer: myRenderer } );
9420 * var circle = L.circle( center, { renderer: myRenderer } );
9424 L.SVG = L.Renderer.extend({
9426 getEvents: function () {
9427 var events = L.Renderer.prototype.getEvents.call(this);
9428 events.zoomstart = this._onZoomStart;
9432 _initContainer: function () {
9433 this._container = L.SVG.create('svg');
9435 // makes it possible to click through svg root; we'll reset it back in individual paths
9436 this._container.setAttribute('pointer-events', 'none');
9438 this._rootGroup = L.SVG.create('g');
9439 this._container.appendChild(this._rootGroup);
9442 _onZoomStart: function () {
9443 // Drag-then-pinch interactions might mess up the center and zoom.
9444 // In this case, the easiest way to prevent this is re-do the renderer
9445 // bounds and padding when the zooming starts.
9449 _update: function () {
9450 if (this._map._animatingZoom && this._bounds) { return; }
9452 L.Renderer.prototype._update.call(this);
9454 var b = this._bounds,
9456 container = this._container;
9458 // set size of svg-container if changed
9459 if (!this._svgSize || !this._svgSize.equals(size)) {
9460 this._svgSize = size;
9461 container.setAttribute('width', size.x);
9462 container.setAttribute('height', size.y);
9465 // movement: update container viewBox so that we don't have to change coordinates of individual layers
9466 L.DomUtil.setPosition(container, b.min);
9467 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
9469 this.fire('update');
9472 // methods below are called by vector layers implementations
9474 _initPath: function (layer) {
9475 var path = layer._path = L.SVG.create('path');
9478 // @option className: String = null
9479 // Custom class name set on an element. Only for SVG renderer.
9480 if (layer.options.className) {
9481 L.DomUtil.addClass(path, layer.options.className);
9484 if (layer.options.interactive) {
9485 L.DomUtil.addClass(path, 'leaflet-interactive');
9488 this._updateStyle(layer);
9489 this._layers[L.stamp(layer)] = layer;
9492 _addPath: function (layer) {
9493 this._rootGroup.appendChild(layer._path);
9494 layer.addInteractiveTarget(layer._path);
9497 _removePath: function (layer) {
9498 L.DomUtil.remove(layer._path);
9499 layer.removeInteractiveTarget(layer._path);
9500 delete this._layers[L.stamp(layer)];
9503 _updatePath: function (layer) {
9508 _updateStyle: function (layer) {
9509 var path = layer._path,
9510 options = layer.options;
9512 if (!path) { return; }
9514 if (options.stroke) {
9515 path.setAttribute('stroke', options.color);
9516 path.setAttribute('stroke-opacity', options.opacity);
9517 path.setAttribute('stroke-width', options.weight);
9518 path.setAttribute('stroke-linecap', options.lineCap);
9519 path.setAttribute('stroke-linejoin', options.lineJoin);
9521 if (options.dashArray) {
9522 path.setAttribute('stroke-dasharray', options.dashArray);
9524 path.removeAttribute('stroke-dasharray');
9527 if (options.dashOffset) {
9528 path.setAttribute('stroke-dashoffset', options.dashOffset);
9530 path.removeAttribute('stroke-dashoffset');
9533 path.setAttribute('stroke', 'none');
9537 path.setAttribute('fill', options.fillColor || options.color);
9538 path.setAttribute('fill-opacity', options.fillOpacity);
9539 path.setAttribute('fill-rule', options.fillRule || 'evenodd');
9541 path.setAttribute('fill', 'none');
9545 _updatePoly: function (layer, closed) {
9546 this._setPath(layer, L.SVG.pointsToPath(layer._parts, closed));
9549 _updateCircle: function (layer) {
9550 var p = layer._point,
9552 r2 = layer._radiusY || r,
9553 arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
9555 // drawing a circle with two half-arcs
9556 var d = layer._empty() ? 'M0 0' :
9557 'M' + (p.x - r) + ',' + p.y +
9558 arc + (r * 2) + ',0 ' +
9559 arc + (-r * 2) + ',0 ';
9561 this._setPath(layer, d);
9564 _setPath: function (layer, path) {
9565 layer._path.setAttribute('d', path);
9568 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
9569 _bringToFront: function (layer) {
9570 L.DomUtil.toFront(layer._path);
9573 _bringToBack: function (layer) {
9574 L.DomUtil.toBack(layer._path);
9579 // @namespace SVG; @section
9580 // There are several static functions which can be called without instantiating L.SVG:
9582 // @function create(name: String): SVGElement
9583 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
9584 // corresponding to the class name passed. For example, using 'line' will return
9585 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
9586 create: function (name) {
9587 return document.createElementNS('http://www.w3.org/2000/svg', name);
9590 // @function pointsToPath(rings: Point[], closed: Boolean): String
9591 // Generates a SVG path string for multiple rings, with each ring turning
9592 // into "M..L..L.." instructions
9593 pointsToPath: function (rings, closed) {
9595 i, j, len, len2, points, p;
9597 for (i = 0, len = rings.length; i < len; i++) {
9600 for (j = 0, len2 = points.length; j < len2; j++) {
9602 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
9605 // closes the ring for polygons; "x" is VML syntax
9606 str += closed ? (L.Browser.svg ? 'z' : 'x') : '';
9609 // SVG complains about empty path strings
9610 return str || 'M0 0';
9614 // @namespace Browser; @property svg: Boolean
9615 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
9616 L.Browser.svg = !!(document.createElementNS && L.SVG.create('svg').createSVGRect);
9620 // @factory L.svg(options?: Renderer options)
9621 // Creates a SVG renderer with the given options.
9622 L.svg = function (options) {
9623 return L.Browser.svg || L.Browser.vml ? new L.SVG(options) : null;
9629 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
9635 * 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.
9637 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
9638 * with old versions of Internet Explorer.
9641 // @namespace Browser; @property vml: Boolean
9642 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
9643 L.Browser.vml = !L.Browser.svg && (function () {
9645 var div = document.createElement('div');
9646 div.innerHTML = '<v:shape adj="1"/>';
9648 var shape = div.firstChild;
9649 shape.style.behavior = 'url(#default#VML)';
9651 return shape && (typeof shape.adj === 'object');
9658 // redefine some SVG methods to handle VML syntax which is similar but with some differences
9659 L.SVG.include(!L.Browser.vml ? {} : {
9661 _initContainer: function () {
9662 this._container = L.DomUtil.create('div', 'leaflet-vml-container');
9665 _update: function () {
9666 if (this._map._animatingZoom) { return; }
9667 L.Renderer.prototype._update.call(this);
9668 this.fire('update');
9671 _initPath: function (layer) {
9672 var container = layer._container = L.SVG.create('shape');
9674 L.DomUtil.addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
9676 container.coordsize = '1 1';
9678 layer._path = L.SVG.create('path');
9679 container.appendChild(layer._path);
9681 this._updateStyle(layer);
9684 _addPath: function (layer) {
9685 var container = layer._container;
9686 this._container.appendChild(container);
9688 if (layer.options.interactive) {
9689 layer.addInteractiveTarget(container);
9693 _removePath: function (layer) {
9694 var container = layer._container;
9695 L.DomUtil.remove(container);
9696 layer.removeInteractiveTarget(container);
9699 _updateStyle: function (layer) {
9700 var stroke = layer._stroke,
9702 options = layer.options,
9703 container = layer._container;
9705 container.stroked = !!options.stroke;
9706 container.filled = !!options.fill;
9708 if (options.stroke) {
9710 stroke = layer._stroke = L.SVG.create('stroke');
9712 container.appendChild(stroke);
9713 stroke.weight = options.weight + 'px';
9714 stroke.color = options.color;
9715 stroke.opacity = options.opacity;
9717 if (options.dashArray) {
9718 stroke.dashStyle = L.Util.isArray(options.dashArray) ?
9719 options.dashArray.join(' ') :
9720 options.dashArray.replace(/( *, *)/g, ' ');
9722 stroke.dashStyle = '';
9724 stroke.endcap = options.lineCap.replace('butt', 'flat');
9725 stroke.joinstyle = options.lineJoin;
9727 } else if (stroke) {
9728 container.removeChild(stroke);
9729 layer._stroke = null;
9734 fill = layer._fill = L.SVG.create('fill');
9736 container.appendChild(fill);
9737 fill.color = options.fillColor || options.color;
9738 fill.opacity = options.fillOpacity;
9741 container.removeChild(fill);
9746 _updateCircle: function (layer) {
9747 var p = layer._point.round(),
9748 r = Math.round(layer._radius),
9749 r2 = Math.round(layer._radiusY || r);
9751 this._setPath(layer, layer._empty() ? 'M0 0' :
9752 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
9755 _setPath: function (layer, path) {
9756 layer._path.v = path;
9759 _bringToFront: function (layer) {
9760 L.DomUtil.toFront(layer._container);
9763 _bringToBack: function (layer) {
9764 L.DomUtil.toBack(layer._container);
9768 if (L.Browser.vml) {
9769 L.SVG.create = (function () {
9771 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
9772 return function (name) {
9773 return document.createElement('<lvml:' + name + ' class="lvml">');
9776 return function (name) {
9777 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
9787 * @inherits Renderer
9790 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
9791 * Inherits `Renderer`.
9793 * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
9794 * available in all web browsers, notably IE8, and overlapping geometries might
9795 * not display properly in some edge cases.
9799 * Use Canvas by default for all paths in the map:
9802 * var map = L.map('map', {
9803 * renderer: L.canvas()
9807 * Use a Canvas renderer with extra padding for specific vector geometries:
9810 * var map = L.map('map');
9811 * var myRenderer = L.canvas({ padding: 0.5 });
9812 * var line = L.polyline( coordinates, { renderer: myRenderer } );
9813 * var circle = L.circle( center, { renderer: myRenderer } );
9817 L.Canvas = L.Renderer.extend({
9819 onAdd: function () {
9820 L.Renderer.prototype.onAdd.call(this);
9822 // Redraw vectors since canvas is cleared upon removal,
9823 // in case of removing the renderer itself from the map.
9827 _initContainer: function () {
9828 var container = this._container = document.createElement('canvas');
9831 .on(container, 'mousemove', L.Util.throttle(this._onMouseMove, 32, this), this)
9832 .on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this)
9833 .on(container, 'mouseout', this._handleMouseOut, this);
9835 this._ctx = container.getContext('2d');
9838 _updatePaths: function () {
9840 this._redrawBounds = null;
9841 for (var id in this._layers) {
9842 layer = this._layers[id];
9848 _update: function () {
9849 if (this._map._animatingZoom && this._bounds) { return; }
9851 this._drawnLayers = {};
9853 L.Renderer.prototype._update.call(this);
9855 var b = this._bounds,
9856 container = this._container,
9858 m = L.Browser.retina ? 2 : 1;
9860 L.DomUtil.setPosition(container, b.min);
9862 // set canvas size (also clearing it); use double size on retina
9863 container.width = m * size.x;
9864 container.height = m * size.y;
9865 container.style.width = size.x + 'px';
9866 container.style.height = size.y + 'px';
9868 if (L.Browser.retina) {
9869 this._ctx.scale(2, 2);
9872 // translate so we use the same path coordinates after canvas element moves
9873 this._ctx.translate(-b.min.x, -b.min.y);
9875 // Tell paths to redraw themselves
9876 this.fire('update');
9879 _initPath: function (layer) {
9880 this._updateDashArray(layer);
9881 this._layers[L.stamp(layer)] = layer;
9883 var order = layer._order = {
9885 prev: this._drawLast,
9888 if (this._drawLast) { this._drawLast.next = order; }
9889 this._drawLast = order;
9890 this._drawFirst = this._drawFirst || this._drawLast;
9893 _addPath: function (layer) {
9894 this._requestRedraw(layer);
9897 _removePath: function (layer) {
9898 var order = layer._order;
9899 var next = order.next;
9900 var prev = order.prev;
9905 this._drawLast = prev;
9910 this._drawFirst = next;
9913 delete layer._order;
9915 delete this._layers[L.stamp(layer)];
9917 this._requestRedraw(layer);
9920 _updatePath: function (layer) {
9921 // Redraw the union of the layer's old pixel
9922 // bounds and the new pixel bounds.
9923 this._extendRedrawBounds(layer);
9926 // The redraw will extend the redraw bounds
9927 // with the new pixel bounds.
9928 this._requestRedraw(layer);
9931 _updateStyle: function (layer) {
9932 this._updateDashArray(layer);
9933 this._requestRedraw(layer);
9936 _updateDashArray: function (layer) {
9937 if (layer.options.dashArray) {
9938 var parts = layer.options.dashArray.split(','),
9941 for (i = 0; i < parts.length; i++) {
9942 dashArray.push(Number(parts[i]));
9944 layer.options._dashArray = dashArray;
9948 _requestRedraw: function (layer) {
9949 if (!this._map) { return; }
9951 this._extendRedrawBounds(layer);
9952 this._redrawRequest = this._redrawRequest || L.Util.requestAnimFrame(this._redraw, this);
9955 _extendRedrawBounds: function (layer) {
9956 var padding = (layer.options.weight || 0) + 1;
9957 this._redrawBounds = this._redrawBounds || new L.Bounds();
9958 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
9959 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
9962 _redraw: function () {
9963 this._redrawRequest = null;
9965 this._clear(); // clear layers in redraw bounds
9966 this._draw(); // draw layers
9968 this._redrawBounds = null;
9971 _clear: function () {
9972 var bounds = this._redrawBounds;
9974 var size = bounds.getSize();
9975 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
9977 this._ctx.clearRect(0, 0, this._container.width, this._container.height);
9981 _draw: function () {
9982 var layer, bounds = this._redrawBounds;
9985 var size = bounds.getSize();
9986 this._ctx.beginPath();
9987 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
9991 this._drawing = true;
9993 for (var order = this._drawFirst; order; order = order.next) {
9994 layer = order.layer;
9995 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
9996 layer._updatePath();
10000 this._drawing = false;
10002 this._ctx.restore(); // Restore state before clipping.
10005 _updatePoly: function (layer, closed) {
10006 if (!this._drawing) { return; }
10009 parts = layer._parts,
10010 len = parts.length,
10013 if (!len) { return; }
10015 this._drawnLayers[layer._leaflet_id] = layer;
10019 if (ctx.setLineDash) {
10020 ctx.setLineDash(layer.options && layer.options._dashArray || []);
10023 for (i = 0; i < len; i++) {
10024 for (j = 0, len2 = parts[i].length; j < len2; j++) {
10026 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
10033 this._fillStroke(ctx, layer);
10035 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
10038 _updateCircle: function (layer) {
10040 if (!this._drawing || layer._empty()) { return; }
10042 var p = layer._point,
10045 s = (layer._radiusY || r) / r;
10047 this._drawnLayers[layer._leaflet_id] = layer;
10055 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
10061 this._fillStroke(ctx, layer);
10064 _fillStroke: function (ctx, layer) {
10065 var options = layer.options;
10067 if (options.fill) {
10068 ctx.globalAlpha = options.fillOpacity;
10069 ctx.fillStyle = options.fillColor || options.color;
10070 ctx.fill(options.fillRule || 'evenodd');
10073 if (options.stroke && options.weight !== 0) {
10074 ctx.globalAlpha = options.opacity;
10075 ctx.lineWidth = options.weight;
10076 ctx.strokeStyle = options.color;
10077 ctx.lineCap = options.lineCap;
10078 ctx.lineJoin = options.lineJoin;
10083 // Canvas obviously doesn't have mouse events for individual drawn objects,
10084 // so we emulate that by calculating what's under the mouse on mousemove/click manually
10086 _onClick: function (e) {
10087 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
10089 for (var order = this._drawFirst; order; order = order.next) {
10090 layer = order.layer;
10091 if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
10092 clickedLayer = layer;
10095 if (clickedLayer) {
10096 L.DomEvent._fakeStop(e);
10097 this._fireEvent([clickedLayer], e);
10101 _onMouseMove: function (e) {
10102 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
10104 var point = this._map.mouseEventToLayerPoint(e);
10105 this._handleMouseHover(e, point);
10109 _handleMouseOut: function (e) {
10110 var layer = this._hoveredLayer;
10112 // if we're leaving the layer, fire mouseout
10113 L.DomUtil.removeClass(this._container, 'leaflet-interactive');
10114 this._fireEvent([layer], e, 'mouseout');
10115 this._hoveredLayer = null;
10119 _handleMouseHover: function (e, point) {
10120 var layer, candidateHoveredLayer;
10122 for (var order = this._drawFirst; order; order = order.next) {
10123 layer = order.layer;
10124 if (layer.options.interactive && layer._containsPoint(point)) {
10125 candidateHoveredLayer = layer;
10129 if (candidateHoveredLayer !== this._hoveredLayer) {
10130 this._handleMouseOut(e);
10132 if (candidateHoveredLayer) {
10133 L.DomUtil.addClass(this._container, 'leaflet-interactive'); // change cursor
10134 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
10135 this._hoveredLayer = candidateHoveredLayer;
10139 if (this._hoveredLayer) {
10140 this._fireEvent([this._hoveredLayer], e);
10144 _fireEvent: function (layers, e, type) {
10145 this._map._fireDOMEvent(e, type || e.type, layers);
10148 _bringToFront: function (layer) {
10149 var order = layer._order;
10150 var next = order.next;
10151 var prev = order.prev;
10162 // Update first entry unless this is the
10164 this._drawFirst = next;
10167 order.prev = this._drawLast;
10168 this._drawLast.next = order;
10171 this._drawLast = order;
10173 this._requestRedraw(layer);
10176 _bringToBack: function (layer) {
10177 var order = layer._order;
10178 var next = order.next;
10179 var prev = order.prev;
10190 // Update last entry unless this is the
10192 this._drawLast = prev;
10197 order.next = this._drawFirst;
10198 this._drawFirst.prev = order;
10199 this._drawFirst = order;
10201 this._requestRedraw(layer);
10205 // @namespace Browser; @property canvas: Boolean
10206 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
10207 L.Browser.canvas = (function () {
10208 return !!document.createElement('canvas').getContext;
10211 // @namespace Canvas
10212 // @factory L.canvas(options?: Renderer options)
10213 // Creates a Canvas renderer with the given options.
10214 L.canvas = function (options) {
10215 return L.Browser.canvas ? new L.Canvas(options) : null;
10218 L.Polyline.prototype._containsPoint = function (p, closed) {
10219 var i, j, k, len, len2, part,
10220 w = this._clickTolerance();
10222 if (!this._pxBounds.contains(p)) { return false; }
10224 // hit detection for polylines
10225 for (i = 0, len = this._parts.length; i < len; i++) {
10226 part = this._parts[i];
10228 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
10229 if (!closed && (j === 0)) { continue; }
10231 if (L.LineUtil.pointToSegmentDistance(p, part[k], part[j]) <= w) {
10239 L.Polygon.prototype._containsPoint = function (p) {
10240 var inside = false,
10241 part, p1, p2, i, j, k, len, len2;
10243 if (!this._pxBounds.contains(p)) { return false; }
10245 // ray casting algorithm for detecting if point is in polygon
10246 for (i = 0, len = this._parts.length; i < len; i++) {
10247 part = this._parts[i];
10249 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
10253 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)) {
10259 // also check if it's on polygon stroke
10260 return inside || L.Polyline.prototype._containsPoint.call(this, p, true);
10263 L.CircleMarker.prototype._containsPoint = function (p) {
10264 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
10272 * @inherits FeatureGroup
10274 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
10275 * GeoJSON data and display it on the map. Extends `FeatureGroup`.
10280 * L.geoJSON(data, {
10281 * style: function (feature) {
10282 * return {color: feature.properties.color};
10284 * }).bindPopup(function (layer) {
10285 * return layer.feature.properties.description;
10290 L.GeoJSON = L.FeatureGroup.extend({
10293 * @aka GeoJSON options
10295 * @option pointToLayer: Function = *
10296 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
10297 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
10298 * The default is to spawn a default `Marker`:
10300 * function(geoJsonPoint, latlng) {
10301 * return L.marker(latlng);
10305 * @option style: Function = *
10306 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
10307 * called internally when data is added.
10308 * The default value is to not override any defaults:
10310 * function (geoJsonFeature) {
10315 * @option onEachFeature: Function = *
10316 * A `Function` that will be called once for each created `Feature`, after it has
10317 * been created and styled. Useful for attaching events and popups to features.
10318 * The default is to do nothing with the newly created layers:
10320 * function (feature, layer) {}
10323 * @option filter: Function = *
10324 * A `Function` that will be used to decide whether to include a feature or not.
10325 * The default is to include all features:
10327 * function (geoJsonFeature) {
10331 * Note: dynamically changing the `filter` option will have effect only on newly
10332 * added data. It will _not_ re-evaluate already included features.
10334 * @option coordsToLatLng: Function = *
10335 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
10336 * The default is the `coordsToLatLng` static method.
10339 initialize: function (geojson, options) {
10340 L.setOptions(this, options);
10345 this.addData(geojson);
10349 // @method addData( <GeoJSON> data ): this
10350 // Adds a GeoJSON object to the layer.
10351 addData: function (geojson) {
10352 var features = L.Util.isArray(geojson) ? geojson : geojson.features,
10356 for (i = 0, len = features.length; i < len; i++) {
10357 // only add this if geometry or geometries are set and not null
10358 feature = features[i];
10359 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
10360 this.addData(feature);
10366 var options = this.options;
10368 if (options.filter && !options.filter(geojson)) { return this; }
10370 var layer = L.GeoJSON.geometryToLayer(geojson, options);
10374 layer.feature = L.GeoJSON.asFeature(geojson);
10376 layer.defaultOptions = layer.options;
10377 this.resetStyle(layer);
10379 if (options.onEachFeature) {
10380 options.onEachFeature(geojson, layer);
10383 return this.addLayer(layer);
10386 // @method resetStyle( <Path> layer ): this
10387 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
10388 resetStyle: function (layer) {
10389 // reset any custom styles
10390 layer.options = L.Util.extend({}, layer.defaultOptions);
10391 this._setLayerStyle(layer, this.options.style);
10395 // @method setStyle( <Function> style ): this
10396 // Changes styles of GeoJSON vector layers with the given style function.
10397 setStyle: function (style) {
10398 return this.eachLayer(function (layer) {
10399 this._setLayerStyle(layer, style);
10403 _setLayerStyle: function (layer, style) {
10404 if (typeof style === 'function') {
10405 style = style(layer.feature);
10407 if (layer.setStyle) {
10408 layer.setStyle(style);
10414 // There are several static functions which can be called without instantiating L.GeoJSON:
10415 L.extend(L.GeoJSON, {
10416 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
10417 // Creates a `Layer` from a given GeoJSON feature. Can use a custom
10418 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
10419 // functions if provided as options.
10420 geometryToLayer: function (geojson, options) {
10422 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
10423 coords = geometry ? geometry.coordinates : null,
10425 pointToLayer = options && options.pointToLayer,
10426 coordsToLatLng = options && options.coordsToLatLng || this.coordsToLatLng,
10427 latlng, latlngs, i, len;
10429 if (!coords && !geometry) {
10433 switch (geometry.type) {
10435 latlng = coordsToLatLng(coords);
10436 return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
10439 for (i = 0, len = coords.length; i < len; i++) {
10440 latlng = coordsToLatLng(coords[i]);
10441 layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng));
10443 return new L.FeatureGroup(layers);
10446 case 'MultiLineString':
10447 latlngs = this.coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, coordsToLatLng);
10448 return new L.Polyline(latlngs, options);
10451 case 'MultiPolygon':
10452 latlngs = this.coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, coordsToLatLng);
10453 return new L.Polygon(latlngs, options);
10455 case 'GeometryCollection':
10456 for (i = 0, len = geometry.geometries.length; i < len; i++) {
10457 var layer = this.geometryToLayer({
10458 geometry: geometry.geometries[i],
10460 properties: geojson.properties
10464 layers.push(layer);
10467 return new L.FeatureGroup(layers);
10470 throw new Error('Invalid GeoJSON object.');
10474 // @function coordsToLatLng(coords: Array): LatLng
10475 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
10476 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
10477 coordsToLatLng: function (coords) {
10478 return new L.LatLng(coords[1], coords[0], coords[2]);
10481 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
10482 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
10483 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
10484 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
10485 coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) {
10488 for (var i = 0, len = coords.length, latlng; i < len; i++) {
10489 latlng = levelsDeep ?
10490 this.coordsToLatLngs(coords[i], levelsDeep - 1, coordsToLatLng) :
10491 (coordsToLatLng || this.coordsToLatLng)(coords[i]);
10493 latlngs.push(latlng);
10499 // @function latLngToCoords(latlng: LatLng): Array
10500 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
10501 latLngToCoords: function (latlng) {
10502 return latlng.alt !== undefined ?
10503 [latlng.lng, latlng.lat, latlng.alt] :
10504 [latlng.lng, latlng.lat];
10507 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
10508 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
10509 // `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.
10510 latLngsToCoords: function (latlngs, levelsDeep, closed) {
10513 for (var i = 0, len = latlngs.length; i < len; i++) {
10514 coords.push(levelsDeep ?
10515 L.GeoJSON.latLngsToCoords(latlngs[i], levelsDeep - 1, closed) :
10516 L.GeoJSON.latLngToCoords(latlngs[i]));
10519 if (!levelsDeep && closed) {
10520 coords.push(coords[0]);
10526 getFeature: function (layer, newGeometry) {
10527 return layer.feature ?
10528 L.extend({}, layer.feature, {geometry: newGeometry}) :
10529 L.GeoJSON.asFeature(newGeometry);
10532 // @function asFeature(geojson: Object): Object
10533 // Normalize GeoJSON geometries/features into GeoJSON features.
10534 asFeature: function (geojson) {
10535 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
10547 var PointToGeoJSON = {
10548 toGeoJSON: function () {
10549 return L.GeoJSON.getFeature(this, {
10551 coordinates: L.GeoJSON.latLngToCoords(this.getLatLng())
10556 // @namespace Marker
10557 // @method toGeoJSON(): Object
10558 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
10559 L.Marker.include(PointToGeoJSON);
10561 // @namespace CircleMarker
10562 // @method toGeoJSON(): Object
10563 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
10564 L.Circle.include(PointToGeoJSON);
10565 L.CircleMarker.include(PointToGeoJSON);
10568 // @namespace Polyline
10569 // @method toGeoJSON(): Object
10570 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
10571 L.Polyline.prototype.toGeoJSON = function () {
10572 var multi = !L.Polyline._flat(this._latlngs);
10574 var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 1 : 0);
10576 return L.GeoJSON.getFeature(this, {
10577 type: (multi ? 'Multi' : '') + 'LineString',
10578 coordinates: coords
10582 // @namespace Polygon
10583 // @method toGeoJSON(): Object
10584 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
10585 L.Polygon.prototype.toGeoJSON = function () {
10586 var holes = !L.Polyline._flat(this._latlngs),
10587 multi = holes && !L.Polyline._flat(this._latlngs[0]);
10589 var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true);
10595 return L.GeoJSON.getFeature(this, {
10596 type: (multi ? 'Multi' : '') + 'Polygon',
10597 coordinates: coords
10602 // @namespace LayerGroup
10603 L.LayerGroup.include({
10604 toMultiPoint: function () {
10607 this.eachLayer(function (layer) {
10608 coords.push(layer.toGeoJSON().geometry.coordinates);
10611 return L.GeoJSON.getFeature(this, {
10612 type: 'MultiPoint',
10613 coordinates: coords
10617 // @method toGeoJSON(): Object
10618 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `GeometryCollection`).
10619 toGeoJSON: function () {
10621 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
10623 if (type === 'MultiPoint') {
10624 return this.toMultiPoint();
10627 var isGeometryCollection = type === 'GeometryCollection',
10630 this.eachLayer(function (layer) {
10631 if (layer.toGeoJSON) {
10632 var json = layer.toGeoJSON();
10633 jsons.push(isGeometryCollection ? json.geometry : L.GeoJSON.asFeature(json));
10637 if (isGeometryCollection) {
10638 return L.GeoJSON.getFeature(this, {
10640 type: 'GeometryCollection'
10645 type: 'FeatureCollection',
10651 // @namespace GeoJSON
10652 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
10653 // Creates a GeoJSON layer. Optionally accepts an object in
10654 // [GeoJSON format](http://geojson.org/geojson-spec.html) to display on the map
10655 // (you can alternatively add it later with `addData` method) and an `options` object.
10656 L.geoJSON = function (geojson, options) {
10657 return new L.GeoJSON(geojson, options);
10659 // Backward compatibility.
10660 L.geoJson = L.geoJSON;
10667 * @inherits Evented
10669 * A class for making DOM elements draggable (including touch support).
10670 * Used internally for map and marker dragging. Only works for elements
10671 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
10675 * var draggable = new L.Draggable(elementToDrag);
10676 * draggable.enable();
10680 L.Draggable = L.Evented.extend({
10683 // @option clickTolerance: Number = 3
10684 // The max number of pixels a user can shift the mouse pointer during a click
10685 // for it to be considered a valid click (as opposed to a mouse drag).
10690 START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'],
10692 mousedown: 'mouseup',
10693 touchstart: 'touchend',
10694 pointerdown: 'touchend',
10695 MSPointerDown: 'touchend'
10698 mousedown: 'mousemove',
10699 touchstart: 'touchmove',
10700 pointerdown: 'touchmove',
10701 MSPointerDown: 'touchmove'
10705 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline: Boolean)
10706 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
10707 initialize: function (element, dragStartTarget, preventOutline) {
10708 this._element = element;
10709 this._dragStartTarget = dragStartTarget || element;
10710 this._preventOutline = preventOutline;
10713 // @method enable()
10714 // Enables the dragging ability
10715 enable: function () {
10716 if (this._enabled) { return; }
10718 L.DomEvent.on(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this);
10720 this._enabled = true;
10723 // @method disable()
10724 // Disables the dragging ability
10725 disable: function () {
10726 if (!this._enabled) { return; }
10728 // If we're currently dragging this draggable,
10729 // disabling it counts as first ending the drag.
10730 if (L.Draggable._dragging === this) {
10734 L.DomEvent.off(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this);
10736 this._enabled = false;
10737 this._moved = false;
10740 _onDown: function (e) {
10741 // Ignore simulated events, since we handle both touch and
10742 // mouse explicitly; otherwise we risk getting duplicates of
10743 // touch events, see #4315.
10744 // Also ignore the event if disabled; this happens in IE11
10745 // under some circumstances, see #3666.
10746 if (e._simulated || !this._enabled) { return; }
10748 this._moved = false;
10750 if (L.DomUtil.hasClass(this._element, 'leaflet-zoom-anim')) { return; }
10752 if (L.Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
10753 L.Draggable._dragging = this; // Prevent dragging multiple objects at once.
10755 if (this._preventOutline) {
10756 L.DomUtil.preventOutline(this._element);
10759 L.DomUtil.disableImageDrag();
10760 L.DomUtil.disableTextSelection();
10762 if (this._moving) { return; }
10764 // @event down: Event
10765 // Fired when a drag is about to start.
10768 var first = e.touches ? e.touches[0] : e;
10770 this._startPoint = new L.Point(first.clientX, first.clientY);
10773 .on(document, L.Draggable.MOVE[e.type], this._onMove, this)
10774 .on(document, L.Draggable.END[e.type], this._onUp, this);
10777 _onMove: function (e) {
10778 // Ignore simulated events, since we handle both touch and
10779 // mouse explicitly; otherwise we risk getting duplicates of
10780 // touch events, see #4315.
10781 // Also ignore the event if disabled; this happens in IE11
10782 // under some circumstances, see #3666.
10783 if (e._simulated || !this._enabled) { return; }
10785 if (e.touches && e.touches.length > 1) {
10786 this._moved = true;
10790 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
10791 newPoint = new L.Point(first.clientX, first.clientY),
10792 offset = newPoint.subtract(this._startPoint);
10794 if (!offset.x && !offset.y) { return; }
10795 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
10797 L.DomEvent.preventDefault(e);
10799 if (!this._moved) {
10800 // @event dragstart: Event
10801 // Fired when a drag starts
10802 this.fire('dragstart');
10804 this._moved = true;
10805 this._startPos = L.DomUtil.getPosition(this._element).subtract(offset);
10807 L.DomUtil.addClass(document.body, 'leaflet-dragging');
10809 this._lastTarget = e.target || e.srcElement;
10810 // IE and Edge do not give the <use> element, so fetch it
10812 if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
10813 this._lastTarget = this._lastTarget.correspondingUseElement;
10815 L.DomUtil.addClass(this._lastTarget, 'leaflet-drag-target');
10818 this._newPos = this._startPos.add(offset);
10819 this._moving = true;
10821 L.Util.cancelAnimFrame(this._animRequest);
10822 this._lastEvent = e;
10823 this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true);
10826 _updatePosition: function () {
10827 var e = {originalEvent: this._lastEvent};
10829 // @event predrag: Event
10830 // Fired continuously during dragging *before* each corresponding
10831 // update of the element's position.
10832 this.fire('predrag', e);
10833 L.DomUtil.setPosition(this._element, this._newPos);
10835 // @event drag: Event
10836 // Fired continuously during dragging.
10837 this.fire('drag', e);
10840 _onUp: function (e) {
10841 // Ignore simulated events, since we handle both touch and
10842 // mouse explicitly; otherwise we risk getting duplicates of
10843 // touch events, see #4315.
10844 // Also ignore the event if disabled; this happens in IE11
10845 // under some circumstances, see #3666.
10846 if (e._simulated || !this._enabled) { return; }
10850 finishDrag: function () {
10851 L.DomUtil.removeClass(document.body, 'leaflet-dragging');
10853 if (this._lastTarget) {
10854 L.DomUtil.removeClass(this._lastTarget, 'leaflet-drag-target');
10855 this._lastTarget = null;
10858 for (var i in L.Draggable.MOVE) {
10860 .off(document, L.Draggable.MOVE[i], this._onMove, this)
10861 .off(document, L.Draggable.END[i], this._onUp, this);
10864 L.DomUtil.enableImageDrag();
10865 L.DomUtil.enableTextSelection();
10867 if (this._moved && this._moving) {
10868 // ensure drag is not fired after dragend
10869 L.Util.cancelAnimFrame(this._animRequest);
10871 // @event dragend: DragEndEvent
10872 // Fired when the drag ends.
10873 this.fire('dragend', {
10874 distance: this._newPos.distanceTo(this._startPos)
10878 this._moving = false;
10879 L.Draggable._dragging = false;
10887 L.Handler is a base class for handler classes that are used internally to inject
10888 interaction features like dragging to classes like Map and Marker.
10893 // Abstract class for map interaction handlers
10895 L.Handler = L.Class.extend({
10896 initialize: function (map) {
10900 // @method enable(): this
10901 // Enables the handler
10902 enable: function () {
10903 if (this._enabled) { return this; }
10905 this._enabled = true;
10910 // @method disable(): this
10911 // Disables the handler
10912 disable: function () {
10913 if (!this._enabled) { return this; }
10915 this._enabled = false;
10916 this.removeHooks();
10920 // @method enabled(): Boolean
10921 // Returns `true` if the handler is enabled
10922 enabled: function () {
10923 return !!this._enabled;
10926 // @section Extension methods
10927 // Classes inheriting from `Handler` must implement the two following methods:
10928 // @method addHooks()
10929 // Called when the handler is enabled, should add event hooks.
10930 // @method removeHooks()
10931 // Called when the handler is disabled, should remove the event hooks added previously.
10937 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
10941 // @section Interaction Options
10942 L.Map.mergeOptions({
10943 // @option dragging: Boolean = true
10944 // Whether the map be draggable with mouse/touch or not.
10947 // @section Panning Inertia Options
10948 // @option inertia: Boolean = *
10949 // If enabled, panning of the map will have an inertia effect where
10950 // the map builds momentum while dragging and continues moving in
10951 // the same direction for some time. Feels especially nice on touch
10952 // devices. Enabled by default unless running on old Android devices.
10953 inertia: !L.Browser.android23,
10955 // @option inertiaDeceleration: Number = 3000
10956 // The rate with which the inertial movement slows down, in pixels/second².
10957 inertiaDeceleration: 3400, // px/s^2
10959 // @option inertiaMaxSpeed: Number = Infinity
10960 // Max speed of the inertial movement, in pixels/second.
10961 inertiaMaxSpeed: Infinity, // px/s
10963 // @option easeLinearity: Number = 0.2
10964 easeLinearity: 0.2,
10966 // TODO refactor, move to CRS
10967 // @option worldCopyJump: Boolean = false
10968 // With this option enabled, the map tracks when you pan to another "copy"
10969 // of the world and seamlessly jumps to the original one so that all overlays
10970 // like markers and vector layers are still visible.
10971 worldCopyJump: false,
10973 // @option maxBoundsViscosity: Number = 0.0
10974 // If `maxBounds` is set, this option will control how solid the bounds
10975 // are when dragging the map around. The default value of `0.0` allows the
10976 // user to drag outside the bounds at normal speed, higher values will
10977 // slow down map dragging outside bounds, and `1.0` makes the bounds fully
10978 // solid, preventing the user from dragging outside the bounds.
10979 maxBoundsViscosity: 0.0
10982 L.Map.Drag = L.Handler.extend({
10983 addHooks: function () {
10984 if (!this._draggable) {
10985 var map = this._map;
10987 this._draggable = new L.Draggable(map._mapPane, map._container);
10989 this._draggable.on({
10990 down: this._onDown,
10991 dragstart: this._onDragStart,
10992 drag: this._onDrag,
10993 dragend: this._onDragEnd
10996 this._draggable.on('predrag', this._onPreDragLimit, this);
10997 if (map.options.worldCopyJump) {
10998 this._draggable.on('predrag', this._onPreDragWrap, this);
10999 map.on('zoomend', this._onZoomEnd, this);
11001 map.whenReady(this._onZoomEnd, this);
11004 L.DomUtil.addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
11005 this._draggable.enable();
11006 this._positions = [];
11010 removeHooks: function () {
11011 L.DomUtil.removeClass(this._map._container, 'leaflet-grab');
11012 L.DomUtil.removeClass(this._map._container, 'leaflet-touch-drag');
11013 this._draggable.disable();
11016 moved: function () {
11017 return this._draggable && this._draggable._moved;
11020 moving: function () {
11021 return this._draggable && this._draggable._moving;
11024 _onDown: function () {
11028 _onDragStart: function () {
11029 var map = this._map;
11031 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
11032 var bounds = L.latLngBounds(this._map.options.maxBounds);
11034 this._offsetLimit = L.bounds(
11035 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
11036 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
11037 .add(this._map.getSize()));
11039 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
11041 this._offsetLimit = null;
11046 .fire('dragstart');
11048 if (map.options.inertia) {
11049 this._positions = [];
11054 _onDrag: function (e) {
11055 if (this._map.options.inertia) {
11056 var time = this._lastTime = +new Date(),
11057 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
11059 this._positions.push(pos);
11060 this._times.push(time);
11062 if (time - this._times[0] > 50) {
11063 this._positions.shift();
11064 this._times.shift();
11073 _onZoomEnd: function () {
11074 var pxCenter = this._map.getSize().divideBy(2),
11075 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
11077 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
11078 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
11081 _viscousLimit: function (value, threshold) {
11082 return value - (value - threshold) * this._viscosity;
11085 _onPreDragLimit: function () {
11086 if (!this._viscosity || !this._offsetLimit) { return; }
11088 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
11090 var limit = this._offsetLimit;
11091 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
11092 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
11093 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
11094 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
11096 this._draggable._newPos = this._draggable._startPos.add(offset);
11099 _onPreDragWrap: function () {
11100 // TODO refactor to be able to adjust map pane position after zoom
11101 var worldWidth = this._worldWidth,
11102 halfWidth = Math.round(worldWidth / 2),
11103 dx = this._initialWorldOffset,
11104 x = this._draggable._newPos.x,
11105 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
11106 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
11107 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
11109 this._draggable._absPos = this._draggable._newPos.clone();
11110 this._draggable._newPos.x = newX;
11113 _onDragEnd: function (e) {
11114 var map = this._map,
11115 options = map.options,
11117 noInertia = !options.inertia || this._times.length < 2;
11119 map.fire('dragend', e);
11122 map.fire('moveend');
11126 var direction = this._lastPos.subtract(this._positions[0]),
11127 duration = (this._lastTime - this._times[0]) / 1000,
11128 ease = options.easeLinearity,
11130 speedVector = direction.multiplyBy(ease / duration),
11131 speed = speedVector.distanceTo([0, 0]),
11133 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
11134 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
11136 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
11137 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
11139 if (!offset.x && !offset.y) {
11140 map.fire('moveend');
11143 offset = map._limitOffset(offset, map.options.maxBounds);
11145 L.Util.requestAnimFrame(function () {
11146 map.panBy(offset, {
11147 duration: decelerationDuration,
11148 easeLinearity: ease,
11158 // @section Handlers
11159 // @property dragging: Handler
11160 // Map dragging handler (by both mouse and touch).
11161 L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag);
11166 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
11170 // @section Interaction Options
11172 L.Map.mergeOptions({
11173 // @option doubleClickZoom: Boolean|String = true
11174 // Whether the map can be zoomed in by double clicking on it and
11175 // zoomed out by double clicking while holding shift. If passed
11176 // `'center'`, double-click zoom will zoom to the center of the
11177 // view regardless of where the mouse was.
11178 doubleClickZoom: true
11181 L.Map.DoubleClickZoom = L.Handler.extend({
11182 addHooks: function () {
11183 this._map.on('dblclick', this._onDoubleClick, this);
11186 removeHooks: function () {
11187 this._map.off('dblclick', this._onDoubleClick, this);
11190 _onDoubleClick: function (e) {
11191 var map = this._map,
11192 oldZoom = map.getZoom(),
11193 delta = map.options.zoomDelta,
11194 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
11196 if (map.options.doubleClickZoom === 'center') {
11199 map.setZoomAround(e.containerPoint, zoom);
11204 // @section Handlers
11206 // Map properties include interaction handlers that allow you to control
11207 // interaction behavior in runtime, enabling or disabling certain features such
11208 // as dragging or touch zoom (see `Handler` methods). For example:
11211 // map.doubleClickZoom.disable();
11214 // @property doubleClickZoom: Handler
11215 // Double click zoom handler.
11216 L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom);
11221 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
11225 // @section Interaction Options
11226 L.Map.mergeOptions({
11227 // @section Mousewheel options
11228 // @option scrollWheelZoom: Boolean|String = true
11229 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
11230 // it will zoom to the center of the view regardless of where the mouse was.
11231 scrollWheelZoom: true,
11233 // @option wheelDebounceTime: Number = 40
11234 // Limits the rate at which a wheel can fire (in milliseconds). By default
11235 // user can't zoom via wheel more often than once per 40 ms.
11236 wheelDebounceTime: 40,
11238 // @option wheelPxPerZoomLevel: Number = 60
11239 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
11240 // mean a change of one full zoom level. Smaller values will make wheel-zooming
11241 // faster (and vice versa).
11242 wheelPxPerZoomLevel: 60
11245 L.Map.ScrollWheelZoom = L.Handler.extend({
11246 addHooks: function () {
11247 L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this);
11252 removeHooks: function () {
11253 L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll, this);
11256 _onWheelScroll: function (e) {
11257 var delta = L.DomEvent.getWheelDelta(e);
11259 var debounce = this._map.options.wheelDebounceTime;
11261 this._delta += delta;
11262 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
11264 if (!this._startTime) {
11265 this._startTime = +new Date();
11268 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
11270 clearTimeout(this._timer);
11271 this._timer = setTimeout(L.bind(this._performZoom, this), left);
11273 L.DomEvent.stop(e);
11276 _performZoom: function () {
11277 var map = this._map,
11278 zoom = map.getZoom(),
11279 snap = this._map.options.zoomSnap || 0;
11281 map._stop(); // stop panning and fly animations if any
11283 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
11284 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
11285 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
11286 d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
11287 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
11290 this._startTime = null;
11292 if (!delta) { return; }
11294 if (map.options.scrollWheelZoom === 'center') {
11295 map.setZoom(zoom + delta);
11297 map.setZoomAround(this._lastMousePos, zoom + delta);
11302 // @section Handlers
11303 // @property scrollWheelZoom: Handler
11304 // Scroll wheel zoom handler.
11305 L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);
11310 * Extends the event handling code with double tap support for mobile browsers.
11313 L.extend(L.DomEvent, {
11315 _touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart',
11316 _touchend: L.Browser.msPointer ? 'MSPointerUp' : L.Browser.pointer ? 'pointerup' : 'touchend',
11318 // inspired by Zepto touch code by Thomas Fuchs
11319 addDoubleTapListener: function (obj, handler, id) {
11324 function onTouchStart(e) {
11327 if (L.Browser.pointer) {
11328 count = L.DomEvent._pointersCount;
11330 count = e.touches.length;
11333 if (count > 1) { return; }
11335 var now = Date.now(),
11336 delta = now - (last || now);
11338 touch = e.touches ? e.touches[0] : e;
11339 doubleTap = (delta > 0 && delta <= delay);
11343 function onTouchEnd() {
11344 if (doubleTap && !touch.cancelBubble) {
11345 if (L.Browser.pointer) {
11346 // work around .type being readonly with MSPointer* events
11352 newTouch[i] = prop && prop.bind ? prop.bind(touch) : prop;
11356 touch.type = 'dblclick';
11362 var pre = '_leaflet_',
11363 touchstart = this._touchstart,
11364 touchend = this._touchend;
11366 obj[pre + touchstart + id] = onTouchStart;
11367 obj[pre + touchend + id] = onTouchEnd;
11368 obj[pre + 'dblclick' + id] = handler;
11370 obj.addEventListener(touchstart, onTouchStart, false);
11371 obj.addEventListener(touchend, onTouchEnd, false);
11373 // On some platforms (notably, chrome on win10 + touchscreen + mouse),
11374 // the browser doesn't fire touchend/pointerup events but does fire
11375 // native dblclicks. See #4127.
11376 if (!L.Browser.edge) {
11377 obj.addEventListener('dblclick', handler, false);
11383 removeDoubleTapListener: function (obj, id) {
11384 var pre = '_leaflet_',
11385 touchstart = obj[pre + this._touchstart + id],
11386 touchend = obj[pre + this._touchend + id],
11387 dblclick = obj[pre + 'dblclick' + id];
11389 obj.removeEventListener(this._touchstart, touchstart, false);
11390 obj.removeEventListener(this._touchend, touchend, false);
11391 if (!L.Browser.edge) {
11392 obj.removeEventListener('dblclick', dblclick, false);
11402 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
11405 L.extend(L.DomEvent, {
11407 POINTER_DOWN: L.Browser.msPointer ? 'MSPointerDown' : 'pointerdown',
11408 POINTER_MOVE: L.Browser.msPointer ? 'MSPointerMove' : 'pointermove',
11409 POINTER_UP: L.Browser.msPointer ? 'MSPointerUp' : 'pointerup',
11410 POINTER_CANCEL: L.Browser.msPointer ? 'MSPointerCancel' : 'pointercancel',
11411 TAG_WHITE_LIST: ['INPUT', 'SELECT', 'OPTION'],
11416 // Provides a touch events wrapper for (ms)pointer events.
11417 // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
11419 addPointerListener: function (obj, type, handler, id) {
11421 if (type === 'touchstart') {
11422 this._addPointerStart(obj, handler, id);
11424 } else if (type === 'touchmove') {
11425 this._addPointerMove(obj, handler, id);
11427 } else if (type === 'touchend') {
11428 this._addPointerEnd(obj, handler, id);
11434 removePointerListener: function (obj, type, id) {
11435 var handler = obj['_leaflet_' + type + id];
11437 if (type === 'touchstart') {
11438 obj.removeEventListener(this.POINTER_DOWN, handler, false);
11440 } else if (type === 'touchmove') {
11441 obj.removeEventListener(this.POINTER_MOVE, handler, false);
11443 } else if (type === 'touchend') {
11444 obj.removeEventListener(this.POINTER_UP, handler, false);
11445 obj.removeEventListener(this.POINTER_CANCEL, handler, false);
11451 _addPointerStart: function (obj, handler, id) {
11452 var onDown = L.bind(function (e) {
11453 if (e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
11454 // In IE11, some touch events needs to fire for form controls, or
11455 // the controls will stop working. We keep a whitelist of tag names that
11456 // need these events. For other target tags, we prevent default on the event.
11457 if (this.TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
11458 L.DomEvent.preventDefault(e);
11464 this._handlePointer(e, handler);
11467 obj['_leaflet_touchstart' + id] = onDown;
11468 obj.addEventListener(this.POINTER_DOWN, onDown, false);
11470 // need to keep track of what pointers and how many are active to provide e.touches emulation
11471 if (!this._pointerDocListener) {
11472 var pointerUp = L.bind(this._globalPointerUp, this);
11474 // we listen documentElement as any drags that end by moving the touch off the screen get fired there
11475 document.documentElement.addEventListener(this.POINTER_DOWN, L.bind(this._globalPointerDown, this), true);
11476 document.documentElement.addEventListener(this.POINTER_MOVE, L.bind(this._globalPointerMove, this), true);
11477 document.documentElement.addEventListener(this.POINTER_UP, pointerUp, true);
11478 document.documentElement.addEventListener(this.POINTER_CANCEL, pointerUp, true);
11480 this._pointerDocListener = true;
11484 _globalPointerDown: function (e) {
11485 this._pointers[e.pointerId] = e;
11486 this._pointersCount++;
11489 _globalPointerMove: function (e) {
11490 if (this._pointers[e.pointerId]) {
11491 this._pointers[e.pointerId] = e;
11495 _globalPointerUp: function (e) {
11496 delete this._pointers[e.pointerId];
11497 this._pointersCount--;
11500 _handlePointer: function (e, handler) {
11502 for (var i in this._pointers) {
11503 e.touches.push(this._pointers[i]);
11505 e.changedTouches = [e];
11510 _addPointerMove: function (obj, handler, id) {
11511 var onMove = L.bind(function (e) {
11512 // don't fire touch moves when mouse isn't down
11513 if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
11515 this._handlePointer(e, handler);
11518 obj['_leaflet_touchmove' + id] = onMove;
11519 obj.addEventListener(this.POINTER_MOVE, onMove, false);
11522 _addPointerEnd: function (obj, handler, id) {
11523 var onUp = L.bind(function (e) {
11524 this._handlePointer(e, handler);
11527 obj['_leaflet_touchend' + id] = onUp;
11528 obj.addEventListener(this.POINTER_UP, onUp, false);
11529 obj.addEventListener(this.POINTER_CANCEL, onUp, false);
11536 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
11540 // @section Interaction Options
11541 L.Map.mergeOptions({
11542 // @section Touch interaction options
11543 // @option touchZoom: Boolean|String = *
11544 // Whether the map can be zoomed by touch-dragging with two fingers. If
11545 // passed `'center'`, it will zoom to the center of the view regardless of
11546 // where the touch events (fingers) were. Enabled for touch-capable web
11547 // browsers except for old Androids.
11548 touchZoom: L.Browser.touch && !L.Browser.android23,
11550 // @option bounceAtZoomLimits: Boolean = true
11551 // Set it to false if you don't want the map to zoom beyond min/max zoom
11552 // and then bounce back when pinch-zooming.
11553 bounceAtZoomLimits: true
11556 L.Map.TouchZoom = L.Handler.extend({
11557 addHooks: function () {
11558 L.DomUtil.addClass(this._map._container, 'leaflet-touch-zoom');
11559 L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
11562 removeHooks: function () {
11563 L.DomUtil.removeClass(this._map._container, 'leaflet-touch-zoom');
11564 L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
11567 _onTouchStart: function (e) {
11568 var map = this._map;
11569 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
11571 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
11572 p2 = map.mouseEventToContainerPoint(e.touches[1]);
11574 this._centerPoint = map.getSize()._divideBy(2);
11575 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
11576 if (map.options.touchZoom !== 'center') {
11577 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
11580 this._startDist = p1.distanceTo(p2);
11581 this._startZoom = map.getZoom();
11583 this._moved = false;
11584 this._zooming = true;
11589 .on(document, 'touchmove', this._onTouchMove, this)
11590 .on(document, 'touchend', this._onTouchEnd, this);
11592 L.DomEvent.preventDefault(e);
11595 _onTouchMove: function (e) {
11596 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
11598 var map = this._map,
11599 p1 = map.mouseEventToContainerPoint(e.touches[0]),
11600 p2 = map.mouseEventToContainerPoint(e.touches[1]),
11601 scale = p1.distanceTo(p2) / this._startDist;
11604 this._zoom = map.getScaleZoom(scale, this._startZoom);
11606 if (!map.options.bounceAtZoomLimits && (
11607 (this._zoom < map.getMinZoom() && scale < 1) ||
11608 (this._zoom > map.getMaxZoom() && scale > 1))) {
11609 this._zoom = map._limitZoom(this._zoom);
11612 if (map.options.touchZoom === 'center') {
11613 this._center = this._startLatLng;
11614 if (scale === 1) { return; }
11616 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
11617 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
11618 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
11619 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
11622 if (!this._moved) {
11623 map._moveStart(true);
11624 this._moved = true;
11627 L.Util.cancelAnimFrame(this._animRequest);
11629 var moveFn = L.bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
11630 this._animRequest = L.Util.requestAnimFrame(moveFn, this, true);
11632 L.DomEvent.preventDefault(e);
11635 _onTouchEnd: function () {
11636 if (!this._moved || !this._zooming) {
11637 this._zooming = false;
11641 this._zooming = false;
11642 L.Util.cancelAnimFrame(this._animRequest);
11645 .off(document, 'touchmove', this._onTouchMove)
11646 .off(document, 'touchend', this._onTouchEnd);
11648 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
11649 if (this._map.options.zoomAnimation) {
11650 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
11652 this._map._resetView(this._center, this._map._limitZoom(this._zoom));
11657 // @section Handlers
11658 // @property touchZoom: Handler
11659 // Touch zoom handler.
11660 L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom);
11665 * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
11669 // @section Interaction Options
11670 L.Map.mergeOptions({
11671 // @section Touch interaction options
11672 // @option tap: Boolean = true
11673 // Enables mobile hacks for supporting instant taps (fixing 200ms click
11674 // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
11677 // @option tapTolerance: Number = 15
11678 // The max number of pixels a user can shift his finger during touch
11679 // for it to be considered a valid tap.
11683 L.Map.Tap = L.Handler.extend({
11684 addHooks: function () {
11685 L.DomEvent.on(this._map._container, 'touchstart', this._onDown, this);
11688 removeHooks: function () {
11689 L.DomEvent.off(this._map._container, 'touchstart', this._onDown, this);
11692 _onDown: function (e) {
11693 if (!e.touches) { return; }
11695 L.DomEvent.preventDefault(e);
11697 this._fireClick = true;
11699 // don't simulate click or track longpress if more than 1 touch
11700 if (e.touches.length > 1) {
11701 this._fireClick = false;
11702 clearTimeout(this._holdTimeout);
11706 var first = e.touches[0],
11709 this._startPos = this._newPos = new L.Point(first.clientX, first.clientY);
11711 // if touching a link, highlight it
11712 if (el.tagName && el.tagName.toLowerCase() === 'a') {
11713 L.DomUtil.addClass(el, 'leaflet-active');
11716 // simulate long hold but setting a timeout
11717 this._holdTimeout = setTimeout(L.bind(function () {
11718 if (this._isTapValid()) {
11719 this._fireClick = false;
11721 this._simulateEvent('contextmenu', first);
11725 this._simulateEvent('mousedown', first);
11727 L.DomEvent.on(document, {
11728 touchmove: this._onMove,
11729 touchend: this._onUp
11733 _onUp: function (e) {
11734 clearTimeout(this._holdTimeout);
11736 L.DomEvent.off(document, {
11737 touchmove: this._onMove,
11738 touchend: this._onUp
11741 if (this._fireClick && e && e.changedTouches) {
11743 var first = e.changedTouches[0],
11746 if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
11747 L.DomUtil.removeClass(el, 'leaflet-active');
11750 this._simulateEvent('mouseup', first);
11752 // simulate click if the touch didn't move too much
11753 if (this._isTapValid()) {
11754 this._simulateEvent('click', first);
11759 _isTapValid: function () {
11760 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
11763 _onMove: function (e) {
11764 var first = e.touches[0];
11765 this._newPos = new L.Point(first.clientX, first.clientY);
11766 this._simulateEvent('mousemove', first);
11769 _simulateEvent: function (type, e) {
11770 var simulatedEvent = document.createEvent('MouseEvents');
11772 simulatedEvent._simulated = true;
11773 e.target._simulatedClick = true;
11775 simulatedEvent.initMouseEvent(
11776 type, true, true, window, 1,
11777 e.screenX, e.screenY,
11778 e.clientX, e.clientY,
11779 false, false, false, false, 0, null);
11781 e.target.dispatchEvent(simulatedEvent);
11785 // @section Handlers
11786 // @property tap: Handler
11787 // Mobile touch hacks (quick tap and touch hold) handler.
11788 if (L.Browser.touch && !L.Browser.pointer) {
11789 L.Map.addInitHook('addHandler', 'tap', L.Map.Tap);
11795 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
11796 * (zoom to a selected bounding box), enabled by default.
11800 // @section Interaction Options
11801 L.Map.mergeOptions({
11802 // @option boxZoom: Boolean = true
11803 // Whether the map can be zoomed to a rectangular area specified by
11804 // dragging the mouse while pressing the shift key.
11808 L.Map.BoxZoom = L.Handler.extend({
11809 initialize: function (map) {
11811 this._container = map._container;
11812 this._pane = map._panes.overlayPane;
11815 addHooks: function () {
11816 L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
11819 removeHooks: function () {
11820 L.DomEvent.off(this._container, 'mousedown', this._onMouseDown, this);
11823 moved: function () {
11824 return this._moved;
11827 _resetState: function () {
11828 this._moved = false;
11831 _onMouseDown: function (e) {
11832 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
11834 this._resetState();
11836 L.DomUtil.disableTextSelection();
11837 L.DomUtil.disableImageDrag();
11839 this._startPoint = this._map.mouseEventToContainerPoint(e);
11841 L.DomEvent.on(document, {
11842 contextmenu: L.DomEvent.stop,
11843 mousemove: this._onMouseMove,
11844 mouseup: this._onMouseUp,
11845 keydown: this._onKeyDown
11849 _onMouseMove: function (e) {
11850 if (!this._moved) {
11851 this._moved = true;
11853 this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._container);
11854 L.DomUtil.addClass(this._container, 'leaflet-crosshair');
11856 this._map.fire('boxzoomstart');
11859 this._point = this._map.mouseEventToContainerPoint(e);
11861 var bounds = new L.Bounds(this._point, this._startPoint),
11862 size = bounds.getSize();
11864 L.DomUtil.setPosition(this._box, bounds.min);
11866 this._box.style.width = size.x + 'px';
11867 this._box.style.height = size.y + 'px';
11870 _finish: function () {
11872 L.DomUtil.remove(this._box);
11873 L.DomUtil.removeClass(this._container, 'leaflet-crosshair');
11876 L.DomUtil.enableTextSelection();
11877 L.DomUtil.enableImageDrag();
11879 L.DomEvent.off(document, {
11880 contextmenu: L.DomEvent.stop,
11881 mousemove: this._onMouseMove,
11882 mouseup: this._onMouseUp,
11883 keydown: this._onKeyDown
11887 _onMouseUp: function (e) {
11888 if ((e.which !== 1) && (e.button !== 1)) { return; }
11892 if (!this._moved) { return; }
11893 // Postpone to next JS tick so internal click event handling
11894 // still see it as "moved".
11895 setTimeout(L.bind(this._resetState, this), 0);
11897 var bounds = new L.LatLngBounds(
11898 this._map.containerPointToLatLng(this._startPoint),
11899 this._map.containerPointToLatLng(this._point));
11903 .fire('boxzoomend', {boxZoomBounds: bounds});
11906 _onKeyDown: function (e) {
11907 if (e.keyCode === 27) {
11913 // @section Handlers
11914 // @property boxZoom: Handler
11915 // Box (shift-drag with mouse) zoom handler.
11916 L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom);
11921 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
11925 // @section Keyboard Navigation Options
11926 L.Map.mergeOptions({
11927 // @option keyboard: Boolean = true
11928 // Makes the map focusable and allows users to navigate the map with keyboard
11929 // arrows and `+`/`-` keys.
11932 // @option keyboardPanDelta: Number = 80
11933 // Amount of pixels to pan when pressing an arrow key.
11934 keyboardPanDelta: 80
11937 L.Map.Keyboard = L.Handler.extend({
11944 zoomIn: [187, 107, 61, 171],
11945 zoomOut: [189, 109, 54, 173]
11948 initialize: function (map) {
11951 this._setPanDelta(map.options.keyboardPanDelta);
11952 this._setZoomDelta(map.options.zoomDelta);
11955 addHooks: function () {
11956 var container = this._map._container;
11958 // make the container focusable by tabbing
11959 if (container.tabIndex <= 0) {
11960 container.tabIndex = '0';
11963 L.DomEvent.on(container, {
11964 focus: this._onFocus,
11965 blur: this._onBlur,
11966 mousedown: this._onMouseDown
11970 focus: this._addHooks,
11971 blur: this._removeHooks
11975 removeHooks: function () {
11976 this._removeHooks();
11978 L.DomEvent.off(this._map._container, {
11979 focus: this._onFocus,
11980 blur: this._onBlur,
11981 mousedown: this._onMouseDown
11985 focus: this._addHooks,
11986 blur: this._removeHooks
11990 _onMouseDown: function () {
11991 if (this._focused) { return; }
11993 var body = document.body,
11994 docEl = document.documentElement,
11995 top = body.scrollTop || docEl.scrollTop,
11996 left = body.scrollLeft || docEl.scrollLeft;
11998 this._map._container.focus();
12000 window.scrollTo(left, top);
12003 _onFocus: function () {
12004 this._focused = true;
12005 this._map.fire('focus');
12008 _onBlur: function () {
12009 this._focused = false;
12010 this._map.fire('blur');
12013 _setPanDelta: function (panDelta) {
12014 var keys = this._panKeys = {},
12015 codes = this.keyCodes,
12018 for (i = 0, len = codes.left.length; i < len; i++) {
12019 keys[codes.left[i]] = [-1 * panDelta, 0];
12021 for (i = 0, len = codes.right.length; i < len; i++) {
12022 keys[codes.right[i]] = [panDelta, 0];
12024 for (i = 0, len = codes.down.length; i < len; i++) {
12025 keys[codes.down[i]] = [0, panDelta];
12027 for (i = 0, len = codes.up.length; i < len; i++) {
12028 keys[codes.up[i]] = [0, -1 * panDelta];
12032 _setZoomDelta: function (zoomDelta) {
12033 var keys = this._zoomKeys = {},
12034 codes = this.keyCodes,
12037 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
12038 keys[codes.zoomIn[i]] = zoomDelta;
12040 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
12041 keys[codes.zoomOut[i]] = -zoomDelta;
12045 _addHooks: function () {
12046 L.DomEvent.on(document, 'keydown', this._onKeyDown, this);
12049 _removeHooks: function () {
12050 L.DomEvent.off(document, 'keydown', this._onKeyDown, this);
12053 _onKeyDown: function (e) {
12054 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
12056 var key = e.keyCode,
12060 if (key in this._panKeys) {
12062 if (map._panAnim && map._panAnim._inProgress) { return; }
12064 offset = this._panKeys[key];
12066 offset = L.point(offset).multiplyBy(3);
12071 if (map.options.maxBounds) {
12072 map.panInsideBounds(map.options.maxBounds);
12075 } else if (key in this._zoomKeys) {
12076 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
12078 } else if (key === 27) {
12085 L.DomEvent.stop(e);
12089 // @section Handlers
12090 // @section Handlers
12091 // @property keyboard: Handler
12092 // Keyboard navigation handler.
12093 L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard);
12098 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
12102 /* @namespace Marker
12103 * @section Interaction handlers
12105 * 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:
12108 * marker.dragging.disable();
12111 * @property dragging: Handler
12112 * Marker dragging handler (by both mouse and touch).
12115 L.Handler.MarkerDrag = L.Handler.extend({
12116 initialize: function (marker) {
12117 this._marker = marker;
12120 addHooks: function () {
12121 var icon = this._marker._icon;
12123 if (!this._draggable) {
12124 this._draggable = new L.Draggable(icon, icon, true);
12127 this._draggable.on({
12128 dragstart: this._onDragStart,
12129 drag: this._onDrag,
12130 dragend: this._onDragEnd
12133 L.DomUtil.addClass(icon, 'leaflet-marker-draggable');
12136 removeHooks: function () {
12137 this._draggable.off({
12138 dragstart: this._onDragStart,
12139 drag: this._onDrag,
12140 dragend: this._onDragEnd
12141 }, this).disable();
12143 if (this._marker._icon) {
12144 L.DomUtil.removeClass(this._marker._icon, 'leaflet-marker-draggable');
12148 moved: function () {
12149 return this._draggable && this._draggable._moved;
12152 _onDragStart: function () {
12153 // @section Dragging events
12154 // @event dragstart: Event
12155 // Fired when the user starts dragging the marker.
12157 // @event movestart: Event
12158 // Fired when the marker starts moving (because of dragging).
12160 this._oldLatLng = this._marker.getLatLng();
12164 .fire('dragstart');
12167 _onDrag: function (e) {
12168 var marker = this._marker,
12169 shadow = marker._shadow,
12170 iconPos = L.DomUtil.getPosition(marker._icon),
12171 latlng = marker._map.layerPointToLatLng(iconPos);
12173 // update shadow position
12175 L.DomUtil.setPosition(shadow, iconPos);
12178 marker._latlng = latlng;
12180 e.oldLatLng = this._oldLatLng;
12182 // @event drag: Event
12183 // Fired repeatedly while the user drags the marker.
12189 _onDragEnd: function (e) {
12190 // @event dragend: DragEndEvent
12191 // Fired when the user stops dragging the marker.
12193 // @event moveend: Event
12194 // Fired when the marker stops moving (because of dragging).
12195 delete this._oldLatLng;
12198 .fire('dragend', e);
12209 * L.Control is a base class for implementing map controls. Handles positioning.
12210 * All other controls extend from this class.
12213 L.Control = L.Class.extend({
12215 // @aka Control options
12217 // @option position: String = 'topright'
12218 // The position of the control (one of the map corners). Possible values are `'topleft'`,
12219 // `'topright'`, `'bottomleft'` or `'bottomright'`
12220 position: 'topright'
12223 initialize: function (options) {
12224 L.setOptions(this, options);
12228 * Classes extending L.Control will inherit the following methods:
12230 * @method getPosition: string
12231 * Returns the position of the control.
12233 getPosition: function () {
12234 return this.options.position;
12237 // @method setPosition(position: string): this
12238 // Sets the position of the control.
12239 setPosition: function (position) {
12240 var map = this._map;
12243 map.removeControl(this);
12246 this.options.position = position;
12249 map.addControl(this);
12255 // @method getContainer: HTMLElement
12256 // Returns the HTMLElement that contains the control.
12257 getContainer: function () {
12258 return this._container;
12261 // @method addTo(map: Map): this
12262 // Adds the control to the given map.
12263 addTo: function (map) {
12267 var container = this._container = this.onAdd(map),
12268 pos = this.getPosition(),
12269 corner = map._controlCorners[pos];
12271 L.DomUtil.addClass(container, 'leaflet-control');
12273 if (pos.indexOf('bottom') !== -1) {
12274 corner.insertBefore(container, corner.firstChild);
12276 corner.appendChild(container);
12282 // @method remove: this
12283 // Removes the control from the map it is currently active on.
12284 remove: function () {
12289 L.DomUtil.remove(this._container);
12291 if (this.onRemove) {
12292 this.onRemove(this._map);
12300 _refocusOnMap: function (e) {
12301 // if map exists and event is not a keyboard event
12302 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
12303 this._map.getContainer().focus();
12308 L.control = function (options) {
12309 return new L.Control(options);
12312 /* @section Extension methods
12315 * Every control should extend from `L.Control` and (re-)implement the following methods.
12317 * @method onAdd(map: Map): HTMLElement
12318 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
12320 * @method onRemove(map: Map)
12321 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
12325 * @section Methods for Layers and Controls
12328 // @method addControl(control: Control): this
12329 // Adds the given control to the map
12330 addControl: function (control) {
12331 control.addTo(this);
12335 // @method removeControl(control: Control): this
12336 // Removes the given control from the map
12337 removeControl: function (control) {
12342 _initControlPos: function () {
12343 var corners = this._controlCorners = {},
12345 container = this._controlContainer =
12346 L.DomUtil.create('div', l + 'control-container', this._container);
12348 function createCorner(vSide, hSide) {
12349 var className = l + vSide + ' ' + l + hSide;
12351 corners[vSide + hSide] = L.DomUtil.create('div', className, container);
12354 createCorner('top', 'left');
12355 createCorner('top', 'right');
12356 createCorner('bottom', 'left');
12357 createCorner('bottom', 'right');
12360 _clearControlPos: function () {
12361 L.DomUtil.remove(this._controlContainer);
12368 * @class Control.Zoom
12369 * @aka L.Control.Zoom
12370 * @inherits Control
12372 * 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`.
12375 L.Control.Zoom = L.Control.extend({
12377 // @aka Control.Zoom options
12379 position: 'topleft',
12381 // @option zoomInText: String = '+'
12382 // The text set on the 'zoom in' button.
12385 // @option zoomInTitle: String = 'Zoom in'
12386 // The title set on the 'zoom in' button.
12387 zoomInTitle: 'Zoom in',
12389 // @option zoomOutText: String = '-'
12390 // The text set on the 'zoom out' button.
12393 // @option zoomOutTitle: String = 'Zoom out'
12394 // The title set on the 'zoom out' button.
12395 zoomOutTitle: 'Zoom out'
12398 onAdd: function (map) {
12399 var zoomName = 'leaflet-control-zoom',
12400 container = L.DomUtil.create('div', zoomName + ' leaflet-bar'),
12401 options = this.options;
12403 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle,
12404 zoomName + '-in', container, this._zoomIn);
12405 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
12406 zoomName + '-out', container, this._zoomOut);
12408 this._updateDisabled();
12409 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
12414 onRemove: function (map) {
12415 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
12418 disable: function () {
12419 this._disabled = true;
12420 this._updateDisabled();
12424 enable: function () {
12425 this._disabled = false;
12426 this._updateDisabled();
12430 _zoomIn: function (e) {
12431 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
12432 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
12436 _zoomOut: function (e) {
12437 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
12438 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
12442 _createButton: function (html, title, className, container, fn) {
12443 var link = L.DomUtil.create('a', className, container);
12444 link.innerHTML = html;
12446 link.title = title;
12449 * Will force screen readers like VoiceOver to read this as "Zoom in - button"
12451 link.setAttribute('role', 'button');
12452 link.setAttribute('aria-label', title);
12455 .on(link, 'mousedown dblclick', L.DomEvent.stopPropagation)
12456 .on(link, 'click', L.DomEvent.stop)
12457 .on(link, 'click', fn, this)
12458 .on(link, 'click', this._refocusOnMap, this);
12463 _updateDisabled: function () {
12464 var map = this._map,
12465 className = 'leaflet-disabled';
12467 L.DomUtil.removeClass(this._zoomInButton, className);
12468 L.DomUtil.removeClass(this._zoomOutButton, className);
12470 if (this._disabled || map._zoom === map.getMinZoom()) {
12471 L.DomUtil.addClass(this._zoomOutButton, className);
12473 if (this._disabled || map._zoom === map.getMaxZoom()) {
12474 L.DomUtil.addClass(this._zoomInButton, className);
12480 // @section Control options
12481 // @option zoomControl: Boolean = true
12482 // Whether a [zoom control](#control-zoom) is added to the map by default.
12483 L.Map.mergeOptions({
12487 L.Map.addInitHook(function () {
12488 if (this.options.zoomControl) {
12489 this.zoomControl = new L.Control.Zoom();
12490 this.addControl(this.zoomControl);
12494 // @namespace Control.Zoom
12495 // @factory L.control.zoom(options: Control.Zoom options)
12496 // Creates a zoom control
12497 L.control.zoom = function (options) {
12498 return new L.Control.Zoom(options);
12504 * @class Control.Attribution
12505 * @aka L.Control.Attribution
12506 * @inherits Control
12508 * 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.
12511 L.Control.Attribution = L.Control.extend({
12513 // @aka Control.Attribution options
12515 position: 'bottomright',
12517 // @option prefix: String = 'Leaflet'
12518 // The HTML text shown before the attributions. Pass `false` to disable.
12519 prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
12522 initialize: function (options) {
12523 L.setOptions(this, options);
12525 this._attributions = {};
12528 onAdd: function (map) {
12529 map.attributionControl = this;
12530 this._container = L.DomUtil.create('div', 'leaflet-control-attribution');
12532 L.DomEvent.disableClickPropagation(this._container);
12535 // TODO ugly, refactor
12536 for (var i in map._layers) {
12537 if (map._layers[i].getAttribution) {
12538 this.addAttribution(map._layers[i].getAttribution());
12544 return this._container;
12547 // @method setPrefix(prefix: String): this
12548 // Sets the text before the attributions.
12549 setPrefix: function (prefix) {
12550 this.options.prefix = prefix;
12555 // @method addAttribution(text: String): this
12556 // Adds an attribution text (e.g. `'Vector data © Mapbox'`).
12557 addAttribution: function (text) {
12558 if (!text) { return this; }
12560 if (!this._attributions[text]) {
12561 this._attributions[text] = 0;
12563 this._attributions[text]++;
12570 // @method removeAttribution(text: String): this
12571 // Removes an attribution text.
12572 removeAttribution: function (text) {
12573 if (!text) { return this; }
12575 if (this._attributions[text]) {
12576 this._attributions[text]--;
12583 _update: function () {
12584 if (!this._map) { return; }
12588 for (var i in this._attributions) {
12589 if (this._attributions[i]) {
12594 var prefixAndAttribs = [];
12596 if (this.options.prefix) {
12597 prefixAndAttribs.push(this.options.prefix);
12599 if (attribs.length) {
12600 prefixAndAttribs.push(attribs.join(', '));
12603 this._container.innerHTML = prefixAndAttribs.join(' | ');
12608 // @section Control options
12609 // @option attributionControl: Boolean = true
12610 // Whether a [attribution control](#control-attribution) is added to the map by default.
12611 L.Map.mergeOptions({
12612 attributionControl: true
12615 L.Map.addInitHook(function () {
12616 if (this.options.attributionControl) {
12617 new L.Control.Attribution().addTo(this);
12621 // @namespace Control.Attribution
12622 // @factory L.control.attribution(options: Control.Attribution options)
12623 // Creates an attribution control.
12624 L.control.attribution = function (options) {
12625 return new L.Control.Attribution(options);
12631 * @class Control.Scale
12632 * @aka L.Control.Scale
12633 * @inherits Control
12635 * 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`.
12640 * L.control.scale().addTo(map);
12644 L.Control.Scale = L.Control.extend({
12646 // @aka Control.Scale options
12648 position: 'bottomleft',
12650 // @option maxWidth: Number = 100
12651 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
12654 // @option metric: Boolean = True
12655 // Whether to show the metric scale line (m/km).
12658 // @option imperial: Boolean = True
12659 // Whether to show the imperial scale line (mi/ft).
12662 // @option updateWhenIdle: Boolean = false
12663 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
12666 onAdd: function (map) {
12667 var className = 'leaflet-control-scale',
12668 container = L.DomUtil.create('div', className),
12669 options = this.options;
12671 this._addScales(options, className + '-line', container);
12673 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
12674 map.whenReady(this._update, this);
12679 onRemove: function (map) {
12680 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
12683 _addScales: function (options, className, container) {
12684 if (options.metric) {
12685 this._mScale = L.DomUtil.create('div', className, container);
12687 if (options.imperial) {
12688 this._iScale = L.DomUtil.create('div', className, container);
12692 _update: function () {
12693 var map = this._map,
12694 y = map.getSize().y / 2;
12696 var maxMeters = map.distance(
12697 map.containerPointToLatLng([0, y]),
12698 map.containerPointToLatLng([this.options.maxWidth, y]));
12700 this._updateScales(maxMeters);
12703 _updateScales: function (maxMeters) {
12704 if (this.options.metric && maxMeters) {
12705 this._updateMetric(maxMeters);
12707 if (this.options.imperial && maxMeters) {
12708 this._updateImperial(maxMeters);
12712 _updateMetric: function (maxMeters) {
12713 var meters = this._getRoundNum(maxMeters),
12714 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
12716 this._updateScale(this._mScale, label, meters / maxMeters);
12719 _updateImperial: function (maxMeters) {
12720 var maxFeet = maxMeters * 3.2808399,
12721 maxMiles, miles, feet;
12723 if (maxFeet > 5280) {
12724 maxMiles = maxFeet / 5280;
12725 miles = this._getRoundNum(maxMiles);
12726 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
12729 feet = this._getRoundNum(maxFeet);
12730 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
12734 _updateScale: function (scale, text, ratio) {
12735 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
12736 scale.innerHTML = text;
12739 _getRoundNum: function (num) {
12740 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
12753 // @factory L.control.scale(options?: Control.Scale options)
12754 // Creates an scale control with the given options.
12755 L.control.scale = function (options) {
12756 return new L.Control.Scale(options);
12762 * @class Control.Layers
12763 * @aka L.Control.Layers
12764 * @inherits Control
12766 * 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`.
12771 * var baseLayers = {
12772 * "Mapbox": mapbox,
12773 * "OpenStreetMap": osm
12777 * "Marker": marker,
12778 * "Roads": roadsLayer
12781 * L.control.layers(baseLayers, overlays).addTo(map);
12784 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
12788 * "<someName1>": layer1,
12789 * "<someName2>": layer2
12793 * The layer names can contain HTML, which allows you to add additional styling to the items:
12796 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
12801 L.Control.Layers = L.Control.extend({
12803 // @aka Control.Layers options
12805 // @option collapsed: Boolean = true
12806 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
12808 position: 'topright',
12810 // @option autoZIndex: Boolean = true
12811 // 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.
12814 // @option hideSingleBase: Boolean = false
12815 // If `true`, the base layers in the control will be hidden when there is only one.
12816 hideSingleBase: false,
12818 // @option sortLayers: Boolean = false
12819 // Whether to sort the layers. When `false`, layers will keep the order
12820 // in which they were added to the control.
12823 // @option sortFunction: Function = *
12824 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
12825 // that will be used for sorting the layers, when `sortLayers` is `true`.
12826 // The function receives both the `L.Layer` instances and their names, as in
12827 // `sortFunction(layerA, layerB, nameA, nameB)`.
12828 // By default, it sorts layers alphabetically by their name.
12829 sortFunction: function (layerA, layerB, nameA, nameB) {
12830 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
12834 initialize: function (baseLayers, overlays, options) {
12835 L.setOptions(this, options);
12838 this._lastZIndex = 0;
12839 this._handlingClick = false;
12841 for (var i in baseLayers) {
12842 this._addLayer(baseLayers[i], i);
12845 for (i in overlays) {
12846 this._addLayer(overlays[i], i, true);
12850 onAdd: function (map) {
12851 this._initLayout();
12855 map.on('zoomend', this._checkDisabledLayers, this);
12857 return this._container;
12860 onRemove: function () {
12861 this._map.off('zoomend', this._checkDisabledLayers, this);
12863 for (var i = 0; i < this._layers.length; i++) {
12864 this._layers[i].layer.off('add remove', this._onLayerChange, this);
12868 // @method addBaseLayer(layer: Layer, name: String): this
12869 // Adds a base layer (radio button entry) with the given name to the control.
12870 addBaseLayer: function (layer, name) {
12871 this._addLayer(layer, name);
12872 return (this._map) ? this._update() : this;
12875 // @method addOverlay(layer: Layer, name: String): this
12876 // Adds an overlay (checkbox entry) with the given name to the control.
12877 addOverlay: function (layer, name) {
12878 this._addLayer(layer, name, true);
12879 return (this._map) ? this._update() : this;
12882 // @method removeLayer(layer: Layer): this
12883 // Remove the given layer from the control.
12884 removeLayer: function (layer) {
12885 layer.off('add remove', this._onLayerChange, this);
12887 var obj = this._getLayer(L.stamp(layer));
12889 this._layers.splice(this._layers.indexOf(obj), 1);
12891 return (this._map) ? this._update() : this;
12894 // @method expand(): this
12895 // Expand the control container if collapsed.
12896 expand: function () {
12897 L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');
12898 this._form.style.height = null;
12899 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
12900 if (acceptableHeight < this._form.clientHeight) {
12901 L.DomUtil.addClass(this._form, 'leaflet-control-layers-scrollbar');
12902 this._form.style.height = acceptableHeight + 'px';
12904 L.DomUtil.removeClass(this._form, 'leaflet-control-layers-scrollbar');
12906 this._checkDisabledLayers();
12910 // @method collapse(): this
12911 // Collapse the control container if expanded.
12912 collapse: function () {
12913 L.DomUtil.removeClass(this._container, 'leaflet-control-layers-expanded');
12917 _initLayout: function () {
12918 var className = 'leaflet-control-layers',
12919 container = this._container = L.DomUtil.create('div', className);
12921 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
12922 container.setAttribute('aria-haspopup', true);
12924 L.DomEvent.disableClickPropagation(container);
12925 if (!L.Browser.touch) {
12926 L.DomEvent.disableScrollPropagation(container);
12929 var form = this._form = L.DomUtil.create('form', className + '-list');
12931 if (!L.Browser.android) {
12932 L.DomEvent.on(container, {
12933 mouseenter: this.expand,
12934 mouseleave: this.collapse
12938 var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
12940 link.title = 'Layers';
12942 if (L.Browser.touch) {
12944 .on(link, 'click', L.DomEvent.stop)
12945 .on(link, 'click', this.expand, this);
12947 L.DomEvent.on(link, 'focus', this.expand, this);
12950 // work around for Firefox Android issue https://github.com/Leaflet/Leaflet/issues/2033
12951 L.DomEvent.on(form, 'click', function () {
12952 setTimeout(L.bind(this._onInputClick, this), 0);
12955 this._map.on('click', this.collapse, this);
12956 // TODO keyboard accessibility
12958 if (!this.options.collapsed) {
12962 this._baseLayersList = L.DomUtil.create('div', className + '-base', form);
12963 this._separator = L.DomUtil.create('div', className + '-separator', form);
12964 this._overlaysList = L.DomUtil.create('div', className + '-overlays', form);
12966 container.appendChild(form);
12969 _getLayer: function (id) {
12970 for (var i = 0; i < this._layers.length; i++) {
12972 if (this._layers[i] && L.stamp(this._layers[i].layer) === id) {
12973 return this._layers[i];
12978 _addLayer: function (layer, name, overlay) {
12979 layer.on('add remove', this._onLayerChange, this);
12981 this._layers.push({
12987 if (this.options.sortLayers) {
12988 this._layers.sort(L.bind(function (a, b) {
12989 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
12993 if (this.options.autoZIndex && layer.setZIndex) {
12994 this._lastZIndex++;
12995 layer.setZIndex(this._lastZIndex);
12999 _update: function () {
13000 if (!this._container) { return this; }
13002 L.DomUtil.empty(this._baseLayersList);
13003 L.DomUtil.empty(this._overlaysList);
13005 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
13007 for (i = 0; i < this._layers.length; i++) {
13008 obj = this._layers[i];
13009 this._addItem(obj);
13010 overlaysPresent = overlaysPresent || obj.overlay;
13011 baseLayersPresent = baseLayersPresent || !obj.overlay;
13012 baseLayersCount += !obj.overlay ? 1 : 0;
13015 // Hide base layers section if there's only one layer.
13016 if (this.options.hideSingleBase) {
13017 baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
13018 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
13021 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
13026 _onLayerChange: function (e) {
13027 if (!this._handlingClick) {
13031 var obj = this._getLayer(L.stamp(e.target));
13034 // @section Layer events
13035 // @event baselayerchange: LayersControlEvent
13036 // Fired when the base layer is changed through the [layer control](#control-layers).
13037 // @event overlayadd: LayersControlEvent
13038 // Fired when an overlay is selected through the [layer control](#control-layers).
13039 // @event overlayremove: LayersControlEvent
13040 // Fired when an overlay is deselected through the [layer control](#control-layers).
13041 // @namespace Control.Layers
13042 var type = obj.overlay ?
13043 (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
13044 (e.type === 'add' ? 'baselayerchange' : null);
13047 this._map.fire(type, obj);
13051 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
13052 _createRadioElement: function (name, checked) {
13054 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
13055 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
13057 var radioFragment = document.createElement('div');
13058 radioFragment.innerHTML = radioHtml;
13060 return radioFragment.firstChild;
13063 _addItem: function (obj) {
13064 var label = document.createElement('label'),
13065 checked = this._map.hasLayer(obj.layer),
13069 input = document.createElement('input');
13070 input.type = 'checkbox';
13071 input.className = 'leaflet-control-layers-selector';
13072 input.defaultChecked = checked;
13074 input = this._createRadioElement('leaflet-base-layers', checked);
13077 input.layerId = L.stamp(obj.layer);
13079 L.DomEvent.on(input, 'click', this._onInputClick, this);
13081 var name = document.createElement('span');
13082 name.innerHTML = ' ' + obj.name;
13084 // Helps from preventing layer control flicker when checkboxes are disabled
13085 // https://github.com/Leaflet/Leaflet/issues/2771
13086 var holder = document.createElement('div');
13088 label.appendChild(holder);
13089 holder.appendChild(input);
13090 holder.appendChild(name);
13092 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
13093 container.appendChild(label);
13095 this._checkDisabledLayers();
13099 _onInputClick: function () {
13100 var inputs = this._form.getElementsByTagName('input'),
13101 input, layer, hasLayer;
13102 var addedLayers = [],
13103 removedLayers = [];
13105 this._handlingClick = true;
13107 for (var i = inputs.length - 1; i >= 0; i--) {
13109 layer = this._getLayer(input.layerId).layer;
13110 hasLayer = this._map.hasLayer(layer);
13112 if (input.checked && !hasLayer) {
13113 addedLayers.push(layer);
13115 } else if (!input.checked && hasLayer) {
13116 removedLayers.push(layer);
13120 // Bugfix issue 2318: Should remove all old layers before readding new ones
13121 for (i = 0; i < removedLayers.length; i++) {
13122 this._map.removeLayer(removedLayers[i]);
13124 for (i = 0; i < addedLayers.length; i++) {
13125 this._map.addLayer(addedLayers[i]);
13128 this._handlingClick = false;
13130 this._refocusOnMap();
13133 _checkDisabledLayers: function () {
13134 var inputs = this._form.getElementsByTagName('input'),
13137 zoom = this._map.getZoom();
13139 for (var i = inputs.length - 1; i >= 0; i--) {
13141 layer = this._getLayer(input.layerId).layer;
13142 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
13143 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
13148 _expand: function () {
13149 // Backward compatibility, remove me in 1.1.
13150 return this.expand();
13153 _collapse: function () {
13154 // Backward compatibility, remove me in 1.1.
13155 return this.collapse();
13161 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
13162 // 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.
13163 L.control.layers = function (baseLayers, overlays, options) {
13164 return new L.Control.Layers(baseLayers, overlays, options);
13169 }(window, document));
13170 //# sourceMappingURL=leaflet-src.map