2 Leaflet 1.0.1, a JS library for interactive maps. http://leafletjs.com
3 (c) 2010-2016 Vladimir Agafonkin, (c) 2010-2011 CloudMade
5 (function (window, document, undefined) {
13 L.noConflict = function () {
21 // define Leaflet for Node module pattern loaders, including Browserify
22 if (typeof module === 'object' && typeof module.exports === 'object') {
25 // define Leaflet as an AMD module
26 } else if (typeof define === 'function' && define.amd) {
30 // define Leaflet as a global L variable, saving the original L to restore later if needed
31 if (typeof window !== 'undefined') {
40 * Various utility functions, used by Leaflet internally.
45 // @function extend(dest: Object, src?: Object): Object
46 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
47 extend: function (dest) {
50 for (j = 1, len = arguments.length; j < len; j++) {
59 // @function create(proto: Object, properties?: Object): Object
60 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
61 create: Object.create || (function () {
63 return function (proto) {
69 // @function bind(fn: Function, …): Function
70 // Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
71 // Has a `L.bind()` shortcut.
72 bind: function (fn, obj) {
73 var slice = Array.prototype.slice;
76 return fn.bind.apply(fn, slice.call(arguments, 1));
79 var args = slice.call(arguments, 2);
82 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
86 // @function stamp(obj: Object): Number
87 // Returns the unique ID of an object, assiging it one if it doesn't have it.
88 stamp: function (obj) {
90 obj._leaflet_id = obj._leaflet_id || ++L.Util.lastId;
91 return obj._leaflet_id;
95 // @property lastId: Number
96 // Last unique ID used by [`stamp()`](#util-stamp)
99 // @function throttle(fn: Function, time: Number, context: Object): Function
100 // Returns a function which executes function `fn` with the given scope `context`
101 // (so that the `this` keyword refers to `context` inside `fn`'s code). The function
102 // `fn` will be called no more than one time per given amount of `time`. The arguments
103 // received by the bound function will be any arguments passed when binding the
104 // function, followed by any arguments passed when invoking the bound function.
105 // Has an `L.bind` shortcut.
106 throttle: function (fn, time, context) {
107 var lock, args, wrapperFn, later;
109 later = function () {
110 // reset lock and call if queued
113 wrapperFn.apply(context, args);
118 wrapperFn = function () {
120 // called too soon, queue to call later
124 // call and lock until later
125 fn.apply(context, arguments);
126 setTimeout(later, time);
134 // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
135 // Returns the number `num` modulo `range` in such a way so it lies within
136 // `range[0]` and `range[1]`. The returned value will be always smaller than
137 // `range[1]` unless `includeMax` is set to `true`.
138 wrapNum: function (x, range, includeMax) {
142 return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
145 // @function falseFn(): Function
146 // Returns a function which always returns `false`.
147 falseFn: function () { return false; },
149 // @function formatNum(num: Number, digits?: Number): Number
150 // Returns the number `num` rounded to `digits` decimals, or to 5 decimals by default.
151 formatNum: function (num, digits) {
152 var pow = Math.pow(10, digits || 5);
153 return Math.round(num * pow) / pow;
156 // @function trim(str: String): String
157 // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
158 trim: function (str) {
159 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
162 // @function splitWords(str: String): String[]
163 // Trims and splits the string on whitespace and returns the array of parts.
164 splitWords: function (str) {
165 return L.Util.trim(str).split(/\s+/);
168 // @function setOptions(obj: Object, options: Object): Object
169 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
170 setOptions: function (obj, options) {
171 if (!obj.hasOwnProperty('options')) {
172 obj.options = obj.options ? L.Util.create(obj.options) : {};
174 for (var i in options) {
175 obj.options[i] = options[i];
180 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
181 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
182 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
183 // be appended at the end. If `uppercase` is `true`, the parameter names will
184 // be uppercased (e.g. `'?A=foo&B=bar'`)
185 getParamString: function (obj, existingUrl, uppercase) {
188 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
190 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
193 // @function template(str: String, data: Object): String
194 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
195 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
196 // `('Hello foo, bar')`. You can also specify functions instead of strings for
197 // data values — they will be evaluated passing `data` as an argument.
198 template: function (str, data) {
199 return str.replace(L.Util.templateRe, function (str, key) {
200 var value = data[key];
202 if (value === undefined) {
203 throw new Error('No value provided for variable ' + str);
205 } else if (typeof value === 'function') {
212 templateRe: /\{ *([\w_\-]+) *\}/g,
214 // @function isArray(obj): Boolean
215 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
216 isArray: Array.isArray || function (obj) {
217 return (Object.prototype.toString.call(obj) === '[object Array]');
220 // @function indexOf(array: Array, el: Object): Number
221 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
222 indexOf: function (array, el) {
223 for (var i = 0; i < array.length; i++) {
224 if (array[i] === el) { return i; }
229 // @property emptyImageUrl: String
230 // Data URI string containing a base64-encoded empty GIF image.
231 // Used as a hack to free memory from unused images on WebKit-powered
232 // mobile devices (by setting image `src` to this string).
233 emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
237 // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
239 function getPrefixed(name) {
240 return window['webkit' + name] || window['moz' + name] || window['ms' + name];
245 // fallback for IE 7-8
246 function timeoutDefer(fn) {
247 var time = +new Date(),
248 timeToCall = Math.max(0, 16 - (time - lastTime));
250 lastTime = time + timeToCall;
251 return window.setTimeout(fn, timeToCall);
254 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer,
255 cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
256 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
259 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
260 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
261 // `context` if given. When `immediate` is set, `fn` is called immediately if
262 // the browser doesn't have native support for
263 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
264 // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
265 L.Util.requestAnimFrame = function (fn, context, immediate) {
266 if (immediate && requestFn === timeoutDefer) {
269 return requestFn.call(window, L.bind(fn, context));
273 // @function cancelAnimFrame(id: Number): undefined
274 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
275 L.Util.cancelAnimFrame = function (id) {
277 cancelFn.call(window, id);
282 // shortcuts for most used utility functions
283 L.extend = L.Util.extend;
284 L.bind = L.Util.bind;
285 L.stamp = L.Util.stamp;
286 L.setOptions = L.Util.setOptions;
297 // Thanks to John Resig and Dean Edwards for inspiration!
299 L.Class = function () {};
301 L.Class.extend = function (props) {
303 // @function extend(props: Object): Function
304 // [Extends the current class](#class-inheritance) given the properties to be included.
305 // Returns a Javascript function that is a class constructor (to be called with `new`).
306 var NewClass = function () {
308 // call the constructor
309 if (this.initialize) {
310 this.initialize.apply(this, arguments);
313 // call all constructor hooks
314 this.callInitHooks();
317 var parentProto = NewClass.__super__ = this.prototype;
319 var proto = L.Util.create(parentProto);
320 proto.constructor = NewClass;
322 NewClass.prototype = proto;
324 // inherit parent's statics
325 for (var i in this) {
326 if (this.hasOwnProperty(i) && i !== 'prototype') {
327 NewClass[i] = this[i];
331 // mix static properties into the class
333 L.extend(NewClass, props.statics);
334 delete props.statics;
337 // mix includes into the prototype
338 if (props.includes) {
339 L.Util.extend.apply(null, [proto].concat(props.includes));
340 delete props.includes;
345 props.options = L.Util.extend(L.Util.create(proto.options), props.options);
348 // mix given properties into the prototype
349 L.extend(proto, props);
351 proto._initHooks = [];
353 // add method for calling all hooks
354 proto.callInitHooks = function () {
356 if (this._initHooksCalled) { return; }
358 if (parentProto.callInitHooks) {
359 parentProto.callInitHooks.call(this);
362 this._initHooksCalled = true;
364 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
365 proto._initHooks[i].call(this);
373 // @function include(properties: Object): this
374 // [Includes a mixin](#class-includes) into the current class.
375 L.Class.include = function (props) {
376 L.extend(this.prototype, props);
380 // @function mergeOptions(options: Object): this
381 // [Merges `options`](#class-options) into the defaults of the class.
382 L.Class.mergeOptions = function (options) {
383 L.extend(this.prototype.options, options);
387 // @function addInitHook(fn: Function): this
388 // Adds a [constructor hook](#class-constructor-hooks) to the class.
389 L.Class.addInitHook = function (fn) { // (Function) || (String, args...)
390 var args = Array.prototype.slice.call(arguments, 1);
392 var init = typeof fn === 'function' ? fn : function () {
393 this[fn].apply(this, args);
396 this.prototype._initHooks = this.prototype._initHooks || [];
397 this.prototype._initHooks.push(init);
408 * A set of methods shared between event-powered classes (like `Map` and `Marker`). Generally, events allow you to execute some function when something happens with an object (e.g. the user clicks on the map, causing the map to fire `'click'` event).
413 * map.on('click', function(e) {
418 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
421 * function onClick(e) { ... }
423 * map.on('click', onClick);
424 * map.off('click', onClick);
429 L.Evented = L.Class.extend({
431 /* @method on(type: String, fn: Function, context?: Object): this
432 * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`).
435 * @method on(eventMap: Object): this
436 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
438 on: function (types, fn, context) {
440 // types can be a map of types/handlers
441 if (typeof types === 'object') {
442 for (var type in types) {
443 // we don't process space-separated events here for performance;
444 // it's a hot path since Layer uses the on(obj) syntax
445 this._on(type, types[type], fn);
449 // types can be a string of space-separated words
450 types = L.Util.splitWords(types);
452 for (var i = 0, len = types.length; i < len; i++) {
453 this._on(types[i], fn, context);
460 /* @method off(type: String, fn?: Function, context?: Object): this
461 * Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener.
464 * @method off(eventMap: Object): this
465 * Removes a set of type/listener pairs.
469 * Removes all listeners to all events on the object.
471 off: function (types, fn, context) {
474 // clear all listeners if called without arguments
477 } else if (typeof types === 'object') {
478 for (var type in types) {
479 this._off(type, types[type], fn);
483 types = L.Util.splitWords(types);
485 for (var i = 0, len = types.length; i < len; i++) {
486 this._off(types[i], fn, context);
493 // attach listener (without syntactic sugar now)
494 _on: function (type, fn, context) {
495 this._events = this._events || {};
497 /* get/init listeners for type */
498 var typeListeners = this._events[type];
499 if (!typeListeners) {
501 this._events[type] = typeListeners;
504 if (context === this) {
505 // Less memory footprint.
508 var newListener = {fn: fn, ctx: context},
509 listeners = typeListeners;
511 // check if fn already there
512 for (var i = 0, len = listeners.length; i < len; i++) {
513 if (listeners[i].fn === fn && listeners[i].ctx === context) {
518 listeners.push(newListener);
519 typeListeners.count++;
522 _off: function (type, fn, context) {
527 if (!this._events) { return; }
529 listeners = this._events[type];
536 // Set all removed listeners to noop so they are not called if remove happens in fire
537 for (i = 0, len = listeners.length; i < len; i++) {
538 listeners[i].fn = L.Util.falseFn;
540 // clear all listeners for a type if function isn't specified
541 delete this._events[type];
545 if (context === this) {
551 // find fn and remove it
552 for (i = 0, len = listeners.length; i < len; i++) {
553 var l = listeners[i];
554 if (l.ctx !== context) { continue; }
557 // set the removed listener to noop so that's not called if remove happens in fire
558 l.fn = L.Util.falseFn;
560 if (this._firingCount) {
561 /* copy array in case events are being fired */
562 this._events[type] = listeners = listeners.slice();
564 listeners.splice(i, 1);
572 // @method fire(type: String, data?: Object, propagate?: Boolean): this
573 // Fires an event of the specified type. You can optionally provide an data
574 // object — the first argument of the listener function will contain its
575 // properties. The event might can optionally be propagated to event parents.
576 fire: function (type, data, propagate) {
577 if (!this.listens(type, propagate)) { return this; }
579 var event = L.Util.extend({}, data, {type: type, target: this});
582 var listeners = this._events[type];
585 this._firingCount = (this._firingCount + 1) || 1;
586 for (var i = 0, len = listeners.length; i < len; i++) {
587 var l = listeners[i];
588 l.fn.call(l.ctx || this, event);
596 // propagate the event to parents (set with addEventParent)
597 this._propagateEvent(event);
603 // @method listens(type: String): Boolean
604 // Returns `true` if a particular event type has any listeners attached to it.
605 listens: function (type, propagate) {
606 var listeners = this._events && this._events[type];
607 if (listeners && listeners.length) { return true; }
610 // also check parents for listeners if event propagates
611 for (var id in this._eventParents) {
612 if (this._eventParents[id].listens(type, propagate)) { return true; }
618 // @method once(…): this
619 // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
620 once: function (types, fn, context) {
622 if (typeof types === 'object') {
623 for (var type in types) {
624 this.once(type, types[type], fn);
629 var handler = L.bind(function () {
631 .off(types, fn, context)
632 .off(types, handler, context);
635 // add a listener that's executed once and removed after that
637 .on(types, fn, context)
638 .on(types, handler, context);
641 // @method addEventParent(obj: Evented): this
642 // Adds an event parent - an `Evented` that will receive propagated events
643 addEventParent: function (obj) {
644 this._eventParents = this._eventParents || {};
645 this._eventParents[L.stamp(obj)] = obj;
649 // @method removeEventParent(obj: Evented): this
650 // Removes an event parent, so it will stop receiving propagated events
651 removeEventParent: function (obj) {
652 if (this._eventParents) {
653 delete this._eventParents[L.stamp(obj)];
658 _propagateEvent: function (e) {
659 for (var id in this._eventParents) {
660 this._eventParents[id].fire(e.type, L.extend({layer: e.target}, e), true);
665 var proto = L.Evented.prototype;
667 // aliases; we should ditch those eventually
669 // @method addEventListener(…): this
670 // Alias to [`on(…)`](#evented-on)
671 proto.addEventListener = proto.on;
673 // @method removeEventListener(…): this
674 // Alias to [`off(…)`](#evented-off)
676 // @method clearAllEventListeners(…): this
677 // Alias to [`off()`](#evented-off)
678 proto.removeEventListener = proto.clearAllEventListeners = proto.off;
680 // @method addOneTimeEventListener(…): this
681 // Alias to [`once(…)`](#evented-once)
682 proto.addOneTimeEventListener = proto.once;
684 // @method fireEvent(…): this
685 // Alias to [`fire(…)`](#evented-fire)
686 proto.fireEvent = proto.fire;
688 // @method hasEventListeners(…): Boolean
689 // Alias to [`listens(…)`](#evented-listens)
690 proto.hasEventListeners = proto.listens;
692 L.Mixin = {Events: proto};
700 * A namespace with static properties for browser/feature detection used by Leaflet internally.
705 * if (L.Browser.ielt9) {
706 * alert('Upgrade your browser, dude!');
713 var ua = navigator.userAgent.toLowerCase(),
714 doc = document.documentElement,
716 ie = 'ActiveXObject' in window,
718 webkit = ua.indexOf('webkit') !== -1,
719 phantomjs = ua.indexOf('phantom') !== -1,
720 android23 = ua.search('android [23]') !== -1,
721 chrome = ua.indexOf('chrome') !== -1,
722 gecko = ua.indexOf('gecko') !== -1 && !webkit && !window.opera && !ie,
724 win = navigator.platform.indexOf('Win') === 0,
726 mobile = typeof orientation !== 'undefined' || ua.indexOf('mobile') !== -1,
727 msPointer = !window.PointerEvent && window.MSPointerEvent,
728 pointer = window.PointerEvent || msPointer,
730 ie3d = ie && ('transition' in doc.style),
731 webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23,
732 gecko3d = 'MozPerspective' in doc.style,
733 opera12 = 'OTransition' in doc.style;
736 var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
737 (window.DocumentTouch && document instanceof window.DocumentTouch));
741 // @property ie: Boolean
742 // `true` for all Internet Explorer versions (not Edge).
745 // @property ielt9: Boolean
746 // `true` for Internet Explorer versions less than 9.
747 ielt9: ie && !document.addEventListener,
749 // @property edge: Boolean
750 // `true` for the Edge web browser.
751 edge: 'msLaunchUri' in navigator && !('documentMode' in document),
753 // @property webkit: Boolean
754 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
757 // @property gecko: Boolean
758 // `true` for gecko-based browsers like Firefox.
761 // @property android: Boolean
762 // `true` for any browser running on an Android platform.
763 android: ua.indexOf('android') !== -1,
765 // @property android23: Boolean
766 // `true` for browsers running on Android 2 or Android 3.
767 android23: android23,
769 // @property chrome: Boolean
770 // `true` for the Chrome browser.
773 // @property safari: Boolean
774 // `true` for the Safari browser.
775 safari: !chrome && ua.indexOf('safari') !== -1,
778 // @property win: Boolean
779 // `true` when the browser is running in a Windows platform
783 // @property ie3d: Boolean
784 // `true` for all Internet Explorer versions supporting CSS transforms.
787 // @property webkit3d: Boolean
788 // `true` for webkit-based browsers supporting CSS transforms.
791 // @property gecko3d: Boolean
792 // `true` for gecko-based browsers supporting CSS transforms.
795 // @property opera12: Boolean
796 // `true` for the Opera browser supporting CSS transforms (version 12 or later).
799 // @property any3d: Boolean
800 // `true` for all browsers supporting CSS transforms.
801 any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantomjs,
804 // @property mobile: Boolean
805 // `true` for all browsers running in a mobile device.
808 // @property mobileWebkit: Boolean
809 // `true` for all webkit-based browsers in a mobile device.
810 mobileWebkit: mobile && webkit,
812 // @property mobileWebkit3d: Boolean
813 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
814 mobileWebkit3d: mobile && webkit3d,
816 // @property mobileOpera: Boolean
817 // `true` for the Opera browser in a mobile device.
818 mobileOpera: mobile && window.opera,
820 // @property mobileGecko: Boolean
821 // `true` for gecko-based browsers running in a mobile device.
822 mobileGecko: mobile && gecko,
825 // @property touch: Boolean
826 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
829 // @property msPointer: Boolean
830 // `true` for browsers implementing the Microsoft touch events model (notably IE10).
831 msPointer: !!msPointer,
833 // @property pointer: Boolean
834 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
838 // @property retina: Boolean
839 // `true` for browsers on a high-resolution "retina" screen.
840 retina: (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1
851 * Represents a point with `x` and `y` coordinates in pixels.
856 * var point = L.point(200, 300);
859 * All Leaflet methods and options that accept `Point` objects also accept them in a simple Array form (unless noted otherwise), so these lines are equivalent:
862 * map.panBy([200, 300]);
863 * map.panBy(L.point(200, 300));
867 L.Point = function (x, y, round) {
868 this.x = (round ? Math.round(x) : x);
869 this.y = (round ? Math.round(y) : y);
872 L.Point.prototype = {
874 // @method clone(): Point
875 // Returns a copy of the current point.
877 return new L.Point(this.x, this.y);
880 // @method add(otherPoint: Point): Point
881 // Returns the result of addition of the current and the given points.
882 add: function (point) {
883 // non-destructive, returns a new point
884 return this.clone()._add(L.point(point));
887 _add: function (point) {
888 // destructive, used directly for performance in situations where it's safe to modify existing point
894 // @method subtract(otherPoint: Point): Point
895 // Returns the result of subtraction of the given point from the current.
896 subtract: function (point) {
897 return this.clone()._subtract(L.point(point));
900 _subtract: function (point) {
906 // @method divideBy(num: Number): Point
907 // Returns the result of division of the current point by the given number.
908 divideBy: function (num) {
909 return this.clone()._divideBy(num);
912 _divideBy: function (num) {
918 // @method multiplyBy(num: Number): Point
919 // Returns the result of multiplication of the current point by the given number.
920 multiplyBy: function (num) {
921 return this.clone()._multiplyBy(num);
924 _multiplyBy: function (num) {
930 // @method scaleBy(scale: Point): Point
931 // Multiply each coordinate of the current point by each coordinate of
932 // `scale`. In linear algebra terms, multiply the point by the
933 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
934 // defined by `scale`.
935 scaleBy: function (point) {
936 return new L.Point(this.x * point.x, this.y * point.y);
939 // @method unscaleBy(scale: Point): Point
940 // Inverse of `scaleBy`. Divide each coordinate of the current point by
941 // each coordinate of `scale`.
942 unscaleBy: function (point) {
943 return new L.Point(this.x / point.x, this.y / point.y);
946 // @method round(): Point
947 // Returns a copy of the current point with rounded coordinates.
949 return this.clone()._round();
952 _round: function () {
953 this.x = Math.round(this.x);
954 this.y = Math.round(this.y);
958 // @method floor(): Point
959 // Returns a copy of the current point with floored coordinates (rounded down).
961 return this.clone()._floor();
964 _floor: function () {
965 this.x = Math.floor(this.x);
966 this.y = Math.floor(this.y);
970 // @method ceil(): Point
971 // Returns a copy of the current point with ceiled coordinates (rounded up).
973 return this.clone()._ceil();
977 this.x = Math.ceil(this.x);
978 this.y = Math.ceil(this.y);
982 // @method distanceTo(otherPoint: Point): Number
983 // Returns the cartesian distance between the current and the given points.
984 distanceTo: function (point) {
985 point = L.point(point);
987 var x = point.x - this.x,
988 y = point.y - this.y;
990 return Math.sqrt(x * x + y * y);
993 // @method equals(otherPoint: Point): Boolean
994 // Returns `true` if the given point has the same coordinates.
995 equals: function (point) {
996 point = L.point(point);
998 return point.x === this.x &&
1002 // @method contains(otherPoint: Point): Boolean
1003 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
1004 contains: function (point) {
1005 point = L.point(point);
1007 return Math.abs(point.x) <= Math.abs(this.x) &&
1008 Math.abs(point.y) <= Math.abs(this.y);
1011 // @method toString(): String
1012 // Returns a string representation of the point for debugging purposes.
1013 toString: function () {
1015 L.Util.formatNum(this.x) + ', ' +
1016 L.Util.formatNum(this.y) + ')';
1020 // @factory L.point(x: Number, y: Number, round?: Boolean)
1021 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
1024 // @factory L.point(coords: Number[])
1025 // Expects an array of the form `[x, y]` instead.
1028 // @factory L.point(coords: Object)
1029 // Expects a plain object of the form `{x: Number, y: Number}` instead.
1030 L.point = function (x, y, round) {
1031 if (x instanceof L.Point) {
1034 if (L.Util.isArray(x)) {
1035 return new L.Point(x[0], x[1]);
1037 if (x === undefined || x === null) {
1040 if (typeof x === 'object' && 'x' in x && 'y' in x) {
1041 return new L.Point(x.x, x.y);
1043 return new L.Point(x, y, round);
1052 * Represents a rectangular area in pixel coordinates.
1057 * var p1 = L.point(10, 10),
1058 * p2 = L.point(40, 60),
1059 * bounds = L.bounds(p1, p2);
1062 * All Leaflet methods that accept `Bounds` objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
1065 * otherBounds.intersects([[10, 10], [40, 60]]);
1069 L.Bounds = function (a, b) {
1072 var points = b ? [a, b] : a;
1074 for (var i = 0, len = points.length; i < len; i++) {
1075 this.extend(points[i]);
1079 L.Bounds.prototype = {
1080 // @method extend(point: Point): this
1081 // Extends the bounds to contain the given point.
1082 extend: function (point) { // (Point)
1083 point = L.point(point);
1085 // @property min: Point
1086 // The top left corner of the rectangle.
1087 // @property max: Point
1088 // The bottom right corner of the rectangle.
1089 if (!this.min && !this.max) {
1090 this.min = point.clone();
1091 this.max = point.clone();
1093 this.min.x = Math.min(point.x, this.min.x);
1094 this.max.x = Math.max(point.x, this.max.x);
1095 this.min.y = Math.min(point.y, this.min.y);
1096 this.max.y = Math.max(point.y, this.max.y);
1101 // @method getCenter(round?: Boolean): Point
1102 // Returns the center point of the bounds.
1103 getCenter: function (round) {
1105 (this.min.x + this.max.x) / 2,
1106 (this.min.y + this.max.y) / 2, round);
1109 // @method getBottomLeft(): Point
1110 // Returns the bottom-left point of the bounds.
1111 getBottomLeft: function () {
1112 return new L.Point(this.min.x, this.max.y);
1115 // @method getTopRight(): Point
1116 // Returns the top-right point of the bounds.
1117 getTopRight: function () { // -> Point
1118 return new L.Point(this.max.x, this.min.y);
1121 // @method getSize(): Point
1122 // Returns the size of the given bounds
1123 getSize: function () {
1124 return this.max.subtract(this.min);
1127 // @method contains(otherBounds: Bounds): Boolean
1128 // Returns `true` if the rectangle contains the given one.
1130 // @method contains(point: Point): Boolean
1131 // Returns `true` if the rectangle contains the given point.
1132 contains: function (obj) {
1135 if (typeof obj[0] === 'number' || obj instanceof L.Point) {
1138 obj = L.bounds(obj);
1141 if (obj instanceof L.Bounds) {
1148 return (min.x >= this.min.x) &&
1149 (max.x <= this.max.x) &&
1150 (min.y >= this.min.y) &&
1151 (max.y <= this.max.y);
1154 // @method intersects(otherBounds: Bounds): Boolean
1155 // Returns `true` if the rectangle intersects the given bounds. Two bounds
1156 // intersect if they have at least one point in common.
1157 intersects: function (bounds) { // (Bounds) -> Boolean
1158 bounds = L.bounds(bounds);
1164 xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1165 yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1167 return xIntersects && yIntersects;
1170 // @method overlaps(otherBounds: Bounds): Boolean
1171 // Returns `true` if the rectangle overlaps the given bounds. Two bounds
1172 // overlap if their intersection is an area.
1173 overlaps: function (bounds) { // (Bounds) -> Boolean
1174 bounds = L.bounds(bounds);
1180 xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1181 yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1183 return xOverlaps && yOverlaps;
1186 isValid: function () {
1187 return !!(this.min && this.max);
1192 // @factory L.bounds(topLeft: Point, bottomRight: Point)
1193 // Creates a Bounds object from two coordinates (usually top-left and bottom-right corners).
1195 // @factory L.bounds(points: Point[])
1196 // Creates a Bounds object from the points it contains
1197 L.bounds = function (a, b) {
1198 if (!a || a instanceof L.Bounds) {
1201 return new L.Bounds(a, b);
1207 * @class Transformation
1208 * @aka L.Transformation
1210 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1211 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1212 * the reverse. Used by Leaflet in its projections code.
1217 * var transformation = new L.Transformation(2, 5, -1, 10),
1218 * p = L.point(1, 2),
1219 * p2 = transformation.transform(p), // L.point(7, 8)
1220 * p3 = transformation.untransform(p2); // L.point(1, 2)
1225 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1226 // Creates a `Transformation` object with the given coefficients.
1227 L.Transformation = function (a, b, c, d) {
1234 L.Transformation.prototype = {
1235 // @method transform(point: Point, scale?: Number): Point
1236 // Returns a transformed point, optionally multiplied by the given scale.
1237 // Only accepts real `L.Point` instances, not arrays.
1238 transform: function (point, scale) { // (Point, Number) -> Point
1239 return this._transform(point.clone(), scale);
1242 // destructive transform (faster)
1243 _transform: function (point, scale) {
1245 point.x = scale * (this._a * point.x + this._b);
1246 point.y = scale * (this._c * point.y + this._d);
1250 // @method untransform(point: Point, scale?: Number): Point
1251 // Returns the reverse transformation of the given point, optionally divided
1252 // by the given scale. Only accepts real `L.Point` instances, not arrays.
1253 untransform: function (point, scale) {
1256 (point.x / scale - this._b) / this._a,
1257 (point.y / scale - this._d) / this._c);
1264 * @namespace DomUtil
1266 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
1267 * tree, used by Leaflet internally.
1269 * Most functions expecting or returning a `HTMLElement` also work for
1270 * SVG elements. The only difference is that classes refer to CSS classes
1271 * in HTML and SVG classes in SVG.
1276 // @function get(id: String|HTMLElement): HTMLElement
1277 // Returns an element given its DOM id, or returns the element itself
1278 // if it was passed directly.
1279 get: function (id) {
1280 return typeof id === 'string' ? document.getElementById(id) : id;
1283 // @function getStyle(el: HTMLElement, styleAttrib: String): String
1284 // Returns the value for a certain style attribute on an element,
1285 // including computed values or values set through CSS.
1286 getStyle: function (el, style) {
1288 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
1290 if ((!value || value === 'auto') && document.defaultView) {
1291 var css = document.defaultView.getComputedStyle(el, null);
1292 value = css ? css[style] : null;
1295 return value === 'auto' ? null : value;
1298 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
1299 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
1300 create: function (tagName, className, container) {
1302 var el = document.createElement(tagName);
1303 el.className = className || '';
1306 container.appendChild(el);
1312 // @function remove(el: HTMLElement)
1313 // Removes `el` from its parent element
1314 remove: function (el) {
1315 var parent = el.parentNode;
1317 parent.removeChild(el);
1321 // @function empty(el: HTMLElement)
1322 // Removes all of `el`'s children elements from `el`
1323 empty: function (el) {
1324 while (el.firstChild) {
1325 el.removeChild(el.firstChild);
1329 // @function toFront(el: HTMLElement)
1330 // Makes `el` the last children of its parent, so it renders in front of the other children.
1331 toFront: function (el) {
1332 el.parentNode.appendChild(el);
1335 // @function toBack(el: HTMLElement)
1336 // Makes `el` the first children of its parent, so it renders back from the other children.
1337 toBack: function (el) {
1338 var parent = el.parentNode;
1339 parent.insertBefore(el, parent.firstChild);
1342 // @function hasClass(el: HTMLElement, name: String): Boolean
1343 // Returns `true` if the element's class attribute contains `name`.
1344 hasClass: function (el, name) {
1345 if (el.classList !== undefined) {
1346 return el.classList.contains(name);
1348 var className = L.DomUtil.getClass(el);
1349 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
1352 // @function addClass(el: HTMLElement, name: String)
1353 // Adds `name` to the element's class attribute.
1354 addClass: function (el, name) {
1355 if (el.classList !== undefined) {
1356 var classes = L.Util.splitWords(name);
1357 for (var i = 0, len = classes.length; i < len; i++) {
1358 el.classList.add(classes[i]);
1360 } else if (!L.DomUtil.hasClass(el, name)) {
1361 var className = L.DomUtil.getClass(el);
1362 L.DomUtil.setClass(el, (className ? className + ' ' : '') + name);
1366 // @function removeClass(el: HTMLElement, name: String)
1367 // Removes `name` from the element's class attribute.
1368 removeClass: function (el, name) {
1369 if (el.classList !== undefined) {
1370 el.classList.remove(name);
1372 L.DomUtil.setClass(el, L.Util.trim((' ' + L.DomUtil.getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
1376 // @function setClass(el: HTMLElement, name: String)
1377 // Sets the element's class.
1378 setClass: function (el, name) {
1379 if (el.className.baseVal === undefined) {
1380 el.className = name;
1382 // in case of SVG element
1383 el.className.baseVal = name;
1387 // @function getClass(el: HTMLElement): String
1388 // Returns the element's class.
1389 getClass: function (el) {
1390 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
1393 // @function setOpacity(el: HTMLElement, opacity: Number)
1394 // Set the opacity of an element (including old IE support).
1395 // `opacity` must be a number from `0` to `1`.
1396 setOpacity: function (el, value) {
1398 if ('opacity' in el.style) {
1399 el.style.opacity = value;
1401 } else if ('filter' in el.style) {
1402 L.DomUtil._setOpacityIE(el, value);
1406 _setOpacityIE: function (el, value) {
1408 filterName = 'DXImageTransform.Microsoft.Alpha';
1410 // filters collection throws an error if we try to retrieve a filter that doesn't exist
1412 filter = el.filters.item(filterName);
1414 // don't set opacity to 1 if we haven't already set an opacity,
1415 // it isn't needed and breaks transparent pngs.
1416 if (value === 1) { return; }
1419 value = Math.round(value * 100);
1422 filter.Enabled = (value !== 100);
1423 filter.Opacity = value;
1425 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
1429 // @function testProp(props: String[]): String|false
1430 // Goes through the array of style names and returns the first name
1431 // that is a valid style name for an element. If no such name is found,
1432 // it returns false. Useful for vendor-prefixed styles like `transform`.
1433 testProp: function (props) {
1435 var style = document.documentElement.style;
1437 for (var i = 0; i < props.length; i++) {
1438 if (props[i] in style) {
1445 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
1446 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
1447 // and optionally scaled by `scale`. Does not have an effect if the
1448 // browser doesn't support 3D CSS transforms.
1449 setTransform: function (el, offset, scale) {
1450 var pos = offset || new L.Point(0, 0);
1452 el.style[L.DomUtil.TRANSFORM] =
1454 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
1455 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
1456 (scale ? ' scale(' + scale + ')' : '');
1459 // @function setPosition(el: HTMLElement, position: Point)
1460 // Sets the position of `el` to coordinates specified by `position`,
1461 // using CSS translate or top/left positioning depending on the browser
1462 // (used by Leaflet internally to position its layers).
1463 setPosition: function (el, point) { // (HTMLElement, Point[, Boolean])
1466 el._leaflet_pos = point;
1469 if (L.Browser.any3d) {
1470 L.DomUtil.setTransform(el, point);
1472 el.style.left = point.x + 'px';
1473 el.style.top = point.y + 'px';
1477 // @function getPosition(el: HTMLElement): Point
1478 // Returns the coordinates of an element previously positioned with setPosition.
1479 getPosition: function (el) {
1480 // this method is only used for elements previously positioned using setPosition,
1481 // so it's safe to cache the position for performance
1483 return el._leaflet_pos || new L.Point(0, 0);
1489 // prefix style property names
1491 // @property TRANSFORM: String
1492 // Vendor-prefixed fransform style name (e.g. `'webkitTransform'` for WebKit).
1493 L.DomUtil.TRANSFORM = L.DomUtil.testProp(
1494 ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
1497 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
1498 // the same for the transitionend event, in particular the Android 4.1 stock browser
1500 // @property TRANSITION: String
1501 // Vendor-prefixed transform style name.
1502 var transition = L.DomUtil.TRANSITION = L.DomUtil.testProp(
1503 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
1505 L.DomUtil.TRANSITION_END =
1506 transition === 'webkitTransition' || transition === 'OTransition' ? transition + 'End' : 'transitionend';
1508 // @function disableTextSelection()
1509 // Prevents the user from generating `selectstart` DOM events, usually generated
1510 // when the user drags the mouse through a page with text. Used internally
1511 // by Leaflet to override the behaviour of any click-and-drag interaction on
1512 // the map. Affects drag interactions on the whole document.
1514 // @function enableTextSelection()
1515 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
1516 if ('onselectstart' in document) {
1517 L.DomUtil.disableTextSelection = function () {
1518 L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);
1520 L.DomUtil.enableTextSelection = function () {
1521 L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);
1525 var userSelectProperty = L.DomUtil.testProp(
1526 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
1528 L.DomUtil.disableTextSelection = function () {
1529 if (userSelectProperty) {
1530 var style = document.documentElement.style;
1531 this._userSelect = style[userSelectProperty];
1532 style[userSelectProperty] = 'none';
1535 L.DomUtil.enableTextSelection = function () {
1536 if (userSelectProperty) {
1537 document.documentElement.style[userSelectProperty] = this._userSelect;
1538 delete this._userSelect;
1543 // @function disableImageDrag()
1544 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
1545 // for `dragstart` DOM events, usually generated when the user drags an image.
1546 L.DomUtil.disableImageDrag = function () {
1547 L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault);
1550 // @function enableImageDrag()
1551 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
1552 L.DomUtil.enableImageDrag = function () {
1553 L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault);
1556 // @function preventOutline(el: HTMLElement)
1557 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
1558 // of the element `el` invisible. Used internally by Leaflet to prevent
1559 // focusable elements from displaying an outline when the user performs a
1560 // drag interaction on them.
1561 L.DomUtil.preventOutline = function (element) {
1562 while (element.tabIndex === -1) {
1563 element = element.parentNode;
1565 if (!element || !element.style) { return; }
1566 L.DomUtil.restoreOutline();
1567 this._outlineElement = element;
1568 this._outlineStyle = element.style.outline;
1569 element.style.outline = 'none';
1570 L.DomEvent.on(window, 'keydown', L.DomUtil.restoreOutline, this);
1573 // @function restoreOutline()
1574 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
1575 L.DomUtil.restoreOutline = function () {
1576 if (!this._outlineElement) { return; }
1577 this._outlineElement.style.outline = this._outlineStyle;
1578 delete this._outlineElement;
1579 delete this._outlineStyle;
1580 L.DomEvent.off(window, 'keydown', L.DomUtil.restoreOutline, this);
1589 * Represents a geographical point with a certain latitude and longitude.
1594 * var latlng = L.latLng(50.5, 30.5);
1597 * All Leaflet methods that accept LatLng objects also accept them in a simple Array form and simple object form (unless noted otherwise), so these lines are equivalent:
1600 * map.panTo([50, 30]);
1601 * map.panTo({lon: 30, lat: 50});
1602 * map.panTo({lat: 50, lng: 30});
1603 * map.panTo(L.latLng(50, 30));
1607 L.LatLng = function (lat, lng, alt) {
1608 if (isNaN(lat) || isNaN(lng)) {
1609 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1612 // @property lat: Number
1613 // Latitude in degrees
1616 // @property lng: Number
1617 // Longitude in degrees
1620 // @property alt: Number
1621 // Altitude in meters (optional)
1622 if (alt !== undefined) {
1627 L.LatLng.prototype = {
1628 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1629 // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overriden by setting `maxMargin` to a small number.
1630 equals: function (obj, maxMargin) {
1631 if (!obj) { return false; }
1633 obj = L.latLng(obj);
1635 var margin = Math.max(
1636 Math.abs(this.lat - obj.lat),
1637 Math.abs(this.lng - obj.lng));
1639 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1642 // @method toString(): String
1643 // Returns a string representation of the point (for debugging purposes).
1644 toString: function (precision) {
1646 L.Util.formatNum(this.lat, precision) + ', ' +
1647 L.Util.formatNum(this.lng, precision) + ')';
1650 // @method distanceTo(otherLatLng: LatLng): Number
1651 // Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula).
1652 distanceTo: function (other) {
1653 return L.CRS.Earth.distance(this, L.latLng(other));
1656 // @method wrap(): LatLng
1657 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1659 return L.CRS.Earth.wrapLatLng(this);
1662 // @method toBounds(sizeInMeters: Number): LatLngBounds
1663 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters` meters apart from the `LatLng`.
1664 toBounds: function (sizeInMeters) {
1665 var latAccuracy = 180 * sizeInMeters / 40075017,
1666 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1668 return L.latLngBounds(
1669 [this.lat - latAccuracy, this.lng - lngAccuracy],
1670 [this.lat + latAccuracy, this.lng + lngAccuracy]);
1673 clone: function () {
1674 return new L.LatLng(this.lat, this.lng, this.alt);
1680 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1681 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1684 // @factory L.latLng(coords: Array): LatLng
1685 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1688 // @factory L.latLng(coords: Object): LatLng
1689 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1691 L.latLng = function (a, b, c) {
1692 if (a instanceof L.LatLng) {
1695 if (L.Util.isArray(a) && typeof a[0] !== 'object') {
1696 if (a.length === 3) {
1697 return new L.LatLng(a[0], a[1], a[2]);
1699 if (a.length === 2) {
1700 return new L.LatLng(a[0], a[1]);
1704 if (a === undefined || a === null) {
1707 if (typeof a === 'object' && 'lat' in a) {
1708 return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1710 if (b === undefined) {
1713 return new L.LatLng(a, b, c);
1719 * @class LatLngBounds
1720 * @aka L.LatLngBounds
1722 * Represents a rectangular geographical area on a map.
1727 * var southWest = L.latLng(40.712, -74.227),
1728 * northEast = L.latLng(40.774, -74.125),
1729 * bounds = L.latLngBounds(southWest, northEast);
1732 * All Leaflet methods that accept LatLngBounds objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
1736 * [40.712, -74.227],
1742 L.LatLngBounds = function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[])
1743 if (!southWest) { return; }
1745 var latlngs = northEast ? [southWest, northEast] : southWest;
1747 for (var i = 0, len = latlngs.length; i < len; i++) {
1748 this.extend(latlngs[i]);
1752 L.LatLngBounds.prototype = {
1754 // @method extend(latlng: LatLng): this
1755 // Extend the bounds to contain the given point
1758 // @method extend(otherBounds: LatLngBounds): this
1759 // Extend the bounds to contain the given bounds
1760 extend: function (obj) {
1761 var sw = this._southWest,
1762 ne = this._northEast,
1765 if (obj instanceof L.LatLng) {
1769 } else if (obj instanceof L.LatLngBounds) {
1770 sw2 = obj._southWest;
1771 ne2 = obj._northEast;
1773 if (!sw2 || !ne2) { return this; }
1776 return obj ? this.extend(L.latLng(obj) || L.latLngBounds(obj)) : this;
1780 this._southWest = new L.LatLng(sw2.lat, sw2.lng);
1781 this._northEast = new L.LatLng(ne2.lat, ne2.lng);
1783 sw.lat = Math.min(sw2.lat, sw.lat);
1784 sw.lng = Math.min(sw2.lng, sw.lng);
1785 ne.lat = Math.max(ne2.lat, ne.lat);
1786 ne.lng = Math.max(ne2.lng, ne.lng);
1792 // @method pad(bufferRatio: Number): LatLngBounds
1793 // Returns bigger bounds created by extending the current bounds by a given percentage in each direction.
1794 pad: function (bufferRatio) {
1795 var sw = this._southWest,
1796 ne = this._northEast,
1797 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1798 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1800 return new L.LatLngBounds(
1801 new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1802 new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1805 // @method getCenter(): LatLng
1806 // Returns the center point of the bounds.
1807 getCenter: function () {
1808 return new L.LatLng(
1809 (this._southWest.lat + this._northEast.lat) / 2,
1810 (this._southWest.lng + this._northEast.lng) / 2);
1813 // @method getSouthWest(): LatLng
1814 // Returns the south-west point of the bounds.
1815 getSouthWest: function () {
1816 return this._southWest;
1819 // @method getNorthEast(): LatLng
1820 // Returns the north-east point of the bounds.
1821 getNorthEast: function () {
1822 return this._northEast;
1825 // @method getNorthWest(): LatLng
1826 // Returns the north-west point of the bounds.
1827 getNorthWest: function () {
1828 return new L.LatLng(this.getNorth(), this.getWest());
1831 // @method getSouthEast(): LatLng
1832 // Returns the south-east point of the bounds.
1833 getSouthEast: function () {
1834 return new L.LatLng(this.getSouth(), this.getEast());
1837 // @method getWest(): Number
1838 // Returns the west longitude of the bounds
1839 getWest: function () {
1840 return this._southWest.lng;
1843 // @method getSouth(): Number
1844 // Returns the south latitude of the bounds
1845 getSouth: function () {
1846 return this._southWest.lat;
1849 // @method getEast(): Number
1850 // Returns the east longitude of the bounds
1851 getEast: function () {
1852 return this._northEast.lng;
1855 // @method getNorth(): Number
1856 // Returns the north latitude of the bounds
1857 getNorth: function () {
1858 return this._northEast.lat;
1861 // @method contains(otherBounds: LatLngBounds): Boolean
1862 // Returns `true` if the rectangle contains the given one.
1865 // @method contains (latlng: LatLng): Boolean
1866 // Returns `true` if the rectangle contains the given point.
1867 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1868 if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
1869 obj = L.latLng(obj);
1871 obj = L.latLngBounds(obj);
1874 var sw = this._southWest,
1875 ne = this._northEast,
1878 if (obj instanceof L.LatLngBounds) {
1879 sw2 = obj.getSouthWest();
1880 ne2 = obj.getNorthEast();
1885 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1886 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1889 // @method intersects(otherBounds: LatLngBounds): Boolean
1890 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1891 intersects: function (bounds) {
1892 bounds = L.latLngBounds(bounds);
1894 var sw = this._southWest,
1895 ne = this._northEast,
1896 sw2 = bounds.getSouthWest(),
1897 ne2 = bounds.getNorthEast(),
1899 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1900 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1902 return latIntersects && lngIntersects;
1905 // @method overlaps(otherBounds: Bounds): Boolean
1906 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1907 overlaps: function (bounds) {
1908 bounds = L.latLngBounds(bounds);
1910 var sw = this._southWest,
1911 ne = this._northEast,
1912 sw2 = bounds.getSouthWest(),
1913 ne2 = bounds.getNorthEast(),
1915 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1916 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1918 return latOverlaps && lngOverlaps;
1921 // @method toBBoxString(): String
1922 // Returns a string with bounding box coordinates in a 'southwest_lng,southwest_lat,northeast_lng,northeast_lat' format. Useful for sending requests to web services that return geo data.
1923 toBBoxString: function () {
1924 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1927 // @method equals(otherBounds: LatLngBounds): Boolean
1928 // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds.
1929 equals: function (bounds) {
1930 if (!bounds) { return false; }
1932 bounds = L.latLngBounds(bounds);
1934 return this._southWest.equals(bounds.getSouthWest()) &&
1935 this._northEast.equals(bounds.getNorthEast());
1938 // @method isValid(): Boolean
1939 // Returns `true` if the bounds are properly initialized.
1940 isValid: function () {
1941 return !!(this._southWest && this._northEast);
1945 // TODO International date line?
1947 // @factory L.latLngBounds(southWest: LatLng, northEast: LatLng)
1948 // Creates a `LatLngBounds` object by defining south-west and north-east corners of the rectangle.
1951 // @factory L.latLngBounds(latlngs: LatLng[])
1952 // Creates a `LatLngBounds` object defined by the geographical points it contains. Very useful for zooming the map to fit a particular set of locations with [`fitBounds`](#map-fitbounds).
1953 L.latLngBounds = function (a, b) {
1954 if (a instanceof L.LatLngBounds) {
1957 return new L.LatLngBounds(a, b);
1963 * @namespace Projection
1965 * Leaflet comes with a set of already defined Projections out of the box:
1967 * @projection L.Projection.LonLat
1969 * Equirectangular, or Plate Carree projection — the most simple projection,
1970 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
1971 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
1972 * `EPSG:3395` and `Simple` CRS.
1977 L.Projection.LonLat = {
1978 project: function (latlng) {
1979 return new L.Point(latlng.lng, latlng.lat);
1982 unproject: function (point) {
1983 return new L.LatLng(point.y, point.x);
1986 bounds: L.bounds([-180, -90], [180, 90])
1992 * @namespace Projection
1993 * @projection L.Projection.SphericalMercator
1995 * Spherical Mercator projection — the most common projection for online maps,
1996 * used by almost all free and commercial tile providers. Assumes that Earth is
1997 * a sphere. Used by the `EPSG:3857` CRS.
2000 L.Projection.SphericalMercator = {
2003 MAX_LATITUDE: 85.0511287798,
2005 project: function (latlng) {
2006 var d = Math.PI / 180,
2007 max = this.MAX_LATITUDE,
2008 lat = Math.max(Math.min(max, latlng.lat), -max),
2009 sin = Math.sin(lat * d);
2012 this.R * latlng.lng * d,
2013 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
2016 unproject: function (point) {
2017 var d = 180 / Math.PI;
2019 return new L.LatLng(
2020 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
2021 point.x * d / this.R);
2024 bounds: (function () {
2025 var d = 6378137 * Math.PI;
2026 return L.bounds([-d, -d], [d, d]);
2035 * Abstract class that defines coordinate reference systems for projecting
2036 * geographical points into pixel (screen) coordinates and back (and to
2037 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
2038 * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
2040 * Leaflet defines the most usual CRSs by default. If you want to use a
2041 * CRS not defined by default, take a look at the
2042 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
2046 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
2047 // Projects geographical coordinates into pixel coordinates for a given zoom.
2048 latLngToPoint: function (latlng, zoom) {
2049 var projectedPoint = this.projection.project(latlng),
2050 scale = this.scale(zoom);
2052 return this.transformation._transform(projectedPoint, scale);
2055 // @method pointToLatLng(point: Point, zoom: Number): LatLng
2056 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
2057 // zoom into geographical coordinates.
2058 pointToLatLng: function (point, zoom) {
2059 var scale = this.scale(zoom),
2060 untransformedPoint = this.transformation.untransform(point, scale);
2062 return this.projection.unproject(untransformedPoint);
2065 // @method project(latlng: LatLng): Point
2066 // Projects geographical coordinates into coordinates in units accepted for
2067 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
2068 project: function (latlng) {
2069 return this.projection.project(latlng);
2072 // @method unproject(point: Point): LatLng
2073 // Given a projected coordinate returns the corresponding LatLng.
2074 // The inverse of `project`.
2075 unproject: function (point) {
2076 return this.projection.unproject(point);
2079 // @method scale(zoom: Number): Number
2080 // Returns the scale used when transforming projected coordinates into
2081 // pixel coordinates for a particular zoom. For example, it returns
2082 // `256 * 2^zoom` for Mercator-based CRS.
2083 scale: function (zoom) {
2084 return 256 * Math.pow(2, zoom);
2087 // @method zoom(scale: Number): Number
2088 // Inverse of `scale()`, returns the zoom level corresponding to a scale
2089 // factor of `scale`.
2090 zoom: function (scale) {
2091 return Math.log(scale / 256) / Math.LN2;
2094 // @method getProjectedBounds(zoom: Number): Bounds
2095 // Returns the projection's bounds scaled and transformed for the provided `zoom`.
2096 getProjectedBounds: function (zoom) {
2097 if (this.infinite) { return null; }
2099 var b = this.projection.bounds,
2100 s = this.scale(zoom),
2101 min = this.transformation.transform(b.min, s),
2102 max = this.transformation.transform(b.max, s);
2104 return L.bounds(min, max);
2107 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
2108 // Returns the distance between two geographical coordinates.
2110 // @property code: String
2111 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
2113 // @property wrapLng: Number[]
2114 // An array of two numbers defining whether the longitude (horizontal) coordinate
2115 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
2116 // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
2118 // @property wrapLat: Number[]
2119 // Like `wrapLng`, but for the latitude (vertical) axis.
2121 // wrapLng: [min, max],
2122 // wrapLat: [min, max],
2124 // @property infinite: Boolean
2125 // If true, the coordinate space will be unbounded (infinite in both axes)
2128 // @method wrapLatLng(latlng: LatLng): LatLng
2129 // Returns a `LatLng` where lat and lng has been wrapped according to the
2130 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
2131 wrapLatLng: function (latlng) {
2132 var lng = this.wrapLng ? L.Util.wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
2133 lat = this.wrapLat ? L.Util.wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
2136 return L.latLng(lat, lng, alt);
2146 * A simple CRS that maps longitude and latitude into `x` and `y` directly.
2147 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
2148 * axis should still be inverted (going from bottom to top). `distance()` returns
2149 * simple euclidean distance.
2152 L.CRS.Simple = L.extend({}, L.CRS, {
2153 projection: L.Projection.LonLat,
2154 transformation: new L.Transformation(1, 0, -1, 0),
2156 scale: function (zoom) {
2157 return Math.pow(2, zoom);
2160 zoom: function (scale) {
2161 return Math.log(scale) / Math.LN2;
2164 distance: function (latlng1, latlng2) {
2165 var dx = latlng2.lng - latlng1.lng,
2166 dy = latlng2.lat - latlng1.lat;
2168 return Math.sqrt(dx * dx + dy * dy);
2180 * Serves as the base for CRS that are global such that they cover the earth.
2181 * Can only be used as the base for other CRS and cannot be used directly,
2182 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
2186 L.CRS.Earth = L.extend({}, L.CRS, {
2187 wrapLng: [-180, 180],
2189 // Mean Earth Radius, as recommended for use by
2190 // the International Union of Geodesy and Geophysics,
2191 // see http://rosettacode.org/wiki/Haversine_formula
2194 // distance between two geographical points using spherical law of cosines approximation
2195 distance: function (latlng1, latlng2) {
2196 var rad = Math.PI / 180,
2197 lat1 = latlng1.lat * rad,
2198 lat2 = latlng2.lat * rad,
2199 a = Math.sin(lat1) * Math.sin(lat2) +
2200 Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad);
2202 return this.R * Math.acos(Math.min(a, 1));
2210 * @crs L.CRS.EPSG3857
2212 * The most common CRS for online maps, used by almost all free and commercial
2213 * tile providers. Uses Spherical Mercator projection. Set in by default in
2214 * Map's `crs` option.
2217 L.CRS.EPSG3857 = L.extend({}, L.CRS.Earth, {
2219 projection: L.Projection.SphericalMercator,
2221 transformation: (function () {
2222 var scale = 0.5 / (Math.PI * L.Projection.SphericalMercator.R);
2223 return new L.Transformation(scale, 0.5, -scale, 0.5);
2227 L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
2235 * @crs L.CRS.EPSG4326
2237 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
2240 L.CRS.EPSG4326 = L.extend({}, L.CRS.Earth, {
2242 projection: L.Projection.LonLat,
2243 transformation: new L.Transformation(1 / 180, 1, -1 / 180, 0.5)
2253 * The central class of the API — it is used to create a map on a page and manipulate it.
2258 * // initialize the map on the "map" div with a given center and zoom
2259 * var map = L.map('map', {
2260 * center: [51.505, -0.09],
2267 L.Map = L.Evented.extend({
2270 // @section Map State Options
2271 // @option crs: CRS = L.CRS.EPSG3857
2272 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
2273 // sure what it means.
2274 crs: L.CRS.EPSG3857,
2276 // @option center: LatLng = undefined
2277 // Initial geographic center of the map
2280 // @option zoom: Number = undefined
2281 // Initial map zoom level
2284 // @option minZoom: Number = undefined
2285 // Minimum zoom level of the map. Overrides any `minZoom` option set on map layers.
2288 // @option maxZoom: Number = undefined
2289 // Maximum zoom level of the map. Overrides any `maxZoom` option set on map layers.
2292 // @option layers: Layer[] = []
2293 // Array of layers that will be added to the map initially
2296 // @option maxBounds: LatLngBounds = null
2297 // When this option is set, the map restricts the view to the given
2298 // geographical bounds, bouncing the user back when he tries to pan
2299 // outside the view. To set the restriction dynamically, use
2300 // [`setMaxBounds`](#map-setmaxbounds) method.
2301 maxBounds: undefined,
2303 // @option renderer: Renderer = *
2304 // The default method for drawing vector layers on the map. `L.SVG`
2305 // or `L.Canvas` by default depending on browser support.
2306 renderer: undefined,
2309 // @section Animation Options
2310 // @option fadeAnimation: Boolean = true
2311 // Whether the tile fade animation is enabled. By default it's enabled
2312 // in all browsers that support CSS3 Transitions except Android.
2313 fadeAnimation: true,
2315 // @option markerZoomAnimation: Boolean = true
2316 // Whether markers animate their zoom with the zoom animation, if disabled
2317 // they will disappear for the length of the animation. By default it's
2318 // enabled in all browsers that support CSS3 Transitions except Android.
2319 markerZoomAnimation: true,
2321 // @option transform3DLimit: Number = 2^23
2322 // Defines the maximum size of a CSS translation transform. The default
2323 // value should not be changed unless a web browser positions layers in
2324 // the wrong place after doing a large `panBy`.
2325 transform3DLimit: 8388608, // Precision limit of a 32-bit float
2327 // @section Interaction Options
2328 // @option zoomSnap: Number = 1
2329 // Forces the map's zoom level to always be a multiple of this, particularly
2330 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
2331 // By default, the zoom level snaps to the nearest integer; lower values
2332 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
2333 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
2336 // @option zoomDelta: Number = 1
2337 // Controls how much the map's zoom level will change after a
2338 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
2339 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
2340 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
2343 // @option trackResize: Boolean = true
2344 // Whether the map automatically handles browser window resize to update itself.
2348 initialize: function (id, options) { // (HTMLElement or String, Object)
2349 options = L.setOptions(this, options);
2351 this._initContainer(id);
2354 // hack for https://github.com/Leaflet/Leaflet/issues/1980
2355 this._onResize = L.bind(this._onResize, this);
2359 if (options.maxBounds) {
2360 this.setMaxBounds(options.maxBounds);
2363 if (options.zoom !== undefined) {
2364 this._zoom = this._limitZoom(options.zoom);
2367 if (options.center && options.zoom !== undefined) {
2368 this.setView(L.latLng(options.center), options.zoom, {reset: true});
2371 this._handlers = [];
2373 this._zoomBoundLayers = {};
2374 this._sizeChanged = true;
2376 this.callInitHooks();
2378 this._addLayers(this.options.layers);
2382 // @section Methods for modifying map state
2384 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
2385 // Sets the view of the map (geographical center and zoom) with the given
2386 // animation options.
2387 setView: function (center, zoom) {
2388 // replaced by animation-powered implementation in Map.PanAnimation.js
2389 zoom = zoom === undefined ? this.getZoom() : zoom;
2390 this._resetView(L.latLng(center), zoom);
2394 // @method setZoom(zoom: Number, options: Zoom/pan options): this
2395 // Sets the zoom of the map.
2396 setZoom: function (zoom, options) {
2397 if (!this._loaded) {
2401 return this.setView(this.getCenter(), zoom, {zoom: options});
2404 // @method zoomIn(delta?: Number, options?: Zoom options): this
2405 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
2406 zoomIn: function (delta, options) {
2407 delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
2408 return this.setZoom(this._zoom + delta, options);
2411 // @method zoomOut(delta?: Number, options?: Zoom options): this
2412 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
2413 zoomOut: function (delta, options) {
2414 delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
2415 return this.setZoom(this._zoom - delta, options);
2418 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
2419 // Zooms the map while keeping a specified geographical point on the map
2420 // stationary (e.g. used internally for scroll zoom and double-click zoom).
2422 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
2423 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
2424 setZoomAround: function (latlng, zoom, options) {
2425 var scale = this.getZoomScale(zoom),
2426 viewHalf = this.getSize().divideBy(2),
2427 containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng),
2429 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
2430 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
2432 return this.setView(newCenter, zoom, {zoom: options});
2435 _getBoundsCenterZoom: function (bounds, options) {
2437 options = options || {};
2438 bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds);
2440 var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]),
2441 paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]),
2443 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
2445 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
2447 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
2449 swPoint = this.project(bounds.getSouthWest(), zoom),
2450 nePoint = this.project(bounds.getNorthEast(), zoom),
2451 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
2459 // @method fitBounds(bounds: LatLngBounds, options: fitBounds options): this
2460 // Sets a map view that contains the given geographical bounds with the
2461 // maximum zoom level possible.
2462 fitBounds: function (bounds, options) {
2464 bounds = L.latLngBounds(bounds);
2466 if (!bounds.isValid()) {
2467 throw new Error('Bounds are not valid.');
2470 var target = this._getBoundsCenterZoom(bounds, options);
2471 return this.setView(target.center, target.zoom, options);
2474 // @method fitWorld(options?: fitBounds options): this
2475 // Sets a map view that mostly contains the whole world with the maximum
2476 // zoom level possible.
2477 fitWorld: function (options) {
2478 return this.fitBounds([[-90, -180], [90, 180]], options);
2481 // @method panTo(latlng: LatLng, options?: Pan options): this
2482 // Pans the map to a given center.
2483 panTo: function (center, options) { // (LatLng)
2484 return this.setView(center, this._zoom, {pan: options});
2487 // @method panBy(offset: Point): this
2488 // Pans the map by a given number of pixels (animated).
2489 panBy: function (offset) { // (Point)
2490 // replaced with animated panBy in Map.PanAnimation.js
2491 this.fire('movestart');
2493 this._rawPanBy(L.point(offset));
2496 return this.fire('moveend');
2499 // @method setMaxBounds(bounds: Bounds): this
2500 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
2501 setMaxBounds: function (bounds) {
2502 bounds = L.latLngBounds(bounds);
2504 if (!bounds.isValid()) {
2505 this.options.maxBounds = null;
2506 return this.off('moveend', this._panInsideMaxBounds);
2507 } else if (this.options.maxBounds) {
2508 this.off('moveend', this._panInsideMaxBounds);
2511 this.options.maxBounds = bounds;
2514 this._panInsideMaxBounds();
2517 return this.on('moveend', this._panInsideMaxBounds);
2520 // @method setMinZoom(zoom: Number): this
2521 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
2522 setMinZoom: function (zoom) {
2523 this.options.minZoom = zoom;
2525 if (this._loaded && this.getZoom() < this.options.minZoom) {
2526 return this.setZoom(zoom);
2532 // @method setMaxZoom(zoom: Number): this
2533 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
2534 setMaxZoom: function (zoom) {
2535 this.options.maxZoom = zoom;
2537 if (this._loaded && (this.getZoom() > this.options.maxZoom)) {
2538 return this.setZoom(zoom);
2544 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
2545 // Pans the map to the closest view that would lie inside the given bounds (if it's not already), controlling the animation using the options specific, if any.
2546 panInsideBounds: function (bounds, options) {
2547 this._enforcingBounds = true;
2548 var center = this.getCenter(),
2549 newCenter = this._limitCenter(center, this._zoom, L.latLngBounds(bounds));
2551 if (!center.equals(newCenter)) {
2552 this.panTo(newCenter, options);
2555 this._enforcingBounds = false;
2559 // @method invalidateSize(options: Zoom/Pan options): this
2560 // Checks if the map container size changed and updates the map if so —
2561 // call it after you've changed the map size dynamically, also animating
2562 // pan by default. If `options.pan` is `false`, panning will not occur.
2563 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
2564 // that it doesn't happen often even if the method is called many
2568 // @method invalidateSize(animate: Boolean): this
2569 // Checks if the map container size changed and updates the map if so —
2570 // call it after you've changed the map size dynamically, also animating
2572 invalidateSize: function (options) {
2573 if (!this._loaded) { return this; }
2575 options = L.extend({
2578 }, options === true ? {animate: true} : options);
2580 var oldSize = this.getSize();
2581 this._sizeChanged = true;
2582 this._lastCenter = null;
2584 var newSize = this.getSize(),
2585 oldCenter = oldSize.divideBy(2).round(),
2586 newCenter = newSize.divideBy(2).round(),
2587 offset = oldCenter.subtract(newCenter);
2589 if (!offset.x && !offset.y) { return this; }
2591 if (options.animate && options.pan) {
2596 this._rawPanBy(offset);
2601 if (options.debounceMoveend) {
2602 clearTimeout(this._sizeTimer);
2603 this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
2605 this.fire('moveend');
2609 // @section Map state change events
2610 // @event resize: ResizeEvent
2611 // Fired when the map is resized.
2612 return this.fire('resize', {
2618 // @section Methods for modifying map state
2619 // @method stop(): this
2620 // Stops the currently running `panTo` or `flyTo` animation, if any.
2622 this.setZoom(this._limitZoom(this._zoom));
2623 if (!this.options.zoomSnap) {
2624 this.fire('viewreset');
2626 return this._stop();
2630 // TODO handler.addTo
2631 // TODO Appropiate docs section?
2632 // @section Other Methods
2633 // @method addHandler(name: String, HandlerClass: Function): this
2634 // Adds a new `Handler` to the map, given its name and constructor function.
2635 addHandler: function (name, HandlerClass) {
2636 if (!HandlerClass) { return this; }
2638 var handler = this[name] = new HandlerClass(this);
2640 this._handlers.push(handler);
2642 if (this.options[name]) {
2649 // @method remove(): this
2650 // Destroys the map and clears all related event listeners.
2651 remove: function () {
2653 this._initEvents(true);
2655 if (this._containerId !== this._container._leaflet_id) {
2656 throw new Error('Map container is being reused by another instance');
2660 // throws error in IE6-8
2661 delete this._container._leaflet_id;
2662 delete this._containerId;
2665 this._container._leaflet_id = undefined;
2667 this._containerId = undefined;
2670 L.DomUtil.remove(this._mapPane);
2672 if (this._clearControlPos) {
2673 this._clearControlPos();
2676 this._clearHandlers();
2679 // @section Map state change events
2680 // @event unload: Event
2681 // Fired when the map is destroyed with [remove](#map-remove) method.
2682 this.fire('unload');
2685 for (var i in this._layers) {
2686 this._layers[i].remove();
2692 // @section Other Methods
2693 // @method createPane(name: String, container?: HTMLElement): HTMLElement
2694 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
2695 // then returns it. The pane is created as a children of `container`, or
2696 // as a children of the main map pane if not set.
2697 createPane: function (name, container) {
2698 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
2699 pane = L.DomUtil.create('div', className, container || this._mapPane);
2702 this._panes[name] = pane;
2707 // @section Methods for Getting Map State
2709 // @method getCenter(): LatLng
2710 // Returns the geographical center of the map view
2711 getCenter: function () {
2712 this._checkIfLoaded();
2714 if (this._lastCenter && !this._moved()) {
2715 return this._lastCenter;
2717 return this.layerPointToLatLng(this._getCenterLayerPoint());
2720 // @method getZoom(): Number
2721 // Returns the current zoom level of the map view
2722 getZoom: function () {
2726 // @method getBounds(): LatLngBounds
2727 // Returns the geographical bounds visible in the current map view
2728 getBounds: function () {
2729 var bounds = this.getPixelBounds(),
2730 sw = this.unproject(bounds.getBottomLeft()),
2731 ne = this.unproject(bounds.getTopRight());
2733 return new L.LatLngBounds(sw, ne);
2736 // @method getMinZoom(): Number
2737 // Returns the minimum zoom level of the map (if set in the `minZoom` option of the map or of any layers), or `0` by default.
2738 getMinZoom: function () {
2739 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
2742 // @method getMaxZoom(): Number
2743 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
2744 getMaxZoom: function () {
2745 return this.options.maxZoom === undefined ?
2746 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
2747 this.options.maxZoom;
2750 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number
2751 // Returns the maximum zoom level on which the given bounds fit to the map
2752 // view in its entirety. If `inside` (optional) is set to `true`, the method
2753 // instead returns the minimum zoom level on which the map view fits into
2754 // the given bounds in its entirety.
2755 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
2756 bounds = L.latLngBounds(bounds);
2757 padding = L.point(padding || [0, 0]);
2759 var zoom = this.getZoom() || 0,
2760 min = this.getMinZoom(),
2761 max = this.getMaxZoom(),
2762 nw = bounds.getNorthWest(),
2763 se = bounds.getSouthEast(),
2764 size = this.getSize().subtract(padding),
2765 boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)),
2766 snap = L.Browser.any3d ? this.options.zoomSnap : 1;
2768 var scale = Math.min(size.x / boundsSize.x, size.y / boundsSize.y);
2769 zoom = this.getScaleZoom(scale, zoom);
2772 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
2773 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
2776 return Math.max(min, Math.min(max, zoom));
2779 // @method getSize(): Point
2780 // Returns the current size of the map container (in pixels).
2781 getSize: function () {
2782 if (!this._size || this._sizeChanged) {
2783 this._size = new L.Point(
2784 this._container.clientWidth,
2785 this._container.clientHeight);
2787 this._sizeChanged = false;
2789 return this._size.clone();
2792 // @method getPixelBounds(): Bounds
2793 // Returns the bounds of the current map view in projected pixel
2794 // coordinates (sometimes useful in layer and overlay implementations).
2795 getPixelBounds: function (center, zoom) {
2796 var topLeftPoint = this._getTopLeftPoint(center, zoom);
2797 return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
2800 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
2801 // the map pane? "left point of the map layer" can be confusing, specially
2802 // since there can be negative offsets.
2803 // @method getPixelOrigin(): Point
2804 // Returns the projected pixel coordinates of the top left point of
2805 // the map layer (useful in custom layer and overlay implementations).
2806 getPixelOrigin: function () {
2807 this._checkIfLoaded();
2808 return this._pixelOrigin;
2811 // @method getPixelWorldBounds(zoom?: Number): Bounds
2812 // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
2813 // If `zoom` is omitted, the map's current zoom level is used.
2814 getPixelWorldBounds: function (zoom) {
2815 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
2818 // @section Other Methods
2820 // @method getPane(pane: String|HTMLElement): HTMLElement
2821 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
2822 getPane: function (pane) {
2823 return typeof pane === 'string' ? this._panes[pane] : pane;
2826 // @method getPanes(): Object
2827 // Returns a plain object containing the names of all [panes](#map-pane) as keys and
2828 // the panes as values.
2829 getPanes: function () {
2833 // @method getContainer: HTMLElement
2834 // Returns the HTML element that contains the map.
2835 getContainer: function () {
2836 return this._container;
2840 // @section Conversion Methods
2842 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
2843 // Returns the scale factor to be applied to a map transition from zoom level
2844 // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
2845 getZoomScale: function (toZoom, fromZoom) {
2846 // TODO replace with universal implementation after refactoring projections
2847 var crs = this.options.crs;
2848 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
2849 return crs.scale(toZoom) / crs.scale(fromZoom);
2852 // @method getScaleZoom(scale: Number, fromZoom: Number): Number
2853 // Returns the zoom level that the map would end up at, if it is at `fromZoom`
2854 // level and everything is scaled by a factor of `scale`. Inverse of
2855 // [`getZoomScale`](#map-getZoomScale).
2856 getScaleZoom: function (scale, fromZoom) {
2857 var crs = this.options.crs;
2858 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
2859 var zoom = crs.zoom(scale * crs.scale(fromZoom));
2860 return isNaN(zoom) ? Infinity : zoom;
2863 // @method project(latlng: LatLng, zoom: Number): Point
2864 // Projects a geographical coordinate `LatLng` according to the projection
2865 // of the map's CRS, then scales it according to `zoom` and the CRS's
2866 // `Transformation`. The result is pixel coordinate relative to
2868 project: function (latlng, zoom) {
2869 zoom = zoom === undefined ? this._zoom : zoom;
2870 return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
2873 // @method unproject(point: Point, zoom: Number): LatLng
2874 // Inverse of [`project`](#map-project).
2875 unproject: function (point, zoom) {
2876 zoom = zoom === undefined ? this._zoom : zoom;
2877 return this.options.crs.pointToLatLng(L.point(point), zoom);
2880 // @method layerPointToLatLng(point: Point): LatLng
2881 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
2882 // returns the corresponding geographical coordinate (for the current zoom level).
2883 layerPointToLatLng: function (point) {
2884 var projectedPoint = L.point(point).add(this.getPixelOrigin());
2885 return this.unproject(projectedPoint);
2888 // @method latLngToLayerPoint(latlng: LatLng): Point
2889 // Given a geographical coordinate, returns the corresponding pixel coordinate
2890 // relative to the [origin pixel](#map-getpixelorigin).
2891 latLngToLayerPoint: function (latlng) {
2892 var projectedPoint = this.project(L.latLng(latlng))._round();
2893 return projectedPoint._subtract(this.getPixelOrigin());
2896 // @method wrapLatLng(latlng: LatLng): LatLng
2897 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
2898 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
2900 // By default this means longitude is wrapped around the dateline so its
2901 // value is between -180 and +180 degrees.
2902 wrapLatLng: function (latlng) {
2903 return this.options.crs.wrapLatLng(L.latLng(latlng));
2906 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
2907 // Returns the distance between two geographical coordinates according to
2908 // the map's CRS. By default this measures distance in meters.
2909 distance: function (latlng1, latlng2) {
2910 return this.options.crs.distance(L.latLng(latlng1), L.latLng(latlng2));
2913 // @method containerPointToLayerPoint(point: Point): Point
2914 // Given a pixel coordinate relative to the map container, returns the corresponding
2915 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
2916 containerPointToLayerPoint: function (point) { // (Point)
2917 return L.point(point).subtract(this._getMapPanePos());
2920 // @method layerPointToContainerPoint(point: Point): Point
2921 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
2922 // returns the corresponding pixel coordinate relative to the map container.
2923 layerPointToContainerPoint: function (point) { // (Point)
2924 return L.point(point).add(this._getMapPanePos());
2927 // @method containerPointToLatLng(point: Point): Point
2928 // Given a pixel coordinate relative to the map container, returns
2929 // the corresponding geographical coordinate (for the current zoom level).
2930 containerPointToLatLng: function (point) {
2931 var layerPoint = this.containerPointToLayerPoint(L.point(point));
2932 return this.layerPointToLatLng(layerPoint);
2935 // @method latLngToContainerPoint(latlng: LatLng): Point
2936 // Given a geographical coordinate, returns the corresponding pixel coordinate
2937 // relative to the map container.
2938 latLngToContainerPoint: function (latlng) {
2939 return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
2942 // @method mouseEventToContainerPoint(ev: MouseEvent): Point
2943 // Given a MouseEvent object, returns the pixel coordinate relative to the
2944 // map container where the event took place.
2945 mouseEventToContainerPoint: function (e) {
2946 return L.DomEvent.getMousePosition(e, this._container);
2949 // @method mouseEventToLayerPoint(ev: MouseEvent): Point
2950 // Given a MouseEvent object, returns the pixel coordinate relative to
2951 // the [origin pixel](#map-getpixelorigin) where the event took place.
2952 mouseEventToLayerPoint: function (e) {
2953 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
2956 // @method mouseEventToLatLng(ev: MouseEvent): LatLng
2957 // Given a MouseEvent object, returns geographical coordinate where the
2958 // event took place.
2959 mouseEventToLatLng: function (e) { // (MouseEvent)
2960 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
2964 // map initialization methods
2966 _initContainer: function (id) {
2967 var container = this._container = L.DomUtil.get(id);
2970 throw new Error('Map container not found.');
2971 } else if (container._leaflet_id) {
2972 throw new Error('Map container is already initialized.');
2975 L.DomEvent.addListener(container, 'scroll', this._onScroll, this);
2976 this._containerId = L.Util.stamp(container);
2979 _initLayout: function () {
2980 var container = this._container;
2982 this._fadeAnimated = this.options.fadeAnimation && L.Browser.any3d;
2984 L.DomUtil.addClass(container, 'leaflet-container' +
2985 (L.Browser.touch ? ' leaflet-touch' : '') +
2986 (L.Browser.retina ? ' leaflet-retina' : '') +
2987 (L.Browser.ielt9 ? ' leaflet-oldie' : '') +
2988 (L.Browser.safari ? ' leaflet-safari' : '') +
2989 (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
2991 var position = L.DomUtil.getStyle(container, 'position');
2993 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
2994 container.style.position = 'relative';
2999 if (this._initControlPos) {
3000 this._initControlPos();
3004 _initPanes: function () {
3005 var panes = this._panes = {};
3006 this._paneRenderers = {};
3010 // Panes are DOM elements used to control the ordering of layers on the map. You
3011 // can access panes with [`map.getPane`](#map-getpane) or
3012 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
3013 // [`map.createPane`](#map-createpane) method.
3015 // Every map has the following default panes that differ only in zIndex.
3017 // @pane mapPane: HTMLElement = 'auto'
3018 // Pane that contains all other map panes
3020 this._mapPane = this.createPane('mapPane', this._container);
3021 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
3023 // @pane tilePane: HTMLElement = 200
3024 // Pane for `GridLayer`s and `TileLayer`s
3025 this.createPane('tilePane');
3026 // @pane overlayPane: HTMLElement = 400
3027 // Pane for vector overlays (`Path`s), like `Polyline`s and `Polygon`s
3028 this.createPane('shadowPane');
3029 // @pane shadowPane: HTMLElement = 500
3030 // Pane for overlay shadows (e.g. `Marker` shadows)
3031 this.createPane('overlayPane');
3032 // @pane markerPane: HTMLElement = 600
3033 // Pane for `Icon`s of `Marker`s
3034 this.createPane('markerPane');
3035 // @pane tooltipPane: HTMLElement = 650
3036 // Pane for tooltip.
3037 this.createPane('tooltipPane');
3038 // @pane popupPane: HTMLElement = 700
3039 // Pane for `Popup`s.
3040 this.createPane('popupPane');
3042 if (!this.options.markerZoomAnimation) {
3043 L.DomUtil.addClass(panes.markerPane, 'leaflet-zoom-hide');
3044 L.DomUtil.addClass(panes.shadowPane, 'leaflet-zoom-hide');
3049 // private methods that modify map state
3051 // @section Map state change events
3052 _resetView: function (center, zoom) {
3053 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
3055 var loading = !this._loaded;
3056 this._loaded = true;
3057 zoom = this._limitZoom(zoom);
3059 this.fire('viewprereset');
3061 var zoomChanged = this._zoom !== zoom;
3063 ._moveStart(zoomChanged)
3064 ._move(center, zoom)
3065 ._moveEnd(zoomChanged);
3067 // @event viewreset: Event
3068 // Fired when the map needs to redraw its content (this usually happens
3069 // on map zoom or load). Very useful for creating custom overlays.
3070 this.fire('viewreset');
3072 // @event load: Event
3073 // Fired when the map is initialized (when its center and zoom are set
3074 // for the first time).
3080 _moveStart: function (zoomChanged) {
3081 // @event zoomstart: Event
3082 // Fired when the map zoom is about to change (e.g. before zoom animation).
3083 // @event movestart: Event
3084 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
3086 this.fire('zoomstart');
3088 return this.fire('movestart');
3091 _move: function (center, zoom, data) {
3092 if (zoom === undefined) {
3095 var zoomChanged = this._zoom !== zoom;
3098 this._lastCenter = center;
3099 this._pixelOrigin = this._getNewPixelOrigin(center);
3101 // @event zoom: Event
3102 // Fired repeatedly during any change in zoom level, including zoom
3103 // and fly animations.
3104 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530
3105 this.fire('zoom', data);
3108 // @event move: Event
3109 // Fired repeatedly during any movement of the map, including pan and
3111 return this.fire('move', data);
3114 _moveEnd: function (zoomChanged) {
3115 // @event zoomend: Event
3116 // Fired when the map has changed, after any animations.
3118 this.fire('zoomend');
3121 // @event moveend: Event
3122 // Fired when the center of the map stops changing (e.g. user stopped
3123 // dragging the map).
3124 return this.fire('moveend');
3127 _stop: function () {
3128 L.Util.cancelAnimFrame(this._flyToFrame);
3129 if (this._panAnim) {
3130 this._panAnim.stop();
3135 _rawPanBy: function (offset) {
3136 L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
3139 _getZoomSpan: function () {
3140 return this.getMaxZoom() - this.getMinZoom();
3143 _panInsideMaxBounds: function () {
3144 if (!this._enforcingBounds) {
3145 this.panInsideBounds(this.options.maxBounds);
3149 _checkIfLoaded: function () {
3150 if (!this._loaded) {
3151 throw new Error('Set map center and zoom first.');
3155 // DOM event handling
3157 // @section Interaction events
3158 _initEvents: function (remove) {
3159 if (!L.DomEvent) { return; }
3162 this._targets[L.stamp(this._container)] = this;
3164 var onOff = remove ? 'off' : 'on';
3166 // @event click: MouseEvent
3167 // Fired when the user clicks (or taps) the map.
3168 // @event dblclick: MouseEvent
3169 // Fired when the user double-clicks (or double-taps) the map.
3170 // @event mousedown: MouseEvent
3171 // Fired when the user pushes the mouse button on the map.
3172 // @event mouseup: MouseEvent
3173 // Fired when the user releases the mouse button on the map.
3174 // @event mouseover: MouseEvent
3175 // Fired when the mouse enters the map.
3176 // @event mouseout: MouseEvent
3177 // Fired when the mouse leaves the map.
3178 // @event mousemove: MouseEvent
3179 // Fired while the mouse moves over the map.
3180 // @event contextmenu: MouseEvent
3181 // Fired when the user pushes the right mouse button on the map, prevents
3182 // default browser context menu from showing if there are listeners on
3183 // this event. Also fired on mobile when the user holds a single touch
3184 // for a second (also called long press).
3185 // @event keypress: KeyboardEvent
3186 // Fired when the user presses a key from the keyboard while the map is focused.
3187 L.DomEvent[onOff](this._container, 'click dblclick mousedown mouseup ' +
3188 'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);
3190 if (this.options.trackResize) {
3191 L.DomEvent[onOff](window, 'resize', this._onResize, this);
3194 if (L.Browser.any3d && this.options.transform3DLimit) {
3195 this[onOff]('moveend', this._onMoveEnd);
3199 _onResize: function () {
3200 L.Util.cancelAnimFrame(this._resizeRequest);
3201 this._resizeRequest = L.Util.requestAnimFrame(
3202 function () { this.invalidateSize({debounceMoveend: true}); }, this);
3205 _onScroll: function () {
3206 this._container.scrollTop = 0;
3207 this._container.scrollLeft = 0;
3210 _onMoveEnd: function () {
3211 var pos = this._getMapPanePos();
3212 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
3213 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
3214 // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
3215 this._resetView(this.getCenter(), this.getZoom());
3219 _findEventTargets: function (e, type) {
3222 isHover = type === 'mouseout' || type === 'mouseover',
3223 src = e.target || e.srcElement,
3227 target = this._targets[L.stamp(src)];
3228 if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
3229 // Prevent firing click after you just dragged an object.
3233 if (target && target.listens(type, true)) {
3234 if (isHover && !L.DomEvent._isExternalTarget(src, e)) { break; }
3235 targets.push(target);
3236 if (isHover) { break; }
3238 if (src === this._container) { break; }
3239 src = src.parentNode;
3241 if (!targets.length && !dragging && !isHover && L.DomEvent._isExternalTarget(src, e)) {
3247 _handleDOMEvent: function (e) {
3248 if (!this._loaded || L.DomEvent._skipped(e)) { return; }
3250 var type = e.type === 'keypress' && e.keyCode === 13 ? 'click' : e.type;
3252 if (type === 'mousedown') {
3253 // prevents outline when clicking on keyboard-focusable element
3254 L.DomUtil.preventOutline(e.target || e.srcElement);
3257 this._fireDOMEvent(e, type);
3260 _fireDOMEvent: function (e, type, targets) {
3262 if (e.type === 'click') {
3263 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
3264 // @event preclick: MouseEvent
3265 // Fired before mouse click on the map (sometimes useful when you
3266 // want something to happen on click before any existing click
3267 // handlers start running).
3268 var synth = L.Util.extend({}, e);
3269 synth.type = 'preclick';
3270 this._fireDOMEvent(synth, synth.type, targets);
3273 if (e._stopped) { return; }
3275 // Find the layer the event is propagating from and its parents.
3276 targets = (targets || []).concat(this._findEventTargets(e, type));
3278 if (!targets.length) { return; }
3280 var target = targets[0];
3281 if (type === 'contextmenu' && target.listens(type, true)) {
3282 L.DomEvent.preventDefault(e);
3289 if (e.type !== 'keypress') {
3290 var isMarker = target instanceof L.Marker;
3291 data.containerPoint = isMarker ?
3292 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
3293 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
3294 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
3297 for (var i = 0; i < targets.length; i++) {
3298 targets[i].fire(type, data, true);
3299 if (data.originalEvent._stopped ||
3300 (targets[i].options.nonBubblingEvents && L.Util.indexOf(targets[i].options.nonBubblingEvents, type) !== -1)) { return; }
3304 _draggableMoved: function (obj) {
3305 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
3306 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
3309 _clearHandlers: function () {
3310 for (var i = 0, len = this._handlers.length; i < len; i++) {
3311 this._handlers[i].disable();
3315 // @section Other Methods
3317 // @method whenReady(fn: Function, context?: Object): this
3318 // Runs the given function `fn` when the map gets initialized with
3319 // a view (center and zoom) and at least one layer, or immediately
3320 // if it's already initialized, optionally passing a function context.
3321 whenReady: function (callback, context) {
3323 callback.call(context || this, {target: this});
3325 this.on('load', callback, context);
3331 // private methods for getting map state
3333 _getMapPanePos: function () {
3334 return L.DomUtil.getPosition(this._mapPane) || new L.Point(0, 0);
3337 _moved: function () {
3338 var pos = this._getMapPanePos();
3339 return pos && !pos.equals([0, 0]);
3342 _getTopLeftPoint: function (center, zoom) {
3343 var pixelOrigin = center && zoom !== undefined ?
3344 this._getNewPixelOrigin(center, zoom) :
3345 this.getPixelOrigin();
3346 return pixelOrigin.subtract(this._getMapPanePos());
3349 _getNewPixelOrigin: function (center, zoom) {
3350 var viewHalf = this.getSize()._divideBy(2);
3351 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
3354 _latLngToNewLayerPoint: function (latlng, zoom, center) {
3355 var topLeft = this._getNewPixelOrigin(center, zoom);
3356 return this.project(latlng, zoom)._subtract(topLeft);
3359 // layer point of the current center
3360 _getCenterLayerPoint: function () {
3361 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
3364 // offset of the specified place to the current center in pixels
3365 _getCenterOffset: function (latlng) {
3366 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
3369 // adjust center for view to get inside bounds
3370 _limitCenter: function (center, zoom, bounds) {
3372 if (!bounds) { return center; }
3374 var centerPoint = this.project(center, zoom),
3375 viewHalf = this.getSize().divideBy(2),
3376 viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
3377 offset = this._getBoundsOffset(viewBounds, bounds, zoom);
3379 // If offset is less than a pixel, ignore.
3380 // This prevents unstable projections from getting into
3381 // an infinite loop of tiny offsets.
3382 if (offset.round().equals([0, 0])) {
3386 return this.unproject(centerPoint.add(offset), zoom);
3389 // adjust offset for view to get inside bounds
3390 _limitOffset: function (offset, bounds) {
3391 if (!bounds) { return offset; }
3393 var viewBounds = this.getPixelBounds(),
3394 newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
3396 return offset.add(this._getBoundsOffset(newBounds, bounds));
3399 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
3400 _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
3401 var projectedMaxBounds = L.bounds(
3402 this.project(maxBounds.getNorthEast(), zoom),
3403 this.project(maxBounds.getSouthWest(), zoom)
3405 minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
3406 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
3408 dx = this._rebound(minOffset.x, -maxOffset.x),
3409 dy = this._rebound(minOffset.y, -maxOffset.y);
3411 return new L.Point(dx, dy);
3414 _rebound: function (left, right) {
3415 return left + right > 0 ?
3416 Math.round(left - right) / 2 :
3417 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
3420 _limitZoom: function (zoom) {
3421 var min = this.getMinZoom(),
3422 max = this.getMaxZoom(),
3423 snap = L.Browser.any3d ? this.options.zoomSnap : 1;
3425 zoom = Math.round(zoom / snap) * snap;
3427 return Math.max(min, Math.min(max, zoom));
3433 // @factory L.map(id: String, options?: Map options)
3434 // Instantiates a map object given the DOM ID of a `<div>` element
3435 // and optionally an object literal with `Map options`.
3438 // @factory L.map(el: HTMLElement, options?: Map options)
3439 // Instantiates a map object given an instance of a `<div>` HTML element
3440 // and optionally an object literal with `Map options`.
3441 L.map = function (id, options) {
3442 return new L.Map(id, options);
3454 * A set of methods from the Layer base class that all Leaflet layers use.
3455 * Inherits all methods, options and events from `L.Evented`.
3460 * var layer = L.Marker(latlng).addTo(map);
3466 * Fired after the layer is added to a map
3468 * @event remove: Event
3469 * Fired after the layer is removed from a map
3473 L.Layer = L.Evented.extend({
3475 // Classes extending `L.Layer` will inherit the following options:
3477 // @option pane: String = 'overlayPane'
3478 // By default the layer will be added to the map's [overlay pane](#map-overlaypane). Overriding this option will cause the layer to be placed on another pane by default.
3479 pane: 'overlayPane',
3480 nonBubblingEvents: [] // Array of events that should not be bubbled to DOM parents (like the map)
3484 * Classes extending `L.Layer` will inherit the following methods:
3486 * @method addTo(map: Map): this
3487 * Adds the layer to the given map
3489 addTo: function (map) {
3494 // @method remove: this
3495 // Removes the layer from the map it is currently active on.
3496 remove: function () {
3497 return this.removeFrom(this._map || this._mapToAdd);
3500 // @method removeFrom(map: Map): this
3501 // Removes the layer from the given map
3502 removeFrom: function (obj) {
3504 obj.removeLayer(this);
3509 // @method getPane(name? : String): HTMLElement
3510 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
3511 getPane: function (name) {
3512 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
3515 addInteractiveTarget: function (targetEl) {
3516 this._map._targets[L.stamp(targetEl)] = this;
3520 removeInteractiveTarget: function (targetEl) {
3521 delete this._map._targets[L.stamp(targetEl)];
3525 _layerAdd: function (e) {
3528 // check in case layer gets added and then removed before the map is ready
3529 if (!map.hasLayer(this)) { return; }
3532 this._zoomAnimated = map._zoomAnimated;
3534 if (this.getEvents) {
3535 var events = this.getEvents();
3536 map.on(events, this);
3537 this.once('remove', function () {
3538 map.off(events, this);
3544 if (this.getAttribution && this._map.attributionControl) {
3545 this._map.attributionControl.addAttribution(this.getAttribution());
3549 map.fire('layeradd', {layer: this});
3553 /* @section Extension methods
3556 * Every layer should extend from `L.Layer` and (re-)implement the following methods.
3558 * @method onAdd(map: Map): this
3559 * Should contain code that creates DOM elements for the layer, adds them to `map panes` where they should belong and puts listeners on relevant map events. Called on [`map.addLayer(layer)`](#map-addlayer).
3561 * @method onRemove(map: Map): this
3562 * Should contain all clean up code that removes the layer's elements from the DOM and removes listeners previously added in [`onAdd`](#layer-onadd). Called on [`map.removeLayer(layer)`](#map-removelayer).
3564 * @method getEvents(): Object
3565 * This optional method should return an object like `{ viewreset: this._reset }` for [`addEventListener`](#evented-addeventlistener). The event handlers in this object will be automatically added and removed from the map with your layer.
3567 * @method getAttribution(): String
3568 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
3570 * @method beforeAdd(map: Map): this
3571 * Optional method. Called on [`map.addLayer(layer)`](#map-addlayer), before the layer is added to the map, before events are initialized, without waiting until the map is in a usable state. Use for early initialization only.
3576 * @section Layer events
3578 * @event layeradd: LayerEvent
3579 * Fired when a new layer is added to the map.
3581 * @event layerremove: LayerEvent
3582 * Fired when some layer is removed from the map
3584 * @section Methods for Layers and Controls
3587 // @method addLayer(layer: Layer): this
3588 // Adds the given layer to the map
3589 addLayer: function (layer) {
3590 var id = L.stamp(layer);
3591 if (this._layers[id]) { return this; }
3592 this._layers[id] = layer;
3594 layer._mapToAdd = this;
3596 if (layer.beforeAdd) {
3597 layer.beforeAdd(this);
3600 this.whenReady(layer._layerAdd, layer);
3605 // @method removeLayer(layer: Layer): this
3606 // Removes the given layer from the map.
3607 removeLayer: function (layer) {
3608 var id = L.stamp(layer);
3610 if (!this._layers[id]) { return this; }
3613 layer.onRemove(this);
3616 if (layer.getAttribution && this.attributionControl) {
3617 this.attributionControl.removeAttribution(layer.getAttribution());
3620 delete this._layers[id];
3623 this.fire('layerremove', {layer: layer});
3624 layer.fire('remove');
3627 layer._map = layer._mapToAdd = null;
3632 // @method hasLayer(layer: Layer): Boolean
3633 // Returns `true` if the given layer is currently added to the map
3634 hasLayer: function (layer) {
3635 return !!layer && (L.stamp(layer) in this._layers);
3638 /* @method eachLayer(fn: Function, context?: Object): this
3639 * Iterates over the layers of the map, optionally specifying context of the iterator function.
3641 * map.eachLayer(function(layer){
3642 * layer.bindPopup('Hello');
3646 eachLayer: function (method, context) {
3647 for (var i in this._layers) {
3648 method.call(context, this._layers[i]);
3653 _addLayers: function (layers) {
3654 layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];
3656 for (var i = 0, len = layers.length; i < len; i++) {
3657 this.addLayer(layers[i]);
3661 _addZoomLimit: function (layer) {
3662 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
3663 this._zoomBoundLayers[L.stamp(layer)] = layer;
3664 this._updateZoomLevels();
3668 _removeZoomLimit: function (layer) {
3669 var id = L.stamp(layer);
3671 if (this._zoomBoundLayers[id]) {
3672 delete this._zoomBoundLayers[id];
3673 this._updateZoomLevels();
3677 _updateZoomLevels: function () {
3678 var minZoom = Infinity,
3679 maxZoom = -Infinity,
3680 oldZoomSpan = this._getZoomSpan();
3682 for (var i in this._zoomBoundLayers) {
3683 var options = this._zoomBoundLayers[i].options;
3685 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
3686 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
3689 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
3690 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
3692 // @section Map state change events
3693 // @event zoomlevelschange: Event
3694 // Fired when the number of zoomlevels on the map is changed due
3695 // to adding or removing a layer.
3696 if (oldZoomSpan !== this._getZoomSpan()) {
3697 this.fire('zoomlevelschange');
3705 * @namespace Projection
3706 * @projection L.Projection.Mercator
3708 * Elliptical Mercator projection — more complex than Spherical Mercator. Takes into account that Earth is a geoid, not a perfect sphere. Used by the EPSG:3395 CRS.
3711 L.Projection.Mercator = {
3713 R_MINOR: 6356752.314245179,
3715 bounds: L.bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
3717 project: function (latlng) {
3718 var d = Math.PI / 180,
3721 tmp = this.R_MINOR / r,
3722 e = Math.sqrt(1 - tmp * tmp),
3723 con = e * Math.sin(y);
3725 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
3726 y = -r * Math.log(Math.max(ts, 1E-10));
3728 return new L.Point(latlng.lng * d * r, y);
3731 unproject: function (point) {
3732 var d = 180 / Math.PI,
3734 tmp = this.R_MINOR / r,
3735 e = Math.sqrt(1 - tmp * tmp),
3736 ts = Math.exp(-point.y / r),
3737 phi = Math.PI / 2 - 2 * Math.atan(ts);
3739 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
3740 con = e * Math.sin(phi);
3741 con = Math.pow((1 - con) / (1 + con), e / 2);
3742 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
3746 return new L.LatLng(phi * d, point.x * d / r);
3754 * @crs L.CRS.EPSG3395
3756 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
3759 L.CRS.EPSG3395 = L.extend({}, L.CRS.Earth, {
3761 projection: L.Projection.Mercator,
3763 transformation: (function () {
3764 var scale = 0.5 / (Math.PI * L.Projection.Mercator.R);
3765 return new L.Transformation(scale, 0.5, -scale, 0.5);
3776 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
3777 * GridLayer can be extended to create a tiled grid of HTML elements like `<canvas>`, `<img>` or `<div>`. GridLayer will handle creating and animating these DOM elements for you.
3780 * @section Synchronous usage
3783 * To create a custom layer, extend GridLayer and implement the `createTile()` method, which will be passed a `Point` object with the `x`, `y`, and `z` (zoom level) coordinates to draw your tile.
3786 * var CanvasLayer = L.GridLayer.extend({
3787 * createTile: function(coords){
3788 * // create a <canvas> element for drawing
3789 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
3791 * // setup tile width and height according to the options
3792 * var size = this.getTileSize();
3793 * tile.width = size.x;
3794 * tile.height = size.y;
3796 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z
3797 * var ctx = tile.getContext('2d');
3799 * // return the tile so it can be rendered on screen
3805 * @section Asynchronous usage
3808 * Tile creation can also be asynchronous, this is useful when using a third-party drawing library. Once the tile is finished drawing it can be passed to the `done()` callback.
3811 * var CanvasLayer = L.GridLayer.extend({
3812 * createTile: function(coords, done){
3815 * // create a <canvas> element for drawing
3816 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
3818 * // setup tile width and height according to the options
3819 * var size = this.getTileSize();
3820 * tile.width = size.x;
3821 * tile.height = size.y;
3823 * // draw something asynchronously and pass the tile to the done() callback
3824 * setTimeout(function() {
3825 * done(error, tile);
3837 L.GridLayer = L.Layer.extend({
3840 // @aka GridLayer options
3842 // @option tileSize: Number|Point = 256
3843 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
3846 // @option opacity: Number = 1.0
3847 // Opacity of the tiles. Can be used in the `createTile()` function.
3850 // @option updateWhenIdle: Boolean = depends
3851 // If `false`, new tiles are loaded during panning, otherwise only after it (for better performance). `true` by default on mobile browsers, otherwise `false`.
3852 updateWhenIdle: L.Browser.mobile,
3854 // @option updateWhenZooming: Boolean = true
3855 // By default, a smooth zoom animation (during a [touch zoom](#map-touchzoom) or a [`flyTo()`](#map-flyto)) will update grid layers every integer zoom level. Setting this option to `false` will update the grid layer only when the smooth animation ends.
3856 updateWhenZooming: true,
3858 // @option updateInterval: Number = 200
3859 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
3860 updateInterval: 200,
3862 // @option attribution: String = null
3863 // String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox".
3866 // @option zIndex: Number = 1
3867 // The explicit zIndex of the tile layer.
3870 // @option bounds: LatLngBounds = undefined
3871 // If set, tiles will only be loaded inside the set `LatLngBounds`.
3874 // @option minZoom: Number = 0
3875 // The minimum zoom level that tiles will be loaded at. By default the entire map.
3878 // @option maxZoom: Number = undefined
3879 // The maximum zoom level that tiles will be loaded at.
3882 // @option noWrap: Boolean = false
3883 // Whether the layer is wrapped around the antimeridian. If `true`, the
3884 // GridLayer will only be displayed once at low zoom levels. Has no
3885 // effect when the [map CRS](#map-crs) doesn't wrap around.
3888 // @option pane: String = 'tilePane'
3889 // `Map pane` where the grid layer will be added.
3892 // @option className: String = ''
3893 // A custom class name to assign to the tile layer. Empty by default.
3896 // @option keepBuffer: Number = 2
3897 // When panning the map, keep this many rows and columns of tiles before unloading them.
3901 initialize: function (options) {
3902 L.setOptions(this, options);
3905 onAdd: function () {
3906 this._initContainer();
3915 beforeAdd: function (map) {
3916 map._addZoomLimit(this);
3919 onRemove: function (map) {
3920 this._removeAllTiles();
3921 L.DomUtil.remove(this._container);
3922 map._removeZoomLimit(this);
3923 this._container = null;
3924 this._tileZoom = null;
3927 // @method bringToFront: this
3928 // Brings the tile layer to the top of all tile layers.
3929 bringToFront: function () {
3931 L.DomUtil.toFront(this._container);
3932 this._setAutoZIndex(Math.max);
3937 // @method bringToBack: this
3938 // Brings the tile layer to the bottom of all tile layers.
3939 bringToBack: function () {
3941 L.DomUtil.toBack(this._container);
3942 this._setAutoZIndex(Math.min);
3947 // @method getAttribution: String
3948 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
3949 getAttribution: function () {
3950 return this.options.attribution;
3953 // @method getContainer: HTMLElement
3954 // Returns the HTML element that contains the tiles for this layer.
3955 getContainer: function () {
3956 return this._container;
3959 // @method setOpacity(opacity: Number): this
3960 // Changes the [opacity](#gridlayer-opacity) of the grid layer.
3961 setOpacity: function (opacity) {
3962 this.options.opacity = opacity;
3963 this._updateOpacity();
3967 // @method setZIndex(zIndex: Number): this
3968 // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
3969 setZIndex: function (zIndex) {
3970 this.options.zIndex = zIndex;
3971 this._updateZIndex();
3976 // @method isLoading: Boolean
3977 // Returns `true` if any tile in the grid layer has not finished loading.
3978 isLoading: function () {
3979 return this._loading;
3982 // @method redraw: this
3983 // Causes the layer to clear all the tiles and request them again.
3984 redraw: function () {
3986 this._removeAllTiles();
3992 getEvents: function () {
3994 viewprereset: this._invalidateAll,
3995 viewreset: this._resetView,
3996 zoom: this._resetView,
3997 moveend: this._onMoveEnd
4000 if (!this.options.updateWhenIdle) {
4001 // update tiles on move, but not more often than once per given interval
4002 if (!this._onMove) {
4003 this._onMove = L.Util.throttle(this._onMoveEnd, this.options.updateInterval, this);
4006 events.move = this._onMove;
4009 if (this._zoomAnimated) {
4010 events.zoomanim = this._animateZoom;
4016 // @section Extension methods
4017 // Layers extending `GridLayer` shall reimplement the following method.
4018 // @method createTile(coords: Object, done?: Function): HTMLElement
4019 // Called only internally, must be overriden by classes extending `GridLayer`.
4020 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
4021 // is specified, it must be called when the tile has finished loading and drawing.
4022 createTile: function () {
4023 return document.createElement('div');
4027 // @method getTileSize: Point
4028 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
4029 getTileSize: function () {
4030 var s = this.options.tileSize;
4031 return s instanceof L.Point ? s : new L.Point(s, s);
4034 _updateZIndex: function () {
4035 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
4036 this._container.style.zIndex = this.options.zIndex;
4040 _setAutoZIndex: function (compare) {
4041 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
4043 var layers = this.getPane().children,
4044 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
4046 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
4048 zIndex = layers[i].style.zIndex;
4050 if (layers[i] !== this._container && zIndex) {
4051 edgeZIndex = compare(edgeZIndex, +zIndex);
4055 if (isFinite(edgeZIndex)) {
4056 this.options.zIndex = edgeZIndex + compare(-1, 1);
4057 this._updateZIndex();
4061 _updateOpacity: function () {
4062 if (!this._map) { return; }
4064 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
4065 if (L.Browser.ielt9) { return; }
4067 L.DomUtil.setOpacity(this._container, this.options.opacity);
4069 var now = +new Date(),
4073 for (var key in this._tiles) {
4074 var tile = this._tiles[key];
4075 if (!tile.current || !tile.loaded) { continue; }
4077 var fade = Math.min(1, (now - tile.loaded) / 200);
4079 L.DomUtil.setOpacity(tile.el, fade);
4083 if (tile.active) { willPrune = true; }
4088 if (willPrune && !this._noPrune) { this._pruneTiles(); }
4091 L.Util.cancelAnimFrame(this._fadeFrame);
4092 this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this);
4096 _initContainer: function () {
4097 if (this._container) { return; }
4099 this._container = L.DomUtil.create('div', 'leaflet-layer ' + (this.options.className || ''));
4100 this._updateZIndex();
4102 if (this.options.opacity < 1) {
4103 this._updateOpacity();
4106 this.getPane().appendChild(this._container);
4109 _updateLevels: function () {
4111 var zoom = this._tileZoom,
4112 maxZoom = this.options.maxZoom;
4114 if (zoom === undefined) { return undefined; }
4116 for (var z in this._levels) {
4117 if (this._levels[z].el.children.length || z === zoom) {
4118 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
4120 L.DomUtil.remove(this._levels[z].el);
4121 this._removeTilesAtZoom(z);
4122 delete this._levels[z];
4126 var level = this._levels[zoom],
4130 level = this._levels[zoom] = {};
4132 level.el = L.DomUtil.create('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
4133 level.el.style.zIndex = maxZoom;
4135 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
4138 this._setZoomTransform(level, map.getCenter(), map.getZoom());
4140 // force the browser to consider the newly added element for transition
4141 L.Util.falseFn(level.el.offsetWidth);
4144 this._level = level;
4149 _pruneTiles: function () {
4156 var zoom = this._map.getZoom();
4157 if (zoom > this.options.maxZoom ||
4158 zoom < this.options.minZoom) {
4159 this._removeAllTiles();
4163 for (key in this._tiles) {
4164 tile = this._tiles[key];
4165 tile.retain = tile.current;
4168 for (key in this._tiles) {
4169 tile = this._tiles[key];
4170 if (tile.current && !tile.active) {
4171 var coords = tile.coords;
4172 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
4173 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
4178 for (key in this._tiles) {
4179 if (!this._tiles[key].retain) {
4180 this._removeTile(key);
4185 _removeTilesAtZoom: function (zoom) {
4186 for (var key in this._tiles) {
4187 if (this._tiles[key].coords.z !== zoom) {
4190 this._removeTile(key);
4194 _removeAllTiles: function () {
4195 for (var key in this._tiles) {
4196 this._removeTile(key);
4200 _invalidateAll: function () {
4201 for (var z in this._levels) {
4202 L.DomUtil.remove(this._levels[z].el);
4203 delete this._levels[z];
4205 this._removeAllTiles();
4207 this._tileZoom = null;
4210 _retainParent: function (x, y, z, minZoom) {
4211 var x2 = Math.floor(x / 2),
4212 y2 = Math.floor(y / 2),
4214 coords2 = new L.Point(+x2, +y2);
4217 var key = this._tileCoordsToKey(coords2),
4218 tile = this._tiles[key];
4220 if (tile && tile.active) {
4224 } else if (tile && tile.loaded) {
4229 return this._retainParent(x2, y2, z2, minZoom);
4235 _retainChildren: function (x, y, z, maxZoom) {
4237 for (var i = 2 * x; i < 2 * x + 2; i++) {
4238 for (var j = 2 * y; j < 2 * y + 2; j++) {
4240 var coords = new L.Point(i, j);
4243 var key = this._tileCoordsToKey(coords),
4244 tile = this._tiles[key];
4246 if (tile && tile.active) {
4250 } else if (tile && tile.loaded) {
4254 if (z + 1 < maxZoom) {
4255 this._retainChildren(i, j, z + 1, maxZoom);
4261 _resetView: function (e) {
4262 var animating = e && (e.pinch || e.flyTo);
4263 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
4266 _animateZoom: function (e) {
4267 this._setView(e.center, e.zoom, true, e.noUpdate);
4270 _setView: function (center, zoom, noPrune, noUpdate) {
4271 var tileZoom = Math.round(zoom);
4272 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
4273 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
4274 tileZoom = undefined;
4277 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
4279 if (!noUpdate || tileZoomChanged) {
4281 this._tileZoom = tileZoom;
4283 if (this._abortLoading) {
4284 this._abortLoading();
4287 this._updateLevels();
4290 if (tileZoom !== undefined) {
4291 this._update(center);
4298 // Flag to prevent _updateOpacity from pruning tiles during
4299 // a zoom anim or a pinch gesture
4300 this._noPrune = !!noPrune;
4303 this._setZoomTransforms(center, zoom);
4306 _setZoomTransforms: function (center, zoom) {
4307 for (var i in this._levels) {
4308 this._setZoomTransform(this._levels[i], center, zoom);
4312 _setZoomTransform: function (level, center, zoom) {
4313 var scale = this._map.getZoomScale(zoom, level.zoom),
4314 translate = level.origin.multiplyBy(scale)
4315 .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
4317 if (L.Browser.any3d) {
4318 L.DomUtil.setTransform(level.el, translate, scale);
4320 L.DomUtil.setPosition(level.el, translate);
4324 _resetGrid: function () {
4325 var map = this._map,
4326 crs = map.options.crs,
4327 tileSize = this._tileSize = this.getTileSize(),
4328 tileZoom = this._tileZoom;
4330 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
4332 this._globalTileRange = this._pxBoundsToTileRange(bounds);
4335 this._wrapX = crs.wrapLng && !this.options.noWrap && [
4336 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
4337 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
4339 this._wrapY = crs.wrapLat && !this.options.noWrap && [
4340 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
4341 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
4345 _onMoveEnd: function () {
4346 if (!this._map || this._map._animatingZoom) { return; }
4351 _getTiledPixelBounds: function (center) {
4352 var map = this._map,
4353 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
4354 scale = map.getZoomScale(mapZoom, this._tileZoom),
4355 pixelCenter = map.project(center, this._tileZoom).floor(),
4356 halfSize = map.getSize().divideBy(scale * 2);
4358 return new L.Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
4361 // Private method to load tiles in the grid's active zoom level according to map bounds
4362 _update: function (center) {
4363 var map = this._map;
4364 if (!map) { return; }
4365 var zoom = map.getZoom();
4367 if (center === undefined) { center = map.getCenter(); }
4368 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom
4370 var pixelBounds = this._getTiledPixelBounds(center),
4371 tileRange = this._pxBoundsToTileRange(pixelBounds),
4372 tileCenter = tileRange.getCenter(),
4374 margin = this.options.keepBuffer,
4375 noPruneRange = new L.Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
4376 tileRange.getTopRight().add([margin, -margin]));
4378 for (var key in this._tiles) {
4379 var c = this._tiles[key].coords;
4380 if (c.z !== this._tileZoom || !noPruneRange.contains(L.point(c.x, c.y))) {
4381 this._tiles[key].current = false;
4385 // _update just loads more tiles. If the tile zoom level differs too much
4386 // from the map's, let _setView reset levels and prune old tiles.
4387 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
4389 // create a queue of coordinates to load tiles from
4390 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
4391 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
4392 var coords = new L.Point(i, j);
4393 coords.z = this._tileZoom;
4395 if (!this._isValidTile(coords)) { continue; }
4397 var tile = this._tiles[this._tileCoordsToKey(coords)];
4399 tile.current = true;
4406 // sort tile queue to load tiles in order of their distance to center
4407 queue.sort(function (a, b) {
4408 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
4411 if (queue.length !== 0) {
4412 // if it's the first batch of tiles to load
4413 if (!this._loading) {
4414 this._loading = true;
4415 // @event loading: Event
4416 // Fired when the grid layer starts loading tiles.
4417 this.fire('loading');
4420 // create DOM fragment to append tiles in one batch
4421 var fragment = document.createDocumentFragment();
4423 for (i = 0; i < queue.length; i++) {
4424 this._addTile(queue[i], fragment);
4427 this._level.el.appendChild(fragment);
4431 _isValidTile: function (coords) {
4432 var crs = this._map.options.crs;
4434 if (!crs.infinite) {
4435 // don't load tile if it's out of bounds and not wrapped
4436 var bounds = this._globalTileRange;
4437 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
4438 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
4441 if (!this.options.bounds) { return true; }
4443 // don't load tile if it doesn't intersect the bounds in options
4444 var tileBounds = this._tileCoordsToBounds(coords);
4445 return L.latLngBounds(this.options.bounds).overlaps(tileBounds);
4448 _keyToBounds: function (key) {
4449 return this._tileCoordsToBounds(this._keyToTileCoords(key));
4452 // converts tile coordinates to its geographical bounds
4453 _tileCoordsToBounds: function (coords) {
4455 var map = this._map,
4456 tileSize = this.getTileSize(),
4458 nwPoint = coords.scaleBy(tileSize),
4459 sePoint = nwPoint.add(tileSize),
4461 nw = map.unproject(nwPoint, coords.z),
4462 se = map.unproject(sePoint, coords.z);
4464 if (!this.options.noWrap) {
4465 nw = map.wrapLatLng(nw);
4466 se = map.wrapLatLng(se);
4469 return new L.LatLngBounds(nw, se);
4472 // converts tile coordinates to key for the tile cache
4473 _tileCoordsToKey: function (coords) {
4474 return coords.x + ':' + coords.y + ':' + coords.z;
4477 // converts tile cache key to coordinates
4478 _keyToTileCoords: function (key) {
4479 var k = key.split(':'),
4480 coords = new L.Point(+k[0], +k[1]);
4485 _removeTile: function (key) {
4486 var tile = this._tiles[key];
4487 if (!tile) { return; }
4489 L.DomUtil.remove(tile.el);
4491 delete this._tiles[key];
4493 // @event tileunload: TileEvent
4494 // Fired when a tile is removed (e.g. when a tile goes off the screen).
4495 this.fire('tileunload', {
4497 coords: this._keyToTileCoords(key)
4501 _initTile: function (tile) {
4502 L.DomUtil.addClass(tile, 'leaflet-tile');
4504 var tileSize = this.getTileSize();
4505 tile.style.width = tileSize.x + 'px';
4506 tile.style.height = tileSize.y + 'px';
4508 tile.onselectstart = L.Util.falseFn;
4509 tile.onmousemove = L.Util.falseFn;
4511 // update opacity on tiles in IE7-8 because of filter inheritance problems
4512 if (L.Browser.ielt9 && this.options.opacity < 1) {
4513 L.DomUtil.setOpacity(tile, this.options.opacity);
4516 // without this hack, tiles disappear after zoom on Chrome for Android
4517 // https://github.com/Leaflet/Leaflet/issues/2078
4518 if (L.Browser.android && !L.Browser.android23) {
4519 tile.style.WebkitBackfaceVisibility = 'hidden';
4523 _addTile: function (coords, container) {
4524 var tilePos = this._getTilePos(coords),
4525 key = this._tileCoordsToKey(coords);
4527 var tile = this.createTile(this._wrapCoords(coords), L.bind(this._tileReady, this, coords));
4529 this._initTile(tile);
4531 // if createTile is defined with a second argument ("done" callback),
4532 // we know that tile is async and will be ready later; otherwise
4533 if (this.createTile.length < 2) {
4534 // mark tile as ready, but delay one frame for opacity animation to happen
4535 L.Util.requestAnimFrame(L.bind(this._tileReady, this, coords, null, tile));
4538 L.DomUtil.setPosition(tile, tilePos);
4540 // save tile in cache
4541 this._tiles[key] = {
4547 container.appendChild(tile);
4548 // @event tileloadstart: TileEvent
4549 // Fired when a tile is requested and starts loading.
4550 this.fire('tileloadstart', {
4556 _tileReady: function (coords, err, tile) {
4557 if (!this._map) { return; }
4560 // @event tileerror: TileErrorEvent
4561 // Fired when there is an error loading a tile.
4562 this.fire('tileerror', {
4569 var key = this._tileCoordsToKey(coords);
4571 tile = this._tiles[key];
4572 if (!tile) { return; }
4574 tile.loaded = +new Date();
4575 if (this._map._fadeAnimated) {
4576 L.DomUtil.setOpacity(tile.el, 0);
4577 L.Util.cancelAnimFrame(this._fadeFrame);
4578 this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this);
4585 L.DomUtil.addClass(tile.el, 'leaflet-tile-loaded');
4587 // @event tileload: TileEvent
4588 // Fired when a tile loads.
4589 this.fire('tileload', {
4595 if (this._noTilesToLoad()) {
4596 this._loading = false;
4597 // @event load: Event
4598 // Fired when the grid layer loaded all visible tiles.
4601 if (L.Browser.ielt9 || !this._map._fadeAnimated) {
4602 L.Util.requestAnimFrame(this._pruneTiles, this);
4604 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
4605 // to trigger a pruning.
4606 setTimeout(L.bind(this._pruneTiles, this), 250);
4611 _getTilePos: function (coords) {
4612 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
4615 _wrapCoords: function (coords) {
4616 var newCoords = new L.Point(
4617 this._wrapX ? L.Util.wrapNum(coords.x, this._wrapX) : coords.x,
4618 this._wrapY ? L.Util.wrapNum(coords.y, this._wrapY) : coords.y);
4619 newCoords.z = coords.z;
4623 _pxBoundsToTileRange: function (bounds) {
4624 var tileSize = this.getTileSize();
4625 return new L.Bounds(
4626 bounds.min.unscaleBy(tileSize).floor(),
4627 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
4630 _noTilesToLoad: function () {
4631 for (var key in this._tiles) {
4632 if (!this._tiles[key].loaded) { return false; }
4638 // @factory L.gridLayer(options?: GridLayer options)
4639 // Creates a new instance of GridLayer with the supplied options.
4640 L.gridLayer = function (options) {
4641 return new L.GridLayer(options);
4648 * @inherits GridLayer
4650 * Used to load and display tile layers on the map. Extends `GridLayer`.
4655 * L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map);
4658 * @section URL template
4661 * A string of the following form:
4664 * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
4667 * `{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.
4669 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
4672 * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
4677 L.TileLayer = L.GridLayer.extend({
4680 // @aka TileLayer options
4682 // @option minZoom: Number = 0
4683 // Minimum zoom number.
4686 // @option maxZoom: Number = 18
4687 // Maximum zoom number.
4690 // @option maxNativeZoom: Number = null
4691 // Maximum zoom number the tile source has available. If it is specified,
4692 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
4693 // from `maxNativeZoom` level and auto-scaled.
4694 maxNativeZoom: null,
4696 // @option subdomains: String|String[] = 'abc'
4697 // 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.
4700 // @option errorTileUrl: String = ''
4701 // URL to the tile image to show in place of the tile that failed to load.
4704 // @option zoomOffset: Number = 0
4705 // The zoom number used in tile URLs will be offset with this value.
4708 // @option tms: Boolean = false
4709 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
4712 // @option zoomReverse: Boolean = false
4713 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
4716 // @option detectRetina: Boolean = false
4717 // 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.
4718 detectRetina: false,
4720 // @option crossOrigin: Boolean = false
4721 // If true, all tiles will have their crossOrigin attribute set to ''. This is needed if you want to access tile pixel data.
4725 initialize: function (url, options) {
4729 options = L.setOptions(this, options);
4731 // detecting retina displays, adjusting tileSize and zoom levels
4732 if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {
4734 options.tileSize = Math.floor(options.tileSize / 2);
4736 if (!options.zoomReverse) {
4737 options.zoomOffset++;
4740 options.zoomOffset--;
4744 options.minZoom = Math.max(0, options.minZoom);
4747 if (typeof options.subdomains === 'string') {
4748 options.subdomains = options.subdomains.split('');
4751 // for https://github.com/Leaflet/Leaflet/issues/137
4752 if (!L.Browser.android) {
4753 this.on('tileunload', this._onTileRemove);
4757 // @method setUrl(url: String, noRedraw?: Boolean): this
4758 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
4759 setUrl: function (url, noRedraw) {
4768 // @method createTile(coords: Object, done?: Function): HTMLElement
4769 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
4770 // to return an `<img>` HTML element with the appropiate image URL given `coords`. The `done`
4771 // callback is called when the tile has been loaded.
4772 createTile: function (coords, done) {
4773 var tile = document.createElement('img');
4775 L.DomEvent.on(tile, 'load', L.bind(this._tileOnLoad, this, done, tile));
4776 L.DomEvent.on(tile, 'error', L.bind(this._tileOnError, this, done, tile));
4778 if (this.options.crossOrigin) {
4779 tile.crossOrigin = '';
4783 Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
4784 http://www.w3.org/TR/WCAG20-TECHS/H67
4788 tile.src = this.getTileUrl(coords);
4793 // @section Extension methods
4795 // Layers extending `TileLayer` might reimplement the following method.
4796 // @method getTileUrl(coords: Object): String
4797 // Called only internally, returns the URL for a tile given its coordinates.
4798 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
4799 getTileUrl: function (coords) {
4801 r: L.Browser.retina ? '@2x' : '',
4802 s: this._getSubdomain(coords),
4805 z: this._getZoomForUrl()
4807 if (this._map && !this._map.options.crs.infinite) {
4808 var invertedY = this._globalTileRange.max.y - coords.y;
4809 if (this.options.tms) {
4810 data['y'] = invertedY;
4812 data['-y'] = invertedY;
4815 return L.Util.template(this._url, L.extend(data, this.options));
4818 _tileOnLoad: function (done, tile) {
4819 // For https://github.com/Leaflet/Leaflet/issues/3332
4820 if (L.Browser.ielt9) {
4821 setTimeout(L.bind(done, this, null, tile), 0);
4827 _tileOnError: function (done, tile, e) {
4828 var errorUrl = this.options.errorTileUrl;
4830 tile.src = errorUrl;
4835 getTileSize: function () {
4836 var map = this._map,
4837 tileSize = L.GridLayer.prototype.getTileSize.call(this),
4838 zoom = this._tileZoom + this.options.zoomOffset,
4839 zoomN = this.options.maxNativeZoom;
4841 // increase tile size when overscaling
4842 return zoomN !== null && zoom > zoomN ?
4843 tileSize.divideBy(map.getZoomScale(zoomN, zoom)).round() :
4847 _onTileRemove: function (e) {
4848 e.tile.onload = null;
4851 _getZoomForUrl: function () {
4853 var options = this.options,
4854 zoom = this._tileZoom;
4856 if (options.zoomReverse) {
4857 zoom = options.maxZoom - zoom;
4860 zoom += options.zoomOffset;
4862 return options.maxNativeZoom !== null ? Math.min(zoom, options.maxNativeZoom) : zoom;
4865 _getSubdomain: function (tilePoint) {
4866 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
4867 return this.options.subdomains[index];
4870 // stops loading all tiles in the background layer
4871 _abortLoading: function () {
4873 for (i in this._tiles) {
4874 if (this._tiles[i].coords.z !== this._tileZoom) {
4875 tile = this._tiles[i].el;
4877 tile.onload = L.Util.falseFn;
4878 tile.onerror = L.Util.falseFn;
4880 if (!tile.complete) {
4881 tile.src = L.Util.emptyImageUrl;
4882 L.DomUtil.remove(tile);
4890 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
4891 // Instantiates a tile layer object given a `URL template` and optionally an options object.
4893 L.tileLayer = function (url, options) {
4894 return new L.TileLayer(url, options);
4900 * @class TileLayer.WMS
4901 * @inherits TileLayer
4902 * @aka L.TileLayer.WMS
4903 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
4908 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
4909 * layers: 'nexrad-n0r-900913',
4910 * format: 'image/png',
4911 * transparent: true,
4912 * attribution: "Weather data © 2012 IEM Nexrad"
4917 L.TileLayer.WMS = L.TileLayer.extend({
4920 // @aka TileLayer.WMS options
4921 // If any custom options not documented here are used, they will be sent to the
4922 // WMS server as extra parameters in each request URL. This can be useful for
4923 // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
4928 // @option layers: String = ''
4929 // **(required)** Comma-separated list of WMS layers to show.
4932 // @option styles: String = ''
4933 // Comma-separated list of WMS styles.
4936 // @option format: String = 'image/jpeg'
4937 // WMS image format (use `'image/png'` for layers with transparency).
4938 format: 'image/jpeg',
4940 // @option transparent: Boolean = false
4941 // If `true`, the WMS service will return images with transparency.
4944 // @option version: String = '1.1.1'
4945 // Version of the WMS service to use
4950 // @option crs: CRS = null
4951 // Coordinate Reference System to use for the WMS requests, defaults to
4952 // map CRS. Don't change this if you're not sure what it means.
4955 // @option uppercase: Boolean = false
4956 // If `true`, WMS request parameter keys will be uppercase.
4960 initialize: function (url, options) {
4964 var wmsParams = L.extend({}, this.defaultWmsParams);
4966 // all keys that are not TileLayer options go to WMS params
4967 for (var i in options) {
4968 if (!(i in this.options)) {
4969 wmsParams[i] = options[i];
4973 options = L.setOptions(this, options);
4975 wmsParams.width = wmsParams.height = options.tileSize * (options.detectRetina && L.Browser.retina ? 2 : 1);
4977 this.wmsParams = wmsParams;
4980 onAdd: function (map) {
4982 this._crs = this.options.crs || map.options.crs;
4983 this._wmsVersion = parseFloat(this.wmsParams.version);
4985 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
4986 this.wmsParams[projectionKey] = this._crs.code;
4988 L.TileLayer.prototype.onAdd.call(this, map);
4991 getTileUrl: function (coords) {
4993 var tileBounds = this._tileCoordsToBounds(coords),
4994 nw = this._crs.project(tileBounds.getNorthWest()),
4995 se = this._crs.project(tileBounds.getSouthEast()),
4997 bbox = (this._wmsVersion >= 1.3 && this._crs === L.CRS.EPSG4326 ?
4998 [se.y, nw.x, nw.y, se.x] :
4999 [nw.x, se.y, se.x, nw.y]).join(','),
5001 url = L.TileLayer.prototype.getTileUrl.call(this, coords);
5004 L.Util.getParamString(this.wmsParams, url, this.options.uppercase) +
5005 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
5008 // @method setParams(params: Object, noRedraw?: Boolean): this
5009 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
5010 setParams: function (params, noRedraw) {
5012 L.extend(this.wmsParams, params);
5023 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
5024 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
5025 L.tileLayer.wms = function (url, options) {
5026 return new L.TileLayer.WMS(url, options);
5032 * @class ImageOverlay
5033 * @aka L.ImageOverlay
5034 * @inherits Interactive layer
5036 * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
5041 * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
5042 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
5043 * L.imageOverlay(imageUrl, imageBounds).addTo(map);
5047 L.ImageOverlay = L.Layer.extend({
5050 // @aka ImageOverlay options
5052 // @option opacity: Number = 1.0
5053 // The opacity of the image overlay.
5056 // @option alt: String = ''
5057 // Text for the `alt` attribute of the image (useful for accessibility).
5060 // @option interactive: Boolean = false
5061 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
5064 // @option attribution: String = null
5065 // An optional string containing HTML to be shown on the `Attribution control`
5068 // @option crossOrigin: Boolean = false
5069 // If true, the image will have its crossOrigin attribute set to ''. This is needed if you want to access image pixel data.
5073 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
5075 this._bounds = L.latLngBounds(bounds);
5077 L.setOptions(this, options);
5080 onAdd: function () {
5084 if (this.options.opacity < 1) {
5085 this._updateOpacity();
5089 if (this.options.interactive) {
5090 L.DomUtil.addClass(this._image, 'leaflet-interactive');
5091 this.addInteractiveTarget(this._image);
5094 this.getPane().appendChild(this._image);
5098 onRemove: function () {
5099 L.DomUtil.remove(this._image);
5100 if (this.options.interactive) {
5101 this.removeInteractiveTarget(this._image);
5105 // @method setOpacity(opacity: Number): this
5106 // Sets the opacity of the overlay.
5107 setOpacity: function (opacity) {
5108 this.options.opacity = opacity;
5111 this._updateOpacity();
5116 setStyle: function (styleOpts) {
5117 if (styleOpts.opacity) {
5118 this.setOpacity(styleOpts.opacity);
5123 // @method bringToFront(): this
5124 // Brings the layer to the top of all overlays.
5125 bringToFront: function () {
5127 L.DomUtil.toFront(this._image);
5132 // @method bringToBack(): this
5133 // Brings the layer to the bottom of all overlays.
5134 bringToBack: function () {
5136 L.DomUtil.toBack(this._image);
5141 // @method setUrl(url: String): this
5142 // Changes the URL of the image.
5143 setUrl: function (url) {
5147 this._image.src = url;
5152 setBounds: function (bounds) {
5153 this._bounds = bounds;
5161 getAttribution: function () {
5162 return this.options.attribution;
5165 getEvents: function () {
5168 viewreset: this._reset
5171 if (this._zoomAnimated) {
5172 events.zoomanim = this._animateZoom;
5178 getBounds: function () {
5179 return this._bounds;
5182 getElement: function () {
5186 _initImage: function () {
5187 var img = this._image = L.DomUtil.create('img',
5188 'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : ''));
5190 img.onselectstart = L.Util.falseFn;
5191 img.onmousemove = L.Util.falseFn;
5193 img.onload = L.bind(this.fire, this, 'load');
5195 if (this.options.crossOrigin) {
5196 img.crossOrigin = '';
5199 img.src = this._url;
5200 img.alt = this.options.alt;
5203 _animateZoom: function (e) {
5204 var scale = this._map.getZoomScale(e.zoom),
5205 offset = this._map._latLngToNewLayerPoint(this._bounds.getNorthWest(), e.zoom, e.center);
5207 L.DomUtil.setTransform(this._image, offset, scale);
5210 _reset: function () {
5211 var image = this._image,
5212 bounds = new L.Bounds(
5213 this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
5214 this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
5215 size = bounds.getSize();
5217 L.DomUtil.setPosition(image, bounds.min);
5219 image.style.width = size.x + 'px';
5220 image.style.height = size.y + 'px';
5223 _updateOpacity: function () {
5224 L.DomUtil.setOpacity(this._image, this.options.opacity);
5228 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
5229 // Instantiates an image overlay object given the URL of the image and the
5230 // geographical bounds it is tied to.
5231 L.imageOverlay = function (url, bounds, options) {
5232 return new L.ImageOverlay(url, bounds, options);
5242 * Represents an icon to provide when creating a marker.
5247 * var myIcon = L.icon({
5248 * iconUrl: 'my-icon.png',
5249 * iconRetinaUrl: 'my-icon@2x.png',
5250 * iconSize: [38, 95],
5251 * iconAnchor: [22, 94],
5252 * popupAnchor: [-3, -76],
5253 * shadowUrl: 'my-icon-shadow.png',
5254 * shadowRetinaUrl: 'my-icon-shadow@2x.png',
5255 * shadowSize: [68, 95],
5256 * shadowAnchor: [22, 94]
5259 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
5262 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
5266 L.Icon = L.Class.extend({
5271 * @option iconUrl: String = null
5272 * **(required)** The URL to the icon image (absolute or relative to your script path).
5274 * @option iconRetinaUrl: String = null
5275 * The URL to a retina sized version of the icon image (absolute or relative to your
5276 * script path). Used for Retina screen devices.
5278 * @option iconSize: Point = null
5279 * Size of the icon image in pixels.
5281 * @option iconAnchor: Point = null
5282 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
5283 * will be aligned so that this point is at the marker's geographical location. Centered
5284 * by default if size is specified, also can be set in CSS with negative margins.
5286 * @option popupAnchor: Point = null
5287 * The coordinates of the point from which popups will "open", relative to the icon anchor.
5289 * @option shadowUrl: String = null
5290 * The URL to the icon shadow image. If not specified, no shadow image will be created.
5292 * @option shadowRetinaUrl: String = null
5294 * @option shadowSize: Point = null
5295 * Size of the shadow image in pixels.
5297 * @option shadowAnchor: Point = null
5298 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
5299 * as iconAnchor if not specified).
5301 * @option className: String = ''
5302 * A custom class name to assign to both icon and shadow images. Empty by default.
5305 initialize: function (options) {
5306 L.setOptions(this, options);
5309 // @method createIcon(oldIcon?: HTMLElement): HTMLElement
5310 // Called internally when the icon has to be shown, returns a `<img>` HTML element
5311 // styled according to the options.
5312 createIcon: function (oldIcon) {
5313 return this._createIcon('icon', oldIcon);
5316 // @method createShadow(oldIcon?: HTMLElement): HTMLElement
5317 // As `createIcon`, but for the shadow beneath it.
5318 createShadow: function (oldIcon) {
5319 return this._createIcon('shadow', oldIcon);
5322 _createIcon: function (name, oldIcon) {
5323 var src = this._getIconUrl(name);
5326 if (name === 'icon') {
5327 throw new Error('iconUrl not set in Icon options (see the docs).');
5332 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
5333 this._setIconStyles(img, name);
5338 _setIconStyles: function (img, name) {
5339 var options = this.options;
5340 var sizeOption = options[name + 'Size'];
5342 if (typeof sizeOption === 'number') {
5343 sizeOption = [sizeOption, sizeOption];
5346 var size = L.point(sizeOption),
5347 anchor = L.point(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
5348 size && size.divideBy(2, true));
5350 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
5353 img.style.marginLeft = (-anchor.x) + 'px';
5354 img.style.marginTop = (-anchor.y) + 'px';
5358 img.style.width = size.x + 'px';
5359 img.style.height = size.y + 'px';
5363 _createImg: function (src, el) {
5364 el = el || document.createElement('img');
5369 _getIconUrl: function (name) {
5370 return L.Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
5375 // @factory L.icon(options: Icon options)
5376 // Creates an icon instance with the given options.
5377 L.icon = function (options) {
5378 return new L.Icon(options);
5384 * @miniclass Icon.Default (Icon)
5385 * @aka L.Icon.Default
5388 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
5389 * no icon is specified. Points to the blue marker image distributed with Leaflet
5392 * In order to change the default icon, just change the properties of `L.Icon.Default.prototype.options`
5393 * (which is a set of `Icon options`).
5396 L.Icon.Default = L.Icon.extend({
5399 iconUrl: 'marker-icon.png',
5400 iconRetinaUrl: 'marker-icon-2x.png',
5401 shadowUrl: 'marker-shadow.png',
5403 iconAnchor: [12, 41],
5404 popupAnchor: [1, -34],
5405 tooltipAnchor: [16, -28],
5406 shadowSize: [41, 41]
5409 _getIconUrl: function (name) {
5410 if (!L.Icon.Default.imagePath) { // Deprecated, backwards-compatibility only
5411 L.Icon.Default.imagePath = this._detectIconPath();
5414 // @option imagePath: String
5415 // `L.Icon.Default` will try to auto-detect the absolute location of the
5416 // blue icon images. If you are placing these images in a non-standard
5417 // way, set this option to point to the right absolute path.
5418 return (this.options.imagePath || L.Icon.Default.imagePath) + L.Icon.prototype._getIconUrl.call(this, name);
5421 _detectIconPath: function () {
5422 var el = L.DomUtil.create('div', 'leaflet-default-icon-path', document.body);
5423 var path = L.DomUtil.getStyle(el, 'background-image') ||
5424 L.DomUtil.getStyle(el, 'backgroundImage'); // IE8
5426 document.body.removeChild(el);
5428 return path.indexOf('url') === 0 ?
5429 path.replace(/^url\([\"\']?/, '').replace(/marker-icon\.png[\"\']?\)$/, '') : '';
5437 * @inherits Interactive layer
5439 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
5444 * L.marker([50.5, 30.5]).addTo(map);
5448 L.Marker = L.Layer.extend({
5451 // @aka Marker options
5453 // @option icon: Icon = *
5454 // 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.
5455 icon: new L.Icon.Default(),
5457 // Option inherited from "Interactive layer" abstract class
5460 // @option draggable: Boolean = false
5461 // Whether the marker is draggable with mouse/touch or not.
5464 // @option keyboard: Boolean = true
5465 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
5468 // @option title: String = ''
5469 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
5472 // @option alt: String = ''
5473 // Text for the `alt` attribute of the icon image (useful for accessibility).
5476 // @option zIndexOffset: Number = 0
5477 // 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).
5480 // @option opacity: Number = 1.0
5481 // The opacity of the marker.
5484 // @option riseOnHover: Boolean = false
5485 // If `true`, the marker will get on top of others when you hover the mouse over it.
5488 // @option riseOffset: Number = 250
5489 // The z-index offset used for the `riseOnHover` feature.
5492 // @option pane: String = 'markerPane'
5493 // `Map pane` where the markers icon will be added.
5496 // FIXME: shadowPane is no longer a valid option
5497 nonBubblingEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu']
5502 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
5505 initialize: function (latlng, options) {
5506 L.setOptions(this, options);
5507 this._latlng = L.latLng(latlng);
5510 onAdd: function (map) {
5511 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
5513 if (this._zoomAnimated) {
5514 map.on('zoomanim', this._animateZoom, this);
5521 onRemove: function (map) {
5522 if (this.dragging && this.dragging.enabled()) {
5523 this.options.draggable = true;
5524 this.dragging.removeHooks();
5527 if (this._zoomAnimated) {
5528 map.off('zoomanim', this._animateZoom, this);
5532 this._removeShadow();
5535 getEvents: function () {
5538 viewreset: this.update
5542 // @method getLatLng: LatLng
5543 // Returns the current geographical position of the marker.
5544 getLatLng: function () {
5545 return this._latlng;
5548 // @method setLatLng(latlng: LatLng): this
5549 // Changes the marker position to the given point.
5550 setLatLng: function (latlng) {
5551 var oldLatLng = this._latlng;
5552 this._latlng = L.latLng(latlng);
5555 // @event move: Event
5556 // 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`.
5557 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
5560 // @method setZIndexOffset(offset: Number): this
5561 // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
5562 setZIndexOffset: function (offset) {
5563 this.options.zIndexOffset = offset;
5564 return this.update();
5567 // @method setIcon(icon: Icon): this
5568 // Changes the marker icon.
5569 setIcon: function (icon) {
5571 this.options.icon = icon;
5579 this.bindPopup(this._popup, this._popup.options);
5585 getElement: function () {
5589 update: function () {
5592 var pos = this._map.latLngToLayerPoint(this._latlng).round();
5599 _initIcon: function () {
5600 var options = this.options,
5601 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
5603 var icon = options.icon.createIcon(this._icon),
5606 // if we're not reusing the icon, remove the old one and init new one
5607 if (icon !== this._icon) {
5613 if (options.title) {
5614 icon.title = options.title;
5617 icon.alt = options.alt;
5621 L.DomUtil.addClass(icon, classToAdd);
5623 if (options.keyboard) {
5624 icon.tabIndex = '0';
5629 if (options.riseOnHover) {
5631 mouseover: this._bringToFront,
5632 mouseout: this._resetZIndex
5636 var newShadow = options.icon.createShadow(this._shadow),
5639 if (newShadow !== this._shadow) {
5640 this._removeShadow();
5645 L.DomUtil.addClass(newShadow, classToAdd);
5647 this._shadow = newShadow;
5650 if (options.opacity < 1) {
5651 this._updateOpacity();
5656 this.getPane().appendChild(this._icon);
5658 this._initInteraction();
5659 if (newShadow && addShadow) {
5660 this.getPane('shadowPane').appendChild(this._shadow);
5664 _removeIcon: function () {
5665 if (this.options.riseOnHover) {
5667 mouseover: this._bringToFront,
5668 mouseout: this._resetZIndex
5672 L.DomUtil.remove(this._icon);
5673 this.removeInteractiveTarget(this._icon);
5678 _removeShadow: function () {
5680 L.DomUtil.remove(this._shadow);
5682 this._shadow = null;
5685 _setPos: function (pos) {
5686 L.DomUtil.setPosition(this._icon, pos);
5689 L.DomUtil.setPosition(this._shadow, pos);
5692 this._zIndex = pos.y + this.options.zIndexOffset;
5694 this._resetZIndex();
5697 _updateZIndex: function (offset) {
5698 this._icon.style.zIndex = this._zIndex + offset;
5701 _animateZoom: function (opt) {
5702 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
5707 _initInteraction: function () {
5709 if (!this.options.interactive) { return; }
5711 L.DomUtil.addClass(this._icon, 'leaflet-interactive');
5713 this.addInteractiveTarget(this._icon);
5715 if (L.Handler.MarkerDrag) {
5716 var draggable = this.options.draggable;
5717 if (this.dragging) {
5718 draggable = this.dragging.enabled();
5719 this.dragging.disable();
5722 this.dragging = new L.Handler.MarkerDrag(this);
5725 this.dragging.enable();
5730 // @method setOpacity(opacity: Number): this
5731 // Changes the opacity of the marker.
5732 setOpacity: function (opacity) {
5733 this.options.opacity = opacity;
5735 this._updateOpacity();
5741 _updateOpacity: function () {
5742 var opacity = this.options.opacity;
5744 L.DomUtil.setOpacity(this._icon, opacity);
5747 L.DomUtil.setOpacity(this._shadow, opacity);
5751 _bringToFront: function () {
5752 this._updateZIndex(this.options.riseOffset);
5755 _resetZIndex: function () {
5756 this._updateZIndex(0);
5761 // factory L.marker(latlng: LatLng, options? : Marker options)
5763 // @factory L.marker(latlng: LatLng, options? : Marker options)
5764 // Instantiates a Marker object given a geographical point and optionally an options object.
5765 L.marker = function (latlng, options) {
5766 return new L.Marker(latlng, options);
5776 * Represents a lightweight icon for markers that uses a simple `<div>`
5777 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
5781 * var myIcon = L.divIcon({className: 'my-div-icon'});
5782 * // you can set .my-div-icon styles in CSS
5784 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
5787 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
5790 L.DivIcon = L.Icon.extend({
5793 // @aka DivIcon options
5794 iconSize: [12, 12], // also can be set through CSS
5796 // iconAnchor: (Point),
5797 // popupAnchor: (Point),
5799 // @option html: String = ''
5800 // Custom HTML code to put inside the div element, empty by default.
5803 // @option bgPos: Point = [0, 0]
5804 // Optional relative position of the background, in pixels
5807 className: 'leaflet-div-icon'
5810 createIcon: function (oldIcon) {
5811 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
5812 options = this.options;
5814 div.innerHTML = options.html !== false ? options.html : '';
5816 if (options.bgPos) {
5817 var bgPos = L.point(options.bgPos);
5818 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
5820 this._setIconStyles(div, 'icon');
5825 createShadow: function () {
5830 // @factory L.divIcon(options: DivIcon options)
5831 // Creates a `DivIcon` instance with the given options.
5832 L.divIcon = function (options) {
5833 return new L.DivIcon(options);
5842 * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
5845 // @namespace DivOverlay
5846 L.DivOverlay = L.Layer.extend({
5849 // @aka DivOverlay options
5851 // @option offset: Point = Point(0, 7)
5852 // The offset of the popup position. Useful to control the anchor
5853 // of the popup when opening it on some overlays.
5856 // @option className: String = ''
5857 // A custom CSS class name to assign to the popup.
5860 // @option pane: String = 'popupPane'
5861 // `Map pane` where the popup will be added.
5865 initialize: function (options, source) {
5866 L.setOptions(this, options);
5868 this._source = source;
5871 onAdd: function (map) {
5872 this._zoomAnimated = map._zoomAnimated;
5874 if (!this._container) {
5878 if (map._fadeAnimated) {
5879 L.DomUtil.setOpacity(this._container, 0);
5882 clearTimeout(this._removeTimeout);
5883 this.getPane().appendChild(this._container);
5886 if (map._fadeAnimated) {
5887 L.DomUtil.setOpacity(this._container, 1);
5890 this.bringToFront();
5893 onRemove: function (map) {
5894 if (map._fadeAnimated) {
5895 L.DomUtil.setOpacity(this._container, 0);
5896 this._removeTimeout = setTimeout(L.bind(L.DomUtil.remove, L.DomUtil, this._container), 200);
5898 L.DomUtil.remove(this._container);
5903 // @method getLatLng: LatLng
5904 // Returns the geographical point of popup.
5905 getLatLng: function () {
5906 return this._latlng;
5909 // @method setLatLng(latlng: LatLng): this
5910 // Sets the geographical point where the popup will open.
5911 setLatLng: function (latlng) {
5912 this._latlng = L.latLng(latlng);
5914 this._updatePosition();
5920 // @method getContent: String|HTMLElement
5921 // Returns the content of the popup.
5922 getContent: function () {
5923 return this._content;
5926 // @method setContent(htmlContent: String|HTMLElement|Function): this
5927 // 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.
5928 setContent: function (content) {
5929 this._content = content;
5934 // @method getElement: String|HTMLElement
5935 // Alias for [getContent()](#popup-getcontent)
5936 getElement: function () {
5937 return this._container;
5940 // @method update: null
5941 // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
5942 update: function () {
5943 if (!this._map) { return; }
5945 this._container.style.visibility = 'hidden';
5947 this._updateContent();
5948 this._updateLayout();
5949 this._updatePosition();
5951 this._container.style.visibility = '';
5956 getEvents: function () {
5958 zoom: this._updatePosition,
5959 viewreset: this._updatePosition
5962 if (this._zoomAnimated) {
5963 events.zoomanim = this._animateZoom;
5968 // @method isOpen: Boolean
5969 // Returns `true` when the popup is visible on the map.
5970 isOpen: function () {
5971 return !!this._map && this._map.hasLayer(this);
5974 // @method bringToFront: this
5975 // Brings this popup in front of other popups (in the same map pane).
5976 bringToFront: function () {
5978 L.DomUtil.toFront(this._container);
5983 // @method bringToBack: this
5984 // Brings this popup to the back of other popups (in the same map pane).
5985 bringToBack: function () {
5987 L.DomUtil.toBack(this._container);
5992 _updateContent: function () {
5993 if (!this._content) { return; }
5995 var node = this._contentNode;
5996 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
5998 if (typeof content === 'string') {
5999 node.innerHTML = content;
6001 while (node.hasChildNodes()) {
6002 node.removeChild(node.firstChild);
6004 node.appendChild(content);
6006 this.fire('contentupdate');
6009 _updatePosition: function () {
6010 if (!this._map) { return; }
6012 var pos = this._map.latLngToLayerPoint(this._latlng),
6013 offset = L.point(this.options.offset),
6014 anchor = this._getAnchor();
6016 if (this._zoomAnimated) {
6017 L.DomUtil.setPosition(this._container, pos.add(anchor));
6019 offset = offset.add(pos).add(anchor);
6022 var bottom = this._containerBottom = -offset.y,
6023 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
6025 // bottom position the popup in case the height of the popup changes (images loading etc)
6026 this._container.style.bottom = bottom + 'px';
6027 this._container.style.left = left + 'px';
6030 _getAnchor: function () {
6040 * @inherits DivOverlay
6042 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
6043 * open popups while making sure that only one popup is open at one time
6044 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
6048 * If you want to just bind a popup to marker click and then open it, it's really easy:
6051 * marker.bindPopup(popupContent).openPopup();
6053 * Path overlays like polylines also have a `bindPopup` method.
6054 * Here's a more complicated way to open a popup on a map:
6057 * var popup = L.popup()
6058 * .setLatLng(latlng)
6059 * .setContent('<p>Hello world!<br />This is a nice popup.</p>')
6066 L.Popup = L.DivOverlay.extend({
6069 // @aka Popup options
6071 // @option maxWidth: Number = 300
6072 // Max width of the popup, in pixels.
6075 // @option minWidth: Number = 50
6076 // Min width of the popup, in pixels.
6079 // @option maxHeight: Number = null
6080 // If set, creates a scrollable container of the given height
6081 // inside a popup if its content exceeds it.
6084 // @option autoPan: Boolean = true
6085 // Set it to `false` if you don't want the map to do panning animation
6086 // to fit the opened popup.
6089 // @option autoPanPaddingTopLeft: Point = null
6090 // The margin between the popup and the top left corner of the map
6091 // view after autopanning was performed.
6092 autoPanPaddingTopLeft: null,
6094 // @option autoPanPaddingBottomRight: Point = null
6095 // The margin between the popup and the bottom right corner of the map
6096 // view after autopanning was performed.
6097 autoPanPaddingBottomRight: null,
6099 // @option autoPanPadding: Point = Point(5, 5)
6100 // Equivalent of setting both top left and bottom right autopan padding to the same value.
6101 autoPanPadding: [5, 5],
6103 // @option keepInView: Boolean = false
6104 // Set it to `true` if you want to prevent users from panning the popup
6105 // off of the screen while it is open.
6108 // @option closeButton: Boolean = true
6109 // Controls the presence of a close button in the popup.
6112 // @option autoClose: Boolean = true
6113 // Set it to `false` if you want to override the default behavior of
6114 // the popup closing when user clicks the map (set globally by
6115 // the Map's [closePopupOnClick](#map-closepopuponclick) option).
6118 // @option className: String = ''
6119 // A custom CSS class name to assign to the popup.
6124 // @method openOn(map: Map): this
6125 // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
6126 openOn: function (map) {
6127 map.openPopup(this);
6131 onAdd: function (map) {
6132 L.DivOverlay.prototype.onAdd.call(this, map);
6135 // @section Popup events
6136 // @event popupopen: PopupEvent
6137 // Fired when a popup is opened in the map
6138 map.fire('popupopen', {popup: this});
6142 // @section Popup events
6143 // @event popupopen: PopupEvent
6144 // Fired when a popup bound to this layer is opened
6145 this._source.fire('popupopen', {popup: this}, true);
6146 // For non-path layers, we toggle the popup when clicking
6147 // again the layer, so prevent the map to reopen it.
6148 if (!(this._source instanceof L.Path)) {
6149 this._source.on('preclick', L.DomEvent.stopPropagation);
6154 onRemove: function (map) {
6155 L.DivOverlay.prototype.onRemove.call(this, map);
6158 // @section Popup events
6159 // @event popupclose: PopupEvent
6160 // Fired when a popup in the map is closed
6161 map.fire('popupclose', {popup: this});
6165 // @section Popup events
6166 // @event popupclose: PopupEvent
6167 // Fired when a popup bound to this layer is closed
6168 this._source.fire('popupclose', {popup: this}, true);
6169 if (!(this._source instanceof L.Path)) {
6170 this._source.off('preclick', L.DomEvent.stopPropagation);
6175 getEvents: function () {
6176 var events = L.DivOverlay.prototype.getEvents.call(this);
6178 if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
6179 events.preclick = this._close;
6182 if (this.options.keepInView) {
6183 events.moveend = this._adjustPan;
6189 _close: function () {
6191 this._map.closePopup(this);
6195 _initLayout: function () {
6196 var prefix = 'leaflet-popup',
6197 container = this._container = L.DomUtil.create('div',
6198 prefix + ' ' + (this.options.className || '') +
6199 ' leaflet-zoom-animated');
6201 if (this.options.closeButton) {
6202 var closeButton = this._closeButton = L.DomUtil.create('a', prefix + '-close-button', container);
6203 closeButton.href = '#close';
6204 closeButton.innerHTML = '×';
6206 L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);
6209 var wrapper = this._wrapper = L.DomUtil.create('div', prefix + '-content-wrapper', container);
6210 this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);
6213 .disableClickPropagation(wrapper)
6214 .disableScrollPropagation(this._contentNode)
6215 .on(wrapper, 'contextmenu', L.DomEvent.stopPropagation);
6217 this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container);
6218 this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);
6221 _updateLayout: function () {
6222 var container = this._contentNode,
6223 style = container.style;
6226 style.whiteSpace = 'nowrap';
6228 var width = container.offsetWidth;
6229 width = Math.min(width, this.options.maxWidth);
6230 width = Math.max(width, this.options.minWidth);
6232 style.width = (width + 1) + 'px';
6233 style.whiteSpace = '';
6237 var height = container.offsetHeight,
6238 maxHeight = this.options.maxHeight,
6239 scrolledClass = 'leaflet-popup-scrolled';
6241 if (maxHeight && height > maxHeight) {
6242 style.height = maxHeight + 'px';
6243 L.DomUtil.addClass(container, scrolledClass);
6245 L.DomUtil.removeClass(container, scrolledClass);
6248 this._containerWidth = this._container.offsetWidth;
6251 _animateZoom: function (e) {
6252 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
6253 anchor = this._getAnchor();
6254 L.DomUtil.setPosition(this._container, pos.add(anchor));
6257 _adjustPan: function () {
6258 if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; }
6260 var map = this._map,
6261 marginBottom = parseInt(L.DomUtil.getStyle(this._container, 'marginBottom'), 10) || 0,
6262 containerHeight = this._container.offsetHeight + marginBottom,
6263 containerWidth = this._containerWidth,
6264 layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);
6266 layerPos._add(L.DomUtil.getPosition(this._container));
6268 var containerPos = map.layerPointToContainerPoint(layerPos),
6269 padding = L.point(this.options.autoPanPadding),
6270 paddingTL = L.point(this.options.autoPanPaddingTopLeft || padding),
6271 paddingBR = L.point(this.options.autoPanPaddingBottomRight || padding),
6272 size = map.getSize(),
6276 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
6277 dx = containerPos.x + containerWidth - size.x + paddingBR.x;
6279 if (containerPos.x - dx - paddingTL.x < 0) { // left
6280 dx = containerPos.x - paddingTL.x;
6282 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
6283 dy = containerPos.y + containerHeight - size.y + paddingBR.y;
6285 if (containerPos.y - dy - paddingTL.y < 0) { // top
6286 dy = containerPos.y - paddingTL.y;
6290 // @section Popup events
6291 // @event autopanstart: Event
6292 // Fired when the map starts autopanning when opening a popup.
6295 .fire('autopanstart')
6300 _onCloseButtonClick: function (e) {
6305 _getAnchor: function () {
6306 // Where should we anchor the popup on the source layer?
6307 return L.point(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
6313 // @factory L.popup(options?: Popup options, source?: Layer)
6314 // 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.
6315 L.popup = function (options, source) {
6316 return new L.Popup(options, source);
6321 * @section Interaction Options
6322 * @option closePopupOnClick: Boolean = true
6323 * Set it to `false` if you don't want popups to close when user clicks the map.
6325 L.Map.mergeOptions({
6326 closePopupOnClick: true
6331 // @section Methods for Layers and Controls
6333 // @method openPopup(popup: Popup): this
6334 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
6336 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
6337 // Creates a popup with the specified content and options and opens it in the given point on a map.
6338 openPopup: function (popup, latlng, options) {
6339 if (!(popup instanceof L.Popup)) {
6340 popup = new L.Popup(options).setContent(popup);
6344 popup.setLatLng(latlng);
6347 if (this.hasLayer(popup)) {
6351 if (this._popup && this._popup.options.autoClose) {
6355 this._popup = popup;
6356 return this.addLayer(popup);
6359 // @method closePopup(popup?: Popup): this
6360 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
6361 closePopup: function (popup) {
6362 if (!popup || popup === this._popup) {
6363 popup = this._popup;
6367 this.removeLayer(popup);
6377 * @section Popup methods example
6379 * All layers share a set of methods convenient for binding popups to it.
6382 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
6383 * layer.openPopup();
6384 * layer.closePopup();
6387 * 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.
6390 // @section Popup methods
6393 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
6394 // Binds a popup to the layer with the passed `content` and sets up the
6395 // neccessary event listeners. If a `Function` is passed it will receive
6396 // the layer as the first argument and should return a `String` or `HTMLElement`.
6397 bindPopup: function (content, options) {
6399 if (content instanceof L.Popup) {
6400 L.setOptions(content, options);
6401 this._popup = content;
6402 content._source = this;
6404 if (!this._popup || options) {
6405 this._popup = new L.Popup(options, this);
6407 this._popup.setContent(content);
6410 if (!this._popupHandlersAdded) {
6412 click: this._openPopup,
6413 remove: this.closePopup,
6414 move: this._movePopup
6416 this._popupHandlersAdded = true;
6422 // @method unbindPopup(): this
6423 // Removes the popup previously bound with `bindPopup`.
6424 unbindPopup: function () {
6427 click: this._openPopup,
6428 remove: this.closePopup,
6429 move: this._movePopup
6431 this._popupHandlersAdded = false;
6437 // @method openPopup(latlng?: LatLng): this
6438 // Opens the bound popup at the specificed `latlng` or at the default popup anchor if no `latlng` is passed.
6439 openPopup: function (layer, latlng) {
6440 if (!(layer instanceof L.Layer)) {
6445 if (layer instanceof L.FeatureGroup) {
6446 for (var id in this._layers) {
6447 layer = this._layers[id];
6453 latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
6456 if (this._popup && this._map) {
6457 // set popup source to this layer
6458 this._popup._source = layer;
6460 // update the popup (content, layout, ect...)
6461 this._popup.update();
6463 // open the popup on the map
6464 this._map.openPopup(this._popup, latlng);
6470 // @method closePopup(): this
6471 // Closes the popup bound to this layer if it is open.
6472 closePopup: function () {
6474 this._popup._close();
6479 // @method togglePopup(): this
6480 // Opens or closes the popup bound to this layer depending on its current state.
6481 togglePopup: function (target) {
6483 if (this._popup._map) {
6486 this.openPopup(target);
6492 // @method isPopupOpen(): boolean
6493 // Returns `true` if the popup bound to this layer is currently open.
6494 isPopupOpen: function () {
6495 return this._popup.isOpen();
6498 // @method setPopupContent(content: String|HTMLElement|Popup): this
6499 // Sets the content of the popup bound to this layer.
6500 setPopupContent: function (content) {
6502 this._popup.setContent(content);
6507 // @method getPopup(): Popup
6508 // Returns the popup bound to this layer.
6509 getPopup: function () {
6513 _openPopup: function (e) {
6514 var layer = e.layer || e.target;
6524 // prevent map click
6527 // if this inherits from Path its a vector and we can just
6528 // open the popup at the new location
6529 if (layer instanceof L.Path) {
6530 this.openPopup(e.layer || e.target, e.latlng);
6534 // otherwise treat it like a marker and figure out
6535 // if we should toggle it open/closed
6536 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
6539 this.openPopup(layer, e.latlng);
6543 _movePopup: function (e) {
6544 this._popup.setLatLng(e.latlng);
6551 * Popup extension to L.Marker, adding popup-related methods.
6555 _getPopupAnchor: function () {
6556 return this.options.icon.options.popupAnchor || [0, 0];
6564 * @inherits DivOverlay
6566 * Used to display small texts on top of map layers.
6571 * marker.bindTooltip("my tooltip text").openTooltip();
6573 * Note about tooltip offset. Leaflet takes two options in consideration
6574 * for computing tooltip offseting:
6575 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
6576 * Add a positive x offset to move the tooltip to the right, and a positive y offset to
6577 * move it to the bottom. Negatives will move to the left and top.
6578 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
6579 * should adapt this value if you use a custom icon.
6583 // @namespace Tooltip
6584 L.Tooltip = L.DivOverlay.extend({
6587 // @aka Tooltip options
6589 // @option pane: String = 'tooltipPane'
6590 // `Map pane` where the tooltip will be added.
6591 pane: 'tooltipPane',
6593 // @option offset: Point = Point(0, 0)
6594 // Optional offset of the tooltip position.
6597 // @option direction: String = 'auto'
6598 // Direction where to open the tooltip. Possible values are: `right`, `left`,
6599 // `top`, `bottom`, `center`, `auto`.
6600 // `auto` will dynamicaly switch between `right` and `left` according to the tooltip
6601 // position on the map.
6604 // @option permanent: Boolean = false
6605 // Whether to open the tooltip permanently or only on mouseover.
6608 // @option sticky: Boolean = false
6609 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
6612 // @option interactive: Boolean = false
6613 // If true, the tooltip will listen to the feature events.
6616 // @option opacity: Number = 0.9
6617 // Tooltip container opacity.
6621 onAdd: function (map) {
6622 L.DivOverlay.prototype.onAdd.call(this, map);
6623 this.setOpacity(this.options.opacity);
6626 // @section Tooltip events
6627 // @event tooltipopen: TooltipEvent
6628 // Fired when a tooltip is opened in the map.
6629 map.fire('tooltipopen', {tooltip: this});
6633 // @section Tooltip events
6634 // @event tooltipopen: TooltipEvent
6635 // Fired when a tooltip bound to this layer is opened.
6636 this._source.fire('tooltipopen', {tooltip: this}, true);
6640 onRemove: function (map) {
6641 L.DivOverlay.prototype.onRemove.call(this, map);
6644 // @section Tooltip events
6645 // @event tooltipclose: TooltipEvent
6646 // Fired when a tooltip in the map is closed.
6647 map.fire('tooltipclose', {tooltip: this});
6651 // @section Tooltip events
6652 // @event tooltipclose: TooltipEvent
6653 // Fired when a tooltip bound to this layer is closed.
6654 this._source.fire('tooltipclose', {tooltip: this}, true);
6658 getEvents: function () {
6659 var events = L.DivOverlay.prototype.getEvents.call(this);
6661 if (L.Browser.touch && !this.options.permanent) {
6662 events.preclick = this._close;
6668 _close: function () {
6670 this._map.closeTooltip(this);
6674 _initLayout: function () {
6675 var prefix = 'leaflet-tooltip',
6676 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
6678 this._contentNode = this._container = L.DomUtil.create('div', className);
6681 _updateLayout: function () {},
6683 _adjustPan: function () {},
6685 _setPosition: function (pos) {
6686 var map = this._map,
6687 container = this._container,
6688 centerPoint = map.latLngToContainerPoint(map.getCenter()),
6689 tooltipPoint = map.layerPointToContainerPoint(pos),
6690 direction = this.options.direction,
6691 tooltipWidth = container.offsetWidth,
6692 tooltipHeight = container.offsetHeight,
6693 offset = L.point(this.options.offset),
6694 anchor = this._getAnchor();
6696 if (direction === 'top') {
6697 pos = pos.add(L.point(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y));
6698 } else if (direction === 'bottom') {
6699 pos = pos.subtract(L.point(tooltipWidth / 2 - offset.x, -offset.y));
6700 } else if (direction === 'center') {
6701 pos = pos.subtract(L.point(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y));
6702 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
6703 direction = 'right';
6704 pos = pos.add([offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y]);
6707 pos = pos.subtract(L.point(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y));
6710 L.DomUtil.removeClass(container, 'leaflet-tooltip-right');
6711 L.DomUtil.removeClass(container, 'leaflet-tooltip-left');
6712 L.DomUtil.removeClass(container, 'leaflet-tooltip-top');
6713 L.DomUtil.removeClass(container, 'leaflet-tooltip-bottom');
6714 L.DomUtil.addClass(container, 'leaflet-tooltip-' + direction);
6715 L.DomUtil.setPosition(container, pos);
6718 _updatePosition: function () {
6719 var pos = this._map.latLngToLayerPoint(this._latlng);
6720 this._setPosition(pos);
6723 setOpacity: function (opacity) {
6724 this.options.opacity = opacity;
6726 if (this._container) {
6727 L.DomUtil.setOpacity(this._container, opacity);
6731 _animateZoom: function (e) {
6732 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
6733 this._setPosition(pos);
6736 _getAnchor: function () {
6737 // Where should we anchor the tooltip on the source layer?
6738 return L.point(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
6743 // @namespace Tooltip
6744 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
6745 // 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.
6746 L.tooltip = function (options, source) {
6747 return new L.Tooltip(options, source);
6751 // @section Methods for Layers and Controls
6754 // @method openTooltip(tooltip: Tooltip): this
6755 // Opens the specified tooltip.
6757 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
6758 // Creates a tooltip with the specified content and options and open it.
6759 openTooltip: function (tooltip, latlng, options) {
6760 if (!(tooltip instanceof L.Tooltip)) {
6761 tooltip = new L.Tooltip(options).setContent(tooltip);
6765 tooltip.setLatLng(latlng);
6768 if (this.hasLayer(tooltip)) {
6772 return this.addLayer(tooltip);
6775 // @method closeTooltip(tooltip?: Tooltip): this
6776 // Closes the tooltip given as parameter.
6777 closeTooltip: function (tooltip) {
6779 this.removeLayer(tooltip);
6790 * @section Tooltip methods example
6792 * All layers share a set of methods convenient for binding tooltips to it.
6795 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
6796 * layer.openTooltip();
6797 * layer.closeTooltip();
6801 // @section Tooltip methods
6804 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
6805 // Binds a tooltip to the layer with the passed `content` and sets up the
6806 // neccessary event listeners. If a `Function` is passed it will receive
6807 // the layer as the first argument and should return a `String` or `HTMLElement`.
6808 bindTooltip: function (content, options) {
6810 if (content instanceof L.Tooltip) {
6811 L.setOptions(content, options);
6812 this._tooltip = content;
6813 content._source = this;
6815 if (!this._tooltip || options) {
6816 this._tooltip = L.tooltip(options, this);
6818 this._tooltip.setContent(content);
6822 this._initTooltipInteractions();
6824 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
6831 // @method unbindTooltip(): this
6832 // Removes the tooltip previously bound with `bindTooltip`.
6833 unbindTooltip: function () {
6834 if (this._tooltip) {
6835 this._initTooltipInteractions(true);
6836 this.closeTooltip();
6837 this._tooltip = null;
6842 _initTooltipInteractions: function (remove) {
6843 if (!remove && this._tooltipHandlersAdded) { return; }
6844 var onOff = remove ? 'off' : 'on',
6846 remove: this.closeTooltip,
6847 move: this._moveTooltip
6849 if (!this._tooltip.options.permanent) {
6850 events.mouseover = this._openTooltip;
6851 events.mouseout = this.closeTooltip;
6852 if (this._tooltip.options.sticky) {
6853 events.mousemove = this._moveTooltip;
6855 if (L.Browser.touch) {
6856 events.click = this._openTooltip;
6859 events.add = this._openTooltip;
6861 this[onOff](events);
6862 this._tooltipHandlersAdded = !remove;
6865 // @method openTooltip(latlng?: LatLng): this
6866 // Opens the bound tooltip at the specificed `latlng` or at the default tooltip anchor if no `latlng` is passed.
6867 openTooltip: function (layer, latlng) {
6868 if (!(layer instanceof L.Layer)) {
6873 if (layer instanceof L.FeatureGroup) {
6874 for (var id in this._layers) {
6875 layer = this._layers[id];
6881 latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
6884 if (this._tooltip && this._map) {
6886 // set tooltip source to this layer
6887 this._tooltip._source = layer;
6889 // update the tooltip (content, layout, ect...)
6890 this._tooltip.update();
6892 // open the tooltip on the map
6893 this._map.openTooltip(this._tooltip, latlng);
6895 // Tooltip container may not be defined if not permanent and never
6897 if (this._tooltip.options.interactive && this._tooltip._container) {
6898 L.DomUtil.addClass(this._tooltip._container, 'leaflet-clickable');
6899 this.addInteractiveTarget(this._tooltip._container);
6906 // @method closeTooltip(): this
6907 // Closes the tooltip bound to this layer if it is open.
6908 closeTooltip: function () {
6909 if (this._tooltip) {
6910 this._tooltip._close();
6911 if (this._tooltip.options.interactive && this._tooltip._container) {
6912 L.DomUtil.removeClass(this._tooltip._container, 'leaflet-clickable');
6913 this.removeInteractiveTarget(this._tooltip._container);
6919 // @method toggleTooltip(): this
6920 // Opens or closes the tooltip bound to this layer depending on its current state.
6921 toggleTooltip: function (target) {
6922 if (this._tooltip) {
6923 if (this._tooltip._map) {
6924 this.closeTooltip();
6926 this.openTooltip(target);
6932 // @method isTooltipOpen(): boolean
6933 // Returns `true` if the tooltip bound to this layer is currently open.
6934 isTooltipOpen: function () {
6935 return this._tooltip.isOpen();
6938 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
6939 // Sets the content of the tooltip bound to this layer.
6940 setTooltipContent: function (content) {
6941 if (this._tooltip) {
6942 this._tooltip.setContent(content);
6947 // @method getTooltip(): Tooltip
6948 // Returns the tooltip bound to this layer.
6949 getTooltip: function () {
6950 return this._tooltip;
6953 _openTooltip: function (e) {
6954 var layer = e.layer || e.target;
6956 if (!this._tooltip || !this._map) {
6959 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
6962 _moveTooltip: function (e) {
6963 var latlng = e.latlng, containerPoint, layerPoint;
6964 if (this._tooltip.options.sticky && e.originalEvent) {
6965 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
6966 layerPoint = this._map.containerPointToLayerPoint(containerPoint);
6967 latlng = this._map.layerPointToLatLng(layerPoint);
6969 this._tooltip.setLatLng(latlng);
6976 * Tooltip extension to L.Marker, adding tooltip-related methods.
6980 _getTooltipAnchor: function () {
6981 return this.options.icon.options.tooltipAnchor || [0, 0];
6992 * Used to group several layers and handle them as one. If you add it to the map,
6993 * any layers added or removed from the group will be added/removed on the map as
6994 * well. Extends `Layer`.
6999 * L.layerGroup([marker1, marker2])
7000 * .addLayer(polyline)
7005 L.LayerGroup = L.Layer.extend({
7007 initialize: function (layers) {
7013 for (i = 0, len = layers.length; i < len; i++) {
7014 this.addLayer(layers[i]);
7019 // @method addLayer(layer: Layer): this
7020 // Adds the given layer to the group.
7021 addLayer: function (layer) {
7022 var id = this.getLayerId(layer);
7024 this._layers[id] = layer;
7027 this._map.addLayer(layer);
7033 // @method removeLayer(layer: Layer): this
7034 // Removes the given layer from the group.
7036 // @method removeLayer(id: Number): this
7037 // Removes the layer with the given internal ID from the group.
7038 removeLayer: function (layer) {
7039 var id = layer in this._layers ? layer : this.getLayerId(layer);
7041 if (this._map && this._layers[id]) {
7042 this._map.removeLayer(this._layers[id]);
7045 delete this._layers[id];
7050 // @method hasLayer(layer: Layer): Boolean
7051 // Returns `true` if the given layer is currently added to the group.
7052 hasLayer: function (layer) {
7053 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
7056 // @method clearLayers(): this
7057 // Removes all the layers from the group.
7058 clearLayers: function () {
7059 for (var i in this._layers) {
7060 this.removeLayer(this._layers[i]);
7065 // @method invoke(methodName: String, …): this
7066 // Calls `methodName` on every layer contained in this group, passing any
7067 // additional parameters. Has no effect if the layers contained do not
7068 // implement `methodName`.
7069 invoke: function (methodName) {
7070 var args = Array.prototype.slice.call(arguments, 1),
7073 for (i in this._layers) {
7074 layer = this._layers[i];
7076 if (layer[methodName]) {
7077 layer[methodName].apply(layer, args);
7084 onAdd: function (map) {
7085 for (var i in this._layers) {
7086 map.addLayer(this._layers[i]);
7090 onRemove: function (map) {
7091 for (var i in this._layers) {
7092 map.removeLayer(this._layers[i]);
7096 // @method eachLayer(fn: Function, context?: Object): this
7097 // Iterates over the layers of the group, optionally specifying context of the iterator function.
7099 // group.eachLayer(function (layer) {
7100 // layer.bindPopup('Hello');
7103 eachLayer: function (method, context) {
7104 for (var i in this._layers) {
7105 method.call(context, this._layers[i]);
7110 // @method getLayer(id: Number): Layer
7111 // Returns the layer with the given internal ID.
7112 getLayer: function (id) {
7113 return this._layers[id];
7116 // @method getLayers(): Layer[]
7117 // Returns an array of all the layers added to the group.
7118 getLayers: function () {
7121 for (var i in this._layers) {
7122 layers.push(this._layers[i]);
7127 // @method setZIndex(zIndex: Number): this
7128 // Calls `setZIndex` on every layer contained in this group, passing the z-index.
7129 setZIndex: function (zIndex) {
7130 return this.invoke('setZIndex', zIndex);
7133 // @method getLayerId(layer: Layer): Number
7134 // Returns the internal ID for a layer
7135 getLayerId: function (layer) {
7136 return L.stamp(layer);
7141 // @factory L.layerGroup(layers: Layer[])
7142 // Create a layer group, optionally given an initial set of layers.
7143 L.layerGroup = function (layers) {
7144 return new L.LayerGroup(layers);
7150 * @class FeatureGroup
7151 * @aka L.FeatureGroup
7152 * @inherits LayerGroup
7154 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
7155 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
7156 * * Events are propagated to the `FeatureGroup`, so if the group has an event
7157 * handler, it will handle events from any of the layers. This includes mouse events
7158 * and custom events.
7159 * * Has `layeradd` and `layerremove` events
7164 * L.featureGroup([marker1, marker2, polyline])
7165 * .bindPopup('Hello world!')
7166 * .on('click', function() { alert('Clicked on a member of the group!'); })
7171 L.FeatureGroup = L.LayerGroup.extend({
7173 addLayer: function (layer) {
7174 if (this.hasLayer(layer)) {
7178 layer.addEventParent(this);
7180 L.LayerGroup.prototype.addLayer.call(this, layer);
7182 // @event layeradd: LayerEvent
7183 // Fired when a layer is added to this `FeatureGroup`
7184 return this.fire('layeradd', {layer: layer});
7187 removeLayer: function (layer) {
7188 if (!this.hasLayer(layer)) {
7191 if (layer in this._layers) {
7192 layer = this._layers[layer];
7195 layer.removeEventParent(this);
7197 L.LayerGroup.prototype.removeLayer.call(this, layer);
7199 // @event layerremove: LayerEvent
7200 // Fired when a layer is removed from this `FeatureGroup`
7201 return this.fire('layerremove', {layer: layer});
7204 // @method setStyle(style: Path options): this
7205 // Sets the given path options to each layer of the group that has a `setStyle` method.
7206 setStyle: function (style) {
7207 return this.invoke('setStyle', style);
7210 // @method bringToFront(): this
7211 // Brings the layer group to the top of all other layers
7212 bringToFront: function () {
7213 return this.invoke('bringToFront');
7216 // @method bringToBack(): this
7217 // Brings the layer group to the top of all other layers
7218 bringToBack: function () {
7219 return this.invoke('bringToBack');
7222 // @method getBounds(): LatLngBounds
7223 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
7224 getBounds: function () {
7225 var bounds = new L.LatLngBounds();
7227 for (var id in this._layers) {
7228 var layer = this._layers[id];
7229 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
7235 // @factory L.featureGroup(layers: Layer[])
7236 // Create a feature group, optionally given an initial set of layers.
7237 L.featureGroup = function (layers) {
7238 return new L.FeatureGroup(layers);
7248 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
7249 * DOM container of the renderer, its bounds, and its zoom animation.
7251 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
7252 * itself can be added or removed to the map. All paths use a renderer, which can
7253 * be implicit (the map will decide the type of renderer and use it automatically)
7254 * or explicit (using the [`renderer`](#path-renderer) option of the path).
7256 * Do not use this class directly, use `SVG` and `Canvas` instead.
7258 * @event update: Event
7259 * Fired when the renderer updates its bounds, center and zoom, for example when
7263 L.Renderer = L.Layer.extend({
7266 // @aka Renderer options
7268 // @option padding: Number = 0.1
7269 // How much to extend the clip area around the map view (relative to its size)
7270 // e.g. 0.1 would be 10% of map view in each direction
7274 initialize: function (options) {
7275 L.setOptions(this, options);
7279 onAdd: function () {
7280 if (!this._container) {
7281 this._initContainer(); // defined by renderer implementations
7283 if (this._zoomAnimated) {
7284 L.DomUtil.addClass(this._container, 'leaflet-zoom-animated');
7288 this.getPane().appendChild(this._container);
7292 onRemove: function () {
7293 L.DomUtil.remove(this._container);
7296 getEvents: function () {
7298 viewreset: this._reset,
7300 moveend: this._update
7302 if (this._zoomAnimated) {
7303 events.zoomanim = this._onAnimZoom;
7308 _onAnimZoom: function (ev) {
7309 this._updateTransform(ev.center, ev.zoom);
7312 _onZoom: function () {
7313 this._updateTransform(this._map.getCenter(), this._map.getZoom());
7316 _updateTransform: function (center, zoom) {
7317 var scale = this._map.getZoomScale(zoom, this._zoom),
7318 position = L.DomUtil.getPosition(this._container),
7319 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
7320 currentCenterPoint = this._map.project(this._center, zoom),
7321 destCenterPoint = this._map.project(center, zoom),
7322 centerOffset = destCenterPoint.subtract(currentCenterPoint),
7324 topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
7326 if (L.Browser.any3d) {
7327 L.DomUtil.setTransform(this._container, topLeftOffset, scale);
7329 L.DomUtil.setPosition(this._container, topLeftOffset);
7333 _reset: function () {
7335 this._updateTransform(this._center, this._zoom);
7338 _update: function () {
7339 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
7340 // Subclasses are responsible of firing the 'update' event.
7341 var p = this.options.padding,
7342 size = this._map.getSize(),
7343 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
7345 this._bounds = new L.Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
7347 this._center = this._map.getCenter();
7348 this._zoom = this._map.getZoom();
7354 // @namespace Map; @method getRenderer(layer: Path): Renderer
7355 // Returns the instance of `Renderer` that should be used to render the given
7356 // `Path`. It will ensure that the `renderer` options of the map and paths
7357 // are respected, and that the renderers do exist on the map.
7358 getRenderer: function (layer) {
7359 // @namespace Path; @option renderer: Renderer
7360 // Use this specific instance of `Renderer` for this path. Takes
7361 // precedence over the map's [default renderer](#map-renderer).
7362 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
7365 // @namespace Map; @option preferCanvas: Boolean = false
7366 // Whether `Path`s should be rendered on a `Canvas` renderer.
7367 // By default, all `Path`s are rendered in a `SVG` renderer.
7368 renderer = this._renderer = (this.options.preferCanvas && L.canvas()) || L.svg();
7371 if (!this.hasLayer(renderer)) {
7372 this.addLayer(renderer);
7377 _getPaneRenderer: function (name) {
7378 if (name === 'overlayPane' || name === undefined) {
7382 var renderer = this._paneRenderers[name];
7383 if (renderer === undefined) {
7384 renderer = (L.SVG && L.svg({pane: name})) || (L.Canvas && L.canvas({pane: name}));
7385 this._paneRenderers[name] = renderer;
7396 * @inherits Interactive layer
7398 * An abstract class that contains options and constants shared between vector
7399 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
7402 L.Path = L.Layer.extend({
7405 // @aka Path options
7407 // @option stroke: Boolean = true
7408 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
7411 // @option color: String = '#3388ff'
7415 // @option weight: Number = 3
7416 // Stroke width in pixels
7419 // @option opacity: Number = 1.0
7423 // @option lineCap: String= 'round'
7424 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
7427 // @option lineJoin: String = 'round'
7428 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
7431 // @option dashArray: String = null
7432 // 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).
7435 // @option dashOffset: String = null
7436 // 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).
7439 // @option fill: Boolean = depends
7440 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
7443 // @option fillColor: String = *
7444 // Fill color. Defaults to the value of the [`color`](#path-color) option
7447 // @option fillOpacity: Number = 0.2
7451 // @option fillRule: String = 'evenodd'
7452 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
7453 fillRule: 'evenodd',
7457 // Option inherited from "Interactive layer" abstract class
7461 beforeAdd: function (map) {
7462 // Renderer is set here because we need to call renderer.getEvents
7463 // before this.getEvents.
7464 this._renderer = map.getRenderer(this);
7467 onAdd: function () {
7468 this._renderer._initPath(this);
7470 this._renderer._addPath(this);
7471 this._renderer.on('update', this._update, this);
7474 onRemove: function () {
7475 this._renderer._removePath(this);
7476 this._renderer.off('update', this._update, this);
7479 getEvents: function () {
7481 zoomend: this._project,
7482 viewreset: this._reset
7486 // @method redraw(): this
7487 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
7488 redraw: function () {
7490 this._renderer._updatePath(this);
7495 // @method setStyle(style: Path options): this
7496 // Changes the appearance of a Path based on the options in the `Path options` object.
7497 setStyle: function (style) {
7498 L.setOptions(this, style);
7499 if (this._renderer) {
7500 this._renderer._updateStyle(this);
7505 // @method bringToFront(): this
7506 // Brings the layer to the top of all path layers.
7507 bringToFront: function () {
7508 if (this._renderer) {
7509 this._renderer._bringToFront(this);
7514 // @method bringToBack(): this
7515 // Brings the layer to the bottom of all path layers.
7516 bringToBack: function () {
7517 if (this._renderer) {
7518 this._renderer._bringToBack(this);
7523 getElement: function () {
7527 _reset: function () {
7528 // defined in children classes
7533 _clickTolerance: function () {
7534 // used when doing hit detection for Canvas layers
7535 return (this.options.stroke ? this.options.weight / 2 : 0) + (L.Browser.touch ? 10 : 0);
7542 * @namespace LineUtil
7544 * Various utility functions for polyine points processing, used by Leaflet internally to make polylines lightning-fast.
7549 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
7550 // Improves rendering performance dramatically by lessening the number of points to draw.
7552 // @function simplify(points: Point[], tolerance: Number): Point[]
7553 // Dramatically reduces the number of points in a polyline while retaining
7554 // its shape and returns a new array of simplified points, using the
7555 // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
7556 // Used for a huge performance boost when processing/displaying Leaflet polylines for
7557 // each zoom level and also reducing visual noise. tolerance affects the amount of
7558 // simplification (lesser value means higher quality but slower and with more points).
7559 // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
7560 simplify: function (points, tolerance) {
7561 if (!tolerance || !points.length) {
7562 return points.slice();
7565 var sqTolerance = tolerance * tolerance;
7567 // stage 1: vertex reduction
7568 points = this._reducePoints(points, sqTolerance);
7570 // stage 2: Douglas-Peucker simplification
7571 points = this._simplifyDP(points, sqTolerance);
7576 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
7577 // Returns the distance between point `p` and segment `p1` to `p2`.
7578 pointToSegmentDistance: function (p, p1, p2) {
7579 return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));
7582 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
7583 // Returns the closest point from a point `p` on a segment `p1` to `p2`.
7584 closestPointOnSegment: function (p, p1, p2) {
7585 return this._sqClosestPointOnSegment(p, p1, p2);
7588 // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
7589 _simplifyDP: function (points, sqTolerance) {
7591 var len = points.length,
7592 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
7593 markers = new ArrayConstructor(len);
7595 markers[0] = markers[len - 1] = 1;
7597 this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
7602 for (i = 0; i < len; i++) {
7604 newPoints.push(points[i]);
7611 _simplifyDPStep: function (points, markers, sqTolerance, first, last) {
7616 for (i = first + 1; i <= last - 1; i++) {
7617 sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);
7619 if (sqDist > maxSqDist) {
7625 if (maxSqDist > sqTolerance) {
7628 this._simplifyDPStep(points, markers, sqTolerance, first, index);
7629 this._simplifyDPStep(points, markers, sqTolerance, index, last);
7633 // reduce points that are too close to each other to a single point
7634 _reducePoints: function (points, sqTolerance) {
7635 var reducedPoints = [points[0]];
7637 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
7638 if (this._sqDist(points[i], points[prev]) > sqTolerance) {
7639 reducedPoints.push(points[i]);
7643 if (prev < len - 1) {
7644 reducedPoints.push(points[len - 1]);
7646 return reducedPoints;
7650 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
7651 // Clips the segment a to b by rectangular bounds with the
7652 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
7653 // (modifying the segment points directly!). Used by Leaflet to only show polyline
7654 // points that are on the screen or near, increasing performance.
7655 clipSegment: function (a, b, bounds, useLastCode, round) {
7656 var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
7657 codeB = this._getBitCode(b, bounds),
7659 codeOut, p, newCode;
7661 // save 2nd code to avoid calculating it on the next segment
7662 this._lastCode = codeB;
7665 // if a,b is inside the clip window (trivial accept)
7666 if (!(codeA | codeB)) {
7670 // if a,b is outside the clip window (trivial reject)
7671 if (codeA & codeB) {
7676 codeOut = codeA || codeB;
7677 p = this._getEdgeIntersection(a, b, codeOut, bounds, round);
7678 newCode = this._getBitCode(p, bounds);
7680 if (codeOut === codeA) {
7690 _getEdgeIntersection: function (a, b, code, bounds, round) {
7697 if (code & 8) { // top
7698 x = a.x + dx * (max.y - a.y) / dy;
7701 } else if (code & 4) { // bottom
7702 x = a.x + dx * (min.y - a.y) / dy;
7705 } else if (code & 2) { // right
7707 y = a.y + dy * (max.x - a.x) / dx;
7709 } else if (code & 1) { // left
7711 y = a.y + dy * (min.x - a.x) / dx;
7714 return new L.Point(x, y, round);
7717 _getBitCode: function (p, bounds) {
7720 if (p.x < bounds.min.x) { // left
7722 } else if (p.x > bounds.max.x) { // right
7726 if (p.y < bounds.min.y) { // bottom
7728 } else if (p.y > bounds.max.y) { // top
7735 // square distance (to avoid unnecessary Math.sqrt calls)
7736 _sqDist: function (p1, p2) {
7737 var dx = p2.x - p1.x,
7739 return dx * dx + dy * dy;
7742 // return closest point on segment or distance to that point
7743 _sqClosestPointOnSegment: function (p, p1, p2, sqDist) {
7748 dot = dx * dx + dy * dy,
7752 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
7766 return sqDist ? dx * dx + dy * dy : new L.Point(x, y);
7777 * A class for drawing polyline overlays on a map. Extends `Path`.
7782 * // create a red polyline from an array of LatLng points
7789 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
7791 * // zoom the map to the polyline
7792 * map.fitBounds(polyline.getBounds());
7795 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
7798 * // create a red polyline from an array of arrays of LatLng points
7800 * [[-122.68, 45.51],
7810 L.Polyline = L.Path.extend({
7813 // @aka Polyline options
7815 // @option smoothFactor: Number = 1.0
7816 // How much to simplify the polyline on each zoom level. More means
7817 // better performance and smoother look, and less means more accurate representation.
7820 // @option noClip: Boolean = false
7821 // Disable polyline clipping.
7825 initialize: function (latlngs, options) {
7826 L.setOptions(this, options);
7827 this._setLatLngs(latlngs);
7830 // @method getLatLngs(): LatLng[]
7831 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
7832 getLatLngs: function () {
7833 return this._latlngs;
7836 // @method setLatLngs(latlngs: LatLng[]): this
7837 // Replaces all the points in the polyline with the given array of geographical points.
7838 setLatLngs: function (latlngs) {
7839 this._setLatLngs(latlngs);
7840 return this.redraw();
7843 // @method isEmpty(): Boolean
7844 // Returns `true` if the Polyline has no LatLngs.
7845 isEmpty: function () {
7846 return !this._latlngs.length;
7849 closestLayerPoint: function (p) {
7850 var minDistance = Infinity,
7852 closest = L.LineUtil._sqClosestPointOnSegment,
7855 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
7856 var points = this._parts[j];
7858 for (var i = 1, len = points.length; i < len; i++) {
7862 var sqDist = closest(p, p1, p2, true);
7864 if (sqDist < minDistance) {
7865 minDistance = sqDist;
7866 minPoint = closest(p, p1, p2);
7871 minPoint.distance = Math.sqrt(minDistance);
7876 // @method getCenter(): LatLng
7877 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
7878 getCenter: function () {
7879 // throws error when not yet added to map as this center calculation requires projected coordinates
7881 throw new Error('Must add layer to map before using getCenter()');
7884 var i, halfDist, segDist, dist, p1, p2, ratio,
7885 points = this._rings[0],
7886 len = points.length;
7888 if (!len) { return null; }
7890 // polyline centroid algorithm; only uses the first ring if there are multiple
7892 for (i = 0, halfDist = 0; i < len - 1; i++) {
7893 halfDist += points[i].distanceTo(points[i + 1]) / 2;
7896 // The line is so small in the current view that all points are on the same pixel.
7897 if (halfDist === 0) {
7898 return this._map.layerPointToLatLng(points[0]);
7901 for (i = 0, dist = 0; i < len - 1; i++) {
7904 segDist = p1.distanceTo(p2);
7907 if (dist > halfDist) {
7908 ratio = (dist - halfDist) / segDist;
7909 return this._map.layerPointToLatLng([
7910 p2.x - ratio * (p2.x - p1.x),
7911 p2.y - ratio * (p2.y - p1.y)
7917 // @method getBounds(): LatLngBounds
7918 // Returns the `LatLngBounds` of the path.
7919 getBounds: function () {
7920 return this._bounds;
7923 // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
7924 // Adds a given point to the polyline. By default, adds to the first ring of
7925 // the polyline in case of a multi-polyline, but can be overridden by passing
7926 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
7927 addLatLng: function (latlng, latlngs) {
7928 latlngs = latlngs || this._defaultShape();
7929 latlng = L.latLng(latlng);
7930 latlngs.push(latlng);
7931 this._bounds.extend(latlng);
7932 return this.redraw();
7935 _setLatLngs: function (latlngs) {
7936 this._bounds = new L.LatLngBounds();
7937 this._latlngs = this._convertLatLngs(latlngs);
7940 _defaultShape: function () {
7941 return L.Polyline._flat(this._latlngs) ? this._latlngs : this._latlngs[0];
7944 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
7945 _convertLatLngs: function (latlngs) {
7947 flat = L.Polyline._flat(latlngs);
7949 for (var i = 0, len = latlngs.length; i < len; i++) {
7951 result[i] = L.latLng(latlngs[i]);
7952 this._bounds.extend(result[i]);
7954 result[i] = this._convertLatLngs(latlngs[i]);
7961 _project: function () {
7962 var pxBounds = new L.Bounds();
7964 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
7966 var w = this._clickTolerance(),
7967 p = new L.Point(w, w);
7969 if (this._bounds.isValid() && pxBounds.isValid()) {
7970 pxBounds.min._subtract(p);
7971 pxBounds.max._add(p);
7972 this._pxBounds = pxBounds;
7976 // recursively turns latlngs into a set of rings with projected coordinates
7977 _projectLatlngs: function (latlngs, result, projectedBounds) {
7978 var flat = latlngs[0] instanceof L.LatLng,
7979 len = latlngs.length,
7984 for (i = 0; i < len; i++) {
7985 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
7986 projectedBounds.extend(ring[i]);
7990 for (i = 0; i < len; i++) {
7991 this._projectLatlngs(latlngs[i], result, projectedBounds);
7996 // clip polyline by renderer bounds so that we have less to render for performance
7997 _clipPoints: function () {
7998 var bounds = this._renderer._bounds;
8001 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8005 if (this.options.noClip) {
8006 this._parts = this._rings;
8010 var parts = this._parts,
8011 i, j, k, len, len2, segment, points;
8013 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8014 points = this._rings[i];
8016 for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8017 segment = L.LineUtil.clipSegment(points[j], points[j + 1], bounds, j, true);
8019 if (!segment) { continue; }
8021 parts[k] = parts[k] || [];
8022 parts[k].push(segment[0]);
8024 // if segment goes out of screen, or it's the last one, it's the end of the line part
8025 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8026 parts[k].push(segment[1]);
8033 // simplify each clipped part of the polyline for performance
8034 _simplifyPoints: function () {
8035 var parts = this._parts,
8036 tolerance = this.options.smoothFactor;
8038 for (var i = 0, len = parts.length; i < len; i++) {
8039 parts[i] = L.LineUtil.simplify(parts[i], tolerance);
8043 _update: function () {
8044 if (!this._map) { return; }
8047 this._simplifyPoints();
8051 _updatePath: function () {
8052 this._renderer._updatePoly(this);
8056 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8057 // Instantiates a polyline object given an array of geographical points and
8058 // optionally an options object. You can create a `Polyline` object with
8059 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8060 // of geographic points.
8061 L.polyline = function (latlngs, options) {
8062 return new L.Polyline(latlngs, options);
8065 L.Polyline._flat = function (latlngs) {
8066 // true if it's a flat array of latlngs; false if nested
8067 return !L.Util.isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
8073 * @namespace PolyUtil
8074 * Various utility functions for polygon geometries.
8079 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
8080 * 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)).
8081 * Used by Leaflet to only show polygon points that are on the screen or near, increasing
8082 * performance. Note that polygon points needs different algorithm for clipping
8083 * than polyline, so there's a seperate method for it.
8085 L.PolyUtil.clipPolygon = function (points, bounds, round) {
8087 edges = [1, 4, 2, 8],
8093 for (i = 0, len = points.length; i < len; i++) {
8094 points[i]._code = lu._getBitCode(points[i], bounds);
8097 // for each edge (left, bottom, right, top)
8098 for (k = 0; k < 4; k++) {
8102 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
8106 // if a is inside the clip window
8107 if (!(a._code & edge)) {
8108 // if b is outside the clip window (a->b goes out of screen)
8109 if (b._code & edge) {
8110 p = lu._getEdgeIntersection(b, a, edge, bounds, round);
8111 p._code = lu._getBitCode(p, bounds);
8112 clippedPoints.push(p);
8114 clippedPoints.push(a);
8116 // else if b is inside the clip window (a->b enters the screen)
8117 } else if (!(b._code & edge)) {
8118 p = lu._getEdgeIntersection(b, a, edge, bounds, round);
8119 p._code = lu._getBitCode(p, bounds);
8120 clippedPoints.push(p);
8123 points = clippedPoints;
8134 * @inherits Polyline
8136 * A class for drawing polygon overlays on a map. Extends `Polyline`.
8138 * 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.
8144 * // create a red polygon from an array of LatLng points
8145 * var latlngs = [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]];
8147 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8149 * // zoom the map to the polygon
8150 * map.fitBounds(polygon.getBounds());
8153 * 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:
8157 * [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]], // outer ring
8158 * [[-108.58,37.29],[-108.58,40.71],[-102.50,40.71],[-102.50,37.29]] // hole
8162 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8166 * [ // first polygon
8167 * [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]], // outer ring
8168 * [[-108.58,37.29],[-108.58,40.71],[-102.50,40.71],[-102.50,37.29]] // hole
8170 * [ // second polygon
8171 * [[-109.05, 37],[-109.03, 41],[-102.05, 41],[-102.04, 37],[-109.05, 38]]
8177 L.Polygon = L.Polyline.extend({
8183 isEmpty: function () {
8184 return !this._latlngs.length || !this._latlngs[0].length;
8187 getCenter: function () {
8188 // throws error when not yet added to map as this center calculation requires projected coordinates
8190 throw new Error('Must add layer to map before using getCenter()');
8193 var i, j, p1, p2, f, area, x, y, center,
8194 points = this._rings[0],
8195 len = points.length;
8197 if (!len) { return null; }
8199 // polygon centroid algorithm; only uses the first ring if there are multiple
8203 for (i = 0, j = len - 1; i < len; j = i++) {
8207 f = p1.y * p2.x - p2.y * p1.x;
8208 x += (p1.x + p2.x) * f;
8209 y += (p1.y + p2.y) * f;
8214 // Polygon is so small that all points are on same pixel.
8217 center = [x / area, y / area];
8219 return this._map.layerPointToLatLng(center);
8222 _convertLatLngs: function (latlngs) {
8223 var result = L.Polyline.prototype._convertLatLngs.call(this, latlngs),
8224 len = result.length;
8226 // remove last point if it equals first one
8227 if (len >= 2 && result[0] instanceof L.LatLng && result[0].equals(result[len - 1])) {
8233 _setLatLngs: function (latlngs) {
8234 L.Polyline.prototype._setLatLngs.call(this, latlngs);
8235 if (L.Polyline._flat(this._latlngs)) {
8236 this._latlngs = [this._latlngs];
8240 _defaultShape: function () {
8241 return L.Polyline._flat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8244 _clipPoints: function () {
8245 // polygons need a different clipping algorithm so we redefine that
8247 var bounds = this._renderer._bounds,
8248 w = this.options.weight,
8249 p = new L.Point(w, w);
8251 // increase clip padding by stroke width to avoid stroke on clip edges
8252 bounds = new L.Bounds(bounds.min.subtract(p), bounds.max.add(p));
8255 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8259 if (this.options.noClip) {
8260 this._parts = this._rings;
8264 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8265 clipped = L.PolyUtil.clipPolygon(this._rings[i], bounds, true);
8266 if (clipped.length) {
8267 this._parts.push(clipped);
8272 _updatePath: function () {
8273 this._renderer._updatePoly(this, true);
8278 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8279 L.polygon = function (latlngs, options) {
8280 return new L.Polygon(latlngs, options);
8286 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
8294 * A class for drawing rectangle overlays on a map. Extends `Polygon`.
8299 * // define rectangle geographical bounds
8300 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
8302 * // create an orange rectangle
8303 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
8305 * // zoom the map to the rectangle bounds
8306 * map.fitBounds(bounds);
8312 L.Rectangle = L.Polygon.extend({
8313 initialize: function (latLngBounds, options) {
8314 L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
8317 // @method setBounds(latLngBounds: LatLngBounds): this
8318 // Redraws the rectangle with the passed bounds.
8319 setBounds: function (latLngBounds) {
8320 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
8323 _boundsToLatLngs: function (latLngBounds) {
8324 latLngBounds = L.latLngBounds(latLngBounds);
8326 latLngBounds.getSouthWest(),
8327 latLngBounds.getNorthWest(),
8328 latLngBounds.getNorthEast(),
8329 latLngBounds.getSouthEast()
8335 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
8336 L.rectangle = function (latLngBounds, options) {
8337 return new L.Rectangle(latLngBounds, options);
8343 * @class CircleMarker
8344 * @aka L.CircleMarker
8347 * A circle of a fixed size with radius specified in pixels. Extends `Path`.
8350 L.CircleMarker = L.Path.extend({
8353 // @aka CircleMarker options
8357 // @option radius: Number = 10
8358 // Radius of the circle marker, in pixels
8362 initialize: function (latlng, options) {
8363 L.setOptions(this, options);
8364 this._latlng = L.latLng(latlng);
8365 this._radius = this.options.radius;
8368 // @method setLatLng(latLng: LatLng): this
8369 // Sets the position of a circle marker to a new location.
8370 setLatLng: function (latlng) {
8371 this._latlng = L.latLng(latlng);
8373 return this.fire('move', {latlng: this._latlng});
8376 // @method getLatLng(): LatLng
8377 // Returns the current geographical position of the circle marker
8378 getLatLng: function () {
8379 return this._latlng;
8382 // @method setRadius(radius: Number): this
8383 // Sets the radius of a circle marker. Units are in pixels.
8384 setRadius: function (radius) {
8385 this.options.radius = this._radius = radius;
8386 return this.redraw();
8389 // @method getRadius(): Number
8390 // Returns the current radius of the circle
8391 getRadius: function () {
8392 return this._radius;
8395 setStyle : function (options) {
8396 var radius = options && options.radius || this._radius;
8397 L.Path.prototype.setStyle.call(this, options);
8398 this.setRadius(radius);
8402 _project: function () {
8403 this._point = this._map.latLngToLayerPoint(this._latlng);
8404 this._updateBounds();
8407 _updateBounds: function () {
8408 var r = this._radius,
8409 r2 = this._radiusY || r,
8410 w = this._clickTolerance(),
8411 p = [r + w, r2 + w];
8412 this._pxBounds = new L.Bounds(this._point.subtract(p), this._point.add(p));
8415 _update: function () {
8421 _updatePath: function () {
8422 this._renderer._updateCircle(this);
8425 _empty: function () {
8426 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
8431 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
8432 // Instantiates a circle marker object given a geographical point, and an optional options object.
8433 L.circleMarker = function (latlng, options) {
8434 return new L.CircleMarker(latlng, options);
8442 * @inherits CircleMarker
8444 * A class for drawing circle overlays on a map. Extends `CircleMarker`.
8446 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
8451 * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
8455 L.Circle = L.CircleMarker.extend({
8457 initialize: function (latlng, options, legacyOptions) {
8458 if (typeof options === 'number') {
8459 // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
8460 options = L.extend({}, legacyOptions, {radius: options});
8462 L.setOptions(this, options);
8463 this._latlng = L.latLng(latlng);
8465 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
8468 // @aka Circle options
8469 // @option radius: Number; Radius of the circle, in meters.
8470 this._mRadius = this.options.radius;
8473 // @method setRadius(radius: Number): this
8474 // Sets the radius of a circle. Units are in meters.
8475 setRadius: function (radius) {
8476 this._mRadius = radius;
8477 return this.redraw();
8480 // @method getRadius(): Number
8481 // Returns the current radius of a circle. Units are in meters.
8482 getRadius: function () {
8483 return this._mRadius;
8486 // @method getBounds(): LatLngBounds
8487 // Returns the `LatLngBounds` of the path.
8488 getBounds: function () {
8489 var half = [this._radius, this._radiusY || this._radius];
8491 return new L.LatLngBounds(
8492 this._map.layerPointToLatLng(this._point.subtract(half)),
8493 this._map.layerPointToLatLng(this._point.add(half)));
8496 setStyle: L.Path.prototype.setStyle,
8498 _project: function () {
8500 var lng = this._latlng.lng,
8501 lat = this._latlng.lat,
8503 crs = map.options.crs;
8505 if (crs.distance === L.CRS.Earth.distance) {
8506 var d = Math.PI / 180,
8507 latR = (this._mRadius / L.CRS.Earth.R) / d,
8508 top = map.project([lat + latR, lng]),
8509 bottom = map.project([lat - latR, lng]),
8510 p = top.add(bottom).divideBy(2),
8511 lat2 = map.unproject(p).lat,
8512 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
8513 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
8515 if (isNaN(lngR) || lngR === 0) {
8516 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
8519 this._point = p.subtract(map.getPixelOrigin());
8520 this._radius = isNaN(lngR) ? 0 : Math.max(Math.round(p.x - map.project([lat2, lng - lngR]).x), 1);
8521 this._radiusY = Math.max(Math.round(p.y - top.y), 1);
8524 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
8526 this._point = map.latLngToLayerPoint(this._latlng);
8527 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
8530 this._updateBounds();
8534 // @factory L.circle(latlng: LatLng, options?: Circle options)
8535 // Instantiates a circle object given a geographical point, and an options object
8536 // which contains the circle radius.
8538 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
8539 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
8540 // Do not use in new applications or plugins.
8541 L.circle = function (latlng, options, legacyOptions) {
8542 return new L.Circle(latlng, options, legacyOptions);
8549 * @inherits Renderer
8552 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
8553 * Inherits `Renderer`.
8555 * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
8556 * available in all web browsers, notably Android 2.x and 3.x.
8558 * Although SVG is not available on IE7 and IE8, these browsers support
8559 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
8560 * (a now deprecated technology), and the SVG renderer will fall back to VML in
8565 * Use SVG by default for all paths in the map:
8568 * var map = L.map('map', {
8573 * Use a SVG renderer with extra padding for specific vector geometries:
8576 * var map = L.map('map');
8577 * var myRenderer = L.svg({ padding: 0.5 });
8578 * var line = L.polyline( coordinates, { renderer: myRenderer } );
8579 * var circle = L.circle( center, { renderer: myRenderer } );
8583 L.SVG = L.Renderer.extend({
8585 getEvents: function () {
8586 var events = L.Renderer.prototype.getEvents.call(this);
8587 events.zoomstart = this._onZoomStart;
8591 _initContainer: function () {
8592 this._container = L.SVG.create('svg');
8594 // makes it possible to click through svg root; we'll reset it back in individual paths
8595 this._container.setAttribute('pointer-events', 'none');
8597 this._rootGroup = L.SVG.create('g');
8598 this._container.appendChild(this._rootGroup);
8601 _onZoomStart: function () {
8602 // Drag-then-pinch interactions might mess up the center and zoom.
8603 // In this case, the easiest way to prevent this is re-do the renderer
8604 // bounds and padding when the zooming starts.
8608 _update: function () {
8609 if (this._map._animatingZoom && this._bounds) { return; }
8611 L.Renderer.prototype._update.call(this);
8613 var b = this._bounds,
8615 container = this._container;
8617 // set size of svg-container if changed
8618 if (!this._svgSize || !this._svgSize.equals(size)) {
8619 this._svgSize = size;
8620 container.setAttribute('width', size.x);
8621 container.setAttribute('height', size.y);
8624 // movement: update container viewBox so that we don't have to change coordinates of individual layers
8625 L.DomUtil.setPosition(container, b.min);
8626 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
8628 this.fire('update');
8631 // methods below are called by vector layers implementations
8633 _initPath: function (layer) {
8634 var path = layer._path = L.SVG.create('path');
8637 // @option className: String = null
8638 // Custom class name set on an element. Only for SVG renderer.
8639 if (layer.options.className) {
8640 L.DomUtil.addClass(path, layer.options.className);
8643 if (layer.options.interactive) {
8644 L.DomUtil.addClass(path, 'leaflet-interactive');
8647 this._updateStyle(layer);
8650 _addPath: function (layer) {
8651 this._rootGroup.appendChild(layer._path);
8652 layer.addInteractiveTarget(layer._path);
8655 _removePath: function (layer) {
8656 L.DomUtil.remove(layer._path);
8657 layer.removeInteractiveTarget(layer._path);
8660 _updatePath: function (layer) {
8665 _updateStyle: function (layer) {
8666 var path = layer._path,
8667 options = layer.options;
8669 if (!path) { return; }
8671 if (options.stroke) {
8672 path.setAttribute('stroke', options.color);
8673 path.setAttribute('stroke-opacity', options.opacity);
8674 path.setAttribute('stroke-width', options.weight);
8675 path.setAttribute('stroke-linecap', options.lineCap);
8676 path.setAttribute('stroke-linejoin', options.lineJoin);
8678 if (options.dashArray) {
8679 path.setAttribute('stroke-dasharray', options.dashArray);
8681 path.removeAttribute('stroke-dasharray');
8684 if (options.dashOffset) {
8685 path.setAttribute('stroke-dashoffset', options.dashOffset);
8687 path.removeAttribute('stroke-dashoffset');
8690 path.setAttribute('stroke', 'none');
8694 path.setAttribute('fill', options.fillColor || options.color);
8695 path.setAttribute('fill-opacity', options.fillOpacity);
8696 path.setAttribute('fill-rule', options.fillRule || 'evenodd');
8698 path.setAttribute('fill', 'none');
8702 _updatePoly: function (layer, closed) {
8703 this._setPath(layer, L.SVG.pointsToPath(layer._parts, closed));
8706 _updateCircle: function (layer) {
8707 var p = layer._point,
8709 r2 = layer._radiusY || r,
8710 arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
8712 // drawing a circle with two half-arcs
8713 var d = layer._empty() ? 'M0 0' :
8714 'M' + (p.x - r) + ',' + p.y +
8715 arc + (r * 2) + ',0 ' +
8716 arc + (-r * 2) + ',0 ';
8718 this._setPath(layer, d);
8721 _setPath: function (layer, path) {
8722 layer._path.setAttribute('d', path);
8725 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
8726 _bringToFront: function (layer) {
8727 L.DomUtil.toFront(layer._path);
8730 _bringToBack: function (layer) {
8731 L.DomUtil.toBack(layer._path);
8736 // @namespace SVG; @section
8737 // There are several static functions which can be called without instantiating L.SVG:
8739 // @function create(name: String): SVGElement
8740 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
8741 // corresponding to the class name passed. For example, using 'line' will return
8742 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
8743 create: function (name) {
8744 return document.createElementNS('http://www.w3.org/2000/svg', name);
8747 // @function pointsToPath(rings: Point[], closed: Boolean): String
8748 // Generates a SVG path string for multiple rings, with each ring turning
8749 // into "M..L..L.." instructions
8750 pointsToPath: function (rings, closed) {
8752 i, j, len, len2, points, p;
8754 for (i = 0, len = rings.length; i < len; i++) {
8757 for (j = 0, len2 = points.length; j < len2; j++) {
8759 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
8762 // closes the ring for polygons; "x" is VML syntax
8763 str += closed ? (L.Browser.svg ? 'z' : 'x') : '';
8766 // SVG complains about empty path strings
8767 return str || 'M0 0';
8771 // @namespace Browser; @property svg: Boolean
8772 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
8773 L.Browser.svg = !!(document.createElementNS && L.SVG.create('svg').createSVGRect);
8777 // @factory L.svg(options?: Renderer options)
8778 // Creates a SVG renderer with the given options.
8779 L.svg = function (options) {
8780 return L.Browser.svg || L.Browser.vml ? new L.SVG(options) : null;
8786 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
8792 * 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.
8794 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
8795 * with old versions of Internet Explorer.
8798 // @namespace Browser; @property vml: Boolean
8799 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
8800 L.Browser.vml = !L.Browser.svg && (function () {
8802 var div = document.createElement('div');
8803 div.innerHTML = '<v:shape adj="1"/>';
8805 var shape = div.firstChild;
8806 shape.style.behavior = 'url(#default#VML)';
8808 return shape && (typeof shape.adj === 'object');
8815 // redefine some SVG methods to handle VML syntax which is similar but with some differences
8816 L.SVG.include(!L.Browser.vml ? {} : {
8818 _initContainer: function () {
8819 this._container = L.DomUtil.create('div', 'leaflet-vml-container');
8822 _update: function () {
8823 if (this._map._animatingZoom) { return; }
8824 L.Renderer.prototype._update.call(this);
8825 this.fire('update');
8828 _initPath: function (layer) {
8829 var container = layer._container = L.SVG.create('shape');
8831 L.DomUtil.addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
8833 container.coordsize = '1 1';
8835 layer._path = L.SVG.create('path');
8836 container.appendChild(layer._path);
8838 this._updateStyle(layer);
8841 _addPath: function (layer) {
8842 var container = layer._container;
8843 this._container.appendChild(container);
8845 if (layer.options.interactive) {
8846 layer.addInteractiveTarget(container);
8850 _removePath: function (layer) {
8851 var container = layer._container;
8852 L.DomUtil.remove(container);
8853 layer.removeInteractiveTarget(container);
8856 _updateStyle: function (layer) {
8857 var stroke = layer._stroke,
8859 options = layer.options,
8860 container = layer._container;
8862 container.stroked = !!options.stroke;
8863 container.filled = !!options.fill;
8865 if (options.stroke) {
8867 stroke = layer._stroke = L.SVG.create('stroke');
8869 container.appendChild(stroke);
8870 stroke.weight = options.weight + 'px';
8871 stroke.color = options.color;
8872 stroke.opacity = options.opacity;
8874 if (options.dashArray) {
8875 stroke.dashStyle = L.Util.isArray(options.dashArray) ?
8876 options.dashArray.join(' ') :
8877 options.dashArray.replace(/( *, *)/g, ' ');
8879 stroke.dashStyle = '';
8881 stroke.endcap = options.lineCap.replace('butt', 'flat');
8882 stroke.joinstyle = options.lineJoin;
8884 } else if (stroke) {
8885 container.removeChild(stroke);
8886 layer._stroke = null;
8891 fill = layer._fill = L.SVG.create('fill');
8893 container.appendChild(fill);
8894 fill.color = options.fillColor || options.color;
8895 fill.opacity = options.fillOpacity;
8898 container.removeChild(fill);
8903 _updateCircle: function (layer) {
8904 var p = layer._point.round(),
8905 r = Math.round(layer._radius),
8906 r2 = Math.round(layer._radiusY || r);
8908 this._setPath(layer, layer._empty() ? 'M0 0' :
8909 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
8912 _setPath: function (layer, path) {
8913 layer._path.v = path;
8916 _bringToFront: function (layer) {
8917 L.DomUtil.toFront(layer._container);
8920 _bringToBack: function (layer) {
8921 L.DomUtil.toBack(layer._container);
8925 if (L.Browser.vml) {
8926 L.SVG.create = (function () {
8928 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
8929 return function (name) {
8930 return document.createElement('<lvml:' + name + ' class="lvml">');
8933 return function (name) {
8934 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
8944 * @inherits Renderer
8947 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
8948 * Inherits `Renderer`.
8950 * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
8951 * available in all web browsers, notably IE8, and overlapping geometries might
8952 * not display properly in some edge cases.
8956 * Use Canvas by default for all paths in the map:
8959 * var map = L.map('map', {
8960 * renderer: L.canvas()
8964 * Use a Canvas renderer with extra padding for specific vector geometries:
8967 * var map = L.map('map');
8968 * var myRenderer = L.canvas({ padding: 0.5 });
8969 * var line = L.polyline( coordinates, { renderer: myRenderer } );
8970 * var circle = L.circle( center, { renderer: myRenderer } );
8974 L.Canvas = L.Renderer.extend({
8976 onAdd: function () {
8977 L.Renderer.prototype.onAdd.call(this);
8979 this._layers = this._layers || {};
8981 // Redraw vectors since canvas is cleared upon removal,
8982 // in case of removing the renderer itself from the map.
8986 _initContainer: function () {
8987 var container = this._container = document.createElement('canvas');
8990 .on(container, 'mousemove', L.Util.throttle(this._onMouseMove, 32, this), this)
8991 .on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this)
8992 .on(container, 'mouseout', this._handleMouseOut, this);
8994 this._ctx = container.getContext('2d');
8997 _update: function () {
8998 if (this._map._animatingZoom && this._bounds) { return; }
9000 this._drawnLayers = {};
9002 L.Renderer.prototype._update.call(this);
9004 var b = this._bounds,
9005 container = this._container,
9007 m = L.Browser.retina ? 2 : 1;
9009 L.DomUtil.setPosition(container, b.min);
9011 // set canvas size (also clearing it); use double size on retina
9012 container.width = m * size.x;
9013 container.height = m * size.y;
9014 container.style.width = size.x + 'px';
9015 container.style.height = size.y + 'px';
9017 if (L.Browser.retina) {
9018 this._ctx.scale(2, 2);
9021 // translate so we use the same path coordinates after canvas element moves
9022 this._ctx.translate(-b.min.x, -b.min.y);
9024 // Tell paths to redraw themselves
9025 this.fire('update');
9028 _initPath: function (layer) {
9029 this._updateDashArray(layer);
9030 this._layers[L.stamp(layer)] = layer;
9033 _addPath: L.Util.falseFn,
9035 _removePath: function (layer) {
9036 layer._removed = true;
9037 this._requestRedraw(layer);
9040 _updatePath: function (layer) {
9041 this._redrawBounds = layer._pxBounds;
9046 this._redrawBounds = null;
9049 _updateStyle: function (layer) {
9050 this._updateDashArray(layer);
9051 this._requestRedraw(layer);
9054 _updateDashArray: function (layer) {
9055 if (layer.options.dashArray) {
9056 var parts = layer.options.dashArray.split(','),
9059 for (i = 0; i < parts.length; i++) {
9060 dashArray.push(Number(parts[i]));
9062 layer.options._dashArray = dashArray;
9066 _requestRedraw: function (layer) {
9067 if (!this._map) { return; }
9069 var padding = (layer.options.weight || 0) + 1;
9070 this._redrawBounds = this._redrawBounds || new L.Bounds();
9071 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
9072 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
9074 this._redrawRequest = this._redrawRequest || L.Util.requestAnimFrame(this._redraw, this);
9077 _redraw: function () {
9078 this._redrawRequest = null;
9080 this._draw(true); // clear layers in redraw bounds
9081 this._draw(); // draw layers
9083 this._redrawBounds = null;
9086 _draw: function (clear) {
9087 this._clear = clear;
9088 var layer, bounds = this._redrawBounds;
9091 this._ctx.beginPath();
9092 this._ctx.rect(bounds.min.x, bounds.min.y, bounds.max.x - bounds.min.x, bounds.max.y - bounds.min.y);
9096 for (var id in this._layers) {
9097 layer = this._layers[id];
9098 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
9099 layer._updatePath();
9101 if (clear && layer._removed) {
9102 delete layer._removed;
9103 delete this._layers[id];
9106 this._ctx.restore(); // Restore state before clipping.
9109 _updatePoly: function (layer, closed) {
9112 parts = layer._parts,
9116 if (!len) { return; }
9118 this._drawnLayers[layer._leaflet_id] = layer;
9122 if (ctx.setLineDash) {
9123 ctx.setLineDash(layer.options && layer.options._dashArray || []);
9126 for (i = 0; i < len; i++) {
9127 for (j = 0, len2 = parts[i].length; j < len2; j++) {
9129 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
9136 this._fillStroke(ctx, layer);
9138 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
9141 _updateCircle: function (layer) {
9143 if (layer._empty()) { return; }
9145 var p = layer._point,
9148 s = (layer._radiusY || r) / r;
9150 this._drawnLayers[layer._leaflet_id] = layer;
9158 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
9164 this._fillStroke(ctx, layer);
9167 _fillStroke: function (ctx, layer) {
9168 var clear = this._clear,
9169 options = layer.options;
9171 ctx.globalCompositeOperation = clear ? 'destination-out' : 'source-over';
9174 ctx.globalAlpha = clear ? 1 : options.fillOpacity;
9175 ctx.fillStyle = options.fillColor || options.color;
9176 ctx.fill(options.fillRule || 'evenodd');
9179 if (options.stroke && options.weight !== 0) {
9180 ctx.globalAlpha = clear ? 1 : options.opacity;
9182 // if clearing shape, do it with the previously drawn line width
9183 layer._prevWeight = ctx.lineWidth = clear ? layer._prevWeight + 1 : options.weight;
9185 ctx.strokeStyle = options.color;
9186 ctx.lineCap = options.lineCap;
9187 ctx.lineJoin = options.lineJoin;
9192 // Canvas obviously doesn't have mouse events for individual drawn objects,
9193 // so we emulate that by calculating what's under the mouse on mousemove/click manually
9195 _onClick: function (e) {
9196 var point = this._map.mouseEventToLayerPoint(e), layers = [], layer;
9198 for (var id in this._layers) {
9199 layer = this._layers[id];
9200 if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
9201 L.DomEvent._fakeStop(e);
9205 if (layers.length) {
9206 this._fireEvent(layers, e);
9210 _onMouseMove: function (e) {
9211 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
9213 var point = this._map.mouseEventToLayerPoint(e);
9214 this._handleMouseOut(e, point);
9215 this._handleMouseHover(e, point);
9219 _handleMouseOut: function (e, point) {
9220 var layer = this._hoveredLayer;
9221 if (layer && (e.type === 'mouseout' || !layer._containsPoint(point))) {
9222 // if we're leaving the layer, fire mouseout
9223 L.DomUtil.removeClass(this._container, 'leaflet-interactive');
9224 this._fireEvent([layer], e, 'mouseout');
9225 this._hoveredLayer = null;
9229 _handleMouseHover: function (e, point) {
9232 for (id in this._drawnLayers) {
9233 layer = this._drawnLayers[id];
9234 if (layer.options.interactive && layer._containsPoint(point)) {
9235 L.DomUtil.addClass(this._container, 'leaflet-interactive'); // change cursor
9236 this._fireEvent([layer], e, 'mouseover');
9237 this._hoveredLayer = layer;
9241 if (this._hoveredLayer) {
9242 this._fireEvent([this._hoveredLayer], e);
9246 _fireEvent: function (layers, e, type) {
9247 this._map._fireDOMEvent(e, type || e.type, layers);
9250 // TODO _bringToFront & _bringToBack, pretty tricky
9252 _bringToFront: L.Util.falseFn,
9253 _bringToBack: L.Util.falseFn
9256 // @namespace Browser; @property canvas: Boolean
9257 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
9258 L.Browser.canvas = (function () {
9259 return !!document.createElement('canvas').getContext;
9262 // @namespace Canvas
9263 // @factory L.canvas(options?: Renderer options)
9264 // Creates a Canvas renderer with the given options.
9265 L.canvas = function (options) {
9266 return L.Browser.canvas ? new L.Canvas(options) : null;
9269 L.Polyline.prototype._containsPoint = function (p, closed) {
9270 var i, j, k, len, len2, part,
9271 w = this._clickTolerance();
9273 if (!this._pxBounds.contains(p)) { return false; }
9275 // hit detection for polylines
9276 for (i = 0, len = this._parts.length; i < len; i++) {
9277 part = this._parts[i];
9279 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
9280 if (!closed && (j === 0)) { continue; }
9282 if (L.LineUtil.pointToSegmentDistance(p, part[k], part[j]) <= w) {
9290 L.Polygon.prototype._containsPoint = function (p) {
9292 part, p1, p2, i, j, k, len, len2;
9294 if (!this._pxBounds.contains(p)) { return false; }
9296 // ray casting algorithm for detecting if point is in polygon
9297 for (i = 0, len = this._parts.length; i < len; i++) {
9298 part = this._parts[i];
9300 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
9304 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)) {
9310 // also check if it's on polygon stroke
9311 return inside || L.Polyline.prototype._containsPoint.call(this, p, true);
9314 L.CircleMarker.prototype._containsPoint = function (p) {
9315 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
9323 * @inherits FeatureGroup
9325 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
9326 * GeoJSON data and display it on the map. Extends `FeatureGroup`.
9332 * style: function (feature) {
9333 * return {color: feature.properties.color};
9335 * }).bindPopup(function (layer) {
9336 * return layer.feature.properties.description;
9341 L.GeoJSON = L.FeatureGroup.extend({
9344 * @aka GeoJSON options
9346 * @option pointToLayer: Function = *
9347 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
9348 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
9349 * The default is to spawn a default `Marker`:
9351 * function(geoJsonPoint, latlng) {
9352 * return L.marker(latlng);
9356 * @option style: Function = *
9357 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
9358 * called internally when data is added.
9359 * The default value is to not override any defaults:
9361 * function (geoJsonFeature) {
9366 * @option onEachFeature: Function = *
9367 * A `Function` that will be called once for each created `Feature`, after it has
9368 * been created and styled. Useful for attaching events and popups to features.
9369 * The default is to do nothing with the newly created layers:
9371 * function (feature, layer) {}
9374 * @option filter: Function = *
9375 * A `Function` that will be used to decide whether to include a feature or not.
9376 * The default is to include all features:
9378 * function (geoJsonFeature) {
9382 * Note: dynamically changing the `filter` option will have effect only on newly
9383 * added data. It will _not_ re-evaluate already included features.
9385 * @option coordsToLatLng: Function = *
9386 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
9387 * The default is the `coordsToLatLng` static method.
9390 initialize: function (geojson, options) {
9391 L.setOptions(this, options);
9396 this.addData(geojson);
9400 // @method addData( <GeoJSON> data ): Layer
9401 // Adds a GeoJSON object to the layer.
9402 addData: function (geojson) {
9403 var features = L.Util.isArray(geojson) ? geojson : geojson.features,
9407 for (i = 0, len = features.length; i < len; i++) {
9408 // only add this if geometry or geometries are set and not null
9409 feature = features[i];
9410 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
9411 this.addData(feature);
9417 var options = this.options;
9419 if (options.filter && !options.filter(geojson)) { return this; }
9421 var layer = L.GeoJSON.geometryToLayer(geojson, options);
9425 layer.feature = L.GeoJSON.asFeature(geojson);
9427 layer.defaultOptions = layer.options;
9428 this.resetStyle(layer);
9430 if (options.onEachFeature) {
9431 options.onEachFeature(geojson, layer);
9434 return this.addLayer(layer);
9437 // @method resetStyle( <Path> layer ): Layer
9438 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
9439 resetStyle: function (layer) {
9440 // reset any custom styles
9441 layer.options = L.Util.extend({}, layer.defaultOptions);
9442 this._setLayerStyle(layer, this.options.style);
9446 // @method setStyle( <Function> style ): Layer
9447 // Changes styles of GeoJSON vector layers with the given style function.
9448 setStyle: function (style) {
9449 return this.eachLayer(function (layer) {
9450 this._setLayerStyle(layer, style);
9454 _setLayerStyle: function (layer, style) {
9455 if (typeof style === 'function') {
9456 style = style(layer.feature);
9458 if (layer.setStyle) {
9459 layer.setStyle(style);
9465 // There are several static functions which can be called without instantiating L.GeoJSON:
9466 L.extend(L.GeoJSON, {
9467 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
9468 // Creates a `Layer` from a given GeoJSON feature. Can use a custom
9469 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
9470 // functions if provided as options.
9471 geometryToLayer: function (geojson, options) {
9473 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
9474 coords = geometry ? geometry.coordinates : null,
9476 pointToLayer = options && options.pointToLayer,
9477 coordsToLatLng = options && options.coordsToLatLng || this.coordsToLatLng,
9478 latlng, latlngs, i, len;
9480 if (!coords && !geometry) {
9484 switch (geometry.type) {
9486 latlng = coordsToLatLng(coords);
9487 return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
9490 for (i = 0, len = coords.length; i < len; i++) {
9491 latlng = coordsToLatLng(coords[i]);
9492 layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng));
9494 return new L.FeatureGroup(layers);
9497 case 'MultiLineString':
9498 latlngs = this.coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, coordsToLatLng);
9499 return new L.Polyline(latlngs, options);
9502 case 'MultiPolygon':
9503 latlngs = this.coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, coordsToLatLng);
9504 return new L.Polygon(latlngs, options);
9506 case 'GeometryCollection':
9507 for (i = 0, len = geometry.geometries.length; i < len; i++) {
9508 var layer = this.geometryToLayer({
9509 geometry: geometry.geometries[i],
9511 properties: geojson.properties
9518 return new L.FeatureGroup(layers);
9521 throw new Error('Invalid GeoJSON object.');
9525 // @function coordsToLatLng(coords: Array): LatLng
9526 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
9527 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
9528 coordsToLatLng: function (coords) {
9529 return new L.LatLng(coords[1], coords[0], coords[2]);
9532 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
9533 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
9534 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
9535 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
9536 coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) {
9539 for (var i = 0, len = coords.length, latlng; i < len; i++) {
9540 latlng = levelsDeep ?
9541 this.coordsToLatLngs(coords[i], levelsDeep - 1, coordsToLatLng) :
9542 (coordsToLatLng || this.coordsToLatLng)(coords[i]);
9544 latlngs.push(latlng);
9550 // @function latLngToCoords(latlng: LatLng): Array
9551 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
9552 latLngToCoords: function (latlng) {
9553 return latlng.alt !== undefined ?
9554 [latlng.lng, latlng.lat, latlng.alt] :
9555 [latlng.lng, latlng.lat];
9558 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
9559 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
9560 // `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.
9561 latLngsToCoords: function (latlngs, levelsDeep, closed) {
9564 for (var i = 0, len = latlngs.length; i < len; i++) {
9565 coords.push(levelsDeep ?
9566 L.GeoJSON.latLngsToCoords(latlngs[i], levelsDeep - 1, closed) :
9567 L.GeoJSON.latLngToCoords(latlngs[i]));
9570 if (!levelsDeep && closed) {
9571 coords.push(coords[0]);
9577 getFeature: function (layer, newGeometry) {
9578 return layer.feature ?
9579 L.extend({}, layer.feature, {geometry: newGeometry}) :
9580 L.GeoJSON.asFeature(newGeometry);
9583 // @function asFeature(geojson: Object): Object
9584 // Normalize GeoJSON geometries/features into GeoJSON features.
9585 asFeature: function (geojson) {
9586 if (geojson.type === 'Feature') {
9598 var PointToGeoJSON = {
9599 toGeoJSON: function () {
9600 return L.GeoJSON.getFeature(this, {
9602 coordinates: L.GeoJSON.latLngToCoords(this.getLatLng())
9607 L.Marker.include(PointToGeoJSON);
9609 // @namespace CircleMarker
9610 // @method toGeoJSON(): Object
9611 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
9612 L.Circle.include(PointToGeoJSON);
9613 L.CircleMarker.include(PointToGeoJSON);
9616 // @namespace Polyline
9617 // @method toGeoJSON(): Object
9618 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
9619 L.Polyline.prototype.toGeoJSON = function () {
9620 var multi = !L.Polyline._flat(this._latlngs);
9622 var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 1 : 0);
9624 return L.GeoJSON.getFeature(this, {
9625 type: (multi ? 'Multi' : '') + 'LineString',
9630 // @namespace Polygon
9631 // @method toGeoJSON(): Object
9632 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
9633 L.Polygon.prototype.toGeoJSON = function () {
9634 var holes = !L.Polyline._flat(this._latlngs),
9635 multi = holes && !L.Polyline._flat(this._latlngs[0]);
9637 var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true);
9643 return L.GeoJSON.getFeature(this, {
9644 type: (multi ? 'Multi' : '') + 'Polygon',
9650 // @namespace LayerGroup
9651 L.LayerGroup.include({
9652 toMultiPoint: function () {
9655 this.eachLayer(function (layer) {
9656 coords.push(layer.toGeoJSON().geometry.coordinates);
9659 return L.GeoJSON.getFeature(this, {
9665 // @method toGeoJSON(): Object
9666 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `GeometryCollection`).
9667 toGeoJSON: function () {
9669 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
9671 if (type === 'MultiPoint') {
9672 return this.toMultiPoint();
9675 var isGeometryCollection = type === 'GeometryCollection',
9678 this.eachLayer(function (layer) {
9679 if (layer.toGeoJSON) {
9680 var json = layer.toGeoJSON();
9681 jsons.push(isGeometryCollection ? json.geometry : L.GeoJSON.asFeature(json));
9685 if (isGeometryCollection) {
9686 return L.GeoJSON.getFeature(this, {
9688 type: 'GeometryCollection'
9693 type: 'FeatureCollection',
9699 // @namespace GeoJSON
9700 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
9701 // Creates a GeoJSON layer. Optionally accepts an object in
9702 // [GeoJSON format](http://geojson.org/geojson-spec.html) to display on the map
9703 // (you can alternatively add it later with `addData` method) and an `options` object.
9704 L.geoJSON = function (geojson, options) {
9705 return new L.GeoJSON(geojson, options);
9707 // Backward compatibility.
9708 L.geoJson = L.geoJSON;
9713 * @namespace DomEvent
9714 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
9717 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
9721 var eventsKey = '_leaflet_events';
9725 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
9726 // Adds a listener function (`fn`) to a particular DOM event type of the
9727 // element `el`. You can optionally specify the context of the listener
9728 // (object the `this` keyword will point to). You can also pass several
9729 // space-separated types (e.g. `'click dblclick'`).
9732 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
9733 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
9734 on: function (obj, types, fn, context) {
9736 if (typeof types === 'object') {
9737 for (var type in types) {
9738 this._on(obj, type, types[type], fn);
9741 types = L.Util.splitWords(types);
9743 for (var i = 0, len = types.length; i < len; i++) {
9744 this._on(obj, types[i], fn, context);
9751 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
9752 // Removes a previously added listener function. If no function is specified,
9753 // it will remove all the listeners of that particular DOM event from the element.
9754 // Note that if you passed a custom context to on, you must pass the same
9755 // context to `off` in order to remove the listener.
9758 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
9759 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
9760 off: function (obj, types, fn, context) {
9762 if (typeof types === 'object') {
9763 for (var type in types) {
9764 this._off(obj, type, types[type], fn);
9767 types = L.Util.splitWords(types);
9769 for (var i = 0, len = types.length; i < len; i++) {
9770 this._off(obj, types[i], fn, context);
9777 _on: function (obj, type, fn, context) {
9778 var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : '');
9780 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
9782 var handler = function (e) {
9783 return fn.call(context || obj, e || window.event);
9786 var originalHandler = handler;
9788 if (L.Browser.pointer && type.indexOf('touch') === 0) {
9789 this.addPointerListener(obj, type, handler, id);
9791 } else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
9792 this.addDoubleTapListener(obj, handler, id);
9794 } else if ('addEventListener' in obj) {
9796 if (type === 'mousewheel') {
9797 obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
9799 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
9800 handler = function (e) {
9801 e = e || window.event;
9802 if (L.DomEvent._isExternalTarget(obj, e)) {
9806 obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
9809 if (type === 'click' && L.Browser.android) {
9810 handler = function (e) {
9811 return L.DomEvent._filterClick(e, originalHandler);
9814 obj.addEventListener(type, handler, false);
9817 } else if ('attachEvent' in obj) {
9818 obj.attachEvent('on' + type, handler);
9821 obj[eventsKey] = obj[eventsKey] || {};
9822 obj[eventsKey][id] = handler;
9827 _off: function (obj, type, fn, context) {
9829 var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : ''),
9830 handler = obj[eventsKey] && obj[eventsKey][id];
9832 if (!handler) { return this; }
9834 if (L.Browser.pointer && type.indexOf('touch') === 0) {
9835 this.removePointerListener(obj, type, id);
9837 } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
9838 this.removeDoubleTapListener(obj, id);
9840 } else if ('removeEventListener' in obj) {
9842 if (type === 'mousewheel') {
9843 obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
9846 obj.removeEventListener(
9847 type === 'mouseenter' ? 'mouseover' :
9848 type === 'mouseleave' ? 'mouseout' : type, handler, false);
9851 } else if ('detachEvent' in obj) {
9852 obj.detachEvent('on' + type, handler);
9855 obj[eventsKey][id] = null;
9860 // @function stopPropagation(ev: DOMEvent): this
9861 // Stop the given event from propagation to parent elements. Used inside the listener functions:
9863 // L.DomEvent.on(div, 'click', function (ev) {
9864 // L.DomEvent.stopPropagation(ev);
9867 stopPropagation: function (e) {
9869 if (e.stopPropagation) {
9870 e.stopPropagation();
9871 } else if (e.originalEvent) { // In case of Leaflet event.
9872 e.originalEvent._stopped = true;
9874 e.cancelBubble = true;
9876 L.DomEvent._skipped(e);
9881 // @function disableScrollPropagation(el: HTMLElement): this
9882 // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
9883 disableScrollPropagation: function (el) {
9884 return L.DomEvent.on(el, 'mousewheel', L.DomEvent.stopPropagation);
9887 // @function disableClickPropagation(el: HTMLElement): this
9888 // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
9889 // `'mousedown'` and `'touchstart'` events (plus browser variants).
9890 disableClickPropagation: function (el) {
9891 var stop = L.DomEvent.stopPropagation;
9893 L.DomEvent.on(el, L.Draggable.START.join(' '), stop);
9895 return L.DomEvent.on(el, {
9896 click: L.DomEvent._fakeStop,
9901 // @function preventDefault(ev: DOMEvent): this
9902 // Prevents the default action of the DOM Event `ev` from happening (such as
9903 // following a link in the href of the a element, or doing a POST request
9904 // with page reload when a `<form>` is submitted).
9905 // Use it inside listener functions.
9906 preventDefault: function (e) {
9908 if (e.preventDefault) {
9911 e.returnValue = false;
9916 // @function stop(ev): this
9917 // Does `stopPropagation` and `preventDefault` at the same time.
9918 stop: function (e) {
9921 .stopPropagation(e);
9924 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
9925 // Gets normalized mouse position from a DOM event relative to the
9926 // `container` or to the whole page if not specified.
9927 getMousePosition: function (e, container) {
9929 return new L.Point(e.clientX, e.clientY);
9932 var rect = container.getBoundingClientRect();
9935 e.clientX - rect.left - container.clientLeft,
9936 e.clientY - rect.top - container.clientTop);
9939 // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
9940 // and Firefox scrolls device pixels, not CSS pixels
9941 _wheelPxFactor: (L.Browser.win && L.Browser.chrome) ? 2 :
9942 L.Browser.gecko ? window.devicePixelRatio :
9945 // @function getWheelDelta(ev: DOMEvent): Number
9946 // Gets normalized wheel delta from a mousewheel DOM event, in vertical
9947 // pixels scrolled (negative if scrolling down).
9948 // Events from pointing devices without precise scrolling are mapped to
9949 // a best guess of 60 pixels.
9950 getWheelDelta: function (e) {
9951 return (L.Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
9952 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / L.DomEvent._wheelPxFactor : // Pixels
9953 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
9954 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
9955 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
9956 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
9957 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
9958 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
9964 _fakeStop: function (e) {
9965 // fakes stopPropagation by setting a special event flag, checked/reset with L.DomEvent._skipped(e)
9966 L.DomEvent._skipEvents[e.type] = true;
9969 _skipped: function (e) {
9970 var skipped = this._skipEvents[e.type];
9971 // reset when checking, as it's only used in map container and propagates outside of the map
9972 this._skipEvents[e.type] = false;
9976 // check if element really left/entered the event target (for mouseenter/mouseleave)
9977 _isExternalTarget: function (el, e) {
9979 var related = e.relatedTarget;
9981 if (!related) { return true; }
9984 while (related && (related !== el)) {
9985 related = related.parentNode;
9990 return (related !== el);
9993 // this is a horrible workaround for a bug in Android where a single touch triggers two click events
9994 _filterClick: function (e, handler) {
9995 var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
9996 elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick);
9998 // are they closer together than 500ms yet more than 100ms?
9999 // Android typically triggers them ~300ms apart while multiple listeners
10000 // on the same event should be triggered far faster;
10001 // or check if click is simulated on the element, and if it is, reject any non-simulated events
10003 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
10004 L.DomEvent.stop(e);
10007 L.DomEvent._lastClick = timeStamp;
10013 // @function addListener(…): this
10014 // Alias to [`L.DomEvent.on`](#domevent-on)
10015 L.DomEvent.addListener = L.DomEvent.on;
10017 // @function removeListener(…): this
10018 // Alias to [`L.DomEvent.off`](#domevent-off)
10019 L.DomEvent.removeListener = L.DomEvent.off;
10026 * @inherits Evented
10028 * A class for making DOM elements draggable (including touch support).
10029 * Used internally for map and marker dragging. Only works for elements
10030 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
10034 * var draggable = new L.Draggable(elementToDrag);
10035 * draggable.enable();
10039 L.Draggable = L.Evented.extend({
10042 // @option clickTolerance: Number = 3
10043 // The max number of pixels a user can shift the mouse pointer during a click
10044 // for it to be considered a valid click (as opposed to a mouse drag).
10049 START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'],
10051 mousedown: 'mouseup',
10052 touchstart: 'touchend',
10053 pointerdown: 'touchend',
10054 MSPointerDown: 'touchend'
10057 mousedown: 'mousemove',
10058 touchstart: 'touchmove',
10059 pointerdown: 'touchmove',
10060 MSPointerDown: 'touchmove'
10064 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline: Boolean)
10065 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
10066 initialize: function (element, dragStartTarget, preventOutline) {
10067 this._element = element;
10068 this._dragStartTarget = dragStartTarget || element;
10069 this._preventOutline = preventOutline;
10072 // @method enable()
10073 // Enables the dragging ability
10074 enable: function () {
10075 if (this._enabled) { return; }
10077 L.DomEvent.on(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this);
10079 this._enabled = true;
10082 // @method disable()
10083 // Disables the dragging ability
10084 disable: function () {
10085 if (!this._enabled) { return; }
10087 L.DomEvent.off(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this);
10089 this._enabled = false;
10090 this._moved = false;
10093 _onDown: function (e) {
10094 // Ignore simulated events, since we handle both touch and
10095 // mouse explicitly; otherwise we risk getting duplicates of
10096 // touch events, see #4315.
10097 // Also ignore the event if disabled; this happens in IE11
10098 // under some circumstances, see #3666.
10099 if (e._simulated || !this._enabled) { return; }
10101 this._moved = false;
10103 if (L.DomUtil.hasClass(this._element, 'leaflet-zoom-anim')) { return; }
10105 if (L.Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches) || !this._enabled) { return; }
10106 L.Draggable._dragging = true; // Prevent dragging multiple objects at once.
10108 if (this._preventOutline) {
10109 L.DomUtil.preventOutline(this._element);
10112 L.DomUtil.disableImageDrag();
10113 L.DomUtil.disableTextSelection();
10115 if (this._moving) { return; }
10117 // @event down: Event
10118 // Fired when a drag is about to start.
10121 var first = e.touches ? e.touches[0] : e;
10123 this._startPoint = new L.Point(first.clientX, first.clientY);
10126 .on(document, L.Draggable.MOVE[e.type], this._onMove, this)
10127 .on(document, L.Draggable.END[e.type], this._onUp, this);
10130 _onMove: function (e) {
10131 // Ignore simulated events, since we handle both touch and
10132 // mouse explicitly; otherwise we risk getting duplicates of
10133 // touch events, see #4315.
10134 // Also ignore the event if disabled; this happens in IE11
10135 // under some circumstances, see #3666.
10136 if (e._simulated || !this._enabled) { return; }
10138 if (e.touches && e.touches.length > 1) {
10139 this._moved = true;
10143 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
10144 newPoint = new L.Point(first.clientX, first.clientY),
10145 offset = newPoint.subtract(this._startPoint);
10147 if (!offset.x && !offset.y) { return; }
10148 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
10150 L.DomEvent.preventDefault(e);
10152 if (!this._moved) {
10153 // @event dragstart: Event
10154 // Fired when a drag starts
10155 this.fire('dragstart');
10157 this._moved = true;
10158 this._startPos = L.DomUtil.getPosition(this._element).subtract(offset);
10160 L.DomUtil.addClass(document.body, 'leaflet-dragging');
10162 this._lastTarget = e.target || e.srcElement;
10163 // IE and Edge do not give the <use> element, so fetch it
10165 if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
10166 this._lastTarget = this._lastTarget.correspondingUseElement;
10168 L.DomUtil.addClass(this._lastTarget, 'leaflet-drag-target');
10171 this._newPos = this._startPos.add(offset);
10172 this._moving = true;
10174 L.Util.cancelAnimFrame(this._animRequest);
10175 this._lastEvent = e;
10176 this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true);
10179 _updatePosition: function () {
10180 var e = {originalEvent: this._lastEvent};
10182 // @event predrag: Event
10183 // Fired continuously during dragging *before* each corresponding
10184 // update of the element's position.
10185 this.fire('predrag', e);
10186 L.DomUtil.setPosition(this._element, this._newPos);
10188 // @event drag: Event
10189 // Fired continuously during dragging.
10190 this.fire('drag', e);
10193 _onUp: function (e) {
10194 // Ignore simulated events, since we handle both touch and
10195 // mouse explicitly; otherwise we risk getting duplicates of
10196 // touch events, see #4315.
10197 // Also ignore the event if disabled; this happens in IE11
10198 // under some circumstances, see #3666.
10199 if (e._simulated || !this._enabled) { return; }
10201 L.DomUtil.removeClass(document.body, 'leaflet-dragging');
10203 if (this._lastTarget) {
10204 L.DomUtil.removeClass(this._lastTarget, 'leaflet-drag-target');
10205 this._lastTarget = null;
10208 for (var i in L.Draggable.MOVE) {
10210 .off(document, L.Draggable.MOVE[i], this._onMove, this)
10211 .off(document, L.Draggable.END[i], this._onUp, this);
10214 L.DomUtil.enableImageDrag();
10215 L.DomUtil.enableTextSelection();
10217 if (this._moved && this._moving) {
10218 // ensure drag is not fired after dragend
10219 L.Util.cancelAnimFrame(this._animRequest);
10221 // @event dragend: DragEndEvent
10222 // Fired when the drag ends.
10223 this.fire('dragend', {
10224 distance: this._newPos.distanceTo(this._startPos)
10228 this._moving = false;
10229 L.Draggable._dragging = false;
10236 L.Handler is a base class for handler classes that are used internally to inject
10237 interaction features like dragging to classes like Map and Marker.
10242 // Abstract class for map interaction handlers
10244 L.Handler = L.Class.extend({
10245 initialize: function (map) {
10249 // @method enable(): this
10250 // Enables the handler
10251 enable: function () {
10252 if (this._enabled) { return this; }
10254 this._enabled = true;
10259 // @method disable(): this
10260 // Disables the handler
10261 disable: function () {
10262 if (!this._enabled) { return this; }
10264 this._enabled = false;
10265 this.removeHooks();
10269 // @method enabled(): Boolean
10270 // Returns `true` if the handler is enabled
10271 enabled: function () {
10272 return !!this._enabled;
10275 // @section Extension methods
10276 // Classes inheriting from `Handler` must implement the two following methods:
10277 // @method addHooks()
10278 // Called when the handler is enabled, should add event hooks.
10279 // @method removeHooks()
10280 // Called when the handler is disabled, should remove the event hooks added previously.
10286 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
10290 // @section Interaction Options
10291 L.Map.mergeOptions({
10292 // @option dragging: Boolean = true
10293 // Whether the map be draggable with mouse/touch or not.
10296 // @section Panning Inertia Options
10297 // @option inertia: Boolean = *
10298 // If enabled, panning of the map will have an inertia effect where
10299 // the map builds momentum while dragging and continues moving in
10300 // the same direction for some time. Feels especially nice on touch
10301 // devices. Enabled by default unless running on old Android devices.
10302 inertia: !L.Browser.android23,
10304 // @option inertiaDeceleration: Number = 3000
10305 // The rate with which the inertial movement slows down, in pixels/second².
10306 inertiaDeceleration: 3400, // px/s^2
10308 // @option inertiaMaxSpeed: Number = Infinity
10309 // Max speed of the inertial movement, in pixels/second.
10310 inertiaMaxSpeed: Infinity, // px/s
10312 // @option easeLinearity: Number = 0.2
10313 easeLinearity: 0.2,
10315 // TODO refactor, move to CRS
10316 // @option worldCopyJump: Boolean = false
10317 // With this option enabled, the map tracks when you pan to another "copy"
10318 // of the world and seamlessly jumps to the original one so that all overlays
10319 // like markers and vector layers are still visible.
10320 worldCopyJump: false,
10322 // @option maxBoundsViscosity: Number = 0.0
10323 // If `maxBounds` is set, this option will control how solid the bounds
10324 // are when dragging the map around. The default value of `0.0` allows the
10325 // user to drag outside the bounds at normal speed, higher values will
10326 // slow down map dragging outside bounds, and `1.0` makes the bounds fully
10327 // solid, preventing the user from dragging outside the bounds.
10328 maxBoundsViscosity: 0.0
10331 L.Map.Drag = L.Handler.extend({
10332 addHooks: function () {
10333 if (!this._draggable) {
10334 var map = this._map;
10336 this._draggable = new L.Draggable(map._mapPane, map._container);
10338 this._draggable.on({
10339 down: this._onDown,
10340 dragstart: this._onDragStart,
10341 drag: this._onDrag,
10342 dragend: this._onDragEnd
10345 this._draggable.on('predrag', this._onPreDragLimit, this);
10346 if (map.options.worldCopyJump) {
10347 this._draggable.on('predrag', this._onPreDragWrap, this);
10348 map.on('zoomend', this._onZoomEnd, this);
10350 map.whenReady(this._onZoomEnd, this);
10353 L.DomUtil.addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
10354 this._draggable.enable();
10355 this._positions = [];
10359 removeHooks: function () {
10360 L.DomUtil.removeClass(this._map._container, 'leaflet-grab');
10361 L.DomUtil.removeClass(this._map._container, 'leaflet-touch-drag');
10362 this._draggable.disable();
10365 moved: function () {
10366 return this._draggable && this._draggable._moved;
10369 moving: function () {
10370 return this._draggable && this._draggable._moving;
10373 _onDown: function () {
10377 _onDragStart: function () {
10378 var map = this._map;
10380 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
10381 var bounds = L.latLngBounds(this._map.options.maxBounds);
10383 this._offsetLimit = L.bounds(
10384 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
10385 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
10386 .add(this._map.getSize()));
10388 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
10390 this._offsetLimit = null;
10395 .fire('dragstart');
10397 if (map.options.inertia) {
10398 this._positions = [];
10403 _onDrag: function (e) {
10404 if (this._map.options.inertia) {
10405 var time = this._lastTime = +new Date(),
10406 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
10408 this._positions.push(pos);
10409 this._times.push(time);
10411 if (time - this._times[0] > 50) {
10412 this._positions.shift();
10413 this._times.shift();
10422 _onZoomEnd: function () {
10423 var pxCenter = this._map.getSize().divideBy(2),
10424 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
10426 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
10427 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
10430 _viscousLimit: function (value, threshold) {
10431 return value - (value - threshold) * this._viscosity;
10434 _onPreDragLimit: function () {
10435 if (!this._viscosity || !this._offsetLimit) { return; }
10437 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
10439 var limit = this._offsetLimit;
10440 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
10441 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
10442 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
10443 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
10445 this._draggable._newPos = this._draggable._startPos.add(offset);
10448 _onPreDragWrap: function () {
10449 // TODO refactor to be able to adjust map pane position after zoom
10450 var worldWidth = this._worldWidth,
10451 halfWidth = Math.round(worldWidth / 2),
10452 dx = this._initialWorldOffset,
10453 x = this._draggable._newPos.x,
10454 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
10455 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
10456 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
10458 this._draggable._absPos = this._draggable._newPos.clone();
10459 this._draggable._newPos.x = newX;
10462 _onDragEnd: function (e) {
10463 var map = this._map,
10464 options = map.options,
10466 noInertia = !options.inertia || this._times.length < 2;
10468 map.fire('dragend', e);
10471 map.fire('moveend');
10475 var direction = this._lastPos.subtract(this._positions[0]),
10476 duration = (this._lastTime - this._times[0]) / 1000,
10477 ease = options.easeLinearity,
10479 speedVector = direction.multiplyBy(ease / duration),
10480 speed = speedVector.distanceTo([0, 0]),
10482 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
10483 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
10485 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
10486 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
10488 if (!offset.x && !offset.y) {
10489 map.fire('moveend');
10492 offset = map._limitOffset(offset, map.options.maxBounds);
10494 L.Util.requestAnimFrame(function () {
10495 map.panBy(offset, {
10496 duration: decelerationDuration,
10497 easeLinearity: ease,
10507 // @section Handlers
10508 // @property dragging: Handler
10509 // Map dragging handler (by both mouse and touch).
10510 L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag);
10515 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
10519 // @section Interaction Options
10521 L.Map.mergeOptions({
10522 // @option doubleClickZoom: Boolean|String = true
10523 // Whether the map can be zoomed in by double clicking on it and
10524 // zoomed out by double clicking while holding shift. If passed
10525 // `'center'`, double-click zoom will zoom to the center of the
10526 // view regardless of where the mouse was.
10527 doubleClickZoom: true
10530 L.Map.DoubleClickZoom = L.Handler.extend({
10531 addHooks: function () {
10532 this._map.on('dblclick', this._onDoubleClick, this);
10535 removeHooks: function () {
10536 this._map.off('dblclick', this._onDoubleClick, this);
10539 _onDoubleClick: function (e) {
10540 var map = this._map,
10541 oldZoom = map.getZoom(),
10542 delta = map.options.zoomDelta,
10543 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
10545 if (map.options.doubleClickZoom === 'center') {
10548 map.setZoomAround(e.containerPoint, zoom);
10553 // @section Handlers
10555 // Map properties include interaction handlers that allow you to control
10556 // interaction behavior in runtime, enabling or disabling certain features such
10557 // as dragging or touch zoom (see `Handler` methods). For example:
10560 // map.doubleClickZoom.disable();
10563 // @property doubleClickZoom: Handler
10564 // Double click zoom handler.
10565 L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom);
10570 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
10574 // @section Interaction Options
10575 L.Map.mergeOptions({
10576 // @section Mousewheel options
10577 // @option scrollWheelZoom: Boolean|String = true
10578 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
10579 // it will zoom to the center of the view regardless of where the mouse was.
10580 scrollWheelZoom: true,
10582 // @option wheelDebounceTime: Number = 40
10583 // Limits the rate at which a wheel can fire (in milliseconds). By default
10584 // user can't zoom via wheel more often than once per 40 ms.
10585 wheelDebounceTime: 40,
10587 // @option wheelPxPerZoomLevel: Number = 60
10588 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
10589 // mean a change of one full zoom level. Smaller values will make wheel-zooming
10590 // faster (and vice versa).
10591 wheelPxPerZoomLevel: 60
10594 L.Map.ScrollWheelZoom = L.Handler.extend({
10595 addHooks: function () {
10596 L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this);
10601 removeHooks: function () {
10602 L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll, this);
10605 _onWheelScroll: function (e) {
10606 var delta = L.DomEvent.getWheelDelta(e);
10608 var debounce = this._map.options.wheelDebounceTime;
10610 this._delta += delta;
10611 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
10613 if (!this._startTime) {
10614 this._startTime = +new Date();
10617 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
10619 clearTimeout(this._timer);
10620 this._timer = setTimeout(L.bind(this._performZoom, this), left);
10622 L.DomEvent.stop(e);
10625 _performZoom: function () {
10626 var map = this._map,
10627 zoom = map.getZoom(),
10628 snap = this._map.options.zoomSnap || 0;
10630 map._stop(); // stop panning and fly animations if any
10632 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
10633 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
10634 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
10635 d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
10636 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
10639 this._startTime = null;
10641 if (!delta) { return; }
10643 if (map.options.scrollWheelZoom === 'center') {
10644 map.setZoom(zoom + delta);
10646 map.setZoomAround(this._lastMousePos, zoom + delta);
10651 // @section Handlers
10652 // @property scrollWheelZoom: Handler
10653 // Scroll wheel zoom handler.
10654 L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);
10659 * Extends the event handling code with double tap support for mobile browsers.
10662 L.extend(L.DomEvent, {
10664 _touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart',
10665 _touchend: L.Browser.msPointer ? 'MSPointerUp' : L.Browser.pointer ? 'pointerup' : 'touchend',
10667 // inspired by Zepto touch code by Thomas Fuchs
10668 addDoubleTapListener: function (obj, handler, id) {
10673 function onTouchStart(e) {
10676 if (L.Browser.pointer) {
10677 count = L.DomEvent._pointersCount;
10679 count = e.touches.length;
10682 if (count > 1) { return; }
10684 var now = Date.now(),
10685 delta = now - (last || now);
10687 touch = e.touches ? e.touches[0] : e;
10688 doubleTap = (delta > 0 && delta <= delay);
10692 function onTouchEnd() {
10693 if (doubleTap && !touch.cancelBubble) {
10694 if (L.Browser.pointer) {
10695 // work around .type being readonly with MSPointer* events
10701 newTouch[i] = prop && prop.bind ? prop.bind(touch) : prop;
10705 touch.type = 'dblclick';
10711 var pre = '_leaflet_',
10712 touchstart = this._touchstart,
10713 touchend = this._touchend;
10715 obj[pre + touchstart + id] = onTouchStart;
10716 obj[pre + touchend + id] = onTouchEnd;
10717 obj[pre + 'dblclick' + id] = handler;
10719 obj.addEventListener(touchstart, onTouchStart, false);
10720 obj.addEventListener(touchend, onTouchEnd, false);
10722 // On some platforms (notably, chrome on win10 + touchscreen + mouse),
10723 // the browser doesn't fire touchend/pointerup events but does fire
10724 // native dblclicks. See #4127.
10725 if (!L.Browser.edge) {
10726 obj.addEventListener('dblclick', handler, false);
10732 removeDoubleTapListener: function (obj, id) {
10733 var pre = '_leaflet_',
10734 touchstart = obj[pre + this._touchstart + id],
10735 touchend = obj[pre + this._touchend + id],
10736 dblclick = obj[pre + 'dblclick' + id];
10738 obj.removeEventListener(this._touchstart, touchstart, false);
10739 obj.removeEventListener(this._touchend, touchend, false);
10740 if (!L.Browser.edge) {
10741 obj.removeEventListener('dblclick', dblclick, false);
10751 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
10754 L.extend(L.DomEvent, {
10756 POINTER_DOWN: L.Browser.msPointer ? 'MSPointerDown' : 'pointerdown',
10757 POINTER_MOVE: L.Browser.msPointer ? 'MSPointerMove' : 'pointermove',
10758 POINTER_UP: L.Browser.msPointer ? 'MSPointerUp' : 'pointerup',
10759 POINTER_CANCEL: L.Browser.msPointer ? 'MSPointerCancel' : 'pointercancel',
10760 TAG_WHITE_LIST: ['INPUT', 'SELECT', 'OPTION'],
10765 // Provides a touch events wrapper for (ms)pointer events.
10766 // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
10768 addPointerListener: function (obj, type, handler, id) {
10770 if (type === 'touchstart') {
10771 this._addPointerStart(obj, handler, id);
10773 } else if (type === 'touchmove') {
10774 this._addPointerMove(obj, handler, id);
10776 } else if (type === 'touchend') {
10777 this._addPointerEnd(obj, handler, id);
10783 removePointerListener: function (obj, type, id) {
10784 var handler = obj['_leaflet_' + type + id];
10786 if (type === 'touchstart') {
10787 obj.removeEventListener(this.POINTER_DOWN, handler, false);
10789 } else if (type === 'touchmove') {
10790 obj.removeEventListener(this.POINTER_MOVE, handler, false);
10792 } else if (type === 'touchend') {
10793 obj.removeEventListener(this.POINTER_UP, handler, false);
10794 obj.removeEventListener(this.POINTER_CANCEL, handler, false);
10800 _addPointerStart: function (obj, handler, id) {
10801 var onDown = L.bind(function (e) {
10802 if (e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
10803 // In IE11, some touch events needs to fire for form controls, or
10804 // the controls will stop working. We keep a whitelist of tag names that
10805 // need these events. For other target tags, we prevent default on the event.
10806 if (this.TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
10807 L.DomEvent.preventDefault(e);
10813 this._handlePointer(e, handler);
10816 obj['_leaflet_touchstart' + id] = onDown;
10817 obj.addEventListener(this.POINTER_DOWN, onDown, false);
10819 // need to keep track of what pointers and how many are active to provide e.touches emulation
10820 if (!this._pointerDocListener) {
10821 var pointerUp = L.bind(this._globalPointerUp, this);
10823 // we listen documentElement as any drags that end by moving the touch off the screen get fired there
10824 document.documentElement.addEventListener(this.POINTER_DOWN, L.bind(this._globalPointerDown, this), true);
10825 document.documentElement.addEventListener(this.POINTER_MOVE, L.bind(this._globalPointerMove, this), true);
10826 document.documentElement.addEventListener(this.POINTER_UP, pointerUp, true);
10827 document.documentElement.addEventListener(this.POINTER_CANCEL, pointerUp, true);
10829 this._pointerDocListener = true;
10833 _globalPointerDown: function (e) {
10834 this._pointers[e.pointerId] = e;
10835 this._pointersCount++;
10838 _globalPointerMove: function (e) {
10839 if (this._pointers[e.pointerId]) {
10840 this._pointers[e.pointerId] = e;
10844 _globalPointerUp: function (e) {
10845 delete this._pointers[e.pointerId];
10846 this._pointersCount--;
10849 _handlePointer: function (e, handler) {
10851 for (var i in this._pointers) {
10852 e.touches.push(this._pointers[i]);
10854 e.changedTouches = [e];
10859 _addPointerMove: function (obj, handler, id) {
10860 var onMove = L.bind(function (e) {
10861 // don't fire touch moves when mouse isn't down
10862 if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
10864 this._handlePointer(e, handler);
10867 obj['_leaflet_touchmove' + id] = onMove;
10868 obj.addEventListener(this.POINTER_MOVE, onMove, false);
10871 _addPointerEnd: function (obj, handler, id) {
10872 var onUp = L.bind(function (e) {
10873 this._handlePointer(e, handler);
10876 obj['_leaflet_touchend' + id] = onUp;
10877 obj.addEventListener(this.POINTER_UP, onUp, false);
10878 obj.addEventListener(this.POINTER_CANCEL, onUp, false);
10885 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
10889 // @section Interaction Options
10890 L.Map.mergeOptions({
10891 // @section Touch interaction options
10892 // @option touchZoom: Boolean|String = *
10893 // Whether the map can be zoomed by touch-dragging with two fingers. If
10894 // passed `'center'`, it will zoom to the center of the view regardless of
10895 // where the touch events (fingers) were. Enabled for touch-capable web
10896 // browsers except for old Androids.
10897 touchZoom: L.Browser.touch && !L.Browser.android23,
10899 // @option bounceAtZoomLimits: Boolean = true
10900 // Set it to false if you don't want the map to zoom beyond min/max zoom
10901 // and then bounce back when pinch-zooming.
10902 bounceAtZoomLimits: true
10905 L.Map.TouchZoom = L.Handler.extend({
10906 addHooks: function () {
10907 L.DomUtil.addClass(this._map._container, 'leaflet-touch-zoom');
10908 L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
10911 removeHooks: function () {
10912 L.DomUtil.removeClass(this._map._container, 'leaflet-touch-zoom');
10913 L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
10916 _onTouchStart: function (e) {
10917 var map = this._map;
10918 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
10920 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
10921 p2 = map.mouseEventToContainerPoint(e.touches[1]);
10923 this._centerPoint = map.getSize()._divideBy(2);
10924 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
10925 if (map.options.touchZoom !== 'center') {
10926 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
10929 this._startDist = p1.distanceTo(p2);
10930 this._startZoom = map.getZoom();
10932 this._moved = false;
10933 this._zooming = true;
10938 .on(document, 'touchmove', this._onTouchMove, this)
10939 .on(document, 'touchend', this._onTouchEnd, this);
10941 L.DomEvent.preventDefault(e);
10944 _onTouchMove: function (e) {
10945 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
10947 var map = this._map,
10948 p1 = map.mouseEventToContainerPoint(e.touches[0]),
10949 p2 = map.mouseEventToContainerPoint(e.touches[1]),
10950 scale = p1.distanceTo(p2) / this._startDist;
10953 this._zoom = map.getScaleZoom(scale, this._startZoom);
10955 if (!map.options.bounceAtZoomLimits && (
10956 (this._zoom < map.getMinZoom() && scale < 1) ||
10957 (this._zoom > map.getMaxZoom() && scale > 1))) {
10958 this._zoom = map._limitZoom(this._zoom);
10961 if (map.options.touchZoom === 'center') {
10962 this._center = this._startLatLng;
10963 if (scale === 1) { return; }
10965 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
10966 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
10967 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
10968 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
10971 if (!this._moved) {
10972 map._moveStart(true);
10973 this._moved = true;
10976 L.Util.cancelAnimFrame(this._animRequest);
10978 var moveFn = L.bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
10979 this._animRequest = L.Util.requestAnimFrame(moveFn, this, true);
10981 L.DomEvent.preventDefault(e);
10984 _onTouchEnd: function () {
10985 if (!this._moved || !this._zooming) {
10986 this._zooming = false;
10990 this._zooming = false;
10991 L.Util.cancelAnimFrame(this._animRequest);
10994 .off(document, 'touchmove', this._onTouchMove)
10995 .off(document, 'touchend', this._onTouchEnd);
10997 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
10998 if (this._map.options.zoomAnimation) {
10999 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
11001 this._map._resetView(this._center, this._map._limitZoom(this._zoom));
11006 // @section Handlers
11007 // @property touchZoom: Handler
11008 // Touch zoom handler.
11009 L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom);
11014 * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
11018 // @section Interaction Options
11019 L.Map.mergeOptions({
11020 // @section Touch interaction options
11021 // @option tap: Boolean = true
11022 // Enables mobile hacks for supporting instant taps (fixing 200ms click
11023 // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
11026 // @option tapTolerance: Number = 15
11027 // The max number of pixels a user can shift his finger during touch
11028 // for it to be considered a valid tap.
11032 L.Map.Tap = L.Handler.extend({
11033 addHooks: function () {
11034 L.DomEvent.on(this._map._container, 'touchstart', this._onDown, this);
11037 removeHooks: function () {
11038 L.DomEvent.off(this._map._container, 'touchstart', this._onDown, this);
11041 _onDown: function (e) {
11042 if (!e.touches) { return; }
11044 L.DomEvent.preventDefault(e);
11046 this._fireClick = true;
11048 // don't simulate click or track longpress if more than 1 touch
11049 if (e.touches.length > 1) {
11050 this._fireClick = false;
11051 clearTimeout(this._holdTimeout);
11055 var first = e.touches[0],
11058 this._startPos = this._newPos = new L.Point(first.clientX, first.clientY);
11060 // if touching a link, highlight it
11061 if (el.tagName && el.tagName.toLowerCase() === 'a') {
11062 L.DomUtil.addClass(el, 'leaflet-active');
11065 // simulate long hold but setting a timeout
11066 this._holdTimeout = setTimeout(L.bind(function () {
11067 if (this._isTapValid()) {
11068 this._fireClick = false;
11070 this._simulateEvent('contextmenu', first);
11074 this._simulateEvent('mousedown', first);
11076 L.DomEvent.on(document, {
11077 touchmove: this._onMove,
11078 touchend: this._onUp
11082 _onUp: function (e) {
11083 clearTimeout(this._holdTimeout);
11085 L.DomEvent.off(document, {
11086 touchmove: this._onMove,
11087 touchend: this._onUp
11090 if (this._fireClick && e && e.changedTouches) {
11092 var first = e.changedTouches[0],
11095 if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
11096 L.DomUtil.removeClass(el, 'leaflet-active');
11099 this._simulateEvent('mouseup', first);
11101 // simulate click if the touch didn't move too much
11102 if (this._isTapValid()) {
11103 this._simulateEvent('click', first);
11108 _isTapValid: function () {
11109 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
11112 _onMove: function (e) {
11113 var first = e.touches[0];
11114 this._newPos = new L.Point(first.clientX, first.clientY);
11115 this._simulateEvent('mousemove', first);
11118 _simulateEvent: function (type, e) {
11119 var simulatedEvent = document.createEvent('MouseEvents');
11121 simulatedEvent._simulated = true;
11122 e.target._simulatedClick = true;
11124 simulatedEvent.initMouseEvent(
11125 type, true, true, window, 1,
11126 e.screenX, e.screenY,
11127 e.clientX, e.clientY,
11128 false, false, false, false, 0, null);
11130 e.target.dispatchEvent(simulatedEvent);
11134 // @section Handlers
11135 // @property tap: Handler
11136 // Mobile touch hacks (quick tap and touch hold) handler.
11137 if (L.Browser.touch && !L.Browser.pointer) {
11138 L.Map.addInitHook('addHandler', 'tap', L.Map.Tap);
11144 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
11145 * (zoom to a selected bounding box), enabled by default.
11149 // @section Interaction Options
11150 L.Map.mergeOptions({
11151 // @option boxZoom: Boolean = true
11152 // Whether the map can be zoomed to a rectangular area specified by
11153 // dragging the mouse while pressing the shift key.
11157 L.Map.BoxZoom = L.Handler.extend({
11158 initialize: function (map) {
11160 this._container = map._container;
11161 this._pane = map._panes.overlayPane;
11164 addHooks: function () {
11165 L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
11168 removeHooks: function () {
11169 L.DomEvent.off(this._container, 'mousedown', this._onMouseDown, this);
11172 moved: function () {
11173 return this._moved;
11176 _resetState: function () {
11177 this._moved = false;
11180 _onMouseDown: function (e) {
11181 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
11183 this._resetState();
11185 L.DomUtil.disableTextSelection();
11186 L.DomUtil.disableImageDrag();
11188 this._startPoint = this._map.mouseEventToContainerPoint(e);
11190 L.DomEvent.on(document, {
11191 contextmenu: L.DomEvent.stop,
11192 mousemove: this._onMouseMove,
11193 mouseup: this._onMouseUp,
11194 keydown: this._onKeyDown
11198 _onMouseMove: function (e) {
11199 if (!this._moved) {
11200 this._moved = true;
11202 this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._container);
11203 L.DomUtil.addClass(this._container, 'leaflet-crosshair');
11205 this._map.fire('boxzoomstart');
11208 this._point = this._map.mouseEventToContainerPoint(e);
11210 var bounds = new L.Bounds(this._point, this._startPoint),
11211 size = bounds.getSize();
11213 L.DomUtil.setPosition(this._box, bounds.min);
11215 this._box.style.width = size.x + 'px';
11216 this._box.style.height = size.y + 'px';
11219 _finish: function () {
11221 L.DomUtil.remove(this._box);
11222 L.DomUtil.removeClass(this._container, 'leaflet-crosshair');
11225 L.DomUtil.enableTextSelection();
11226 L.DomUtil.enableImageDrag();
11228 L.DomEvent.off(document, {
11229 contextmenu: L.DomEvent.stop,
11230 mousemove: this._onMouseMove,
11231 mouseup: this._onMouseUp,
11232 keydown: this._onKeyDown
11236 _onMouseUp: function (e) {
11237 if ((e.which !== 1) && (e.button !== 1)) { return; }
11241 if (!this._moved) { return; }
11242 // Postpone to next JS tick so internal click event handling
11243 // still see it as "moved".
11244 setTimeout(L.bind(this._resetState, this), 0);
11246 var bounds = new L.LatLngBounds(
11247 this._map.containerPointToLatLng(this._startPoint),
11248 this._map.containerPointToLatLng(this._point));
11252 .fire('boxzoomend', {boxZoomBounds: bounds});
11255 _onKeyDown: function (e) {
11256 if (e.keyCode === 27) {
11262 // @section Handlers
11263 // @property boxZoom: Handler
11264 // Box (shift-drag with mouse) zoom handler.
11265 L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom);
11270 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
11274 // @section Keyboard Navigation Options
11275 L.Map.mergeOptions({
11276 // @option keyboard: Boolean = true
11277 // Makes the map focusable and allows users to navigate the map with keyboard
11278 // arrows and `+`/`-` keys.
11281 // @option keyboardPanDelta: Number = 80
11282 // Amount of pixels to pan when pressing an arrow key.
11283 keyboardPanDelta: 80
11286 L.Map.Keyboard = L.Handler.extend({
11293 zoomIn: [187, 107, 61, 171],
11294 zoomOut: [189, 109, 54, 173]
11297 initialize: function (map) {
11300 this._setPanDelta(map.options.keyboardPanDelta);
11301 this._setZoomDelta(map.options.zoomDelta);
11304 addHooks: function () {
11305 var container = this._map._container;
11307 // make the container focusable by tabbing
11308 if (container.tabIndex <= 0) {
11309 container.tabIndex = '0';
11312 L.DomEvent.on(container, {
11313 focus: this._onFocus,
11314 blur: this._onBlur,
11315 mousedown: this._onMouseDown
11319 focus: this._addHooks,
11320 blur: this._removeHooks
11324 removeHooks: function () {
11325 this._removeHooks();
11327 L.DomEvent.off(this._map._container, {
11328 focus: this._onFocus,
11329 blur: this._onBlur,
11330 mousedown: this._onMouseDown
11334 focus: this._addHooks,
11335 blur: this._removeHooks
11339 _onMouseDown: function () {
11340 if (this._focused) { return; }
11342 var body = document.body,
11343 docEl = document.documentElement,
11344 top = body.scrollTop || docEl.scrollTop,
11345 left = body.scrollLeft || docEl.scrollLeft;
11347 this._map._container.focus();
11349 window.scrollTo(left, top);
11352 _onFocus: function () {
11353 this._focused = true;
11354 this._map.fire('focus');
11357 _onBlur: function () {
11358 this._focused = false;
11359 this._map.fire('blur');
11362 _setPanDelta: function (panDelta) {
11363 var keys = this._panKeys = {},
11364 codes = this.keyCodes,
11367 for (i = 0, len = codes.left.length; i < len; i++) {
11368 keys[codes.left[i]] = [-1 * panDelta, 0];
11370 for (i = 0, len = codes.right.length; i < len; i++) {
11371 keys[codes.right[i]] = [panDelta, 0];
11373 for (i = 0, len = codes.down.length; i < len; i++) {
11374 keys[codes.down[i]] = [0, panDelta];
11376 for (i = 0, len = codes.up.length; i < len; i++) {
11377 keys[codes.up[i]] = [0, -1 * panDelta];
11381 _setZoomDelta: function (zoomDelta) {
11382 var keys = this._zoomKeys = {},
11383 codes = this.keyCodes,
11386 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
11387 keys[codes.zoomIn[i]] = zoomDelta;
11389 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
11390 keys[codes.zoomOut[i]] = -zoomDelta;
11394 _addHooks: function () {
11395 L.DomEvent.on(document, 'keydown', this._onKeyDown, this);
11398 _removeHooks: function () {
11399 L.DomEvent.off(document, 'keydown', this._onKeyDown, this);
11402 _onKeyDown: function (e) {
11403 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
11405 var key = e.keyCode,
11409 if (key in this._panKeys) {
11411 if (map._panAnim && map._panAnim._inProgress) { return; }
11413 offset = this._panKeys[key];
11415 offset = L.point(offset).multiplyBy(3);
11420 if (map.options.maxBounds) {
11421 map.panInsideBounds(map.options.maxBounds);
11424 } else if (key in this._zoomKeys) {
11425 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
11427 } else if (key === 27) {
11434 L.DomEvent.stop(e);
11438 // @section Handlers
11439 // @section Handlers
11440 // @property keyboard: Handler
11441 // Keyboard navigation handler.
11442 L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard);
11447 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
11451 /* @namespace Marker
11452 * @section Interaction handlers
11454 * 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:
11457 * marker.dragging.disable();
11460 * @property dragging: Handler
11461 * Marker dragging handler (by both mouse and touch).
11464 L.Handler.MarkerDrag = L.Handler.extend({
11465 initialize: function (marker) {
11466 this._marker = marker;
11469 addHooks: function () {
11470 var icon = this._marker._icon;
11472 if (!this._draggable) {
11473 this._draggable = new L.Draggable(icon, icon, true);
11476 this._draggable.on({
11477 dragstart: this._onDragStart,
11478 drag: this._onDrag,
11479 dragend: this._onDragEnd
11482 L.DomUtil.addClass(icon, 'leaflet-marker-draggable');
11485 removeHooks: function () {
11486 this._draggable.off({
11487 dragstart: this._onDragStart,
11488 drag: this._onDrag,
11489 dragend: this._onDragEnd
11490 }, this).disable();
11492 if (this._marker._icon) {
11493 L.DomUtil.removeClass(this._marker._icon, 'leaflet-marker-draggable');
11497 moved: function () {
11498 return this._draggable && this._draggable._moved;
11501 _onDragStart: function () {
11502 // @section Dragging events
11503 // @event dragstart: Event
11504 // Fired when the user starts dragging the marker.
11506 // @event movestart: Event
11507 // Fired when the marker starts moving (because of dragging).
11509 this._oldLatLng = this._marker.getLatLng();
11513 .fire('dragstart');
11516 _onDrag: function (e) {
11517 var marker = this._marker,
11518 shadow = marker._shadow,
11519 iconPos = L.DomUtil.getPosition(marker._icon),
11520 latlng = marker._map.layerPointToLatLng(iconPos);
11522 // update shadow position
11524 L.DomUtil.setPosition(shadow, iconPos);
11527 marker._latlng = latlng;
11529 e.oldLatLng = this._oldLatLng;
11531 // @event drag: Event
11532 // Fired repeatedly while the user drags the marker.
11538 _onDragEnd: function (e) {
11539 // @event dragend: DragEndEvent
11540 // Fired when the user stops dragging the marker.
11542 // @event moveend: Event
11543 // Fired when the marker stops moving (because of dragging).
11544 delete this._oldLatLng;
11547 .fire('dragend', e);
11557 * L.Control is a base class for implementing map controls. Handles positioning.
11558 * All other controls extend from this class.
11561 L.Control = L.Class.extend({
11563 // @aka Control options
11565 // @option position: String = 'topright'
11566 // The position of the control (one of the map corners). Possible values are `'topleft'`,
11567 // `'topright'`, `'bottomleft'` or `'bottomright'`
11568 position: 'topright'
11571 initialize: function (options) {
11572 L.setOptions(this, options);
11576 * Classes extending L.Control will inherit the following methods:
11578 * @method getPosition: string
11579 * Returns the position of the control.
11581 getPosition: function () {
11582 return this.options.position;
11585 // @method setPosition(position: string): this
11586 // Sets the position of the control.
11587 setPosition: function (position) {
11588 var map = this._map;
11591 map.removeControl(this);
11594 this.options.position = position;
11597 map.addControl(this);
11603 // @method getContainer: HTMLElement
11604 // Returns the HTMLElement that contains the control.
11605 getContainer: function () {
11606 return this._container;
11609 // @method addTo(map: Map): this
11610 // Adds the control to the given map.
11611 addTo: function (map) {
11615 var container = this._container = this.onAdd(map),
11616 pos = this.getPosition(),
11617 corner = map._controlCorners[pos];
11619 L.DomUtil.addClass(container, 'leaflet-control');
11621 if (pos.indexOf('bottom') !== -1) {
11622 corner.insertBefore(container, corner.firstChild);
11624 corner.appendChild(container);
11630 // @method remove: this
11631 // Removes the control from the map it is currently active on.
11632 remove: function () {
11637 L.DomUtil.remove(this._container);
11639 if (this.onRemove) {
11640 this.onRemove(this._map);
11648 _refocusOnMap: function (e) {
11649 // if map exists and event is not a keyboard event
11650 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
11651 this._map.getContainer().focus();
11656 L.control = function (options) {
11657 return new L.Control(options);
11660 /* @section Extension methods
11663 * Every control should extend from `L.Control` and (re-)implement the following methods.
11665 * @method onAdd(map: Map): HTMLElement
11666 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
11668 * @method onRemove(map: Map)
11669 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
11673 * @section Methods for Layers and Controls
11676 // @method addControl(control: Control): this
11677 // Adds the given control to the map
11678 addControl: function (control) {
11679 control.addTo(this);
11683 // @method removeControl(control: Control): this
11684 // Removes the given control from the map
11685 removeControl: function (control) {
11690 _initControlPos: function () {
11691 var corners = this._controlCorners = {},
11693 container = this._controlContainer =
11694 L.DomUtil.create('div', l + 'control-container', this._container);
11696 function createCorner(vSide, hSide) {
11697 var className = l + vSide + ' ' + l + hSide;
11699 corners[vSide + hSide] = L.DomUtil.create('div', className, container);
11702 createCorner('top', 'left');
11703 createCorner('top', 'right');
11704 createCorner('bottom', 'left');
11705 createCorner('bottom', 'right');
11708 _clearControlPos: function () {
11709 L.DomUtil.remove(this._controlContainer);
11716 * @class Control.Zoom
11717 * @aka L.Control.Zoom
11718 * @inherits Control
11720 * 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`.
11723 L.Control.Zoom = L.Control.extend({
11725 // @aka Control.Zoom options
11727 position: 'topleft',
11729 // @option zoomInText: String = '+'
11730 // The text set on the 'zoom in' button.
11733 // @option zoomInTitle: String = 'Zoom in'
11734 // The title set on the 'zoom in' button.
11735 zoomInTitle: 'Zoom in',
11737 // @option zoomOutText: String = '-'
11738 // The text set on the 'zoom out' button.
11741 // @option zoomOutTitle: String = 'Zoom out'
11742 // The title set on the 'zoom out' button.
11743 zoomOutTitle: 'Zoom out'
11746 onAdd: function (map) {
11747 var zoomName = 'leaflet-control-zoom',
11748 container = L.DomUtil.create('div', zoomName + ' leaflet-bar'),
11749 options = this.options;
11751 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle,
11752 zoomName + '-in', container, this._zoomIn);
11753 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
11754 zoomName + '-out', container, this._zoomOut);
11756 this._updateDisabled();
11757 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
11762 onRemove: function (map) {
11763 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
11766 disable: function () {
11767 this._disabled = true;
11768 this._updateDisabled();
11772 enable: function () {
11773 this._disabled = false;
11774 this._updateDisabled();
11778 _zoomIn: function (e) {
11779 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
11780 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
11784 _zoomOut: function (e) {
11785 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
11786 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
11790 _createButton: function (html, title, className, container, fn) {
11791 var link = L.DomUtil.create('a', className, container);
11792 link.innerHTML = html;
11794 link.title = title;
11797 .on(link, 'mousedown dblclick', L.DomEvent.stopPropagation)
11798 .on(link, 'click', L.DomEvent.stop)
11799 .on(link, 'click', fn, this)
11800 .on(link, 'click', this._refocusOnMap, this);
11805 _updateDisabled: function () {
11806 var map = this._map,
11807 className = 'leaflet-disabled';
11809 L.DomUtil.removeClass(this._zoomInButton, className);
11810 L.DomUtil.removeClass(this._zoomOutButton, className);
11812 if (this._disabled || map._zoom === map.getMinZoom()) {
11813 L.DomUtil.addClass(this._zoomOutButton, className);
11815 if (this._disabled || map._zoom === map.getMaxZoom()) {
11816 L.DomUtil.addClass(this._zoomInButton, className);
11822 // @section Control options
11823 // @option zoomControl: Boolean = true
11824 // Whether a [zoom control](#control-zoom) is added to the map by default.
11825 L.Map.mergeOptions({
11829 L.Map.addInitHook(function () {
11830 if (this.options.zoomControl) {
11831 this.zoomControl = new L.Control.Zoom();
11832 this.addControl(this.zoomControl);
11836 // @namespace Control.Zoom
11837 // @factory L.control.zoom(options: Control.Zoom options)
11838 // Creates a zoom control
11839 L.control.zoom = function (options) {
11840 return new L.Control.Zoom(options);
11846 * @class Control.Attribution
11847 * @aka L.Control.Attribution
11848 * @inherits Control
11850 * 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.
11853 L.Control.Attribution = L.Control.extend({
11855 // @aka Control.Attribution options
11857 position: 'bottomright',
11859 // @option prefix: String = 'Leaflet'
11860 // The HTML text shown before the attributions. Pass `false` to disable.
11861 prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
11864 initialize: function (options) {
11865 L.setOptions(this, options);
11867 this._attributions = {};
11870 onAdd: function (map) {
11871 map.attributionControl = this;
11872 this._container = L.DomUtil.create('div', 'leaflet-control-attribution');
11874 L.DomEvent.disableClickPropagation(this._container);
11877 // TODO ugly, refactor
11878 for (var i in map._layers) {
11879 if (map._layers[i].getAttribution) {
11880 this.addAttribution(map._layers[i].getAttribution());
11886 return this._container;
11889 // @method setPrefix(prefix: String): this
11890 // Sets the text before the attributions.
11891 setPrefix: function (prefix) {
11892 this.options.prefix = prefix;
11897 // @method addAttribution(text: String): this
11898 // Adds an attribution text (e.g. `'Vector data © Mapbox'`).
11899 addAttribution: function (text) {
11900 if (!text) { return this; }
11902 if (!this._attributions[text]) {
11903 this._attributions[text] = 0;
11905 this._attributions[text]++;
11912 // @method removeAttribution(text: String): this
11913 // Removes an attribution text.
11914 removeAttribution: function (text) {
11915 if (!text) { return this; }
11917 if (this._attributions[text]) {
11918 this._attributions[text]--;
11925 _update: function () {
11926 if (!this._map) { return; }
11930 for (var i in this._attributions) {
11931 if (this._attributions[i]) {
11936 var prefixAndAttribs = [];
11938 if (this.options.prefix) {
11939 prefixAndAttribs.push(this.options.prefix);
11941 if (attribs.length) {
11942 prefixAndAttribs.push(attribs.join(', '));
11945 this._container.innerHTML = prefixAndAttribs.join(' | ');
11950 // @section Control options
11951 // @option attributionControl: Boolean = true
11952 // Whether a [attribution control](#control-attribution) is added to the map by default.
11953 L.Map.mergeOptions({
11954 attributionControl: true
11957 L.Map.addInitHook(function () {
11958 if (this.options.attributionControl) {
11959 new L.Control.Attribution().addTo(this);
11963 // @namespace Control.Attribution
11964 // @factory L.control.attribution(options: Control.Attribution options)
11965 // Creates an attribution control.
11966 L.control.attribution = function (options) {
11967 return new L.Control.Attribution(options);
11973 * @class Control.Scale
11974 * @aka L.Control.Scale
11975 * @inherits Control
11977 * 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`.
11982 * L.control.scale().addTo(map);
11986 L.Control.Scale = L.Control.extend({
11988 // @aka Control.Scale options
11990 position: 'bottomleft',
11992 // @option maxWidth: Number = 100
11993 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
11996 // @option metric: Boolean = True
11997 // Whether to show the metric scale line (m/km).
12000 // @option imperial: Boolean = True
12001 // Whether to show the imperial scale line (mi/ft).
12004 // @option updateWhenIdle: Boolean = false
12005 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
12008 onAdd: function (map) {
12009 var className = 'leaflet-control-scale',
12010 container = L.DomUtil.create('div', className),
12011 options = this.options;
12013 this._addScales(options, className + '-line', container);
12015 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
12016 map.whenReady(this._update, this);
12021 onRemove: function (map) {
12022 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
12025 _addScales: function (options, className, container) {
12026 if (options.metric) {
12027 this._mScale = L.DomUtil.create('div', className, container);
12029 if (options.imperial) {
12030 this._iScale = L.DomUtil.create('div', className, container);
12034 _update: function () {
12035 var map = this._map,
12036 y = map.getSize().y / 2;
12038 var maxMeters = map.distance(
12039 map.containerPointToLatLng([0, y]),
12040 map.containerPointToLatLng([this.options.maxWidth, y]));
12042 this._updateScales(maxMeters);
12045 _updateScales: function (maxMeters) {
12046 if (this.options.metric && maxMeters) {
12047 this._updateMetric(maxMeters);
12049 if (this.options.imperial && maxMeters) {
12050 this._updateImperial(maxMeters);
12054 _updateMetric: function (maxMeters) {
12055 var meters = this._getRoundNum(maxMeters),
12056 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
12058 this._updateScale(this._mScale, label, meters / maxMeters);
12061 _updateImperial: function (maxMeters) {
12062 var maxFeet = maxMeters * 3.2808399,
12063 maxMiles, miles, feet;
12065 if (maxFeet > 5280) {
12066 maxMiles = maxFeet / 5280;
12067 miles = this._getRoundNum(maxMiles);
12068 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
12071 feet = this._getRoundNum(maxFeet);
12072 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
12076 _updateScale: function (scale, text, ratio) {
12077 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
12078 scale.innerHTML = text;
12081 _getRoundNum: function (num) {
12082 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
12095 // @factory L.control.scale(options?: Control.Scale options)
12096 // Creates an scale control with the given options.
12097 L.control.scale = function (options) {
12098 return new L.Control.Scale(options);
12104 * @class Control.Layers
12105 * @aka L.Control.Layers
12106 * @inherits Control
12108 * 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`.
12113 * var baseLayers = {
12114 * "Mapbox": mapbox,
12115 * "OpenStreetMap": osm
12119 * "Marker": marker,
12120 * "Roads": roadsLayer
12123 * L.control.layers(baseLayers, overlays).addTo(map);
12126 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
12130 * "<someName1>": layer1,
12131 * "<someName2>": layer2
12135 * The layer names can contain HTML, which allows you to add additional styling to the items:
12138 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
12143 L.Control.Layers = L.Control.extend({
12145 // @aka Control.Layers options
12147 // @option collapsed: Boolean = true
12148 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
12150 position: 'topright',
12152 // @option autoZIndex: Boolean = true
12153 // 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.
12156 // @option hideSingleBase: Boolean = false
12157 // If `true`, the base layers in the control will be hidden when there is only one.
12158 hideSingleBase: false
12161 initialize: function (baseLayers, overlays, options) {
12162 L.setOptions(this, options);
12165 this._lastZIndex = 0;
12166 this._handlingClick = false;
12168 for (var i in baseLayers) {
12169 this._addLayer(baseLayers[i], i);
12172 for (i in overlays) {
12173 this._addLayer(overlays[i], i, true);
12177 onAdd: function (map) {
12178 this._initLayout();
12182 map.on('zoomend', this._checkDisabledLayers, this);
12184 return this._container;
12187 onRemove: function () {
12188 this._map.off('zoomend', this._checkDisabledLayers, this);
12190 for (var i = 0; i < this._layers.length; i++) {
12191 this._layers[i].layer.off('add remove', this._onLayerChange, this);
12195 // @method addBaseLayer(layer: Layer, name: String): this
12196 // Adds a base layer (radio button entry) with the given name to the control.
12197 addBaseLayer: function (layer, name) {
12198 this._addLayer(layer, name);
12199 return (this._map) ? this._update() : this;
12202 // @method addOverlay(layer: Layer, name: String): this
12203 // Adds an overlay (checkbox entry) with the given name to the control.
12204 addOverlay: function (layer, name) {
12205 this._addLayer(layer, name, true);
12206 return (this._map) ? this._update() : this;
12209 // @method removeLayer(layer: Layer): this
12210 // Remove the given layer from the control.
12211 removeLayer: function (layer) {
12212 layer.off('add remove', this._onLayerChange, this);
12214 var obj = this._getLayer(L.stamp(layer));
12216 this._layers.splice(this._layers.indexOf(obj), 1);
12218 return (this._map) ? this._update() : this;
12221 // @method expand(): this
12222 // Expand the control container if collapsed.
12223 expand: function () {
12224 L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');
12225 this._form.style.height = null;
12226 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
12227 if (acceptableHeight < this._form.clientHeight) {
12228 L.DomUtil.addClass(this._form, 'leaflet-control-layers-scrollbar');
12229 this._form.style.height = acceptableHeight + 'px';
12231 L.DomUtil.removeClass(this._form, 'leaflet-control-layers-scrollbar');
12233 this._checkDisabledLayers();
12237 // @method collapse(): this
12238 // Collapse the control container if expanded.
12239 collapse: function () {
12240 L.DomUtil.removeClass(this._container, 'leaflet-control-layers-expanded');
12244 _initLayout: function () {
12245 var className = 'leaflet-control-layers',
12246 container = this._container = L.DomUtil.create('div', className);
12248 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
12249 container.setAttribute('aria-haspopup', true);
12251 L.DomEvent.disableClickPropagation(container);
12252 if (!L.Browser.touch) {
12253 L.DomEvent.disableScrollPropagation(container);
12256 var form = this._form = L.DomUtil.create('form', className + '-list');
12258 if (this.options.collapsed) {
12259 if (!L.Browser.android) {
12260 L.DomEvent.on(container, {
12261 mouseenter: this.expand,
12262 mouseleave: this.collapse
12266 var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
12268 link.title = 'Layers';
12270 if (L.Browser.touch) {
12272 .on(link, 'click', L.DomEvent.stop)
12273 .on(link, 'click', this.expand, this);
12275 L.DomEvent.on(link, 'focus', this.expand, this);
12278 // work around for Firefox Android issue https://github.com/Leaflet/Leaflet/issues/2033
12279 L.DomEvent.on(form, 'click', function () {
12280 setTimeout(L.bind(this._onInputClick, this), 0);
12283 this._map.on('click', this.collapse, this);
12284 // TODO keyboard accessibility
12289 this._baseLayersList = L.DomUtil.create('div', className + '-base', form);
12290 this._separator = L.DomUtil.create('div', className + '-separator', form);
12291 this._overlaysList = L.DomUtil.create('div', className + '-overlays', form);
12293 container.appendChild(form);
12296 _getLayer: function (id) {
12297 for (var i = 0; i < this._layers.length; i++) {
12299 if (this._layers[i] && L.stamp(this._layers[i].layer) === id) {
12300 return this._layers[i];
12305 _addLayer: function (layer, name, overlay) {
12306 layer.on('add remove', this._onLayerChange, this);
12308 this._layers.push({
12314 if (this.options.autoZIndex && layer.setZIndex) {
12315 this._lastZIndex++;
12316 layer.setZIndex(this._lastZIndex);
12320 _update: function () {
12321 if (!this._container) { return this; }
12323 L.DomUtil.empty(this._baseLayersList);
12324 L.DomUtil.empty(this._overlaysList);
12326 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
12328 for (i = 0; i < this._layers.length; i++) {
12329 obj = this._layers[i];
12330 this._addItem(obj);
12331 overlaysPresent = overlaysPresent || obj.overlay;
12332 baseLayersPresent = baseLayersPresent || !obj.overlay;
12333 baseLayersCount += !obj.overlay ? 1 : 0;
12336 // Hide base layers section if there's only one layer.
12337 if (this.options.hideSingleBase) {
12338 baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
12339 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
12342 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
12347 _onLayerChange: function (e) {
12348 if (!this._handlingClick) {
12352 var obj = this._getLayer(L.stamp(e.target));
12355 // @section Layer events
12356 // @event baselayerchange: LayersControlEvent
12357 // Fired when the base layer is changed through the [layer control](#control-layers).
12358 // @event overlayadd: LayersControlEvent
12359 // Fired when an overlay is selected through the [layer control](#control-layers).
12360 // @event overlayremove: LayersControlEvent
12361 // Fired when an overlay is deselected through the [layer control](#control-layers).
12362 // @namespace Control.Layers
12363 var type = obj.overlay ?
12364 (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
12365 (e.type === 'add' ? 'baselayerchange' : null);
12368 this._map.fire(type, obj);
12372 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
12373 _createRadioElement: function (name, checked) {
12375 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
12376 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
12378 var radioFragment = document.createElement('div');
12379 radioFragment.innerHTML = radioHtml;
12381 return radioFragment.firstChild;
12384 _addItem: function (obj) {
12385 var label = document.createElement('label'),
12386 checked = this._map.hasLayer(obj.layer),
12390 input = document.createElement('input');
12391 input.type = 'checkbox';
12392 input.className = 'leaflet-control-layers-selector';
12393 input.defaultChecked = checked;
12395 input = this._createRadioElement('leaflet-base-layers', checked);
12398 input.layerId = L.stamp(obj.layer);
12400 L.DomEvent.on(input, 'click', this._onInputClick, this);
12402 var name = document.createElement('span');
12403 name.innerHTML = ' ' + obj.name;
12405 // Helps from preventing layer control flicker when checkboxes are disabled
12406 // https://github.com/Leaflet/Leaflet/issues/2771
12407 var holder = document.createElement('div');
12409 label.appendChild(holder);
12410 holder.appendChild(input);
12411 holder.appendChild(name);
12413 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
12414 container.appendChild(label);
12416 this._checkDisabledLayers();
12420 _onInputClick: function () {
12421 var inputs = this._form.getElementsByTagName('input'),
12422 input, layer, hasLayer;
12423 var addedLayers = [],
12424 removedLayers = [];
12426 this._handlingClick = true;
12428 for (var i = inputs.length - 1; i >= 0; i--) {
12430 layer = this._getLayer(input.layerId).layer;
12431 hasLayer = this._map.hasLayer(layer);
12433 if (input.checked && !hasLayer) {
12434 addedLayers.push(layer);
12436 } else if (!input.checked && hasLayer) {
12437 removedLayers.push(layer);
12441 // Bugfix issue 2318: Should remove all old layers before readding new ones
12442 for (i = 0; i < removedLayers.length; i++) {
12443 this._map.removeLayer(removedLayers[i]);
12445 for (i = 0; i < addedLayers.length; i++) {
12446 this._map.addLayer(addedLayers[i]);
12449 this._handlingClick = false;
12451 this._refocusOnMap();
12454 _checkDisabledLayers: function () {
12455 var inputs = this._form.getElementsByTagName('input'),
12458 zoom = this._map.getZoom();
12460 for (var i = inputs.length - 1; i >= 0; i--) {
12462 layer = this._getLayer(input.layerId).layer;
12463 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
12464 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
12469 _expand: function () {
12470 // Backward compatibility, remove me in 1.1.
12471 return this.expand();
12474 _collapse: function () {
12475 // Backward compatibility, remove me in 1.1.
12476 return this.collapse();
12482 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
12483 // 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.
12484 L.control.layers = function (baseLayers, overlays, options) {
12485 return new L.Control.Layers(baseLayers, overlays, options);
12491 * @class PosAnimation
12492 * @aka L.PosAnimation
12493 * @inherits Evented
12494 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
12498 * var fx = new L.PosAnimation();
12499 * fx.run(el, [300, 500], 0.5);
12502 * @constructor L.PosAnimation()
12503 * Creates a `PosAnimation` object.
12507 L.PosAnimation = L.Evented.extend({
12509 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
12510 // Run an animation of a given element to a new position, optionally setting
12511 // duration in seconds (`0.25` by default) and easing linearity factor (3rd
12512 // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
12513 // `0.5` by default).
12514 run: function (el, newPos, duration, easeLinearity) {
12518 this._inProgress = true;
12519 this._duration = duration || 0.25;
12520 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
12522 this._startPos = L.DomUtil.getPosition(el);
12523 this._offset = newPos.subtract(this._startPos);
12524 this._startTime = +new Date();
12526 // @event start: Event
12527 // Fired when the animation starts
12528 this.fire('start');
12534 // Stops the animation (if currently running).
12535 stop: function () {
12536 if (!this._inProgress) { return; }
12542 _animate: function () {
12544 this._animId = L.Util.requestAnimFrame(this._animate, this);
12548 _step: function (round) {
12549 var elapsed = (+new Date()) - this._startTime,
12550 duration = this._duration * 1000;
12552 if (elapsed < duration) {
12553 this._runFrame(this._easeOut(elapsed / duration), round);
12560 _runFrame: function (progress, round) {
12561 var pos = this._startPos.add(this._offset.multiplyBy(progress));
12565 L.DomUtil.setPosition(this._el, pos);
12567 // @event step: Event
12568 // Fired continuously during the animation.
12572 _complete: function () {
12573 L.Util.cancelAnimFrame(this._animId);
12575 this._inProgress = false;
12576 // @event end: Event
12577 // Fired when the animation ends.
12581 _easeOut: function (t) {
12582 return 1 - Math.pow(1 - t, this._easeOutPower);
12589 * Extends L.Map to handle panning animations.
12594 setView: function (center, zoom, options) {
12596 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
12597 center = this._limitCenter(L.latLng(center), zoom, this.options.maxBounds);
12598 options = options || {};
12602 if (this._loaded && !options.reset && options !== true) {
12604 if (options.animate !== undefined) {
12605 options.zoom = L.extend({animate: options.animate}, options.zoom);
12606 options.pan = L.extend({animate: options.animate, duration: options.duration}, options.pan);
12609 // try animating pan or zoom
12610 var moved = (this._zoom !== zoom) ?
12611 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
12612 this._tryAnimatedPan(center, options.pan);
12615 // prevent resize handler call, the view will refresh after animation anyway
12616 clearTimeout(this._sizeTimer);
12621 // animation didn't start, just reset the map view
12622 this._resetView(center, zoom);
12627 panBy: function (offset, options) {
12628 offset = L.point(offset).round();
12629 options = options || {};
12631 if (!offset.x && !offset.y) {
12632 return this.fire('moveend');
12634 // If we pan too far, Chrome gets issues with tiles
12635 // and makes them disappear or appear in the wrong place (slightly offset) #2602
12636 if (options.animate !== true && !this.getSize().contains(offset)) {
12637 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
12641 if (!this._panAnim) {
12642 this._panAnim = new L.PosAnimation();
12645 'step': this._onPanTransitionStep,
12646 'end': this._onPanTransitionEnd
12650 // don't fire movestart if animating inertia
12651 if (!options.noMoveStart) {
12652 this.fire('movestart');
12655 // animate pan unless animate: false specified
12656 if (options.animate !== false) {
12657 L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
12659 var newPos = this._getMapPanePos().subtract(offset).round();
12660 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
12662 this._rawPanBy(offset);
12663 this.fire('move').fire('moveend');
12669 _onPanTransitionStep: function () {
12673 _onPanTransitionEnd: function () {
12674 L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
12675 this.fire('moveend');
12678 _tryAnimatedPan: function (center, options) {
12679 // difference between the new and current centers in pixels
12680 var offset = this._getCenterOffset(center)._floor();
12682 // don't animate too far unless animate: true specified in options
12683 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
12685 this.panBy(offset, options);
12694 * Extends L.Map to handle zoom animations.
12698 // @section Animation Options
12699 L.Map.mergeOptions({
12700 // @option zoomAnimation: Boolean = true
12701 // Whether the map zoom animation is enabled. By default it's enabled
12702 // in all browsers that support CSS3 Transitions except Android.
12703 zoomAnimation: true,
12705 // @option zoomAnimationThreshold: Number = 4
12706 // Won't animate zoom if the zoom difference exceeds this value.
12707 zoomAnimationThreshold: 4
12710 var zoomAnimated = L.DomUtil.TRANSITION && L.Browser.any3d && !L.Browser.mobileOpera;
12712 if (zoomAnimated) {
12714 L.Map.addInitHook(function () {
12715 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
12716 this._zoomAnimated = this.options.zoomAnimation;
12718 // zoom transitions run with the same duration for all layers, so if one of transitionend events
12719 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
12720 if (this._zoomAnimated) {
12722 this._createAnimProxy();
12724 L.DomEvent.on(this._proxy, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
12729 L.Map.include(!zoomAnimated ? {} : {
12731 _createAnimProxy: function () {
12733 var proxy = this._proxy = L.DomUtil.create('div', 'leaflet-proxy leaflet-zoom-animated');
12734 this._panes.mapPane.appendChild(proxy);
12736 this.on('zoomanim', function (e) {
12737 var prop = L.DomUtil.TRANSFORM,
12738 transform = proxy.style[prop];
12740 L.DomUtil.setTransform(proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
12742 // workaround for case when transform is the same and so transitionend event is not fired
12743 if (transform === proxy.style[prop] && this._animatingZoom) {
12744 this._onZoomTransitionEnd();
12748 this.on('load moveend', function () {
12749 var c = this.getCenter(),
12750 z = this.getZoom();
12751 L.DomUtil.setTransform(proxy, this.project(c, z), this.getZoomScale(z, 1));
12755 _catchTransitionEnd: function (e) {
12756 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
12757 this._onZoomTransitionEnd();
12761 _nothingToAnimate: function () {
12762 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
12765 _tryAnimatedZoom: function (center, zoom, options) {
12767 if (this._animatingZoom) { return true; }
12769 options = options || {};
12771 // don't animate if disabled, not supported or zoom difference is too large
12772 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
12773 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
12775 // offset is the pixel coords of the zoom origin relative to the current center
12776 var scale = this.getZoomScale(zoom),
12777 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
12779 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
12780 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
12782 L.Util.requestAnimFrame(function () {
12785 ._animateZoom(center, zoom, true);
12791 _animateZoom: function (center, zoom, startAnim, noUpdate) {
12793 this._animatingZoom = true;
12795 // remember what center/zoom to set after animation
12796 this._animateToCenter = center;
12797 this._animateToZoom = zoom;
12799 L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
12802 // @event zoomanim: ZoomAnimEvent
12803 // Fired on every frame of a zoom animation
12804 this.fire('zoomanim', {
12810 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
12811 setTimeout(L.bind(this._onZoomTransitionEnd, this), 250);
12814 _onZoomTransitionEnd: function () {
12815 if (!this._animatingZoom) { return; }
12817 L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
12819 this._animatingZoom = false;
12821 this._move(this._animateToCenter, this._animateToZoom);
12823 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
12824 L.Util.requestAnimFrame(function () {
12825 this._moveEnd(true);
12833 // @section Methods for modifying map state
12836 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
12837 // Sets the view of the map (geographical center and zoom) performing a smooth
12838 // pan-zoom animation.
12839 flyTo: function (targetCenter, targetZoom, options) {
12841 options = options || {};
12842 if (options.animate === false || !L.Browser.any3d) {
12843 return this.setView(targetCenter, targetZoom, options);
12848 var from = this.project(this.getCenter()),
12849 to = this.project(targetCenter),
12850 size = this.getSize(),
12851 startZoom = this._zoom;
12853 targetCenter = L.latLng(targetCenter);
12854 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
12856 var w0 = Math.max(size.x, size.y),
12857 w1 = w0 * this.getZoomScale(startZoom, targetZoom),
12858 u1 = (to.distanceTo(from)) || 1,
12863 var s1 = i ? -1 : 1,
12865 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
12866 b1 = 2 * s2 * rho2 * u1,
12868 sq = Math.sqrt(b * b + 1) - b;
12870 // workaround for floating point precision bug when sq = 0, log = -Infinite,
12871 // thus triggering an infinite loop in flyTo
12872 var log = sq < 0.000000001 ? -18 : Math.log(sq);
12877 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
12878 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
12879 function tanh(n) { return sinh(n) / cosh(n); }
12883 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
12884 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
12886 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
12888 var start = Date.now(),
12889 S = (r(1) - r0) / rho,
12890 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
12893 var t = (Date.now() - start) / duration,
12894 s = easeOut(t) * S;
12897 this._flyToFrame = L.Util.requestAnimFrame(frame, this);
12900 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
12901 this.getScaleZoom(w0 / w(s), startZoom),
12906 ._move(targetCenter, targetZoom)
12911 this._moveStart(true);
12917 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
12918 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
12919 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
12920 flyToBounds: function (bounds, options) {
12921 var target = this._getBoundsCenterZoom(bounds, options);
12922 return this.flyTo(target.center, target.zoom, options);
12929 * Provides L.Map with convenient shortcuts for using browser geolocation features.
12935 // @section Geolocation methods
12936 _defaultLocateOptions: {
12940 // maxZoom: <Number>
12942 // enableHighAccuracy: false
12945 // @method locate(options?: Locate options): this
12946 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
12947 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
12948 // and optionally sets the map view to the user's location with respect to
12949 // detection accuracy (or to the world view if geolocation failed).
12950 // Note that, if your page doesn't use HTTPS, this method will fail in
12951 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
12952 // See `Locate options` for more details.
12953 locate: function (options) {
12955 options = this._locateOptions = L.extend({}, this._defaultLocateOptions, options);
12957 if (!('geolocation' in navigator)) {
12958 this._handleGeolocationError({
12960 message: 'Geolocation not supported.'
12965 var onResponse = L.bind(this._handleGeolocationResponse, this),
12966 onError = L.bind(this._handleGeolocationError, this);
12968 if (options.watch) {
12969 this._locationWatchId =
12970 navigator.geolocation.watchPosition(onResponse, onError, options);
12972 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
12977 // @method stopLocate(): this
12978 // Stops watching location previously initiated by `map.locate({watch: true})`
12979 // and aborts resetting the map view if map.locate was called with
12980 // `{setView: true}`.
12981 stopLocate: function () {
12982 if (navigator.geolocation && navigator.geolocation.clearWatch) {
12983 navigator.geolocation.clearWatch(this._locationWatchId);
12985 if (this._locateOptions) {
12986 this._locateOptions.setView = false;
12991 _handleGeolocationError: function (error) {
12992 var c = error.code,
12993 message = error.message ||
12994 (c === 1 ? 'permission denied' :
12995 (c === 2 ? 'position unavailable' : 'timeout'));
12997 if (this._locateOptions.setView && !this._loaded) {
13001 // @section Location events
13002 // @event locationerror: ErrorEvent
13003 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
13004 this.fire('locationerror', {
13006 message: 'Geolocation error: ' + message + '.'
13010 _handleGeolocationResponse: function (pos) {
13011 var lat = pos.coords.latitude,
13012 lng = pos.coords.longitude,
13013 latlng = new L.LatLng(lat, lng),
13014 bounds = latlng.toBounds(pos.coords.accuracy),
13015 options = this._locateOptions;
13017 if (options.setView) {
13018 var zoom = this.getBoundsZoom(bounds);
13019 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
13025 timestamp: pos.timestamp
13028 for (var i in pos.coords) {
13029 if (typeof pos.coords[i] === 'number') {
13030 data[i] = pos.coords[i];
13034 // @event locationfound: LocationEvent
13035 // Fired when geolocation (using the [`locate`](#map-locate) method)
13036 // went successfully.
13037 this.fire('locationfound', data);
13043 }(window, document));
13044 //# sourceMappingURL=leaflet-src.map