2 Leaflet 1.0.3, a JS library for interactive maps. http://leafletjs.com
3 (c) 2010-2016 Vladimir Agafonkin, (c) 2010-2011 CloudMade
5 (function (window, document, undefined) {
13 L.noConflict = function () {
21 // define Leaflet for Node module pattern loaders, including Browserify
22 if (typeof module === 'object' && typeof module.exports === 'object') {
25 // define Leaflet as an AMD module
26 } else if (typeof define === 'function' && define.amd) {
30 // define Leaflet as a global L variable, saving the original L to restore later if needed
31 if (typeof window !== 'undefined') {
40 * Various utility functions, used by Leaflet internally.
45 // @function extend(dest: Object, src?: Object): Object
46 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
47 extend: function (dest) {
50 for (j = 1, len = arguments.length; j < len; j++) {
59 // @function create(proto: Object, properties?: Object): Object
60 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
61 create: Object.create || (function () {
63 return function (proto) {
69 // @function bind(fn: Function, …): Function
70 // Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
71 // Has a `L.bind()` shortcut.
72 bind: function (fn, obj) {
73 var slice = Array.prototype.slice;
76 return fn.bind.apply(fn, slice.call(arguments, 1));
79 var args = slice.call(arguments, 2);
82 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
86 // @function stamp(obj: Object): Number
87 // Returns the unique ID of an object, assiging it one if it doesn't have it.
88 stamp: function (obj) {
90 obj._leaflet_id = obj._leaflet_id || ++L.Util.lastId;
91 return obj._leaflet_id;
95 // @property lastId: Number
96 // Last unique ID used by [`stamp()`](#util-stamp)
99 // @function throttle(fn: Function, time: Number, context: Object): Function
100 // Returns a function which executes function `fn` with the given scope `context`
101 // (so that the `this` keyword refers to `context` inside `fn`'s code). The function
102 // `fn` will be called no more than one time per given amount of `time`. The arguments
103 // received by the bound function will be any arguments passed when binding the
104 // function, followed by any arguments passed when invoking the bound function.
105 // Has an `L.bind` shortcut.
106 throttle: function (fn, time, context) {
107 var lock, args, wrapperFn, later;
109 later = function () {
110 // reset lock and call if queued
113 wrapperFn.apply(context, args);
118 wrapperFn = function () {
120 // called too soon, queue to call later
124 // call and lock until later
125 fn.apply(context, arguments);
126 setTimeout(later, time);
134 // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
135 // Returns the number `num` modulo `range` in such a way so it lies within
136 // `range[0]` and `range[1]`. The returned value will be always smaller than
137 // `range[1]` unless `includeMax` is set to `true`.
138 wrapNum: function (x, range, includeMax) {
142 return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
145 // @function falseFn(): Function
146 // Returns a function which always returns `false`.
147 falseFn: function () { return false; },
149 // @function formatNum(num: Number, digits?: Number): Number
150 // Returns the number `num` rounded to `digits` decimals, or to 5 decimals by default.
151 formatNum: function (num, digits) {
152 var pow = Math.pow(10, digits || 5);
153 return Math.round(num * pow) / pow;
156 // @function trim(str: String): String
157 // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
158 trim: function (str) {
159 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
162 // @function splitWords(str: String): String[]
163 // Trims and splits the string on whitespace and returns the array of parts.
164 splitWords: function (str) {
165 return L.Util.trim(str).split(/\s+/);
168 // @function setOptions(obj: Object, options: Object): Object
169 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
170 setOptions: function (obj, options) {
171 if (!obj.hasOwnProperty('options')) {
172 obj.options = obj.options ? L.Util.create(obj.options) : {};
174 for (var i in options) {
175 obj.options[i] = options[i];
180 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
181 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
182 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
183 // be appended at the end. If `uppercase` is `true`, the parameter names will
184 // be uppercased (e.g. `'?A=foo&B=bar'`)
185 getParamString: function (obj, existingUrl, uppercase) {
188 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
190 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
193 // @function template(str: String, data: Object): String
194 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
195 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
196 // `('Hello foo, bar')`. You can also specify functions instead of strings for
197 // data values — they will be evaluated passing `data` as an argument.
198 template: function (str, data) {
199 return str.replace(L.Util.templateRe, function (str, key) {
200 var value = data[key];
202 if (value === undefined) {
203 throw new Error('No value provided for variable ' + str);
205 } else if (typeof value === 'function') {
212 templateRe: /\{ *([\w_\-]+) *\}/g,
214 // @function isArray(obj): Boolean
215 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
216 isArray: Array.isArray || function (obj) {
217 return (Object.prototype.toString.call(obj) === '[object Array]');
220 // @function indexOf(array: Array, el: Object): Number
221 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
222 indexOf: function (array, el) {
223 for (var i = 0; i < array.length; i++) {
224 if (array[i] === el) { return i; }
229 // @property emptyImageUrl: String
230 // Data URI string containing a base64-encoded empty GIF image.
231 // Used as a hack to free memory from unused images on WebKit-powered
232 // mobile devices (by setting image `src` to this string).
233 emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
237 // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
239 function getPrefixed(name) {
240 return window['webkit' + name] || window['moz' + name] || window['ms' + name];
245 // fallback for IE 7-8
246 function timeoutDefer(fn) {
247 var time = +new Date(),
248 timeToCall = Math.max(0, 16 - (time - lastTime));
250 lastTime = time + timeToCall;
251 return window.setTimeout(fn, timeToCall);
254 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer,
255 cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
256 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
259 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
260 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
261 // `context` if given. When `immediate` is set, `fn` is called immediately if
262 // the browser doesn't have native support for
263 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
264 // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
265 L.Util.requestAnimFrame = function (fn, context, immediate) {
266 if (immediate && requestFn === timeoutDefer) {
269 return requestFn.call(window, L.bind(fn, context));
273 // @function cancelAnimFrame(id: Number): undefined
274 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
275 L.Util.cancelAnimFrame = function (id) {
277 cancelFn.call(window, id);
282 // shortcuts for most used utility functions
283 L.extend = L.Util.extend;
284 L.bind = L.Util.bind;
285 L.stamp = L.Util.stamp;
286 L.setOptions = L.Util.setOptions;
297 // Thanks to John Resig and Dean Edwards for inspiration!
299 L.Class = function () {};
301 L.Class.extend = function (props) {
303 // @function extend(props: Object): Function
304 // [Extends the current class](#class-inheritance) given the properties to be included.
305 // Returns a Javascript function that is a class constructor (to be called with `new`).
306 var NewClass = function () {
308 // call the constructor
309 if (this.initialize) {
310 this.initialize.apply(this, arguments);
313 // call all constructor hooks
314 this.callInitHooks();
317 var parentProto = NewClass.__super__ = this.prototype;
319 var proto = L.Util.create(parentProto);
320 proto.constructor = NewClass;
322 NewClass.prototype = proto;
324 // inherit parent's statics
325 for (var i in this) {
326 if (this.hasOwnProperty(i) && i !== 'prototype') {
327 NewClass[i] = this[i];
331 // mix static properties into the class
333 L.extend(NewClass, props.statics);
334 delete props.statics;
337 // mix includes into the prototype
338 if (props.includes) {
339 L.Util.extend.apply(null, [proto].concat(props.includes));
340 delete props.includes;
345 props.options = L.Util.extend(L.Util.create(proto.options), props.options);
348 // mix given properties into the prototype
349 L.extend(proto, props);
351 proto._initHooks = [];
353 // add method for calling all hooks
354 proto.callInitHooks = function () {
356 if (this._initHooksCalled) { return; }
358 if (parentProto.callInitHooks) {
359 parentProto.callInitHooks.call(this);
362 this._initHooksCalled = true;
364 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
365 proto._initHooks[i].call(this);
373 // @function include(properties: Object): this
374 // [Includes a mixin](#class-includes) into the current class.
375 L.Class.include = function (props) {
376 L.extend(this.prototype, props);
380 // @function mergeOptions(options: Object): this
381 // [Merges `options`](#class-options) into the defaults of the class.
382 L.Class.mergeOptions = function (options) {
383 L.extend(this.prototype.options, options);
387 // @function addInitHook(fn: Function): this
388 // Adds a [constructor hook](#class-constructor-hooks) to the class.
389 L.Class.addInitHook = function (fn) { // (Function) || (String, args...)
390 var args = Array.prototype.slice.call(arguments, 1);
392 var init = typeof fn === 'function' ? fn : function () {
393 this[fn].apply(this, args);
396 this.prototype._initHooks = this.prototype._initHooks || [];
397 this.prototype._initHooks.push(init);
408 * A set of methods shared between event-powered classes (like `Map` and `Marker`). Generally, events allow you to execute some function when something happens with an object (e.g. the user clicks on the map, causing the map to fire `'click'` event).
413 * map.on('click', function(e) {
418 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
421 * function onClick(e) { ... }
423 * map.on('click', onClick);
424 * map.off('click', onClick);
429 L.Evented = L.Class.extend({
431 /* @method on(type: String, fn: Function, context?: Object): this
432 * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`).
435 * @method on(eventMap: Object): this
436 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
438 on: function (types, fn, context) {
440 // types can be a map of types/handlers
441 if (typeof types === 'object') {
442 for (var type in types) {
443 // we don't process space-separated events here for performance;
444 // it's a hot path since Layer uses the on(obj) syntax
445 this._on(type, types[type], fn);
449 // types can be a string of space-separated words
450 types = L.Util.splitWords(types);
452 for (var i = 0, len = types.length; i < len; i++) {
453 this._on(types[i], fn, context);
460 /* @method off(type: String, fn?: Function, context?: Object): this
461 * Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener.
464 * @method off(eventMap: Object): this
465 * Removes a set of type/listener pairs.
469 * Removes all listeners to all events on the object.
471 off: function (types, fn, context) {
474 // clear all listeners if called without arguments
477 } else if (typeof types === 'object') {
478 for (var type in types) {
479 this._off(type, types[type], fn);
483 types = L.Util.splitWords(types);
485 for (var i = 0, len = types.length; i < len; i++) {
486 this._off(types[i], fn, context);
493 // attach listener (without syntactic sugar now)
494 _on: function (type, fn, context) {
495 this._events = this._events || {};
497 /* get/init listeners for type */
498 var typeListeners = this._events[type];
499 if (!typeListeners) {
501 this._events[type] = typeListeners;
504 if (context === this) {
505 // Less memory footprint.
508 var newListener = {fn: fn, ctx: context},
509 listeners = typeListeners;
511 // check if fn already there
512 for (var i = 0, len = listeners.length; i < len; i++) {
513 if (listeners[i].fn === fn && listeners[i].ctx === context) {
518 listeners.push(newListener);
521 _off: function (type, fn, context) {
526 if (!this._events) { return; }
528 listeners = this._events[type];
535 // Set all removed listeners to noop so they are not called if remove happens in fire
536 for (i = 0, len = listeners.length; i < len; i++) {
537 listeners[i].fn = L.Util.falseFn;
539 // clear all listeners for a type if function isn't specified
540 delete this._events[type];
544 if (context === this) {
550 // find fn and remove it
551 for (i = 0, len = listeners.length; i < len; i++) {
552 var l = listeners[i];
553 if (l.ctx !== context) { continue; }
556 // set the removed listener to noop so that's not called if remove happens in fire
557 l.fn = L.Util.falseFn;
559 if (this._firingCount) {
560 /* copy array in case events are being fired */
561 this._events[type] = listeners = listeners.slice();
563 listeners.splice(i, 1);
571 // @method fire(type: String, data?: Object, propagate?: Boolean): this
572 // Fires an event of the specified type. You can optionally provide an data
573 // object — the first argument of the listener function will contain its
574 // properties. The event can optionally be propagated to event parents.
575 fire: function (type, data, propagate) {
576 if (!this.listens(type, propagate)) { return this; }
578 var event = L.Util.extend({}, data, {type: type, target: this});
581 var listeners = this._events[type];
584 this._firingCount = (this._firingCount + 1) || 1;
585 for (var i = 0, len = listeners.length; i < len; i++) {
586 var l = listeners[i];
587 l.fn.call(l.ctx || this, event);
595 // propagate the event to parents (set with addEventParent)
596 this._propagateEvent(event);
602 // @method listens(type: String): Boolean
603 // Returns `true` if a particular event type has any listeners attached to it.
604 listens: function (type, propagate) {
605 var listeners = this._events && this._events[type];
606 if (listeners && listeners.length) { return true; }
609 // also check parents for listeners if event propagates
610 for (var id in this._eventParents) {
611 if (this._eventParents[id].listens(type, propagate)) { return true; }
617 // @method once(…): this
618 // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
619 once: function (types, fn, context) {
621 if (typeof types === 'object') {
622 for (var type in types) {
623 this.once(type, types[type], fn);
628 var handler = L.bind(function () {
630 .off(types, fn, context)
631 .off(types, handler, context);
634 // add a listener that's executed once and removed after that
636 .on(types, fn, context)
637 .on(types, handler, context);
640 // @method addEventParent(obj: Evented): this
641 // Adds an event parent - an `Evented` that will receive propagated events
642 addEventParent: function (obj) {
643 this._eventParents = this._eventParents || {};
644 this._eventParents[L.stamp(obj)] = obj;
648 // @method removeEventParent(obj: Evented): this
649 // Removes an event parent, so it will stop receiving propagated events
650 removeEventParent: function (obj) {
651 if (this._eventParents) {
652 delete this._eventParents[L.stamp(obj)];
657 _propagateEvent: function (e) {
658 for (var id in this._eventParents) {
659 this._eventParents[id].fire(e.type, L.extend({layer: e.target}, e), true);
664 var proto = L.Evented.prototype;
666 // aliases; we should ditch those eventually
668 // @method addEventListener(…): this
669 // Alias to [`on(…)`](#evented-on)
670 proto.addEventListener = proto.on;
672 // @method removeEventListener(…): this
673 // Alias to [`off(…)`](#evented-off)
675 // @method clearAllEventListeners(…): this
676 // Alias to [`off()`](#evented-off)
677 proto.removeEventListener = proto.clearAllEventListeners = proto.off;
679 // @method addOneTimeEventListener(…): this
680 // Alias to [`once(…)`](#evented-once)
681 proto.addOneTimeEventListener = proto.once;
683 // @method fireEvent(…): this
684 // Alias to [`fire(…)`](#evented-fire)
685 proto.fireEvent = proto.fire;
687 // @method hasEventListeners(…): Boolean
688 // Alias to [`listens(…)`](#evented-listens)
689 proto.hasEventListeners = proto.listens;
691 L.Mixin = {Events: proto};
699 * A namespace with static properties for browser/feature detection used by Leaflet internally.
704 * if (L.Browser.ielt9) {
705 * alert('Upgrade your browser, dude!');
712 var ua = navigator.userAgent.toLowerCase(),
713 doc = document.documentElement,
715 ie = 'ActiveXObject' in window,
717 webkit = ua.indexOf('webkit') !== -1,
718 phantomjs = ua.indexOf('phantom') !== -1,
719 android23 = ua.search('android [23]') !== -1,
720 chrome = ua.indexOf('chrome') !== -1,
721 gecko = ua.indexOf('gecko') !== -1 && !webkit && !window.opera && !ie,
723 win = navigator.platform.indexOf('Win') === 0,
725 mobile = typeof orientation !== 'undefined' || ua.indexOf('mobile') !== -1,
726 msPointer = !window.PointerEvent && window.MSPointerEvent,
727 pointer = window.PointerEvent || msPointer,
729 ie3d = ie && ('transition' in doc.style),
730 webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23,
731 gecko3d = 'MozPerspective' in doc.style,
732 opera12 = 'OTransition' in doc.style;
735 var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
736 (window.DocumentTouch && document instanceof window.DocumentTouch));
740 // @property ie: Boolean
741 // `true` for all Internet Explorer versions (not Edge).
744 // @property ielt9: Boolean
745 // `true` for Internet Explorer versions less than 9.
746 ielt9: ie && !document.addEventListener,
748 // @property edge: Boolean
749 // `true` for the Edge web browser.
750 edge: 'msLaunchUri' in navigator && !('documentMode' in document),
752 // @property webkit: Boolean
753 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
756 // @property gecko: Boolean
757 // `true` for gecko-based browsers like Firefox.
760 // @property android: Boolean
761 // `true` for any browser running on an Android platform.
762 android: ua.indexOf('android') !== -1,
764 // @property android23: Boolean
765 // `true` for browsers running on Android 2 or Android 3.
766 android23: android23,
768 // @property chrome: Boolean
769 // `true` for the Chrome browser.
772 // @property safari: Boolean
773 // `true` for the Safari browser.
774 safari: !chrome && ua.indexOf('safari') !== -1,
777 // @property win: Boolean
778 // `true` when the browser is running in a Windows platform
782 // @property ie3d: Boolean
783 // `true` for all Internet Explorer versions supporting CSS transforms.
786 // @property webkit3d: Boolean
787 // `true` for webkit-based browsers supporting CSS transforms.
790 // @property gecko3d: Boolean
791 // `true` for gecko-based browsers supporting CSS transforms.
794 // @property opera12: Boolean
795 // `true` for the Opera browser supporting CSS transforms (version 12 or later).
798 // @property any3d: Boolean
799 // `true` for all browsers supporting CSS transforms.
800 any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantomjs,
803 // @property mobile: Boolean
804 // `true` for all browsers running in a mobile device.
807 // @property mobileWebkit: Boolean
808 // `true` for all webkit-based browsers in a mobile device.
809 mobileWebkit: mobile && webkit,
811 // @property mobileWebkit3d: Boolean
812 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
813 mobileWebkit3d: mobile && webkit3d,
815 // @property mobileOpera: Boolean
816 // `true` for the Opera browser in a mobile device.
817 mobileOpera: mobile && window.opera,
819 // @property mobileGecko: Boolean
820 // `true` for gecko-based browsers running in a mobile device.
821 mobileGecko: mobile && gecko,
824 // @property touch: Boolean
825 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
826 // This does not necessarily mean that the browser is running in a computer with
827 // a touchscreen, it only means that the browser is capable of understanding
831 // @property msPointer: Boolean
832 // `true` for browsers implementing the Microsoft touch events model (notably IE10).
833 msPointer: !!msPointer,
835 // @property pointer: Boolean
836 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
840 // @property retina: Boolean
841 // `true` for browsers on a high-resolution "retina" screen.
842 retina: (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1
853 * Represents a point with `x` and `y` coordinates in pixels.
858 * var point = L.point(200, 300);
861 * All Leaflet methods and options that accept `Point` objects also accept them in a simple Array form (unless noted otherwise), so these lines are equivalent:
864 * map.panBy([200, 300]);
865 * map.panBy(L.point(200, 300));
869 L.Point = function (x, y, round) {
870 // @property x: Number; The `x` coordinate of the point
871 this.x = (round ? Math.round(x) : x);
872 // @property y: Number; The `y` coordinate of the point
873 this.y = (round ? Math.round(y) : y);
876 L.Point.prototype = {
878 // @method clone(): Point
879 // Returns a copy of the current point.
881 return new L.Point(this.x, this.y);
884 // @method add(otherPoint: Point): Point
885 // Returns the result of addition of the current and the given points.
886 add: function (point) {
887 // non-destructive, returns a new point
888 return this.clone()._add(L.point(point));
891 _add: function (point) {
892 // destructive, used directly for performance in situations where it's safe to modify existing point
898 // @method subtract(otherPoint: Point): Point
899 // Returns the result of subtraction of the given point from the current.
900 subtract: function (point) {
901 return this.clone()._subtract(L.point(point));
904 _subtract: function (point) {
910 // @method divideBy(num: Number): Point
911 // Returns the result of division of the current point by the given number.
912 divideBy: function (num) {
913 return this.clone()._divideBy(num);
916 _divideBy: function (num) {
922 // @method multiplyBy(num: Number): Point
923 // Returns the result of multiplication of the current point by the given number.
924 multiplyBy: function (num) {
925 return this.clone()._multiplyBy(num);
928 _multiplyBy: function (num) {
934 // @method scaleBy(scale: Point): Point
935 // Multiply each coordinate of the current point by each coordinate of
936 // `scale`. In linear algebra terms, multiply the point by the
937 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
938 // defined by `scale`.
939 scaleBy: function (point) {
940 return new L.Point(this.x * point.x, this.y * point.y);
943 // @method unscaleBy(scale: Point): Point
944 // Inverse of `scaleBy`. Divide each coordinate of the current point by
945 // each coordinate of `scale`.
946 unscaleBy: function (point) {
947 return new L.Point(this.x / point.x, this.y / point.y);
950 // @method round(): Point
951 // Returns a copy of the current point with rounded coordinates.
953 return this.clone()._round();
956 _round: function () {
957 this.x = Math.round(this.x);
958 this.y = Math.round(this.y);
962 // @method floor(): Point
963 // Returns a copy of the current point with floored coordinates (rounded down).
965 return this.clone()._floor();
968 _floor: function () {
969 this.x = Math.floor(this.x);
970 this.y = Math.floor(this.y);
974 // @method ceil(): Point
975 // Returns a copy of the current point with ceiled coordinates (rounded up).
977 return this.clone()._ceil();
981 this.x = Math.ceil(this.x);
982 this.y = Math.ceil(this.y);
986 // @method distanceTo(otherPoint: Point): Number
987 // Returns the cartesian distance between the current and the given points.
988 distanceTo: function (point) {
989 point = L.point(point);
991 var x = point.x - this.x,
992 y = point.y - this.y;
994 return Math.sqrt(x * x + y * y);
997 // @method equals(otherPoint: Point): Boolean
998 // Returns `true` if the given point has the same coordinates.
999 equals: function (point) {
1000 point = L.point(point);
1002 return point.x === this.x &&
1006 // @method contains(otherPoint: Point): Boolean
1007 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
1008 contains: function (point) {
1009 point = L.point(point);
1011 return Math.abs(point.x) <= Math.abs(this.x) &&
1012 Math.abs(point.y) <= Math.abs(this.y);
1015 // @method toString(): String
1016 // Returns a string representation of the point for debugging purposes.
1017 toString: function () {
1019 L.Util.formatNum(this.x) + ', ' +
1020 L.Util.formatNum(this.y) + ')';
1024 // @factory L.point(x: Number, y: Number, round?: Boolean)
1025 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
1028 // @factory L.point(coords: Number[])
1029 // Expects an array of the form `[x, y]` instead.
1032 // @factory L.point(coords: Object)
1033 // Expects a plain object of the form `{x: Number, y: Number}` instead.
1034 L.point = function (x, y, round) {
1035 if (x instanceof L.Point) {
1038 if (L.Util.isArray(x)) {
1039 return new L.Point(x[0], x[1]);
1041 if (x === undefined || x === null) {
1044 if (typeof x === 'object' && 'x' in x && 'y' in x) {
1045 return new L.Point(x.x, x.y);
1047 return new L.Point(x, y, round);
1056 * Represents a rectangular area in pixel coordinates.
1061 * var p1 = L.point(10, 10),
1062 * p2 = L.point(40, 60),
1063 * bounds = L.bounds(p1, p2);
1066 * All Leaflet methods that accept `Bounds` objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
1069 * otherBounds.intersects([[10, 10], [40, 60]]);
1073 L.Bounds = function (a, b) {
1076 var points = b ? [a, b] : a;
1078 for (var i = 0, len = points.length; i < len; i++) {
1079 this.extend(points[i]);
1083 L.Bounds.prototype = {
1084 // @method extend(point: Point): this
1085 // Extends the bounds to contain the given point.
1086 extend: function (point) { // (Point)
1087 point = L.point(point);
1089 // @property min: Point
1090 // The top left corner of the rectangle.
1091 // @property max: Point
1092 // The bottom right corner of the rectangle.
1093 if (!this.min && !this.max) {
1094 this.min = point.clone();
1095 this.max = point.clone();
1097 this.min.x = Math.min(point.x, this.min.x);
1098 this.max.x = Math.max(point.x, this.max.x);
1099 this.min.y = Math.min(point.y, this.min.y);
1100 this.max.y = Math.max(point.y, this.max.y);
1105 // @method getCenter(round?: Boolean): Point
1106 // Returns the center point of the bounds.
1107 getCenter: function (round) {
1109 (this.min.x + this.max.x) / 2,
1110 (this.min.y + this.max.y) / 2, round);
1113 // @method getBottomLeft(): Point
1114 // Returns the bottom-left point of the bounds.
1115 getBottomLeft: function () {
1116 return new L.Point(this.min.x, this.max.y);
1119 // @method getTopRight(): Point
1120 // Returns the top-right point of the bounds.
1121 getTopRight: function () { // -> Point
1122 return new L.Point(this.max.x, this.min.y);
1125 // @method getSize(): Point
1126 // Returns the size of the given bounds
1127 getSize: function () {
1128 return this.max.subtract(this.min);
1131 // @method contains(otherBounds: Bounds): Boolean
1132 // Returns `true` if the rectangle contains the given one.
1134 // @method contains(point: Point): Boolean
1135 // Returns `true` if the rectangle contains the given point.
1136 contains: function (obj) {
1139 if (typeof obj[0] === 'number' || obj instanceof L.Point) {
1142 obj = L.bounds(obj);
1145 if (obj instanceof L.Bounds) {
1152 return (min.x >= this.min.x) &&
1153 (max.x <= this.max.x) &&
1154 (min.y >= this.min.y) &&
1155 (max.y <= this.max.y);
1158 // @method intersects(otherBounds: Bounds): Boolean
1159 // Returns `true` if the rectangle intersects the given bounds. Two bounds
1160 // intersect if they have at least one point in common.
1161 intersects: function (bounds) { // (Bounds) -> Boolean
1162 bounds = L.bounds(bounds);
1168 xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1169 yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1171 return xIntersects && yIntersects;
1174 // @method overlaps(otherBounds: Bounds): Boolean
1175 // Returns `true` if the rectangle overlaps the given bounds. Two bounds
1176 // overlap if their intersection is an area.
1177 overlaps: function (bounds) { // (Bounds) -> Boolean
1178 bounds = L.bounds(bounds);
1184 xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1185 yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1187 return xOverlaps && yOverlaps;
1190 isValid: function () {
1191 return !!(this.min && this.max);
1196 // @factory L.bounds(topLeft: Point, bottomRight: Point)
1197 // Creates a Bounds object from two coordinates (usually top-left and bottom-right corners).
1199 // @factory L.bounds(points: Point[])
1200 // Creates a Bounds object from the points it contains
1201 L.bounds = function (a, b) {
1202 if (!a || a instanceof L.Bounds) {
1205 return new L.Bounds(a, b);
1211 * @class Transformation
1212 * @aka L.Transformation
1214 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1215 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1216 * the reverse. Used by Leaflet in its projections code.
1221 * var transformation = new L.Transformation(2, 5, -1, 10),
1222 * p = L.point(1, 2),
1223 * p2 = transformation.transform(p), // L.point(7, 8)
1224 * p3 = transformation.untransform(p2); // L.point(1, 2)
1229 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1230 // Creates a `Transformation` object with the given coefficients.
1231 L.Transformation = function (a, b, c, d) {
1238 L.Transformation.prototype = {
1239 // @method transform(point: Point, scale?: Number): Point
1240 // Returns a transformed point, optionally multiplied by the given scale.
1241 // Only accepts actual `L.Point` instances, not arrays.
1242 transform: function (point, scale) { // (Point, Number) -> Point
1243 return this._transform(point.clone(), scale);
1246 // destructive transform (faster)
1247 _transform: function (point, scale) {
1249 point.x = scale * (this._a * point.x + this._b);
1250 point.y = scale * (this._c * point.y + this._d);
1254 // @method untransform(point: Point, scale?: Number): Point
1255 // Returns the reverse transformation of the given point, optionally divided
1256 // by the given scale. Only accepts actual `L.Point` instances, not arrays.
1257 untransform: function (point, scale) {
1260 (point.x / scale - this._b) / this._a,
1261 (point.y / scale - this._d) / this._c);
1268 * @namespace DomUtil
1270 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
1271 * tree, used by Leaflet internally.
1273 * Most functions expecting or returning a `HTMLElement` also work for
1274 * SVG elements. The only difference is that classes refer to CSS classes
1275 * in HTML and SVG classes in SVG.
1280 // @function get(id: String|HTMLElement): HTMLElement
1281 // Returns an element given its DOM id, or returns the element itself
1282 // if it was passed directly.
1283 get: function (id) {
1284 return typeof id === 'string' ? document.getElementById(id) : id;
1287 // @function getStyle(el: HTMLElement, styleAttrib: String): String
1288 // Returns the value for a certain style attribute on an element,
1289 // including computed values or values set through CSS.
1290 getStyle: function (el, style) {
1292 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
1294 if ((!value || value === 'auto') && document.defaultView) {
1295 var css = document.defaultView.getComputedStyle(el, null);
1296 value = css ? css[style] : null;
1299 return value === 'auto' ? null : value;
1302 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
1303 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
1304 create: function (tagName, className, container) {
1306 var el = document.createElement(tagName);
1307 el.className = className || '';
1310 container.appendChild(el);
1316 // @function remove(el: HTMLElement)
1317 // Removes `el` from its parent element
1318 remove: function (el) {
1319 var parent = el.parentNode;
1321 parent.removeChild(el);
1325 // @function empty(el: HTMLElement)
1326 // Removes all of `el`'s children elements from `el`
1327 empty: function (el) {
1328 while (el.firstChild) {
1329 el.removeChild(el.firstChild);
1333 // @function toFront(el: HTMLElement)
1334 // Makes `el` the last children of its parent, so it renders in front of the other children.
1335 toFront: function (el) {
1336 el.parentNode.appendChild(el);
1339 // @function toBack(el: HTMLElement)
1340 // Makes `el` the first children of its parent, so it renders back from the other children.
1341 toBack: function (el) {
1342 var parent = el.parentNode;
1343 parent.insertBefore(el, parent.firstChild);
1346 // @function hasClass(el: HTMLElement, name: String): Boolean
1347 // Returns `true` if the element's class attribute contains `name`.
1348 hasClass: function (el, name) {
1349 if (el.classList !== undefined) {
1350 return el.classList.contains(name);
1352 var className = L.DomUtil.getClass(el);
1353 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
1356 // @function addClass(el: HTMLElement, name: String)
1357 // Adds `name` to the element's class attribute.
1358 addClass: function (el, name) {
1359 if (el.classList !== undefined) {
1360 var classes = L.Util.splitWords(name);
1361 for (var i = 0, len = classes.length; i < len; i++) {
1362 el.classList.add(classes[i]);
1364 } else if (!L.DomUtil.hasClass(el, name)) {
1365 var className = L.DomUtil.getClass(el);
1366 L.DomUtil.setClass(el, (className ? className + ' ' : '') + name);
1370 // @function removeClass(el: HTMLElement, name: String)
1371 // Removes `name` from the element's class attribute.
1372 removeClass: function (el, name) {
1373 if (el.classList !== undefined) {
1374 el.classList.remove(name);
1376 L.DomUtil.setClass(el, L.Util.trim((' ' + L.DomUtil.getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
1380 // @function setClass(el: HTMLElement, name: String)
1381 // Sets the element's class.
1382 setClass: function (el, name) {
1383 if (el.className.baseVal === undefined) {
1384 el.className = name;
1386 // in case of SVG element
1387 el.className.baseVal = name;
1391 // @function getClass(el: HTMLElement): String
1392 // Returns the element's class.
1393 getClass: function (el) {
1394 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
1397 // @function setOpacity(el: HTMLElement, opacity: Number)
1398 // Set the opacity of an element (including old IE support).
1399 // `opacity` must be a number from `0` to `1`.
1400 setOpacity: function (el, value) {
1402 if ('opacity' in el.style) {
1403 el.style.opacity = value;
1405 } else if ('filter' in el.style) {
1406 L.DomUtil._setOpacityIE(el, value);
1410 _setOpacityIE: function (el, value) {
1412 filterName = 'DXImageTransform.Microsoft.Alpha';
1414 // filters collection throws an error if we try to retrieve a filter that doesn't exist
1416 filter = el.filters.item(filterName);
1418 // don't set opacity to 1 if we haven't already set an opacity,
1419 // it isn't needed and breaks transparent pngs.
1420 if (value === 1) { return; }
1423 value = Math.round(value * 100);
1426 filter.Enabled = (value !== 100);
1427 filter.Opacity = value;
1429 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
1433 // @function testProp(props: String[]): String|false
1434 // Goes through the array of style names and returns the first name
1435 // that is a valid style name for an element. If no such name is found,
1436 // it returns false. Useful for vendor-prefixed styles like `transform`.
1437 testProp: function (props) {
1439 var style = document.documentElement.style;
1441 for (var i = 0; i < props.length; i++) {
1442 if (props[i] in style) {
1449 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
1450 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
1451 // and optionally scaled by `scale`. Does not have an effect if the
1452 // browser doesn't support 3D CSS transforms.
1453 setTransform: function (el, offset, scale) {
1454 var pos = offset || new L.Point(0, 0);
1456 el.style[L.DomUtil.TRANSFORM] =
1458 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
1459 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
1460 (scale ? ' scale(' + scale + ')' : '');
1463 // @function setPosition(el: HTMLElement, position: Point)
1464 // Sets the position of `el` to coordinates specified by `position`,
1465 // using CSS translate or top/left positioning depending on the browser
1466 // (used by Leaflet internally to position its layers).
1467 setPosition: function (el, point) { // (HTMLElement, Point[, Boolean])
1470 el._leaflet_pos = point;
1473 if (L.Browser.any3d) {
1474 L.DomUtil.setTransform(el, point);
1476 el.style.left = point.x + 'px';
1477 el.style.top = point.y + 'px';
1481 // @function getPosition(el: HTMLElement): Point
1482 // Returns the coordinates of an element previously positioned with setPosition.
1483 getPosition: function (el) {
1484 // this method is only used for elements previously positioned using setPosition,
1485 // so it's safe to cache the position for performance
1487 return el._leaflet_pos || new L.Point(0, 0);
1493 // prefix style property names
1495 // @property TRANSFORM: String
1496 // Vendor-prefixed fransform style name (e.g. `'webkitTransform'` for WebKit).
1497 L.DomUtil.TRANSFORM = L.DomUtil.testProp(
1498 ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
1501 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
1502 // the same for the transitionend event, in particular the Android 4.1 stock browser
1504 // @property TRANSITION: String
1505 // Vendor-prefixed transform style name.
1506 var transition = L.DomUtil.TRANSITION = L.DomUtil.testProp(
1507 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
1509 L.DomUtil.TRANSITION_END =
1510 transition === 'webkitTransition' || transition === 'OTransition' ? transition + 'End' : 'transitionend';
1512 // @function disableTextSelection()
1513 // Prevents the user from generating `selectstart` DOM events, usually generated
1514 // when the user drags the mouse through a page with text. Used internally
1515 // by Leaflet to override the behaviour of any click-and-drag interaction on
1516 // the map. Affects drag interactions on the whole document.
1518 // @function enableTextSelection()
1519 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
1520 if ('onselectstart' in document) {
1521 L.DomUtil.disableTextSelection = function () {
1522 L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);
1524 L.DomUtil.enableTextSelection = function () {
1525 L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);
1529 var userSelectProperty = L.DomUtil.testProp(
1530 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
1532 L.DomUtil.disableTextSelection = function () {
1533 if (userSelectProperty) {
1534 var style = document.documentElement.style;
1535 this._userSelect = style[userSelectProperty];
1536 style[userSelectProperty] = 'none';
1539 L.DomUtil.enableTextSelection = function () {
1540 if (userSelectProperty) {
1541 document.documentElement.style[userSelectProperty] = this._userSelect;
1542 delete this._userSelect;
1547 // @function disableImageDrag()
1548 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
1549 // for `dragstart` DOM events, usually generated when the user drags an image.
1550 L.DomUtil.disableImageDrag = function () {
1551 L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault);
1554 // @function enableImageDrag()
1555 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
1556 L.DomUtil.enableImageDrag = function () {
1557 L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault);
1560 // @function preventOutline(el: HTMLElement)
1561 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
1562 // of the element `el` invisible. Used internally by Leaflet to prevent
1563 // focusable elements from displaying an outline when the user performs a
1564 // drag interaction on them.
1565 L.DomUtil.preventOutline = function (element) {
1566 while (element.tabIndex === -1) {
1567 element = element.parentNode;
1569 if (!element || !element.style) { return; }
1570 L.DomUtil.restoreOutline();
1571 this._outlineElement = element;
1572 this._outlineStyle = element.style.outline;
1573 element.style.outline = 'none';
1574 L.DomEvent.on(window, 'keydown', L.DomUtil.restoreOutline, this);
1577 // @function restoreOutline()
1578 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
1579 L.DomUtil.restoreOutline = function () {
1580 if (!this._outlineElement) { return; }
1581 this._outlineElement.style.outline = this._outlineStyle;
1582 delete this._outlineElement;
1583 delete this._outlineStyle;
1584 L.DomEvent.off(window, 'keydown', L.DomUtil.restoreOutline, this);
1593 * Represents a geographical point with a certain latitude and longitude.
1598 * var latlng = L.latLng(50.5, 30.5);
1601 * All Leaflet methods that accept LatLng objects also accept them in a simple Array form and simple object form (unless noted otherwise), so these lines are equivalent:
1604 * map.panTo([50, 30]);
1605 * map.panTo({lon: 30, lat: 50});
1606 * map.panTo({lat: 50, lng: 30});
1607 * map.panTo(L.latLng(50, 30));
1611 L.LatLng = function (lat, lng, alt) {
1612 if (isNaN(lat) || isNaN(lng)) {
1613 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1616 // @property lat: Number
1617 // Latitude in degrees
1620 // @property lng: Number
1621 // Longitude in degrees
1624 // @property alt: Number
1625 // Altitude in meters (optional)
1626 if (alt !== undefined) {
1631 L.LatLng.prototype = {
1632 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1633 // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overriden by setting `maxMargin` to a small number.
1634 equals: function (obj, maxMargin) {
1635 if (!obj) { return false; }
1637 obj = L.latLng(obj);
1639 var margin = Math.max(
1640 Math.abs(this.lat - obj.lat),
1641 Math.abs(this.lng - obj.lng));
1643 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1646 // @method toString(): String
1647 // Returns a string representation of the point (for debugging purposes).
1648 toString: function (precision) {
1650 L.Util.formatNum(this.lat, precision) + ', ' +
1651 L.Util.formatNum(this.lng, precision) + ')';
1654 // @method distanceTo(otherLatLng: LatLng): Number
1655 // Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula).
1656 distanceTo: function (other) {
1657 return L.CRS.Earth.distance(this, L.latLng(other));
1660 // @method wrap(): LatLng
1661 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1663 return L.CRS.Earth.wrapLatLng(this);
1666 // @method toBounds(sizeInMeters: Number): LatLngBounds
1667 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
1668 toBounds: function (sizeInMeters) {
1669 var latAccuracy = 180 * sizeInMeters / 40075017,
1670 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1672 return L.latLngBounds(
1673 [this.lat - latAccuracy, this.lng - lngAccuracy],
1674 [this.lat + latAccuracy, this.lng + lngAccuracy]);
1677 clone: function () {
1678 return new L.LatLng(this.lat, this.lng, this.alt);
1684 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1685 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1688 // @factory L.latLng(coords: Array): LatLng
1689 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1692 // @factory L.latLng(coords: Object): LatLng
1693 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1695 L.latLng = function (a, b, c) {
1696 if (a instanceof L.LatLng) {
1699 if (L.Util.isArray(a) && typeof a[0] !== 'object') {
1700 if (a.length === 3) {
1701 return new L.LatLng(a[0], a[1], a[2]);
1703 if (a.length === 2) {
1704 return new L.LatLng(a[0], a[1]);
1708 if (a === undefined || a === null) {
1711 if (typeof a === 'object' && 'lat' in a) {
1712 return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1714 if (b === undefined) {
1717 return new L.LatLng(a, b, c);
1723 * @class LatLngBounds
1724 * @aka L.LatLngBounds
1726 * Represents a rectangular geographical area on a map.
1731 * var corner1 = L.latLng(40.712, -74.227),
1732 * corner2 = L.latLng(40.774, -74.125),
1733 * bounds = L.latLngBounds(corner1, corner2);
1736 * All Leaflet methods that accept LatLngBounds objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
1740 * [40.712, -74.227],
1745 * Caution: if the area crosses the antimeridian (often confused with the International Date Line), you must specify corners _outside_ the [-180, 180] degrees longitude range.
1748 L.LatLngBounds = function (corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
1749 if (!corner1) { return; }
1751 var latlngs = corner2 ? [corner1, corner2] : corner1;
1753 for (var i = 0, len = latlngs.length; i < len; i++) {
1754 this.extend(latlngs[i]);
1758 L.LatLngBounds.prototype = {
1760 // @method extend(latlng: LatLng): this
1761 // Extend the bounds to contain the given point
1764 // @method extend(otherBounds: LatLngBounds): this
1765 // Extend the bounds to contain the given bounds
1766 extend: function (obj) {
1767 var sw = this._southWest,
1768 ne = this._northEast,
1771 if (obj instanceof L.LatLng) {
1775 } else if (obj instanceof L.LatLngBounds) {
1776 sw2 = obj._southWest;
1777 ne2 = obj._northEast;
1779 if (!sw2 || !ne2) { return this; }
1782 return obj ? this.extend(L.latLng(obj) || L.latLngBounds(obj)) : this;
1786 this._southWest = new L.LatLng(sw2.lat, sw2.lng);
1787 this._northEast = new L.LatLng(ne2.lat, ne2.lng);
1789 sw.lat = Math.min(sw2.lat, sw.lat);
1790 sw.lng = Math.min(sw2.lng, sw.lng);
1791 ne.lat = Math.max(ne2.lat, ne.lat);
1792 ne.lng = Math.max(ne2.lng, ne.lng);
1798 // @method pad(bufferRatio: Number): LatLngBounds
1799 // Returns bigger bounds created by extending the current bounds by a given percentage in each direction.
1800 pad: function (bufferRatio) {
1801 var sw = this._southWest,
1802 ne = this._northEast,
1803 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1804 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1806 return new L.LatLngBounds(
1807 new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1808 new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1811 // @method getCenter(): LatLng
1812 // Returns the center point of the bounds.
1813 getCenter: function () {
1814 return new L.LatLng(
1815 (this._southWest.lat + this._northEast.lat) / 2,
1816 (this._southWest.lng + this._northEast.lng) / 2);
1819 // @method getSouthWest(): LatLng
1820 // Returns the south-west point of the bounds.
1821 getSouthWest: function () {
1822 return this._southWest;
1825 // @method getNorthEast(): LatLng
1826 // Returns the north-east point of the bounds.
1827 getNorthEast: function () {
1828 return this._northEast;
1831 // @method getNorthWest(): LatLng
1832 // Returns the north-west point of the bounds.
1833 getNorthWest: function () {
1834 return new L.LatLng(this.getNorth(), this.getWest());
1837 // @method getSouthEast(): LatLng
1838 // Returns the south-east point of the bounds.
1839 getSouthEast: function () {
1840 return new L.LatLng(this.getSouth(), this.getEast());
1843 // @method getWest(): Number
1844 // Returns the west longitude of the bounds
1845 getWest: function () {
1846 return this._southWest.lng;
1849 // @method getSouth(): Number
1850 // Returns the south latitude of the bounds
1851 getSouth: function () {
1852 return this._southWest.lat;
1855 // @method getEast(): Number
1856 // Returns the east longitude of the bounds
1857 getEast: function () {
1858 return this._northEast.lng;
1861 // @method getNorth(): Number
1862 // Returns the north latitude of the bounds
1863 getNorth: function () {
1864 return this._northEast.lat;
1867 // @method contains(otherBounds: LatLngBounds): Boolean
1868 // Returns `true` if the rectangle contains the given one.
1871 // @method contains (latlng: LatLng): Boolean
1872 // Returns `true` if the rectangle contains the given point.
1873 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1874 if (typeof obj[0] === 'number' || obj instanceof L.LatLng || 'lat' in obj) {
1875 obj = L.latLng(obj);
1877 obj = L.latLngBounds(obj);
1880 var sw = this._southWest,
1881 ne = this._northEast,
1884 if (obj instanceof L.LatLngBounds) {
1885 sw2 = obj.getSouthWest();
1886 ne2 = obj.getNorthEast();
1891 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1892 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1895 // @method intersects(otherBounds: LatLngBounds): Boolean
1896 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1897 intersects: function (bounds) {
1898 bounds = L.latLngBounds(bounds);
1900 var sw = this._southWest,
1901 ne = this._northEast,
1902 sw2 = bounds.getSouthWest(),
1903 ne2 = bounds.getNorthEast(),
1905 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1906 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1908 return latIntersects && lngIntersects;
1911 // @method overlaps(otherBounds: Bounds): Boolean
1912 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1913 overlaps: function (bounds) {
1914 bounds = L.latLngBounds(bounds);
1916 var sw = this._southWest,
1917 ne = this._northEast,
1918 sw2 = bounds.getSouthWest(),
1919 ne2 = bounds.getNorthEast(),
1921 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1922 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1924 return latOverlaps && lngOverlaps;
1927 // @method toBBoxString(): String
1928 // Returns a string with bounding box coordinates in a 'southwest_lng,southwest_lat,northeast_lng,northeast_lat' format. Useful for sending requests to web services that return geo data.
1929 toBBoxString: function () {
1930 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1933 // @method equals(otherBounds: LatLngBounds): Boolean
1934 // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds.
1935 equals: function (bounds) {
1936 if (!bounds) { return false; }
1938 bounds = L.latLngBounds(bounds);
1940 return this._southWest.equals(bounds.getSouthWest()) &&
1941 this._northEast.equals(bounds.getNorthEast());
1944 // @method isValid(): Boolean
1945 // Returns `true` if the bounds are properly initialized.
1946 isValid: function () {
1947 return !!(this._southWest && this._northEast);
1951 // TODO International date line?
1953 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
1954 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
1957 // @factory L.latLngBounds(latlngs: LatLng[])
1958 // Creates a `LatLngBounds` object defined by the geographical points it contains. Very useful for zooming the map to fit a particular set of locations with [`fitBounds`](#map-fitbounds).
1959 L.latLngBounds = function (a, b) {
1960 if (a instanceof L.LatLngBounds) {
1963 return new L.LatLngBounds(a, b);
1969 * @namespace Projection
1971 * Leaflet comes with a set of already defined Projections out of the box:
1973 * @projection L.Projection.LonLat
1975 * Equirectangular, or Plate Carree projection — the most simple projection,
1976 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
1977 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
1978 * `EPSG:3395` and `Simple` CRS.
1983 L.Projection.LonLat = {
1984 project: function (latlng) {
1985 return new L.Point(latlng.lng, latlng.lat);
1988 unproject: function (point) {
1989 return new L.LatLng(point.y, point.x);
1992 bounds: L.bounds([-180, -90], [180, 90])
1998 * @namespace Projection
1999 * @projection L.Projection.SphericalMercator
2001 * Spherical Mercator projection — the most common projection for online maps,
2002 * used by almost all free and commercial tile providers. Assumes that Earth is
2003 * a sphere. Used by the `EPSG:3857` CRS.
2006 L.Projection.SphericalMercator = {
2009 MAX_LATITUDE: 85.0511287798,
2011 project: function (latlng) {
2012 var d = Math.PI / 180,
2013 max = this.MAX_LATITUDE,
2014 lat = Math.max(Math.min(max, latlng.lat), -max),
2015 sin = Math.sin(lat * d);
2018 this.R * latlng.lng * d,
2019 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
2022 unproject: function (point) {
2023 var d = 180 / Math.PI;
2025 return new L.LatLng(
2026 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
2027 point.x * d / this.R);
2030 bounds: (function () {
2031 var d = 6378137 * Math.PI;
2032 return L.bounds([-d, -d], [d, d]);
2041 * Abstract class that defines coordinate reference systems for projecting
2042 * geographical points into pixel (screen) coordinates and back (and to
2043 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
2044 * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
2046 * Leaflet defines the most usual CRSs by default. If you want to use a
2047 * CRS not defined by default, take a look at the
2048 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
2052 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
2053 // Projects geographical coordinates into pixel coordinates for a given zoom.
2054 latLngToPoint: function (latlng, zoom) {
2055 var projectedPoint = this.projection.project(latlng),
2056 scale = this.scale(zoom);
2058 return this.transformation._transform(projectedPoint, scale);
2061 // @method pointToLatLng(point: Point, zoom: Number): LatLng
2062 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
2063 // zoom into geographical coordinates.
2064 pointToLatLng: function (point, zoom) {
2065 var scale = this.scale(zoom),
2066 untransformedPoint = this.transformation.untransform(point, scale);
2068 return this.projection.unproject(untransformedPoint);
2071 // @method project(latlng: LatLng): Point
2072 // Projects geographical coordinates into coordinates in units accepted for
2073 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
2074 project: function (latlng) {
2075 return this.projection.project(latlng);
2078 // @method unproject(point: Point): LatLng
2079 // Given a projected coordinate returns the corresponding LatLng.
2080 // The inverse of `project`.
2081 unproject: function (point) {
2082 return this.projection.unproject(point);
2085 // @method scale(zoom: Number): Number
2086 // Returns the scale used when transforming projected coordinates into
2087 // pixel coordinates for a particular zoom. For example, it returns
2088 // `256 * 2^zoom` for Mercator-based CRS.
2089 scale: function (zoom) {
2090 return 256 * Math.pow(2, zoom);
2093 // @method zoom(scale: Number): Number
2094 // Inverse of `scale()`, returns the zoom level corresponding to a scale
2095 // factor of `scale`.
2096 zoom: function (scale) {
2097 return Math.log(scale / 256) / Math.LN2;
2100 // @method getProjectedBounds(zoom: Number): Bounds
2101 // Returns the projection's bounds scaled and transformed for the provided `zoom`.
2102 getProjectedBounds: function (zoom) {
2103 if (this.infinite) { return null; }
2105 var b = this.projection.bounds,
2106 s = this.scale(zoom),
2107 min = this.transformation.transform(b.min, s),
2108 max = this.transformation.transform(b.max, s);
2110 return L.bounds(min, max);
2113 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
2114 // Returns the distance between two geographical coordinates.
2116 // @property code: String
2117 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
2119 // @property wrapLng: Number[]
2120 // An array of two numbers defining whether the longitude (horizontal) coordinate
2121 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
2122 // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
2124 // @property wrapLat: Number[]
2125 // Like `wrapLng`, but for the latitude (vertical) axis.
2127 // wrapLng: [min, max],
2128 // wrapLat: [min, max],
2130 // @property infinite: Boolean
2131 // If true, the coordinate space will be unbounded (infinite in both axes)
2134 // @method wrapLatLng(latlng: LatLng): LatLng
2135 // Returns a `LatLng` where lat and lng has been wrapped according to the
2136 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
2137 // Only accepts actual `L.LatLng` instances, not arrays.
2138 wrapLatLng: function (latlng) {
2139 var lng = this.wrapLng ? L.Util.wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
2140 lat = this.wrapLat ? L.Util.wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
2143 return L.latLng(lat, lng, alt);
2146 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
2147 // Returns a `LatLngBounds` with the same size as the given one, ensuring
2148 // that its center is within the CRS's bounds.
2149 // Only accepts actual `L.LatLngBounds` instances, not arrays.
2150 wrapLatLngBounds: function (bounds) {
2151 var center = bounds.getCenter(),
2152 newCenter = this.wrapLatLng(center),
2153 latShift = center.lat - newCenter.lat,
2154 lngShift = center.lng - newCenter.lng;
2156 if (latShift === 0 && lngShift === 0) {
2160 var sw = bounds.getSouthWest(),
2161 ne = bounds.getNorthEast(),
2162 newSw = L.latLng({lat: sw.lat - latShift, lng: sw.lng - lngShift}),
2163 newNe = L.latLng({lat: ne.lat - latShift, lng: ne.lng - lngShift});
2165 return new L.LatLngBounds(newSw, newNe);
2175 * A simple CRS that maps longitude and latitude into `x` and `y` directly.
2176 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
2177 * axis should still be inverted (going from bottom to top). `distance()` returns
2178 * simple euclidean distance.
2181 L.CRS.Simple = L.extend({}, L.CRS, {
2182 projection: L.Projection.LonLat,
2183 transformation: new L.Transformation(1, 0, -1, 0),
2185 scale: function (zoom) {
2186 return Math.pow(2, zoom);
2189 zoom: function (scale) {
2190 return Math.log(scale) / Math.LN2;
2193 distance: function (latlng1, latlng2) {
2194 var dx = latlng2.lng - latlng1.lng,
2195 dy = latlng2.lat - latlng1.lat;
2197 return Math.sqrt(dx * dx + dy * dy);
2209 * Serves as the base for CRS that are global such that they cover the earth.
2210 * Can only be used as the base for other CRS and cannot be used directly,
2211 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
2215 L.CRS.Earth = L.extend({}, L.CRS, {
2216 wrapLng: [-180, 180],
2218 // Mean Earth Radius, as recommended for use by
2219 // the International Union of Geodesy and Geophysics,
2220 // see http://rosettacode.org/wiki/Haversine_formula
2223 // distance between two geographical points using spherical law of cosines approximation
2224 distance: function (latlng1, latlng2) {
2225 var rad = Math.PI / 180,
2226 lat1 = latlng1.lat * rad,
2227 lat2 = latlng2.lat * rad,
2228 a = Math.sin(lat1) * Math.sin(lat2) +
2229 Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad);
2231 return this.R * Math.acos(Math.min(a, 1));
2239 * @crs L.CRS.EPSG3857
2241 * The most common CRS for online maps, used by almost all free and commercial
2242 * tile providers. Uses Spherical Mercator projection. Set in by default in
2243 * Map's `crs` option.
2246 L.CRS.EPSG3857 = L.extend({}, L.CRS.Earth, {
2248 projection: L.Projection.SphericalMercator,
2250 transformation: (function () {
2251 var scale = 0.5 / (Math.PI * L.Projection.SphericalMercator.R);
2252 return new L.Transformation(scale, 0.5, -scale, 0.5);
2256 L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
2264 * @crs L.CRS.EPSG4326
2266 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
2268 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
2269 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer`
2270 * with this CRS, ensure that there are two 256x256 pixel tiles covering the
2271 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
2272 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
2275 L.CRS.EPSG4326 = L.extend({}, L.CRS.Earth, {
2277 projection: L.Projection.LonLat,
2278 transformation: new L.Transformation(1 / 180, 1, -1 / 180, 0.5)
2288 * The central class of the API — it is used to create a map on a page and manipulate it.
2293 * // initialize the map on the "map" div with a given center and zoom
2294 * var map = L.map('map', {
2295 * center: [51.505, -0.09],
2302 L.Map = L.Evented.extend({
2305 // @section Map State Options
2306 // @option crs: CRS = L.CRS.EPSG3857
2307 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
2308 // sure what it means.
2309 crs: L.CRS.EPSG3857,
2311 // @option center: LatLng = undefined
2312 // Initial geographic center of the map
2315 // @option zoom: Number = undefined
2316 // Initial map zoom level
2319 // @option minZoom: Number = undefined
2320 // Minimum zoom level of the map. Overrides any `minZoom` option set on map layers.
2323 // @option maxZoom: Number = undefined
2324 // Maximum zoom level of the map. Overrides any `maxZoom` option set on map layers.
2327 // @option layers: Layer[] = []
2328 // Array of layers that will be added to the map initially
2331 // @option maxBounds: LatLngBounds = null
2332 // When this option is set, the map restricts the view to the given
2333 // geographical bounds, bouncing the user back if the user tries to pan
2334 // outside the view. To set the restriction dynamically, use
2335 // [`setMaxBounds`](#map-setmaxbounds) method.
2336 maxBounds: undefined,
2338 // @option renderer: Renderer = *
2339 // The default method for drawing vector layers on the map. `L.SVG`
2340 // or `L.Canvas` by default depending on browser support.
2341 renderer: undefined,
2344 // @section Animation Options
2345 // @option zoomAnimation: Boolean = true
2346 // Whether the map zoom animation is enabled. By default it's enabled
2347 // in all browsers that support CSS3 Transitions except Android.
2348 zoomAnimation: true,
2350 // @option zoomAnimationThreshold: Number = 4
2351 // Won't animate zoom if the zoom difference exceeds this value.
2352 zoomAnimationThreshold: 4,
2354 // @option fadeAnimation: Boolean = true
2355 // Whether the tile fade animation is enabled. By default it's enabled
2356 // in all browsers that support CSS3 Transitions except Android.
2357 fadeAnimation: true,
2359 // @option markerZoomAnimation: Boolean = true
2360 // Whether markers animate their zoom with the zoom animation, if disabled
2361 // they will disappear for the length of the animation. By default it's
2362 // enabled in all browsers that support CSS3 Transitions except Android.
2363 markerZoomAnimation: true,
2365 // @option transform3DLimit: Number = 2^23
2366 // Defines the maximum size of a CSS translation transform. The default
2367 // value should not be changed unless a web browser positions layers in
2368 // the wrong place after doing a large `panBy`.
2369 transform3DLimit: 8388608, // Precision limit of a 32-bit float
2371 // @section Interaction Options
2372 // @option zoomSnap: Number = 1
2373 // Forces the map's zoom level to always be a multiple of this, particularly
2374 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
2375 // By default, the zoom level snaps to the nearest integer; lower values
2376 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
2377 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
2380 // @option zoomDelta: Number = 1
2381 // Controls how much the map's zoom level will change after a
2382 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
2383 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
2384 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
2387 // @option trackResize: Boolean = true
2388 // Whether the map automatically handles browser window resize to update itself.
2392 initialize: function (id, options) { // (HTMLElement or String, Object)
2393 options = L.setOptions(this, options);
2395 this._initContainer(id);
2398 // hack for https://github.com/Leaflet/Leaflet/issues/1980
2399 this._onResize = L.bind(this._onResize, this);
2403 if (options.maxBounds) {
2404 this.setMaxBounds(options.maxBounds);
2407 if (options.zoom !== undefined) {
2408 this._zoom = this._limitZoom(options.zoom);
2411 if (options.center && options.zoom !== undefined) {
2412 this.setView(L.latLng(options.center), options.zoom, {reset: true});
2415 this._handlers = [];
2417 this._zoomBoundLayers = {};
2418 this._sizeChanged = true;
2420 this.callInitHooks();
2422 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
2423 this._zoomAnimated = L.DomUtil.TRANSITION && L.Browser.any3d && !L.Browser.mobileOpera &&
2424 this.options.zoomAnimation;
2426 // zoom transitions run with the same duration for all layers, so if one of transitionend events
2427 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
2428 if (this._zoomAnimated) {
2429 this._createAnimProxy();
2430 L.DomEvent.on(this._proxy, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
2433 this._addLayers(this.options.layers);
2437 // @section Methods for modifying map state
2439 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
2440 // Sets the view of the map (geographical center and zoom) with the given
2441 // animation options.
2442 setView: function (center, zoom, options) {
2444 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
2445 center = this._limitCenter(L.latLng(center), zoom, this.options.maxBounds);
2446 options = options || {};
2450 if (this._loaded && !options.reset && options !== true) {
2452 if (options.animate !== undefined) {
2453 options.zoom = L.extend({animate: options.animate}, options.zoom);
2454 options.pan = L.extend({animate: options.animate, duration: options.duration}, options.pan);
2457 // try animating pan or zoom
2458 var moved = (this._zoom !== zoom) ?
2459 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
2460 this._tryAnimatedPan(center, options.pan);
2463 // prevent resize handler call, the view will refresh after animation anyway
2464 clearTimeout(this._sizeTimer);
2469 // animation didn't start, just reset the map view
2470 this._resetView(center, zoom);
2475 // @method setZoom(zoom: Number, options: Zoom/pan options): this
2476 // Sets the zoom of the map.
2477 setZoom: function (zoom, options) {
2478 if (!this._loaded) {
2482 return this.setView(this.getCenter(), zoom, {zoom: options});
2485 // @method zoomIn(delta?: Number, options?: Zoom options): this
2486 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
2487 zoomIn: function (delta, options) {
2488 delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
2489 return this.setZoom(this._zoom + delta, options);
2492 // @method zoomOut(delta?: Number, options?: Zoom options): this
2493 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
2494 zoomOut: function (delta, options) {
2495 delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
2496 return this.setZoom(this._zoom - delta, options);
2499 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
2500 // Zooms the map while keeping a specified geographical point on the map
2501 // stationary (e.g. used internally for scroll zoom and double-click zoom).
2503 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
2504 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
2505 setZoomAround: function (latlng, zoom, options) {
2506 var scale = this.getZoomScale(zoom),
2507 viewHalf = this.getSize().divideBy(2),
2508 containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng),
2510 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
2511 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
2513 return this.setView(newCenter, zoom, {zoom: options});
2516 _getBoundsCenterZoom: function (bounds, options) {
2518 options = options || {};
2519 bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds);
2521 var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]),
2522 paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]),
2524 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
2526 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
2528 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
2530 swPoint = this.project(bounds.getSouthWest(), zoom),
2531 nePoint = this.project(bounds.getNorthEast(), zoom),
2532 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
2540 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
2541 // Sets a map view that contains the given geographical bounds with the
2542 // maximum zoom level possible.
2543 fitBounds: function (bounds, options) {
2545 bounds = L.latLngBounds(bounds);
2547 if (!bounds.isValid()) {
2548 throw new Error('Bounds are not valid.');
2551 var target = this._getBoundsCenterZoom(bounds, options);
2552 return this.setView(target.center, target.zoom, options);
2555 // @method fitWorld(options?: fitBounds options): this
2556 // Sets a map view that mostly contains the whole world with the maximum
2557 // zoom level possible.
2558 fitWorld: function (options) {
2559 return this.fitBounds([[-90, -180], [90, 180]], options);
2562 // @method panTo(latlng: LatLng, options?: Pan options): this
2563 // Pans the map to a given center.
2564 panTo: function (center, options) { // (LatLng)
2565 return this.setView(center, this._zoom, {pan: options});
2568 // @method panBy(offset: Point): this
2569 // Pans the map by a given number of pixels (animated).
2570 panBy: function (offset, options) {
2571 offset = L.point(offset).round();
2572 options = options || {};
2574 if (!offset.x && !offset.y) {
2575 return this.fire('moveend');
2577 // If we pan too far, Chrome gets issues with tiles
2578 // and makes them disappear or appear in the wrong place (slightly offset) #2602
2579 if (options.animate !== true && !this.getSize().contains(offset)) {
2580 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
2584 if (!this._panAnim) {
2585 this._panAnim = new L.PosAnimation();
2588 'step': this._onPanTransitionStep,
2589 'end': this._onPanTransitionEnd
2593 // don't fire movestart if animating inertia
2594 if (!options.noMoveStart) {
2595 this.fire('movestart');
2598 // animate pan unless animate: false specified
2599 if (options.animate !== false) {
2600 L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
2602 var newPos = this._getMapPanePos().subtract(offset).round();
2603 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
2605 this._rawPanBy(offset);
2606 this.fire('move').fire('moveend');
2612 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
2613 // Sets the view of the map (geographical center and zoom) performing a smooth
2614 // pan-zoom animation.
2615 flyTo: function (targetCenter, targetZoom, options) {
2617 options = options || {};
2618 if (options.animate === false || !L.Browser.any3d) {
2619 return this.setView(targetCenter, targetZoom, options);
2624 var from = this.project(this.getCenter()),
2625 to = this.project(targetCenter),
2626 size = this.getSize(),
2627 startZoom = this._zoom;
2629 targetCenter = L.latLng(targetCenter);
2630 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
2632 var w0 = Math.max(size.x, size.y),
2633 w1 = w0 * this.getZoomScale(startZoom, targetZoom),
2634 u1 = (to.distanceTo(from)) || 1,
2639 var s1 = i ? -1 : 1,
2641 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
2642 b1 = 2 * s2 * rho2 * u1,
2644 sq = Math.sqrt(b * b + 1) - b;
2646 // workaround for floating point precision bug when sq = 0, log = -Infinite,
2647 // thus triggering an infinite loop in flyTo
2648 var log = sq < 0.000000001 ? -18 : Math.log(sq);
2653 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
2654 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
2655 function tanh(n) { return sinh(n) / cosh(n); }
2659 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
2660 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
2662 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
2664 var start = Date.now(),
2665 S = (r(1) - r0) / rho,
2666 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
2669 var t = (Date.now() - start) / duration,
2673 this._flyToFrame = L.Util.requestAnimFrame(frame, this);
2676 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
2677 this.getScaleZoom(w0 / w(s), startZoom),
2682 ._move(targetCenter, targetZoom)
2687 this._moveStart(true);
2693 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
2694 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
2695 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
2696 flyToBounds: function (bounds, options) {
2697 var target = this._getBoundsCenterZoom(bounds, options);
2698 return this.flyTo(target.center, target.zoom, options);
2701 // @method setMaxBounds(bounds: Bounds): this
2702 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
2703 setMaxBounds: function (bounds) {
2704 bounds = L.latLngBounds(bounds);
2706 if (!bounds.isValid()) {
2707 this.options.maxBounds = null;
2708 return this.off('moveend', this._panInsideMaxBounds);
2709 } else if (this.options.maxBounds) {
2710 this.off('moveend', this._panInsideMaxBounds);
2713 this.options.maxBounds = bounds;
2716 this._panInsideMaxBounds();
2719 return this.on('moveend', this._panInsideMaxBounds);
2722 // @method setMinZoom(zoom: Number): this
2723 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
2724 setMinZoom: function (zoom) {
2725 this.options.minZoom = zoom;
2727 if (this._loaded && this.getZoom() < this.options.minZoom) {
2728 return this.setZoom(zoom);
2734 // @method setMaxZoom(zoom: Number): this
2735 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
2736 setMaxZoom: function (zoom) {
2737 this.options.maxZoom = zoom;
2739 if (this._loaded && (this.getZoom() > this.options.maxZoom)) {
2740 return this.setZoom(zoom);
2746 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
2747 // Pans the map to the closest view that would lie inside the given bounds (if it's not already), controlling the animation using the options specific, if any.
2748 panInsideBounds: function (bounds, options) {
2749 this._enforcingBounds = true;
2750 var center = this.getCenter(),
2751 newCenter = this._limitCenter(center, this._zoom, L.latLngBounds(bounds));
2753 if (!center.equals(newCenter)) {
2754 this.panTo(newCenter, options);
2757 this._enforcingBounds = false;
2761 // @method invalidateSize(options: Zoom/Pan options): this
2762 // Checks if the map container size changed and updates the map if so —
2763 // call it after you've changed the map size dynamically, also animating
2764 // pan by default. If `options.pan` is `false`, panning will not occur.
2765 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
2766 // that it doesn't happen often even if the method is called many
2770 // @method invalidateSize(animate: Boolean): this
2771 // Checks if the map container size changed and updates the map if so —
2772 // call it after you've changed the map size dynamically, also animating
2774 invalidateSize: function (options) {
2775 if (!this._loaded) { return this; }
2777 options = L.extend({
2780 }, options === true ? {animate: true} : options);
2782 var oldSize = this.getSize();
2783 this._sizeChanged = true;
2784 this._lastCenter = null;
2786 var newSize = this.getSize(),
2787 oldCenter = oldSize.divideBy(2).round(),
2788 newCenter = newSize.divideBy(2).round(),
2789 offset = oldCenter.subtract(newCenter);
2791 if (!offset.x && !offset.y) { return this; }
2793 if (options.animate && options.pan) {
2798 this._rawPanBy(offset);
2803 if (options.debounceMoveend) {
2804 clearTimeout(this._sizeTimer);
2805 this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
2807 this.fire('moveend');
2811 // @section Map state change events
2812 // @event resize: ResizeEvent
2813 // Fired when the map is resized.
2814 return this.fire('resize', {
2820 // @section Methods for modifying map state
2821 // @method stop(): this
2822 // Stops the currently running `panTo` or `flyTo` animation, if any.
2824 this.setZoom(this._limitZoom(this._zoom));
2825 if (!this.options.zoomSnap) {
2826 this.fire('viewreset');
2828 return this._stop();
2831 // @section Geolocation methods
2832 // @method locate(options?: Locate options): this
2833 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
2834 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
2835 // and optionally sets the map view to the user's location with respect to
2836 // detection accuracy (or to the world view if geolocation failed).
2837 // Note that, if your page doesn't use HTTPS, this method will fail in
2838 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
2839 // See `Locate options` for more details.
2840 locate: function (options) {
2842 options = this._locateOptions = L.extend({
2846 // maxZoom: <Number>
2848 // enableHighAccuracy: false
2851 if (!('geolocation' in navigator)) {
2852 this._handleGeolocationError({
2854 message: 'Geolocation not supported.'
2859 var onResponse = L.bind(this._handleGeolocationResponse, this),
2860 onError = L.bind(this._handleGeolocationError, this);
2862 if (options.watch) {
2863 this._locationWatchId =
2864 navigator.geolocation.watchPosition(onResponse, onError, options);
2866 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
2871 // @method stopLocate(): this
2872 // Stops watching location previously initiated by `map.locate({watch: true})`
2873 // and aborts resetting the map view if map.locate was called with
2874 // `{setView: true}`.
2875 stopLocate: function () {
2876 if (navigator.geolocation && navigator.geolocation.clearWatch) {
2877 navigator.geolocation.clearWatch(this._locationWatchId);
2879 if (this._locateOptions) {
2880 this._locateOptions.setView = false;
2885 _handleGeolocationError: function (error) {
2887 message = error.message ||
2888 (c === 1 ? 'permission denied' :
2889 (c === 2 ? 'position unavailable' : 'timeout'));
2891 if (this._locateOptions.setView && !this._loaded) {
2895 // @section Location events
2896 // @event locationerror: ErrorEvent
2897 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
2898 this.fire('locationerror', {
2900 message: 'Geolocation error: ' + message + '.'
2904 _handleGeolocationResponse: function (pos) {
2905 var lat = pos.coords.latitude,
2906 lng = pos.coords.longitude,
2907 latlng = new L.LatLng(lat, lng),
2908 bounds = latlng.toBounds(pos.coords.accuracy),
2909 options = this._locateOptions;
2911 if (options.setView) {
2912 var zoom = this.getBoundsZoom(bounds);
2913 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
2919 timestamp: pos.timestamp
2922 for (var i in pos.coords) {
2923 if (typeof pos.coords[i] === 'number') {
2924 data[i] = pos.coords[i];
2928 // @event locationfound: LocationEvent
2929 // Fired when geolocation (using the [`locate`](#map-locate) method)
2930 // went successfully.
2931 this.fire('locationfound', data);
2934 // TODO handler.addTo
2935 // TODO Appropiate docs section?
2936 // @section Other Methods
2937 // @method addHandler(name: String, HandlerClass: Function): this
2938 // Adds a new `Handler` to the map, given its name and constructor function.
2939 addHandler: function (name, HandlerClass) {
2940 if (!HandlerClass) { return this; }
2942 var handler = this[name] = new HandlerClass(this);
2944 this._handlers.push(handler);
2946 if (this.options[name]) {
2953 // @method remove(): this
2954 // Destroys the map and clears all related event listeners.
2955 remove: function () {
2957 this._initEvents(true);
2959 if (this._containerId !== this._container._leaflet_id) {
2960 throw new Error('Map container is being reused by another instance');
2964 // throws error in IE6-8
2965 delete this._container._leaflet_id;
2966 delete this._containerId;
2969 this._container._leaflet_id = undefined;
2971 this._containerId = undefined;
2974 L.DomUtil.remove(this._mapPane);
2976 if (this._clearControlPos) {
2977 this._clearControlPos();
2980 this._clearHandlers();
2983 // @section Map state change events
2984 // @event unload: Event
2985 // Fired when the map is destroyed with [remove](#map-remove) method.
2986 this.fire('unload');
2989 for (var i in this._layers) {
2990 this._layers[i].remove();
2996 // @section Other Methods
2997 // @method createPane(name: String, container?: HTMLElement): HTMLElement
2998 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
2999 // then returns it. The pane is created as a children of `container`, or
3000 // as a children of the main map pane if not set.
3001 createPane: function (name, container) {
3002 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3003 pane = L.DomUtil.create('div', className, container || this._mapPane);
3006 this._panes[name] = pane;
3011 // @section Methods for Getting Map State
3013 // @method getCenter(): LatLng
3014 // Returns the geographical center of the map view
3015 getCenter: function () {
3016 this._checkIfLoaded();
3018 if (this._lastCenter && !this._moved()) {
3019 return this._lastCenter;
3021 return this.layerPointToLatLng(this._getCenterLayerPoint());
3024 // @method getZoom(): Number
3025 // Returns the current zoom level of the map view
3026 getZoom: function () {
3030 // @method getBounds(): LatLngBounds
3031 // Returns the geographical bounds visible in the current map view
3032 getBounds: function () {
3033 var bounds = this.getPixelBounds(),
3034 sw = this.unproject(bounds.getBottomLeft()),
3035 ne = this.unproject(bounds.getTopRight());
3037 return new L.LatLngBounds(sw, ne);
3040 // @method getMinZoom(): Number
3041 // Returns the minimum zoom level of the map (if set in the `minZoom` option of the map or of any layers), or `0` by default.
3042 getMinZoom: function () {
3043 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3046 // @method getMaxZoom(): Number
3047 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3048 getMaxZoom: function () {
3049 return this.options.maxZoom === undefined ?
3050 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3051 this.options.maxZoom;
3054 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number
3055 // Returns the maximum zoom level on which the given bounds fit to the map
3056 // view in its entirety. If `inside` (optional) is set to `true`, the method
3057 // instead returns the minimum zoom level on which the map view fits into
3058 // the given bounds in its entirety.
3059 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3060 bounds = L.latLngBounds(bounds);
3061 padding = L.point(padding || [0, 0]);
3063 var zoom = this.getZoom() || 0,
3064 min = this.getMinZoom(),
3065 max = this.getMaxZoom(),
3066 nw = bounds.getNorthWest(),
3067 se = bounds.getSouthEast(),
3068 size = this.getSize().subtract(padding),
3069 boundsSize = L.bounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3070 snap = L.Browser.any3d ? this.options.zoomSnap : 1;
3072 var scale = Math.min(size.x / boundsSize.x, size.y / boundsSize.y);
3073 zoom = this.getScaleZoom(scale, zoom);
3076 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3077 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3080 return Math.max(min, Math.min(max, zoom));
3083 // @method getSize(): Point
3084 // Returns the current size of the map container (in pixels).
3085 getSize: function () {
3086 if (!this._size || this._sizeChanged) {
3087 this._size = new L.Point(
3088 this._container.clientWidth || 0,
3089 this._container.clientHeight || 0);
3091 this._sizeChanged = false;
3093 return this._size.clone();
3096 // @method getPixelBounds(): Bounds
3097 // Returns the bounds of the current map view in projected pixel
3098 // coordinates (sometimes useful in layer and overlay implementations).
3099 getPixelBounds: function (center, zoom) {
3100 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3101 return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3104 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3105 // the map pane? "left point of the map layer" can be confusing, specially
3106 // since there can be negative offsets.
3107 // @method getPixelOrigin(): Point
3108 // Returns the projected pixel coordinates of the top left point of
3109 // the map layer (useful in custom layer and overlay implementations).
3110 getPixelOrigin: function () {
3111 this._checkIfLoaded();
3112 return this._pixelOrigin;
3115 // @method getPixelWorldBounds(zoom?: Number): Bounds
3116 // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3117 // If `zoom` is omitted, the map's current zoom level is used.
3118 getPixelWorldBounds: function (zoom) {
3119 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3122 // @section Other Methods
3124 // @method getPane(pane: String|HTMLElement): HTMLElement
3125 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3126 getPane: function (pane) {
3127 return typeof pane === 'string' ? this._panes[pane] : pane;
3130 // @method getPanes(): Object
3131 // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3132 // the panes as values.
3133 getPanes: function () {
3137 // @method getContainer: HTMLElement
3138 // Returns the HTML element that contains the map.
3139 getContainer: function () {
3140 return this._container;
3144 // @section Conversion Methods
3146 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3147 // Returns the scale factor to be applied to a map transition from zoom level
3148 // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3149 getZoomScale: function (toZoom, fromZoom) {
3150 // TODO replace with universal implementation after refactoring projections
3151 var crs = this.options.crs;
3152 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3153 return crs.scale(toZoom) / crs.scale(fromZoom);
3156 // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3157 // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3158 // level and everything is scaled by a factor of `scale`. Inverse of
3159 // [`getZoomScale`](#map-getZoomScale).
3160 getScaleZoom: function (scale, fromZoom) {
3161 var crs = this.options.crs;
3162 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3163 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3164 return isNaN(zoom) ? Infinity : zoom;
3167 // @method project(latlng: LatLng, zoom: Number): Point
3168 // Projects a geographical coordinate `LatLng` according to the projection
3169 // of the map's CRS, then scales it according to `zoom` and the CRS's
3170 // `Transformation`. The result is pixel coordinate relative to
3172 project: function (latlng, zoom) {
3173 zoom = zoom === undefined ? this._zoom : zoom;
3174 return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
3177 // @method unproject(point: Point, zoom: Number): LatLng
3178 // Inverse of [`project`](#map-project).
3179 unproject: function (point, zoom) {
3180 zoom = zoom === undefined ? this._zoom : zoom;
3181 return this.options.crs.pointToLatLng(L.point(point), zoom);
3184 // @method layerPointToLatLng(point: Point): LatLng
3185 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3186 // returns the corresponding geographical coordinate (for the current zoom level).
3187 layerPointToLatLng: function (point) {
3188 var projectedPoint = L.point(point).add(this.getPixelOrigin());
3189 return this.unproject(projectedPoint);
3192 // @method latLngToLayerPoint(latlng: LatLng): Point
3193 // Given a geographical coordinate, returns the corresponding pixel coordinate
3194 // relative to the [origin pixel](#map-getpixelorigin).
3195 latLngToLayerPoint: function (latlng) {
3196 var projectedPoint = this.project(L.latLng(latlng))._round();
3197 return projectedPoint._subtract(this.getPixelOrigin());
3200 // @method wrapLatLng(latlng: LatLng): LatLng
3201 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
3202 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
3204 // By default this means longitude is wrapped around the dateline so its
3205 // value is between -180 and +180 degrees.
3206 wrapLatLng: function (latlng) {
3207 return this.options.crs.wrapLatLng(L.latLng(latlng));
3210 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
3211 // Returns a `LatLngBounds` with the same size as the given one, ensuring that
3212 // its center is within the CRS's bounds.
3213 // By default this means the center longitude is wrapped around the dateline so its
3214 // value is between -180 and +180 degrees, and the majority of the bounds
3215 // overlaps the CRS's bounds.
3216 wrapLatLngBounds: function (latlng) {
3217 return this.options.crs.wrapLatLngBounds(L.latLngBounds(latlng));
3220 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
3221 // Returns the distance between two geographical coordinates according to
3222 // the map's CRS. By default this measures distance in meters.
3223 distance: function (latlng1, latlng2) {
3224 return this.options.crs.distance(L.latLng(latlng1), L.latLng(latlng2));
3227 // @method containerPointToLayerPoint(point: Point): Point
3228 // Given a pixel coordinate relative to the map container, returns the corresponding
3229 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
3230 containerPointToLayerPoint: function (point) { // (Point)
3231 return L.point(point).subtract(this._getMapPanePos());
3234 // @method layerPointToContainerPoint(point: Point): Point
3235 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3236 // returns the corresponding pixel coordinate relative to the map container.
3237 layerPointToContainerPoint: function (point) { // (Point)
3238 return L.point(point).add(this._getMapPanePos());
3241 // @method containerPointToLatLng(point: Point): LatLng
3242 // Given a pixel coordinate relative to the map container, returns
3243 // the corresponding geographical coordinate (for the current zoom level).
3244 containerPointToLatLng: function (point) {
3245 var layerPoint = this.containerPointToLayerPoint(L.point(point));
3246 return this.layerPointToLatLng(layerPoint);
3249 // @method latLngToContainerPoint(latlng: LatLng): Point
3250 // Given a geographical coordinate, returns the corresponding pixel coordinate
3251 // relative to the map container.
3252 latLngToContainerPoint: function (latlng) {
3253 return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
3256 // @method mouseEventToContainerPoint(ev: MouseEvent): Point
3257 // Given a MouseEvent object, returns the pixel coordinate relative to the
3258 // map container where the event took place.
3259 mouseEventToContainerPoint: function (e) {
3260 return L.DomEvent.getMousePosition(e, this._container);
3263 // @method mouseEventToLayerPoint(ev: MouseEvent): Point
3264 // Given a MouseEvent object, returns the pixel coordinate relative to
3265 // the [origin pixel](#map-getpixelorigin) where the event took place.
3266 mouseEventToLayerPoint: function (e) {
3267 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
3270 // @method mouseEventToLatLng(ev: MouseEvent): LatLng
3271 // Given a MouseEvent object, returns geographical coordinate where the
3272 // event took place.
3273 mouseEventToLatLng: function (e) { // (MouseEvent)
3274 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
3278 // map initialization methods
3280 _initContainer: function (id) {
3281 var container = this._container = L.DomUtil.get(id);
3284 throw new Error('Map container not found.');
3285 } else if (container._leaflet_id) {
3286 throw new Error('Map container is already initialized.');
3289 L.DomEvent.addListener(container, 'scroll', this._onScroll, this);
3290 this._containerId = L.Util.stamp(container);
3293 _initLayout: function () {
3294 var container = this._container;
3296 this._fadeAnimated = this.options.fadeAnimation && L.Browser.any3d;
3298 L.DomUtil.addClass(container, 'leaflet-container' +
3299 (L.Browser.touch ? ' leaflet-touch' : '') +
3300 (L.Browser.retina ? ' leaflet-retina' : '') +
3301 (L.Browser.ielt9 ? ' leaflet-oldie' : '') +
3302 (L.Browser.safari ? ' leaflet-safari' : '') +
3303 (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
3305 var position = L.DomUtil.getStyle(container, 'position');
3307 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
3308 container.style.position = 'relative';
3313 if (this._initControlPos) {
3314 this._initControlPos();
3318 _initPanes: function () {
3319 var panes = this._panes = {};
3320 this._paneRenderers = {};
3324 // Panes are DOM elements used to control the ordering of layers on the map. You
3325 // can access panes with [`map.getPane`](#map-getpane) or
3326 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
3327 // [`map.createPane`](#map-createpane) method.
3329 // Every map has the following default panes that differ only in zIndex.
3331 // @pane mapPane: HTMLElement = 'auto'
3332 // Pane that contains all other map panes
3334 this._mapPane = this.createPane('mapPane', this._container);
3335 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
3337 // @pane tilePane: HTMLElement = 200
3338 // Pane for `GridLayer`s and `TileLayer`s
3339 this.createPane('tilePane');
3340 // @pane overlayPane: HTMLElement = 400
3341 // Pane for vector overlays (`Path`s), like `Polyline`s and `Polygon`s
3342 this.createPane('shadowPane');
3343 // @pane shadowPane: HTMLElement = 500
3344 // Pane for overlay shadows (e.g. `Marker` shadows)
3345 this.createPane('overlayPane');
3346 // @pane markerPane: HTMLElement = 600
3347 // Pane for `Icon`s of `Marker`s
3348 this.createPane('markerPane');
3349 // @pane tooltipPane: HTMLElement = 650
3350 // Pane for tooltip.
3351 this.createPane('tooltipPane');
3352 // @pane popupPane: HTMLElement = 700
3353 // Pane for `Popup`s.
3354 this.createPane('popupPane');
3356 if (!this.options.markerZoomAnimation) {
3357 L.DomUtil.addClass(panes.markerPane, 'leaflet-zoom-hide');
3358 L.DomUtil.addClass(panes.shadowPane, 'leaflet-zoom-hide');
3363 // private methods that modify map state
3365 // @section Map state change events
3366 _resetView: function (center, zoom) {
3367 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
3369 var loading = !this._loaded;
3370 this._loaded = true;
3371 zoom = this._limitZoom(zoom);
3373 this.fire('viewprereset');
3375 var zoomChanged = this._zoom !== zoom;
3377 ._moveStart(zoomChanged)
3378 ._move(center, zoom)
3379 ._moveEnd(zoomChanged);
3381 // @event viewreset: Event
3382 // Fired when the map needs to redraw its content (this usually happens
3383 // on map zoom or load). Very useful for creating custom overlays.
3384 this.fire('viewreset');
3386 // @event load: Event
3387 // Fired when the map is initialized (when its center and zoom are set
3388 // for the first time).
3394 _moveStart: function (zoomChanged) {
3395 // @event zoomstart: Event
3396 // Fired when the map zoom is about to change (e.g. before zoom animation).
3397 // @event movestart: Event
3398 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
3400 this.fire('zoomstart');
3402 return this.fire('movestart');
3405 _move: function (center, zoom, data) {
3406 if (zoom === undefined) {
3409 var zoomChanged = this._zoom !== zoom;
3412 this._lastCenter = center;
3413 this._pixelOrigin = this._getNewPixelOrigin(center);
3415 // @event zoom: Event
3416 // Fired repeatedly during any change in zoom level, including zoom
3417 // and fly animations.
3418 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530
3419 this.fire('zoom', data);
3422 // @event move: Event
3423 // Fired repeatedly during any movement of the map, including pan and
3425 return this.fire('move', data);
3428 _moveEnd: function (zoomChanged) {
3429 // @event zoomend: Event
3430 // Fired when the map has changed, after any animations.
3432 this.fire('zoomend');
3435 // @event moveend: Event
3436 // Fired when the center of the map stops changing (e.g. user stopped
3437 // dragging the map).
3438 return this.fire('moveend');
3441 _stop: function () {
3442 L.Util.cancelAnimFrame(this._flyToFrame);
3443 if (this._panAnim) {
3444 this._panAnim.stop();
3449 _rawPanBy: function (offset) {
3450 L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
3453 _getZoomSpan: function () {
3454 return this.getMaxZoom() - this.getMinZoom();
3457 _panInsideMaxBounds: function () {
3458 if (!this._enforcingBounds) {
3459 this.panInsideBounds(this.options.maxBounds);
3463 _checkIfLoaded: function () {
3464 if (!this._loaded) {
3465 throw new Error('Set map center and zoom first.');
3469 // DOM event handling
3471 // @section Interaction events
3472 _initEvents: function (remove) {
3473 if (!L.DomEvent) { return; }
3476 this._targets[L.stamp(this._container)] = this;
3478 var onOff = remove ? 'off' : 'on';
3480 // @event click: MouseEvent
3481 // Fired when the user clicks (or taps) the map.
3482 // @event dblclick: MouseEvent
3483 // Fired when the user double-clicks (or double-taps) the map.
3484 // @event mousedown: MouseEvent
3485 // Fired when the user pushes the mouse button on the map.
3486 // @event mouseup: MouseEvent
3487 // Fired when the user releases the mouse button on the map.
3488 // @event mouseover: MouseEvent
3489 // Fired when the mouse enters the map.
3490 // @event mouseout: MouseEvent
3491 // Fired when the mouse leaves the map.
3492 // @event mousemove: MouseEvent
3493 // Fired while the mouse moves over the map.
3494 // @event contextmenu: MouseEvent
3495 // Fired when the user pushes the right mouse button on the map, prevents
3496 // default browser context menu from showing if there are listeners on
3497 // this event. Also fired on mobile when the user holds a single touch
3498 // for a second (also called long press).
3499 // @event keypress: KeyboardEvent
3500 // Fired when the user presses a key from the keyboard while the map is focused.
3501 L.DomEvent[onOff](this._container, 'click dblclick mousedown mouseup ' +
3502 'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);
3504 if (this.options.trackResize) {
3505 L.DomEvent[onOff](window, 'resize', this._onResize, this);
3508 if (L.Browser.any3d && this.options.transform3DLimit) {
3509 this[onOff]('moveend', this._onMoveEnd);
3513 _onResize: function () {
3514 L.Util.cancelAnimFrame(this._resizeRequest);
3515 this._resizeRequest = L.Util.requestAnimFrame(
3516 function () { this.invalidateSize({debounceMoveend: true}); }, this);
3519 _onScroll: function () {
3520 this._container.scrollTop = 0;
3521 this._container.scrollLeft = 0;
3524 _onMoveEnd: function () {
3525 var pos = this._getMapPanePos();
3526 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
3527 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
3528 // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
3529 this._resetView(this.getCenter(), this.getZoom());
3533 _findEventTargets: function (e, type) {
3536 isHover = type === 'mouseout' || type === 'mouseover',
3537 src = e.target || e.srcElement,
3541 target = this._targets[L.stamp(src)];
3542 if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
3543 // Prevent firing click after you just dragged an object.
3547 if (target && target.listens(type, true)) {
3548 if (isHover && !L.DomEvent._isExternalTarget(src, e)) { break; }
3549 targets.push(target);
3550 if (isHover) { break; }
3552 if (src === this._container) { break; }
3553 src = src.parentNode;
3555 if (!targets.length && !dragging && !isHover && L.DomEvent._isExternalTarget(src, e)) {
3561 _handleDOMEvent: function (e) {
3562 if (!this._loaded || L.DomEvent._skipped(e)) { return; }
3564 var type = e.type === 'keypress' && e.keyCode === 13 ? 'click' : e.type;
3566 if (type === 'mousedown') {
3567 // prevents outline when clicking on keyboard-focusable element
3568 L.DomUtil.preventOutline(e.target || e.srcElement);
3571 this._fireDOMEvent(e, type);
3574 _fireDOMEvent: function (e, type, targets) {
3576 if (e.type === 'click') {
3577 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
3578 // @event preclick: MouseEvent
3579 // Fired before mouse click on the map (sometimes useful when you
3580 // want something to happen on click before any existing click
3581 // handlers start running).
3582 var synth = L.Util.extend({}, e);
3583 synth.type = 'preclick';
3584 this._fireDOMEvent(synth, synth.type, targets);
3587 if (e._stopped) { return; }
3589 // Find the layer the event is propagating from and its parents.
3590 targets = (targets || []).concat(this._findEventTargets(e, type));
3592 if (!targets.length) { return; }
3594 var target = targets[0];
3595 if (type === 'contextmenu' && target.listens(type, true)) {
3596 L.DomEvent.preventDefault(e);
3603 if (e.type !== 'keypress') {
3604 var isMarker = target instanceof L.Marker;
3605 data.containerPoint = isMarker ?
3606 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
3607 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
3608 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
3611 for (var i = 0; i < targets.length; i++) {
3612 targets[i].fire(type, data, true);
3613 if (data.originalEvent._stopped ||
3614 (targets[i].options.nonBubblingEvents && L.Util.indexOf(targets[i].options.nonBubblingEvents, type) !== -1)) { return; }
3618 _draggableMoved: function (obj) {
3619 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
3620 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
3623 _clearHandlers: function () {
3624 for (var i = 0, len = this._handlers.length; i < len; i++) {
3625 this._handlers[i].disable();
3629 // @section Other Methods
3631 // @method whenReady(fn: Function, context?: Object): this
3632 // Runs the given function `fn` when the map gets initialized with
3633 // a view (center and zoom) and at least one layer, or immediately
3634 // if it's already initialized, optionally passing a function context.
3635 whenReady: function (callback, context) {
3637 callback.call(context || this, {target: this});
3639 this.on('load', callback, context);
3645 // private methods for getting map state
3647 _getMapPanePos: function () {
3648 return L.DomUtil.getPosition(this._mapPane) || new L.Point(0, 0);
3651 _moved: function () {
3652 var pos = this._getMapPanePos();
3653 return pos && !pos.equals([0, 0]);
3656 _getTopLeftPoint: function (center, zoom) {
3657 var pixelOrigin = center && zoom !== undefined ?
3658 this._getNewPixelOrigin(center, zoom) :
3659 this.getPixelOrigin();
3660 return pixelOrigin.subtract(this._getMapPanePos());
3663 _getNewPixelOrigin: function (center, zoom) {
3664 var viewHalf = this.getSize()._divideBy(2);
3665 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
3668 _latLngToNewLayerPoint: function (latlng, zoom, center) {
3669 var topLeft = this._getNewPixelOrigin(center, zoom);
3670 return this.project(latlng, zoom)._subtract(topLeft);
3673 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
3674 var topLeft = this._getNewPixelOrigin(center, zoom);
3676 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
3677 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
3678 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
3679 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
3683 // layer point of the current center
3684 _getCenterLayerPoint: function () {
3685 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
3688 // offset of the specified place to the current center in pixels
3689 _getCenterOffset: function (latlng) {
3690 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
3693 // adjust center for view to get inside bounds
3694 _limitCenter: function (center, zoom, bounds) {
3696 if (!bounds) { return center; }
3698 var centerPoint = this.project(center, zoom),
3699 viewHalf = this.getSize().divideBy(2),
3700 viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
3701 offset = this._getBoundsOffset(viewBounds, bounds, zoom);
3703 // If offset is less than a pixel, ignore.
3704 // This prevents unstable projections from getting into
3705 // an infinite loop of tiny offsets.
3706 if (offset.round().equals([0, 0])) {
3710 return this.unproject(centerPoint.add(offset), zoom);
3713 // adjust offset for view to get inside bounds
3714 _limitOffset: function (offset, bounds) {
3715 if (!bounds) { return offset; }
3717 var viewBounds = this.getPixelBounds(),
3718 newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
3720 return offset.add(this._getBoundsOffset(newBounds, bounds));
3723 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
3724 _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
3725 var projectedMaxBounds = L.bounds(
3726 this.project(maxBounds.getNorthEast(), zoom),
3727 this.project(maxBounds.getSouthWest(), zoom)
3729 minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
3730 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
3732 dx = this._rebound(minOffset.x, -maxOffset.x),
3733 dy = this._rebound(minOffset.y, -maxOffset.y);
3735 return new L.Point(dx, dy);
3738 _rebound: function (left, right) {
3739 return left + right > 0 ?
3740 Math.round(left - right) / 2 :
3741 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
3744 _limitZoom: function (zoom) {
3745 var min = this.getMinZoom(),
3746 max = this.getMaxZoom(),
3747 snap = L.Browser.any3d ? this.options.zoomSnap : 1;
3749 zoom = Math.round(zoom / snap) * snap;
3751 return Math.max(min, Math.min(max, zoom));
3754 _onPanTransitionStep: function () {
3758 _onPanTransitionEnd: function () {
3759 L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
3760 this.fire('moveend');
3763 _tryAnimatedPan: function (center, options) {
3764 // difference between the new and current centers in pixels
3765 var offset = this._getCenterOffset(center)._floor();
3767 // don't animate too far unless animate: true specified in options
3768 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
3770 this.panBy(offset, options);
3775 _createAnimProxy: function () {
3777 var proxy = this._proxy = L.DomUtil.create('div', 'leaflet-proxy leaflet-zoom-animated');
3778 this._panes.mapPane.appendChild(proxy);
3780 this.on('zoomanim', function (e) {
3781 var prop = L.DomUtil.TRANSFORM,
3782 transform = proxy.style[prop];
3784 L.DomUtil.setTransform(proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
3786 // workaround for case when transform is the same and so transitionend event is not fired
3787 if (transform === proxy.style[prop] && this._animatingZoom) {
3788 this._onZoomTransitionEnd();
3792 this.on('load moveend', function () {
3793 var c = this.getCenter(),
3795 L.DomUtil.setTransform(proxy, this.project(c, z), this.getZoomScale(z, 1));
3799 _catchTransitionEnd: function (e) {
3800 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
3801 this._onZoomTransitionEnd();
3805 _nothingToAnimate: function () {
3806 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
3809 _tryAnimatedZoom: function (center, zoom, options) {
3811 if (this._animatingZoom) { return true; }
3813 options = options || {};
3815 // don't animate if disabled, not supported or zoom difference is too large
3816 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
3817 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
3819 // offset is the pixel coords of the zoom origin relative to the current center
3820 var scale = this.getZoomScale(zoom),
3821 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
3823 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
3824 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
3826 L.Util.requestAnimFrame(function () {
3829 ._animateZoom(center, zoom, true);
3835 _animateZoom: function (center, zoom, startAnim, noUpdate) {
3837 this._animatingZoom = true;
3839 // remember what center/zoom to set after animation
3840 this._animateToCenter = center;
3841 this._animateToZoom = zoom;
3843 L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
3846 // @event zoomanim: ZoomAnimEvent
3847 // Fired on every frame of a zoom animation
3848 this.fire('zoomanim', {
3854 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
3855 setTimeout(L.bind(this._onZoomTransitionEnd, this), 250);
3858 _onZoomTransitionEnd: function () {
3859 if (!this._animatingZoom) { return; }
3861 L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
3863 this._animatingZoom = false;
3865 this._move(this._animateToCenter, this._animateToZoom);
3867 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
3868 L.Util.requestAnimFrame(function () {
3869 this._moveEnd(true);
3876 // @factory L.map(id: String, options?: Map options)
3877 // Instantiates a map object given the DOM ID of a `<div>` element
3878 // and optionally an object literal with `Map options`.
3881 // @factory L.map(el: HTMLElement, options?: Map options)
3882 // Instantiates a map object given an instance of a `<div>` HTML element
3883 // and optionally an object literal with `Map options`.
3884 L.map = function (id, options) {
3885 return new L.Map(id, options);
3897 * A set of methods from the Layer base class that all Leaflet layers use.
3898 * Inherits all methods, options and events from `L.Evented`.
3903 * var layer = L.Marker(latlng).addTo(map);
3909 * Fired after the layer is added to a map
3911 * @event remove: Event
3912 * Fired after the layer is removed from a map
3916 L.Layer = L.Evented.extend({
3918 // Classes extending `L.Layer` will inherit the following options:
3920 // @option pane: String = 'overlayPane'
3921 // 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.
3922 pane: 'overlayPane',
3923 nonBubblingEvents: [], // Array of events that should not be bubbled to DOM parents (like the map),
3925 // @option attribution: String = null
3926 // String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox".
3931 * Classes extending `L.Layer` will inherit the following methods:
3933 * @method addTo(map: Map): this
3934 * Adds the layer to the given map
3936 addTo: function (map) {
3941 // @method remove: this
3942 // Removes the layer from the map it is currently active on.
3943 remove: function () {
3944 return this.removeFrom(this._map || this._mapToAdd);
3947 // @method removeFrom(map: Map): this
3948 // Removes the layer from the given map
3949 removeFrom: function (obj) {
3951 obj.removeLayer(this);
3956 // @method getPane(name? : String): HTMLElement
3957 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
3958 getPane: function (name) {
3959 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
3962 addInteractiveTarget: function (targetEl) {
3963 this._map._targets[L.stamp(targetEl)] = this;
3967 removeInteractiveTarget: function (targetEl) {
3968 delete this._map._targets[L.stamp(targetEl)];
3972 // @method getAttribution: String
3973 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
3974 getAttribution: function () {
3975 return this.options.attribution;
3978 _layerAdd: function (e) {
3981 // check in case layer gets added and then removed before the map is ready
3982 if (!map.hasLayer(this)) { return; }
3985 this._zoomAnimated = map._zoomAnimated;
3987 if (this.getEvents) {
3988 var events = this.getEvents();
3989 map.on(events, this);
3990 this.once('remove', function () {
3991 map.off(events, this);
3997 if (this.getAttribution && map.attributionControl) {
3998 map.attributionControl.addAttribution(this.getAttribution());
4002 map.fire('layeradd', {layer: this});
4006 /* @section Extension methods
4009 * Every layer should extend from `L.Layer` and (re-)implement the following methods.
4011 * @method onAdd(map: Map): this
4012 * 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).
4014 * @method onRemove(map: Map): this
4015 * 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).
4017 * @method getEvents(): Object
4018 * 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.
4020 * @method getAttribution(): String
4021 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
4023 * @method beforeAdd(map: Map): this
4024 * 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.
4029 * @section Layer events
4031 * @event layeradd: LayerEvent
4032 * Fired when a new layer is added to the map.
4034 * @event layerremove: LayerEvent
4035 * Fired when some layer is removed from the map
4037 * @section Methods for Layers and Controls
4040 // @method addLayer(layer: Layer): this
4041 // Adds the given layer to the map
4042 addLayer: function (layer) {
4043 var id = L.stamp(layer);
4044 if (this._layers[id]) { return this; }
4045 this._layers[id] = layer;
4047 layer._mapToAdd = this;
4049 if (layer.beforeAdd) {
4050 layer.beforeAdd(this);
4053 this.whenReady(layer._layerAdd, layer);
4058 // @method removeLayer(layer: Layer): this
4059 // Removes the given layer from the map.
4060 removeLayer: function (layer) {
4061 var id = L.stamp(layer);
4063 if (!this._layers[id]) { return this; }
4066 layer.onRemove(this);
4069 if (layer.getAttribution && this.attributionControl) {
4070 this.attributionControl.removeAttribution(layer.getAttribution());
4073 delete this._layers[id];
4076 this.fire('layerremove', {layer: layer});
4077 layer.fire('remove');
4080 layer._map = layer._mapToAdd = null;
4085 // @method hasLayer(layer: Layer): Boolean
4086 // Returns `true` if the given layer is currently added to the map
4087 hasLayer: function (layer) {
4088 return !!layer && (L.stamp(layer) in this._layers);
4091 /* @method eachLayer(fn: Function, context?: Object): this
4092 * Iterates over the layers of the map, optionally specifying context of the iterator function.
4094 * map.eachLayer(function(layer){
4095 * layer.bindPopup('Hello');
4099 eachLayer: function (method, context) {
4100 for (var i in this._layers) {
4101 method.call(context, this._layers[i]);
4106 _addLayers: function (layers) {
4107 layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];
4109 for (var i = 0, len = layers.length; i < len; i++) {
4110 this.addLayer(layers[i]);
4114 _addZoomLimit: function (layer) {
4115 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
4116 this._zoomBoundLayers[L.stamp(layer)] = layer;
4117 this._updateZoomLevels();
4121 _removeZoomLimit: function (layer) {
4122 var id = L.stamp(layer);
4124 if (this._zoomBoundLayers[id]) {
4125 delete this._zoomBoundLayers[id];
4126 this._updateZoomLevels();
4130 _updateZoomLevels: function () {
4131 var minZoom = Infinity,
4132 maxZoom = -Infinity,
4133 oldZoomSpan = this._getZoomSpan();
4135 for (var i in this._zoomBoundLayers) {
4136 var options = this._zoomBoundLayers[i].options;
4138 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
4139 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
4142 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
4143 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
4145 // @section Map state change events
4146 // @event zoomlevelschange: Event
4147 // Fired when the number of zoomlevels on the map is changed due
4148 // to adding or removing a layer.
4149 if (oldZoomSpan !== this._getZoomSpan()) {
4150 this.fire('zoomlevelschange');
4153 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
4154 this.setZoom(this._layersMaxZoom);
4156 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
4157 this.setZoom(this._layersMinZoom);
4165 * @namespace DomEvent
4166 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
4169 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
4173 var eventsKey = '_leaflet_events';
4177 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
4178 // Adds a listener function (`fn`) to a particular DOM event type of the
4179 // element `el`. You can optionally specify the context of the listener
4180 // (object the `this` keyword will point to). You can also pass several
4181 // space-separated types (e.g. `'click dblclick'`).
4184 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
4185 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
4186 on: function (obj, types, fn, context) {
4188 if (typeof types === 'object') {
4189 for (var type in types) {
4190 this._on(obj, type, types[type], fn);
4193 types = L.Util.splitWords(types);
4195 for (var i = 0, len = types.length; i < len; i++) {
4196 this._on(obj, types[i], fn, context);
4203 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
4204 // Removes a previously added listener function. If no function is specified,
4205 // it will remove all the listeners of that particular DOM event from the element.
4206 // Note that if you passed a custom context to on, you must pass the same
4207 // context to `off` in order to remove the listener.
4210 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
4211 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
4212 off: function (obj, types, fn, context) {
4214 if (typeof types === 'object') {
4215 for (var type in types) {
4216 this._off(obj, type, types[type], fn);
4219 types = L.Util.splitWords(types);
4221 for (var i = 0, len = types.length; i < len; i++) {
4222 this._off(obj, types[i], fn, context);
4229 _on: function (obj, type, fn, context) {
4230 var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : '');
4232 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
4234 var handler = function (e) {
4235 return fn.call(context || obj, e || window.event);
4238 var originalHandler = handler;
4240 if (L.Browser.pointer && type.indexOf('touch') === 0) {
4241 this.addPointerListener(obj, type, handler, id);
4243 } else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener &&
4244 !(L.Browser.pointer && L.Browser.chrome)) {
4245 // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
4247 this.addDoubleTapListener(obj, handler, id);
4249 } else if ('addEventListener' in obj) {
4251 if (type === 'mousewheel') {
4252 obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
4254 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
4255 handler = function (e) {
4256 e = e || window.event;
4257 if (L.DomEvent._isExternalTarget(obj, e)) {
4261 obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
4264 if (type === 'click' && L.Browser.android) {
4265 handler = function (e) {
4266 return L.DomEvent._filterClick(e, originalHandler);
4269 obj.addEventListener(type, handler, false);
4272 } else if ('attachEvent' in obj) {
4273 obj.attachEvent('on' + type, handler);
4276 obj[eventsKey] = obj[eventsKey] || {};
4277 obj[eventsKey][id] = handler;
4282 _off: function (obj, type, fn, context) {
4284 var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : ''),
4285 handler = obj[eventsKey] && obj[eventsKey][id];
4287 if (!handler) { return this; }
4289 if (L.Browser.pointer && type.indexOf('touch') === 0) {
4290 this.removePointerListener(obj, type, id);
4292 } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
4293 this.removeDoubleTapListener(obj, id);
4295 } else if ('removeEventListener' in obj) {
4297 if (type === 'mousewheel') {
4298 obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
4301 obj.removeEventListener(
4302 type === 'mouseenter' ? 'mouseover' :
4303 type === 'mouseleave' ? 'mouseout' : type, handler, false);
4306 } else if ('detachEvent' in obj) {
4307 obj.detachEvent('on' + type, handler);
4310 obj[eventsKey][id] = null;
4315 // @function stopPropagation(ev: DOMEvent): this
4316 // Stop the given event from propagation to parent elements. Used inside the listener functions:
4318 // L.DomEvent.on(div, 'click', function (ev) {
4319 // L.DomEvent.stopPropagation(ev);
4322 stopPropagation: function (e) {
4324 if (e.stopPropagation) {
4325 e.stopPropagation();
4326 } else if (e.originalEvent) { // In case of Leaflet event.
4327 e.originalEvent._stopped = true;
4329 e.cancelBubble = true;
4331 L.DomEvent._skipped(e);
4336 // @function disableScrollPropagation(el: HTMLElement): this
4337 // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
4338 disableScrollPropagation: function (el) {
4339 return L.DomEvent.on(el, 'mousewheel', L.DomEvent.stopPropagation);
4342 // @function disableClickPropagation(el: HTMLElement): this
4343 // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
4344 // `'mousedown'` and `'touchstart'` events (plus browser variants).
4345 disableClickPropagation: function (el) {
4346 var stop = L.DomEvent.stopPropagation;
4348 L.DomEvent.on(el, L.Draggable.START.join(' '), stop);
4350 return L.DomEvent.on(el, {
4351 click: L.DomEvent._fakeStop,
4356 // @function preventDefault(ev: DOMEvent): this
4357 // Prevents the default action of the DOM Event `ev` from happening (such as
4358 // following a link in the href of the a element, or doing a POST request
4359 // with page reload when a `<form>` is submitted).
4360 // Use it inside listener functions.
4361 preventDefault: function (e) {
4363 if (e.preventDefault) {
4366 e.returnValue = false;
4371 // @function stop(ev): this
4372 // Does `stopPropagation` and `preventDefault` at the same time.
4373 stop: function (e) {
4376 .stopPropagation(e);
4379 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
4380 // Gets normalized mouse position from a DOM event relative to the
4381 // `container` or to the whole page if not specified.
4382 getMousePosition: function (e, container) {
4384 return new L.Point(e.clientX, e.clientY);
4387 var rect = container.getBoundingClientRect();
4390 e.clientX - rect.left - container.clientLeft,
4391 e.clientY - rect.top - container.clientTop);
4394 // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
4395 // and Firefox scrolls device pixels, not CSS pixels
4396 _wheelPxFactor: (L.Browser.win && L.Browser.chrome) ? 2 :
4397 L.Browser.gecko ? window.devicePixelRatio :
4400 // @function getWheelDelta(ev: DOMEvent): Number
4401 // Gets normalized wheel delta from a mousewheel DOM event, in vertical
4402 // pixels scrolled (negative if scrolling down).
4403 // Events from pointing devices without precise scrolling are mapped to
4404 // a best guess of 60 pixels.
4405 getWheelDelta: function (e) {
4406 return (L.Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
4407 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / L.DomEvent._wheelPxFactor : // Pixels
4408 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
4409 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
4410 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
4411 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
4412 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
4413 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
4419 _fakeStop: function (e) {
4420 // fakes stopPropagation by setting a special event flag, checked/reset with L.DomEvent._skipped(e)
4421 L.DomEvent._skipEvents[e.type] = true;
4424 _skipped: function (e) {
4425 var skipped = this._skipEvents[e.type];
4426 // reset when checking, as it's only used in map container and propagates outside of the map
4427 this._skipEvents[e.type] = false;
4431 // check if element really left/entered the event target (for mouseenter/mouseleave)
4432 _isExternalTarget: function (el, e) {
4434 var related = e.relatedTarget;
4436 if (!related) { return true; }
4439 while (related && (related !== el)) {
4440 related = related.parentNode;
4445 return (related !== el);
4448 // this is a horrible workaround for a bug in Android where a single touch triggers two click events
4449 _filterClick: function (e, handler) {
4450 var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
4451 elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick);
4453 // are they closer together than 500ms yet more than 100ms?
4454 // Android typically triggers them ~300ms apart while multiple listeners
4455 // on the same event should be triggered far faster;
4456 // or check if click is simulated on the element, and if it is, reject any non-simulated events
4458 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
4462 L.DomEvent._lastClick = timeStamp;
4468 // @function addListener(…): this
4469 // Alias to [`L.DomEvent.on`](#domevent-on)
4470 L.DomEvent.addListener = L.DomEvent.on;
4472 // @function removeListener(…): this
4473 // Alias to [`L.DomEvent.off`](#domevent-off)
4474 L.DomEvent.removeListener = L.DomEvent.off;
4479 * @class PosAnimation
4480 * @aka L.PosAnimation
4482 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
4486 * var fx = new L.PosAnimation();
4487 * fx.run(el, [300, 500], 0.5);
4490 * @constructor L.PosAnimation()
4491 * Creates a `PosAnimation` object.
4495 L.PosAnimation = L.Evented.extend({
4497 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
4498 // Run an animation of a given element to a new position, optionally setting
4499 // duration in seconds (`0.25` by default) and easing linearity factor (3rd
4500 // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
4501 // `0.5` by default).
4502 run: function (el, newPos, duration, easeLinearity) {
4506 this._inProgress = true;
4507 this._duration = duration || 0.25;
4508 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
4510 this._startPos = L.DomUtil.getPosition(el);
4511 this._offset = newPos.subtract(this._startPos);
4512 this._startTime = +new Date();
4514 // @event start: Event
4515 // Fired when the animation starts
4522 // Stops the animation (if currently running).
4524 if (!this._inProgress) { return; }
4530 _animate: function () {
4532 this._animId = L.Util.requestAnimFrame(this._animate, this);
4536 _step: function (round) {
4537 var elapsed = (+new Date()) - this._startTime,
4538 duration = this._duration * 1000;
4540 if (elapsed < duration) {
4541 this._runFrame(this._easeOut(elapsed / duration), round);
4548 _runFrame: function (progress, round) {
4549 var pos = this._startPos.add(this._offset.multiplyBy(progress));
4553 L.DomUtil.setPosition(this._el, pos);
4555 // @event step: Event
4556 // Fired continuously during the animation.
4560 _complete: function () {
4561 L.Util.cancelAnimFrame(this._animId);
4563 this._inProgress = false;
4564 // @event end: Event
4565 // Fired when the animation ends.
4569 _easeOut: function (t) {
4570 return 1 - Math.pow(1 - t, this._easeOutPower);
4577 * @namespace Projection
4578 * @projection L.Projection.Mercator
4580 * 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.
4583 L.Projection.Mercator = {
4585 R_MINOR: 6356752.314245179,
4587 bounds: L.bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
4589 project: function (latlng) {
4590 var d = Math.PI / 180,
4593 tmp = this.R_MINOR / r,
4594 e = Math.sqrt(1 - tmp * tmp),
4595 con = e * Math.sin(y);
4597 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
4598 y = -r * Math.log(Math.max(ts, 1E-10));
4600 return new L.Point(latlng.lng * d * r, y);
4603 unproject: function (point) {
4604 var d = 180 / Math.PI,
4606 tmp = this.R_MINOR / r,
4607 e = Math.sqrt(1 - tmp * tmp),
4608 ts = Math.exp(-point.y / r),
4609 phi = Math.PI / 2 - 2 * Math.atan(ts);
4611 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
4612 con = e * Math.sin(phi);
4613 con = Math.pow((1 - con) / (1 + con), e / 2);
4614 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
4618 return new L.LatLng(phi * d, point.x * d / r);
4626 * @crs L.CRS.EPSG3395
4628 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
4631 L.CRS.EPSG3395 = L.extend({}, L.CRS.Earth, {
4633 projection: L.Projection.Mercator,
4635 transformation: (function () {
4636 var scale = 0.5 / (Math.PI * L.Projection.Mercator.R);
4637 return new L.Transformation(scale, 0.5, -scale, 0.5);
4648 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
4649 * 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.
4652 * @section Synchronous usage
4655 * 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.
4658 * var CanvasLayer = L.GridLayer.extend({
4659 * createTile: function(coords){
4660 * // create a <canvas> element for drawing
4661 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
4663 * // setup tile width and height according to the options
4664 * var size = this.getTileSize();
4665 * tile.width = size.x;
4666 * tile.height = size.y;
4668 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z
4669 * var ctx = tile.getContext('2d');
4671 * // return the tile so it can be rendered on screen
4677 * @section Asynchronous usage
4680 * 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.
4683 * var CanvasLayer = L.GridLayer.extend({
4684 * createTile: function(coords, done){
4687 * // create a <canvas> element for drawing
4688 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
4690 * // setup tile width and height according to the options
4691 * var size = this.getTileSize();
4692 * tile.width = size.x;
4693 * tile.height = size.y;
4695 * // draw something asynchronously and pass the tile to the done() callback
4696 * setTimeout(function() {
4697 * done(error, tile);
4709 L.GridLayer = L.Layer.extend({
4712 // @aka GridLayer options
4714 // @option tileSize: Number|Point = 256
4715 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
4718 // @option opacity: Number = 1.0
4719 // Opacity of the tiles. Can be used in the `createTile()` function.
4722 // @option updateWhenIdle: Boolean = depends
4723 // If `false`, new tiles are loaded during panning, otherwise only after it (for better performance). `true` by default on mobile browsers, otherwise `false`.
4724 updateWhenIdle: L.Browser.mobile,
4726 // @option updateWhenZooming: Boolean = true
4727 // 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.
4728 updateWhenZooming: true,
4730 // @option updateInterval: Number = 200
4731 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
4732 updateInterval: 200,
4734 // @option zIndex: Number = 1
4735 // The explicit zIndex of the tile layer.
4738 // @option bounds: LatLngBounds = undefined
4739 // If set, tiles will only be loaded inside the set `LatLngBounds`.
4742 // @option minZoom: Number = 0
4743 // The minimum zoom level that tiles will be loaded at. By default the entire map.
4746 // @option maxZoom: Number = undefined
4747 // The maximum zoom level that tiles will be loaded at.
4750 // @option noWrap: Boolean = false
4751 // Whether the layer is wrapped around the antimeridian. If `true`, the
4752 // GridLayer will only be displayed once at low zoom levels. Has no
4753 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
4754 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
4755 // tiles outside the CRS limits.
4758 // @option pane: String = 'tilePane'
4759 // `Map pane` where the grid layer will be added.
4762 // @option className: String = ''
4763 // A custom class name to assign to the tile layer. Empty by default.
4766 // @option keepBuffer: Number = 2
4767 // When panning the map, keep this many rows and columns of tiles before unloading them.
4771 initialize: function (options) {
4772 L.setOptions(this, options);
4775 onAdd: function () {
4776 this._initContainer();
4785 beforeAdd: function (map) {
4786 map._addZoomLimit(this);
4789 onRemove: function (map) {
4790 this._removeAllTiles();
4791 L.DomUtil.remove(this._container);
4792 map._removeZoomLimit(this);
4793 this._container = null;
4794 this._tileZoom = null;
4797 // @method bringToFront: this
4798 // Brings the tile layer to the top of all tile layers.
4799 bringToFront: function () {
4801 L.DomUtil.toFront(this._container);
4802 this._setAutoZIndex(Math.max);
4807 // @method bringToBack: this
4808 // Brings the tile layer to the bottom of all tile layers.
4809 bringToBack: function () {
4811 L.DomUtil.toBack(this._container);
4812 this._setAutoZIndex(Math.min);
4817 // @method getContainer: HTMLElement
4818 // Returns the HTML element that contains the tiles for this layer.
4819 getContainer: function () {
4820 return this._container;
4823 // @method setOpacity(opacity: Number): this
4824 // Changes the [opacity](#gridlayer-opacity) of the grid layer.
4825 setOpacity: function (opacity) {
4826 this.options.opacity = opacity;
4827 this._updateOpacity();
4831 // @method setZIndex(zIndex: Number): this
4832 // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
4833 setZIndex: function (zIndex) {
4834 this.options.zIndex = zIndex;
4835 this._updateZIndex();
4840 // @method isLoading: Boolean
4841 // Returns `true` if any tile in the grid layer has not finished loading.
4842 isLoading: function () {
4843 return this._loading;
4846 // @method redraw: this
4847 // Causes the layer to clear all the tiles and request them again.
4848 redraw: function () {
4850 this._removeAllTiles();
4856 getEvents: function () {
4858 viewprereset: this._invalidateAll,
4859 viewreset: this._resetView,
4860 zoom: this._resetView,
4861 moveend: this._onMoveEnd
4864 if (!this.options.updateWhenIdle) {
4865 // update tiles on move, but not more often than once per given interval
4866 if (!this._onMove) {
4867 this._onMove = L.Util.throttle(this._onMoveEnd, this.options.updateInterval, this);
4870 events.move = this._onMove;
4873 if (this._zoomAnimated) {
4874 events.zoomanim = this._animateZoom;
4880 // @section Extension methods
4881 // Layers extending `GridLayer` shall reimplement the following method.
4882 // @method createTile(coords: Object, done?: Function): HTMLElement
4883 // Called only internally, must be overriden by classes extending `GridLayer`.
4884 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
4885 // is specified, it must be called when the tile has finished loading and drawing.
4886 createTile: function () {
4887 return document.createElement('div');
4891 // @method getTileSize: Point
4892 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
4893 getTileSize: function () {
4894 var s = this.options.tileSize;
4895 return s instanceof L.Point ? s : new L.Point(s, s);
4898 _updateZIndex: function () {
4899 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
4900 this._container.style.zIndex = this.options.zIndex;
4904 _setAutoZIndex: function (compare) {
4905 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
4907 var layers = this.getPane().children,
4908 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
4910 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
4912 zIndex = layers[i].style.zIndex;
4914 if (layers[i] !== this._container && zIndex) {
4915 edgeZIndex = compare(edgeZIndex, +zIndex);
4919 if (isFinite(edgeZIndex)) {
4920 this.options.zIndex = edgeZIndex + compare(-1, 1);
4921 this._updateZIndex();
4925 _updateOpacity: function () {
4926 if (!this._map) { return; }
4928 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
4929 if (L.Browser.ielt9) { return; }
4931 L.DomUtil.setOpacity(this._container, this.options.opacity);
4933 var now = +new Date(),
4937 for (var key in this._tiles) {
4938 var tile = this._tiles[key];
4939 if (!tile.current || !tile.loaded) { continue; }
4941 var fade = Math.min(1, (now - tile.loaded) / 200);
4943 L.DomUtil.setOpacity(tile.el, fade);
4947 if (tile.active) { willPrune = true; }
4952 if (willPrune && !this._noPrune) { this._pruneTiles(); }
4955 L.Util.cancelAnimFrame(this._fadeFrame);
4956 this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this);
4960 _initContainer: function () {
4961 if (this._container) { return; }
4963 this._container = L.DomUtil.create('div', 'leaflet-layer ' + (this.options.className || ''));
4964 this._updateZIndex();
4966 if (this.options.opacity < 1) {
4967 this._updateOpacity();
4970 this.getPane().appendChild(this._container);
4973 _updateLevels: function () {
4975 var zoom = this._tileZoom,
4976 maxZoom = this.options.maxZoom;
4978 if (zoom === undefined) { return undefined; }
4980 for (var z in this._levels) {
4981 if (this._levels[z].el.children.length || z === zoom) {
4982 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
4984 L.DomUtil.remove(this._levels[z].el);
4985 this._removeTilesAtZoom(z);
4986 delete this._levels[z];
4990 var level = this._levels[zoom],
4994 level = this._levels[zoom] = {};
4996 level.el = L.DomUtil.create('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
4997 level.el.style.zIndex = maxZoom;
4999 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
5002 this._setZoomTransform(level, map.getCenter(), map.getZoom());
5004 // force the browser to consider the newly added element for transition
5005 L.Util.falseFn(level.el.offsetWidth);
5008 this._level = level;
5013 _pruneTiles: function () {
5020 var zoom = this._map.getZoom();
5021 if (zoom > this.options.maxZoom ||
5022 zoom < this.options.minZoom) {
5023 this._removeAllTiles();
5027 for (key in this._tiles) {
5028 tile = this._tiles[key];
5029 tile.retain = tile.current;
5032 for (key in this._tiles) {
5033 tile = this._tiles[key];
5034 if (tile.current && !tile.active) {
5035 var coords = tile.coords;
5036 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
5037 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
5042 for (key in this._tiles) {
5043 if (!this._tiles[key].retain) {
5044 this._removeTile(key);
5049 _removeTilesAtZoom: function (zoom) {
5050 for (var key in this._tiles) {
5051 if (this._tiles[key].coords.z !== zoom) {
5054 this._removeTile(key);
5058 _removeAllTiles: function () {
5059 for (var key in this._tiles) {
5060 this._removeTile(key);
5064 _invalidateAll: function () {
5065 for (var z in this._levels) {
5066 L.DomUtil.remove(this._levels[z].el);
5067 delete this._levels[z];
5069 this._removeAllTiles();
5071 this._tileZoom = null;
5074 _retainParent: function (x, y, z, minZoom) {
5075 var x2 = Math.floor(x / 2),
5076 y2 = Math.floor(y / 2),
5078 coords2 = new L.Point(+x2, +y2);
5081 var key = this._tileCoordsToKey(coords2),
5082 tile = this._tiles[key];
5084 if (tile && tile.active) {
5088 } else if (tile && tile.loaded) {
5093 return this._retainParent(x2, y2, z2, minZoom);
5099 _retainChildren: function (x, y, z, maxZoom) {
5101 for (var i = 2 * x; i < 2 * x + 2; i++) {
5102 for (var j = 2 * y; j < 2 * y + 2; j++) {
5104 var coords = new L.Point(i, j);
5107 var key = this._tileCoordsToKey(coords),
5108 tile = this._tiles[key];
5110 if (tile && tile.active) {
5114 } else if (tile && tile.loaded) {
5118 if (z + 1 < maxZoom) {
5119 this._retainChildren(i, j, z + 1, maxZoom);
5125 _resetView: function (e) {
5126 var animating = e && (e.pinch || e.flyTo);
5127 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
5130 _animateZoom: function (e) {
5131 this._setView(e.center, e.zoom, true, e.noUpdate);
5134 _setView: function (center, zoom, noPrune, noUpdate) {
5135 var tileZoom = Math.round(zoom);
5136 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
5137 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
5138 tileZoom = undefined;
5141 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
5143 if (!noUpdate || tileZoomChanged) {
5145 this._tileZoom = tileZoom;
5147 if (this._abortLoading) {
5148 this._abortLoading();
5151 this._updateLevels();
5154 if (tileZoom !== undefined) {
5155 this._update(center);
5162 // Flag to prevent _updateOpacity from pruning tiles during
5163 // a zoom anim or a pinch gesture
5164 this._noPrune = !!noPrune;
5167 this._setZoomTransforms(center, zoom);
5170 _setZoomTransforms: function (center, zoom) {
5171 for (var i in this._levels) {
5172 this._setZoomTransform(this._levels[i], center, zoom);
5176 _setZoomTransform: function (level, center, zoom) {
5177 var scale = this._map.getZoomScale(zoom, level.zoom),
5178 translate = level.origin.multiplyBy(scale)
5179 .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
5181 if (L.Browser.any3d) {
5182 L.DomUtil.setTransform(level.el, translate, scale);
5184 L.DomUtil.setPosition(level.el, translate);
5188 _resetGrid: function () {
5189 var map = this._map,
5190 crs = map.options.crs,
5191 tileSize = this._tileSize = this.getTileSize(),
5192 tileZoom = this._tileZoom;
5194 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
5196 this._globalTileRange = this._pxBoundsToTileRange(bounds);
5199 this._wrapX = crs.wrapLng && !this.options.noWrap && [
5200 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
5201 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
5203 this._wrapY = crs.wrapLat && !this.options.noWrap && [
5204 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
5205 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
5209 _onMoveEnd: function () {
5210 if (!this._map || this._map._animatingZoom) { return; }
5215 _getTiledPixelBounds: function (center) {
5216 var map = this._map,
5217 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
5218 scale = map.getZoomScale(mapZoom, this._tileZoom),
5219 pixelCenter = map.project(center, this._tileZoom).floor(),
5220 halfSize = map.getSize().divideBy(scale * 2);
5222 return new L.Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
5225 // Private method to load tiles in the grid's active zoom level according to map bounds
5226 _update: function (center) {
5227 var map = this._map;
5228 if (!map) { return; }
5229 var zoom = map.getZoom();
5231 if (center === undefined) { center = map.getCenter(); }
5232 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom
5234 var pixelBounds = this._getTiledPixelBounds(center),
5235 tileRange = this._pxBoundsToTileRange(pixelBounds),
5236 tileCenter = tileRange.getCenter(),
5238 margin = this.options.keepBuffer,
5239 noPruneRange = new L.Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
5240 tileRange.getTopRight().add([margin, -margin]));
5242 for (var key in this._tiles) {
5243 var c = this._tiles[key].coords;
5244 if (c.z !== this._tileZoom || !noPruneRange.contains(L.point(c.x, c.y))) {
5245 this._tiles[key].current = false;
5249 // _update just loads more tiles. If the tile zoom level differs too much
5250 // from the map's, let _setView reset levels and prune old tiles.
5251 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
5253 // create a queue of coordinates to load tiles from
5254 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
5255 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
5256 var coords = new L.Point(i, j);
5257 coords.z = this._tileZoom;
5259 if (!this._isValidTile(coords)) { continue; }
5261 var tile = this._tiles[this._tileCoordsToKey(coords)];
5263 tile.current = true;
5270 // sort tile queue to load tiles in order of their distance to center
5271 queue.sort(function (a, b) {
5272 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
5275 if (queue.length !== 0) {
5276 // if it's the first batch of tiles to load
5277 if (!this._loading) {
5278 this._loading = true;
5279 // @event loading: Event
5280 // Fired when the grid layer starts loading tiles.
5281 this.fire('loading');
5284 // create DOM fragment to append tiles in one batch
5285 var fragment = document.createDocumentFragment();
5287 for (i = 0; i < queue.length; i++) {
5288 this._addTile(queue[i], fragment);
5291 this._level.el.appendChild(fragment);
5295 _isValidTile: function (coords) {
5296 var crs = this._map.options.crs;
5298 if (!crs.infinite) {
5299 // don't load tile if it's out of bounds and not wrapped
5300 var bounds = this._globalTileRange;
5301 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
5302 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
5305 if (!this.options.bounds) { return true; }
5307 // don't load tile if it doesn't intersect the bounds in options
5308 var tileBounds = this._tileCoordsToBounds(coords);
5309 return L.latLngBounds(this.options.bounds).overlaps(tileBounds);
5312 _keyToBounds: function (key) {
5313 return this._tileCoordsToBounds(this._keyToTileCoords(key));
5316 // converts tile coordinates to its geographical bounds
5317 _tileCoordsToBounds: function (coords) {
5319 var map = this._map,
5320 tileSize = this.getTileSize(),
5322 nwPoint = coords.scaleBy(tileSize),
5323 sePoint = nwPoint.add(tileSize),
5325 nw = map.unproject(nwPoint, coords.z),
5326 se = map.unproject(sePoint, coords.z),
5327 bounds = new L.LatLngBounds(nw, se);
5329 if (!this.options.noWrap) {
5330 map.wrapLatLngBounds(bounds);
5336 // converts tile coordinates to key for the tile cache
5337 _tileCoordsToKey: function (coords) {
5338 return coords.x + ':' + coords.y + ':' + coords.z;
5341 // converts tile cache key to coordinates
5342 _keyToTileCoords: function (key) {
5343 var k = key.split(':'),
5344 coords = new L.Point(+k[0], +k[1]);
5349 _removeTile: function (key) {
5350 var tile = this._tiles[key];
5351 if (!tile) { return; }
5353 L.DomUtil.remove(tile.el);
5355 delete this._tiles[key];
5357 // @event tileunload: TileEvent
5358 // Fired when a tile is removed (e.g. when a tile goes off the screen).
5359 this.fire('tileunload', {
5361 coords: this._keyToTileCoords(key)
5365 _initTile: function (tile) {
5366 L.DomUtil.addClass(tile, 'leaflet-tile');
5368 var tileSize = this.getTileSize();
5369 tile.style.width = tileSize.x + 'px';
5370 tile.style.height = tileSize.y + 'px';
5372 tile.onselectstart = L.Util.falseFn;
5373 tile.onmousemove = L.Util.falseFn;
5375 // update opacity on tiles in IE7-8 because of filter inheritance problems
5376 if (L.Browser.ielt9 && this.options.opacity < 1) {
5377 L.DomUtil.setOpacity(tile, this.options.opacity);
5380 // without this hack, tiles disappear after zoom on Chrome for Android
5381 // https://github.com/Leaflet/Leaflet/issues/2078
5382 if (L.Browser.android && !L.Browser.android23) {
5383 tile.style.WebkitBackfaceVisibility = 'hidden';
5387 _addTile: function (coords, container) {
5388 var tilePos = this._getTilePos(coords),
5389 key = this._tileCoordsToKey(coords);
5391 var tile = this.createTile(this._wrapCoords(coords), L.bind(this._tileReady, this, coords));
5393 this._initTile(tile);
5395 // if createTile is defined with a second argument ("done" callback),
5396 // we know that tile is async and will be ready later; otherwise
5397 if (this.createTile.length < 2) {
5398 // mark tile as ready, but delay one frame for opacity animation to happen
5399 L.Util.requestAnimFrame(L.bind(this._tileReady, this, coords, null, tile));
5402 L.DomUtil.setPosition(tile, tilePos);
5404 // save tile in cache
5405 this._tiles[key] = {
5411 container.appendChild(tile);
5412 // @event tileloadstart: TileEvent
5413 // Fired when a tile is requested and starts loading.
5414 this.fire('tileloadstart', {
5420 _tileReady: function (coords, err, tile) {
5421 if (!this._map) { return; }
5424 // @event tileerror: TileErrorEvent
5425 // Fired when there is an error loading a tile.
5426 this.fire('tileerror', {
5433 var key = this._tileCoordsToKey(coords);
5435 tile = this._tiles[key];
5436 if (!tile) { return; }
5438 tile.loaded = +new Date();
5439 if (this._map._fadeAnimated) {
5440 L.DomUtil.setOpacity(tile.el, 0);
5441 L.Util.cancelAnimFrame(this._fadeFrame);
5442 this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this);
5449 L.DomUtil.addClass(tile.el, 'leaflet-tile-loaded');
5451 // @event tileload: TileEvent
5452 // Fired when a tile loads.
5453 this.fire('tileload', {
5459 if (this._noTilesToLoad()) {
5460 this._loading = false;
5461 // @event load: Event
5462 // Fired when the grid layer loaded all visible tiles.
5465 if (L.Browser.ielt9 || !this._map._fadeAnimated) {
5466 L.Util.requestAnimFrame(this._pruneTiles, this);
5468 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
5469 // to trigger a pruning.
5470 setTimeout(L.bind(this._pruneTiles, this), 250);
5475 _getTilePos: function (coords) {
5476 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
5479 _wrapCoords: function (coords) {
5480 var newCoords = new L.Point(
5481 this._wrapX ? L.Util.wrapNum(coords.x, this._wrapX) : coords.x,
5482 this._wrapY ? L.Util.wrapNum(coords.y, this._wrapY) : coords.y);
5483 newCoords.z = coords.z;
5487 _pxBoundsToTileRange: function (bounds) {
5488 var tileSize = this.getTileSize();
5489 return new L.Bounds(
5490 bounds.min.unscaleBy(tileSize).floor(),
5491 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
5494 _noTilesToLoad: function () {
5495 for (var key in this._tiles) {
5496 if (!this._tiles[key].loaded) { return false; }
5502 // @factory L.gridLayer(options?: GridLayer options)
5503 // Creates a new instance of GridLayer with the supplied options.
5504 L.gridLayer = function (options) {
5505 return new L.GridLayer(options);
5512 * @inherits GridLayer
5514 * Used to load and display tile layers on the map. Extends `GridLayer`.
5519 * L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map);
5522 * @section URL template
5525 * A string of the following form:
5528 * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
5531 * `{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.
5533 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
5536 * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
5541 L.TileLayer = L.GridLayer.extend({
5544 // @aka TileLayer options
5546 // @option minZoom: Number = 0
5547 // Minimum zoom number.
5550 // @option maxZoom: Number = 18
5551 // Maximum zoom number.
5554 // @option maxNativeZoom: Number = null
5555 // Maximum zoom number the tile source has available. If it is specified,
5556 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
5557 // from `maxNativeZoom` level and auto-scaled.
5558 maxNativeZoom: null,
5560 // @option minNativeZoom: Number = null
5561 // Minimum zoom number the tile source has available. If it is specified,
5562 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
5563 // from `minNativeZoom` level and auto-scaled.
5564 minNativeZoom: null,
5566 // @option subdomains: String|String[] = 'abc'
5567 // 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.
5570 // @option errorTileUrl: String = ''
5571 // URL to the tile image to show in place of the tile that failed to load.
5574 // @option zoomOffset: Number = 0
5575 // The zoom number used in tile URLs will be offset with this value.
5578 // @option tms: Boolean = false
5579 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
5582 // @option zoomReverse: Boolean = false
5583 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
5586 // @option detectRetina: Boolean = false
5587 // 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.
5588 detectRetina: false,
5590 // @option crossOrigin: Boolean = false
5591 // If true, all tiles will have their crossOrigin attribute set to ''. This is needed if you want to access tile pixel data.
5595 initialize: function (url, options) {
5599 options = L.setOptions(this, options);
5601 // detecting retina displays, adjusting tileSize and zoom levels
5602 if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {
5604 options.tileSize = Math.floor(options.tileSize / 2);
5606 if (!options.zoomReverse) {
5607 options.zoomOffset++;
5610 options.zoomOffset--;
5614 options.minZoom = Math.max(0, options.minZoom);
5617 if (typeof options.subdomains === 'string') {
5618 options.subdomains = options.subdomains.split('');
5621 // for https://github.com/Leaflet/Leaflet/issues/137
5622 if (!L.Browser.android) {
5623 this.on('tileunload', this._onTileRemove);
5627 // @method setUrl(url: String, noRedraw?: Boolean): this
5628 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
5629 setUrl: function (url, noRedraw) {
5638 // @method createTile(coords: Object, done?: Function): HTMLElement
5639 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
5640 // to return an `<img>` HTML element with the appropiate image URL given `coords`. The `done`
5641 // callback is called when the tile has been loaded.
5642 createTile: function (coords, done) {
5643 var tile = document.createElement('img');
5645 L.DomEvent.on(tile, 'load', L.bind(this._tileOnLoad, this, done, tile));
5646 L.DomEvent.on(tile, 'error', L.bind(this._tileOnError, this, done, tile));
5648 if (this.options.crossOrigin) {
5649 tile.crossOrigin = '';
5653 Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
5654 http://www.w3.org/TR/WCAG20-TECHS/H67
5659 Set role="presentation" to force screen readers to ignore this
5660 https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
5662 tile.setAttribute('role', 'presentation');
5664 tile.src = this.getTileUrl(coords);
5669 // @section Extension methods
5671 // Layers extending `TileLayer` might reimplement the following method.
5672 // @method getTileUrl(coords: Object): String
5673 // Called only internally, returns the URL for a tile given its coordinates.
5674 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
5675 getTileUrl: function (coords) {
5677 r: L.Browser.retina ? '@2x' : '',
5678 s: this._getSubdomain(coords),
5681 z: this._getZoomForUrl()
5683 if (this._map && !this._map.options.crs.infinite) {
5684 var invertedY = this._globalTileRange.max.y - coords.y;
5685 if (this.options.tms) {
5686 data['y'] = invertedY;
5688 data['-y'] = invertedY;
5691 return L.Util.template(this._url, L.extend(data, this.options));
5694 _tileOnLoad: function (done, tile) {
5695 // For https://github.com/Leaflet/Leaflet/issues/3332
5696 if (L.Browser.ielt9) {
5697 setTimeout(L.bind(done, this, null, tile), 0);
5703 _tileOnError: function (done, tile, e) {
5704 var errorUrl = this.options.errorTileUrl;
5705 if (errorUrl && tile.src !== errorUrl) {
5706 tile.src = errorUrl;
5711 getTileSize: function () {
5712 var map = this._map,
5713 tileSize = L.GridLayer.prototype.getTileSize.call(this),
5714 zoom = this._tileZoom + this.options.zoomOffset,
5715 minNativeZoom = this.options.minNativeZoom,
5716 maxNativeZoom = this.options.maxNativeZoom;
5718 // decrease tile size when scaling below minNativeZoom
5719 if (minNativeZoom !== null && zoom < minNativeZoom) {
5720 return tileSize.divideBy(map.getZoomScale(minNativeZoom, zoom)).round();
5723 // increase tile size when scaling above maxNativeZoom
5724 if (maxNativeZoom !== null && zoom > maxNativeZoom) {
5725 return tileSize.divideBy(map.getZoomScale(maxNativeZoom, zoom)).round();
5731 _onTileRemove: function (e) {
5732 e.tile.onload = null;
5735 _getZoomForUrl: function () {
5736 var zoom = this._tileZoom,
5737 maxZoom = this.options.maxZoom,
5738 zoomReverse = this.options.zoomReverse,
5739 zoomOffset = this.options.zoomOffset,
5740 minNativeZoom = this.options.minNativeZoom,
5741 maxNativeZoom = this.options.maxNativeZoom;
5744 zoom = maxZoom - zoom;
5749 if (minNativeZoom !== null && zoom < minNativeZoom) {
5750 return minNativeZoom;
5753 if (maxNativeZoom !== null && zoom > maxNativeZoom) {
5754 return maxNativeZoom;
5760 _getSubdomain: function (tilePoint) {
5761 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
5762 return this.options.subdomains[index];
5765 // stops loading all tiles in the background layer
5766 _abortLoading: function () {
5768 for (i in this._tiles) {
5769 if (this._tiles[i].coords.z !== this._tileZoom) {
5770 tile = this._tiles[i].el;
5772 tile.onload = L.Util.falseFn;
5773 tile.onerror = L.Util.falseFn;
5775 if (!tile.complete) {
5776 tile.src = L.Util.emptyImageUrl;
5777 L.DomUtil.remove(tile);
5785 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
5786 // Instantiates a tile layer object given a `URL template` and optionally an options object.
5788 L.tileLayer = function (url, options) {
5789 return new L.TileLayer(url, options);
5795 * @class TileLayer.WMS
5796 * @inherits TileLayer
5797 * @aka L.TileLayer.WMS
5798 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
5803 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
5804 * layers: 'nexrad-n0r-900913',
5805 * format: 'image/png',
5806 * transparent: true,
5807 * attribution: "Weather data © 2012 IEM Nexrad"
5812 L.TileLayer.WMS = L.TileLayer.extend({
5815 // @aka TileLayer.WMS options
5816 // If any custom options not documented here are used, they will be sent to the
5817 // WMS server as extra parameters in each request URL. This can be useful for
5818 // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
5823 // @option layers: String = ''
5824 // **(required)** Comma-separated list of WMS layers to show.
5827 // @option styles: String = ''
5828 // Comma-separated list of WMS styles.
5831 // @option format: String = 'image/jpeg'
5832 // WMS image format (use `'image/png'` for layers with transparency).
5833 format: 'image/jpeg',
5835 // @option transparent: Boolean = false
5836 // If `true`, the WMS service will return images with transparency.
5839 // @option version: String = '1.1.1'
5840 // Version of the WMS service to use
5845 // @option crs: CRS = null
5846 // Coordinate Reference System to use for the WMS requests, defaults to
5847 // map CRS. Don't change this if you're not sure what it means.
5850 // @option uppercase: Boolean = false
5851 // If `true`, WMS request parameter keys will be uppercase.
5855 initialize: function (url, options) {
5859 var wmsParams = L.extend({}, this.defaultWmsParams);
5861 // all keys that are not TileLayer options go to WMS params
5862 for (var i in options) {
5863 if (!(i in this.options)) {
5864 wmsParams[i] = options[i];
5868 options = L.setOptions(this, options);
5870 wmsParams.width = wmsParams.height = options.tileSize * (options.detectRetina && L.Browser.retina ? 2 : 1);
5872 this.wmsParams = wmsParams;
5875 onAdd: function (map) {
5877 this._crs = this.options.crs || map.options.crs;
5878 this._wmsVersion = parseFloat(this.wmsParams.version);
5880 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
5881 this.wmsParams[projectionKey] = this._crs.code;
5883 L.TileLayer.prototype.onAdd.call(this, map);
5886 getTileUrl: function (coords) {
5888 var tileBounds = this._tileCoordsToBounds(coords),
5889 nw = this._crs.project(tileBounds.getNorthWest()),
5890 se = this._crs.project(tileBounds.getSouthEast()),
5892 bbox = (this._wmsVersion >= 1.3 && this._crs === L.CRS.EPSG4326 ?
5893 [se.y, nw.x, nw.y, se.x] :
5894 [nw.x, se.y, se.x, nw.y]).join(','),
5896 url = L.TileLayer.prototype.getTileUrl.call(this, coords);
5899 L.Util.getParamString(this.wmsParams, url, this.options.uppercase) +
5900 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
5903 // @method setParams(params: Object, noRedraw?: Boolean): this
5904 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
5905 setParams: function (params, noRedraw) {
5907 L.extend(this.wmsParams, params);
5918 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
5919 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
5920 L.tileLayer.wms = function (url, options) {
5921 return new L.TileLayer.WMS(url, options);
5927 * @class ImageOverlay
5928 * @aka L.ImageOverlay
5929 * @inherits Interactive layer
5931 * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
5936 * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
5937 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
5938 * L.imageOverlay(imageUrl, imageBounds).addTo(map);
5942 L.ImageOverlay = L.Layer.extend({
5945 // @aka ImageOverlay options
5947 // @option opacity: Number = 1.0
5948 // The opacity of the image overlay.
5951 // @option alt: String = ''
5952 // Text for the `alt` attribute of the image (useful for accessibility).
5955 // @option interactive: Boolean = false
5956 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
5959 // @option crossOrigin: Boolean = false
5960 // If true, the image will have its crossOrigin attribute set to ''. This is needed if you want to access image pixel data.
5964 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
5966 this._bounds = L.latLngBounds(bounds);
5968 L.setOptions(this, options);
5971 onAdd: function () {
5975 if (this.options.opacity < 1) {
5976 this._updateOpacity();
5980 if (this.options.interactive) {
5981 L.DomUtil.addClass(this._image, 'leaflet-interactive');
5982 this.addInteractiveTarget(this._image);
5985 this.getPane().appendChild(this._image);
5989 onRemove: function () {
5990 L.DomUtil.remove(this._image);
5991 if (this.options.interactive) {
5992 this.removeInteractiveTarget(this._image);
5996 // @method setOpacity(opacity: Number): this
5997 // Sets the opacity of the overlay.
5998 setOpacity: function (opacity) {
5999 this.options.opacity = opacity;
6002 this._updateOpacity();
6007 setStyle: function (styleOpts) {
6008 if (styleOpts.opacity) {
6009 this.setOpacity(styleOpts.opacity);
6014 // @method bringToFront(): this
6015 // Brings the layer to the top of all overlays.
6016 bringToFront: function () {
6018 L.DomUtil.toFront(this._image);
6023 // @method bringToBack(): this
6024 // Brings the layer to the bottom of all overlays.
6025 bringToBack: function () {
6027 L.DomUtil.toBack(this._image);
6032 // @method setUrl(url: String): this
6033 // Changes the URL of the image.
6034 setUrl: function (url) {
6038 this._image.src = url;
6043 // @method setBounds(bounds: LatLngBounds): this
6044 // Update the bounds that this ImageOverlay covers
6045 setBounds: function (bounds) {
6046 this._bounds = bounds;
6054 getEvents: function () {
6057 viewreset: this._reset
6060 if (this._zoomAnimated) {
6061 events.zoomanim = this._animateZoom;
6067 // @method getBounds(): LatLngBounds
6068 // Get the bounds that this ImageOverlay covers
6069 getBounds: function () {
6070 return this._bounds;
6073 // @method getElement(): HTMLElement
6074 // Get the img element that represents the ImageOverlay on the map
6075 getElement: function () {
6079 _initImage: function () {
6080 var img = this._image = L.DomUtil.create('img',
6081 'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : ''));
6083 img.onselectstart = L.Util.falseFn;
6084 img.onmousemove = L.Util.falseFn;
6086 img.onload = L.bind(this.fire, this, 'load');
6088 if (this.options.crossOrigin) {
6089 img.crossOrigin = '';
6092 img.src = this._url;
6093 img.alt = this.options.alt;
6096 _animateZoom: function (e) {
6097 var scale = this._map.getZoomScale(e.zoom),
6098 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
6100 L.DomUtil.setTransform(this._image, offset, scale);
6103 _reset: function () {
6104 var image = this._image,
6105 bounds = new L.Bounds(
6106 this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
6107 this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
6108 size = bounds.getSize();
6110 L.DomUtil.setPosition(image, bounds.min);
6112 image.style.width = size.x + 'px';
6113 image.style.height = size.y + 'px';
6116 _updateOpacity: function () {
6117 L.DomUtil.setOpacity(this._image, this.options.opacity);
6121 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
6122 // Instantiates an image overlay object given the URL of the image and the
6123 // geographical bounds it is tied to.
6124 L.imageOverlay = function (url, bounds, options) {
6125 return new L.ImageOverlay(url, bounds, options);
6135 * Represents an icon to provide when creating a marker.
6140 * var myIcon = L.icon({
6141 * iconUrl: 'my-icon.png',
6142 * iconRetinaUrl: 'my-icon@2x.png',
6143 * iconSize: [38, 95],
6144 * iconAnchor: [22, 94],
6145 * popupAnchor: [-3, -76],
6146 * shadowUrl: 'my-icon-shadow.png',
6147 * shadowRetinaUrl: 'my-icon-shadow@2x.png',
6148 * shadowSize: [68, 95],
6149 * shadowAnchor: [22, 94]
6152 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
6155 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
6159 L.Icon = L.Class.extend({
6164 * @option iconUrl: String = null
6165 * **(required)** The URL to the icon image (absolute or relative to your script path).
6167 * @option iconRetinaUrl: String = null
6168 * The URL to a retina sized version of the icon image (absolute or relative to your
6169 * script path). Used for Retina screen devices.
6171 * @option iconSize: Point = null
6172 * Size of the icon image in pixels.
6174 * @option iconAnchor: Point = null
6175 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
6176 * will be aligned so that this point is at the marker's geographical location. Centered
6177 * by default if size is specified, also can be set in CSS with negative margins.
6179 * @option popupAnchor: Point = null
6180 * The coordinates of the point from which popups will "open", relative to the icon anchor.
6182 * @option shadowUrl: String = null
6183 * The URL to the icon shadow image. If not specified, no shadow image will be created.
6185 * @option shadowRetinaUrl: String = null
6187 * @option shadowSize: Point = null
6188 * Size of the shadow image in pixels.
6190 * @option shadowAnchor: Point = null
6191 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
6192 * as iconAnchor if not specified).
6194 * @option className: String = ''
6195 * A custom class name to assign to both icon and shadow images. Empty by default.
6198 initialize: function (options) {
6199 L.setOptions(this, options);
6202 // @method createIcon(oldIcon?: HTMLElement): HTMLElement
6203 // Called internally when the icon has to be shown, returns a `<img>` HTML element
6204 // styled according to the options.
6205 createIcon: function (oldIcon) {
6206 return this._createIcon('icon', oldIcon);
6209 // @method createShadow(oldIcon?: HTMLElement): HTMLElement
6210 // As `createIcon`, but for the shadow beneath it.
6211 createShadow: function (oldIcon) {
6212 return this._createIcon('shadow', oldIcon);
6215 _createIcon: function (name, oldIcon) {
6216 var src = this._getIconUrl(name);
6219 if (name === 'icon') {
6220 throw new Error('iconUrl not set in Icon options (see the docs).');
6225 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
6226 this._setIconStyles(img, name);
6231 _setIconStyles: function (img, name) {
6232 var options = this.options;
6233 var sizeOption = options[name + 'Size'];
6235 if (typeof sizeOption === 'number') {
6236 sizeOption = [sizeOption, sizeOption];
6239 var size = L.point(sizeOption),
6240 anchor = L.point(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
6241 size && size.divideBy(2, true));
6243 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
6246 img.style.marginLeft = (-anchor.x) + 'px';
6247 img.style.marginTop = (-anchor.y) + 'px';
6251 img.style.width = size.x + 'px';
6252 img.style.height = size.y + 'px';
6256 _createImg: function (src, el) {
6257 el = el || document.createElement('img');
6262 _getIconUrl: function (name) {
6263 return L.Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
6268 // @factory L.icon(options: Icon options)
6269 // Creates an icon instance with the given options.
6270 L.icon = function (options) {
6271 return new L.Icon(options);
6277 * @miniclass Icon.Default (Icon)
6278 * @aka L.Icon.Default
6281 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
6282 * no icon is specified. Points to the blue marker image distributed with Leaflet
6285 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
6286 * (which is a set of `Icon options`).
6288 * If you want to _completely_ replace the default icon, override the
6289 * `L.Marker.prototype.options.icon` with your own icon instead.
6292 L.Icon.Default = L.Icon.extend({
6295 iconUrl: 'marker-icon.png',
6296 iconRetinaUrl: 'marker-icon-2x.png',
6297 shadowUrl: 'marker-shadow.png',
6299 iconAnchor: [12, 41],
6300 popupAnchor: [1, -34],
6301 tooltipAnchor: [16, -28],
6302 shadowSize: [41, 41]
6305 _getIconUrl: function (name) {
6306 if (!L.Icon.Default.imagePath) { // Deprecated, backwards-compatibility only
6307 L.Icon.Default.imagePath = this._detectIconPath();
6310 // @option imagePath: String
6311 // `L.Icon.Default` will try to auto-detect the absolute location of the
6312 // blue icon images. If you are placing these images in a non-standard
6313 // way, set this option to point to the right absolute path.
6314 return (this.options.imagePath || L.Icon.Default.imagePath) + L.Icon.prototype._getIconUrl.call(this, name);
6317 _detectIconPath: function () {
6318 var el = L.DomUtil.create('div', 'leaflet-default-icon-path', document.body);
6319 var path = L.DomUtil.getStyle(el, 'background-image') ||
6320 L.DomUtil.getStyle(el, 'backgroundImage'); // IE8
6322 document.body.removeChild(el);
6324 return path.indexOf('url') === 0 ?
6325 path.replace(/^url\([\"\']?/, '').replace(/marker-icon\.png[\"\']?\)$/, '') : '';
6333 * @inherits Interactive layer
6335 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
6340 * L.marker([50.5, 30.5]).addTo(map);
6344 L.Marker = L.Layer.extend({
6347 // @aka Marker options
6349 // @option icon: Icon = *
6350 // 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.
6351 icon: new L.Icon.Default(),
6353 // Option inherited from "Interactive layer" abstract class
6356 // @option draggable: Boolean = false
6357 // Whether the marker is draggable with mouse/touch or not.
6360 // @option keyboard: Boolean = true
6361 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
6364 // @option title: String = ''
6365 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
6368 // @option alt: String = ''
6369 // Text for the `alt` attribute of the icon image (useful for accessibility).
6372 // @option zIndexOffset: Number = 0
6373 // 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).
6376 // @option opacity: Number = 1.0
6377 // The opacity of the marker.
6380 // @option riseOnHover: Boolean = false
6381 // If `true`, the marker will get on top of others when you hover the mouse over it.
6384 // @option riseOffset: Number = 250
6385 // The z-index offset used for the `riseOnHover` feature.
6388 // @option pane: String = 'markerPane'
6389 // `Map pane` where the markers icon will be added.
6392 // FIXME: shadowPane is no longer a valid option
6393 nonBubblingEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu']
6398 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
6401 initialize: function (latlng, options) {
6402 L.setOptions(this, options);
6403 this._latlng = L.latLng(latlng);
6406 onAdd: function (map) {
6407 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
6409 if (this._zoomAnimated) {
6410 map.on('zoomanim', this._animateZoom, this);
6417 onRemove: function (map) {
6418 if (this.dragging && this.dragging.enabled()) {
6419 this.options.draggable = true;
6420 this.dragging.removeHooks();
6423 if (this._zoomAnimated) {
6424 map.off('zoomanim', this._animateZoom, this);
6428 this._removeShadow();
6431 getEvents: function () {
6434 viewreset: this.update
6438 // @method getLatLng: LatLng
6439 // Returns the current geographical position of the marker.
6440 getLatLng: function () {
6441 return this._latlng;
6444 // @method setLatLng(latlng: LatLng): this
6445 // Changes the marker position to the given point.
6446 setLatLng: function (latlng) {
6447 var oldLatLng = this._latlng;
6448 this._latlng = L.latLng(latlng);
6451 // @event move: Event
6452 // 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`.
6453 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
6456 // @method setZIndexOffset(offset: Number): this
6457 // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
6458 setZIndexOffset: function (offset) {
6459 this.options.zIndexOffset = offset;
6460 return this.update();
6463 // @method setIcon(icon: Icon): this
6464 // Changes the marker icon.
6465 setIcon: function (icon) {
6467 this.options.icon = icon;
6475 this.bindPopup(this._popup, this._popup.options);
6481 getElement: function () {
6485 update: function () {
6488 var pos = this._map.latLngToLayerPoint(this._latlng).round();
6495 _initIcon: function () {
6496 var options = this.options,
6497 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
6499 var icon = options.icon.createIcon(this._icon),
6502 // if we're not reusing the icon, remove the old one and init new one
6503 if (icon !== this._icon) {
6509 if (options.title) {
6510 icon.title = options.title;
6513 icon.alt = options.alt;
6517 L.DomUtil.addClass(icon, classToAdd);
6519 if (options.keyboard) {
6520 icon.tabIndex = '0';
6525 if (options.riseOnHover) {
6527 mouseover: this._bringToFront,
6528 mouseout: this._resetZIndex
6532 var newShadow = options.icon.createShadow(this._shadow),
6535 if (newShadow !== this._shadow) {
6536 this._removeShadow();
6541 L.DomUtil.addClass(newShadow, classToAdd);
6544 this._shadow = newShadow;
6547 if (options.opacity < 1) {
6548 this._updateOpacity();
6553 this.getPane().appendChild(this._icon);
6555 this._initInteraction();
6556 if (newShadow && addShadow) {
6557 this.getPane('shadowPane').appendChild(this._shadow);
6561 _removeIcon: function () {
6562 if (this.options.riseOnHover) {
6564 mouseover: this._bringToFront,
6565 mouseout: this._resetZIndex
6569 L.DomUtil.remove(this._icon);
6570 this.removeInteractiveTarget(this._icon);
6575 _removeShadow: function () {
6577 L.DomUtil.remove(this._shadow);
6579 this._shadow = null;
6582 _setPos: function (pos) {
6583 L.DomUtil.setPosition(this._icon, pos);
6586 L.DomUtil.setPosition(this._shadow, pos);
6589 this._zIndex = pos.y + this.options.zIndexOffset;
6591 this._resetZIndex();
6594 _updateZIndex: function (offset) {
6595 this._icon.style.zIndex = this._zIndex + offset;
6598 _animateZoom: function (opt) {
6599 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
6604 _initInteraction: function () {
6606 if (!this.options.interactive) { return; }
6608 L.DomUtil.addClass(this._icon, 'leaflet-interactive');
6610 this.addInteractiveTarget(this._icon);
6612 if (L.Handler.MarkerDrag) {
6613 var draggable = this.options.draggable;
6614 if (this.dragging) {
6615 draggable = this.dragging.enabled();
6616 this.dragging.disable();
6619 this.dragging = new L.Handler.MarkerDrag(this);
6622 this.dragging.enable();
6627 // @method setOpacity(opacity: Number): this
6628 // Changes the opacity of the marker.
6629 setOpacity: function (opacity) {
6630 this.options.opacity = opacity;
6632 this._updateOpacity();
6638 _updateOpacity: function () {
6639 var opacity = this.options.opacity;
6641 L.DomUtil.setOpacity(this._icon, opacity);
6644 L.DomUtil.setOpacity(this._shadow, opacity);
6648 _bringToFront: function () {
6649 this._updateZIndex(this.options.riseOffset);
6652 _resetZIndex: function () {
6653 this._updateZIndex(0);
6656 _getPopupAnchor: function () {
6657 return this.options.icon.options.popupAnchor || [0, 0];
6660 _getTooltipAnchor: function () {
6661 return this.options.icon.options.tooltipAnchor || [0, 0];
6666 // factory L.marker(latlng: LatLng, options? : Marker options)
6668 // @factory L.marker(latlng: LatLng, options? : Marker options)
6669 // Instantiates a Marker object given a geographical point and optionally an options object.
6670 L.marker = function (latlng, options) {
6671 return new L.Marker(latlng, options);
6681 * Represents a lightweight icon for markers that uses a simple `<div>`
6682 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
6686 * var myIcon = L.divIcon({className: 'my-div-icon'});
6687 * // you can set .my-div-icon styles in CSS
6689 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
6692 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
6695 L.DivIcon = L.Icon.extend({
6698 // @aka DivIcon options
6699 iconSize: [12, 12], // also can be set through CSS
6701 // iconAnchor: (Point),
6702 // popupAnchor: (Point),
6704 // @option html: String = ''
6705 // Custom HTML code to put inside the div element, empty by default.
6708 // @option bgPos: Point = [0, 0]
6709 // Optional relative position of the background, in pixels
6712 className: 'leaflet-div-icon'
6715 createIcon: function (oldIcon) {
6716 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
6717 options = this.options;
6719 div.innerHTML = options.html !== false ? options.html : '';
6721 if (options.bgPos) {
6722 var bgPos = L.point(options.bgPos);
6723 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
6725 this._setIconStyles(div, 'icon');
6730 createShadow: function () {
6735 // @factory L.divIcon(options: DivIcon options)
6736 // Creates a `DivIcon` instance with the given options.
6737 L.divIcon = function (options) {
6738 return new L.DivIcon(options);
6747 * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
6750 // @namespace DivOverlay
6751 L.DivOverlay = L.Layer.extend({
6754 // @aka DivOverlay options
6756 // @option offset: Point = Point(0, 7)
6757 // The offset of the popup position. Useful to control the anchor
6758 // of the popup when opening it on some overlays.
6761 // @option className: String = ''
6762 // A custom CSS class name to assign to the popup.
6765 // @option pane: String = 'popupPane'
6766 // `Map pane` where the popup will be added.
6770 initialize: function (options, source) {
6771 L.setOptions(this, options);
6773 this._source = source;
6776 onAdd: function (map) {
6777 this._zoomAnimated = map._zoomAnimated;
6779 if (!this._container) {
6783 if (map._fadeAnimated) {
6784 L.DomUtil.setOpacity(this._container, 0);
6787 clearTimeout(this._removeTimeout);
6788 this.getPane().appendChild(this._container);
6791 if (map._fadeAnimated) {
6792 L.DomUtil.setOpacity(this._container, 1);
6795 this.bringToFront();
6798 onRemove: function (map) {
6799 if (map._fadeAnimated) {
6800 L.DomUtil.setOpacity(this._container, 0);
6801 this._removeTimeout = setTimeout(L.bind(L.DomUtil.remove, L.DomUtil, this._container), 200);
6803 L.DomUtil.remove(this._container);
6808 // @method getLatLng: LatLng
6809 // Returns the geographical point of popup.
6810 getLatLng: function () {
6811 return this._latlng;
6814 // @method setLatLng(latlng: LatLng): this
6815 // Sets the geographical point where the popup will open.
6816 setLatLng: function (latlng) {
6817 this._latlng = L.latLng(latlng);
6819 this._updatePosition();
6825 // @method getContent: String|HTMLElement
6826 // Returns the content of the popup.
6827 getContent: function () {
6828 return this._content;
6831 // @method setContent(htmlContent: String|HTMLElement|Function): this
6832 // 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.
6833 setContent: function (content) {
6834 this._content = content;
6839 // @method getElement: String|HTMLElement
6840 // Alias for [getContent()](#popup-getcontent)
6841 getElement: function () {
6842 return this._container;
6845 // @method update: null
6846 // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
6847 update: function () {
6848 if (!this._map) { return; }
6850 this._container.style.visibility = 'hidden';
6852 this._updateContent();
6853 this._updateLayout();
6854 this._updatePosition();
6856 this._container.style.visibility = '';
6861 getEvents: function () {
6863 zoom: this._updatePosition,
6864 viewreset: this._updatePosition
6867 if (this._zoomAnimated) {
6868 events.zoomanim = this._animateZoom;
6873 // @method isOpen: Boolean
6874 // Returns `true` when the popup is visible on the map.
6875 isOpen: function () {
6876 return !!this._map && this._map.hasLayer(this);
6879 // @method bringToFront: this
6880 // Brings this popup in front of other popups (in the same map pane).
6881 bringToFront: function () {
6883 L.DomUtil.toFront(this._container);
6888 // @method bringToBack: this
6889 // Brings this popup to the back of other popups (in the same map pane).
6890 bringToBack: function () {
6892 L.DomUtil.toBack(this._container);
6897 _updateContent: function () {
6898 if (!this._content) { return; }
6900 var node = this._contentNode;
6901 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
6903 if (typeof content === 'string') {
6904 node.innerHTML = content;
6906 while (node.hasChildNodes()) {
6907 node.removeChild(node.firstChild);
6909 node.appendChild(content);
6911 this.fire('contentupdate');
6914 _updatePosition: function () {
6915 if (!this._map) { return; }
6917 var pos = this._map.latLngToLayerPoint(this._latlng),
6918 offset = L.point(this.options.offset),
6919 anchor = this._getAnchor();
6921 if (this._zoomAnimated) {
6922 L.DomUtil.setPosition(this._container, pos.add(anchor));
6924 offset = offset.add(pos).add(anchor);
6927 var bottom = this._containerBottom = -offset.y,
6928 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
6930 // bottom position the popup in case the height of the popup changes (images loading etc)
6931 this._container.style.bottom = bottom + 'px';
6932 this._container.style.left = left + 'px';
6935 _getAnchor: function () {
6945 * @inherits DivOverlay
6947 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
6948 * open popups while making sure that only one popup is open at one time
6949 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
6953 * If you want to just bind a popup to marker click and then open it, it's really easy:
6956 * marker.bindPopup(popupContent).openPopup();
6958 * Path overlays like polylines also have a `bindPopup` method.
6959 * Here's a more complicated way to open a popup on a map:
6962 * var popup = L.popup()
6963 * .setLatLng(latlng)
6964 * .setContent('<p>Hello world!<br />This is a nice popup.</p>')
6971 L.Popup = L.DivOverlay.extend({
6974 // @aka Popup options
6976 // @option maxWidth: Number = 300
6977 // Max width of the popup, in pixels.
6980 // @option minWidth: Number = 50
6981 // Min width of the popup, in pixels.
6984 // @option maxHeight: Number = null
6985 // If set, creates a scrollable container of the given height
6986 // inside a popup if its content exceeds it.
6989 // @option autoPan: Boolean = true
6990 // Set it to `false` if you don't want the map to do panning animation
6991 // to fit the opened popup.
6994 // @option autoPanPaddingTopLeft: Point = null
6995 // The margin between the popup and the top left corner of the map
6996 // view after autopanning was performed.
6997 autoPanPaddingTopLeft: null,
6999 // @option autoPanPaddingBottomRight: Point = null
7000 // The margin between the popup and the bottom right corner of the map
7001 // view after autopanning was performed.
7002 autoPanPaddingBottomRight: null,
7004 // @option autoPanPadding: Point = Point(5, 5)
7005 // Equivalent of setting both top left and bottom right autopan padding to the same value.
7006 autoPanPadding: [5, 5],
7008 // @option keepInView: Boolean = false
7009 // Set it to `true` if you want to prevent users from panning the popup
7010 // off of the screen while it is open.
7013 // @option closeButton: Boolean = true
7014 // Controls the presence of a close button in the popup.
7017 // @option autoClose: Boolean = true
7018 // Set it to `false` if you want to override the default behavior of
7019 // the popup closing when user clicks the map (set globally by
7020 // the Map's [closePopupOnClick](#map-closepopuponclick) option).
7023 // @option className: String = ''
7024 // A custom CSS class name to assign to the popup.
7029 // @method openOn(map: Map): this
7030 // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
7031 openOn: function (map) {
7032 map.openPopup(this);
7036 onAdd: function (map) {
7037 L.DivOverlay.prototype.onAdd.call(this, map);
7040 // @section Popup events
7041 // @event popupopen: PopupEvent
7042 // Fired when a popup is opened in the map
7043 map.fire('popupopen', {popup: this});
7047 // @section Popup events
7048 // @event popupopen: PopupEvent
7049 // Fired when a popup bound to this layer is opened
7050 this._source.fire('popupopen', {popup: this}, true);
7051 // For non-path layers, we toggle the popup when clicking
7052 // again the layer, so prevent the map to reopen it.
7053 if (!(this._source instanceof L.Path)) {
7054 this._source.on('preclick', L.DomEvent.stopPropagation);
7059 onRemove: function (map) {
7060 L.DivOverlay.prototype.onRemove.call(this, map);
7063 // @section Popup events
7064 // @event popupclose: PopupEvent
7065 // Fired when a popup in the map is closed
7066 map.fire('popupclose', {popup: this});
7070 // @section Popup events
7071 // @event popupclose: PopupEvent
7072 // Fired when a popup bound to this layer is closed
7073 this._source.fire('popupclose', {popup: this}, true);
7074 if (!(this._source instanceof L.Path)) {
7075 this._source.off('preclick', L.DomEvent.stopPropagation);
7080 getEvents: function () {
7081 var events = L.DivOverlay.prototype.getEvents.call(this);
7083 if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
7084 events.preclick = this._close;
7087 if (this.options.keepInView) {
7088 events.moveend = this._adjustPan;
7094 _close: function () {
7096 this._map.closePopup(this);
7100 _initLayout: function () {
7101 var prefix = 'leaflet-popup',
7102 container = this._container = L.DomUtil.create('div',
7103 prefix + ' ' + (this.options.className || '') +
7104 ' leaflet-zoom-animated');
7106 if (this.options.closeButton) {
7107 var closeButton = this._closeButton = L.DomUtil.create('a', prefix + '-close-button', container);
7108 closeButton.href = '#close';
7109 closeButton.innerHTML = '×';
7111 L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);
7114 var wrapper = this._wrapper = L.DomUtil.create('div', prefix + '-content-wrapper', container);
7115 this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);
7118 .disableClickPropagation(wrapper)
7119 .disableScrollPropagation(this._contentNode)
7120 .on(wrapper, 'contextmenu', L.DomEvent.stopPropagation);
7122 this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container);
7123 this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);
7126 _updateLayout: function () {
7127 var container = this._contentNode,
7128 style = container.style;
7131 style.whiteSpace = 'nowrap';
7133 var width = container.offsetWidth;
7134 width = Math.min(width, this.options.maxWidth);
7135 width = Math.max(width, this.options.minWidth);
7137 style.width = (width + 1) + 'px';
7138 style.whiteSpace = '';
7142 var height = container.offsetHeight,
7143 maxHeight = this.options.maxHeight,
7144 scrolledClass = 'leaflet-popup-scrolled';
7146 if (maxHeight && height > maxHeight) {
7147 style.height = maxHeight + 'px';
7148 L.DomUtil.addClass(container, scrolledClass);
7150 L.DomUtil.removeClass(container, scrolledClass);
7153 this._containerWidth = this._container.offsetWidth;
7156 _animateZoom: function (e) {
7157 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
7158 anchor = this._getAnchor();
7159 L.DomUtil.setPosition(this._container, pos.add(anchor));
7162 _adjustPan: function () {
7163 if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; }
7165 var map = this._map,
7166 marginBottom = parseInt(L.DomUtil.getStyle(this._container, 'marginBottom'), 10) || 0,
7167 containerHeight = this._container.offsetHeight + marginBottom,
7168 containerWidth = this._containerWidth,
7169 layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);
7171 layerPos._add(L.DomUtil.getPosition(this._container));
7173 var containerPos = map.layerPointToContainerPoint(layerPos),
7174 padding = L.point(this.options.autoPanPadding),
7175 paddingTL = L.point(this.options.autoPanPaddingTopLeft || padding),
7176 paddingBR = L.point(this.options.autoPanPaddingBottomRight || padding),
7177 size = map.getSize(),
7181 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
7182 dx = containerPos.x + containerWidth - size.x + paddingBR.x;
7184 if (containerPos.x - dx - paddingTL.x < 0) { // left
7185 dx = containerPos.x - paddingTL.x;
7187 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
7188 dy = containerPos.y + containerHeight - size.y + paddingBR.y;
7190 if (containerPos.y - dy - paddingTL.y < 0) { // top
7191 dy = containerPos.y - paddingTL.y;
7195 // @section Popup events
7196 // @event autopanstart: Event
7197 // Fired when the map starts autopanning when opening a popup.
7200 .fire('autopanstart')
7205 _onCloseButtonClick: function (e) {
7210 _getAnchor: function () {
7211 // Where should we anchor the popup on the source layer?
7212 return L.point(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
7218 // @factory L.popup(options?: Popup options, source?: Layer)
7219 // 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.
7220 L.popup = function (options, source) {
7221 return new L.Popup(options, source);
7226 * @section Interaction Options
7227 * @option closePopupOnClick: Boolean = true
7228 * Set it to `false` if you don't want popups to close when user clicks the map.
7230 L.Map.mergeOptions({
7231 closePopupOnClick: true
7236 // @section Methods for Layers and Controls
7238 // @method openPopup(popup: Popup): this
7239 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
7241 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
7242 // Creates a popup with the specified content and options and opens it in the given point on a map.
7243 openPopup: function (popup, latlng, options) {
7244 if (!(popup instanceof L.Popup)) {
7245 popup = new L.Popup(options).setContent(popup);
7249 popup.setLatLng(latlng);
7252 if (this.hasLayer(popup)) {
7256 if (this._popup && this._popup.options.autoClose) {
7260 this._popup = popup;
7261 return this.addLayer(popup);
7264 // @method closePopup(popup?: Popup): this
7265 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
7266 closePopup: function (popup) {
7267 if (!popup || popup === this._popup) {
7268 popup = this._popup;
7272 this.removeLayer(popup);
7280 * @section Popup methods example
7282 * All layers share a set of methods convenient for binding popups to it.
7285 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
7286 * layer.openPopup();
7287 * layer.closePopup();
7290 * 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.
7293 // @section Popup methods
7296 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
7297 // Binds a popup to the layer with the passed `content` and sets up the
7298 // neccessary event listeners. If a `Function` is passed it will receive
7299 // the layer as the first argument and should return a `String` or `HTMLElement`.
7300 bindPopup: function (content, options) {
7302 if (content instanceof L.Popup) {
7303 L.setOptions(content, options);
7304 this._popup = content;
7305 content._source = this;
7307 if (!this._popup || options) {
7308 this._popup = new L.Popup(options, this);
7310 this._popup.setContent(content);
7313 if (!this._popupHandlersAdded) {
7315 click: this._openPopup,
7316 remove: this.closePopup,
7317 move: this._movePopup
7319 this._popupHandlersAdded = true;
7325 // @method unbindPopup(): this
7326 // Removes the popup previously bound with `bindPopup`.
7327 unbindPopup: function () {
7330 click: this._openPopup,
7331 remove: this.closePopup,
7332 move: this._movePopup
7334 this._popupHandlersAdded = false;
7340 // @method openPopup(latlng?: LatLng): this
7341 // Opens the bound popup at the specificed `latlng` or at the default popup anchor if no `latlng` is passed.
7342 openPopup: function (layer, latlng) {
7343 if (!(layer instanceof L.Layer)) {
7348 if (layer instanceof L.FeatureGroup) {
7349 for (var id in this._layers) {
7350 layer = this._layers[id];
7356 latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
7359 if (this._popup && this._map) {
7360 // set popup source to this layer
7361 this._popup._source = layer;
7363 // update the popup (content, layout, ect...)
7364 this._popup.update();
7366 // open the popup on the map
7367 this._map.openPopup(this._popup, latlng);
7373 // @method closePopup(): this
7374 // Closes the popup bound to this layer if it is open.
7375 closePopup: function () {
7377 this._popup._close();
7382 // @method togglePopup(): this
7383 // Opens or closes the popup bound to this layer depending on its current state.
7384 togglePopup: function (target) {
7386 if (this._popup._map) {
7389 this.openPopup(target);
7395 // @method isPopupOpen(): boolean
7396 // Returns `true` if the popup bound to this layer is currently open.
7397 isPopupOpen: function () {
7398 return (this._popup ? this._popup.isOpen() : false);
7401 // @method setPopupContent(content: String|HTMLElement|Popup): this
7402 // Sets the content of the popup bound to this layer.
7403 setPopupContent: function (content) {
7405 this._popup.setContent(content);
7410 // @method getPopup(): Popup
7411 // Returns the popup bound to this layer.
7412 getPopup: function () {
7416 _openPopup: function (e) {
7417 var layer = e.layer || e.target;
7427 // prevent map click
7430 // if this inherits from Path its a vector and we can just
7431 // open the popup at the new location
7432 if (layer instanceof L.Path) {
7433 this.openPopup(e.layer || e.target, e.latlng);
7437 // otherwise treat it like a marker and figure out
7438 // if we should toggle it open/closed
7439 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
7442 this.openPopup(layer, e.latlng);
7446 _movePopup: function (e) {
7447 this._popup.setLatLng(e.latlng);
7455 * @inherits DivOverlay
7457 * Used to display small texts on top of map layers.
7462 * marker.bindTooltip("my tooltip text").openTooltip();
7464 * Note about tooltip offset. Leaflet takes two options in consideration
7465 * for computing tooltip offseting:
7466 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
7467 * Add a positive x offset to move the tooltip to the right, and a positive y offset to
7468 * move it to the bottom. Negatives will move to the left and top.
7469 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
7470 * should adapt this value if you use a custom icon.
7474 // @namespace Tooltip
7475 L.Tooltip = L.DivOverlay.extend({
7478 // @aka Tooltip options
7480 // @option pane: String = 'tooltipPane'
7481 // `Map pane` where the tooltip will be added.
7482 pane: 'tooltipPane',
7484 // @option offset: Point = Point(0, 0)
7485 // Optional offset of the tooltip position.
7488 // @option direction: String = 'auto'
7489 // Direction where to open the tooltip. Possible values are: `right`, `left`,
7490 // `top`, `bottom`, `center`, `auto`.
7491 // `auto` will dynamicaly switch between `right` and `left` according to the tooltip
7492 // position on the map.
7495 // @option permanent: Boolean = false
7496 // Whether to open the tooltip permanently or only on mouseover.
7499 // @option sticky: Boolean = false
7500 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
7503 // @option interactive: Boolean = false
7504 // If true, the tooltip will listen to the feature events.
7507 // @option opacity: Number = 0.9
7508 // Tooltip container opacity.
7512 onAdd: function (map) {
7513 L.DivOverlay.prototype.onAdd.call(this, map);
7514 this.setOpacity(this.options.opacity);
7517 // @section Tooltip events
7518 // @event tooltipopen: TooltipEvent
7519 // Fired when a tooltip is opened in the map.
7520 map.fire('tooltipopen', {tooltip: this});
7524 // @section Tooltip events
7525 // @event tooltipopen: TooltipEvent
7526 // Fired when a tooltip bound to this layer is opened.
7527 this._source.fire('tooltipopen', {tooltip: this}, true);
7531 onRemove: function (map) {
7532 L.DivOverlay.prototype.onRemove.call(this, map);
7535 // @section Tooltip events
7536 // @event tooltipclose: TooltipEvent
7537 // Fired when a tooltip in the map is closed.
7538 map.fire('tooltipclose', {tooltip: this});
7542 // @section Tooltip events
7543 // @event tooltipclose: TooltipEvent
7544 // Fired when a tooltip bound to this layer is closed.
7545 this._source.fire('tooltipclose', {tooltip: this}, true);
7549 getEvents: function () {
7550 var events = L.DivOverlay.prototype.getEvents.call(this);
7552 if (L.Browser.touch && !this.options.permanent) {
7553 events.preclick = this._close;
7559 _close: function () {
7561 this._map.closeTooltip(this);
7565 _initLayout: function () {
7566 var prefix = 'leaflet-tooltip',
7567 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
7569 this._contentNode = this._container = L.DomUtil.create('div', className);
7572 _updateLayout: function () {},
7574 _adjustPan: function () {},
7576 _setPosition: function (pos) {
7577 var map = this._map,
7578 container = this._container,
7579 centerPoint = map.latLngToContainerPoint(map.getCenter()),
7580 tooltipPoint = map.layerPointToContainerPoint(pos),
7581 direction = this.options.direction,
7582 tooltipWidth = container.offsetWidth,
7583 tooltipHeight = container.offsetHeight,
7584 offset = L.point(this.options.offset),
7585 anchor = this._getAnchor();
7587 if (direction === 'top') {
7588 pos = pos.add(L.point(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true));
7589 } else if (direction === 'bottom') {
7590 pos = pos.subtract(L.point(tooltipWidth / 2 - offset.x, -offset.y, true));
7591 } else if (direction === 'center') {
7592 pos = pos.subtract(L.point(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true));
7593 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
7594 direction = 'right';
7595 pos = pos.add(L.point(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true));
7598 pos = pos.subtract(L.point(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true));
7601 L.DomUtil.removeClass(container, 'leaflet-tooltip-right');
7602 L.DomUtil.removeClass(container, 'leaflet-tooltip-left');
7603 L.DomUtil.removeClass(container, 'leaflet-tooltip-top');
7604 L.DomUtil.removeClass(container, 'leaflet-tooltip-bottom');
7605 L.DomUtil.addClass(container, 'leaflet-tooltip-' + direction);
7606 L.DomUtil.setPosition(container, pos);
7609 _updatePosition: function () {
7610 var pos = this._map.latLngToLayerPoint(this._latlng);
7611 this._setPosition(pos);
7614 setOpacity: function (opacity) {
7615 this.options.opacity = opacity;
7617 if (this._container) {
7618 L.DomUtil.setOpacity(this._container, opacity);
7622 _animateZoom: function (e) {
7623 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
7624 this._setPosition(pos);
7627 _getAnchor: function () {
7628 // Where should we anchor the tooltip on the source layer?
7629 return L.point(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
7634 // @namespace Tooltip
7635 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
7636 // 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.
7637 L.tooltip = function (options, source) {
7638 return new L.Tooltip(options, source);
7642 // @section Methods for Layers and Controls
7645 // @method openTooltip(tooltip: Tooltip): this
7646 // Opens the specified tooltip.
7648 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
7649 // Creates a tooltip with the specified content and options and open it.
7650 openTooltip: function (tooltip, latlng, options) {
7651 if (!(tooltip instanceof L.Tooltip)) {
7652 tooltip = new L.Tooltip(options).setContent(tooltip);
7656 tooltip.setLatLng(latlng);
7659 if (this.hasLayer(tooltip)) {
7663 return this.addLayer(tooltip);
7666 // @method closeTooltip(tooltip?: Tooltip): this
7667 // Closes the tooltip given as parameter.
7668 closeTooltip: function (tooltip) {
7670 this.removeLayer(tooltip);
7679 * @section Tooltip methods example
7681 * All layers share a set of methods convenient for binding tooltips to it.
7684 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
7685 * layer.openTooltip();
7686 * layer.closeTooltip();
7690 // @section Tooltip methods
7693 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
7694 // Binds a tooltip to the layer with the passed `content` and sets up the
7695 // neccessary event listeners. If a `Function` is passed it will receive
7696 // the layer as the first argument and should return a `String` or `HTMLElement`.
7697 bindTooltip: function (content, options) {
7699 if (content instanceof L.Tooltip) {
7700 L.setOptions(content, options);
7701 this._tooltip = content;
7702 content._source = this;
7704 if (!this._tooltip || options) {
7705 this._tooltip = L.tooltip(options, this);
7707 this._tooltip.setContent(content);
7711 this._initTooltipInteractions();
7713 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
7720 // @method unbindTooltip(): this
7721 // Removes the tooltip previously bound with `bindTooltip`.
7722 unbindTooltip: function () {
7723 if (this._tooltip) {
7724 this._initTooltipInteractions(true);
7725 this.closeTooltip();
7726 this._tooltip = null;
7731 _initTooltipInteractions: function (remove) {
7732 if (!remove && this._tooltipHandlersAdded) { return; }
7733 var onOff = remove ? 'off' : 'on',
7735 remove: this.closeTooltip,
7736 move: this._moveTooltip
7738 if (!this._tooltip.options.permanent) {
7739 events.mouseover = this._openTooltip;
7740 events.mouseout = this.closeTooltip;
7741 if (this._tooltip.options.sticky) {
7742 events.mousemove = this._moveTooltip;
7744 if (L.Browser.touch) {
7745 events.click = this._openTooltip;
7748 events.add = this._openTooltip;
7750 this[onOff](events);
7751 this._tooltipHandlersAdded = !remove;
7754 // @method openTooltip(latlng?: LatLng): this
7755 // Opens the bound tooltip at the specificed `latlng` or at the default tooltip anchor if no `latlng` is passed.
7756 openTooltip: function (layer, latlng) {
7757 if (!(layer instanceof L.Layer)) {
7762 if (layer instanceof L.FeatureGroup) {
7763 for (var id in this._layers) {
7764 layer = this._layers[id];
7770 latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
7773 if (this._tooltip && this._map) {
7775 // set tooltip source to this layer
7776 this._tooltip._source = layer;
7778 // update the tooltip (content, layout, ect...)
7779 this._tooltip.update();
7781 // open the tooltip on the map
7782 this._map.openTooltip(this._tooltip, latlng);
7784 // Tooltip container may not be defined if not permanent and never
7786 if (this._tooltip.options.interactive && this._tooltip._container) {
7787 L.DomUtil.addClass(this._tooltip._container, 'leaflet-clickable');
7788 this.addInteractiveTarget(this._tooltip._container);
7795 // @method closeTooltip(): this
7796 // Closes the tooltip bound to this layer if it is open.
7797 closeTooltip: function () {
7798 if (this._tooltip) {
7799 this._tooltip._close();
7800 if (this._tooltip.options.interactive && this._tooltip._container) {
7801 L.DomUtil.removeClass(this._tooltip._container, 'leaflet-clickable');
7802 this.removeInteractiveTarget(this._tooltip._container);
7808 // @method toggleTooltip(): this
7809 // Opens or closes the tooltip bound to this layer depending on its current state.
7810 toggleTooltip: function (target) {
7811 if (this._tooltip) {
7812 if (this._tooltip._map) {
7813 this.closeTooltip();
7815 this.openTooltip(target);
7821 // @method isTooltipOpen(): boolean
7822 // Returns `true` if the tooltip bound to this layer is currently open.
7823 isTooltipOpen: function () {
7824 return this._tooltip.isOpen();
7827 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
7828 // Sets the content of the tooltip bound to this layer.
7829 setTooltipContent: function (content) {
7830 if (this._tooltip) {
7831 this._tooltip.setContent(content);
7836 // @method getTooltip(): Tooltip
7837 // Returns the tooltip bound to this layer.
7838 getTooltip: function () {
7839 return this._tooltip;
7842 _openTooltip: function (e) {
7843 var layer = e.layer || e.target;
7845 if (!this._tooltip || !this._map) {
7848 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
7851 _moveTooltip: function (e) {
7852 var latlng = e.latlng, containerPoint, layerPoint;
7853 if (this._tooltip.options.sticky && e.originalEvent) {
7854 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
7855 layerPoint = this._map.containerPointToLayerPoint(containerPoint);
7856 latlng = this._map.layerPointToLatLng(layerPoint);
7858 this._tooltip.setLatLng(latlng);
7869 * Used to group several layers and handle them as one. If you add it to the map,
7870 * any layers added or removed from the group will be added/removed on the map as
7871 * well. Extends `Layer`.
7876 * L.layerGroup([marker1, marker2])
7877 * .addLayer(polyline)
7882 L.LayerGroup = L.Layer.extend({
7884 initialize: function (layers) {
7890 for (i = 0, len = layers.length; i < len; i++) {
7891 this.addLayer(layers[i]);
7896 // @method addLayer(layer: Layer): this
7897 // Adds the given layer to the group.
7898 addLayer: function (layer) {
7899 var id = this.getLayerId(layer);
7901 this._layers[id] = layer;
7904 this._map.addLayer(layer);
7910 // @method removeLayer(layer: Layer): this
7911 // Removes the given layer from the group.
7913 // @method removeLayer(id: Number): this
7914 // Removes the layer with the given internal ID from the group.
7915 removeLayer: function (layer) {
7916 var id = layer in this._layers ? layer : this.getLayerId(layer);
7918 if (this._map && this._layers[id]) {
7919 this._map.removeLayer(this._layers[id]);
7922 delete this._layers[id];
7927 // @method hasLayer(layer: Layer): Boolean
7928 // Returns `true` if the given layer is currently added to the group.
7929 hasLayer: function (layer) {
7930 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
7933 // @method clearLayers(): this
7934 // Removes all the layers from the group.
7935 clearLayers: function () {
7936 for (var i in this._layers) {
7937 this.removeLayer(this._layers[i]);
7942 // @method invoke(methodName: String, …): this
7943 // Calls `methodName` on every layer contained in this group, passing any
7944 // additional parameters. Has no effect if the layers contained do not
7945 // implement `methodName`.
7946 invoke: function (methodName) {
7947 var args = Array.prototype.slice.call(arguments, 1),
7950 for (i in this._layers) {
7951 layer = this._layers[i];
7953 if (layer[methodName]) {
7954 layer[methodName].apply(layer, args);
7961 onAdd: function (map) {
7962 for (var i in this._layers) {
7963 map.addLayer(this._layers[i]);
7967 onRemove: function (map) {
7968 for (var i in this._layers) {
7969 map.removeLayer(this._layers[i]);
7973 // @method eachLayer(fn: Function, context?: Object): this
7974 // Iterates over the layers of the group, optionally specifying context of the iterator function.
7976 // group.eachLayer(function (layer) {
7977 // layer.bindPopup('Hello');
7980 eachLayer: function (method, context) {
7981 for (var i in this._layers) {
7982 method.call(context, this._layers[i]);
7987 // @method getLayer(id: Number): Layer
7988 // Returns the layer with the given internal ID.
7989 getLayer: function (id) {
7990 return this._layers[id];
7993 // @method getLayers(): Layer[]
7994 // Returns an array of all the layers added to the group.
7995 getLayers: function () {
7998 for (var i in this._layers) {
7999 layers.push(this._layers[i]);
8004 // @method setZIndex(zIndex: Number): this
8005 // Calls `setZIndex` on every layer contained in this group, passing the z-index.
8006 setZIndex: function (zIndex) {
8007 return this.invoke('setZIndex', zIndex);
8010 // @method getLayerId(layer: Layer): Number
8011 // Returns the internal ID for a layer
8012 getLayerId: function (layer) {
8013 return L.stamp(layer);
8018 // @factory L.layerGroup(layers: Layer[])
8019 // Create a layer group, optionally given an initial set of layers.
8020 L.layerGroup = function (layers) {
8021 return new L.LayerGroup(layers);
8027 * @class FeatureGroup
8028 * @aka L.FeatureGroup
8029 * @inherits LayerGroup
8031 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
8032 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
8033 * * Events are propagated to the `FeatureGroup`, so if the group has an event
8034 * handler, it will handle events from any of the layers. This includes mouse events
8035 * and custom events.
8036 * * Has `layeradd` and `layerremove` events
8041 * L.featureGroup([marker1, marker2, polyline])
8042 * .bindPopup('Hello world!')
8043 * .on('click', function() { alert('Clicked on a member of the group!'); })
8048 L.FeatureGroup = L.LayerGroup.extend({
8050 addLayer: function (layer) {
8051 if (this.hasLayer(layer)) {
8055 layer.addEventParent(this);
8057 L.LayerGroup.prototype.addLayer.call(this, layer);
8059 // @event layeradd: LayerEvent
8060 // Fired when a layer is added to this `FeatureGroup`
8061 return this.fire('layeradd', {layer: layer});
8064 removeLayer: function (layer) {
8065 if (!this.hasLayer(layer)) {
8068 if (layer in this._layers) {
8069 layer = this._layers[layer];
8072 layer.removeEventParent(this);
8074 L.LayerGroup.prototype.removeLayer.call(this, layer);
8076 // @event layerremove: LayerEvent
8077 // Fired when a layer is removed from this `FeatureGroup`
8078 return this.fire('layerremove', {layer: layer});
8081 // @method setStyle(style: Path options): this
8082 // Sets the given path options to each layer of the group that has a `setStyle` method.
8083 setStyle: function (style) {
8084 return this.invoke('setStyle', style);
8087 // @method bringToFront(): this
8088 // Brings the layer group to the top of all other layers
8089 bringToFront: function () {
8090 return this.invoke('bringToFront');
8093 // @method bringToBack(): this
8094 // Brings the layer group to the top of all other layers
8095 bringToBack: function () {
8096 return this.invoke('bringToBack');
8099 // @method getBounds(): LatLngBounds
8100 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
8101 getBounds: function () {
8102 var bounds = new L.LatLngBounds();
8104 for (var id in this._layers) {
8105 var layer = this._layers[id];
8106 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
8112 // @factory L.featureGroup(layers: Layer[])
8113 // Create a feature group, optionally given an initial set of layers.
8114 L.featureGroup = function (layers) {
8115 return new L.FeatureGroup(layers);
8125 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
8126 * DOM container of the renderer, its bounds, and its zoom animation.
8128 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
8129 * itself can be added or removed to the map. All paths use a renderer, which can
8130 * be implicit (the map will decide the type of renderer and use it automatically)
8131 * or explicit (using the [`renderer`](#path-renderer) option of the path).
8133 * Do not use this class directly, use `SVG` and `Canvas` instead.
8135 * @event update: Event
8136 * Fired when the renderer updates its bounds, center and zoom, for example when
8140 L.Renderer = L.Layer.extend({
8143 // @aka Renderer options
8145 // @option padding: Number = 0.1
8146 // How much to extend the clip area around the map view (relative to its size)
8147 // e.g. 0.1 would be 10% of map view in each direction
8151 initialize: function (options) {
8152 L.setOptions(this, options);
8154 this._layers = this._layers || {};
8157 onAdd: function () {
8158 if (!this._container) {
8159 this._initContainer(); // defined by renderer implementations
8161 if (this._zoomAnimated) {
8162 L.DomUtil.addClass(this._container, 'leaflet-zoom-animated');
8166 this.getPane().appendChild(this._container);
8168 this.on('update', this._updatePaths, this);
8171 onRemove: function () {
8172 L.DomUtil.remove(this._container);
8173 this.off('update', this._updatePaths, this);
8176 getEvents: function () {
8178 viewreset: this._reset,
8180 moveend: this._update,
8181 zoomend: this._onZoomEnd
8183 if (this._zoomAnimated) {
8184 events.zoomanim = this._onAnimZoom;
8189 _onAnimZoom: function (ev) {
8190 this._updateTransform(ev.center, ev.zoom);
8193 _onZoom: function () {
8194 this._updateTransform(this._map.getCenter(), this._map.getZoom());
8197 _updateTransform: function (center, zoom) {
8198 var scale = this._map.getZoomScale(zoom, this._zoom),
8199 position = L.DomUtil.getPosition(this._container),
8200 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
8201 currentCenterPoint = this._map.project(this._center, zoom),
8202 destCenterPoint = this._map.project(center, zoom),
8203 centerOffset = destCenterPoint.subtract(currentCenterPoint),
8205 topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
8207 if (L.Browser.any3d) {
8208 L.DomUtil.setTransform(this._container, topLeftOffset, scale);
8210 L.DomUtil.setPosition(this._container, topLeftOffset);
8214 _reset: function () {
8216 this._updateTransform(this._center, this._zoom);
8218 for (var id in this._layers) {
8219 this._layers[id]._reset();
8223 _onZoomEnd: function () {
8224 for (var id in this._layers) {
8225 this._layers[id]._project();
8229 _updatePaths: function () {
8230 for (var id in this._layers) {
8231 this._layers[id]._update();
8235 _update: function () {
8236 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
8237 // Subclasses are responsible of firing the 'update' event.
8238 var p = this.options.padding,
8239 size = this._map.getSize(),
8240 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
8242 this._bounds = new L.Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
8244 this._center = this._map.getCenter();
8245 this._zoom = this._map.getZoom();
8251 // @namespace Map; @method getRenderer(layer: Path): Renderer
8252 // Returns the instance of `Renderer` that should be used to render the given
8253 // `Path`. It will ensure that the `renderer` options of the map and paths
8254 // are respected, and that the renderers do exist on the map.
8255 getRenderer: function (layer) {
8256 // @namespace Path; @option renderer: Renderer
8257 // Use this specific instance of `Renderer` for this path. Takes
8258 // precedence over the map's [default renderer](#map-renderer).
8259 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
8262 // @namespace Map; @option preferCanvas: Boolean = false
8263 // Whether `Path`s should be rendered on a `Canvas` renderer.
8264 // By default, all `Path`s are rendered in a `SVG` renderer.
8265 renderer = this._renderer = (this.options.preferCanvas && L.canvas()) || L.svg();
8268 if (!this.hasLayer(renderer)) {
8269 this.addLayer(renderer);
8274 _getPaneRenderer: function (name) {
8275 if (name === 'overlayPane' || name === undefined) {
8279 var renderer = this._paneRenderers[name];
8280 if (renderer === undefined) {
8281 renderer = (L.SVG && L.svg({pane: name})) || (L.Canvas && L.canvas({pane: name}));
8282 this._paneRenderers[name] = renderer;
8293 * @inherits Interactive layer
8295 * An abstract class that contains options and constants shared between vector
8296 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
8299 L.Path = L.Layer.extend({
8302 // @aka Path options
8304 // @option stroke: Boolean = true
8305 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
8308 // @option color: String = '#3388ff'
8312 // @option weight: Number = 3
8313 // Stroke width in pixels
8316 // @option opacity: Number = 1.0
8320 // @option lineCap: String= 'round'
8321 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
8324 // @option lineJoin: String = 'round'
8325 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
8328 // @option dashArray: String = null
8329 // 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).
8332 // @option dashOffset: String = null
8333 // 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).
8336 // @option fill: Boolean = depends
8337 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
8340 // @option fillColor: String = *
8341 // Fill color. Defaults to the value of the [`color`](#path-color) option
8344 // @option fillOpacity: Number = 0.2
8348 // @option fillRule: String = 'evenodd'
8349 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
8350 fillRule: 'evenodd',
8354 // Option inherited from "Interactive layer" abstract class
8358 beforeAdd: function (map) {
8359 // Renderer is set here because we need to call renderer.getEvents
8360 // before this.getEvents.
8361 this._renderer = map.getRenderer(this);
8364 onAdd: function () {
8365 this._renderer._initPath(this);
8367 this._renderer._addPath(this);
8370 onRemove: function () {
8371 this._renderer._removePath(this);
8374 // @method redraw(): this
8375 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
8376 redraw: function () {
8378 this._renderer._updatePath(this);
8383 // @method setStyle(style: Path options): this
8384 // Changes the appearance of a Path based on the options in the `Path options` object.
8385 setStyle: function (style) {
8386 L.setOptions(this, style);
8387 if (this._renderer) {
8388 this._renderer._updateStyle(this);
8393 // @method bringToFront(): this
8394 // Brings the layer to the top of all path layers.
8395 bringToFront: function () {
8396 if (this._renderer) {
8397 this._renderer._bringToFront(this);
8402 // @method bringToBack(): this
8403 // Brings the layer to the bottom of all path layers.
8404 bringToBack: function () {
8405 if (this._renderer) {
8406 this._renderer._bringToBack(this);
8411 getElement: function () {
8415 _reset: function () {
8416 // defined in children classes
8421 _clickTolerance: function () {
8422 // used when doing hit detection for Canvas layers
8423 return (this.options.stroke ? this.options.weight / 2 : 0) + (L.Browser.touch ? 10 : 0);
8430 * @namespace LineUtil
8432 * Various utility functions for polyine points processing, used by Leaflet internally to make polylines lightning-fast.
8437 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
8438 // Improves rendering performance dramatically by lessening the number of points to draw.
8440 // @function simplify(points: Point[], tolerance: Number): Point[]
8441 // Dramatically reduces the number of points in a polyline while retaining
8442 // its shape and returns a new array of simplified points, using the
8443 // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
8444 // Used for a huge performance boost when processing/displaying Leaflet polylines for
8445 // each zoom level and also reducing visual noise. tolerance affects the amount of
8446 // simplification (lesser value means higher quality but slower and with more points).
8447 // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
8448 simplify: function (points, tolerance) {
8449 if (!tolerance || !points.length) {
8450 return points.slice();
8453 var sqTolerance = tolerance * tolerance;
8455 // stage 1: vertex reduction
8456 points = this._reducePoints(points, sqTolerance);
8458 // stage 2: Douglas-Peucker simplification
8459 points = this._simplifyDP(points, sqTolerance);
8464 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
8465 // Returns the distance between point `p` and segment `p1` to `p2`.
8466 pointToSegmentDistance: function (p, p1, p2) {
8467 return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));
8470 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
8471 // Returns the closest point from a point `p` on a segment `p1` to `p2`.
8472 closestPointOnSegment: function (p, p1, p2) {
8473 return this._sqClosestPointOnSegment(p, p1, p2);
8476 // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
8477 _simplifyDP: function (points, sqTolerance) {
8479 var len = points.length,
8480 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
8481 markers = new ArrayConstructor(len);
8483 markers[0] = markers[len - 1] = 1;
8485 this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
8490 for (i = 0; i < len; i++) {
8492 newPoints.push(points[i]);
8499 _simplifyDPStep: function (points, markers, sqTolerance, first, last) {
8504 for (i = first + 1; i <= last - 1; i++) {
8505 sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);
8507 if (sqDist > maxSqDist) {
8513 if (maxSqDist > sqTolerance) {
8516 this._simplifyDPStep(points, markers, sqTolerance, first, index);
8517 this._simplifyDPStep(points, markers, sqTolerance, index, last);
8521 // reduce points that are too close to each other to a single point
8522 _reducePoints: function (points, sqTolerance) {
8523 var reducedPoints = [points[0]];
8525 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
8526 if (this._sqDist(points[i], points[prev]) > sqTolerance) {
8527 reducedPoints.push(points[i]);
8531 if (prev < len - 1) {
8532 reducedPoints.push(points[len - 1]);
8534 return reducedPoints;
8538 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
8539 // Clips the segment a to b by rectangular bounds with the
8540 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
8541 // (modifying the segment points directly!). Used by Leaflet to only show polyline
8542 // points that are on the screen or near, increasing performance.
8543 clipSegment: function (a, b, bounds, useLastCode, round) {
8544 var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
8545 codeB = this._getBitCode(b, bounds),
8547 codeOut, p, newCode;
8549 // save 2nd code to avoid calculating it on the next segment
8550 this._lastCode = codeB;
8553 // if a,b is inside the clip window (trivial accept)
8554 if (!(codeA | codeB)) {
8558 // if a,b is outside the clip window (trivial reject)
8559 if (codeA & codeB) {
8564 codeOut = codeA || codeB;
8565 p = this._getEdgeIntersection(a, b, codeOut, bounds, round);
8566 newCode = this._getBitCode(p, bounds);
8568 if (codeOut === codeA) {
8578 _getEdgeIntersection: function (a, b, code, bounds, round) {
8585 if (code & 8) { // top
8586 x = a.x + dx * (max.y - a.y) / dy;
8589 } else if (code & 4) { // bottom
8590 x = a.x + dx * (min.y - a.y) / dy;
8593 } else if (code & 2) { // right
8595 y = a.y + dy * (max.x - a.x) / dx;
8597 } else if (code & 1) { // left
8599 y = a.y + dy * (min.x - a.x) / dx;
8602 return new L.Point(x, y, round);
8605 _getBitCode: function (p, bounds) {
8608 if (p.x < bounds.min.x) { // left
8610 } else if (p.x > bounds.max.x) { // right
8614 if (p.y < bounds.min.y) { // bottom
8616 } else if (p.y > bounds.max.y) { // top
8623 // square distance (to avoid unnecessary Math.sqrt calls)
8624 _sqDist: function (p1, p2) {
8625 var dx = p2.x - p1.x,
8627 return dx * dx + dy * dy;
8630 // return closest point on segment or distance to that point
8631 _sqClosestPointOnSegment: function (p, p1, p2, sqDist) {
8636 dot = dx * dx + dy * dy,
8640 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
8654 return sqDist ? dx * dx + dy * dy : new L.Point(x, y);
8665 * A class for drawing polyline overlays on a map. Extends `Path`.
8670 * // create a red polyline from an array of LatLng points
8677 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
8679 * // zoom the map to the polyline
8680 * map.fitBounds(polyline.getBounds());
8683 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
8686 * // create a red polyline from an array of arrays of LatLng points
8688 * [[45.51, -122.68],
8698 L.Polyline = L.Path.extend({
8701 // @aka Polyline options
8703 // @option smoothFactor: Number = 1.0
8704 // How much to simplify the polyline on each zoom level. More means
8705 // better performance and smoother look, and less means more accurate representation.
8708 // @option noClip: Boolean = false
8709 // Disable polyline clipping.
8713 initialize: function (latlngs, options) {
8714 L.setOptions(this, options);
8715 this._setLatLngs(latlngs);
8718 // @method getLatLngs(): LatLng[]
8719 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8720 getLatLngs: function () {
8721 return this._latlngs;
8724 // @method setLatLngs(latlngs: LatLng[]): this
8725 // Replaces all the points in the polyline with the given array of geographical points.
8726 setLatLngs: function (latlngs) {
8727 this._setLatLngs(latlngs);
8728 return this.redraw();
8731 // @method isEmpty(): Boolean
8732 // Returns `true` if the Polyline has no LatLngs.
8733 isEmpty: function () {
8734 return !this._latlngs.length;
8737 closestLayerPoint: function (p) {
8738 var minDistance = Infinity,
8740 closest = L.LineUtil._sqClosestPointOnSegment,
8743 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8744 var points = this._parts[j];
8746 for (var i = 1, len = points.length; i < len; i++) {
8750 var sqDist = closest(p, p1, p2, true);
8752 if (sqDist < minDistance) {
8753 minDistance = sqDist;
8754 minPoint = closest(p, p1, p2);
8759 minPoint.distance = Math.sqrt(minDistance);
8764 // @method getCenter(): LatLng
8765 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
8766 getCenter: function () {
8767 // throws error when not yet added to map as this center calculation requires projected coordinates
8769 throw new Error('Must add layer to map before using getCenter()');
8772 var i, halfDist, segDist, dist, p1, p2, ratio,
8773 points = this._rings[0],
8774 len = points.length;
8776 if (!len) { return null; }
8778 // polyline centroid algorithm; only uses the first ring if there are multiple
8780 for (i = 0, halfDist = 0; i < len - 1; i++) {
8781 halfDist += points[i].distanceTo(points[i + 1]) / 2;
8784 // The line is so small in the current view that all points are on the same pixel.
8785 if (halfDist === 0) {
8786 return this._map.layerPointToLatLng(points[0]);
8789 for (i = 0, dist = 0; i < len - 1; i++) {
8792 segDist = p1.distanceTo(p2);
8795 if (dist > halfDist) {
8796 ratio = (dist - halfDist) / segDist;
8797 return this._map.layerPointToLatLng([
8798 p2.x - ratio * (p2.x - p1.x),
8799 p2.y - ratio * (p2.y - p1.y)
8805 // @method getBounds(): LatLngBounds
8806 // Returns the `LatLngBounds` of the path.
8807 getBounds: function () {
8808 return this._bounds;
8811 // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
8812 // Adds a given point to the polyline. By default, adds to the first ring of
8813 // the polyline in case of a multi-polyline, but can be overridden by passing
8814 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8815 addLatLng: function (latlng, latlngs) {
8816 latlngs = latlngs || this._defaultShape();
8817 latlng = L.latLng(latlng);
8818 latlngs.push(latlng);
8819 this._bounds.extend(latlng);
8820 return this.redraw();
8823 _setLatLngs: function (latlngs) {
8824 this._bounds = new L.LatLngBounds();
8825 this._latlngs = this._convertLatLngs(latlngs);
8828 _defaultShape: function () {
8829 return L.Polyline._flat(this._latlngs) ? this._latlngs : this._latlngs[0];
8832 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8833 _convertLatLngs: function (latlngs) {
8835 flat = L.Polyline._flat(latlngs);
8837 for (var i = 0, len = latlngs.length; i < len; i++) {
8839 result[i] = L.latLng(latlngs[i]);
8840 this._bounds.extend(result[i]);
8842 result[i] = this._convertLatLngs(latlngs[i]);
8849 _project: function () {
8850 var pxBounds = new L.Bounds();
8852 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8854 var w = this._clickTolerance(),
8855 p = new L.Point(w, w);
8857 if (this._bounds.isValid() && pxBounds.isValid()) {
8858 pxBounds.min._subtract(p);
8859 pxBounds.max._add(p);
8860 this._pxBounds = pxBounds;
8864 // recursively turns latlngs into a set of rings with projected coordinates
8865 _projectLatlngs: function (latlngs, result, projectedBounds) {
8866 var flat = latlngs[0] instanceof L.LatLng,
8867 len = latlngs.length,
8872 for (i = 0; i < len; i++) {
8873 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8874 projectedBounds.extend(ring[i]);
8878 for (i = 0; i < len; i++) {
8879 this._projectLatlngs(latlngs[i], result, projectedBounds);
8884 // clip polyline by renderer bounds so that we have less to render for performance
8885 _clipPoints: function () {
8886 var bounds = this._renderer._bounds;
8889 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8893 if (this.options.noClip) {
8894 this._parts = this._rings;
8898 var parts = this._parts,
8899 i, j, k, len, len2, segment, points;
8901 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8902 points = this._rings[i];
8904 for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8905 segment = L.LineUtil.clipSegment(points[j], points[j + 1], bounds, j, true);
8907 if (!segment) { continue; }
8909 parts[k] = parts[k] || [];
8910 parts[k].push(segment[0]);
8912 // if segment goes out of screen, or it's the last one, it's the end of the line part
8913 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8914 parts[k].push(segment[1]);
8921 // simplify each clipped part of the polyline for performance
8922 _simplifyPoints: function () {
8923 var parts = this._parts,
8924 tolerance = this.options.smoothFactor;
8926 for (var i = 0, len = parts.length; i < len; i++) {
8927 parts[i] = L.LineUtil.simplify(parts[i], tolerance);
8931 _update: function () {
8932 if (!this._map) { return; }
8935 this._simplifyPoints();
8939 _updatePath: function () {
8940 this._renderer._updatePoly(this);
8944 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8945 // Instantiates a polyline object given an array of geographical points and
8946 // optionally an options object. You can create a `Polyline` object with
8947 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8948 // of geographic points.
8949 L.polyline = function (latlngs, options) {
8950 return new L.Polyline(latlngs, options);
8953 L.Polyline._flat = function (latlngs) {
8954 // true if it's a flat array of latlngs; false if nested
8955 return !L.Util.isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
8961 * @namespace PolyUtil
8962 * Various utility functions for polygon geometries.
8967 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
8968 * 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)).
8969 * Used by Leaflet to only show polygon points that are on the screen or near, increasing
8970 * performance. Note that polygon points needs different algorithm for clipping
8971 * than polyline, so there's a seperate method for it.
8973 L.PolyUtil.clipPolygon = function (points, bounds, round) {
8975 edges = [1, 4, 2, 8],
8981 for (i = 0, len = points.length; i < len; i++) {
8982 points[i]._code = lu._getBitCode(points[i], bounds);
8985 // for each edge (left, bottom, right, top)
8986 for (k = 0; k < 4; k++) {
8990 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
8994 // if a is inside the clip window
8995 if (!(a._code & edge)) {
8996 // if b is outside the clip window (a->b goes out of screen)
8997 if (b._code & edge) {
8998 p = lu._getEdgeIntersection(b, a, edge, bounds, round);
8999 p._code = lu._getBitCode(p, bounds);
9000 clippedPoints.push(p);
9002 clippedPoints.push(a);
9004 // else if b is inside the clip window (a->b enters the screen)
9005 } else if (!(b._code & edge)) {
9006 p = lu._getEdgeIntersection(b, a, edge, bounds, round);
9007 p._code = lu._getBitCode(p, bounds);
9008 clippedPoints.push(p);
9011 points = clippedPoints;
9022 * @inherits Polyline
9024 * A class for drawing polygon overlays on a map. Extends `Polyline`.
9026 * 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.
9032 * // create a red polygon from an array of LatLng points
9033 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
9035 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
9037 * // zoom the map to the polygon
9038 * map.fitBounds(polygon.getBounds());
9041 * 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:
9045 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
9046 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
9050 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
9054 * [ // first polygon
9055 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
9056 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
9058 * [ // second polygon
9059 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
9065 L.Polygon = L.Polyline.extend({
9071 isEmpty: function () {
9072 return !this._latlngs.length || !this._latlngs[0].length;
9075 getCenter: function () {
9076 // throws error when not yet added to map as this center calculation requires projected coordinates
9078 throw new Error('Must add layer to map before using getCenter()');
9081 var i, j, p1, p2, f, area, x, y, center,
9082 points = this._rings[0],
9083 len = points.length;
9085 if (!len) { return null; }
9087 // polygon centroid algorithm; only uses the first ring if there are multiple
9091 for (i = 0, j = len - 1; i < len; j = i++) {
9095 f = p1.y * p2.x - p2.y * p1.x;
9096 x += (p1.x + p2.x) * f;
9097 y += (p1.y + p2.y) * f;
9102 // Polygon is so small that all points are on same pixel.
9105 center = [x / area, y / area];
9107 return this._map.layerPointToLatLng(center);
9110 _convertLatLngs: function (latlngs) {
9111 var result = L.Polyline.prototype._convertLatLngs.call(this, latlngs),
9112 len = result.length;
9114 // remove last point if it equals first one
9115 if (len >= 2 && result[0] instanceof L.LatLng && result[0].equals(result[len - 1])) {
9121 _setLatLngs: function (latlngs) {
9122 L.Polyline.prototype._setLatLngs.call(this, latlngs);
9123 if (L.Polyline._flat(this._latlngs)) {
9124 this._latlngs = [this._latlngs];
9128 _defaultShape: function () {
9129 return L.Polyline._flat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
9132 _clipPoints: function () {
9133 // polygons need a different clipping algorithm so we redefine that
9135 var bounds = this._renderer._bounds,
9136 w = this.options.weight,
9137 p = new L.Point(w, w);
9139 // increase clip padding by stroke width to avoid stroke on clip edges
9140 bounds = new L.Bounds(bounds.min.subtract(p), bounds.max.add(p));
9143 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
9147 if (this.options.noClip) {
9148 this._parts = this._rings;
9152 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
9153 clipped = L.PolyUtil.clipPolygon(this._rings[i], bounds, true);
9154 if (clipped.length) {
9155 this._parts.push(clipped);
9160 _updatePath: function () {
9161 this._renderer._updatePoly(this, true);
9166 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
9167 L.polygon = function (latlngs, options) {
9168 return new L.Polygon(latlngs, options);
9174 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
9182 * A class for drawing rectangle overlays on a map. Extends `Polygon`.
9187 * // define rectangle geographical bounds
9188 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
9190 * // create an orange rectangle
9191 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
9193 * // zoom the map to the rectangle bounds
9194 * map.fitBounds(bounds);
9200 L.Rectangle = L.Polygon.extend({
9201 initialize: function (latLngBounds, options) {
9202 L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
9205 // @method setBounds(latLngBounds: LatLngBounds): this
9206 // Redraws the rectangle with the passed bounds.
9207 setBounds: function (latLngBounds) {
9208 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
9211 _boundsToLatLngs: function (latLngBounds) {
9212 latLngBounds = L.latLngBounds(latLngBounds);
9214 latLngBounds.getSouthWest(),
9215 latLngBounds.getNorthWest(),
9216 latLngBounds.getNorthEast(),
9217 latLngBounds.getSouthEast()
9223 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
9224 L.rectangle = function (latLngBounds, options) {
9225 return new L.Rectangle(latLngBounds, options);
9231 * @class CircleMarker
9232 * @aka L.CircleMarker
9235 * A circle of a fixed size with radius specified in pixels. Extends `Path`.
9238 L.CircleMarker = L.Path.extend({
9241 // @aka CircleMarker options
9245 // @option radius: Number = 10
9246 // Radius of the circle marker, in pixels
9250 initialize: function (latlng, options) {
9251 L.setOptions(this, options);
9252 this._latlng = L.latLng(latlng);
9253 this._radius = this.options.radius;
9256 // @method setLatLng(latLng: LatLng): this
9257 // Sets the position of a circle marker to a new location.
9258 setLatLng: function (latlng) {
9259 this._latlng = L.latLng(latlng);
9261 return this.fire('move', {latlng: this._latlng});
9264 // @method getLatLng(): LatLng
9265 // Returns the current geographical position of the circle marker
9266 getLatLng: function () {
9267 return this._latlng;
9270 // @method setRadius(radius: Number): this
9271 // Sets the radius of a circle marker. Units are in pixels.
9272 setRadius: function (radius) {
9273 this.options.radius = this._radius = radius;
9274 return this.redraw();
9277 // @method getRadius(): Number
9278 // Returns the current radius of the circle
9279 getRadius: function () {
9280 return this._radius;
9283 setStyle : function (options) {
9284 var radius = options && options.radius || this._radius;
9285 L.Path.prototype.setStyle.call(this, options);
9286 this.setRadius(radius);
9290 _project: function () {
9291 this._point = this._map.latLngToLayerPoint(this._latlng);
9292 this._updateBounds();
9295 _updateBounds: function () {
9296 var r = this._radius,
9297 r2 = this._radiusY || r,
9298 w = this._clickTolerance(),
9299 p = [r + w, r2 + w];
9300 this._pxBounds = new L.Bounds(this._point.subtract(p), this._point.add(p));
9303 _update: function () {
9309 _updatePath: function () {
9310 this._renderer._updateCircle(this);
9313 _empty: function () {
9314 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
9319 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
9320 // Instantiates a circle marker object given a geographical point, and an optional options object.
9321 L.circleMarker = function (latlng, options) {
9322 return new L.CircleMarker(latlng, options);
9330 * @inherits CircleMarker
9332 * A class for drawing circle overlays on a map. Extends `CircleMarker`.
9334 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
9339 * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
9343 L.Circle = L.CircleMarker.extend({
9345 initialize: function (latlng, options, legacyOptions) {
9346 if (typeof options === 'number') {
9347 // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
9348 options = L.extend({}, legacyOptions, {radius: options});
9350 L.setOptions(this, options);
9351 this._latlng = L.latLng(latlng);
9353 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
9356 // @aka Circle options
9357 // @option radius: Number; Radius of the circle, in meters.
9358 this._mRadius = this.options.radius;
9361 // @method setRadius(radius: Number): this
9362 // Sets the radius of a circle. Units are in meters.
9363 setRadius: function (radius) {
9364 this._mRadius = radius;
9365 return this.redraw();
9368 // @method getRadius(): Number
9369 // Returns the current radius of a circle. Units are in meters.
9370 getRadius: function () {
9371 return this._mRadius;
9374 // @method getBounds(): LatLngBounds
9375 // Returns the `LatLngBounds` of the path.
9376 getBounds: function () {
9377 var half = [this._radius, this._radiusY || this._radius];
9379 return new L.LatLngBounds(
9380 this._map.layerPointToLatLng(this._point.subtract(half)),
9381 this._map.layerPointToLatLng(this._point.add(half)));
9384 setStyle: L.Path.prototype.setStyle,
9386 _project: function () {
9388 var lng = this._latlng.lng,
9389 lat = this._latlng.lat,
9391 crs = map.options.crs;
9393 if (crs.distance === L.CRS.Earth.distance) {
9394 var d = Math.PI / 180,
9395 latR = (this._mRadius / L.CRS.Earth.R) / d,
9396 top = map.project([lat + latR, lng]),
9397 bottom = map.project([lat - latR, lng]),
9398 p = top.add(bottom).divideBy(2),
9399 lat2 = map.unproject(p).lat,
9400 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
9401 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
9403 if (isNaN(lngR) || lngR === 0) {
9404 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
9407 this._point = p.subtract(map.getPixelOrigin());
9408 this._radius = isNaN(lngR) ? 0 : Math.max(Math.round(p.x - map.project([lat2, lng - lngR]).x), 1);
9409 this._radiusY = Math.max(Math.round(p.y - top.y), 1);
9412 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
9414 this._point = map.latLngToLayerPoint(this._latlng);
9415 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
9418 this._updateBounds();
9422 // @factory L.circle(latlng: LatLng, options?: Circle options)
9423 // Instantiates a circle object given a geographical point, and an options object
9424 // which contains the circle radius.
9426 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
9427 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
9428 // Do not use in new applications or plugins.
9429 L.circle = function (latlng, options, legacyOptions) {
9430 return new L.Circle(latlng, options, legacyOptions);
9437 * @inherits Renderer
9440 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
9441 * Inherits `Renderer`.
9443 * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
9444 * available in all web browsers, notably Android 2.x and 3.x.
9446 * Although SVG is not available on IE7 and IE8, these browsers support
9447 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
9448 * (a now deprecated technology), and the SVG renderer will fall back to VML in
9453 * Use SVG by default for all paths in the map:
9456 * var map = L.map('map', {
9461 * Use a SVG renderer with extra padding for specific vector geometries:
9464 * var map = L.map('map');
9465 * var myRenderer = L.svg({ padding: 0.5 });
9466 * var line = L.polyline( coordinates, { renderer: myRenderer } );
9467 * var circle = L.circle( center, { renderer: myRenderer } );
9471 L.SVG = L.Renderer.extend({
9473 getEvents: function () {
9474 var events = L.Renderer.prototype.getEvents.call(this);
9475 events.zoomstart = this._onZoomStart;
9479 _initContainer: function () {
9480 this._container = L.SVG.create('svg');
9482 // makes it possible to click through svg root; we'll reset it back in individual paths
9483 this._container.setAttribute('pointer-events', 'none');
9485 this._rootGroup = L.SVG.create('g');
9486 this._container.appendChild(this._rootGroup);
9489 _onZoomStart: function () {
9490 // Drag-then-pinch interactions might mess up the center and zoom.
9491 // In this case, the easiest way to prevent this is re-do the renderer
9492 // bounds and padding when the zooming starts.
9496 _update: function () {
9497 if (this._map._animatingZoom && this._bounds) { return; }
9499 L.Renderer.prototype._update.call(this);
9501 var b = this._bounds,
9503 container = this._container;
9505 // set size of svg-container if changed
9506 if (!this._svgSize || !this._svgSize.equals(size)) {
9507 this._svgSize = size;
9508 container.setAttribute('width', size.x);
9509 container.setAttribute('height', size.y);
9512 // movement: update container viewBox so that we don't have to change coordinates of individual layers
9513 L.DomUtil.setPosition(container, b.min);
9514 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
9516 this.fire('update');
9519 // methods below are called by vector layers implementations
9521 _initPath: function (layer) {
9522 var path = layer._path = L.SVG.create('path');
9525 // @option className: String = null
9526 // Custom class name set on an element. Only for SVG renderer.
9527 if (layer.options.className) {
9528 L.DomUtil.addClass(path, layer.options.className);
9531 if (layer.options.interactive) {
9532 L.DomUtil.addClass(path, 'leaflet-interactive');
9535 this._updateStyle(layer);
9536 this._layers[L.stamp(layer)] = layer;
9539 _addPath: function (layer) {
9540 this._rootGroup.appendChild(layer._path);
9541 layer.addInteractiveTarget(layer._path);
9544 _removePath: function (layer) {
9545 L.DomUtil.remove(layer._path);
9546 layer.removeInteractiveTarget(layer._path);
9547 delete this._layers[L.stamp(layer)];
9550 _updatePath: function (layer) {
9555 _updateStyle: function (layer) {
9556 var path = layer._path,
9557 options = layer.options;
9559 if (!path) { return; }
9561 if (options.stroke) {
9562 path.setAttribute('stroke', options.color);
9563 path.setAttribute('stroke-opacity', options.opacity);
9564 path.setAttribute('stroke-width', options.weight);
9565 path.setAttribute('stroke-linecap', options.lineCap);
9566 path.setAttribute('stroke-linejoin', options.lineJoin);
9568 if (options.dashArray) {
9569 path.setAttribute('stroke-dasharray', options.dashArray);
9571 path.removeAttribute('stroke-dasharray');
9574 if (options.dashOffset) {
9575 path.setAttribute('stroke-dashoffset', options.dashOffset);
9577 path.removeAttribute('stroke-dashoffset');
9580 path.setAttribute('stroke', 'none');
9584 path.setAttribute('fill', options.fillColor || options.color);
9585 path.setAttribute('fill-opacity', options.fillOpacity);
9586 path.setAttribute('fill-rule', options.fillRule || 'evenodd');
9588 path.setAttribute('fill', 'none');
9592 _updatePoly: function (layer, closed) {
9593 this._setPath(layer, L.SVG.pointsToPath(layer._parts, closed));
9596 _updateCircle: function (layer) {
9597 var p = layer._point,
9599 r2 = layer._radiusY || r,
9600 arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
9602 // drawing a circle with two half-arcs
9603 var d = layer._empty() ? 'M0 0' :
9604 'M' + (p.x - r) + ',' + p.y +
9605 arc + (r * 2) + ',0 ' +
9606 arc + (-r * 2) + ',0 ';
9608 this._setPath(layer, d);
9611 _setPath: function (layer, path) {
9612 layer._path.setAttribute('d', path);
9615 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
9616 _bringToFront: function (layer) {
9617 L.DomUtil.toFront(layer._path);
9620 _bringToBack: function (layer) {
9621 L.DomUtil.toBack(layer._path);
9626 // @namespace SVG; @section
9627 // There are several static functions which can be called without instantiating L.SVG:
9629 // @function create(name: String): SVGElement
9630 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
9631 // corresponding to the class name passed. For example, using 'line' will return
9632 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
9633 create: function (name) {
9634 return document.createElementNS('http://www.w3.org/2000/svg', name);
9637 // @function pointsToPath(rings: Point[], closed: Boolean): String
9638 // Generates a SVG path string for multiple rings, with each ring turning
9639 // into "M..L..L.." instructions
9640 pointsToPath: function (rings, closed) {
9642 i, j, len, len2, points, p;
9644 for (i = 0, len = rings.length; i < len; i++) {
9647 for (j = 0, len2 = points.length; j < len2; j++) {
9649 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
9652 // closes the ring for polygons; "x" is VML syntax
9653 str += closed ? (L.Browser.svg ? 'z' : 'x') : '';
9656 // SVG complains about empty path strings
9657 return str || 'M0 0';
9661 // @namespace Browser; @property svg: Boolean
9662 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
9663 L.Browser.svg = !!(document.createElementNS && L.SVG.create('svg').createSVGRect);
9667 // @factory L.svg(options?: Renderer options)
9668 // Creates a SVG renderer with the given options.
9669 L.svg = function (options) {
9670 return L.Browser.svg || L.Browser.vml ? new L.SVG(options) : null;
9676 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
9682 * 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.
9684 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
9685 * with old versions of Internet Explorer.
9688 // @namespace Browser; @property vml: Boolean
9689 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
9690 L.Browser.vml = !L.Browser.svg && (function () {
9692 var div = document.createElement('div');
9693 div.innerHTML = '<v:shape adj="1"/>';
9695 var shape = div.firstChild;
9696 shape.style.behavior = 'url(#default#VML)';
9698 return shape && (typeof shape.adj === 'object');
9705 // redefine some SVG methods to handle VML syntax which is similar but with some differences
9706 L.SVG.include(!L.Browser.vml ? {} : {
9708 _initContainer: function () {
9709 this._container = L.DomUtil.create('div', 'leaflet-vml-container');
9712 _update: function () {
9713 if (this._map._animatingZoom) { return; }
9714 L.Renderer.prototype._update.call(this);
9715 this.fire('update');
9718 _initPath: function (layer) {
9719 var container = layer._container = L.SVG.create('shape');
9721 L.DomUtil.addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
9723 container.coordsize = '1 1';
9725 layer._path = L.SVG.create('path');
9726 container.appendChild(layer._path);
9728 this._updateStyle(layer);
9729 this._layers[L.stamp(layer)] = layer;
9732 _addPath: function (layer) {
9733 var container = layer._container;
9734 this._container.appendChild(container);
9736 if (layer.options.interactive) {
9737 layer.addInteractiveTarget(container);
9741 _removePath: function (layer) {
9742 var container = layer._container;
9743 L.DomUtil.remove(container);
9744 layer.removeInteractiveTarget(container);
9745 delete this._layers[L.stamp(layer)];
9748 _updateStyle: function (layer) {
9749 var stroke = layer._stroke,
9751 options = layer.options,
9752 container = layer._container;
9754 container.stroked = !!options.stroke;
9755 container.filled = !!options.fill;
9757 if (options.stroke) {
9759 stroke = layer._stroke = L.SVG.create('stroke');
9761 container.appendChild(stroke);
9762 stroke.weight = options.weight + 'px';
9763 stroke.color = options.color;
9764 stroke.opacity = options.opacity;
9766 if (options.dashArray) {
9767 stroke.dashStyle = L.Util.isArray(options.dashArray) ?
9768 options.dashArray.join(' ') :
9769 options.dashArray.replace(/( *, *)/g, ' ');
9771 stroke.dashStyle = '';
9773 stroke.endcap = options.lineCap.replace('butt', 'flat');
9774 stroke.joinstyle = options.lineJoin;
9776 } else if (stroke) {
9777 container.removeChild(stroke);
9778 layer._stroke = null;
9783 fill = layer._fill = L.SVG.create('fill');
9785 container.appendChild(fill);
9786 fill.color = options.fillColor || options.color;
9787 fill.opacity = options.fillOpacity;
9790 container.removeChild(fill);
9795 _updateCircle: function (layer) {
9796 var p = layer._point.round(),
9797 r = Math.round(layer._radius),
9798 r2 = Math.round(layer._radiusY || r);
9800 this._setPath(layer, layer._empty() ? 'M0 0' :
9801 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
9804 _setPath: function (layer, path) {
9805 layer._path.v = path;
9808 _bringToFront: function (layer) {
9809 L.DomUtil.toFront(layer._container);
9812 _bringToBack: function (layer) {
9813 L.DomUtil.toBack(layer._container);
9817 if (L.Browser.vml) {
9818 L.SVG.create = (function () {
9820 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
9821 return function (name) {
9822 return document.createElement('<lvml:' + name + ' class="lvml">');
9825 return function (name) {
9826 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
9836 * @inherits Renderer
9839 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
9840 * Inherits `Renderer`.
9842 * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
9843 * available in all web browsers, notably IE8, and overlapping geometries might
9844 * not display properly in some edge cases.
9848 * Use Canvas by default for all paths in the map:
9851 * var map = L.map('map', {
9852 * renderer: L.canvas()
9856 * Use a Canvas renderer with extra padding for specific vector geometries:
9859 * var map = L.map('map');
9860 * var myRenderer = L.canvas({ padding: 0.5 });
9861 * var line = L.polyline( coordinates, { renderer: myRenderer } );
9862 * var circle = L.circle( center, { renderer: myRenderer } );
9866 L.Canvas = L.Renderer.extend({
9867 getEvents: function () {
9868 var events = L.Renderer.prototype.getEvents.call(this);
9869 events.viewprereset = this._onViewPreReset;
9873 _onViewPreReset: function () {
9874 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
9875 this._postponeUpdatePaths = true;
9878 onAdd: function () {
9879 L.Renderer.prototype.onAdd.call(this);
9881 // Redraw vectors since canvas is cleared upon removal,
9882 // in case of removing the renderer itself from the map.
9886 _initContainer: function () {
9887 var container = this._container = document.createElement('canvas');
9890 .on(container, 'mousemove', L.Util.throttle(this._onMouseMove, 32, this), this)
9891 .on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this)
9892 .on(container, 'mouseout', this._handleMouseOut, this);
9894 this._ctx = container.getContext('2d');
9897 _updatePaths: function () {
9898 if (this._postponeUpdatePaths) { return; }
9901 this._redrawBounds = null;
9902 for (var id in this._layers) {
9903 layer = this._layers[id];
9909 _update: function () {
9910 if (this._map._animatingZoom && this._bounds) { return; }
9912 this._drawnLayers = {};
9914 L.Renderer.prototype._update.call(this);
9916 var b = this._bounds,
9917 container = this._container,
9919 m = L.Browser.retina ? 2 : 1;
9921 L.DomUtil.setPosition(container, b.min);
9923 // set canvas size (also clearing it); use double size on retina
9924 container.width = m * size.x;
9925 container.height = m * size.y;
9926 container.style.width = size.x + 'px';
9927 container.style.height = size.y + 'px';
9929 if (L.Browser.retina) {
9930 this._ctx.scale(2, 2);
9933 // translate so we use the same path coordinates after canvas element moves
9934 this._ctx.translate(-b.min.x, -b.min.y);
9936 // Tell paths to redraw themselves
9937 this.fire('update');
9940 _reset: function () {
9941 L.Renderer.prototype._reset.call(this);
9943 if (this._postponeUpdatePaths) {
9944 this._postponeUpdatePaths = false;
9945 this._updatePaths();
9949 _initPath: function (layer) {
9950 this._updateDashArray(layer);
9951 this._layers[L.stamp(layer)] = layer;
9953 var order = layer._order = {
9955 prev: this._drawLast,
9958 if (this._drawLast) { this._drawLast.next = order; }
9959 this._drawLast = order;
9960 this._drawFirst = this._drawFirst || this._drawLast;
9963 _addPath: function (layer) {
9964 this._requestRedraw(layer);
9967 _removePath: function (layer) {
9968 var order = layer._order;
9969 var next = order.next;
9970 var prev = order.prev;
9975 this._drawLast = prev;
9980 this._drawFirst = next;
9983 delete layer._order;
9985 delete this._layers[L.stamp(layer)];
9987 this._requestRedraw(layer);
9990 _updatePath: function (layer) {
9991 // Redraw the union of the layer's old pixel
9992 // bounds and the new pixel bounds.
9993 this._extendRedrawBounds(layer);
9996 // The redraw will extend the redraw bounds
9997 // with the new pixel bounds.
9998 this._requestRedraw(layer);
10001 _updateStyle: function (layer) {
10002 this._updateDashArray(layer);
10003 this._requestRedraw(layer);
10006 _updateDashArray: function (layer) {
10007 if (layer.options.dashArray) {
10008 var parts = layer.options.dashArray.split(','),
10011 for (i = 0; i < parts.length; i++) {
10012 dashArray.push(Number(parts[i]));
10014 layer.options._dashArray = dashArray;
10018 _requestRedraw: function (layer) {
10019 if (!this._map) { return; }
10021 this._extendRedrawBounds(layer);
10022 this._redrawRequest = this._redrawRequest || L.Util.requestAnimFrame(this._redraw, this);
10025 _extendRedrawBounds: function (layer) {
10026 var padding = (layer.options.weight || 0) + 1;
10027 this._redrawBounds = this._redrawBounds || new L.Bounds();
10028 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
10029 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
10032 _redraw: function () {
10033 this._redrawRequest = null;
10035 if (this._redrawBounds) {
10036 this._redrawBounds.min._floor();
10037 this._redrawBounds.max._ceil();
10040 this._clear(); // clear layers in redraw bounds
10041 this._draw(); // draw layers
10043 this._redrawBounds = null;
10046 _clear: function () {
10047 var bounds = this._redrawBounds;
10049 var size = bounds.getSize();
10050 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
10052 this._ctx.clearRect(0, 0, this._container.width, this._container.height);
10056 _draw: function () {
10057 var layer, bounds = this._redrawBounds;
10060 var size = bounds.getSize();
10061 this._ctx.beginPath();
10062 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
10066 this._drawing = true;
10068 for (var order = this._drawFirst; order; order = order.next) {
10069 layer = order.layer;
10070 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
10071 layer._updatePath();
10075 this._drawing = false;
10077 this._ctx.restore(); // Restore state before clipping.
10080 _updatePoly: function (layer, closed) {
10081 if (!this._drawing) { return; }
10084 parts = layer._parts,
10085 len = parts.length,
10088 if (!len) { return; }
10090 this._drawnLayers[layer._leaflet_id] = layer;
10094 if (ctx.setLineDash) {
10095 ctx.setLineDash(layer.options && layer.options._dashArray || []);
10098 for (i = 0; i < len; i++) {
10099 for (j = 0, len2 = parts[i].length; j < len2; j++) {
10101 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
10108 this._fillStroke(ctx, layer);
10110 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
10113 _updateCircle: function (layer) {
10115 if (!this._drawing || layer._empty()) { return; }
10117 var p = layer._point,
10120 s = (layer._radiusY || r) / r;
10122 this._drawnLayers[layer._leaflet_id] = layer;
10130 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
10136 this._fillStroke(ctx, layer);
10139 _fillStroke: function (ctx, layer) {
10140 var options = layer.options;
10142 if (options.fill) {
10143 ctx.globalAlpha = options.fillOpacity;
10144 ctx.fillStyle = options.fillColor || options.color;
10145 ctx.fill(options.fillRule || 'evenodd');
10148 if (options.stroke && options.weight !== 0) {
10149 ctx.globalAlpha = options.opacity;
10150 ctx.lineWidth = options.weight;
10151 ctx.strokeStyle = options.color;
10152 ctx.lineCap = options.lineCap;
10153 ctx.lineJoin = options.lineJoin;
10158 // Canvas obviously doesn't have mouse events for individual drawn objects,
10159 // so we emulate that by calculating what's under the mouse on mousemove/click manually
10161 _onClick: function (e) {
10162 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
10164 for (var order = this._drawFirst; order; order = order.next) {
10165 layer = order.layer;
10166 if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
10167 clickedLayer = layer;
10170 if (clickedLayer) {
10171 L.DomEvent._fakeStop(e);
10172 this._fireEvent([clickedLayer], e);
10176 _onMouseMove: function (e) {
10177 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
10179 var point = this._map.mouseEventToLayerPoint(e);
10180 this._handleMouseHover(e, point);
10184 _handleMouseOut: function (e) {
10185 var layer = this._hoveredLayer;
10187 // if we're leaving the layer, fire mouseout
10188 L.DomUtil.removeClass(this._container, 'leaflet-interactive');
10189 this._fireEvent([layer], e, 'mouseout');
10190 this._hoveredLayer = null;
10194 _handleMouseHover: function (e, point) {
10195 var layer, candidateHoveredLayer;
10197 for (var order = this._drawFirst; order; order = order.next) {
10198 layer = order.layer;
10199 if (layer.options.interactive && layer._containsPoint(point)) {
10200 candidateHoveredLayer = layer;
10204 if (candidateHoveredLayer !== this._hoveredLayer) {
10205 this._handleMouseOut(e);
10207 if (candidateHoveredLayer) {
10208 L.DomUtil.addClass(this._container, 'leaflet-interactive'); // change cursor
10209 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
10210 this._hoveredLayer = candidateHoveredLayer;
10214 if (this._hoveredLayer) {
10215 this._fireEvent([this._hoveredLayer], e);
10219 _fireEvent: function (layers, e, type) {
10220 this._map._fireDOMEvent(e, type || e.type, layers);
10223 _bringToFront: function (layer) {
10224 var order = layer._order;
10225 var next = order.next;
10226 var prev = order.prev;
10237 // Update first entry unless this is the
10239 this._drawFirst = next;
10242 order.prev = this._drawLast;
10243 this._drawLast.next = order;
10246 this._drawLast = order;
10248 this._requestRedraw(layer);
10251 _bringToBack: function (layer) {
10252 var order = layer._order;
10253 var next = order.next;
10254 var prev = order.prev;
10265 // Update last entry unless this is the
10267 this._drawLast = prev;
10272 order.next = this._drawFirst;
10273 this._drawFirst.prev = order;
10274 this._drawFirst = order;
10276 this._requestRedraw(layer);
10280 // @namespace Browser; @property canvas: Boolean
10281 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
10282 L.Browser.canvas = (function () {
10283 return !!document.createElement('canvas').getContext;
10286 // @namespace Canvas
10287 // @factory L.canvas(options?: Renderer options)
10288 // Creates a Canvas renderer with the given options.
10289 L.canvas = function (options) {
10290 return L.Browser.canvas ? new L.Canvas(options) : null;
10293 L.Polyline.prototype._containsPoint = function (p, closed) {
10294 var i, j, k, len, len2, part,
10295 w = this._clickTolerance();
10297 if (!this._pxBounds.contains(p)) { return false; }
10299 // hit detection for polylines
10300 for (i = 0, len = this._parts.length; i < len; i++) {
10301 part = this._parts[i];
10303 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
10304 if (!closed && (j === 0)) { continue; }
10306 if (L.LineUtil.pointToSegmentDistance(p, part[k], part[j]) <= w) {
10314 L.Polygon.prototype._containsPoint = function (p) {
10315 var inside = false,
10316 part, p1, p2, i, j, k, len, len2;
10318 if (!this._pxBounds.contains(p)) { return false; }
10320 // ray casting algorithm for detecting if point is in polygon
10321 for (i = 0, len = this._parts.length; i < len; i++) {
10322 part = this._parts[i];
10324 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
10328 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)) {
10334 // also check if it's on polygon stroke
10335 return inside || L.Polyline.prototype._containsPoint.call(this, p, true);
10338 L.CircleMarker.prototype._containsPoint = function (p) {
10339 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
10347 * @inherits FeatureGroup
10349 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
10350 * GeoJSON data and display it on the map. Extends `FeatureGroup`.
10355 * L.geoJSON(data, {
10356 * style: function (feature) {
10357 * return {color: feature.properties.color};
10359 * }).bindPopup(function (layer) {
10360 * return layer.feature.properties.description;
10365 L.GeoJSON = L.FeatureGroup.extend({
10368 * @aka GeoJSON options
10370 * @option pointToLayer: Function = *
10371 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
10372 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
10373 * The default is to spawn a default `Marker`:
10375 * function(geoJsonPoint, latlng) {
10376 * return L.marker(latlng);
10380 * @option style: Function = *
10381 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
10382 * called internally when data is added.
10383 * The default value is to not override any defaults:
10385 * function (geoJsonFeature) {
10390 * @option onEachFeature: Function = *
10391 * A `Function` that will be called once for each created `Feature`, after it has
10392 * been created and styled. Useful for attaching events and popups to features.
10393 * The default is to do nothing with the newly created layers:
10395 * function (feature, layer) {}
10398 * @option filter: Function = *
10399 * A `Function` that will be used to decide whether to include a feature or not.
10400 * The default is to include all features:
10402 * function (geoJsonFeature) {
10406 * Note: dynamically changing the `filter` option will have effect only on newly
10407 * added data. It will _not_ re-evaluate already included features.
10409 * @option coordsToLatLng: Function = *
10410 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
10411 * The default is the `coordsToLatLng` static method.
10414 initialize: function (geojson, options) {
10415 L.setOptions(this, options);
10420 this.addData(geojson);
10424 // @method addData( <GeoJSON> data ): this
10425 // Adds a GeoJSON object to the layer.
10426 addData: function (geojson) {
10427 var features = L.Util.isArray(geojson) ? geojson : geojson.features,
10431 for (i = 0, len = features.length; i < len; i++) {
10432 // only add this if geometry or geometries are set and not null
10433 feature = features[i];
10434 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
10435 this.addData(feature);
10441 var options = this.options;
10443 if (options.filter && !options.filter(geojson)) { return this; }
10445 var layer = L.GeoJSON.geometryToLayer(geojson, options);
10449 layer.feature = L.GeoJSON.asFeature(geojson);
10451 layer.defaultOptions = layer.options;
10452 this.resetStyle(layer);
10454 if (options.onEachFeature) {
10455 options.onEachFeature(geojson, layer);
10458 return this.addLayer(layer);
10461 // @method resetStyle( <Path> layer ): this
10462 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
10463 resetStyle: function (layer) {
10464 // reset any custom styles
10465 layer.options = L.Util.extend({}, layer.defaultOptions);
10466 this._setLayerStyle(layer, this.options.style);
10470 // @method setStyle( <Function> style ): this
10471 // Changes styles of GeoJSON vector layers with the given style function.
10472 setStyle: function (style) {
10473 return this.eachLayer(function (layer) {
10474 this._setLayerStyle(layer, style);
10478 _setLayerStyle: function (layer, style) {
10479 if (typeof style === 'function') {
10480 style = style(layer.feature);
10482 if (layer.setStyle) {
10483 layer.setStyle(style);
10489 // There are several static functions which can be called without instantiating L.GeoJSON:
10490 L.extend(L.GeoJSON, {
10491 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
10492 // Creates a `Layer` from a given GeoJSON feature. Can use a custom
10493 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
10494 // functions if provided as options.
10495 geometryToLayer: function (geojson, options) {
10497 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
10498 coords = geometry ? geometry.coordinates : null,
10500 pointToLayer = options && options.pointToLayer,
10501 coordsToLatLng = options && options.coordsToLatLng || this.coordsToLatLng,
10502 latlng, latlngs, i, len;
10504 if (!coords && !geometry) {
10508 switch (geometry.type) {
10510 latlng = coordsToLatLng(coords);
10511 return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
10514 for (i = 0, len = coords.length; i < len; i++) {
10515 latlng = coordsToLatLng(coords[i]);
10516 layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng));
10518 return new L.FeatureGroup(layers);
10521 case 'MultiLineString':
10522 latlngs = this.coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, coordsToLatLng);
10523 return new L.Polyline(latlngs, options);
10526 case 'MultiPolygon':
10527 latlngs = this.coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, coordsToLatLng);
10528 return new L.Polygon(latlngs, options);
10530 case 'GeometryCollection':
10531 for (i = 0, len = geometry.geometries.length; i < len; i++) {
10532 var layer = this.geometryToLayer({
10533 geometry: geometry.geometries[i],
10535 properties: geojson.properties
10539 layers.push(layer);
10542 return new L.FeatureGroup(layers);
10545 throw new Error('Invalid GeoJSON object.');
10549 // @function coordsToLatLng(coords: Array): LatLng
10550 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
10551 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
10552 coordsToLatLng: function (coords) {
10553 return new L.LatLng(coords[1], coords[0], coords[2]);
10556 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
10557 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
10558 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
10559 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
10560 coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) {
10563 for (var i = 0, len = coords.length, latlng; i < len; i++) {
10564 latlng = levelsDeep ?
10565 this.coordsToLatLngs(coords[i], levelsDeep - 1, coordsToLatLng) :
10566 (coordsToLatLng || this.coordsToLatLng)(coords[i]);
10568 latlngs.push(latlng);
10574 // @function latLngToCoords(latlng: LatLng): Array
10575 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
10576 latLngToCoords: function (latlng) {
10577 return latlng.alt !== undefined ?
10578 [latlng.lng, latlng.lat, latlng.alt] :
10579 [latlng.lng, latlng.lat];
10582 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
10583 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
10584 // `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.
10585 latLngsToCoords: function (latlngs, levelsDeep, closed) {
10588 for (var i = 0, len = latlngs.length; i < len; i++) {
10589 coords.push(levelsDeep ?
10590 L.GeoJSON.latLngsToCoords(latlngs[i], levelsDeep - 1, closed) :
10591 L.GeoJSON.latLngToCoords(latlngs[i]));
10594 if (!levelsDeep && closed) {
10595 coords.push(coords[0]);
10601 getFeature: function (layer, newGeometry) {
10602 return layer.feature ?
10603 L.extend({}, layer.feature, {geometry: newGeometry}) :
10604 L.GeoJSON.asFeature(newGeometry);
10607 // @function asFeature(geojson: Object): Object
10608 // Normalize GeoJSON geometries/features into GeoJSON features.
10609 asFeature: function (geojson) {
10610 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
10622 var PointToGeoJSON = {
10623 toGeoJSON: function () {
10624 return L.GeoJSON.getFeature(this, {
10626 coordinates: L.GeoJSON.latLngToCoords(this.getLatLng())
10631 // @namespace Marker
10632 // @method toGeoJSON(): Object
10633 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
10634 L.Marker.include(PointToGeoJSON);
10636 // @namespace CircleMarker
10637 // @method toGeoJSON(): Object
10638 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
10639 L.Circle.include(PointToGeoJSON);
10640 L.CircleMarker.include(PointToGeoJSON);
10643 // @namespace Polyline
10644 // @method toGeoJSON(): Object
10645 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
10646 L.Polyline.prototype.toGeoJSON = function () {
10647 var multi = !L.Polyline._flat(this._latlngs);
10649 var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 1 : 0);
10651 return L.GeoJSON.getFeature(this, {
10652 type: (multi ? 'Multi' : '') + 'LineString',
10653 coordinates: coords
10657 // @namespace Polygon
10658 // @method toGeoJSON(): Object
10659 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
10660 L.Polygon.prototype.toGeoJSON = function () {
10661 var holes = !L.Polyline._flat(this._latlngs),
10662 multi = holes && !L.Polyline._flat(this._latlngs[0]);
10664 var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true);
10670 return L.GeoJSON.getFeature(this, {
10671 type: (multi ? 'Multi' : '') + 'Polygon',
10672 coordinates: coords
10677 // @namespace LayerGroup
10678 L.LayerGroup.include({
10679 toMultiPoint: function () {
10682 this.eachLayer(function (layer) {
10683 coords.push(layer.toGeoJSON().geometry.coordinates);
10686 return L.GeoJSON.getFeature(this, {
10687 type: 'MultiPoint',
10688 coordinates: coords
10692 // @method toGeoJSON(): Object
10693 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `GeometryCollection`).
10694 toGeoJSON: function () {
10696 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
10698 if (type === 'MultiPoint') {
10699 return this.toMultiPoint();
10702 var isGeometryCollection = type === 'GeometryCollection',
10705 this.eachLayer(function (layer) {
10706 if (layer.toGeoJSON) {
10707 var json = layer.toGeoJSON();
10708 jsons.push(isGeometryCollection ? json.geometry : L.GeoJSON.asFeature(json));
10712 if (isGeometryCollection) {
10713 return L.GeoJSON.getFeature(this, {
10715 type: 'GeometryCollection'
10720 type: 'FeatureCollection',
10726 // @namespace GeoJSON
10727 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
10728 // Creates a GeoJSON layer. Optionally accepts an object in
10729 // [GeoJSON format](http://geojson.org/geojson-spec.html) to display on the map
10730 // (you can alternatively add it later with `addData` method) and an `options` object.
10731 L.geoJSON = function (geojson, options) {
10732 return new L.GeoJSON(geojson, options);
10734 // Backward compatibility.
10735 L.geoJson = L.geoJSON;
10742 * @inherits Evented
10744 * A class for making DOM elements draggable (including touch support).
10745 * Used internally for map and marker dragging. Only works for elements
10746 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
10750 * var draggable = new L.Draggable(elementToDrag);
10751 * draggable.enable();
10755 L.Draggable = L.Evented.extend({
10758 // @option clickTolerance: Number = 3
10759 // The max number of pixels a user can shift the mouse pointer during a click
10760 // for it to be considered a valid click (as opposed to a mouse drag).
10765 START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'],
10767 mousedown: 'mouseup',
10768 touchstart: 'touchend',
10769 pointerdown: 'touchend',
10770 MSPointerDown: 'touchend'
10773 mousedown: 'mousemove',
10774 touchstart: 'touchmove',
10775 pointerdown: 'touchmove',
10776 MSPointerDown: 'touchmove'
10780 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline: Boolean)
10781 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
10782 initialize: function (element, dragStartTarget, preventOutline) {
10783 this._element = element;
10784 this._dragStartTarget = dragStartTarget || element;
10785 this._preventOutline = preventOutline;
10788 // @method enable()
10789 // Enables the dragging ability
10790 enable: function () {
10791 if (this._enabled) { return; }
10793 L.DomEvent.on(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this);
10795 this._enabled = true;
10798 // @method disable()
10799 // Disables the dragging ability
10800 disable: function () {
10801 if (!this._enabled) { return; }
10803 // If we're currently dragging this draggable,
10804 // disabling it counts as first ending the drag.
10805 if (L.Draggable._dragging === this) {
10809 L.DomEvent.off(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this);
10811 this._enabled = false;
10812 this._moved = false;
10815 _onDown: function (e) {
10816 // Ignore simulated events, since we handle both touch and
10817 // mouse explicitly; otherwise we risk getting duplicates of
10818 // touch events, see #4315.
10819 // Also ignore the event if disabled; this happens in IE11
10820 // under some circumstances, see #3666.
10821 if (e._simulated || !this._enabled) { return; }
10823 this._moved = false;
10825 if (L.DomUtil.hasClass(this._element, 'leaflet-zoom-anim')) { return; }
10827 if (L.Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
10828 L.Draggable._dragging = this; // Prevent dragging multiple objects at once.
10830 if (this._preventOutline) {
10831 L.DomUtil.preventOutline(this._element);
10834 L.DomUtil.disableImageDrag();
10835 L.DomUtil.disableTextSelection();
10837 if (this._moving) { return; }
10839 // @event down: Event
10840 // Fired when a drag is about to start.
10843 var first = e.touches ? e.touches[0] : e;
10845 this._startPoint = new L.Point(first.clientX, first.clientY);
10848 .on(document, L.Draggable.MOVE[e.type], this._onMove, this)
10849 .on(document, L.Draggable.END[e.type], this._onUp, this);
10852 _onMove: function (e) {
10853 // Ignore simulated events, since we handle both touch and
10854 // mouse explicitly; otherwise we risk getting duplicates of
10855 // touch events, see #4315.
10856 // Also ignore the event if disabled; this happens in IE11
10857 // under some circumstances, see #3666.
10858 if (e._simulated || !this._enabled) { return; }
10860 if (e.touches && e.touches.length > 1) {
10861 this._moved = true;
10865 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
10866 newPoint = new L.Point(first.clientX, first.clientY),
10867 offset = newPoint.subtract(this._startPoint);
10869 if (!offset.x && !offset.y) { return; }
10870 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
10872 L.DomEvent.preventDefault(e);
10874 if (!this._moved) {
10875 // @event dragstart: Event
10876 // Fired when a drag starts
10877 this.fire('dragstart');
10879 this._moved = true;
10880 this._startPos = L.DomUtil.getPosition(this._element).subtract(offset);
10882 L.DomUtil.addClass(document.body, 'leaflet-dragging');
10884 this._lastTarget = e.target || e.srcElement;
10885 // IE and Edge do not give the <use> element, so fetch it
10887 if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
10888 this._lastTarget = this._lastTarget.correspondingUseElement;
10890 L.DomUtil.addClass(this._lastTarget, 'leaflet-drag-target');
10893 this._newPos = this._startPos.add(offset);
10894 this._moving = true;
10896 L.Util.cancelAnimFrame(this._animRequest);
10897 this._lastEvent = e;
10898 this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true);
10901 _updatePosition: function () {
10902 var e = {originalEvent: this._lastEvent};
10904 // @event predrag: Event
10905 // Fired continuously during dragging *before* each corresponding
10906 // update of the element's position.
10907 this.fire('predrag', e);
10908 L.DomUtil.setPosition(this._element, this._newPos);
10910 // @event drag: Event
10911 // Fired continuously during dragging.
10912 this.fire('drag', e);
10915 _onUp: function (e) {
10916 // Ignore simulated events, since we handle both touch and
10917 // mouse explicitly; otherwise we risk getting duplicates of
10918 // touch events, see #4315.
10919 // Also ignore the event if disabled; this happens in IE11
10920 // under some circumstances, see #3666.
10921 if (e._simulated || !this._enabled) { return; }
10925 finishDrag: function () {
10926 L.DomUtil.removeClass(document.body, 'leaflet-dragging');
10928 if (this._lastTarget) {
10929 L.DomUtil.removeClass(this._lastTarget, 'leaflet-drag-target');
10930 this._lastTarget = null;
10933 for (var i in L.Draggable.MOVE) {
10935 .off(document, L.Draggable.MOVE[i], this._onMove, this)
10936 .off(document, L.Draggable.END[i], this._onUp, this);
10939 L.DomUtil.enableImageDrag();
10940 L.DomUtil.enableTextSelection();
10942 if (this._moved && this._moving) {
10943 // ensure drag is not fired after dragend
10944 L.Util.cancelAnimFrame(this._animRequest);
10946 // @event dragend: DragEndEvent
10947 // Fired when the drag ends.
10948 this.fire('dragend', {
10949 distance: this._newPos.distanceTo(this._startPos)
10953 this._moving = false;
10954 L.Draggable._dragging = false;
10962 L.Handler is a base class for handler classes that are used internally to inject
10963 interaction features like dragging to classes like Map and Marker.
10968 // Abstract class for map interaction handlers
10970 L.Handler = L.Class.extend({
10971 initialize: function (map) {
10975 // @method enable(): this
10976 // Enables the handler
10977 enable: function () {
10978 if (this._enabled) { return this; }
10980 this._enabled = true;
10985 // @method disable(): this
10986 // Disables the handler
10987 disable: function () {
10988 if (!this._enabled) { return this; }
10990 this._enabled = false;
10991 this.removeHooks();
10995 // @method enabled(): Boolean
10996 // Returns `true` if the handler is enabled
10997 enabled: function () {
10998 return !!this._enabled;
11001 // @section Extension methods
11002 // Classes inheriting from `Handler` must implement the two following methods:
11003 // @method addHooks()
11004 // Called when the handler is enabled, should add event hooks.
11005 // @method removeHooks()
11006 // Called when the handler is disabled, should remove the event hooks added previously.
11012 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
11016 // @section Interaction Options
11017 L.Map.mergeOptions({
11018 // @option dragging: Boolean = true
11019 // Whether the map be draggable with mouse/touch or not.
11022 // @section Panning Inertia Options
11023 // @option inertia: Boolean = *
11024 // If enabled, panning of the map will have an inertia effect where
11025 // the map builds momentum while dragging and continues moving in
11026 // the same direction for some time. Feels especially nice on touch
11027 // devices. Enabled by default unless running on old Android devices.
11028 inertia: !L.Browser.android23,
11030 // @option inertiaDeceleration: Number = 3000
11031 // The rate with which the inertial movement slows down, in pixels/second².
11032 inertiaDeceleration: 3400, // px/s^2
11034 // @option inertiaMaxSpeed: Number = Infinity
11035 // Max speed of the inertial movement, in pixels/second.
11036 inertiaMaxSpeed: Infinity, // px/s
11038 // @option easeLinearity: Number = 0.2
11039 easeLinearity: 0.2,
11041 // TODO refactor, move to CRS
11042 // @option worldCopyJump: Boolean = false
11043 // With this option enabled, the map tracks when you pan to another "copy"
11044 // of the world and seamlessly jumps to the original one so that all overlays
11045 // like markers and vector layers are still visible.
11046 worldCopyJump: false,
11048 // @option maxBoundsViscosity: Number = 0.0
11049 // If `maxBounds` is set, this option will control how solid the bounds
11050 // are when dragging the map around. The default value of `0.0` allows the
11051 // user to drag outside the bounds at normal speed, higher values will
11052 // slow down map dragging outside bounds, and `1.0` makes the bounds fully
11053 // solid, preventing the user from dragging outside the bounds.
11054 maxBoundsViscosity: 0.0
11057 L.Map.Drag = L.Handler.extend({
11058 addHooks: function () {
11059 if (!this._draggable) {
11060 var map = this._map;
11062 this._draggable = new L.Draggable(map._mapPane, map._container);
11064 this._draggable.on({
11065 down: this._onDown,
11066 dragstart: this._onDragStart,
11067 drag: this._onDrag,
11068 dragend: this._onDragEnd
11071 this._draggable.on('predrag', this._onPreDragLimit, this);
11072 if (map.options.worldCopyJump) {
11073 this._draggable.on('predrag', this._onPreDragWrap, this);
11074 map.on('zoomend', this._onZoomEnd, this);
11076 map.whenReady(this._onZoomEnd, this);
11079 L.DomUtil.addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
11080 this._draggable.enable();
11081 this._positions = [];
11085 removeHooks: function () {
11086 L.DomUtil.removeClass(this._map._container, 'leaflet-grab');
11087 L.DomUtil.removeClass(this._map._container, 'leaflet-touch-drag');
11088 this._draggable.disable();
11091 moved: function () {
11092 return this._draggable && this._draggable._moved;
11095 moving: function () {
11096 return this._draggable && this._draggable._moving;
11099 _onDown: function () {
11103 _onDragStart: function () {
11104 var map = this._map;
11106 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
11107 var bounds = L.latLngBounds(this._map.options.maxBounds);
11109 this._offsetLimit = L.bounds(
11110 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
11111 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
11112 .add(this._map.getSize()));
11114 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
11116 this._offsetLimit = null;
11121 .fire('dragstart');
11123 if (map.options.inertia) {
11124 this._positions = [];
11129 _onDrag: function (e) {
11130 if (this._map.options.inertia) {
11131 var time = this._lastTime = +new Date(),
11132 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
11134 this._positions.push(pos);
11135 this._times.push(time);
11137 if (time - this._times[0] > 50) {
11138 this._positions.shift();
11139 this._times.shift();
11148 _onZoomEnd: function () {
11149 var pxCenter = this._map.getSize().divideBy(2),
11150 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
11152 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
11153 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
11156 _viscousLimit: function (value, threshold) {
11157 return value - (value - threshold) * this._viscosity;
11160 _onPreDragLimit: function () {
11161 if (!this._viscosity || !this._offsetLimit) { return; }
11163 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
11165 var limit = this._offsetLimit;
11166 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
11167 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
11168 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
11169 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
11171 this._draggable._newPos = this._draggable._startPos.add(offset);
11174 _onPreDragWrap: function () {
11175 // TODO refactor to be able to adjust map pane position after zoom
11176 var worldWidth = this._worldWidth,
11177 halfWidth = Math.round(worldWidth / 2),
11178 dx = this._initialWorldOffset,
11179 x = this._draggable._newPos.x,
11180 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
11181 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
11182 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
11184 this._draggable._absPos = this._draggable._newPos.clone();
11185 this._draggable._newPos.x = newX;
11188 _onDragEnd: function (e) {
11189 var map = this._map,
11190 options = map.options,
11192 noInertia = !options.inertia || this._times.length < 2;
11194 map.fire('dragend', e);
11197 map.fire('moveend');
11201 var direction = this._lastPos.subtract(this._positions[0]),
11202 duration = (this._lastTime - this._times[0]) / 1000,
11203 ease = options.easeLinearity,
11205 speedVector = direction.multiplyBy(ease / duration),
11206 speed = speedVector.distanceTo([0, 0]),
11208 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
11209 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
11211 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
11212 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
11214 if (!offset.x && !offset.y) {
11215 map.fire('moveend');
11218 offset = map._limitOffset(offset, map.options.maxBounds);
11220 L.Util.requestAnimFrame(function () {
11221 map.panBy(offset, {
11222 duration: decelerationDuration,
11223 easeLinearity: ease,
11233 // @section Handlers
11234 // @property dragging: Handler
11235 // Map dragging handler (by both mouse and touch).
11236 L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag);
11241 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
11245 // @section Interaction Options
11247 L.Map.mergeOptions({
11248 // @option doubleClickZoom: Boolean|String = true
11249 // Whether the map can be zoomed in by double clicking on it and
11250 // zoomed out by double clicking while holding shift. If passed
11251 // `'center'`, double-click zoom will zoom to the center of the
11252 // view regardless of where the mouse was.
11253 doubleClickZoom: true
11256 L.Map.DoubleClickZoom = L.Handler.extend({
11257 addHooks: function () {
11258 this._map.on('dblclick', this._onDoubleClick, this);
11261 removeHooks: function () {
11262 this._map.off('dblclick', this._onDoubleClick, this);
11265 _onDoubleClick: function (e) {
11266 var map = this._map,
11267 oldZoom = map.getZoom(),
11268 delta = map.options.zoomDelta,
11269 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
11271 if (map.options.doubleClickZoom === 'center') {
11274 map.setZoomAround(e.containerPoint, zoom);
11279 // @section Handlers
11281 // Map properties include interaction handlers that allow you to control
11282 // interaction behavior in runtime, enabling or disabling certain features such
11283 // as dragging or touch zoom (see `Handler` methods). For example:
11286 // map.doubleClickZoom.disable();
11289 // @property doubleClickZoom: Handler
11290 // Double click zoom handler.
11291 L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom);
11296 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
11300 // @section Interaction Options
11301 L.Map.mergeOptions({
11302 // @section Mousewheel options
11303 // @option scrollWheelZoom: Boolean|String = true
11304 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
11305 // it will zoom to the center of the view regardless of where the mouse was.
11306 scrollWheelZoom: true,
11308 // @option wheelDebounceTime: Number = 40
11309 // Limits the rate at which a wheel can fire (in milliseconds). By default
11310 // user can't zoom via wheel more often than once per 40 ms.
11311 wheelDebounceTime: 40,
11313 // @option wheelPxPerZoomLevel: Number = 60
11314 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
11315 // mean a change of one full zoom level. Smaller values will make wheel-zooming
11316 // faster (and vice versa).
11317 wheelPxPerZoomLevel: 60
11320 L.Map.ScrollWheelZoom = L.Handler.extend({
11321 addHooks: function () {
11322 L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this);
11327 removeHooks: function () {
11328 L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll, this);
11331 _onWheelScroll: function (e) {
11332 var delta = L.DomEvent.getWheelDelta(e);
11334 var debounce = this._map.options.wheelDebounceTime;
11336 this._delta += delta;
11337 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
11339 if (!this._startTime) {
11340 this._startTime = +new Date();
11343 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
11345 clearTimeout(this._timer);
11346 this._timer = setTimeout(L.bind(this._performZoom, this), left);
11348 L.DomEvent.stop(e);
11351 _performZoom: function () {
11352 var map = this._map,
11353 zoom = map.getZoom(),
11354 snap = this._map.options.zoomSnap || 0;
11356 map._stop(); // stop panning and fly animations if any
11358 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
11359 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
11360 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
11361 d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
11362 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
11365 this._startTime = null;
11367 if (!delta) { return; }
11369 if (map.options.scrollWheelZoom === 'center') {
11370 map.setZoom(zoom + delta);
11372 map.setZoomAround(this._lastMousePos, zoom + delta);
11377 // @section Handlers
11378 // @property scrollWheelZoom: Handler
11379 // Scroll wheel zoom handler.
11380 L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);
11385 * Extends the event handling code with double tap support for mobile browsers.
11388 L.extend(L.DomEvent, {
11390 _touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart',
11391 _touchend: L.Browser.msPointer ? 'MSPointerUp' : L.Browser.pointer ? 'pointerup' : 'touchend',
11393 // inspired by Zepto touch code by Thomas Fuchs
11394 addDoubleTapListener: function (obj, handler, id) {
11399 function onTouchStart(e) {
11402 if (L.Browser.pointer) {
11403 if ((!L.Browser.edge) || e.pointerType === 'mouse') { return; }
11404 count = L.DomEvent._pointersCount;
11406 count = e.touches.length;
11409 if (count > 1) { return; }
11411 var now = Date.now(),
11412 delta = now - (last || now);
11414 touch = e.touches ? e.touches[0] : e;
11415 doubleTap = (delta > 0 && delta <= delay);
11419 function onTouchEnd(e) {
11420 if (doubleTap && !touch.cancelBubble) {
11421 if (L.Browser.pointer) {
11422 if ((!L.Browser.edge) || e.pointerType === 'mouse') { return; }
11424 // work around .type being readonly with MSPointer* events
11430 newTouch[i] = prop && prop.bind ? prop.bind(touch) : prop;
11434 touch.type = 'dblclick';
11440 var pre = '_leaflet_',
11441 touchstart = this._touchstart,
11442 touchend = this._touchend;
11444 obj[pre + touchstart + id] = onTouchStart;
11445 obj[pre + touchend + id] = onTouchEnd;
11446 obj[pre + 'dblclick' + id] = handler;
11448 obj.addEventListener(touchstart, onTouchStart, false);
11449 obj.addEventListener(touchend, onTouchEnd, false);
11451 // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
11452 // the browser doesn't fire touchend/pointerup events but does fire
11453 // native dblclicks. See #4127.
11454 // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
11455 obj.addEventListener('dblclick', handler, false);
11460 removeDoubleTapListener: function (obj, id) {
11461 var pre = '_leaflet_',
11462 touchstart = obj[pre + this._touchstart + id],
11463 touchend = obj[pre + this._touchend + id],
11464 dblclick = obj[pre + 'dblclick' + id];
11466 obj.removeEventListener(this._touchstart, touchstart, false);
11467 obj.removeEventListener(this._touchend, touchend, false);
11468 if (!L.Browser.edge) {
11469 obj.removeEventListener('dblclick', dblclick, false);
11479 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
11482 L.extend(L.DomEvent, {
11484 POINTER_DOWN: L.Browser.msPointer ? 'MSPointerDown' : 'pointerdown',
11485 POINTER_MOVE: L.Browser.msPointer ? 'MSPointerMove' : 'pointermove',
11486 POINTER_UP: L.Browser.msPointer ? 'MSPointerUp' : 'pointerup',
11487 POINTER_CANCEL: L.Browser.msPointer ? 'MSPointerCancel' : 'pointercancel',
11488 TAG_WHITE_LIST: ['INPUT', 'SELECT', 'OPTION'],
11493 // Provides a touch events wrapper for (ms)pointer events.
11494 // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
11496 addPointerListener: function (obj, type, handler, id) {
11498 if (type === 'touchstart') {
11499 this._addPointerStart(obj, handler, id);
11501 } else if (type === 'touchmove') {
11502 this._addPointerMove(obj, handler, id);
11504 } else if (type === 'touchend') {
11505 this._addPointerEnd(obj, handler, id);
11511 removePointerListener: function (obj, type, id) {
11512 var handler = obj['_leaflet_' + type + id];
11514 if (type === 'touchstart') {
11515 obj.removeEventListener(this.POINTER_DOWN, handler, false);
11517 } else if (type === 'touchmove') {
11518 obj.removeEventListener(this.POINTER_MOVE, handler, false);
11520 } else if (type === 'touchend') {
11521 obj.removeEventListener(this.POINTER_UP, handler, false);
11522 obj.removeEventListener(this.POINTER_CANCEL, handler, false);
11528 _addPointerStart: function (obj, handler, id) {
11529 var onDown = L.bind(function (e) {
11530 if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
11531 // In IE11, some touch events needs to fire for form controls, or
11532 // the controls will stop working. We keep a whitelist of tag names that
11533 // need these events. For other target tags, we prevent default on the event.
11534 if (this.TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
11535 L.DomEvent.preventDefault(e);
11541 this._handlePointer(e, handler);
11544 obj['_leaflet_touchstart' + id] = onDown;
11545 obj.addEventListener(this.POINTER_DOWN, onDown, false);
11547 // need to keep track of what pointers and how many are active to provide e.touches emulation
11548 if (!this._pointerDocListener) {
11549 var pointerUp = L.bind(this._globalPointerUp, this);
11551 // we listen documentElement as any drags that end by moving the touch off the screen get fired there
11552 document.documentElement.addEventListener(this.POINTER_DOWN, L.bind(this._globalPointerDown, this), true);
11553 document.documentElement.addEventListener(this.POINTER_MOVE, L.bind(this._globalPointerMove, this), true);
11554 document.documentElement.addEventListener(this.POINTER_UP, pointerUp, true);
11555 document.documentElement.addEventListener(this.POINTER_CANCEL, pointerUp, true);
11557 this._pointerDocListener = true;
11561 _globalPointerDown: function (e) {
11562 this._pointers[e.pointerId] = e;
11563 this._pointersCount++;
11566 _globalPointerMove: function (e) {
11567 if (this._pointers[e.pointerId]) {
11568 this._pointers[e.pointerId] = e;
11572 _globalPointerUp: function (e) {
11573 delete this._pointers[e.pointerId];
11574 this._pointersCount--;
11577 _handlePointer: function (e, handler) {
11579 for (var i in this._pointers) {
11580 e.touches.push(this._pointers[i]);
11582 e.changedTouches = [e];
11587 _addPointerMove: function (obj, handler, id) {
11588 var onMove = L.bind(function (e) {
11589 // don't fire touch moves when mouse isn't down
11590 if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
11592 this._handlePointer(e, handler);
11595 obj['_leaflet_touchmove' + id] = onMove;
11596 obj.addEventListener(this.POINTER_MOVE, onMove, false);
11599 _addPointerEnd: function (obj, handler, id) {
11600 var onUp = L.bind(function (e) {
11601 this._handlePointer(e, handler);
11604 obj['_leaflet_touchend' + id] = onUp;
11605 obj.addEventListener(this.POINTER_UP, onUp, false);
11606 obj.addEventListener(this.POINTER_CANCEL, onUp, false);
11613 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
11617 // @section Interaction Options
11618 L.Map.mergeOptions({
11619 // @section Touch interaction options
11620 // @option touchZoom: Boolean|String = *
11621 // Whether the map can be zoomed by touch-dragging with two fingers. If
11622 // passed `'center'`, it will zoom to the center of the view regardless of
11623 // where the touch events (fingers) were. Enabled for touch-capable web
11624 // browsers except for old Androids.
11625 touchZoom: L.Browser.touch && !L.Browser.android23,
11627 // @option bounceAtZoomLimits: Boolean = true
11628 // Set it to false if you don't want the map to zoom beyond min/max zoom
11629 // and then bounce back when pinch-zooming.
11630 bounceAtZoomLimits: true
11633 L.Map.TouchZoom = L.Handler.extend({
11634 addHooks: function () {
11635 L.DomUtil.addClass(this._map._container, 'leaflet-touch-zoom');
11636 L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
11639 removeHooks: function () {
11640 L.DomUtil.removeClass(this._map._container, 'leaflet-touch-zoom');
11641 L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
11644 _onTouchStart: function (e) {
11645 var map = this._map;
11646 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
11648 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
11649 p2 = map.mouseEventToContainerPoint(e.touches[1]);
11651 this._centerPoint = map.getSize()._divideBy(2);
11652 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
11653 if (map.options.touchZoom !== 'center') {
11654 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
11657 this._startDist = p1.distanceTo(p2);
11658 this._startZoom = map.getZoom();
11660 this._moved = false;
11661 this._zooming = true;
11666 .on(document, 'touchmove', this._onTouchMove, this)
11667 .on(document, 'touchend', this._onTouchEnd, this);
11669 L.DomEvent.preventDefault(e);
11672 _onTouchMove: function (e) {
11673 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
11675 var map = this._map,
11676 p1 = map.mouseEventToContainerPoint(e.touches[0]),
11677 p2 = map.mouseEventToContainerPoint(e.touches[1]),
11678 scale = p1.distanceTo(p2) / this._startDist;
11681 this._zoom = map.getScaleZoom(scale, this._startZoom);
11683 if (!map.options.bounceAtZoomLimits && (
11684 (this._zoom < map.getMinZoom() && scale < 1) ||
11685 (this._zoom > map.getMaxZoom() && scale > 1))) {
11686 this._zoom = map._limitZoom(this._zoom);
11689 if (map.options.touchZoom === 'center') {
11690 this._center = this._startLatLng;
11691 if (scale === 1) { return; }
11693 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
11694 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
11695 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
11696 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
11699 if (!this._moved) {
11700 map._moveStart(true);
11701 this._moved = true;
11704 L.Util.cancelAnimFrame(this._animRequest);
11706 var moveFn = L.bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
11707 this._animRequest = L.Util.requestAnimFrame(moveFn, this, true);
11709 L.DomEvent.preventDefault(e);
11712 _onTouchEnd: function () {
11713 if (!this._moved || !this._zooming) {
11714 this._zooming = false;
11718 this._zooming = false;
11719 L.Util.cancelAnimFrame(this._animRequest);
11722 .off(document, 'touchmove', this._onTouchMove)
11723 .off(document, 'touchend', this._onTouchEnd);
11725 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
11726 if (this._map.options.zoomAnimation) {
11727 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
11729 this._map._resetView(this._center, this._map._limitZoom(this._zoom));
11734 // @section Handlers
11735 // @property touchZoom: Handler
11736 // Touch zoom handler.
11737 L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom);
11742 * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
11746 // @section Interaction Options
11747 L.Map.mergeOptions({
11748 // @section Touch interaction options
11749 // @option tap: Boolean = true
11750 // Enables mobile hacks for supporting instant taps (fixing 200ms click
11751 // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
11754 // @option tapTolerance: Number = 15
11755 // The max number of pixels a user can shift his finger during touch
11756 // for it to be considered a valid tap.
11760 L.Map.Tap = L.Handler.extend({
11761 addHooks: function () {
11762 L.DomEvent.on(this._map._container, 'touchstart', this._onDown, this);
11765 removeHooks: function () {
11766 L.DomEvent.off(this._map._container, 'touchstart', this._onDown, this);
11769 _onDown: function (e) {
11770 if (!e.touches) { return; }
11772 L.DomEvent.preventDefault(e);
11774 this._fireClick = true;
11776 // don't simulate click or track longpress if more than 1 touch
11777 if (e.touches.length > 1) {
11778 this._fireClick = false;
11779 clearTimeout(this._holdTimeout);
11783 var first = e.touches[0],
11786 this._startPos = this._newPos = new L.Point(first.clientX, first.clientY);
11788 // if touching a link, highlight it
11789 if (el.tagName && el.tagName.toLowerCase() === 'a') {
11790 L.DomUtil.addClass(el, 'leaflet-active');
11793 // simulate long hold but setting a timeout
11794 this._holdTimeout = setTimeout(L.bind(function () {
11795 if (this._isTapValid()) {
11796 this._fireClick = false;
11798 this._simulateEvent('contextmenu', first);
11802 this._simulateEvent('mousedown', first);
11804 L.DomEvent.on(document, {
11805 touchmove: this._onMove,
11806 touchend: this._onUp
11810 _onUp: function (e) {
11811 clearTimeout(this._holdTimeout);
11813 L.DomEvent.off(document, {
11814 touchmove: this._onMove,
11815 touchend: this._onUp
11818 if (this._fireClick && e && e.changedTouches) {
11820 var first = e.changedTouches[0],
11823 if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
11824 L.DomUtil.removeClass(el, 'leaflet-active');
11827 this._simulateEvent('mouseup', first);
11829 // simulate click if the touch didn't move too much
11830 if (this._isTapValid()) {
11831 this._simulateEvent('click', first);
11836 _isTapValid: function () {
11837 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
11840 _onMove: function (e) {
11841 var first = e.touches[0];
11842 this._newPos = new L.Point(first.clientX, first.clientY);
11843 this._simulateEvent('mousemove', first);
11846 _simulateEvent: function (type, e) {
11847 var simulatedEvent = document.createEvent('MouseEvents');
11849 simulatedEvent._simulated = true;
11850 e.target._simulatedClick = true;
11852 simulatedEvent.initMouseEvent(
11853 type, true, true, window, 1,
11854 e.screenX, e.screenY,
11855 e.clientX, e.clientY,
11856 false, false, false, false, 0, null);
11858 e.target.dispatchEvent(simulatedEvent);
11862 // @section Handlers
11863 // @property tap: Handler
11864 // Mobile touch hacks (quick tap and touch hold) handler.
11865 if (L.Browser.touch && !L.Browser.pointer) {
11866 L.Map.addInitHook('addHandler', 'tap', L.Map.Tap);
11872 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
11873 * (zoom to a selected bounding box), enabled by default.
11877 // @section Interaction Options
11878 L.Map.mergeOptions({
11879 // @option boxZoom: Boolean = true
11880 // Whether the map can be zoomed to a rectangular area specified by
11881 // dragging the mouse while pressing the shift key.
11885 L.Map.BoxZoom = L.Handler.extend({
11886 initialize: function (map) {
11888 this._container = map._container;
11889 this._pane = map._panes.overlayPane;
11892 addHooks: function () {
11893 L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
11896 removeHooks: function () {
11897 L.DomEvent.off(this._container, 'mousedown', this._onMouseDown, this);
11900 moved: function () {
11901 return this._moved;
11904 _resetState: function () {
11905 this._moved = false;
11908 _onMouseDown: function (e) {
11909 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
11911 this._resetState();
11913 L.DomUtil.disableTextSelection();
11914 L.DomUtil.disableImageDrag();
11916 this._startPoint = this._map.mouseEventToContainerPoint(e);
11918 L.DomEvent.on(document, {
11919 contextmenu: L.DomEvent.stop,
11920 mousemove: this._onMouseMove,
11921 mouseup: this._onMouseUp,
11922 keydown: this._onKeyDown
11926 _onMouseMove: function (e) {
11927 if (!this._moved) {
11928 this._moved = true;
11930 this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._container);
11931 L.DomUtil.addClass(this._container, 'leaflet-crosshair');
11933 this._map.fire('boxzoomstart');
11936 this._point = this._map.mouseEventToContainerPoint(e);
11938 var bounds = new L.Bounds(this._point, this._startPoint),
11939 size = bounds.getSize();
11941 L.DomUtil.setPosition(this._box, bounds.min);
11943 this._box.style.width = size.x + 'px';
11944 this._box.style.height = size.y + 'px';
11947 _finish: function () {
11949 L.DomUtil.remove(this._box);
11950 L.DomUtil.removeClass(this._container, 'leaflet-crosshair');
11953 L.DomUtil.enableTextSelection();
11954 L.DomUtil.enableImageDrag();
11956 L.DomEvent.off(document, {
11957 contextmenu: L.DomEvent.stop,
11958 mousemove: this._onMouseMove,
11959 mouseup: this._onMouseUp,
11960 keydown: this._onKeyDown
11964 _onMouseUp: function (e) {
11965 if ((e.which !== 1) && (e.button !== 1)) { return; }
11969 if (!this._moved) { return; }
11970 // Postpone to next JS tick so internal click event handling
11971 // still see it as "moved".
11972 setTimeout(L.bind(this._resetState, this), 0);
11974 var bounds = new L.LatLngBounds(
11975 this._map.containerPointToLatLng(this._startPoint),
11976 this._map.containerPointToLatLng(this._point));
11980 .fire('boxzoomend', {boxZoomBounds: bounds});
11983 _onKeyDown: function (e) {
11984 if (e.keyCode === 27) {
11990 // @section Handlers
11991 // @property boxZoom: Handler
11992 // Box (shift-drag with mouse) zoom handler.
11993 L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom);
11998 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
12002 // @section Keyboard Navigation Options
12003 L.Map.mergeOptions({
12004 // @option keyboard: Boolean = true
12005 // Makes the map focusable and allows users to navigate the map with keyboard
12006 // arrows and `+`/`-` keys.
12009 // @option keyboardPanDelta: Number = 80
12010 // Amount of pixels to pan when pressing an arrow key.
12011 keyboardPanDelta: 80
12014 L.Map.Keyboard = L.Handler.extend({
12021 zoomIn: [187, 107, 61, 171],
12022 zoomOut: [189, 109, 54, 173]
12025 initialize: function (map) {
12028 this._setPanDelta(map.options.keyboardPanDelta);
12029 this._setZoomDelta(map.options.zoomDelta);
12032 addHooks: function () {
12033 var container = this._map._container;
12035 // make the container focusable by tabbing
12036 if (container.tabIndex <= 0) {
12037 container.tabIndex = '0';
12040 L.DomEvent.on(container, {
12041 focus: this._onFocus,
12042 blur: this._onBlur,
12043 mousedown: this._onMouseDown
12047 focus: this._addHooks,
12048 blur: this._removeHooks
12052 removeHooks: function () {
12053 this._removeHooks();
12055 L.DomEvent.off(this._map._container, {
12056 focus: this._onFocus,
12057 blur: this._onBlur,
12058 mousedown: this._onMouseDown
12062 focus: this._addHooks,
12063 blur: this._removeHooks
12067 _onMouseDown: function () {
12068 if (this._focused) { return; }
12070 var body = document.body,
12071 docEl = document.documentElement,
12072 top = body.scrollTop || docEl.scrollTop,
12073 left = body.scrollLeft || docEl.scrollLeft;
12075 this._map._container.focus();
12077 window.scrollTo(left, top);
12080 _onFocus: function () {
12081 this._focused = true;
12082 this._map.fire('focus');
12085 _onBlur: function () {
12086 this._focused = false;
12087 this._map.fire('blur');
12090 _setPanDelta: function (panDelta) {
12091 var keys = this._panKeys = {},
12092 codes = this.keyCodes,
12095 for (i = 0, len = codes.left.length; i < len; i++) {
12096 keys[codes.left[i]] = [-1 * panDelta, 0];
12098 for (i = 0, len = codes.right.length; i < len; i++) {
12099 keys[codes.right[i]] = [panDelta, 0];
12101 for (i = 0, len = codes.down.length; i < len; i++) {
12102 keys[codes.down[i]] = [0, panDelta];
12104 for (i = 0, len = codes.up.length; i < len; i++) {
12105 keys[codes.up[i]] = [0, -1 * panDelta];
12109 _setZoomDelta: function (zoomDelta) {
12110 var keys = this._zoomKeys = {},
12111 codes = this.keyCodes,
12114 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
12115 keys[codes.zoomIn[i]] = zoomDelta;
12117 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
12118 keys[codes.zoomOut[i]] = -zoomDelta;
12122 _addHooks: function () {
12123 L.DomEvent.on(document, 'keydown', this._onKeyDown, this);
12126 _removeHooks: function () {
12127 L.DomEvent.off(document, 'keydown', this._onKeyDown, this);
12130 _onKeyDown: function (e) {
12131 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
12133 var key = e.keyCode,
12137 if (key in this._panKeys) {
12139 if (map._panAnim && map._panAnim._inProgress) { return; }
12141 offset = this._panKeys[key];
12143 offset = L.point(offset).multiplyBy(3);
12148 if (map.options.maxBounds) {
12149 map.panInsideBounds(map.options.maxBounds);
12152 } else if (key in this._zoomKeys) {
12153 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
12155 } else if (key === 27) {
12162 L.DomEvent.stop(e);
12166 // @section Handlers
12167 // @section Handlers
12168 // @property keyboard: Handler
12169 // Keyboard navigation handler.
12170 L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard);
12175 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
12179 /* @namespace Marker
12180 * @section Interaction handlers
12182 * 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:
12185 * marker.dragging.disable();
12188 * @property dragging: Handler
12189 * Marker dragging handler (by both mouse and touch).
12192 L.Handler.MarkerDrag = L.Handler.extend({
12193 initialize: function (marker) {
12194 this._marker = marker;
12197 addHooks: function () {
12198 var icon = this._marker._icon;
12200 if (!this._draggable) {
12201 this._draggable = new L.Draggable(icon, icon, true);
12204 this._draggable.on({
12205 dragstart: this._onDragStart,
12206 drag: this._onDrag,
12207 dragend: this._onDragEnd
12210 L.DomUtil.addClass(icon, 'leaflet-marker-draggable');
12213 removeHooks: function () {
12214 this._draggable.off({
12215 dragstart: this._onDragStart,
12216 drag: this._onDrag,
12217 dragend: this._onDragEnd
12218 }, this).disable();
12220 if (this._marker._icon) {
12221 L.DomUtil.removeClass(this._marker._icon, 'leaflet-marker-draggable');
12225 moved: function () {
12226 return this._draggable && this._draggable._moved;
12229 _onDragStart: function () {
12230 // @section Dragging events
12231 // @event dragstart: Event
12232 // Fired when the user starts dragging the marker.
12234 // @event movestart: Event
12235 // Fired when the marker starts moving (because of dragging).
12237 this._oldLatLng = this._marker.getLatLng();
12241 .fire('dragstart');
12244 _onDrag: function (e) {
12245 var marker = this._marker,
12246 shadow = marker._shadow,
12247 iconPos = L.DomUtil.getPosition(marker._icon),
12248 latlng = marker._map.layerPointToLatLng(iconPos);
12250 // update shadow position
12252 L.DomUtil.setPosition(shadow, iconPos);
12255 marker._latlng = latlng;
12257 e.oldLatLng = this._oldLatLng;
12259 // @event drag: Event
12260 // Fired repeatedly while the user drags the marker.
12266 _onDragEnd: function (e) {
12267 // @event dragend: DragEndEvent
12268 // Fired when the user stops dragging the marker.
12270 // @event moveend: Event
12271 // Fired when the marker stops moving (because of dragging).
12272 delete this._oldLatLng;
12275 .fire('dragend', e);
12286 * L.Control is a base class for implementing map controls. Handles positioning.
12287 * All other controls extend from this class.
12290 L.Control = L.Class.extend({
12292 // @aka Control options
12294 // @option position: String = 'topright'
12295 // The position of the control (one of the map corners). Possible values are `'topleft'`,
12296 // `'topright'`, `'bottomleft'` or `'bottomright'`
12297 position: 'topright'
12300 initialize: function (options) {
12301 L.setOptions(this, options);
12305 * Classes extending L.Control will inherit the following methods:
12307 * @method getPosition: string
12308 * Returns the position of the control.
12310 getPosition: function () {
12311 return this.options.position;
12314 // @method setPosition(position: string): this
12315 // Sets the position of the control.
12316 setPosition: function (position) {
12317 var map = this._map;
12320 map.removeControl(this);
12323 this.options.position = position;
12326 map.addControl(this);
12332 // @method getContainer: HTMLElement
12333 // Returns the HTMLElement that contains the control.
12334 getContainer: function () {
12335 return this._container;
12338 // @method addTo(map: Map): this
12339 // Adds the control to the given map.
12340 addTo: function (map) {
12344 var container = this._container = this.onAdd(map),
12345 pos = this.getPosition(),
12346 corner = map._controlCorners[pos];
12348 L.DomUtil.addClass(container, 'leaflet-control');
12350 if (pos.indexOf('bottom') !== -1) {
12351 corner.insertBefore(container, corner.firstChild);
12353 corner.appendChild(container);
12359 // @method remove: this
12360 // Removes the control from the map it is currently active on.
12361 remove: function () {
12366 L.DomUtil.remove(this._container);
12368 if (this.onRemove) {
12369 this.onRemove(this._map);
12377 _refocusOnMap: function (e) {
12378 // if map exists and event is not a keyboard event
12379 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
12380 this._map.getContainer().focus();
12385 L.control = function (options) {
12386 return new L.Control(options);
12389 /* @section Extension methods
12392 * Every control should extend from `L.Control` and (re-)implement the following methods.
12394 * @method onAdd(map: Map): HTMLElement
12395 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
12397 * @method onRemove(map: Map)
12398 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
12402 * @section Methods for Layers and Controls
12405 // @method addControl(control: Control): this
12406 // Adds the given control to the map
12407 addControl: function (control) {
12408 control.addTo(this);
12412 // @method removeControl(control: Control): this
12413 // Removes the given control from the map
12414 removeControl: function (control) {
12419 _initControlPos: function () {
12420 var corners = this._controlCorners = {},
12422 container = this._controlContainer =
12423 L.DomUtil.create('div', l + 'control-container', this._container);
12425 function createCorner(vSide, hSide) {
12426 var className = l + vSide + ' ' + l + hSide;
12428 corners[vSide + hSide] = L.DomUtil.create('div', className, container);
12431 createCorner('top', 'left');
12432 createCorner('top', 'right');
12433 createCorner('bottom', 'left');
12434 createCorner('bottom', 'right');
12437 _clearControlPos: function () {
12438 L.DomUtil.remove(this._controlContainer);
12445 * @class Control.Zoom
12446 * @aka L.Control.Zoom
12447 * @inherits Control
12449 * 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`.
12452 L.Control.Zoom = L.Control.extend({
12454 // @aka Control.Zoom options
12456 position: 'topleft',
12458 // @option zoomInText: String = '+'
12459 // The text set on the 'zoom in' button.
12462 // @option zoomInTitle: String = 'Zoom in'
12463 // The title set on the 'zoom in' button.
12464 zoomInTitle: 'Zoom in',
12466 // @option zoomOutText: String = '-'
12467 // The text set on the 'zoom out' button.
12470 // @option zoomOutTitle: String = 'Zoom out'
12471 // The title set on the 'zoom out' button.
12472 zoomOutTitle: 'Zoom out'
12475 onAdd: function (map) {
12476 var zoomName = 'leaflet-control-zoom',
12477 container = L.DomUtil.create('div', zoomName + ' leaflet-bar'),
12478 options = this.options;
12480 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle,
12481 zoomName + '-in', container, this._zoomIn);
12482 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
12483 zoomName + '-out', container, this._zoomOut);
12485 this._updateDisabled();
12486 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
12491 onRemove: function (map) {
12492 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
12495 disable: function () {
12496 this._disabled = true;
12497 this._updateDisabled();
12501 enable: function () {
12502 this._disabled = false;
12503 this._updateDisabled();
12507 _zoomIn: function (e) {
12508 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
12509 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
12513 _zoomOut: function (e) {
12514 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
12515 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
12519 _createButton: function (html, title, className, container, fn) {
12520 var link = L.DomUtil.create('a', className, container);
12521 link.innerHTML = html;
12523 link.title = title;
12526 * Will force screen readers like VoiceOver to read this as "Zoom in - button"
12528 link.setAttribute('role', 'button');
12529 link.setAttribute('aria-label', title);
12532 .on(link, 'mousedown dblclick', L.DomEvent.stopPropagation)
12533 .on(link, 'click', L.DomEvent.stop)
12534 .on(link, 'click', fn, this)
12535 .on(link, 'click', this._refocusOnMap, this);
12540 _updateDisabled: function () {
12541 var map = this._map,
12542 className = 'leaflet-disabled';
12544 L.DomUtil.removeClass(this._zoomInButton, className);
12545 L.DomUtil.removeClass(this._zoomOutButton, className);
12547 if (this._disabled || map._zoom === map.getMinZoom()) {
12548 L.DomUtil.addClass(this._zoomOutButton, className);
12550 if (this._disabled || map._zoom === map.getMaxZoom()) {
12551 L.DomUtil.addClass(this._zoomInButton, className);
12557 // @section Control options
12558 // @option zoomControl: Boolean = true
12559 // Whether a [zoom control](#control-zoom) is added to the map by default.
12560 L.Map.mergeOptions({
12564 L.Map.addInitHook(function () {
12565 if (this.options.zoomControl) {
12566 this.zoomControl = new L.Control.Zoom();
12567 this.addControl(this.zoomControl);
12571 // @namespace Control.Zoom
12572 // @factory L.control.zoom(options: Control.Zoom options)
12573 // Creates a zoom control
12574 L.control.zoom = function (options) {
12575 return new L.Control.Zoom(options);
12581 * @class Control.Attribution
12582 * @aka L.Control.Attribution
12583 * @inherits Control
12585 * 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.
12588 L.Control.Attribution = L.Control.extend({
12590 // @aka Control.Attribution options
12592 position: 'bottomright',
12594 // @option prefix: String = 'Leaflet'
12595 // The HTML text shown before the attributions. Pass `false` to disable.
12596 prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
12599 initialize: function (options) {
12600 L.setOptions(this, options);
12602 this._attributions = {};
12605 onAdd: function (map) {
12606 map.attributionControl = this;
12607 this._container = L.DomUtil.create('div', 'leaflet-control-attribution');
12609 L.DomEvent.disableClickPropagation(this._container);
12612 // TODO ugly, refactor
12613 for (var i in map._layers) {
12614 if (map._layers[i].getAttribution) {
12615 this.addAttribution(map._layers[i].getAttribution());
12621 return this._container;
12624 // @method setPrefix(prefix: String): this
12625 // Sets the text before the attributions.
12626 setPrefix: function (prefix) {
12627 this.options.prefix = prefix;
12632 // @method addAttribution(text: String): this
12633 // Adds an attribution text (e.g. `'Vector data © Mapbox'`).
12634 addAttribution: function (text) {
12635 if (!text) { return this; }
12637 if (!this._attributions[text]) {
12638 this._attributions[text] = 0;
12640 this._attributions[text]++;
12647 // @method removeAttribution(text: String): this
12648 // Removes an attribution text.
12649 removeAttribution: function (text) {
12650 if (!text) { return this; }
12652 if (this._attributions[text]) {
12653 this._attributions[text]--;
12660 _update: function () {
12661 if (!this._map) { return; }
12665 for (var i in this._attributions) {
12666 if (this._attributions[i]) {
12671 var prefixAndAttribs = [];
12673 if (this.options.prefix) {
12674 prefixAndAttribs.push(this.options.prefix);
12676 if (attribs.length) {
12677 prefixAndAttribs.push(attribs.join(', '));
12680 this._container.innerHTML = prefixAndAttribs.join(' | ');
12685 // @section Control options
12686 // @option attributionControl: Boolean = true
12687 // Whether a [attribution control](#control-attribution) is added to the map by default.
12688 L.Map.mergeOptions({
12689 attributionControl: true
12692 L.Map.addInitHook(function () {
12693 if (this.options.attributionControl) {
12694 new L.Control.Attribution().addTo(this);
12698 // @namespace Control.Attribution
12699 // @factory L.control.attribution(options: Control.Attribution options)
12700 // Creates an attribution control.
12701 L.control.attribution = function (options) {
12702 return new L.Control.Attribution(options);
12708 * @class Control.Scale
12709 * @aka L.Control.Scale
12710 * @inherits Control
12712 * 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`.
12717 * L.control.scale().addTo(map);
12721 L.Control.Scale = L.Control.extend({
12723 // @aka Control.Scale options
12725 position: 'bottomleft',
12727 // @option maxWidth: Number = 100
12728 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
12731 // @option metric: Boolean = True
12732 // Whether to show the metric scale line (m/km).
12735 // @option imperial: Boolean = True
12736 // Whether to show the imperial scale line (mi/ft).
12739 // @option updateWhenIdle: Boolean = false
12740 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
12743 onAdd: function (map) {
12744 var className = 'leaflet-control-scale',
12745 container = L.DomUtil.create('div', className),
12746 options = this.options;
12748 this._addScales(options, className + '-line', container);
12750 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
12751 map.whenReady(this._update, this);
12756 onRemove: function (map) {
12757 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
12760 _addScales: function (options, className, container) {
12761 if (options.metric) {
12762 this._mScale = L.DomUtil.create('div', className, container);
12764 if (options.imperial) {
12765 this._iScale = L.DomUtil.create('div', className, container);
12769 _update: function () {
12770 var map = this._map,
12771 y = map.getSize().y / 2;
12773 var maxMeters = map.distance(
12774 map.containerPointToLatLng([0, y]),
12775 map.containerPointToLatLng([this.options.maxWidth, y]));
12777 this._updateScales(maxMeters);
12780 _updateScales: function (maxMeters) {
12781 if (this.options.metric && maxMeters) {
12782 this._updateMetric(maxMeters);
12784 if (this.options.imperial && maxMeters) {
12785 this._updateImperial(maxMeters);
12789 _updateMetric: function (maxMeters) {
12790 var meters = this._getRoundNum(maxMeters),
12791 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
12793 this._updateScale(this._mScale, label, meters / maxMeters);
12796 _updateImperial: function (maxMeters) {
12797 var maxFeet = maxMeters * 3.2808399,
12798 maxMiles, miles, feet;
12800 if (maxFeet > 5280) {
12801 maxMiles = maxFeet / 5280;
12802 miles = this._getRoundNum(maxMiles);
12803 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
12806 feet = this._getRoundNum(maxFeet);
12807 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
12811 _updateScale: function (scale, text, ratio) {
12812 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
12813 scale.innerHTML = text;
12816 _getRoundNum: function (num) {
12817 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
12830 // @factory L.control.scale(options?: Control.Scale options)
12831 // Creates an scale control with the given options.
12832 L.control.scale = function (options) {
12833 return new L.Control.Scale(options);
12839 * @class Control.Layers
12840 * @aka L.Control.Layers
12841 * @inherits Control
12843 * 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`.
12848 * var baseLayers = {
12849 * "Mapbox": mapbox,
12850 * "OpenStreetMap": osm
12854 * "Marker": marker,
12855 * "Roads": roadsLayer
12858 * L.control.layers(baseLayers, overlays).addTo(map);
12861 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
12865 * "<someName1>": layer1,
12866 * "<someName2>": layer2
12870 * The layer names can contain HTML, which allows you to add additional styling to the items:
12873 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
12878 L.Control.Layers = L.Control.extend({
12880 // @aka Control.Layers options
12882 // @option collapsed: Boolean = true
12883 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
12885 position: 'topright',
12887 // @option autoZIndex: Boolean = true
12888 // 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.
12891 // @option hideSingleBase: Boolean = false
12892 // If `true`, the base layers in the control will be hidden when there is only one.
12893 hideSingleBase: false,
12895 // @option sortLayers: Boolean = false
12896 // Whether to sort the layers. When `false`, layers will keep the order
12897 // in which they were added to the control.
12900 // @option sortFunction: Function = *
12901 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
12902 // that will be used for sorting the layers, when `sortLayers` is `true`.
12903 // The function receives both the `L.Layer` instances and their names, as in
12904 // `sortFunction(layerA, layerB, nameA, nameB)`.
12905 // By default, it sorts layers alphabetically by their name.
12906 sortFunction: function (layerA, layerB, nameA, nameB) {
12907 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
12911 initialize: function (baseLayers, overlays, options) {
12912 L.setOptions(this, options);
12915 this._lastZIndex = 0;
12916 this._handlingClick = false;
12918 for (var i in baseLayers) {
12919 this._addLayer(baseLayers[i], i);
12922 for (i in overlays) {
12923 this._addLayer(overlays[i], i, true);
12927 onAdd: function (map) {
12928 this._initLayout();
12932 map.on('zoomend', this._checkDisabledLayers, this);
12934 return this._container;
12937 onRemove: function () {
12938 this._map.off('zoomend', this._checkDisabledLayers, this);
12940 for (var i = 0; i < this._layers.length; i++) {
12941 this._layers[i].layer.off('add remove', this._onLayerChange, this);
12945 // @method addBaseLayer(layer: Layer, name: String): this
12946 // Adds a base layer (radio button entry) with the given name to the control.
12947 addBaseLayer: function (layer, name) {
12948 this._addLayer(layer, name);
12949 return (this._map) ? this._update() : this;
12952 // @method addOverlay(layer: Layer, name: String): this
12953 // Adds an overlay (checkbox entry) with the given name to the control.
12954 addOverlay: function (layer, name) {
12955 this._addLayer(layer, name, true);
12956 return (this._map) ? this._update() : this;
12959 // @method removeLayer(layer: Layer): this
12960 // Remove the given layer from the control.
12961 removeLayer: function (layer) {
12962 layer.off('add remove', this._onLayerChange, this);
12964 var obj = this._getLayer(L.stamp(layer));
12966 this._layers.splice(this._layers.indexOf(obj), 1);
12968 return (this._map) ? this._update() : this;
12971 // @method expand(): this
12972 // Expand the control container if collapsed.
12973 expand: function () {
12974 L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');
12975 this._form.style.height = null;
12976 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
12977 if (acceptableHeight < this._form.clientHeight) {
12978 L.DomUtil.addClass(this._form, 'leaflet-control-layers-scrollbar');
12979 this._form.style.height = acceptableHeight + 'px';
12981 L.DomUtil.removeClass(this._form, 'leaflet-control-layers-scrollbar');
12983 this._checkDisabledLayers();
12987 // @method collapse(): this
12988 // Collapse the control container if expanded.
12989 collapse: function () {
12990 L.DomUtil.removeClass(this._container, 'leaflet-control-layers-expanded');
12994 _initLayout: function () {
12995 var className = 'leaflet-control-layers',
12996 container = this._container = L.DomUtil.create('div', className),
12997 collapsed = this.options.collapsed;
12999 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
13000 container.setAttribute('aria-haspopup', true);
13002 L.DomEvent.disableClickPropagation(container);
13003 if (!L.Browser.touch) {
13004 L.DomEvent.disableScrollPropagation(container);
13007 var form = this._form = L.DomUtil.create('form', className + '-list');
13010 this._map.on('click', this.collapse, this);
13012 if (!L.Browser.android) {
13013 L.DomEvent.on(container, {
13014 mouseenter: this.expand,
13015 mouseleave: this.collapse
13020 var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
13022 link.title = 'Layers';
13024 if (L.Browser.touch) {
13026 .on(link, 'click', L.DomEvent.stop)
13027 .on(link, 'click', this.expand, this);
13029 L.DomEvent.on(link, 'focus', this.expand, this);
13032 // work around for Firefox Android issue https://github.com/Leaflet/Leaflet/issues/2033
13033 L.DomEvent.on(form, 'click', function () {
13034 setTimeout(L.bind(this._onInputClick, this), 0);
13037 // TODO keyboard accessibility
13043 this._baseLayersList = L.DomUtil.create('div', className + '-base', form);
13044 this._separator = L.DomUtil.create('div', className + '-separator', form);
13045 this._overlaysList = L.DomUtil.create('div', className + '-overlays', form);
13047 container.appendChild(form);
13050 _getLayer: function (id) {
13051 for (var i = 0; i < this._layers.length; i++) {
13053 if (this._layers[i] && L.stamp(this._layers[i].layer) === id) {
13054 return this._layers[i];
13059 _addLayer: function (layer, name, overlay) {
13060 layer.on('add remove', this._onLayerChange, this);
13062 this._layers.push({
13068 if (this.options.sortLayers) {
13069 this._layers.sort(L.bind(function (a, b) {
13070 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
13074 if (this.options.autoZIndex && layer.setZIndex) {
13075 this._lastZIndex++;
13076 layer.setZIndex(this._lastZIndex);
13080 _update: function () {
13081 if (!this._container) { return this; }
13083 L.DomUtil.empty(this._baseLayersList);
13084 L.DomUtil.empty(this._overlaysList);
13086 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
13088 for (i = 0; i < this._layers.length; i++) {
13089 obj = this._layers[i];
13090 this._addItem(obj);
13091 overlaysPresent = overlaysPresent || obj.overlay;
13092 baseLayersPresent = baseLayersPresent || !obj.overlay;
13093 baseLayersCount += !obj.overlay ? 1 : 0;
13096 // Hide base layers section if there's only one layer.
13097 if (this.options.hideSingleBase) {
13098 baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
13099 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
13102 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
13107 _onLayerChange: function (e) {
13108 if (!this._handlingClick) {
13112 var obj = this._getLayer(L.stamp(e.target));
13115 // @section Layer events
13116 // @event baselayerchange: LayersControlEvent
13117 // Fired when the base layer is changed through the [layer control](#control-layers).
13118 // @event overlayadd: LayersControlEvent
13119 // Fired when an overlay is selected through the [layer control](#control-layers).
13120 // @event overlayremove: LayersControlEvent
13121 // Fired when an overlay is deselected through the [layer control](#control-layers).
13122 // @namespace Control.Layers
13123 var type = obj.overlay ?
13124 (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
13125 (e.type === 'add' ? 'baselayerchange' : null);
13128 this._map.fire(type, obj);
13132 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
13133 _createRadioElement: function (name, checked) {
13135 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
13136 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
13138 var radioFragment = document.createElement('div');
13139 radioFragment.innerHTML = radioHtml;
13141 return radioFragment.firstChild;
13144 _addItem: function (obj) {
13145 var label = document.createElement('label'),
13146 checked = this._map.hasLayer(obj.layer),
13150 input = document.createElement('input');
13151 input.type = 'checkbox';
13152 input.className = 'leaflet-control-layers-selector';
13153 input.defaultChecked = checked;
13155 input = this._createRadioElement('leaflet-base-layers', checked);
13158 input.layerId = L.stamp(obj.layer);
13160 L.DomEvent.on(input, 'click', this._onInputClick, this);
13162 var name = document.createElement('span');
13163 name.innerHTML = ' ' + obj.name;
13165 // Helps from preventing layer control flicker when checkboxes are disabled
13166 // https://github.com/Leaflet/Leaflet/issues/2771
13167 var holder = document.createElement('div');
13169 label.appendChild(holder);
13170 holder.appendChild(input);
13171 holder.appendChild(name);
13173 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
13174 container.appendChild(label);
13176 this._checkDisabledLayers();
13180 _onInputClick: function () {
13181 var inputs = this._form.getElementsByTagName('input'),
13182 input, layer, hasLayer;
13183 var addedLayers = [],
13184 removedLayers = [];
13186 this._handlingClick = true;
13188 for (var i = inputs.length - 1; i >= 0; i--) {
13190 layer = this._getLayer(input.layerId).layer;
13191 hasLayer = this._map.hasLayer(layer);
13193 if (input.checked && !hasLayer) {
13194 addedLayers.push(layer);
13196 } else if (!input.checked && hasLayer) {
13197 removedLayers.push(layer);
13201 // Bugfix issue 2318: Should remove all old layers before readding new ones
13202 for (i = 0; i < removedLayers.length; i++) {
13203 this._map.removeLayer(removedLayers[i]);
13205 for (i = 0; i < addedLayers.length; i++) {
13206 this._map.addLayer(addedLayers[i]);
13209 this._handlingClick = false;
13211 this._refocusOnMap();
13214 _checkDisabledLayers: function () {
13215 var inputs = this._form.getElementsByTagName('input'),
13218 zoom = this._map.getZoom();
13220 for (var i = inputs.length - 1; i >= 0; i--) {
13222 layer = this._getLayer(input.layerId).layer;
13223 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
13224 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
13229 _expand: function () {
13230 // Backward compatibility, remove me in 1.1.
13231 return this.expand();
13234 _collapse: function () {
13235 // Backward compatibility, remove me in 1.1.
13236 return this.collapse();
13242 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
13243 // 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.
13244 L.control.layers = function (baseLayers, overlays, options) {
13245 return new L.Control.Layers(baseLayers, overlays, options);
13250 }(window, document));
13251 //# sourceMappingURL=leaflet-src.map