2 * Leaflet 1.2.0, a JS library for interactive maps. http://leafletjs.com
3 * (c) 2010-2017 Vladimir Agafonkin, (c) 2010-2011 CloudMade
5 (function (global, factory) {
6 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
7 typeof define === 'function' && define.amd ? define(['exports'], factory) :
8 (factory((global.L = {})));
9 }(this, (function (exports) { 'use strict';
11 var version = "1.2.0";
16 * Various utility functions, used by Leaflet internally.
19 var freeze = Object.freeze;
20 Object.freeze = function (obj) { return obj; };
22 // @function extend(dest: Object, src?: Object): Object
23 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
24 function extend(dest) {
27 for (j = 1, len = arguments.length; j < len; j++) {
36 // @function create(proto: Object, properties?: Object): Object
37 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
38 var create = Object.create || (function () {
40 return function (proto) {
46 // @function bind(fn: Function, …): Function
47 // 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).
48 // Has a `L.bind()` shortcut.
49 function bind(fn, obj) {
50 var slice = Array.prototype.slice;
53 return fn.bind.apply(fn, slice.call(arguments, 1));
56 var args = slice.call(arguments, 2);
59 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
63 // @property lastId: Number
64 // Last unique ID used by [`stamp()`](#util-stamp)
67 // @function stamp(obj: Object): Number
68 // Returns the unique ID of an object, assiging it one if it doesn't have it.
71 obj._leaflet_id = obj._leaflet_id || ++lastId;
72 return obj._leaflet_id;
76 // @function throttle(fn: Function, time: Number, context: Object): Function
77 // Returns a function which executes function `fn` with the given scope `context`
78 // (so that the `this` keyword refers to `context` inside `fn`'s code). The function
79 // `fn` will be called no more than one time per given amount of `time`. The arguments
80 // received by the bound function will be any arguments passed when binding the
81 // function, followed by any arguments passed when invoking the bound function.
82 // Has an `L.throttle` shortcut.
83 function throttle(fn, time, context) {
84 var lock, args, wrapperFn, later;
87 // reset lock and call if queued
90 wrapperFn.apply(context, args);
95 wrapperFn = function () {
97 // called too soon, queue to call later
101 // call and lock until later
102 fn.apply(context, arguments);
103 setTimeout(later, time);
111 // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
112 // Returns the number `num` modulo `range` in such a way so it lies within
113 // `range[0]` and `range[1]`. The returned value will be always smaller than
114 // `range[1]` unless `includeMax` is set to `true`.
115 function wrapNum(x, range, includeMax) {
119 return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
122 // @function falseFn(): Function
123 // Returns a function which always returns `false`.
124 function falseFn() { return false; }
126 // @function formatNum(num: Number, digits?: Number): Number
127 // Returns the number `num` rounded to `digits` decimals, or to 5 decimals by default.
128 function formatNum(num, digits) {
129 var pow = Math.pow(10, digits || 5);
130 return Math.round(num * pow) / pow;
133 // @function trim(str: String): String
134 // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
136 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
139 // @function splitWords(str: String): String[]
140 // Trims and splits the string on whitespace and returns the array of parts.
141 function splitWords(str) {
142 return trim(str).split(/\s+/);
145 // @function setOptions(obj: Object, options: Object): Object
146 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
147 function setOptions(obj, options) {
148 if (!obj.hasOwnProperty('options')) {
149 obj.options = obj.options ? create(obj.options) : {};
151 for (var i in options) {
152 obj.options[i] = options[i];
157 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
158 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
159 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
160 // be appended at the end. If `uppercase` is `true`, the parameter names will
161 // be uppercased (e.g. `'?A=foo&B=bar'`)
162 function getParamString(obj, existingUrl, uppercase) {
165 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
167 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
170 var templateRe = /\{ *([\w_\-]+) *\}/g;
172 // @function template(str: String, data: Object): String
173 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
174 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
175 // `('Hello foo, bar')`. You can also specify functions instead of strings for
176 // data values — they will be evaluated passing `data` as an argument.
177 function template(str, data) {
178 return str.replace(templateRe, function (str, key) {
179 var value = data[key];
181 if (value === undefined) {
182 throw new Error('No value provided for variable ' + str);
184 } else if (typeof value === 'function') {
191 // @function isArray(obj): Boolean
192 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
193 var isArray = Array.isArray || function (obj) {
194 return (Object.prototype.toString.call(obj) === '[object Array]');
197 // @function indexOf(array: Array, el: Object): Number
198 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
199 function indexOf(array, el) {
200 for (var i = 0; i < array.length; i++) {
201 if (array[i] === el) { return i; }
206 // @property emptyImageUrl: String
207 // Data URI string containing a base64-encoded empty GIF image.
208 // Used as a hack to free memory from unused images on WebKit-powered
209 // mobile devices (by setting image `src` to this string).
210 var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
212 // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
214 function getPrefixed(name) {
215 return window['webkit' + name] || window['moz' + name] || window['ms' + name];
220 // fallback for IE 7-8
221 function timeoutDefer(fn) {
222 var time = +new Date(),
223 timeToCall = Math.max(0, 16 - (time - lastTime));
225 lastTime = time + timeToCall;
226 return window.setTimeout(fn, timeToCall);
229 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
230 var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
231 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
233 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
234 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
235 // `context` if given. When `immediate` is set, `fn` is called immediately if
236 // the browser doesn't have native support for
237 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
238 // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
239 function requestAnimFrame(fn, context, immediate) {
240 if (immediate && requestFn === timeoutDefer) {
243 return requestFn.call(window, bind(fn, context));
247 // @function cancelAnimFrame(id: Number): undefined
248 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
249 function cancelAnimFrame(id) {
251 cancelFn.call(window, id);
256 var Util = (Object.freeze || Object)({
266 formatNum: formatNum,
268 splitWords: splitWords,
269 setOptions: setOptions,
270 getParamString: getParamString,
274 emptyImageUrl: emptyImageUrl,
275 requestFn: requestFn,
277 requestAnimFrame: requestAnimFrame,
278 cancelAnimFrame: cancelAnimFrame
287 // Thanks to John Resig and Dean Edwards for inspiration!
291 Class.extend = function (props) {
293 // @function extend(props: Object): Function
294 // [Extends the current class](#class-inheritance) given the properties to be included.
295 // Returns a Javascript function that is a class constructor (to be called with `new`).
296 var NewClass = function () {
298 // call the constructor
299 if (this.initialize) {
300 this.initialize.apply(this, arguments);
303 // call all constructor hooks
304 this.callInitHooks();
307 var parentProto = NewClass.__super__ = this.prototype;
309 var proto = create(parentProto);
310 proto.constructor = NewClass;
312 NewClass.prototype = proto;
314 // inherit parent's statics
315 for (var i in this) {
316 if (this.hasOwnProperty(i) && i !== 'prototype' && i !== '__super__') {
317 NewClass[i] = this[i];
321 // mix static properties into the class
323 extend(NewClass, props.statics);
324 delete props.statics;
327 // mix includes into the prototype
328 if (props.includes) {
329 checkDeprecatedMixinEvents(props.includes);
330 extend.apply(null, [proto].concat(props.includes));
331 delete props.includes;
336 props.options = extend(create(proto.options), props.options);
339 // mix given properties into the prototype
340 extend(proto, props);
342 proto._initHooks = [];
344 // add method for calling all hooks
345 proto.callInitHooks = function () {
347 if (this._initHooksCalled) { return; }
349 if (parentProto.callInitHooks) {
350 parentProto.callInitHooks.call(this);
353 this._initHooksCalled = true;
355 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
356 proto._initHooks[i].call(this);
364 // @function include(properties: Object): this
365 // [Includes a mixin](#class-includes) into the current class.
366 Class.include = function (props) {
367 extend(this.prototype, props);
371 // @function mergeOptions(options: Object): this
372 // [Merges `options`](#class-options) into the defaults of the class.
373 Class.mergeOptions = function (options) {
374 extend(this.prototype.options, options);
378 // @function addInitHook(fn: Function): this
379 // Adds a [constructor hook](#class-constructor-hooks) to the class.
380 Class.addInitHook = function (fn) { // (Function) || (String, args...)
381 var args = Array.prototype.slice.call(arguments, 1);
383 var init = typeof fn === 'function' ? fn : function () {
384 this[fn].apply(this, args);
387 this.prototype._initHooks = this.prototype._initHooks || [];
388 this.prototype._initHooks.push(init);
392 function checkDeprecatedMixinEvents(includes) {
393 if (!L || !L.Mixin) { return; }
395 includes = isArray(includes) ? includes : [includes];
397 for (var i = 0; i < includes.length; i++) {
398 if (includes[i] === L.Mixin.Events) {
399 console.warn('Deprecated include of L.Mixin.Events: ' +
400 'this property will be removed in future releases, ' +
401 'please inherit from L.Evented instead.', new Error().stack);
411 * 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).
416 * map.on('click', function(e) {
421 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
424 * function onClick(e) { ... }
426 * map.on('click', onClick);
427 * map.off('click', onClick);
432 /* @method on(type: String, fn: Function, context?: Object): this
433 * 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'`).
436 * @method on(eventMap: Object): this
437 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
439 on: function (types, fn, context) {
441 // types can be a map of types/handlers
442 if (typeof types === 'object') {
443 for (var type in types) {
444 // we don't process space-separated events here for performance;
445 // it's a hot path since Layer uses the on(obj) syntax
446 this._on(type, types[type], fn);
450 // types can be a string of space-separated words
451 types = splitWords(types);
453 for (var i = 0, len = types.length; i < len; i++) {
454 this._on(types[i], fn, context);
461 /* @method off(type: String, fn?: Function, context?: Object): this
462 * 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.
465 * @method off(eventMap: Object): this
466 * Removes a set of type/listener pairs.
470 * Removes all listeners to all events on the object.
472 off: function (types, fn, context) {
475 // clear all listeners if called without arguments
478 } else if (typeof types === 'object') {
479 for (var type in types) {
480 this._off(type, types[type], fn);
484 types = splitWords(types);
486 for (var i = 0, len = types.length; i < len; i++) {
487 this._off(types[i], fn, context);
494 // attach listener (without syntactic sugar now)
495 _on: function (type, fn, context) {
496 this._events = this._events || {};
498 /* get/init listeners for type */
499 var typeListeners = this._events[type];
500 if (!typeListeners) {
502 this._events[type] = typeListeners;
505 if (context === this) {
506 // Less memory footprint.
509 var newListener = {fn: fn, ctx: context},
510 listeners = typeListeners;
512 // check if fn already there
513 for (var i = 0, len = listeners.length; i < len; i++) {
514 if (listeners[i].fn === fn && listeners[i].ctx === context) {
519 listeners.push(newListener);
522 _off: function (type, fn, context) {
527 if (!this._events) { return; }
529 listeners = this._events[type];
536 // Set all removed listeners to noop so they are not called if remove happens in fire
537 for (i = 0, len = listeners.length; i < len; i++) {
538 listeners[i].fn = falseFn;
540 // clear all listeners for a type if function isn't specified
541 delete this._events[type];
545 if (context === this) {
551 // find fn and remove it
552 for (i = 0, len = listeners.length; i < len; i++) {
553 var l = listeners[i];
554 if (l.ctx !== context) { continue; }
557 // set the removed listener to noop so that's not called if remove happens in fire
560 if (this._firingCount) {
561 /* copy array in case events are being fired */
562 this._events[type] = listeners = listeners.slice();
564 listeners.splice(i, 1);
572 // @method fire(type: String, data?: Object, propagate?: Boolean): this
573 // Fires an event of the specified type. You can optionally provide an data
574 // object — the first argument of the listener function will contain its
575 // properties. The event can optionally be propagated to event parents.
576 fire: function (type, data, propagate) {
577 if (!this.listens(type, propagate)) { return this; }
579 var event = extend({}, data, {type: type, target: this});
582 var listeners = this._events[type];
585 this._firingCount = (this._firingCount + 1) || 1;
586 for (var i = 0, len = listeners.length; i < len; i++) {
587 var l = listeners[i];
588 l.fn.call(l.ctx || this, event);
596 // propagate the event to parents (set with addEventParent)
597 this._propagateEvent(event);
603 // @method listens(type: String): Boolean
604 // Returns `true` if a particular event type has any listeners attached to it.
605 listens: function (type, propagate) {
606 var listeners = this._events && this._events[type];
607 if (listeners && listeners.length) { return true; }
610 // also check parents for listeners if event propagates
611 for (var id in this._eventParents) {
612 if (this._eventParents[id].listens(type, propagate)) { return true; }
618 // @method once(…): this
619 // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
620 once: function (types, fn, context) {
622 if (typeof types === 'object') {
623 for (var type in types) {
624 this.once(type, types[type], fn);
629 var handler = bind(function () {
631 .off(types, fn, context)
632 .off(types, handler, context);
635 // add a listener that's executed once and removed after that
637 .on(types, fn, context)
638 .on(types, handler, context);
641 // @method addEventParent(obj: Evented): this
642 // Adds an event parent - an `Evented` that will receive propagated events
643 addEventParent: function (obj) {
644 this._eventParents = this._eventParents || {};
645 this._eventParents[stamp(obj)] = obj;
649 // @method removeEventParent(obj: Evented): this
650 // Removes an event parent, so it will stop receiving propagated events
651 removeEventParent: function (obj) {
652 if (this._eventParents) {
653 delete this._eventParents[stamp(obj)];
658 _propagateEvent: function (e) {
659 for (var id in this._eventParents) {
660 this._eventParents[id].fire(e.type, extend({layer: e.target}, e), true);
665 // aliases; we should ditch those eventually
667 // @method addEventListener(…): this
668 // Alias to [`on(…)`](#evented-on)
669 Events.addEventListener = Events.on;
671 // @method removeEventListener(…): this
672 // Alias to [`off(…)`](#evented-off)
674 // @method clearAllEventListeners(…): this
675 // Alias to [`off()`](#evented-off)
676 Events.removeEventListener = Events.clearAllEventListeners = Events.off;
678 // @method addOneTimeEventListener(…): this
679 // Alias to [`once(…)`](#evented-once)
680 Events.addOneTimeEventListener = Events.once;
682 // @method fireEvent(…): this
683 // Alias to [`fire(…)`](#evented-fire)
684 Events.fireEvent = Events.fire;
686 // @method hasEventListeners(…): Boolean
687 // Alias to [`listens(…)`](#evented-listens)
688 Events.hasEventListeners = Events.listens;
690 var Evented = Class.extend(Events);
696 * Represents a point with `x` and `y` coordinates in pixels.
701 * var point = L.point(200, 300);
704 * 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:
707 * map.panBy([200, 300]);
708 * map.panBy(L.point(200, 300));
712 function Point(x, y, round) {
713 // @property x: Number; The `x` coordinate of the point
714 this.x = (round ? Math.round(x) : x);
715 // @property y: Number; The `y` coordinate of the point
716 this.y = (round ? Math.round(y) : y);
721 // @method clone(): Point
722 // Returns a copy of the current point.
724 return new Point(this.x, this.y);
727 // @method add(otherPoint: Point): Point
728 // Returns the result of addition of the current and the given points.
729 add: function (point) {
730 // non-destructive, returns a new point
731 return this.clone()._add(toPoint(point));
734 _add: function (point) {
735 // destructive, used directly for performance in situations where it's safe to modify existing point
741 // @method subtract(otherPoint: Point): Point
742 // Returns the result of subtraction of the given point from the current.
743 subtract: function (point) {
744 return this.clone()._subtract(toPoint(point));
747 _subtract: function (point) {
753 // @method divideBy(num: Number): Point
754 // Returns the result of division of the current point by the given number.
755 divideBy: function (num) {
756 return this.clone()._divideBy(num);
759 _divideBy: function (num) {
765 // @method multiplyBy(num: Number): Point
766 // Returns the result of multiplication of the current point by the given number.
767 multiplyBy: function (num) {
768 return this.clone()._multiplyBy(num);
771 _multiplyBy: function (num) {
777 // @method scaleBy(scale: Point): Point
778 // Multiply each coordinate of the current point by each coordinate of
779 // `scale`. In linear algebra terms, multiply the point by the
780 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
781 // defined by `scale`.
782 scaleBy: function (point) {
783 return new Point(this.x * point.x, this.y * point.y);
786 // @method unscaleBy(scale: Point): Point
787 // Inverse of `scaleBy`. Divide each coordinate of the current point by
788 // each coordinate of `scale`.
789 unscaleBy: function (point) {
790 return new Point(this.x / point.x, this.y / point.y);
793 // @method round(): Point
794 // Returns a copy of the current point with rounded coordinates.
796 return this.clone()._round();
799 _round: function () {
800 this.x = Math.round(this.x);
801 this.y = Math.round(this.y);
805 // @method floor(): Point
806 // Returns a copy of the current point with floored coordinates (rounded down).
808 return this.clone()._floor();
811 _floor: function () {
812 this.x = Math.floor(this.x);
813 this.y = Math.floor(this.y);
817 // @method ceil(): Point
818 // Returns a copy of the current point with ceiled coordinates (rounded up).
820 return this.clone()._ceil();
824 this.x = Math.ceil(this.x);
825 this.y = Math.ceil(this.y);
829 // @method distanceTo(otherPoint: Point): Number
830 // Returns the cartesian distance between the current and the given points.
831 distanceTo: function (point) {
832 point = toPoint(point);
834 var x = point.x - this.x,
835 y = point.y - this.y;
837 return Math.sqrt(x * x + y * y);
840 // @method equals(otherPoint: Point): Boolean
841 // Returns `true` if the given point has the same coordinates.
842 equals: function (point) {
843 point = toPoint(point);
845 return point.x === this.x &&
849 // @method contains(otherPoint: Point): Boolean
850 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
851 contains: function (point) {
852 point = toPoint(point);
854 return Math.abs(point.x) <= Math.abs(this.x) &&
855 Math.abs(point.y) <= Math.abs(this.y);
858 // @method toString(): String
859 // Returns a string representation of the point for debugging purposes.
860 toString: function () {
862 formatNum(this.x) + ', ' +
863 formatNum(this.y) + ')';
867 // @factory L.point(x: Number, y: Number, round?: Boolean)
868 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
871 // @factory L.point(coords: Number[])
872 // Expects an array of the form `[x, y]` instead.
875 // @factory L.point(coords: Object)
876 // Expects a plain object of the form `{x: Number, y: Number}` instead.
877 function toPoint(x, y, round) {
878 if (x instanceof Point) {
882 return new Point(x[0], x[1]);
884 if (x === undefined || x === null) {
887 if (typeof x === 'object' && 'x' in x && 'y' in x) {
888 return new Point(x.x, x.y);
890 return new Point(x, y, round);
897 * Represents a rectangular area in pixel coordinates.
902 * var p1 = L.point(10, 10),
903 * p2 = L.point(40, 60),
904 * bounds = L.bounds(p1, p2);
907 * 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:
910 * otherBounds.intersects([[10, 10], [40, 60]]);
914 function Bounds(a, b) {
917 var points = b ? [a, b] : a;
919 for (var i = 0, len = points.length; i < len; i++) {
920 this.extend(points[i]);
925 // @method extend(point: Point): this
926 // Extends the bounds to contain the given point.
927 extend: function (point) { // (Point)
928 point = toPoint(point);
930 // @property min: Point
931 // The top left corner of the rectangle.
932 // @property max: Point
933 // The bottom right corner of the rectangle.
934 if (!this.min && !this.max) {
935 this.min = point.clone();
936 this.max = point.clone();
938 this.min.x = Math.min(point.x, this.min.x);
939 this.max.x = Math.max(point.x, this.max.x);
940 this.min.y = Math.min(point.y, this.min.y);
941 this.max.y = Math.max(point.y, this.max.y);
946 // @method getCenter(round?: Boolean): Point
947 // Returns the center point of the bounds.
948 getCenter: function (round) {
950 (this.min.x + this.max.x) / 2,
951 (this.min.y + this.max.y) / 2, round);
954 // @method getBottomLeft(): Point
955 // Returns the bottom-left point of the bounds.
956 getBottomLeft: function () {
957 return new Point(this.min.x, this.max.y);
960 // @method getTopRight(): Point
961 // Returns the top-right point of the bounds.
962 getTopRight: function () { // -> Point
963 return new Point(this.max.x, this.min.y);
966 // @method getTopLeft(): Point
967 // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).
968 getTopLeft: function () {
969 return this.min; // left, top
972 // @method getBottomRight(): Point
973 // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).
974 getBottomRight: function () {
975 return this.max; // right, bottom
978 // @method getSize(): Point
979 // Returns the size of the given bounds
980 getSize: function () {
981 return this.max.subtract(this.min);
984 // @method contains(otherBounds: Bounds): Boolean
985 // Returns `true` if the rectangle contains the given one.
987 // @method contains(point: Point): Boolean
988 // Returns `true` if the rectangle contains the given point.
989 contains: function (obj) {
992 if (typeof obj[0] === 'number' || obj instanceof Point) {
998 if (obj instanceof Bounds) {
1005 return (min.x >= this.min.x) &&
1006 (max.x <= this.max.x) &&
1007 (min.y >= this.min.y) &&
1008 (max.y <= this.max.y);
1011 // @method intersects(otherBounds: Bounds): Boolean
1012 // Returns `true` if the rectangle intersects the given bounds. Two bounds
1013 // intersect if they have at least one point in common.
1014 intersects: function (bounds) { // (Bounds) -> Boolean
1015 bounds = toBounds(bounds);
1021 xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1022 yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1024 return xIntersects && yIntersects;
1027 // @method overlaps(otherBounds: Bounds): Boolean
1028 // Returns `true` if the rectangle overlaps the given bounds. Two bounds
1029 // overlap if their intersection is an area.
1030 overlaps: function (bounds) { // (Bounds) -> Boolean
1031 bounds = toBounds(bounds);
1037 xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1038 yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1040 return xOverlaps && yOverlaps;
1043 isValid: function () {
1044 return !!(this.min && this.max);
1049 // @factory L.bounds(corner1: Point, corner2: Point)
1050 // Creates a Bounds object from two corners coordinate pairs.
1052 // @factory L.bounds(points: Point[])
1053 // Creates a Bounds object from the given array of points.
1054 function toBounds(a, b) {
1055 if (!a || a instanceof Bounds) {
1058 return new Bounds(a, b);
1062 * @class LatLngBounds
1063 * @aka L.LatLngBounds
1065 * Represents a rectangular geographical area on a map.
1070 * var corner1 = L.latLng(40.712, -74.227),
1071 * corner2 = L.latLng(40.774, -74.125),
1072 * bounds = L.latLngBounds(corner1, corner2);
1075 * 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:
1079 * [40.712, -74.227],
1084 * 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.
1087 function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
1088 if (!corner1) { return; }
1090 var latlngs = corner2 ? [corner1, corner2] : corner1;
1092 for (var i = 0, len = latlngs.length; i < len; i++) {
1093 this.extend(latlngs[i]);
1097 LatLngBounds.prototype = {
1099 // @method extend(latlng: LatLng): this
1100 // Extend the bounds to contain the given point
1103 // @method extend(otherBounds: LatLngBounds): this
1104 // Extend the bounds to contain the given bounds
1105 extend: function (obj) {
1106 var sw = this._southWest,
1107 ne = this._northEast,
1110 if (obj instanceof LatLng) {
1114 } else if (obj instanceof LatLngBounds) {
1115 sw2 = obj._southWest;
1116 ne2 = obj._northEast;
1118 if (!sw2 || !ne2) { return this; }
1121 return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
1125 this._southWest = new LatLng(sw2.lat, sw2.lng);
1126 this._northEast = new LatLng(ne2.lat, ne2.lng);
1128 sw.lat = Math.min(sw2.lat, sw.lat);
1129 sw.lng = Math.min(sw2.lng, sw.lng);
1130 ne.lat = Math.max(ne2.lat, ne.lat);
1131 ne.lng = Math.max(ne2.lng, ne.lng);
1137 // @method pad(bufferRatio: Number): LatLngBounds
1138 // Returns bigger bounds created by extending the current bounds by a given percentage in each direction.
1139 pad: function (bufferRatio) {
1140 var sw = this._southWest,
1141 ne = this._northEast,
1142 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1143 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1145 return new LatLngBounds(
1146 new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1147 new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1150 // @method getCenter(): LatLng
1151 // Returns the center point of the bounds.
1152 getCenter: function () {
1154 (this._southWest.lat + this._northEast.lat) / 2,
1155 (this._southWest.lng + this._northEast.lng) / 2);
1158 // @method getSouthWest(): LatLng
1159 // Returns the south-west point of the bounds.
1160 getSouthWest: function () {
1161 return this._southWest;
1164 // @method getNorthEast(): LatLng
1165 // Returns the north-east point of the bounds.
1166 getNorthEast: function () {
1167 return this._northEast;
1170 // @method getNorthWest(): LatLng
1171 // Returns the north-west point of the bounds.
1172 getNorthWest: function () {
1173 return new LatLng(this.getNorth(), this.getWest());
1176 // @method getSouthEast(): LatLng
1177 // Returns the south-east point of the bounds.
1178 getSouthEast: function () {
1179 return new LatLng(this.getSouth(), this.getEast());
1182 // @method getWest(): Number
1183 // Returns the west longitude of the bounds
1184 getWest: function () {
1185 return this._southWest.lng;
1188 // @method getSouth(): Number
1189 // Returns the south latitude of the bounds
1190 getSouth: function () {
1191 return this._southWest.lat;
1194 // @method getEast(): Number
1195 // Returns the east longitude of the bounds
1196 getEast: function () {
1197 return this._northEast.lng;
1200 // @method getNorth(): Number
1201 // Returns the north latitude of the bounds
1202 getNorth: function () {
1203 return this._northEast.lat;
1206 // @method contains(otherBounds: LatLngBounds): Boolean
1207 // Returns `true` if the rectangle contains the given one.
1210 // @method contains (latlng: LatLng): Boolean
1211 // Returns `true` if the rectangle contains the given point.
1212 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1213 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
1214 obj = toLatLng(obj);
1216 obj = toLatLngBounds(obj);
1219 var sw = this._southWest,
1220 ne = this._northEast,
1223 if (obj instanceof LatLngBounds) {
1224 sw2 = obj.getSouthWest();
1225 ne2 = obj.getNorthEast();
1230 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1231 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1234 // @method intersects(otherBounds: LatLngBounds): Boolean
1235 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1236 intersects: function (bounds) {
1237 bounds = toLatLngBounds(bounds);
1239 var sw = this._southWest,
1240 ne = this._northEast,
1241 sw2 = bounds.getSouthWest(),
1242 ne2 = bounds.getNorthEast(),
1244 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1245 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1247 return latIntersects && lngIntersects;
1250 // @method overlaps(otherBounds: Bounds): Boolean
1251 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1252 overlaps: function (bounds) {
1253 bounds = toLatLngBounds(bounds);
1255 var sw = this._southWest,
1256 ne = this._northEast,
1257 sw2 = bounds.getSouthWest(),
1258 ne2 = bounds.getNorthEast(),
1260 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1261 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1263 return latOverlaps && lngOverlaps;
1266 // @method toBBoxString(): String
1267 // 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.
1268 toBBoxString: function () {
1269 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1272 // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
1273 // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overriden by setting `maxMargin` to a small number.
1274 equals: function (bounds, maxMargin) {
1275 if (!bounds) { return false; }
1277 bounds = toLatLngBounds(bounds);
1279 return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
1280 this._northEast.equals(bounds.getNorthEast(), maxMargin);
1283 // @method isValid(): Boolean
1284 // Returns `true` if the bounds are properly initialized.
1285 isValid: function () {
1286 return !!(this._southWest && this._northEast);
1290 // TODO International date line?
1292 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
1293 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
1296 // @factory L.latLngBounds(latlngs: LatLng[])
1297 // 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).
1298 function toLatLngBounds(a, b) {
1299 if (a instanceof LatLngBounds) {
1302 return new LatLngBounds(a, b);
1308 * Represents a geographical point with a certain latitude and longitude.
1313 * var latlng = L.latLng(50.5, 30.5);
1316 * 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:
1319 * map.panTo([50, 30]);
1320 * map.panTo({lon: 30, lat: 50});
1321 * map.panTo({lat: 50, lng: 30});
1322 * map.panTo(L.latLng(50, 30));
1326 function LatLng(lat, lng, alt) {
1327 if (isNaN(lat) || isNaN(lng)) {
1328 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1331 // @property lat: Number
1332 // Latitude in degrees
1335 // @property lng: Number
1336 // Longitude in degrees
1339 // @property alt: Number
1340 // Altitude in meters (optional)
1341 if (alt !== undefined) {
1346 LatLng.prototype = {
1347 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1348 // 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.
1349 equals: function (obj, maxMargin) {
1350 if (!obj) { return false; }
1352 obj = toLatLng(obj);
1354 var margin = Math.max(
1355 Math.abs(this.lat - obj.lat),
1356 Math.abs(this.lng - obj.lng));
1358 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1361 // @method toString(): String
1362 // Returns a string representation of the point (for debugging purposes).
1363 toString: function (precision) {
1365 formatNum(this.lat, precision) + ', ' +
1366 formatNum(this.lng, precision) + ')';
1369 // @method distanceTo(otherLatLng: LatLng): Number
1370 // Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula).
1371 distanceTo: function (other) {
1372 return Earth.distance(this, toLatLng(other));
1375 // @method wrap(): LatLng
1376 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1378 return Earth.wrapLatLng(this);
1381 // @method toBounds(sizeInMeters: Number): LatLngBounds
1382 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
1383 toBounds: function (sizeInMeters) {
1384 var latAccuracy = 180 * sizeInMeters / 40075017,
1385 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1387 return toLatLngBounds(
1388 [this.lat - latAccuracy, this.lng - lngAccuracy],
1389 [this.lat + latAccuracy, this.lng + lngAccuracy]);
1392 clone: function () {
1393 return new LatLng(this.lat, this.lng, this.alt);
1399 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1400 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1403 // @factory L.latLng(coords: Array): LatLng
1404 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1407 // @factory L.latLng(coords: Object): LatLng
1408 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1410 function toLatLng(a, b, c) {
1411 if (a instanceof LatLng) {
1414 if (isArray(a) && typeof a[0] !== 'object') {
1415 if (a.length === 3) {
1416 return new LatLng(a[0], a[1], a[2]);
1418 if (a.length === 2) {
1419 return new LatLng(a[0], a[1]);
1423 if (a === undefined || a === null) {
1426 if (typeof a === 'object' && 'lat' in a) {
1427 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1429 if (b === undefined) {
1432 return new LatLng(a, b, c);
1438 * Object that defines coordinate reference systems for projecting
1439 * geographical points into pixel (screen) coordinates and back (and to
1440 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
1441 * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
1443 * Leaflet defines the most usual CRSs by default. If you want to use a
1444 * CRS not defined by default, take a look at the
1445 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
1449 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
1450 // Projects geographical coordinates into pixel coordinates for a given zoom.
1451 latLngToPoint: function (latlng, zoom) {
1452 var projectedPoint = this.projection.project(latlng),
1453 scale = this.scale(zoom);
1455 return this.transformation._transform(projectedPoint, scale);
1458 // @method pointToLatLng(point: Point, zoom: Number): LatLng
1459 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
1460 // zoom into geographical coordinates.
1461 pointToLatLng: function (point, zoom) {
1462 var scale = this.scale(zoom),
1463 untransformedPoint = this.transformation.untransform(point, scale);
1465 return this.projection.unproject(untransformedPoint);
1468 // @method project(latlng: LatLng): Point
1469 // Projects geographical coordinates into coordinates in units accepted for
1470 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
1471 project: function (latlng) {
1472 return this.projection.project(latlng);
1475 // @method unproject(point: Point): LatLng
1476 // Given a projected coordinate returns the corresponding LatLng.
1477 // The inverse of `project`.
1478 unproject: function (point) {
1479 return this.projection.unproject(point);
1482 // @method scale(zoom: Number): Number
1483 // Returns the scale used when transforming projected coordinates into
1484 // pixel coordinates for a particular zoom. For example, it returns
1485 // `256 * 2^zoom` for Mercator-based CRS.
1486 scale: function (zoom) {
1487 return 256 * Math.pow(2, zoom);
1490 // @method zoom(scale: Number): Number
1491 // Inverse of `scale()`, returns the zoom level corresponding to a scale
1492 // factor of `scale`.
1493 zoom: function (scale) {
1494 return Math.log(scale / 256) / Math.LN2;
1497 // @method getProjectedBounds(zoom: Number): Bounds
1498 // Returns the projection's bounds scaled and transformed for the provided `zoom`.
1499 getProjectedBounds: function (zoom) {
1500 if (this.infinite) { return null; }
1502 var b = this.projection.bounds,
1503 s = this.scale(zoom),
1504 min = this.transformation.transform(b.min, s),
1505 max = this.transformation.transform(b.max, s);
1507 return new Bounds(min, max);
1510 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
1511 // Returns the distance between two geographical coordinates.
1513 // @property code: String
1514 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
1516 // @property wrapLng: Number[]
1517 // An array of two numbers defining whether the longitude (horizontal) coordinate
1518 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
1519 // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
1521 // @property wrapLat: Number[]
1522 // Like `wrapLng`, but for the latitude (vertical) axis.
1524 // wrapLng: [min, max],
1525 // wrapLat: [min, max],
1527 // @property infinite: Boolean
1528 // If true, the coordinate space will be unbounded (infinite in both axes)
1531 // @method wrapLatLng(latlng: LatLng): LatLng
1532 // Returns a `LatLng` where lat and lng has been wrapped according to the
1533 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
1534 wrapLatLng: function (latlng) {
1535 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
1536 lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
1539 return new LatLng(lat, lng, alt);
1542 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
1543 // Returns a `LatLngBounds` with the same size as the given one, ensuring
1544 // that its center is within the CRS's bounds.
1545 // Only accepts actual `L.LatLngBounds` instances, not arrays.
1546 wrapLatLngBounds: function (bounds) {
1547 var center = bounds.getCenter(),
1548 newCenter = this.wrapLatLng(center),
1549 latShift = center.lat - newCenter.lat,
1550 lngShift = center.lng - newCenter.lng;
1552 if (latShift === 0 && lngShift === 0) {
1556 var sw = bounds.getSouthWest(),
1557 ne = bounds.getNorthEast(),
1558 newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),
1559 newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);
1561 return new LatLngBounds(newSw, newNe);
1569 * Serves as the base for CRS that are global such that they cover the earth.
1570 * Can only be used as the base for other CRS and cannot be used directly,
1571 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
1575 var Earth = extend({}, CRS, {
1576 wrapLng: [-180, 180],
1578 // Mean Earth Radius, as recommended for use by
1579 // the International Union of Geodesy and Geophysics,
1580 // see http://rosettacode.org/wiki/Haversine_formula
1583 // distance between two geographical points using spherical law of cosines approximation
1584 distance: function (latlng1, latlng2) {
1585 var rad = Math.PI / 180,
1586 lat1 = latlng1.lat * rad,
1587 lat2 = latlng2.lat * rad,
1588 a = Math.sin(lat1) * Math.sin(lat2) +
1589 Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad);
1591 return this.R * Math.acos(Math.min(a, 1));
1596 * @namespace Projection
1597 * @projection L.Projection.SphericalMercator
1599 * Spherical Mercator projection — the most common projection for online maps,
1600 * used by almost all free and commercial tile providers. Assumes that Earth is
1601 * a sphere. Used by the `EPSG:3857` CRS.
1604 var SphericalMercator = {
1607 MAX_LATITUDE: 85.0511287798,
1609 project: function (latlng) {
1610 var d = Math.PI / 180,
1611 max = this.MAX_LATITUDE,
1612 lat = Math.max(Math.min(max, latlng.lat), -max),
1613 sin = Math.sin(lat * d);
1616 this.R * latlng.lng * d,
1617 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
1620 unproject: function (point) {
1621 var d = 180 / Math.PI;
1624 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
1625 point.x * d / this.R);
1628 bounds: (function () {
1629 var d = 6378137 * Math.PI;
1630 return new Bounds([-d, -d], [d, d]);
1635 * @class Transformation
1636 * @aka L.Transformation
1638 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1639 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1640 * the reverse. Used by Leaflet in its projections code.
1645 * var transformation = L.transformation(2, 5, -1, 10),
1646 * p = L.point(1, 2),
1647 * p2 = transformation.transform(p), // L.point(7, 8)
1648 * p3 = transformation.untransform(p2); // L.point(1, 2)
1653 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1654 // Creates a `Transformation` object with the given coefficients.
1655 function Transformation(a, b, c, d) {
1657 // use array properties
1670 Transformation.prototype = {
1671 // @method transform(point: Point, scale?: Number): Point
1672 // Returns a transformed point, optionally multiplied by the given scale.
1673 // Only accepts actual `L.Point` instances, not arrays.
1674 transform: function (point, scale) { // (Point, Number) -> Point
1675 return this._transform(point.clone(), scale);
1678 // destructive transform (faster)
1679 _transform: function (point, scale) {
1681 point.x = scale * (this._a * point.x + this._b);
1682 point.y = scale * (this._c * point.y + this._d);
1686 // @method untransform(point: Point, scale?: Number): Point
1687 // Returns the reverse transformation of the given point, optionally divided
1688 // by the given scale. Only accepts actual `L.Point` instances, not arrays.
1689 untransform: function (point, scale) {
1692 (point.x / scale - this._b) / this._a,
1693 (point.y / scale - this._d) / this._c);
1697 // factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1699 // @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1700 // Instantiates a Transformation object with the given coefficients.
1703 // @factory L.transformation(coefficients: Array): Transformation
1704 // Expects an coeficients array of the form
1705 // `[a: Number, b: Number, c: Number, d: Number]`.
1707 function toTransformation(a, b, c, d) {
1708 return new Transformation(a, b, c, d);
1713 * @crs L.CRS.EPSG3857
1715 * The most common CRS for online maps, used by almost all free and commercial
1716 * tile providers. Uses Spherical Mercator projection. Set in by default in
1717 * Map's `crs` option.
1720 var EPSG3857 = extend({}, Earth, {
1722 projection: SphericalMercator,
1724 transformation: (function () {
1725 var scale = 0.5 / (Math.PI * SphericalMercator.R);
1726 return toTransformation(scale, 0.5, -scale, 0.5);
1730 var EPSG900913 = extend({}, EPSG3857, {
1734 // @namespace SVG; @section
1735 // There are several static functions which can be called without instantiating L.SVG:
1737 // @function create(name: String): SVGElement
1738 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
1739 // corresponding to the class name passed. For example, using 'line' will return
1740 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
1741 function svgCreate(name) {
1742 return document.createElementNS('http://www.w3.org/2000/svg', name);
1745 // @function pointsToPath(rings: Point[], closed: Boolean): String
1746 // Generates a SVG path string for multiple rings, with each ring turning
1747 // into "M..L..L.." instructions
1748 function pointsToPath(rings, closed) {
1750 i, j, len, len2, points, p;
1752 for (i = 0, len = rings.length; i < len; i++) {
1755 for (j = 0, len2 = points.length; j < len2; j++) {
1757 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
1760 // closes the ring for polygons; "x" is VML syntax
1761 str += closed ? (svg ? 'z' : 'x') : '';
1764 // SVG complains about empty path strings
1765 return str || 'M0 0';
1769 * @namespace Browser
1772 * A namespace with static properties for browser/feature detection used by Leaflet internally.
1777 * if (L.Browser.ielt9) {
1778 * alert('Upgrade your browser, dude!');
1783 var style$1 = document.documentElement.style;
1785 // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
1786 var ie = 'ActiveXObject' in window;
1788 // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
1789 var ielt9 = ie && !document.addEventListener;
1791 // @property edge: Boolean; `true` for the Edge web browser.
1792 var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
1794 // @property webkit: Boolean;
1795 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
1796 var webkit = userAgentContains('webkit');
1798 // @property android: Boolean
1799 // `true` for any browser running on an Android platform.
1800 var android = userAgentContains('android');
1802 // @property android23: Boolean; `true` for browsers running on Android 2 or Android 3.
1803 var android23 = userAgentContains('android 2') || userAgentContains('android 3');
1805 // @property opera: Boolean; `true` for the Opera browser
1806 var opera = !!window.opera;
1808 // @property chrome: Boolean; `true` for the Chrome browser.
1809 var chrome = userAgentContains('chrome');
1811 // @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
1812 var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
1814 // @property safari: Boolean; `true` for the Safari browser.
1815 var safari = !chrome && userAgentContains('safari');
1817 var phantom = userAgentContains('phantom');
1819 // @property opera12: Boolean
1820 // `true` for the Opera browser supporting CSS transforms (version 12 or later).
1821 var opera12 = 'OTransition' in style$1;
1823 // @property win: Boolean; `true` when the browser is running in a Windows platform
1824 var win = navigator.platform.indexOf('Win') === 0;
1826 // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
1827 var ie3d = ie && ('transition' in style$1);
1829 // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
1830 var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;
1832 // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
1833 var gecko3d = 'MozPerspective' in style$1;
1835 // @property any3d: Boolean
1836 // `true` for all browsers supporting CSS transforms.
1837 var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
1839 // @property mobile: Boolean; `true` for all browsers running in a mobile device.
1840 var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
1842 // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
1843 var mobileWebkit = mobile && webkit;
1845 // @property mobileWebkit3d: Boolean
1846 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
1847 var mobileWebkit3d = mobile && webkit3d;
1849 // @property msPointer: Boolean
1850 // `true` for browsers implementing the Microsoft touch events model (notably IE10).
1851 var msPointer = !window.PointerEvent && window.MSPointerEvent;
1853 // @property pointer: Boolean
1854 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
1855 var pointer = !!(window.PointerEvent || msPointer);
1857 // @property touch: Boolean
1858 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
1859 // This does not necessarily mean that the browser is running in a computer with
1860 // a touchscreen, it only means that the browser is capable of understanding
1862 var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
1863 (window.DocumentTouch && document instanceof window.DocumentTouch));
1865 // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
1866 var mobileOpera = mobile && opera;
1868 // @property mobileGecko: Boolean
1869 // `true` for gecko-based browsers running in a mobile device.
1870 var mobileGecko = mobile && gecko;
1872 // @property retina: Boolean
1873 // `true` for browsers on a high-resolution "retina" screen.
1874 var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
1877 // @property canvas: Boolean
1878 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
1879 var canvas = (function () {
1880 return !!document.createElement('canvas').getContext;
1883 // @property svg: Boolean
1884 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
1885 var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect);
1887 // @property vml: Boolean
1888 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
1889 var vml = !svg && (function () {
1891 var div = document.createElement('div');
1892 div.innerHTML = '<v:shape adj="1"/>';
1894 var shape = div.firstChild;
1895 shape.style.behavior = 'url(#default#VML)';
1897 return shape && (typeof shape.adj === 'object');
1905 function userAgentContains(str) {
1906 return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
1910 var Browser = (Object.freeze || Object)({
1916 android23: android23,
1929 mobileWebkit: mobileWebkit,
1930 mobileWebkit3d: mobileWebkit3d,
1931 msPointer: msPointer,
1934 mobileOpera: mobileOpera,
1935 mobileGecko: mobileGecko,
1943 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
1947 var POINTER_DOWN = msPointer ? 'MSPointerDown' : 'pointerdown';
1948 var POINTER_MOVE = msPointer ? 'MSPointerMove' : 'pointermove';
1949 var POINTER_UP = msPointer ? 'MSPointerUp' : 'pointerup';
1950 var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel';
1951 var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION'];
1953 var _pointerDocListener = false;
1955 // DomEvent.DoubleTap needs to know about this
1956 var _pointersCount = 0;
1958 // Provides a touch events wrapper for (ms)pointer events.
1959 // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
1961 function addPointerListener(obj, type, handler, id) {
1962 if (type === 'touchstart') {
1963 _addPointerStart(obj, handler, id);
1965 } else if (type === 'touchmove') {
1966 _addPointerMove(obj, handler, id);
1968 } else if (type === 'touchend') {
1969 _addPointerEnd(obj, handler, id);
1975 function removePointerListener(obj, type, id) {
1976 var handler = obj['_leaflet_' + type + id];
1978 if (type === 'touchstart') {
1979 obj.removeEventListener(POINTER_DOWN, handler, false);
1981 } else if (type === 'touchmove') {
1982 obj.removeEventListener(POINTER_MOVE, handler, false);
1984 } else if (type === 'touchend') {
1985 obj.removeEventListener(POINTER_UP, handler, false);
1986 obj.removeEventListener(POINTER_CANCEL, handler, false);
1992 function _addPointerStart(obj, handler, id) {
1993 var onDown = bind(function (e) {
1994 if (e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
1995 // In IE11, some touch events needs to fire for form controls, or
1996 // the controls will stop working. We keep a whitelist of tag names that
1997 // need these events. For other target tags, we prevent default on the event.
1998 if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
2005 _handlePointer(e, handler);
2008 obj['_leaflet_touchstart' + id] = onDown;
2009 obj.addEventListener(POINTER_DOWN, onDown, false);
2011 // need to keep track of what pointers and how many are active to provide e.touches emulation
2012 if (!_pointerDocListener) {
2013 // we listen documentElement as any drags that end by moving the touch off the screen get fired there
2014 document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true);
2015 document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true);
2016 document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true);
2017 document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
2019 _pointerDocListener = true;
2023 function _globalPointerDown(e) {
2024 _pointers[e.pointerId] = e;
2028 function _globalPointerMove(e) {
2029 if (_pointers[e.pointerId]) {
2030 _pointers[e.pointerId] = e;
2034 function _globalPointerUp(e) {
2035 delete _pointers[e.pointerId];
2039 function _handlePointer(e, handler) {
2041 for (var i in _pointers) {
2042 e.touches.push(_pointers[i]);
2044 e.changedTouches = [e];
2049 function _addPointerMove(obj, handler, id) {
2050 var onMove = function (e) {
2051 // don't fire touch moves when mouse isn't down
2052 if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
2054 _handlePointer(e, handler);
2057 obj['_leaflet_touchmove' + id] = onMove;
2058 obj.addEventListener(POINTER_MOVE, onMove, false);
2061 function _addPointerEnd(obj, handler, id) {
2062 var onUp = function (e) {
2063 _handlePointer(e, handler);
2066 obj['_leaflet_touchend' + id] = onUp;
2067 obj.addEventListener(POINTER_UP, onUp, false);
2068 obj.addEventListener(POINTER_CANCEL, onUp, false);
2072 * Extends the event handling code with double tap support for mobile browsers.
2075 var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart';
2076 var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend';
2077 var _pre = '_leaflet_';
2079 // inspired by Zepto touch code by Thomas Fuchs
2080 function addDoubleTapListener(obj, handler, id) {
2085 function onTouchStart(e) {
2089 if ((!edge) || e.pointerType === 'mouse') { return; }
2090 count = _pointersCount;
2092 count = e.touches.length;
2095 if (count > 1) { return; }
2097 var now = Date.now(),
2098 delta = now - (last || now);
2100 touch$$1 = e.touches ? e.touches[0] : e;
2101 doubleTap = (delta > 0 && delta <= delay);
2105 function onTouchEnd(e) {
2106 if (doubleTap && !touch$$1.cancelBubble) {
2108 if ((!edge) || e.pointerType === 'mouse') { return; }
2109 // work around .type being readonly with MSPointer* events
2113 for (i in touch$$1) {
2115 newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop;
2117 touch$$1 = newTouch;
2119 touch$$1.type = 'dblclick';
2125 obj[_pre + _touchstart + id] = onTouchStart;
2126 obj[_pre + _touchend + id] = onTouchEnd;
2127 obj[_pre + 'dblclick' + id] = handler;
2129 obj.addEventListener(_touchstart, onTouchStart, false);
2130 obj.addEventListener(_touchend, onTouchEnd, false);
2132 // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
2133 // the browser doesn't fire touchend/pointerup events but does fire
2134 // native dblclicks. See #4127.
2135 // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
2136 obj.addEventListener('dblclick', handler, false);
2141 function removeDoubleTapListener(obj, id) {
2142 var touchstart = obj[_pre + _touchstart + id],
2143 touchend = obj[_pre + _touchend + id],
2144 dblclick = obj[_pre + 'dblclick' + id];
2146 obj.removeEventListener(_touchstart, touchstart, false);
2147 obj.removeEventListener(_touchend, touchend, false);
2149 obj.removeEventListener('dblclick', dblclick, false);
2156 * @namespace DomEvent
2157 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
2160 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
2162 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
2163 // Adds a listener function (`fn`) to a particular DOM event type of the
2164 // element `el`. You can optionally specify the context of the listener
2165 // (object the `this` keyword will point to). You can also pass several
2166 // space-separated types (e.g. `'click dblclick'`).
2169 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
2170 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2171 function on(obj, types, fn, context) {
2173 if (typeof types === 'object') {
2174 for (var type in types) {
2175 addOne(obj, type, types[type], fn);
2178 types = splitWords(types);
2180 for (var i = 0, len = types.length; i < len; i++) {
2181 addOne(obj, types[i], fn, context);
2188 var eventsKey = '_leaflet_events';
2190 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
2191 // Removes a previously added listener function. If no function is specified,
2192 // it will remove all the listeners of that particular DOM event from the element.
2193 // Note that if you passed a custom context to on, you must pass the same
2194 // context to `off` in order to remove the listener.
2197 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
2198 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2201 // @function off(el: HTMLElement): this
2202 // Removes all known event listeners
2203 function off(obj, types, fn, context) {
2205 if (typeof types === 'object') {
2206 for (var type in types) {
2207 removeOne(obj, type, types[type], fn);
2210 types = splitWords(types);
2212 for (var i = 0, len = types.length; i < len; i++) {
2213 removeOne(obj, types[i], fn, context);
2216 for (var j in obj[eventsKey]) {
2217 removeOne(obj, j, obj[eventsKey][j]);
2219 delete obj[eventsKey];
2225 function addOne(obj, type, fn, context) {
2226 var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
2228 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
2230 var handler = function (e) {
2231 return fn.call(context || obj, e || window.event);
2234 var originalHandler = handler;
2236 if (pointer && type.indexOf('touch') === 0) {
2237 // Needs DomEvent.Pointer.js
2238 addPointerListener(obj, type, handler, id);
2240 } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
2241 !(pointer && chrome)) {
2242 // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
2244 addDoubleTapListener(obj, handler, id);
2246 } else if ('addEventListener' in obj) {
2248 if (type === 'mousewheel') {
2249 obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2251 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
2252 handler = function (e) {
2253 e = e || window.event;
2254 if (isExternalTarget(obj, e)) {
2258 obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
2261 if (type === 'click' && android) {
2262 handler = function (e) {
2263 filterClick(e, originalHandler);
2266 obj.addEventListener(type, handler, false);
2269 } else if ('attachEvent' in obj) {
2270 obj.attachEvent('on' + type, handler);
2273 obj[eventsKey] = obj[eventsKey] || {};
2274 obj[eventsKey][id] = handler;
2277 function removeOne(obj, type, fn, context) {
2279 var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
2280 handler = obj[eventsKey] && obj[eventsKey][id];
2282 if (!handler) { return this; }
2284 if (pointer && type.indexOf('touch') === 0) {
2285 removePointerListener(obj, type, id);
2287 } else if (touch && (type === 'dblclick') && removeDoubleTapListener) {
2288 removeDoubleTapListener(obj, id);
2290 } else if ('removeEventListener' in obj) {
2292 if (type === 'mousewheel') {
2293 obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2296 obj.removeEventListener(
2297 type === 'mouseenter' ? 'mouseover' :
2298 type === 'mouseleave' ? 'mouseout' : type, handler, false);
2301 } else if ('detachEvent' in obj) {
2302 obj.detachEvent('on' + type, handler);
2305 obj[eventsKey][id] = null;
2308 // @function stopPropagation(ev: DOMEvent): this
2309 // Stop the given event from propagation to parent elements. Used inside the listener functions:
2311 // L.DomEvent.on(div, 'click', function (ev) {
2312 // L.DomEvent.stopPropagation(ev);
2315 function stopPropagation(e) {
2317 if (e.stopPropagation) {
2318 e.stopPropagation();
2319 } else if (e.originalEvent) { // In case of Leaflet event.
2320 e.originalEvent._stopped = true;
2322 e.cancelBubble = true;
2329 // @function disableScrollPropagation(el: HTMLElement): this
2330 // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
2331 function disableScrollPropagation(el) {
2332 addOne(el, 'mousewheel', stopPropagation);
2336 // @function disableClickPropagation(el: HTMLElement): this
2337 // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
2338 // `'mousedown'` and `'touchstart'` events (plus browser variants).
2339 function disableClickPropagation(el) {
2340 on(el, 'mousedown touchstart dblclick', stopPropagation);
2341 addOne(el, 'click', fakeStop);
2345 // @function preventDefault(ev: DOMEvent): this
2346 // Prevents the default action of the DOM Event `ev` from happening (such as
2347 // following a link in the href of the a element, or doing a POST request
2348 // with page reload when a `<form>` is submitted).
2349 // Use it inside listener functions.
2350 function preventDefault(e) {
2351 if (e.preventDefault) {
2354 e.returnValue = false;
2359 // @function stop(ev): this
2360 // Does `stopPropagation` and `preventDefault` at the same time.
2367 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
2368 // Gets normalized mouse position from a DOM event relative to the
2369 // `container` or to the whole page if not specified.
2370 function getMousePosition(e, container) {
2372 return new Point(e.clientX, e.clientY);
2375 var rect = container.getBoundingClientRect();
2378 e.clientX - rect.left - container.clientLeft,
2379 e.clientY - rect.top - container.clientTop);
2382 // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
2383 // and Firefox scrolls device pixels, not CSS pixels
2385 (win && chrome) ? 2 * window.devicePixelRatio :
2386 gecko ? window.devicePixelRatio : 1;
2388 // @function getWheelDelta(ev: DOMEvent): Number
2389 // Gets normalized wheel delta from a mousewheel DOM event, in vertical
2390 // pixels scrolled (negative if scrolling down).
2391 // Events from pointing devices without precise scrolling are mapped to
2392 // a best guess of 60 pixels.
2393 function getWheelDelta(e) {
2394 return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
2395 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
2396 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
2397 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
2398 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
2399 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
2400 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
2401 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
2405 var skipEvents = {};
2407 function fakeStop(e) {
2408 // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
2409 skipEvents[e.type] = true;
2412 function skipped(e) {
2413 var events = skipEvents[e.type];
2414 // reset when checking, as it's only used in map container and propagates outside of the map
2415 skipEvents[e.type] = false;
2419 // check if element really left/entered the event target (for mouseenter/mouseleave)
2420 function isExternalTarget(el, e) {
2422 var related = e.relatedTarget;
2424 if (!related) { return true; }
2427 while (related && (related !== el)) {
2428 related = related.parentNode;
2433 return (related !== el);
2438 // this is a horrible workaround for a bug in Android where a single touch triggers two click events
2439 function filterClick(e, handler) {
2440 var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
2441 elapsed = lastClick && (timeStamp - lastClick);
2443 // are they closer together than 500ms yet more than 100ms?
2444 // Android typically triggers them ~300ms apart while multiple listeners
2445 // on the same event should be triggered far faster;
2446 // or check if click is simulated on the element, and if it is, reject any non-simulated events
2448 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
2452 lastClick = timeStamp;
2460 var DomEvent = (Object.freeze || Object)({
2463 stopPropagation: stopPropagation,
2464 disableScrollPropagation: disableScrollPropagation,
2465 disableClickPropagation: disableClickPropagation,
2466 preventDefault: preventDefault,
2468 getMousePosition: getMousePosition,
2469 getWheelDelta: getWheelDelta,
2472 isExternalTarget: isExternalTarget,
2478 * @namespace DomUtil
2480 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
2481 * tree, used by Leaflet internally.
2483 * Most functions expecting or returning a `HTMLElement` also work for
2484 * SVG elements. The only difference is that classes refer to CSS classes
2485 * in HTML and SVG classes in SVG.
2489 // @property TRANSFORM: String
2490 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
2491 var TRANSFORM = testProp(
2492 ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
2494 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
2495 // the same for the transitionend event, in particular the Android 4.1 stock browser
2497 // @property TRANSITION: String
2498 // Vendor-prefixed transition style name.
2499 var TRANSITION = testProp(
2500 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
2502 // @property TRANSITION_END: String
2503 // Vendor-prefixed transitionend event name.
2504 var TRANSITION_END =
2505 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
2508 // @function get(id: String|HTMLElement): HTMLElement
2509 // Returns an element given its DOM id, or returns the element itself
2510 // if it was passed directly.
2512 return typeof id === 'string' ? document.getElementById(id) : id;
2515 // @function getStyle(el: HTMLElement, styleAttrib: String): String
2516 // Returns the value for a certain style attribute on an element,
2517 // including computed values or values set through CSS.
2518 function getStyle(el, style) {
2519 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
2521 if ((!value || value === 'auto') && document.defaultView) {
2522 var css = document.defaultView.getComputedStyle(el, null);
2523 value = css ? css[style] : null;
2525 return value === 'auto' ? null : value;
2528 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
2529 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
2530 function create$1(tagName, className, container) {
2531 var el = document.createElement(tagName);
2532 el.className = className || '';
2535 container.appendChild(el);
2540 // @function remove(el: HTMLElement)
2541 // Removes `el` from its parent element
2542 function remove(el) {
2543 var parent = el.parentNode;
2545 parent.removeChild(el);
2549 // @function empty(el: HTMLElement)
2550 // Removes all of `el`'s children elements from `el`
2551 function empty(el) {
2552 while (el.firstChild) {
2553 el.removeChild(el.firstChild);
2557 // @function toFront(el: HTMLElement)
2558 // Makes `el` the last child of its parent, so it renders in front of the other children.
2559 function toFront(el) {
2560 var parent = el.parentNode;
2561 if (parent.lastChild !== el) {
2562 parent.appendChild(el);
2566 // @function toBack(el: HTMLElement)
2567 // Makes `el` the first child of its parent, so it renders behind the other children.
2568 function toBack(el) {
2569 var parent = el.parentNode;
2570 if (parent.firstChild !== el) {
2571 parent.insertBefore(el, parent.firstChild);
2575 // @function hasClass(el: HTMLElement, name: String): Boolean
2576 // Returns `true` if the element's class attribute contains `name`.
2577 function hasClass(el, name) {
2578 if (el.classList !== undefined) {
2579 return el.classList.contains(name);
2581 var className = getClass(el);
2582 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
2585 // @function addClass(el: HTMLElement, name: String)
2586 // Adds `name` to the element's class attribute.
2587 function addClass(el, name) {
2588 if (el.classList !== undefined) {
2589 var classes = splitWords(name);
2590 for (var i = 0, len = classes.length; i < len; i++) {
2591 el.classList.add(classes[i]);
2593 } else if (!hasClass(el, name)) {
2594 var className = getClass(el);
2595 setClass(el, (className ? className + ' ' : '') + name);
2599 // @function removeClass(el: HTMLElement, name: String)
2600 // Removes `name` from the element's class attribute.
2601 function removeClass(el, name) {
2602 if (el.classList !== undefined) {
2603 el.classList.remove(name);
2605 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
2609 // @function setClass(el: HTMLElement, name: String)
2610 // Sets the element's class.
2611 function setClass(el, name) {
2612 if (el.className.baseVal === undefined) {
2613 el.className = name;
2615 // in case of SVG element
2616 el.className.baseVal = name;
2620 // @function getClass(el: HTMLElement): String
2621 // Returns the element's class.
2622 function getClass(el) {
2623 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
2626 // @function setOpacity(el: HTMLElement, opacity: Number)
2627 // Set the opacity of an element (including old IE support).
2628 // `opacity` must be a number from `0` to `1`.
2629 function setOpacity(el, value) {
2630 if ('opacity' in el.style) {
2631 el.style.opacity = value;
2632 } else if ('filter' in el.style) {
2633 _setOpacityIE(el, value);
2637 function _setOpacityIE(el, value) {
2639 filterName = 'DXImageTransform.Microsoft.Alpha';
2641 // filters collection throws an error if we try to retrieve a filter that doesn't exist
2643 filter = el.filters.item(filterName);
2645 // don't set opacity to 1 if we haven't already set an opacity,
2646 // it isn't needed and breaks transparent pngs.
2647 if (value === 1) { return; }
2650 value = Math.round(value * 100);
2653 filter.Enabled = (value !== 100);
2654 filter.Opacity = value;
2656 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
2660 // @function testProp(props: String[]): String|false
2661 // Goes through the array of style names and returns the first name
2662 // that is a valid style name for an element. If no such name is found,
2663 // it returns false. Useful for vendor-prefixed styles like `transform`.
2664 function testProp(props) {
2665 var style = document.documentElement.style;
2667 for (var i = 0; i < props.length; i++) {
2668 if (props[i] in style) {
2675 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
2676 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
2677 // and optionally scaled by `scale`. Does not have an effect if the
2678 // browser doesn't support 3D CSS transforms.
2679 function setTransform(el, offset, scale) {
2680 var pos = offset || new Point(0, 0);
2682 el.style[TRANSFORM] =
2684 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
2685 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
2686 (scale ? ' scale(' + scale + ')' : '');
2689 // @function setPosition(el: HTMLElement, position: Point)
2690 // Sets the position of `el` to coordinates specified by `position`,
2691 // using CSS translate or top/left positioning depending on the browser
2692 // (used by Leaflet internally to position its layers).
2693 function setPosition(el, point) {
2696 el._leaflet_pos = point;
2700 setTransform(el, point);
2702 el.style.left = point.x + 'px';
2703 el.style.top = point.y + 'px';
2707 // @function getPosition(el: HTMLElement): Point
2708 // Returns the coordinates of an element previously positioned with setPosition.
2709 function getPosition(el) {
2710 // this method is only used for elements previously positioned using setPosition,
2711 // so it's safe to cache the position for performance
2713 return el._leaflet_pos || new Point(0, 0);
2716 // @function disableTextSelection()
2717 // Prevents the user from generating `selectstart` DOM events, usually generated
2718 // when the user drags the mouse through a page with text. Used internally
2719 // by Leaflet to override the behaviour of any click-and-drag interaction on
2720 // the map. Affects drag interactions on the whole document.
2722 // @function enableTextSelection()
2723 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
2724 var disableTextSelection;
2725 var enableTextSelection;
2727 if ('onselectstart' in document) {
2728 disableTextSelection = function () {
2729 on(window, 'selectstart', preventDefault);
2731 enableTextSelection = function () {
2732 off(window, 'selectstart', preventDefault);
2735 var userSelectProperty = testProp(
2736 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
2738 disableTextSelection = function () {
2739 if (userSelectProperty) {
2740 var style = document.documentElement.style;
2741 _userSelect = style[userSelectProperty];
2742 style[userSelectProperty] = 'none';
2745 enableTextSelection = function () {
2746 if (userSelectProperty) {
2747 document.documentElement.style[userSelectProperty] = _userSelect;
2748 _userSelect = undefined;
2753 // @function disableImageDrag()
2754 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
2755 // for `dragstart` DOM events, usually generated when the user drags an image.
2756 function disableImageDrag() {
2757 on(window, 'dragstart', preventDefault);
2760 // @function enableImageDrag()
2761 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
2762 function enableImageDrag() {
2763 off(window, 'dragstart', preventDefault);
2766 var _outlineElement;
2768 // @function preventOutline(el: HTMLElement)
2769 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
2770 // of the element `el` invisible. Used internally by Leaflet to prevent
2771 // focusable elements from displaying an outline when the user performs a
2772 // drag interaction on them.
2773 function preventOutline(element) {
2774 while (element.tabIndex === -1) {
2775 element = element.parentNode;
2777 if (!element.style) { return; }
2779 _outlineElement = element;
2780 _outlineStyle = element.style.outline;
2781 element.style.outline = 'none';
2782 on(window, 'keydown', restoreOutline);
2785 // @function restoreOutline()
2786 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
2787 function restoreOutline() {
2788 if (!_outlineElement) { return; }
2789 _outlineElement.style.outline = _outlineStyle;
2790 _outlineElement = undefined;
2791 _outlineStyle = undefined;
2792 off(window, 'keydown', restoreOutline);
2796 var DomUtil = (Object.freeze || Object)({
2797 TRANSFORM: TRANSFORM,
2798 TRANSITION: TRANSITION,
2799 TRANSITION_END: TRANSITION_END,
2809 removeClass: removeClass,
2812 setOpacity: setOpacity,
2814 setTransform: setTransform,
2815 setPosition: setPosition,
2816 getPosition: getPosition,
2817 disableTextSelection: disableTextSelection,
2818 enableTextSelection: enableTextSelection,
2819 disableImageDrag: disableImageDrag,
2820 enableImageDrag: enableImageDrag,
2821 preventOutline: preventOutline,
2822 restoreOutline: restoreOutline
2826 * @class PosAnimation
2827 * @aka L.PosAnimation
2829 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
2833 * var fx = new L.PosAnimation();
2834 * fx.run(el, [300, 500], 0.5);
2837 * @constructor L.PosAnimation()
2838 * Creates a `PosAnimation` object.
2842 var PosAnimation = Evented.extend({
2844 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
2845 // Run an animation of a given element to a new position, optionally setting
2846 // duration in seconds (`0.25` by default) and easing linearity factor (3rd
2847 // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
2848 // `0.5` by default).
2849 run: function (el, newPos, duration, easeLinearity) {
2853 this._inProgress = true;
2854 this._duration = duration || 0.25;
2855 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
2857 this._startPos = getPosition(el);
2858 this._offset = newPos.subtract(this._startPos);
2859 this._startTime = +new Date();
2861 // @event start: Event
2862 // Fired when the animation starts
2869 // Stops the animation (if currently running).
2871 if (!this._inProgress) { return; }
2877 _animate: function () {
2879 this._animId = requestAnimFrame(this._animate, this);
2883 _step: function (round) {
2884 var elapsed = (+new Date()) - this._startTime,
2885 duration = this._duration * 1000;
2887 if (elapsed < duration) {
2888 this._runFrame(this._easeOut(elapsed / duration), round);
2895 _runFrame: function (progress, round) {
2896 var pos = this._startPos.add(this._offset.multiplyBy(progress));
2900 setPosition(this._el, pos);
2902 // @event step: Event
2903 // Fired continuously during the animation.
2907 _complete: function () {
2908 cancelAnimFrame(this._animId);
2910 this._inProgress = false;
2911 // @event end: Event
2912 // Fired when the animation ends.
2916 _easeOut: function (t) {
2917 return 1 - Math.pow(1 - t, this._easeOutPower);
2926 * The central class of the API — it is used to create a map on a page and manipulate it.
2931 * // initialize the map on the "map" div with a given center and zoom
2932 * var map = L.map('map', {
2933 * center: [51.505, -0.09],
2940 var Map = Evented.extend({
2943 // @section Map State Options
2944 // @option crs: CRS = L.CRS.EPSG3857
2945 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
2946 // sure what it means.
2949 // @option center: LatLng = undefined
2950 // Initial geographic center of the map
2953 // @option zoom: Number = undefined
2954 // Initial map zoom level
2957 // @option minZoom: Number = *
2958 // Minimum zoom level of the map.
2959 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
2960 // the lowest of their `minZoom` options will be used instead.
2963 // @option maxZoom: Number = *
2964 // Maximum zoom level of the map.
2965 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
2966 // the highest of their `maxZoom` options will be used instead.
2969 // @option layers: Layer[] = []
2970 // Array of layers that will be added to the map initially
2973 // @option maxBounds: LatLngBounds = null
2974 // When this option is set, the map restricts the view to the given
2975 // geographical bounds, bouncing the user back if the user tries to pan
2976 // outside the view. To set the restriction dynamically, use
2977 // [`setMaxBounds`](#map-setmaxbounds) method.
2978 maxBounds: undefined,
2980 // @option renderer: Renderer = *
2981 // The default method for drawing vector layers on the map. `L.SVG`
2982 // or `L.Canvas` by default depending on browser support.
2983 renderer: undefined,
2986 // @section Animation Options
2987 // @option zoomAnimation: Boolean = true
2988 // Whether the map zoom animation is enabled. By default it's enabled
2989 // in all browsers that support CSS3 Transitions except Android.
2990 zoomAnimation: true,
2992 // @option zoomAnimationThreshold: Number = 4
2993 // Won't animate zoom if the zoom difference exceeds this value.
2994 zoomAnimationThreshold: 4,
2996 // @option fadeAnimation: Boolean = true
2997 // Whether the tile fade animation is enabled. By default it's enabled
2998 // in all browsers that support CSS3 Transitions except Android.
2999 fadeAnimation: true,
3001 // @option markerZoomAnimation: Boolean = true
3002 // Whether markers animate their zoom with the zoom animation, if disabled
3003 // they will disappear for the length of the animation. By default it's
3004 // enabled in all browsers that support CSS3 Transitions except Android.
3005 markerZoomAnimation: true,
3007 // @option transform3DLimit: Number = 2^23
3008 // Defines the maximum size of a CSS translation transform. The default
3009 // value should not be changed unless a web browser positions layers in
3010 // the wrong place after doing a large `panBy`.
3011 transform3DLimit: 8388608, // Precision limit of a 32-bit float
3013 // @section Interaction Options
3014 // @option zoomSnap: Number = 1
3015 // Forces the map's zoom level to always be a multiple of this, particularly
3016 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
3017 // By default, the zoom level snaps to the nearest integer; lower values
3018 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
3019 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
3022 // @option zoomDelta: Number = 1
3023 // Controls how much the map's zoom level will change after a
3024 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
3025 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
3026 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
3029 // @option trackResize: Boolean = true
3030 // Whether the map automatically handles browser window resize to update itself.
3034 initialize: function (id, options) { // (HTMLElement or String, Object)
3035 options = setOptions(this, options);
3037 this._initContainer(id);
3040 // hack for https://github.com/Leaflet/Leaflet/issues/1980
3041 this._onResize = bind(this._onResize, this);
3045 if (options.maxBounds) {
3046 this.setMaxBounds(options.maxBounds);
3049 if (options.zoom !== undefined) {
3050 this._zoom = this._limitZoom(options.zoom);
3053 if (options.center && options.zoom !== undefined) {
3054 this.setView(toLatLng(options.center), options.zoom, {reset: true});
3057 this._handlers = [];
3059 this._zoomBoundLayers = {};
3060 this._sizeChanged = true;
3062 this.callInitHooks();
3064 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
3065 this._zoomAnimated = TRANSITION && any3d && !mobileOpera &&
3066 this.options.zoomAnimation;
3068 // zoom transitions run with the same duration for all layers, so if one of transitionend events
3069 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
3070 if (this._zoomAnimated) {
3071 this._createAnimProxy();
3072 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
3075 this._addLayers(this.options.layers);
3079 // @section Methods for modifying map state
3081 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
3082 // Sets the view of the map (geographical center and zoom) with the given
3083 // animation options.
3084 setView: function (center, zoom, options) {
3086 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
3087 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
3088 options = options || {};
3092 if (this._loaded && !options.reset && options !== true) {
3094 if (options.animate !== undefined) {
3095 options.zoom = extend({animate: options.animate}, options.zoom);
3096 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
3099 // try animating pan or zoom
3100 var moved = (this._zoom !== zoom) ?
3101 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
3102 this._tryAnimatedPan(center, options.pan);
3105 // prevent resize handler call, the view will refresh after animation anyway
3106 clearTimeout(this._sizeTimer);
3111 // animation didn't start, just reset the map view
3112 this._resetView(center, zoom);
3117 // @method setZoom(zoom: Number, options?: Zoom/pan options): this
3118 // Sets the zoom of the map.
3119 setZoom: function (zoom, options) {
3120 if (!this._loaded) {
3124 return this.setView(this.getCenter(), zoom, {zoom: options});
3127 // @method zoomIn(delta?: Number, options?: Zoom options): this
3128 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3129 zoomIn: function (delta, options) {
3130 delta = delta || (any3d ? this.options.zoomDelta : 1);
3131 return this.setZoom(this._zoom + delta, options);
3134 // @method zoomOut(delta?: Number, options?: Zoom options): this
3135 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3136 zoomOut: function (delta, options) {
3137 delta = delta || (any3d ? this.options.zoomDelta : 1);
3138 return this.setZoom(this._zoom - delta, options);
3141 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
3142 // Zooms the map while keeping a specified geographical point on the map
3143 // stationary (e.g. used internally for scroll zoom and double-click zoom).
3145 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
3146 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
3147 setZoomAround: function (latlng, zoom, options) {
3148 var scale = this.getZoomScale(zoom),
3149 viewHalf = this.getSize().divideBy(2),
3150 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
3152 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
3153 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
3155 return this.setView(newCenter, zoom, {zoom: options});
3158 _getBoundsCenterZoom: function (bounds, options) {
3160 options = options || {};
3161 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
3163 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3164 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3166 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
3168 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
3170 if (zoom === Infinity) {
3172 center: bounds.getCenter(),
3177 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
3179 swPoint = this.project(bounds.getSouthWest(), zoom),
3180 nePoint = this.project(bounds.getNorthEast(), zoom),
3181 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
3189 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
3190 // Sets a map view that contains the given geographical bounds with the
3191 // maximum zoom level possible.
3192 fitBounds: function (bounds, options) {
3194 bounds = toLatLngBounds(bounds);
3196 if (!bounds.isValid()) {
3197 throw new Error('Bounds are not valid.');
3200 var target = this._getBoundsCenterZoom(bounds, options);
3201 return this.setView(target.center, target.zoom, options);
3204 // @method fitWorld(options?: fitBounds options): this
3205 // Sets a map view that mostly contains the whole world with the maximum
3206 // zoom level possible.
3207 fitWorld: function (options) {
3208 return this.fitBounds([[-90, -180], [90, 180]], options);
3211 // @method panTo(latlng: LatLng, options?: Pan options): this
3212 // Pans the map to a given center.
3213 panTo: function (center, options) { // (LatLng)
3214 return this.setView(center, this._zoom, {pan: options});
3217 // @method panBy(offset: Point, options?: Pan options): this
3218 // Pans the map by a given number of pixels (animated).
3219 panBy: function (offset, options) {
3220 offset = toPoint(offset).round();
3221 options = options || {};
3223 if (!offset.x && !offset.y) {
3224 return this.fire('moveend');
3226 // If we pan too far, Chrome gets issues with tiles
3227 // and makes them disappear or appear in the wrong place (slightly offset) #2602
3228 if (options.animate !== true && !this.getSize().contains(offset)) {
3229 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
3233 if (!this._panAnim) {
3234 this._panAnim = new PosAnimation();
3237 'step': this._onPanTransitionStep,
3238 'end': this._onPanTransitionEnd
3242 // don't fire movestart if animating inertia
3243 if (!options.noMoveStart) {
3244 this.fire('movestart');
3247 // animate pan unless animate: false specified
3248 if (options.animate !== false) {
3249 addClass(this._mapPane, 'leaflet-pan-anim');
3251 var newPos = this._getMapPanePos().subtract(offset).round();
3252 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
3254 this._rawPanBy(offset);
3255 this.fire('move').fire('moveend');
3261 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
3262 // Sets the view of the map (geographical center and zoom) performing a smooth
3263 // pan-zoom animation.
3264 flyTo: function (targetCenter, targetZoom, options) {
3266 options = options || {};
3267 if (options.animate === false || !any3d) {
3268 return this.setView(targetCenter, targetZoom, options);
3273 var from = this.project(this.getCenter()),
3274 to = this.project(targetCenter),
3275 size = this.getSize(),
3276 startZoom = this._zoom;
3278 targetCenter = toLatLng(targetCenter);
3279 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
3281 var w0 = Math.max(size.x, size.y),
3282 w1 = w0 * this.getZoomScale(startZoom, targetZoom),
3283 u1 = (to.distanceTo(from)) || 1,
3288 var s1 = i ? -1 : 1,
3290 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
3291 b1 = 2 * s2 * rho2 * u1,
3293 sq = Math.sqrt(b * b + 1) - b;
3295 // workaround for floating point precision bug when sq = 0, log = -Infinite,
3296 // thus triggering an infinite loop in flyTo
3297 var log = sq < 0.000000001 ? -18 : Math.log(sq);
3302 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
3303 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
3304 function tanh(n) { return sinh(n) / cosh(n); }
3308 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
3309 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
3311 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
3313 var start = Date.now(),
3314 S = (r(1) - r0) / rho,
3315 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
3318 var t = (Date.now() - start) / duration,
3322 this._flyToFrame = requestAnimFrame(frame, this);
3325 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
3326 this.getScaleZoom(w0 / w(s), startZoom),
3331 ._move(targetCenter, targetZoom)
3336 this._moveStart(true);
3342 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
3343 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
3344 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
3345 flyToBounds: function (bounds, options) {
3346 var target = this._getBoundsCenterZoom(bounds, options);
3347 return this.flyTo(target.center, target.zoom, options);
3350 // @method setMaxBounds(bounds: Bounds): this
3351 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
3352 setMaxBounds: function (bounds) {
3353 bounds = toLatLngBounds(bounds);
3355 if (!bounds.isValid()) {
3356 this.options.maxBounds = null;
3357 return this.off('moveend', this._panInsideMaxBounds);
3358 } else if (this.options.maxBounds) {
3359 this.off('moveend', this._panInsideMaxBounds);
3362 this.options.maxBounds = bounds;
3365 this._panInsideMaxBounds();
3368 return this.on('moveend', this._panInsideMaxBounds);
3371 // @method setMinZoom(zoom: Number): this
3372 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
3373 setMinZoom: function (zoom) {
3374 this.options.minZoom = zoom;
3376 if (this._loaded && this.getZoom() < this.options.minZoom) {
3377 return this.setZoom(zoom);
3383 // @method setMaxZoom(zoom: Number): this
3384 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
3385 setMaxZoom: function (zoom) {
3386 this.options.maxZoom = zoom;
3388 if (this._loaded && (this.getZoom() > this.options.maxZoom)) {
3389 return this.setZoom(zoom);
3395 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
3396 // 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.
3397 panInsideBounds: function (bounds, options) {
3398 this._enforcingBounds = true;
3399 var center = this.getCenter(),
3400 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
3402 if (!center.equals(newCenter)) {
3403 this.panTo(newCenter, options);
3406 this._enforcingBounds = false;
3410 // @method invalidateSize(options: Zoom/Pan options): this
3411 // Checks if the map container size changed and updates the map if so —
3412 // call it after you've changed the map size dynamically, also animating
3413 // pan by default. If `options.pan` is `false`, panning will not occur.
3414 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
3415 // that it doesn't happen often even if the method is called many
3419 // @method invalidateSize(animate: Boolean): this
3420 // Checks if the map container size changed and updates the map if so —
3421 // call it after you've changed the map size dynamically, also animating
3423 invalidateSize: function (options) {
3424 if (!this._loaded) { return this; }
3429 }, options === true ? {animate: true} : options);
3431 var oldSize = this.getSize();
3432 this._sizeChanged = true;
3433 this._lastCenter = null;
3435 var newSize = this.getSize(),
3436 oldCenter = oldSize.divideBy(2).round(),
3437 newCenter = newSize.divideBy(2).round(),
3438 offset = oldCenter.subtract(newCenter);
3440 if (!offset.x && !offset.y) { return this; }
3442 if (options.animate && options.pan) {
3447 this._rawPanBy(offset);
3452 if (options.debounceMoveend) {
3453 clearTimeout(this._sizeTimer);
3454 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
3456 this.fire('moveend');
3460 // @section Map state change events
3461 // @event resize: ResizeEvent
3462 // Fired when the map is resized.
3463 return this.fire('resize', {
3469 // @section Methods for modifying map state
3470 // @method stop(): this
3471 // Stops the currently running `panTo` or `flyTo` animation, if any.
3473 this.setZoom(this._limitZoom(this._zoom));
3474 if (!this.options.zoomSnap) {
3475 this.fire('viewreset');
3477 return this._stop();
3480 // @section Geolocation methods
3481 // @method locate(options?: Locate options): this
3482 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
3483 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
3484 // and optionally sets the map view to the user's location with respect to
3485 // detection accuracy (or to the world view if geolocation failed).
3486 // Note that, if your page doesn't use HTTPS, this method will fail in
3487 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
3488 // See `Locate options` for more details.
3489 locate: function (options) {
3491 options = this._locateOptions = extend({
3495 // maxZoom: <Number>
3497 // enableHighAccuracy: false
3500 if (!('geolocation' in navigator)) {
3501 this._handleGeolocationError({
3503 message: 'Geolocation not supported.'
3508 var onResponse = bind(this._handleGeolocationResponse, this),
3509 onError = bind(this._handleGeolocationError, this);
3511 if (options.watch) {
3512 this._locationWatchId =
3513 navigator.geolocation.watchPosition(onResponse, onError, options);
3515 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
3520 // @method stopLocate(): this
3521 // Stops watching location previously initiated by `map.locate({watch: true})`
3522 // and aborts resetting the map view if map.locate was called with
3523 // `{setView: true}`.
3524 stopLocate: function () {
3525 if (navigator.geolocation && navigator.geolocation.clearWatch) {
3526 navigator.geolocation.clearWatch(this._locationWatchId);
3528 if (this._locateOptions) {
3529 this._locateOptions.setView = false;
3534 _handleGeolocationError: function (error) {
3536 message = error.message ||
3537 (c === 1 ? 'permission denied' :
3538 (c === 2 ? 'position unavailable' : 'timeout'));
3540 if (this._locateOptions.setView && !this._loaded) {
3544 // @section Location events
3545 // @event locationerror: ErrorEvent
3546 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
3547 this.fire('locationerror', {
3549 message: 'Geolocation error: ' + message + '.'
3553 _handleGeolocationResponse: function (pos) {
3554 var lat = pos.coords.latitude,
3555 lng = pos.coords.longitude,
3556 latlng = new LatLng(lat, lng),
3557 bounds = latlng.toBounds(pos.coords.accuracy),
3558 options = this._locateOptions;
3560 if (options.setView) {
3561 var zoom = this.getBoundsZoom(bounds);
3562 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
3568 timestamp: pos.timestamp
3571 for (var i in pos.coords) {
3572 if (typeof pos.coords[i] === 'number') {
3573 data[i] = pos.coords[i];
3577 // @event locationfound: LocationEvent
3578 // Fired when geolocation (using the [`locate`](#map-locate) method)
3579 // went successfully.
3580 this.fire('locationfound', data);
3583 // TODO handler.addTo
3584 // TODO Appropiate docs section?
3585 // @section Other Methods
3586 // @method addHandler(name: String, HandlerClass: Function): this
3587 // Adds a new `Handler` to the map, given its name and constructor function.
3588 addHandler: function (name, HandlerClass) {
3589 if (!HandlerClass) { return this; }
3591 var handler = this[name] = new HandlerClass(this);
3593 this._handlers.push(handler);
3595 if (this.options[name]) {
3602 // @method remove(): this
3603 // Destroys the map and clears all related event listeners.
3604 remove: function () {
3606 this._initEvents(true);
3608 if (this._containerId !== this._container._leaflet_id) {
3609 throw new Error('Map container is being reused by another instance');
3613 // throws error in IE6-8
3614 delete this._container._leaflet_id;
3615 delete this._containerId;
3618 this._container._leaflet_id = undefined;
3620 this._containerId = undefined;
3623 remove(this._mapPane);
3625 if (this._clearControlPos) {
3626 this._clearControlPos();
3629 this._clearHandlers();
3632 // @section Map state change events
3633 // @event unload: Event
3634 // Fired when the map is destroyed with [remove](#map-remove) method.
3635 this.fire('unload');
3639 for (i in this._layers) {
3640 this._layers[i].remove();
3642 for (i in this._panes) {
3643 remove(this._panes[i]);
3648 delete this._mapPane;
3649 delete this._renderer;
3654 // @section Other Methods
3655 // @method createPane(name: String, container?: HTMLElement): HTMLElement
3656 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
3657 // then returns it. The pane is created as a child of `container`, or
3658 // as a child of the main map pane if not set.
3659 createPane: function (name, container) {
3660 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3661 pane = create$1('div', className, container || this._mapPane);
3664 this._panes[name] = pane;
3669 // @section Methods for Getting Map State
3671 // @method getCenter(): LatLng
3672 // Returns the geographical center of the map view
3673 getCenter: function () {
3674 this._checkIfLoaded();
3676 if (this._lastCenter && !this._moved()) {
3677 return this._lastCenter;
3679 return this.layerPointToLatLng(this._getCenterLayerPoint());
3682 // @method getZoom(): Number
3683 // Returns the current zoom level of the map view
3684 getZoom: function () {
3688 // @method getBounds(): LatLngBounds
3689 // Returns the geographical bounds visible in the current map view
3690 getBounds: function () {
3691 var bounds = this.getPixelBounds(),
3692 sw = this.unproject(bounds.getBottomLeft()),
3693 ne = this.unproject(bounds.getTopRight());
3695 return new LatLngBounds(sw, ne);
3698 // @method getMinZoom(): Number
3699 // 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.
3700 getMinZoom: function () {
3701 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3704 // @method getMaxZoom(): Number
3705 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3706 getMaxZoom: function () {
3707 return this.options.maxZoom === undefined ?
3708 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3709 this.options.maxZoom;
3712 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number
3713 // Returns the maximum zoom level on which the given bounds fit to the map
3714 // view in its entirety. If `inside` (optional) is set to `true`, the method
3715 // instead returns the minimum zoom level on which the map view fits into
3716 // the given bounds in its entirety.
3717 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3718 bounds = toLatLngBounds(bounds);
3719 padding = toPoint(padding || [0, 0]);
3721 var zoom = this.getZoom() || 0,
3722 min = this.getMinZoom(),
3723 max = this.getMaxZoom(),
3724 nw = bounds.getNorthWest(),
3725 se = bounds.getSouthEast(),
3726 size = this.getSize().subtract(padding),
3727 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3728 snap = any3d ? this.options.zoomSnap : 1,
3729 scalex = size.x / boundsSize.x,
3730 scaley = size.y / boundsSize.y,
3731 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
3733 zoom = this.getScaleZoom(scale, zoom);
3736 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3737 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3740 return Math.max(min, Math.min(max, zoom));
3743 // @method getSize(): Point
3744 // Returns the current size of the map container (in pixels).
3745 getSize: function () {
3746 if (!this._size || this._sizeChanged) {
3747 this._size = new Point(
3748 this._container.clientWidth || 0,
3749 this._container.clientHeight || 0);
3751 this._sizeChanged = false;
3753 return this._size.clone();
3756 // @method getPixelBounds(): Bounds
3757 // Returns the bounds of the current map view in projected pixel
3758 // coordinates (sometimes useful in layer and overlay implementations).
3759 getPixelBounds: function (center, zoom) {
3760 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3761 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3764 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3765 // the map pane? "left point of the map layer" can be confusing, specially
3766 // since there can be negative offsets.
3767 // @method getPixelOrigin(): Point
3768 // Returns the projected pixel coordinates of the top left point of
3769 // the map layer (useful in custom layer and overlay implementations).
3770 getPixelOrigin: function () {
3771 this._checkIfLoaded();
3772 return this._pixelOrigin;
3775 // @method getPixelWorldBounds(zoom?: Number): Bounds
3776 // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3777 // If `zoom` is omitted, the map's current zoom level is used.
3778 getPixelWorldBounds: function (zoom) {
3779 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3782 // @section Other Methods
3784 // @method getPane(pane: String|HTMLElement): HTMLElement
3785 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3786 getPane: function (pane) {
3787 return typeof pane === 'string' ? this._panes[pane] : pane;
3790 // @method getPanes(): Object
3791 // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3792 // the panes as values.
3793 getPanes: function () {
3797 // @method getContainer: HTMLElement
3798 // Returns the HTML element that contains the map.
3799 getContainer: function () {
3800 return this._container;
3804 // @section Conversion Methods
3806 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3807 // Returns the scale factor to be applied to a map transition from zoom level
3808 // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3809 getZoomScale: function (toZoom, fromZoom) {
3810 // TODO replace with universal implementation after refactoring projections
3811 var crs = this.options.crs;
3812 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3813 return crs.scale(toZoom) / crs.scale(fromZoom);
3816 // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3817 // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3818 // level and everything is scaled by a factor of `scale`. Inverse of
3819 // [`getZoomScale`](#map-getZoomScale).
3820 getScaleZoom: function (scale, fromZoom) {
3821 var crs = this.options.crs;
3822 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3823 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3824 return isNaN(zoom) ? Infinity : zoom;
3827 // @method project(latlng: LatLng, zoom: Number): Point
3828 // Projects a geographical coordinate `LatLng` according to the projection
3829 // of the map's CRS, then scales it according to `zoom` and the CRS's
3830 // `Transformation`. The result is pixel coordinate relative to
3832 project: function (latlng, zoom) {
3833 zoom = zoom === undefined ? this._zoom : zoom;
3834 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
3837 // @method unproject(point: Point, zoom: Number): LatLng
3838 // Inverse of [`project`](#map-project).
3839 unproject: function (point, zoom) {
3840 zoom = zoom === undefined ? this._zoom : zoom;
3841 return this.options.crs.pointToLatLng(toPoint(point), zoom);
3844 // @method layerPointToLatLng(point: Point): LatLng
3845 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3846 // returns the corresponding geographical coordinate (for the current zoom level).
3847 layerPointToLatLng: function (point) {
3848 var projectedPoint = toPoint(point).add(this.getPixelOrigin());
3849 return this.unproject(projectedPoint);
3852 // @method latLngToLayerPoint(latlng: LatLng): Point
3853 // Given a geographical coordinate, returns the corresponding pixel coordinate
3854 // relative to the [origin pixel](#map-getpixelorigin).
3855 latLngToLayerPoint: function (latlng) {
3856 var projectedPoint = this.project(toLatLng(latlng))._round();
3857 return projectedPoint._subtract(this.getPixelOrigin());
3860 // @method wrapLatLng(latlng: LatLng): LatLng
3861 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
3862 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
3864 // By default this means longitude is wrapped around the dateline so its
3865 // value is between -180 and +180 degrees.
3866 wrapLatLng: function (latlng) {
3867 return this.options.crs.wrapLatLng(toLatLng(latlng));
3870 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
3871 // Returns a `LatLngBounds` with the same size as the given one, ensuring that
3872 // its center is within the CRS's bounds.
3873 // By default this means the center longitude is wrapped around the dateline so its
3874 // value is between -180 and +180 degrees, and the majority of the bounds
3875 // overlaps the CRS's bounds.
3876 wrapLatLngBounds: function (latlng) {
3877 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
3880 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
3881 // Returns the distance between two geographical coordinates according to
3882 // the map's CRS. By default this measures distance in meters.
3883 distance: function (latlng1, latlng2) {
3884 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
3887 // @method containerPointToLayerPoint(point: Point): Point
3888 // Given a pixel coordinate relative to the map container, returns the corresponding
3889 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
3890 containerPointToLayerPoint: function (point) { // (Point)
3891 return toPoint(point).subtract(this._getMapPanePos());
3894 // @method layerPointToContainerPoint(point: Point): Point
3895 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3896 // returns the corresponding pixel coordinate relative to the map container.
3897 layerPointToContainerPoint: function (point) { // (Point)
3898 return toPoint(point).add(this._getMapPanePos());
3901 // @method containerPointToLatLng(point: Point): LatLng
3902 // Given a pixel coordinate relative to the map container, returns
3903 // the corresponding geographical coordinate (for the current zoom level).
3904 containerPointToLatLng: function (point) {
3905 var layerPoint = this.containerPointToLayerPoint(toPoint(point));
3906 return this.layerPointToLatLng(layerPoint);
3909 // @method latLngToContainerPoint(latlng: LatLng): Point
3910 // Given a geographical coordinate, returns the corresponding pixel coordinate
3911 // relative to the map container.
3912 latLngToContainerPoint: function (latlng) {
3913 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
3916 // @method mouseEventToContainerPoint(ev: MouseEvent): Point
3917 // Given a MouseEvent object, returns the pixel coordinate relative to the
3918 // map container where the event took place.
3919 mouseEventToContainerPoint: function (e) {
3920 return getMousePosition(e, this._container);
3923 // @method mouseEventToLayerPoint(ev: MouseEvent): Point
3924 // Given a MouseEvent object, returns the pixel coordinate relative to
3925 // the [origin pixel](#map-getpixelorigin) where the event took place.
3926 mouseEventToLayerPoint: function (e) {
3927 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
3930 // @method mouseEventToLatLng(ev: MouseEvent): LatLng
3931 // Given a MouseEvent object, returns geographical coordinate where the
3932 // event took place.
3933 mouseEventToLatLng: function (e) { // (MouseEvent)
3934 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
3938 // map initialization methods
3940 _initContainer: function (id) {
3941 var container = this._container = get(id);
3944 throw new Error('Map container not found.');
3945 } else if (container._leaflet_id) {
3946 throw new Error('Map container is already initialized.');
3949 on(container, 'scroll', this._onScroll, this);
3950 this._containerId = stamp(container);
3953 _initLayout: function () {
3954 var container = this._container;
3956 this._fadeAnimated = this.options.fadeAnimation && any3d;
3958 addClass(container, 'leaflet-container' +
3959 (touch ? ' leaflet-touch' : '') +
3960 (retina ? ' leaflet-retina' : '') +
3961 (ielt9 ? ' leaflet-oldie' : '') +
3962 (safari ? ' leaflet-safari' : '') +
3963 (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
3965 var position = getStyle(container, 'position');
3967 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
3968 container.style.position = 'relative';
3973 if (this._initControlPos) {
3974 this._initControlPos();
3978 _initPanes: function () {
3979 var panes = this._panes = {};
3980 this._paneRenderers = {};
3984 // Panes are DOM elements used to control the ordering of layers on the map. You
3985 // can access panes with [`map.getPane`](#map-getpane) or
3986 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
3987 // [`map.createPane`](#map-createpane) method.
3989 // Every map has the following default panes that differ only in zIndex.
3991 // @pane mapPane: HTMLElement = 'auto'
3992 // Pane that contains all other map panes
3994 this._mapPane = this.createPane('mapPane', this._container);
3995 setPosition(this._mapPane, new Point(0, 0));
3997 // @pane tilePane: HTMLElement = 200
3998 // Pane for `GridLayer`s and `TileLayer`s
3999 this.createPane('tilePane');
4000 // @pane overlayPane: HTMLElement = 400
4001 // Pane for vector overlays (`Path`s), like `Polyline`s and `Polygon`s
4002 this.createPane('shadowPane');
4003 // @pane shadowPane: HTMLElement = 500
4004 // Pane for overlay shadows (e.g. `Marker` shadows)
4005 this.createPane('overlayPane');
4006 // @pane markerPane: HTMLElement = 600
4007 // Pane for `Icon`s of `Marker`s
4008 this.createPane('markerPane');
4009 // @pane tooltipPane: HTMLElement = 650
4010 // Pane for tooltip.
4011 this.createPane('tooltipPane');
4012 // @pane popupPane: HTMLElement = 700
4013 // Pane for `Popup`s.
4014 this.createPane('popupPane');
4016 if (!this.options.markerZoomAnimation) {
4017 addClass(panes.markerPane, 'leaflet-zoom-hide');
4018 addClass(panes.shadowPane, 'leaflet-zoom-hide');
4023 // private methods that modify map state
4025 // @section Map state change events
4026 _resetView: function (center, zoom) {
4027 setPosition(this._mapPane, new Point(0, 0));
4029 var loading = !this._loaded;
4030 this._loaded = true;
4031 zoom = this._limitZoom(zoom);
4033 this.fire('viewprereset');
4035 var zoomChanged = this._zoom !== zoom;
4037 ._moveStart(zoomChanged)
4038 ._move(center, zoom)
4039 ._moveEnd(zoomChanged);
4041 // @event viewreset: Event
4042 // Fired when the map needs to redraw its content (this usually happens
4043 // on map zoom or load). Very useful for creating custom overlays.
4044 this.fire('viewreset');
4046 // @event load: Event
4047 // Fired when the map is initialized (when its center and zoom are set
4048 // for the first time).
4054 _moveStart: function (zoomChanged) {
4055 // @event zoomstart: Event
4056 // Fired when the map zoom is about to change (e.g. before zoom animation).
4057 // @event movestart: Event
4058 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
4060 this.fire('zoomstart');
4062 return this.fire('movestart');
4065 _move: function (center, zoom, data) {
4066 if (zoom === undefined) {
4069 var zoomChanged = this._zoom !== zoom;
4072 this._lastCenter = center;
4073 this._pixelOrigin = this._getNewPixelOrigin(center);
4075 // @event zoom: Event
4076 // Fired repeatedly during any change in zoom level, including zoom
4077 // and fly animations.
4078 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530
4079 this.fire('zoom', data);
4082 // @event move: Event
4083 // Fired repeatedly during any movement of the map, including pan and
4085 return this.fire('move', data);
4088 _moveEnd: function (zoomChanged) {
4089 // @event zoomend: Event
4090 // Fired when the map has changed, after any animations.
4092 this.fire('zoomend');
4095 // @event moveend: Event
4096 // Fired when the center of the map stops changing (e.g. user stopped
4097 // dragging the map).
4098 return this.fire('moveend');
4101 _stop: function () {
4102 cancelAnimFrame(this._flyToFrame);
4103 if (this._panAnim) {
4104 this._panAnim.stop();
4109 _rawPanBy: function (offset) {
4110 setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
4113 _getZoomSpan: function () {
4114 return this.getMaxZoom() - this.getMinZoom();
4117 _panInsideMaxBounds: function () {
4118 if (!this._enforcingBounds) {
4119 this.panInsideBounds(this.options.maxBounds);
4123 _checkIfLoaded: function () {
4124 if (!this._loaded) {
4125 throw new Error('Set map center and zoom first.');
4129 // DOM event handling
4131 // @section Interaction events
4132 _initEvents: function (remove$$1) {
4134 this._targets[stamp(this._container)] = this;
4136 var onOff = remove$$1 ? off : on;
4138 // @event click: MouseEvent
4139 // Fired when the user clicks (or taps) the map.
4140 // @event dblclick: MouseEvent
4141 // Fired when the user double-clicks (or double-taps) the map.
4142 // @event mousedown: MouseEvent
4143 // Fired when the user pushes the mouse button on the map.
4144 // @event mouseup: MouseEvent
4145 // Fired when the user releases the mouse button on the map.
4146 // @event mouseover: MouseEvent
4147 // Fired when the mouse enters the map.
4148 // @event mouseout: MouseEvent
4149 // Fired when the mouse leaves the map.
4150 // @event mousemove: MouseEvent
4151 // Fired while the mouse moves over the map.
4152 // @event contextmenu: MouseEvent
4153 // Fired when the user pushes the right mouse button on the map, prevents
4154 // default browser context menu from showing if there are listeners on
4155 // this event. Also fired on mobile when the user holds a single touch
4156 // for a second (also called long press).
4157 // @event keypress: KeyboardEvent
4158 // Fired when the user presses a key from the keyboard while the map is focused.
4159 onOff(this._container, 'click dblclick mousedown mouseup ' +
4160 'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);
4162 if (this.options.trackResize) {
4163 onOff(window, 'resize', this._onResize, this);
4166 if (any3d && this.options.transform3DLimit) {
4167 (remove$$1 ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
4171 _onResize: function () {
4172 cancelAnimFrame(this._resizeRequest);
4173 this._resizeRequest = requestAnimFrame(
4174 function () { this.invalidateSize({debounceMoveend: true}); }, this);
4177 _onScroll: function () {
4178 this._container.scrollTop = 0;
4179 this._container.scrollLeft = 0;
4182 _onMoveEnd: function () {
4183 var pos = this._getMapPanePos();
4184 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
4185 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
4186 // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
4187 this._resetView(this.getCenter(), this.getZoom());
4191 _findEventTargets: function (e, type) {
4194 isHover = type === 'mouseout' || type === 'mouseover',
4195 src = e.target || e.srcElement,
4199 target = this._targets[stamp(src)];
4200 if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
4201 // Prevent firing click after you just dragged an object.
4205 if (target && target.listens(type, true)) {
4206 if (isHover && !isExternalTarget(src, e)) { break; }
4207 targets.push(target);
4208 if (isHover) { break; }
4210 if (src === this._container) { break; }
4211 src = src.parentNode;
4213 if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) {
4219 _handleDOMEvent: function (e) {
4220 if (!this._loaded || skipped(e)) { return; }
4224 if (type === 'mousedown' || type === 'keypress') {
4225 // prevents outline when clicking on keyboard-focusable element
4226 preventOutline(e.target || e.srcElement);
4229 this._fireDOMEvent(e, type);
4232 _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
4234 _fireDOMEvent: function (e, type, targets) {
4236 if (e.type === 'click') {
4237 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
4238 // @event preclick: MouseEvent
4239 // Fired before mouse click on the map (sometimes useful when you
4240 // want something to happen on click before any existing click
4241 // handlers start running).
4242 var synth = extend({}, e);
4243 synth.type = 'preclick';
4244 this._fireDOMEvent(synth, synth.type, targets);
4247 if (e._stopped) { return; }
4249 // Find the layer the event is propagating from and its parents.
4250 targets = (targets || []).concat(this._findEventTargets(e, type));
4252 if (!targets.length) { return; }
4254 var target = targets[0];
4255 if (type === 'contextmenu' && target.listens(type, true)) {
4263 if (e.type !== 'keypress') {
4264 var isMarker = (target.options && 'icon' in target.options);
4265 data.containerPoint = isMarker ?
4266 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
4267 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
4268 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
4271 for (var i = 0; i < targets.length; i++) {
4272 targets[i].fire(type, data, true);
4273 if (data.originalEvent._stopped ||
4274 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
4278 _draggableMoved: function (obj) {
4279 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
4280 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
4283 _clearHandlers: function () {
4284 for (var i = 0, len = this._handlers.length; i < len; i++) {
4285 this._handlers[i].disable();
4289 // @section Other Methods
4291 // @method whenReady(fn: Function, context?: Object): this
4292 // Runs the given function `fn` when the map gets initialized with
4293 // a view (center and zoom) and at least one layer, or immediately
4294 // if it's already initialized, optionally passing a function context.
4295 whenReady: function (callback, context) {
4297 callback.call(context || this, {target: this});
4299 this.on('load', callback, context);
4305 // private methods for getting map state
4307 _getMapPanePos: function () {
4308 return getPosition(this._mapPane) || new Point(0, 0);
4311 _moved: function () {
4312 var pos = this._getMapPanePos();
4313 return pos && !pos.equals([0, 0]);
4316 _getTopLeftPoint: function (center, zoom) {
4317 var pixelOrigin = center && zoom !== undefined ?
4318 this._getNewPixelOrigin(center, zoom) :
4319 this.getPixelOrigin();
4320 return pixelOrigin.subtract(this._getMapPanePos());
4323 _getNewPixelOrigin: function (center, zoom) {
4324 var viewHalf = this.getSize()._divideBy(2);
4325 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
4328 _latLngToNewLayerPoint: function (latlng, zoom, center) {
4329 var topLeft = this._getNewPixelOrigin(center, zoom);
4330 return this.project(latlng, zoom)._subtract(topLeft);
4333 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
4334 var topLeft = this._getNewPixelOrigin(center, zoom);
4336 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
4337 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
4338 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
4339 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
4343 // layer point of the current center
4344 _getCenterLayerPoint: function () {
4345 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
4348 // offset of the specified place to the current center in pixels
4349 _getCenterOffset: function (latlng) {
4350 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
4353 // adjust center for view to get inside bounds
4354 _limitCenter: function (center, zoom, bounds) {
4356 if (!bounds) { return center; }
4358 var centerPoint = this.project(center, zoom),
4359 viewHalf = this.getSize().divideBy(2),
4360 viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
4361 offset = this._getBoundsOffset(viewBounds, bounds, zoom);
4363 // If offset is less than a pixel, ignore.
4364 // This prevents unstable projections from getting into
4365 // an infinite loop of tiny offsets.
4366 if (offset.round().equals([0, 0])) {
4370 return this.unproject(centerPoint.add(offset), zoom);
4373 // adjust offset for view to get inside bounds
4374 _limitOffset: function (offset, bounds) {
4375 if (!bounds) { return offset; }
4377 var viewBounds = this.getPixelBounds(),
4378 newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
4380 return offset.add(this._getBoundsOffset(newBounds, bounds));
4383 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
4384 _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
4385 var projectedMaxBounds = toBounds(
4386 this.project(maxBounds.getNorthEast(), zoom),
4387 this.project(maxBounds.getSouthWest(), zoom)
4389 minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
4390 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
4392 dx = this._rebound(minOffset.x, -maxOffset.x),
4393 dy = this._rebound(minOffset.y, -maxOffset.y);
4395 return new Point(dx, dy);
4398 _rebound: function (left, right) {
4399 return left + right > 0 ?
4400 Math.round(left - right) / 2 :
4401 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
4404 _limitZoom: function (zoom) {
4405 var min = this.getMinZoom(),
4406 max = this.getMaxZoom(),
4407 snap = any3d ? this.options.zoomSnap : 1;
4409 zoom = Math.round(zoom / snap) * snap;
4411 return Math.max(min, Math.min(max, zoom));
4414 _onPanTransitionStep: function () {
4418 _onPanTransitionEnd: function () {
4419 removeClass(this._mapPane, 'leaflet-pan-anim');
4420 this.fire('moveend');
4423 _tryAnimatedPan: function (center, options) {
4424 // difference between the new and current centers in pixels
4425 var offset = this._getCenterOffset(center)._floor();
4427 // don't animate too far unless animate: true specified in options
4428 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
4430 this.panBy(offset, options);
4435 _createAnimProxy: function () {
4437 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
4438 this._panes.mapPane.appendChild(proxy);
4440 this.on('zoomanim', function (e) {
4441 var prop = TRANSFORM,
4442 transform = this._proxy.style[prop];
4444 setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
4446 // workaround for case when transform is the same and so transitionend event is not fired
4447 if (transform === this._proxy.style[prop] && this._animatingZoom) {
4448 this._onZoomTransitionEnd();
4452 this.on('load moveend', function () {
4453 var c = this.getCenter(),
4455 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
4458 this._on('unload', this._destroyAnimProxy, this);
4461 _destroyAnimProxy: function () {
4462 remove(this._proxy);
4466 _catchTransitionEnd: function (e) {
4467 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
4468 this._onZoomTransitionEnd();
4472 _nothingToAnimate: function () {
4473 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
4476 _tryAnimatedZoom: function (center, zoom, options) {
4478 if (this._animatingZoom) { return true; }
4480 options = options || {};
4482 // don't animate if disabled, not supported or zoom difference is too large
4483 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
4484 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
4486 // offset is the pixel coords of the zoom origin relative to the current center
4487 var scale = this.getZoomScale(zoom),
4488 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
4490 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
4491 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
4493 requestAnimFrame(function () {
4496 ._animateZoom(center, zoom, true);
4502 _animateZoom: function (center, zoom, startAnim, noUpdate) {
4504 this._animatingZoom = true;
4506 // remember what center/zoom to set after animation
4507 this._animateToCenter = center;
4508 this._animateToZoom = zoom;
4510 addClass(this._mapPane, 'leaflet-zoom-anim');
4513 // @event zoomanim: ZoomAnimEvent
4514 // Fired on every frame of a zoom animation
4515 this.fire('zoomanim', {
4521 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
4522 setTimeout(bind(this._onZoomTransitionEnd, this), 250);
4525 _onZoomTransitionEnd: function () {
4526 if (!this._animatingZoom) { return; }
4528 removeClass(this._mapPane, 'leaflet-zoom-anim');
4530 this._animatingZoom = false;
4532 this._move(this._animateToCenter, this._animateToZoom);
4534 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
4535 requestAnimFrame(function () {
4536 this._moveEnd(true);
4543 // @factory L.map(id: String, options?: Map options)
4544 // Instantiates a map object given the DOM ID of a `<div>` element
4545 // and optionally an object literal with `Map options`.
4548 // @factory L.map(el: HTMLElement, options?: Map options)
4549 // Instantiates a map object given an instance of a `<div>` HTML element
4550 // and optionally an object literal with `Map options`.
4551 function createMap(id, options) {
4552 return new Map(id, options);
4560 * L.Control is a base class for implementing map controls. Handles positioning.
4561 * All other controls extend from this class.
4564 var Control = Class.extend({
4566 // @aka Control options
4568 // @option position: String = 'topright'
4569 // The position of the control (one of the map corners). Possible values are `'topleft'`,
4570 // `'topright'`, `'bottomleft'` or `'bottomright'`
4571 position: 'topright'
4574 initialize: function (options) {
4575 setOptions(this, options);
4579 * Classes extending L.Control will inherit the following methods:
4581 * @method getPosition: string
4582 * Returns the position of the control.
4584 getPosition: function () {
4585 return this.options.position;
4588 // @method setPosition(position: string): this
4589 // Sets the position of the control.
4590 setPosition: function (position) {
4591 var map = this._map;
4594 map.removeControl(this);
4597 this.options.position = position;
4600 map.addControl(this);
4606 // @method getContainer: HTMLElement
4607 // Returns the HTMLElement that contains the control.
4608 getContainer: function () {
4609 return this._container;
4612 // @method addTo(map: Map): this
4613 // Adds the control to the given map.
4614 addTo: function (map) {
4618 var container = this._container = this.onAdd(map),
4619 pos = this.getPosition(),
4620 corner = map._controlCorners[pos];
4622 addClass(container, 'leaflet-control');
4624 if (pos.indexOf('bottom') !== -1) {
4625 corner.insertBefore(container, corner.firstChild);
4627 corner.appendChild(container);
4633 // @method remove: this
4634 // Removes the control from the map it is currently active on.
4635 remove: function () {
4640 remove(this._container);
4642 if (this.onRemove) {
4643 this.onRemove(this._map);
4651 _refocusOnMap: function (e) {
4652 // if map exists and event is not a keyboard event
4653 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
4654 this._map.getContainer().focus();
4659 var control = function (options) {
4660 return new Control(options);
4663 /* @section Extension methods
4666 * Every control should extend from `L.Control` and (re-)implement the following methods.
4668 * @method onAdd(map: Map): HTMLElement
4669 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
4671 * @method onRemove(map: Map)
4672 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
4676 * @section Methods for Layers and Controls
4679 // @method addControl(control: Control): this
4680 // Adds the given control to the map
4681 addControl: function (control) {
4682 control.addTo(this);
4686 // @method removeControl(control: Control): this
4687 // Removes the given control from the map
4688 removeControl: function (control) {
4693 _initControlPos: function () {
4694 var corners = this._controlCorners = {},
4696 container = this._controlContainer =
4697 create$1('div', l + 'control-container', this._container);
4699 function createCorner(vSide, hSide) {
4700 var className = l + vSide + ' ' + l + hSide;
4702 corners[vSide + hSide] = create$1('div', className, container);
4705 createCorner('top', 'left');
4706 createCorner('top', 'right');
4707 createCorner('bottom', 'left');
4708 createCorner('bottom', 'right');
4711 _clearControlPos: function () {
4712 for (var i in this._controlCorners) {
4713 remove(this._controlCorners[i]);
4715 remove(this._controlContainer);
4716 delete this._controlCorners;
4717 delete this._controlContainer;
4722 * @class Control.Layers
4723 * @aka L.Control.Layers
4726 * 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/)). Extends `Control`.
4731 * var baseLayers = {
4733 * "OpenStreetMap": osm
4738 * "Roads": roadsLayer
4741 * L.control.layers(baseLayers, overlays).addTo(map);
4744 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
4748 * "<someName1>": layer1,
4749 * "<someName2>": layer2
4753 * The layer names can contain HTML, which allows you to add additional styling to the items:
4756 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
4760 var Layers = Control.extend({
4762 // @aka Control.Layers options
4764 // @option collapsed: Boolean = true
4765 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
4767 position: 'topright',
4769 // @option autoZIndex: Boolean = true
4770 // 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.
4773 // @option hideSingleBase: Boolean = false
4774 // If `true`, the base layers in the control will be hidden when there is only one.
4775 hideSingleBase: false,
4777 // @option sortLayers: Boolean = false
4778 // Whether to sort the layers. When `false`, layers will keep the order
4779 // in which they were added to the control.
4782 // @option sortFunction: Function = *
4783 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
4784 // that will be used for sorting the layers, when `sortLayers` is `true`.
4785 // The function receives both the `L.Layer` instances and their names, as in
4786 // `sortFunction(layerA, layerB, nameA, nameB)`.
4787 // By default, it sorts layers alphabetically by their name.
4788 sortFunction: function (layerA, layerB, nameA, nameB) {
4789 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
4793 initialize: function (baseLayers, overlays, options) {
4794 setOptions(this, options);
4796 this._layerControlInputs = [];
4798 this._lastZIndex = 0;
4799 this._handlingClick = false;
4801 for (var i in baseLayers) {
4802 this._addLayer(baseLayers[i], i);
4805 for (i in overlays) {
4806 this._addLayer(overlays[i], i, true);
4810 onAdd: function (map) {
4815 map.on('zoomend', this._checkDisabledLayers, this);
4817 for (var i = 0; i < this._layers.length; i++) {
4818 this._layers[i].layer.on('add remove', this._onLayerChange, this);
4821 return this._container;
4824 addTo: function (map) {
4825 Control.prototype.addTo.call(this, map);
4826 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
4827 return this._expandIfNotCollapsed();
4830 onRemove: function () {
4831 this._map.off('zoomend', this._checkDisabledLayers, this);
4833 for (var i = 0; i < this._layers.length; i++) {
4834 this._layers[i].layer.off('add remove', this._onLayerChange, this);
4838 // @method addBaseLayer(layer: Layer, name: String): this
4839 // Adds a base layer (radio button entry) with the given name to the control.
4840 addBaseLayer: function (layer, name) {
4841 this._addLayer(layer, name);
4842 return (this._map) ? this._update() : this;
4845 // @method addOverlay(layer: Layer, name: String): this
4846 // Adds an overlay (checkbox entry) with the given name to the control.
4847 addOverlay: function (layer, name) {
4848 this._addLayer(layer, name, true);
4849 return (this._map) ? this._update() : this;
4852 // @method removeLayer(layer: Layer): this
4853 // Remove the given layer from the control.
4854 removeLayer: function (layer) {
4855 layer.off('add remove', this._onLayerChange, this);
4857 var obj = this._getLayer(stamp(layer));
4859 this._layers.splice(this._layers.indexOf(obj), 1);
4861 return (this._map) ? this._update() : this;
4864 // @method expand(): this
4865 // Expand the control container if collapsed.
4866 expand: function () {
4867 addClass(this._container, 'leaflet-control-layers-expanded');
4868 this._form.style.height = null;
4869 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
4870 if (acceptableHeight < this._form.clientHeight) {
4871 addClass(this._form, 'leaflet-control-layers-scrollbar');
4872 this._form.style.height = acceptableHeight + 'px';
4874 removeClass(this._form, 'leaflet-control-layers-scrollbar');
4876 this._checkDisabledLayers();
4880 // @method collapse(): this
4881 // Collapse the control container if expanded.
4882 collapse: function () {
4883 removeClass(this._container, 'leaflet-control-layers-expanded');
4887 _initLayout: function () {
4888 var className = 'leaflet-control-layers',
4889 container = this._container = create$1('div', className),
4890 collapsed = this.options.collapsed;
4892 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
4893 container.setAttribute('aria-haspopup', true);
4895 disableClickPropagation(container);
4896 disableScrollPropagation(container);
4898 var form = this._form = create$1('form', className + '-list');
4901 this._map.on('click', this.collapse, this);
4905 mouseenter: this.expand,
4906 mouseleave: this.collapse
4911 var link = this._layersLink = create$1('a', className + '-toggle', container);
4913 link.title = 'Layers';
4916 on(link, 'click', stop);
4917 on(link, 'click', this.expand, this);
4919 on(link, 'focus', this.expand, this);
4926 this._baseLayersList = create$1('div', className + '-base', form);
4927 this._separator = create$1('div', className + '-separator', form);
4928 this._overlaysList = create$1('div', className + '-overlays', form);
4930 container.appendChild(form);
4933 _getLayer: function (id) {
4934 for (var i = 0; i < this._layers.length; i++) {
4936 if (this._layers[i] && stamp(this._layers[i].layer) === id) {
4937 return this._layers[i];
4942 _addLayer: function (layer, name, overlay) {
4944 layer.on('add remove', this._onLayerChange, this);
4953 if (this.options.sortLayers) {
4954 this._layers.sort(bind(function (a, b) {
4955 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
4959 if (this.options.autoZIndex && layer.setZIndex) {
4961 layer.setZIndex(this._lastZIndex);
4964 this._expandIfNotCollapsed();
4967 _update: function () {
4968 if (!this._container) { return this; }
4970 empty(this._baseLayersList);
4971 empty(this._overlaysList);
4973 this._layerControlInputs = [];
4974 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
4976 for (i = 0; i < this._layers.length; i++) {
4977 obj = this._layers[i];
4979 overlaysPresent = overlaysPresent || obj.overlay;
4980 baseLayersPresent = baseLayersPresent || !obj.overlay;
4981 baseLayersCount += !obj.overlay ? 1 : 0;
4984 // Hide base layers section if there's only one layer.
4985 if (this.options.hideSingleBase) {
4986 baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
4987 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
4990 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
4995 _onLayerChange: function (e) {
4996 if (!this._handlingClick) {
5000 var obj = this._getLayer(stamp(e.target));
5003 // @section Layer events
5004 // @event baselayerchange: LayersControlEvent
5005 // Fired when the base layer is changed through the [layer control](#control-layers).
5006 // @event overlayadd: LayersControlEvent
5007 // Fired when an overlay is selected through the [layer control](#control-layers).
5008 // @event overlayremove: LayersControlEvent
5009 // Fired when an overlay is deselected through the [layer control](#control-layers).
5010 // @namespace Control.Layers
5011 var type = obj.overlay ?
5012 (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
5013 (e.type === 'add' ? 'baselayerchange' : null);
5016 this._map.fire(type, obj);
5020 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
5021 _createRadioElement: function (name, checked) {
5023 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
5024 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
5026 var radioFragment = document.createElement('div');
5027 radioFragment.innerHTML = radioHtml;
5029 return radioFragment.firstChild;
5032 _addItem: function (obj) {
5033 var label = document.createElement('label'),
5034 checked = this._map.hasLayer(obj.layer),
5038 input = document.createElement('input');
5039 input.type = 'checkbox';
5040 input.className = 'leaflet-control-layers-selector';
5041 input.defaultChecked = checked;
5043 input = this._createRadioElement('leaflet-base-layers', checked);
5046 this._layerControlInputs.push(input);
5047 input.layerId = stamp(obj.layer);
5049 on(input, 'click', this._onInputClick, this);
5051 var name = document.createElement('span');
5052 name.innerHTML = ' ' + obj.name;
5054 // Helps from preventing layer control flicker when checkboxes are disabled
5055 // https://github.com/Leaflet/Leaflet/issues/2771
5056 var holder = document.createElement('div');
5058 label.appendChild(holder);
5059 holder.appendChild(input);
5060 holder.appendChild(name);
5062 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
5063 container.appendChild(label);
5065 this._checkDisabledLayers();
5069 _onInputClick: function () {
5070 var inputs = this._layerControlInputs,
5072 var addedLayers = [],
5075 this._handlingClick = true;
5077 for (var i = inputs.length - 1; i >= 0; i--) {
5079 layer = this._getLayer(input.layerId).layer;
5081 if (input.checked) {
5082 addedLayers.push(layer);
5083 } else if (!input.checked) {
5084 removedLayers.push(layer);
5088 // Bugfix issue 2318: Should remove all old layers before readding new ones
5089 for (i = 0; i < removedLayers.length; i++) {
5090 if (this._map.hasLayer(removedLayers[i])) {
5091 this._map.removeLayer(removedLayers[i]);
5094 for (i = 0; i < addedLayers.length; i++) {
5095 if (!this._map.hasLayer(addedLayers[i])) {
5096 this._map.addLayer(addedLayers[i]);
5100 this._handlingClick = false;
5102 this._refocusOnMap();
5105 _checkDisabledLayers: function () {
5106 var inputs = this._layerControlInputs,
5109 zoom = this._map.getZoom();
5111 for (var i = inputs.length - 1; i >= 0; i--) {
5113 layer = this._getLayer(input.layerId).layer;
5114 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
5115 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
5120 _expandIfNotCollapsed: function () {
5121 if (this._map && !this.options.collapsed) {
5127 _expand: function () {
5128 // Backward compatibility, remove me in 1.1.
5129 return this.expand();
5132 _collapse: function () {
5133 // Backward compatibility, remove me in 1.1.
5134 return this.collapse();
5140 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
5141 // 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.
5142 var layers = function (baseLayers, overlays, options) {
5143 return new Layers(baseLayers, overlays, options);
5147 * @class Control.Zoom
5148 * @aka L.Control.Zoom
5151 * 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`.
5154 var Zoom = Control.extend({
5156 // @aka Control.Zoom options
5158 position: 'topleft',
5160 // @option zoomInText: String = '+'
5161 // The text set on the 'zoom in' button.
5164 // @option zoomInTitle: String = 'Zoom in'
5165 // The title set on the 'zoom in' button.
5166 zoomInTitle: 'Zoom in',
5168 // @option zoomOutText: String = '−'
5169 // The text set on the 'zoom out' button.
5170 zoomOutText: '−',
5172 // @option zoomOutTitle: String = 'Zoom out'
5173 // The title set on the 'zoom out' button.
5174 zoomOutTitle: 'Zoom out'
5177 onAdd: function (map) {
5178 var zoomName = 'leaflet-control-zoom',
5179 container = create$1('div', zoomName + ' leaflet-bar'),
5180 options = this.options;
5182 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle,
5183 zoomName + '-in', container, this._zoomIn);
5184 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
5185 zoomName + '-out', container, this._zoomOut);
5187 this._updateDisabled();
5188 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
5193 onRemove: function (map) {
5194 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
5197 disable: function () {
5198 this._disabled = true;
5199 this._updateDisabled();
5203 enable: function () {
5204 this._disabled = false;
5205 this._updateDisabled();
5209 _zoomIn: function (e) {
5210 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
5211 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5215 _zoomOut: function (e) {
5216 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
5217 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5221 _createButton: function (html, title, className, container, fn) {
5222 var link = create$1('a', className, container);
5223 link.innerHTML = html;
5228 * Will force screen readers like VoiceOver to read this as "Zoom in - button"
5230 link.setAttribute('role', 'button');
5231 link.setAttribute('aria-label', title);
5233 disableClickPropagation(link);
5234 on(link, 'click', stop);
5235 on(link, 'click', fn, this);
5236 on(link, 'click', this._refocusOnMap, this);
5241 _updateDisabled: function () {
5242 var map = this._map,
5243 className = 'leaflet-disabled';
5245 removeClass(this._zoomInButton, className);
5246 removeClass(this._zoomOutButton, className);
5248 if (this._disabled || map._zoom === map.getMinZoom()) {
5249 addClass(this._zoomOutButton, className);
5251 if (this._disabled || map._zoom === map.getMaxZoom()) {
5252 addClass(this._zoomInButton, className);
5258 // @section Control options
5259 // @option zoomControl: Boolean = true
5260 // Whether a [zoom control](#control-zoom) is added to the map by default.
5265 Map.addInitHook(function () {
5266 if (this.options.zoomControl) {
5267 this.zoomControl = new Zoom();
5268 this.addControl(this.zoomControl);
5272 // @namespace Control.Zoom
5273 // @factory L.control.zoom(options: Control.Zoom options)
5274 // Creates a zoom control
5275 var zoom = function (options) {
5276 return new Zoom(options);
5280 * @class Control.Scale
5281 * @aka L.Control.Scale
5284 * 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`.
5289 * L.control.scale().addTo(map);
5293 var Scale = Control.extend({
5295 // @aka Control.Scale options
5297 position: 'bottomleft',
5299 // @option maxWidth: Number = 100
5300 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
5303 // @option metric: Boolean = True
5304 // Whether to show the metric scale line (m/km).
5307 // @option imperial: Boolean = True
5308 // Whether to show the imperial scale line (mi/ft).
5311 // @option updateWhenIdle: Boolean = false
5312 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
5315 onAdd: function (map) {
5316 var className = 'leaflet-control-scale',
5317 container = create$1('div', className),
5318 options = this.options;
5320 this._addScales(options, className + '-line', container);
5322 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5323 map.whenReady(this._update, this);
5328 onRemove: function (map) {
5329 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5332 _addScales: function (options, className, container) {
5333 if (options.metric) {
5334 this._mScale = create$1('div', className, container);
5336 if (options.imperial) {
5337 this._iScale = create$1('div', className, container);
5341 _update: function () {
5342 var map = this._map,
5343 y = map.getSize().y / 2;
5345 var maxMeters = map.distance(
5346 map.containerPointToLatLng([0, y]),
5347 map.containerPointToLatLng([this.options.maxWidth, y]));
5349 this._updateScales(maxMeters);
5352 _updateScales: function (maxMeters) {
5353 if (this.options.metric && maxMeters) {
5354 this._updateMetric(maxMeters);
5356 if (this.options.imperial && maxMeters) {
5357 this._updateImperial(maxMeters);
5361 _updateMetric: function (maxMeters) {
5362 var meters = this._getRoundNum(maxMeters),
5363 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
5365 this._updateScale(this._mScale, label, meters / maxMeters);
5368 _updateImperial: function (maxMeters) {
5369 var maxFeet = maxMeters * 3.2808399,
5370 maxMiles, miles, feet;
5372 if (maxFeet > 5280) {
5373 maxMiles = maxFeet / 5280;
5374 miles = this._getRoundNum(maxMiles);
5375 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
5378 feet = this._getRoundNum(maxFeet);
5379 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
5383 _updateScale: function (scale, text, ratio) {
5384 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
5385 scale.innerHTML = text;
5388 _getRoundNum: function (num) {
5389 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5402 // @factory L.control.scale(options?: Control.Scale options)
5403 // Creates an scale control with the given options.
5404 var scale = function (options) {
5405 return new Scale(options);
5409 * @class Control.Attribution
5410 * @aka L.Control.Attribution
5413 * 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.
5416 var Attribution = Control.extend({
5418 // @aka Control.Attribution options
5420 position: 'bottomright',
5422 // @option prefix: String = 'Leaflet'
5423 // The HTML text shown before the attributions. Pass `false` to disable.
5424 prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
5427 initialize: function (options) {
5428 setOptions(this, options);
5430 this._attributions = {};
5433 onAdd: function (map) {
5434 map.attributionControl = this;
5435 this._container = create$1('div', 'leaflet-control-attribution');
5436 disableClickPropagation(this._container);
5438 // TODO ugly, refactor
5439 for (var i in map._layers) {
5440 if (map._layers[i].getAttribution) {
5441 this.addAttribution(map._layers[i].getAttribution());
5447 return this._container;
5450 // @method setPrefix(prefix: String): this
5451 // Sets the text before the attributions.
5452 setPrefix: function (prefix) {
5453 this.options.prefix = prefix;
5458 // @method addAttribution(text: String): this
5459 // Adds an attribution text (e.g. `'Vector data © Mapbox'`).
5460 addAttribution: function (text) {
5461 if (!text) { return this; }
5463 if (!this._attributions[text]) {
5464 this._attributions[text] = 0;
5466 this._attributions[text]++;
5473 // @method removeAttribution(text: String): this
5474 // Removes an attribution text.
5475 removeAttribution: function (text) {
5476 if (!text) { return this; }
5478 if (this._attributions[text]) {
5479 this._attributions[text]--;
5486 _update: function () {
5487 if (!this._map) { return; }
5491 for (var i in this._attributions) {
5492 if (this._attributions[i]) {
5497 var prefixAndAttribs = [];
5499 if (this.options.prefix) {
5500 prefixAndAttribs.push(this.options.prefix);
5502 if (attribs.length) {
5503 prefixAndAttribs.push(attribs.join(', '));
5506 this._container.innerHTML = prefixAndAttribs.join(' | ');
5511 // @section Control options
5512 // @option attributionControl: Boolean = true
5513 // Whether a [attribution control](#control-attribution) is added to the map by default.
5515 attributionControl: true
5518 Map.addInitHook(function () {
5519 if (this.options.attributionControl) {
5520 new Attribution().addTo(this);
5524 // @namespace Control.Attribution
5525 // @factory L.control.attribution(options: Control.Attribution options)
5526 // Creates an attribution control.
5527 var attribution = function (options) {
5528 return new Attribution(options);
5531 Control.Layers = Layers;
5532 Control.Zoom = Zoom;
5533 Control.Scale = Scale;
5534 Control.Attribution = Attribution;
5536 control.layers = layers;
5537 control.zoom = zoom;
5538 control.scale = scale;
5539 control.attribution = attribution;
5542 L.Handler is a base class for handler classes that are used internally to inject
5543 interaction features like dragging to classes like Map and Marker.
5548 // Abstract class for map interaction handlers
5550 var Handler = Class.extend({
5551 initialize: function (map) {
5555 // @method enable(): this
5556 // Enables the handler
5557 enable: function () {
5558 if (this._enabled) { return this; }
5560 this._enabled = true;
5565 // @method disable(): this
5566 // Disables the handler
5567 disable: function () {
5568 if (!this._enabled) { return this; }
5570 this._enabled = false;
5575 // @method enabled(): Boolean
5576 // Returns `true` if the handler is enabled
5577 enabled: function () {
5578 return !!this._enabled;
5581 // @section Extension methods
5582 // Classes inheriting from `Handler` must implement the two following methods:
5583 // @method addHooks()
5584 // Called when the handler is enabled, should add event hooks.
5585 // @method removeHooks()
5586 // Called when the handler is disabled, should remove the event hooks added previously.
5589 var Mixin = {Events: Events};
5596 * A class for making DOM elements draggable (including touch support).
5597 * Used internally for map and marker dragging. Only works for elements
5598 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
5602 * var draggable = new L.Draggable(elementToDrag);
5603 * draggable.enable();
5607 var START = touch ? 'touchstart mousedown' : 'mousedown';
5609 mousedown: 'mouseup',
5610 touchstart: 'touchend',
5611 pointerdown: 'touchend',
5612 MSPointerDown: 'touchend'
5615 mousedown: 'mousemove',
5616 touchstart: 'touchmove',
5617 pointerdown: 'touchmove',
5618 MSPointerDown: 'touchmove'
5622 var Draggable = Evented.extend({
5626 // @aka Draggable options
5627 // @option clickTolerance: Number = 3
5628 // The max number of pixels a user can shift the mouse pointer during a click
5629 // for it to be considered a valid click (as opposed to a mouse drag).
5633 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
5634 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
5635 initialize: function (element, dragStartTarget, preventOutline$$1, options) {
5636 setOptions(this, options);
5638 this._element = element;
5639 this._dragStartTarget = dragStartTarget || element;
5640 this._preventOutline = preventOutline$$1;
5644 // Enables the dragging ability
5645 enable: function () {
5646 if (this._enabled) { return; }
5648 on(this._dragStartTarget, START, this._onDown, this);
5650 this._enabled = true;
5653 // @method disable()
5654 // Disables the dragging ability
5655 disable: function () {
5656 if (!this._enabled) { return; }
5658 // If we're currently dragging this draggable,
5659 // disabling it counts as first ending the drag.
5660 if (Draggable._dragging === this) {
5664 off(this._dragStartTarget, START, this._onDown, this);
5666 this._enabled = false;
5667 this._moved = false;
5670 _onDown: function (e) {
5671 // Ignore simulated events, since we handle both touch and
5672 // mouse explicitly; otherwise we risk getting duplicates of
5673 // touch events, see #4315.
5674 // Also ignore the event if disabled; this happens in IE11
5675 // under some circumstances, see #3666.
5676 if (e._simulated || !this._enabled) { return; }
5678 this._moved = false;
5680 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
5682 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
5683 Draggable._dragging = this; // Prevent dragging multiple objects at once.
5685 if (this._preventOutline) {
5686 preventOutline(this._element);
5690 disableTextSelection();
5692 if (this._moving) { return; }
5694 // @event down: Event
5695 // Fired when a drag is about to start.
5698 var first = e.touches ? e.touches[0] : e;
5700 this._startPoint = new Point(first.clientX, first.clientY);
5702 on(document, MOVE[e.type], this._onMove, this);
5703 on(document, END[e.type], this._onUp, this);
5706 _onMove: function (e) {
5707 // Ignore simulated events, since we handle both touch and
5708 // mouse explicitly; otherwise we risk getting duplicates of
5709 // touch events, see #4315.
5710 // Also ignore the event if disabled; this happens in IE11
5711 // under some circumstances, see #3666.
5712 if (e._simulated || !this._enabled) { return; }
5714 if (e.touches && e.touches.length > 1) {
5719 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
5720 newPoint = new Point(first.clientX, first.clientY),
5721 offset = newPoint.subtract(this._startPoint);
5723 if (!offset.x && !offset.y) { return; }
5724 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
5729 // @event dragstart: Event
5730 // Fired when a drag starts
5731 this.fire('dragstart');
5734 this._startPos = getPosition(this._element).subtract(offset);
5736 addClass(document.body, 'leaflet-dragging');
5738 this._lastTarget = e.target || e.srcElement;
5739 // IE and Edge do not give the <use> element, so fetch it
5741 if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
5742 this._lastTarget = this._lastTarget.correspondingUseElement;
5744 addClass(this._lastTarget, 'leaflet-drag-target');
5747 this._newPos = this._startPos.add(offset);
5748 this._moving = true;
5750 cancelAnimFrame(this._animRequest);
5751 this._lastEvent = e;
5752 this._animRequest = requestAnimFrame(this._updatePosition, this, true);
5755 _updatePosition: function () {
5756 var e = {originalEvent: this._lastEvent};
5758 // @event predrag: Event
5759 // Fired continuously during dragging *before* each corresponding
5760 // update of the element's position.
5761 this.fire('predrag', e);
5762 setPosition(this._element, this._newPos);
5764 // @event drag: Event
5765 // Fired continuously during dragging.
5766 this.fire('drag', e);
5769 _onUp: function (e) {
5770 // Ignore simulated events, since we handle both touch and
5771 // mouse explicitly; otherwise we risk getting duplicates of
5772 // touch events, see #4315.
5773 // Also ignore the event if disabled; this happens in IE11
5774 // under some circumstances, see #3666.
5775 if (e._simulated || !this._enabled) { return; }
5779 finishDrag: function () {
5780 removeClass(document.body, 'leaflet-dragging');
5782 if (this._lastTarget) {
5783 removeClass(this._lastTarget, 'leaflet-drag-target');
5784 this._lastTarget = null;
5787 for (var i in MOVE) {
5788 off(document, MOVE[i], this._onMove, this);
5789 off(document, END[i], this._onUp, this);
5793 enableTextSelection();
5795 if (this._moved && this._moving) {
5796 // ensure drag is not fired after dragend
5797 cancelAnimFrame(this._animRequest);
5799 // @event dragend: DragEndEvent
5800 // Fired when the drag ends.
5801 this.fire('dragend', {
5802 distance: this._newPos.distanceTo(this._startPos)
5806 this._moving = false;
5807 Draggable._dragging = false;
5813 * @namespace LineUtil
5815 * Various utility functions for polyine points processing, used by Leaflet internally to make polylines lightning-fast.
5818 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
5819 // Improves rendering performance dramatically by lessening the number of points to draw.
5821 // @function simplify(points: Point[], tolerance: Number): Point[]
5822 // Dramatically reduces the number of points in a polyline while retaining
5823 // its shape and returns a new array of simplified points, using the
5824 // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
5825 // Used for a huge performance boost when processing/displaying Leaflet polylines for
5826 // each zoom level and also reducing visual noise. tolerance affects the amount of
5827 // simplification (lesser value means higher quality but slower and with more points).
5828 // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
5829 function simplify(points, tolerance) {
5830 if (!tolerance || !points.length) {
5831 return points.slice();
5834 var sqTolerance = tolerance * tolerance;
5836 // stage 1: vertex reduction
5837 points = _reducePoints(points, sqTolerance);
5839 // stage 2: Douglas-Peucker simplification
5840 points = _simplifyDP(points, sqTolerance);
5845 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
5846 // Returns the distance between point `p` and segment `p1` to `p2`.
5847 function pointToSegmentDistance(p, p1, p2) {
5848 return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
5851 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
5852 // Returns the closest point from a point `p` on a segment `p1` to `p2`.
5853 function closestPointOnSegment(p, p1, p2) {
5854 return _sqClosestPointOnSegment(p, p1, p2);
5857 // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
5858 function _simplifyDP(points, sqTolerance) {
5860 var len = points.length,
5861 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
5862 markers = new ArrayConstructor(len);
5864 markers[0] = markers[len - 1] = 1;
5866 _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
5871 for (i = 0; i < len; i++) {
5873 newPoints.push(points[i]);
5880 function _simplifyDPStep(points, markers, sqTolerance, first, last) {
5885 for (i = first + 1; i <= last - 1; i++) {
5886 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
5888 if (sqDist > maxSqDist) {
5894 if (maxSqDist > sqTolerance) {
5897 _simplifyDPStep(points, markers, sqTolerance, first, index);
5898 _simplifyDPStep(points, markers, sqTolerance, index, last);
5902 // reduce points that are too close to each other to a single point
5903 function _reducePoints(points, sqTolerance) {
5904 var reducedPoints = [points[0]];
5906 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
5907 if (_sqDist(points[i], points[prev]) > sqTolerance) {
5908 reducedPoints.push(points[i]);
5912 if (prev < len - 1) {
5913 reducedPoints.push(points[len - 1]);
5915 return reducedPoints;
5920 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
5921 // Clips the segment a to b by rectangular bounds with the
5922 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
5923 // (modifying the segment points directly!). Used by Leaflet to only show polyline
5924 // points that are on the screen or near, increasing performance.
5925 function clipSegment(a, b, bounds, useLastCode, round) {
5926 var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
5927 codeB = _getBitCode(b, bounds),
5929 codeOut, p, newCode;
5931 // save 2nd code to avoid calculating it on the next segment
5935 // if a,b is inside the clip window (trivial accept)
5936 if (!(codeA | codeB)) {
5940 // if a,b is outside the clip window (trivial reject)
5941 if (codeA & codeB) {
5946 codeOut = codeA || codeB;
5947 p = _getEdgeIntersection(a, b, codeOut, bounds, round);
5948 newCode = _getBitCode(p, bounds);
5950 if (codeOut === codeA) {
5960 function _getEdgeIntersection(a, b, code, bounds, round) {
5967 if (code & 8) { // top
5968 x = a.x + dx * (max.y - a.y) / dy;
5971 } else if (code & 4) { // bottom
5972 x = a.x + dx * (min.y - a.y) / dy;
5975 } else if (code & 2) { // right
5977 y = a.y + dy * (max.x - a.x) / dx;
5979 } else if (code & 1) { // left
5981 y = a.y + dy * (min.x - a.x) / dx;
5984 return new Point(x, y, round);
5987 function _getBitCode(p, bounds) {
5990 if (p.x < bounds.min.x) { // left
5992 } else if (p.x > bounds.max.x) { // right
5996 if (p.y < bounds.min.y) { // bottom
5998 } else if (p.y > bounds.max.y) { // top
6005 // square distance (to avoid unnecessary Math.sqrt calls)
6006 function _sqDist(p1, p2) {
6007 var dx = p2.x - p1.x,
6009 return dx * dx + dy * dy;
6012 // return closest point on segment or distance to that point
6013 function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
6018 dot = dx * dx + dy * dy,
6022 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
6036 return sqDist ? dx * dx + dy * dy : new Point(x, y);
6040 // @function isFlat(latlngs: LatLng[]): Boolean
6041 // Returns true if `latlngs` is a flat array, false is nested.
6042 function isFlat(latlngs) {
6043 return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
6046 function _flat(latlngs) {
6047 console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
6048 return isFlat(latlngs);
6052 var LineUtil = (Object.freeze || Object)({
6054 pointToSegmentDistance: pointToSegmentDistance,
6055 closestPointOnSegment: closestPointOnSegment,
6056 clipSegment: clipSegment,
6057 _getEdgeIntersection: _getEdgeIntersection,
6058 _getBitCode: _getBitCode,
6059 _sqClosestPointOnSegment: _sqClosestPointOnSegment,
6065 * @namespace PolyUtil
6066 * Various utility functions for polygon geometries.
6069 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
6070 * 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)).
6071 * Used by Leaflet to only show polygon points that are on the screen or near, increasing
6072 * performance. Note that polygon points needs different algorithm for clipping
6073 * than polyline, so there's a seperate method for it.
6075 function clipPolygon(points, bounds, round) {
6077 edges = [1, 4, 2, 8],
6082 for (i = 0, len = points.length; i < len; i++) {
6083 points[i]._code = _getBitCode(points[i], bounds);
6086 // for each edge (left, bottom, right, top)
6087 for (k = 0; k < 4; k++) {
6091 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
6095 // if a is inside the clip window
6096 if (!(a._code & edge)) {
6097 // if b is outside the clip window (a->b goes out of screen)
6098 if (b._code & edge) {
6099 p = _getEdgeIntersection(b, a, edge, bounds, round);
6100 p._code = _getBitCode(p, bounds);
6101 clippedPoints.push(p);
6103 clippedPoints.push(a);
6105 // else if b is inside the clip window (a->b enters the screen)
6106 } else if (!(b._code & edge)) {
6107 p = _getEdgeIntersection(b, a, edge, bounds, round);
6108 p._code = _getBitCode(p, bounds);
6109 clippedPoints.push(p);
6112 points = clippedPoints;
6119 var PolyUtil = (Object.freeze || Object)({
6120 clipPolygon: clipPolygon
6124 * @namespace Projection
6126 * Leaflet comes with a set of already defined Projections out of the box:
6128 * @projection L.Projection.LonLat
6130 * Equirectangular, or Plate Carree projection — the most simple projection,
6131 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
6132 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
6133 * `EPSG:4326` and `Simple` CRS.
6137 project: function (latlng) {
6138 return new Point(latlng.lng, latlng.lat);
6141 unproject: function (point) {
6142 return new LatLng(point.y, point.x);
6145 bounds: new Bounds([-180, -90], [180, 90])
6149 * @namespace Projection
6150 * @projection L.Projection.Mercator
6152 * 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.
6157 R_MINOR: 6356752.314245179,
6159 bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
6161 project: function (latlng) {
6162 var d = Math.PI / 180,
6165 tmp = this.R_MINOR / r,
6166 e = Math.sqrt(1 - tmp * tmp),
6167 con = e * Math.sin(y);
6169 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
6170 y = -r * Math.log(Math.max(ts, 1E-10));
6172 return new Point(latlng.lng * d * r, y);
6175 unproject: function (point) {
6176 var d = 180 / Math.PI,
6178 tmp = this.R_MINOR / r,
6179 e = Math.sqrt(1 - tmp * tmp),
6180 ts = Math.exp(-point.y / r),
6181 phi = Math.PI / 2 - 2 * Math.atan(ts);
6183 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
6184 con = e * Math.sin(phi);
6185 con = Math.pow((1 - con) / (1 + con), e / 2);
6186 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
6190 return new LatLng(phi * d, point.x * d / r);
6197 * An object with methods for projecting geographical coordinates of the world onto
6198 * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection).
6200 * @property bounds: Bounds
6201 * The bounds (specified in CRS units) where the projection is valid
6203 * @method project(latlng: LatLng): Point
6204 * Projects geographical coordinates into a 2D point.
6205 * Only accepts actual `L.LatLng` instances, not arrays.
6207 * @method unproject(point: Point): LatLng
6208 * The inverse of `project`. Projects a 2D point into a geographical location.
6209 * Only accepts actual `L.Point` instances, not arrays.
6216 var index = (Object.freeze || Object)({
6219 SphericalMercator: SphericalMercator
6224 * @crs L.CRS.EPSG3395
6226 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
6228 var EPSG3395 = extend({}, Earth, {
6230 projection: Mercator,
6232 transformation: (function () {
6233 var scale = 0.5 / (Math.PI * Mercator.R);
6234 return toTransformation(scale, 0.5, -scale, 0.5);
6240 * @crs L.CRS.EPSG4326
6242 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
6244 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
6245 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer`
6246 * with this CRS, ensure that there are two 256x256 pixel tiles covering the
6247 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
6248 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
6251 var EPSG4326 = extend({}, Earth, {
6254 transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
6261 * A simple CRS that maps longitude and latitude into `x` and `y` directly.
6262 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
6263 * axis should still be inverted (going from bottom to top). `distance()` returns
6264 * simple euclidean distance.
6267 var Simple = extend({}, CRS, {
6269 transformation: toTransformation(1, 0, -1, 0),
6271 scale: function (zoom) {
6272 return Math.pow(2, zoom);
6275 zoom: function (scale) {
6276 return Math.log(scale) / Math.LN2;
6279 distance: function (latlng1, latlng2) {
6280 var dx = latlng2.lng - latlng1.lng,
6281 dy = latlng2.lat - latlng1.lat;
6283 return Math.sqrt(dx * dx + dy * dy);
6290 CRS.EPSG3395 = EPSG3395;
6291 CRS.EPSG3857 = EPSG3857;
6292 CRS.EPSG900913 = EPSG900913;
6293 CRS.EPSG4326 = EPSG4326;
6294 CRS.Simple = Simple;
6302 * A set of methods from the Layer base class that all Leaflet layers use.
6303 * Inherits all methods, options and events from `L.Evented`.
6308 * var layer = L.Marker(latlng).addTo(map);
6314 * Fired after the layer is added to a map
6316 * @event remove: Event
6317 * Fired after the layer is removed from a map
6321 var Layer = Evented.extend({
6323 // Classes extending `L.Layer` will inherit the following options:
6325 // @option pane: String = 'overlayPane'
6326 // 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.
6327 pane: 'overlayPane',
6329 // @option attribution: String = null
6330 // String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox".
6333 bubblingMouseEvents: true
6337 * Classes extending `L.Layer` will inherit the following methods:
6339 * @method addTo(map: Map|LayerGroup): this
6340 * Adds the layer to the given map or layer group.
6342 addTo: function (map) {
6347 // @method remove: this
6348 // Removes the layer from the map it is currently active on.
6349 remove: function () {
6350 return this.removeFrom(this._map || this._mapToAdd);
6353 // @method removeFrom(map: Map): this
6354 // Removes the layer from the given map
6355 removeFrom: function (obj) {
6357 obj.removeLayer(this);
6362 // @method getPane(name? : String): HTMLElement
6363 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
6364 getPane: function (name) {
6365 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
6368 addInteractiveTarget: function (targetEl) {
6369 this._map._targets[stamp(targetEl)] = this;
6373 removeInteractiveTarget: function (targetEl) {
6374 delete this._map._targets[stamp(targetEl)];
6378 // @method getAttribution: String
6379 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
6380 getAttribution: function () {
6381 return this.options.attribution;
6384 _layerAdd: function (e) {
6387 // check in case layer gets added and then removed before the map is ready
6388 if (!map.hasLayer(this)) { return; }
6391 this._zoomAnimated = map._zoomAnimated;
6393 if (this.getEvents) {
6394 var events = this.getEvents();
6395 map.on(events, this);
6396 this.once('remove', function () {
6397 map.off(events, this);
6403 if (this.getAttribution && map.attributionControl) {
6404 map.attributionControl.addAttribution(this.getAttribution());
6408 map.fire('layeradd', {layer: this});
6412 /* @section Extension methods
6415 * Every layer should extend from `L.Layer` and (re-)implement the following methods.
6417 * @method onAdd(map: Map): this
6418 * 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).
6420 * @method onRemove(map: Map): this
6421 * 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).
6423 * @method getEvents(): Object
6424 * 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.
6426 * @method getAttribution(): String
6427 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
6429 * @method beforeAdd(map: Map): this
6430 * 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.
6435 * @section Layer events
6437 * @event layeradd: LayerEvent
6438 * Fired when a new layer is added to the map.
6440 * @event layerremove: LayerEvent
6441 * Fired when some layer is removed from the map
6443 * @section Methods for Layers and Controls
6446 // @method addLayer(layer: Layer): this
6447 // Adds the given layer to the map
6448 addLayer: function (layer) {
6449 if (!layer._layerAdd) {
6450 throw new Error('The provided object is not a Layer.');
6453 var id = stamp(layer);
6454 if (this._layers[id]) { return this; }
6455 this._layers[id] = layer;
6457 layer._mapToAdd = this;
6459 if (layer.beforeAdd) {
6460 layer.beforeAdd(this);
6463 this.whenReady(layer._layerAdd, layer);
6468 // @method removeLayer(layer: Layer): this
6469 // Removes the given layer from the map.
6470 removeLayer: function (layer) {
6471 var id = stamp(layer);
6473 if (!this._layers[id]) { return this; }
6476 layer.onRemove(this);
6479 if (layer.getAttribution && this.attributionControl) {
6480 this.attributionControl.removeAttribution(layer.getAttribution());
6483 delete this._layers[id];
6486 this.fire('layerremove', {layer: layer});
6487 layer.fire('remove');
6490 layer._map = layer._mapToAdd = null;
6495 // @method hasLayer(layer: Layer): Boolean
6496 // Returns `true` if the given layer is currently added to the map
6497 hasLayer: function (layer) {
6498 return !!layer && (stamp(layer) in this._layers);
6501 /* @method eachLayer(fn: Function, context?: Object): this
6502 * Iterates over the layers of the map, optionally specifying context of the iterator function.
6504 * map.eachLayer(function(layer){
6505 * layer.bindPopup('Hello');
6509 eachLayer: function (method, context) {
6510 for (var i in this._layers) {
6511 method.call(context, this._layers[i]);
6516 _addLayers: function (layers) {
6517 layers = layers ? (isArray(layers) ? layers : [layers]) : [];
6519 for (var i = 0, len = layers.length; i < len; i++) {
6520 this.addLayer(layers[i]);
6524 _addZoomLimit: function (layer) {
6525 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
6526 this._zoomBoundLayers[stamp(layer)] = layer;
6527 this._updateZoomLevels();
6531 _removeZoomLimit: function (layer) {
6532 var id = stamp(layer);
6534 if (this._zoomBoundLayers[id]) {
6535 delete this._zoomBoundLayers[id];
6536 this._updateZoomLevels();
6540 _updateZoomLevels: function () {
6541 var minZoom = Infinity,
6542 maxZoom = -Infinity,
6543 oldZoomSpan = this._getZoomSpan();
6545 for (var i in this._zoomBoundLayers) {
6546 var options = this._zoomBoundLayers[i].options;
6548 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
6549 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
6552 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
6553 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
6555 // @section Map state change events
6556 // @event zoomlevelschange: Event
6557 // Fired when the number of zoomlevels on the map is changed due
6558 // to adding or removing a layer.
6559 if (oldZoomSpan !== this._getZoomSpan()) {
6560 this.fire('zoomlevelschange');
6563 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
6564 this.setZoom(this._layersMaxZoom);
6566 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
6567 this.setZoom(this._layersMinZoom);
6577 * Used to group several layers and handle them as one. If you add it to the map,
6578 * any layers added or removed from the group will be added/removed on the map as
6579 * well. Extends `Layer`.
6584 * L.layerGroup([marker1, marker2])
6585 * .addLayer(polyline)
6590 var LayerGroup = Layer.extend({
6592 initialize: function (layers) {
6598 for (i = 0, len = layers.length; i < len; i++) {
6599 this.addLayer(layers[i]);
6604 // @method addLayer(layer: Layer): this
6605 // Adds the given layer to the group.
6606 addLayer: function (layer) {
6607 var id = this.getLayerId(layer);
6609 this._layers[id] = layer;
6612 this._map.addLayer(layer);
6618 // @method removeLayer(layer: Layer): this
6619 // Removes the given layer from the group.
6621 // @method removeLayer(id: Number): this
6622 // Removes the layer with the given internal ID from the group.
6623 removeLayer: function (layer) {
6624 var id = layer in this._layers ? layer : this.getLayerId(layer);
6626 if (this._map && this._layers[id]) {
6627 this._map.removeLayer(this._layers[id]);
6630 delete this._layers[id];
6635 // @method hasLayer(layer: Layer): Boolean
6636 // Returns `true` if the given layer is currently added to the group.
6638 // @method hasLayer(id: Number): Boolean
6639 // Returns `true` if the given internal ID is currently added to the group.
6640 hasLayer: function (layer) {
6641 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
6644 // @method clearLayers(): this
6645 // Removes all the layers from the group.
6646 clearLayers: function () {
6647 for (var i in this._layers) {
6648 this.removeLayer(this._layers[i]);
6653 // @method invoke(methodName: String, …): this
6654 // Calls `methodName` on every layer contained in this group, passing any
6655 // additional parameters. Has no effect if the layers contained do not
6656 // implement `methodName`.
6657 invoke: function (methodName) {
6658 var args = Array.prototype.slice.call(arguments, 1),
6661 for (i in this._layers) {
6662 layer = this._layers[i];
6664 if (layer[methodName]) {
6665 layer[methodName].apply(layer, args);
6672 onAdd: function (map) {
6673 for (var i in this._layers) {
6674 map.addLayer(this._layers[i]);
6678 onRemove: function (map) {
6679 for (var i in this._layers) {
6680 map.removeLayer(this._layers[i]);
6684 // @method eachLayer(fn: Function, context?: Object): this
6685 // Iterates over the layers of the group, optionally specifying context of the iterator function.
6687 // group.eachLayer(function (layer) {
6688 // layer.bindPopup('Hello');
6691 eachLayer: function (method, context) {
6692 for (var i in this._layers) {
6693 method.call(context, this._layers[i]);
6698 // @method getLayer(id: Number): Layer
6699 // Returns the layer with the given internal ID.
6700 getLayer: function (id) {
6701 return this._layers[id];
6704 // @method getLayers(): Layer[]
6705 // Returns an array of all the layers added to the group.
6706 getLayers: function () {
6709 for (var i in this._layers) {
6710 layers.push(this._layers[i]);
6715 // @method setZIndex(zIndex: Number): this
6716 // Calls `setZIndex` on every layer contained in this group, passing the z-index.
6717 setZIndex: function (zIndex) {
6718 return this.invoke('setZIndex', zIndex);
6721 // @method getLayerId(layer: Layer): Number
6722 // Returns the internal ID for a layer
6723 getLayerId: function (layer) {
6724 return stamp(layer);
6729 // @factory L.layerGroup(layers?: Layer[])
6730 // Create a layer group, optionally given an initial set of layers.
6731 var layerGroup = function (layers) {
6732 return new LayerGroup(layers);
6736 * @class FeatureGroup
6737 * @aka L.FeatureGroup
6738 * @inherits LayerGroup
6740 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
6741 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
6742 * * Events are propagated to the `FeatureGroup`, so if the group has an event
6743 * handler, it will handle events from any of the layers. This includes mouse events
6744 * and custom events.
6745 * * Has `layeradd` and `layerremove` events
6750 * L.featureGroup([marker1, marker2, polyline])
6751 * .bindPopup('Hello world!')
6752 * .on('click', function() { alert('Clicked on a member of the group!'); })
6757 var FeatureGroup = LayerGroup.extend({
6759 addLayer: function (layer) {
6760 if (this.hasLayer(layer)) {
6764 layer.addEventParent(this);
6766 LayerGroup.prototype.addLayer.call(this, layer);
6768 // @event layeradd: LayerEvent
6769 // Fired when a layer is added to this `FeatureGroup`
6770 return this.fire('layeradd', {layer: layer});
6773 removeLayer: function (layer) {
6774 if (!this.hasLayer(layer)) {
6777 if (layer in this._layers) {
6778 layer = this._layers[layer];
6781 layer.removeEventParent(this);
6783 LayerGroup.prototype.removeLayer.call(this, layer);
6785 // @event layerremove: LayerEvent
6786 // Fired when a layer is removed from this `FeatureGroup`
6787 return this.fire('layerremove', {layer: layer});
6790 // @method setStyle(style: Path options): this
6791 // Sets the given path options to each layer of the group that has a `setStyle` method.
6792 setStyle: function (style) {
6793 return this.invoke('setStyle', style);
6796 // @method bringToFront(): this
6797 // Brings the layer group to the top of all other layers
6798 bringToFront: function () {
6799 return this.invoke('bringToFront');
6802 // @method bringToBack(): this
6803 // Brings the layer group to the top of all other layers
6804 bringToBack: function () {
6805 return this.invoke('bringToBack');
6808 // @method getBounds(): LatLngBounds
6809 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
6810 getBounds: function () {
6811 var bounds = new LatLngBounds();
6813 for (var id in this._layers) {
6814 var layer = this._layers[id];
6815 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
6821 // @factory L.featureGroup(layers: Layer[])
6822 // Create a feature group, optionally given an initial set of layers.
6823 var featureGroup = function (layers) {
6824 return new FeatureGroup(layers);
6831 * Represents an icon to provide when creating a marker.
6836 * var myIcon = L.icon({
6837 * iconUrl: 'my-icon.png',
6838 * iconRetinaUrl: 'my-icon@2x.png',
6839 * iconSize: [38, 95],
6840 * iconAnchor: [22, 94],
6841 * popupAnchor: [-3, -76],
6842 * shadowUrl: 'my-icon-shadow.png',
6843 * shadowRetinaUrl: 'my-icon-shadow@2x.png',
6844 * shadowSize: [68, 95],
6845 * shadowAnchor: [22, 94]
6848 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
6851 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
6855 var Icon = Class.extend({
6860 * @option iconUrl: String = null
6861 * **(required)** The URL to the icon image (absolute or relative to your script path).
6863 * @option iconRetinaUrl: String = null
6864 * The URL to a retina sized version of the icon image (absolute or relative to your
6865 * script path). Used for Retina screen devices.
6867 * @option iconSize: Point = null
6868 * Size of the icon image in pixels.
6870 * @option iconAnchor: Point = null
6871 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
6872 * will be aligned so that this point is at the marker's geographical location. Centered
6873 * by default if size is specified, also can be set in CSS with negative margins.
6875 * @option popupAnchor: Point = null
6876 * The coordinates of the point from which popups will "open", relative to the icon anchor.
6878 * @option shadowUrl: String = null
6879 * The URL to the icon shadow image. If not specified, no shadow image will be created.
6881 * @option shadowRetinaUrl: String = null
6883 * @option shadowSize: Point = null
6884 * Size of the shadow image in pixels.
6886 * @option shadowAnchor: Point = null
6887 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
6888 * as iconAnchor if not specified).
6890 * @option className: String = ''
6891 * A custom class name to assign to both icon and shadow images. Empty by default.
6894 initialize: function (options) {
6895 setOptions(this, options);
6898 // @method createIcon(oldIcon?: HTMLElement): HTMLElement
6899 // Called internally when the icon has to be shown, returns a `<img>` HTML element
6900 // styled according to the options.
6901 createIcon: function (oldIcon) {
6902 return this._createIcon('icon', oldIcon);
6905 // @method createShadow(oldIcon?: HTMLElement): HTMLElement
6906 // As `createIcon`, but for the shadow beneath it.
6907 createShadow: function (oldIcon) {
6908 return this._createIcon('shadow', oldIcon);
6911 _createIcon: function (name, oldIcon) {
6912 var src = this._getIconUrl(name);
6915 if (name === 'icon') {
6916 throw new Error('iconUrl not set in Icon options (see the docs).');
6921 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
6922 this._setIconStyles(img, name);
6927 _setIconStyles: function (img, name) {
6928 var options = this.options;
6929 var sizeOption = options[name + 'Size'];
6931 if (typeof sizeOption === 'number') {
6932 sizeOption = [sizeOption, sizeOption];
6935 var size = toPoint(sizeOption),
6936 anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
6937 size && size.divideBy(2, true));
6939 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
6942 img.style.marginLeft = (-anchor.x) + 'px';
6943 img.style.marginTop = (-anchor.y) + 'px';
6947 img.style.width = size.x + 'px';
6948 img.style.height = size.y + 'px';
6952 _createImg: function (src, el) {
6953 el = el || document.createElement('img');
6958 _getIconUrl: function (name) {
6959 return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
6964 // @factory L.icon(options: Icon options)
6965 // Creates an icon instance with the given options.
6966 function icon(options) {
6967 return new Icon(options);
6971 * @miniclass Icon.Default (Icon)
6972 * @aka L.Icon.Default
6975 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
6976 * no icon is specified. Points to the blue marker image distributed with Leaflet
6979 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
6980 * (which is a set of `Icon options`).
6982 * If you want to _completely_ replace the default icon, override the
6983 * `L.Marker.prototype.options.icon` with your own icon instead.
6986 var IconDefault = Icon.extend({
6989 iconUrl: 'marker-icon.png',
6990 iconRetinaUrl: 'marker-icon-2x.png',
6991 shadowUrl: 'marker-shadow.png',
6993 iconAnchor: [12, 41],
6994 popupAnchor: [1, -34],
6995 tooltipAnchor: [16, -28],
6996 shadowSize: [41, 41]
6999 _getIconUrl: function (name) {
7000 if (!IconDefault.imagePath) { // Deprecated, backwards-compatibility only
7001 IconDefault.imagePath = this._detectIconPath();
7004 // @option imagePath: String
7005 // `Icon.Default` will try to auto-detect the absolute location of the
7006 // blue icon images. If you are placing these images in a non-standard
7007 // way, set this option to point to the right absolute path.
7008 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
7011 _detectIconPath: function () {
7012 var el = create$1('div', 'leaflet-default-icon-path', document.body);
7013 var path = getStyle(el, 'background-image') ||
7014 getStyle(el, 'backgroundImage'); // IE8
7016 document.body.removeChild(el);
7018 if (path === null || path.indexOf('url') !== 0) {
7021 path = path.replace(/^url\([\"\']?/, '').replace(/marker-icon\.png[\"\']?\)$/, '');
7029 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7033 /* @namespace Marker
7034 * @section Interaction handlers
7036 * 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:
7039 * marker.dragging.disable();
7042 * @property dragging: Handler
7043 * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
7046 var MarkerDrag = Handler.extend({
7047 initialize: function (marker) {
7048 this._marker = marker;
7051 addHooks: function () {
7052 var icon = this._marker._icon;
7054 if (!this._draggable) {
7055 this._draggable = new Draggable(icon, icon, true);
7058 this._draggable.on({
7059 dragstart: this._onDragStart,
7061 dragend: this._onDragEnd
7064 addClass(icon, 'leaflet-marker-draggable');
7067 removeHooks: function () {
7068 this._draggable.off({
7069 dragstart: this._onDragStart,
7071 dragend: this._onDragEnd
7074 if (this._marker._icon) {
7075 removeClass(this._marker._icon, 'leaflet-marker-draggable');
7079 moved: function () {
7080 return this._draggable && this._draggable._moved;
7083 _onDragStart: function () {
7084 // @section Dragging events
7085 // @event dragstart: Event
7086 // Fired when the user starts dragging the marker.
7088 // @event movestart: Event
7089 // Fired when the marker starts moving (because of dragging).
7091 this._oldLatLng = this._marker.getLatLng();
7098 _onDrag: function (e) {
7099 var marker = this._marker,
7100 shadow = marker._shadow,
7101 iconPos = getPosition(marker._icon),
7102 latlng = marker._map.layerPointToLatLng(iconPos);
7104 // update shadow position
7106 setPosition(shadow, iconPos);
7109 marker._latlng = latlng;
7111 e.oldLatLng = this._oldLatLng;
7113 // @event drag: Event
7114 // Fired repeatedly while the user drags the marker.
7120 _onDragEnd: function (e) {
7121 // @event dragend: DragEndEvent
7122 // Fired when the user stops dragging the marker.
7124 // @event moveend: Event
7125 // Fired when the marker stops moving (because of dragging).
7126 delete this._oldLatLng;
7129 .fire('dragend', e);
7135 * @inherits Interactive layer
7137 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
7142 * L.marker([50.5, 30.5]).addTo(map);
7146 var Marker = Layer.extend({
7149 // @aka Marker options
7151 // @option icon: Icon = *
7152 // Icon instance to use for rendering the marker.
7153 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
7154 // If not specified, a common instance of `L.Icon.Default` is used.
7155 icon: new IconDefault(),
7157 // Option inherited from "Interactive layer" abstract class
7160 // @option draggable: Boolean = false
7161 // Whether the marker is draggable with mouse/touch or not.
7164 // @option keyboard: Boolean = true
7165 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
7168 // @option title: String = ''
7169 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
7172 // @option alt: String = ''
7173 // Text for the `alt` attribute of the icon image (useful for accessibility).
7176 // @option zIndexOffset: Number = 0
7177 // 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).
7180 // @option opacity: Number = 1.0
7181 // The opacity of the marker.
7184 // @option riseOnHover: Boolean = false
7185 // If `true`, the marker will get on top of others when you hover the mouse over it.
7188 // @option riseOffset: Number = 250
7189 // The z-index offset used for the `riseOnHover` feature.
7192 // @option pane: String = 'markerPane'
7193 // `Map pane` where the markers icon will be added.
7196 // @option bubblingMouseEvents: Boolean = false
7197 // When `true`, a mouse event on this marker will trigger the same event on the map
7198 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7199 bubblingMouseEvents: false
7204 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
7207 initialize: function (latlng, options) {
7208 setOptions(this, options);
7209 this._latlng = toLatLng(latlng);
7212 onAdd: function (map) {
7213 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
7215 if (this._zoomAnimated) {
7216 map.on('zoomanim', this._animateZoom, this);
7223 onRemove: function (map) {
7224 if (this.dragging && this.dragging.enabled()) {
7225 this.options.draggable = true;
7226 this.dragging.removeHooks();
7228 delete this.dragging;
7230 if (this._zoomAnimated) {
7231 map.off('zoomanim', this._animateZoom, this);
7235 this._removeShadow();
7238 getEvents: function () {
7241 viewreset: this.update
7245 // @method getLatLng: LatLng
7246 // Returns the current geographical position of the marker.
7247 getLatLng: function () {
7248 return this._latlng;
7251 // @method setLatLng(latlng: LatLng): this
7252 // Changes the marker position to the given point.
7253 setLatLng: function (latlng) {
7254 var oldLatLng = this._latlng;
7255 this._latlng = toLatLng(latlng);
7258 // @event move: Event
7259 // 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`.
7260 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7263 // @method setZIndexOffset(offset: Number): this
7264 // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
7265 setZIndexOffset: function (offset) {
7266 this.options.zIndexOffset = offset;
7267 return this.update();
7270 // @method setIcon(icon: Icon): this
7271 // Changes the marker icon.
7272 setIcon: function (icon) {
7274 this.options.icon = icon;
7282 this.bindPopup(this._popup, this._popup.options);
7288 getElement: function () {
7292 update: function () {
7295 var pos = this._map.latLngToLayerPoint(this._latlng).round();
7302 _initIcon: function () {
7303 var options = this.options,
7304 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
7306 var icon = options.icon.createIcon(this._icon),
7309 // if we're not reusing the icon, remove the old one and init new one
7310 if (icon !== this._icon) {
7316 if (options.title) {
7317 icon.title = options.title;
7320 icon.alt = options.alt;
7324 addClass(icon, classToAdd);
7326 if (options.keyboard) {
7327 icon.tabIndex = '0';
7332 if (options.riseOnHover) {
7334 mouseover: this._bringToFront,
7335 mouseout: this._resetZIndex
7339 var newShadow = options.icon.createShadow(this._shadow),
7342 if (newShadow !== this._shadow) {
7343 this._removeShadow();
7348 addClass(newShadow, classToAdd);
7351 this._shadow = newShadow;
7354 if (options.opacity < 1) {
7355 this._updateOpacity();
7360 this.getPane().appendChild(this._icon);
7362 this._initInteraction();
7363 if (newShadow && addShadow) {
7364 this.getPane('shadowPane').appendChild(this._shadow);
7368 _removeIcon: function () {
7369 if (this.options.riseOnHover) {
7371 mouseover: this._bringToFront,
7372 mouseout: this._resetZIndex
7377 this.removeInteractiveTarget(this._icon);
7382 _removeShadow: function () {
7384 remove(this._shadow);
7386 this._shadow = null;
7389 _setPos: function (pos) {
7390 setPosition(this._icon, pos);
7393 setPosition(this._shadow, pos);
7396 this._zIndex = pos.y + this.options.zIndexOffset;
7398 this._resetZIndex();
7401 _updateZIndex: function (offset) {
7402 this._icon.style.zIndex = this._zIndex + offset;
7405 _animateZoom: function (opt) {
7406 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
7411 _initInteraction: function () {
7413 if (!this.options.interactive) { return; }
7415 addClass(this._icon, 'leaflet-interactive');
7417 this.addInteractiveTarget(this._icon);
7420 var draggable = this.options.draggable;
7421 if (this.dragging) {
7422 draggable = this.dragging.enabled();
7423 this.dragging.disable();
7426 this.dragging = new MarkerDrag(this);
7429 this.dragging.enable();
7434 // @method setOpacity(opacity: Number): this
7435 // Changes the opacity of the marker.
7436 setOpacity: function (opacity) {
7437 this.options.opacity = opacity;
7439 this._updateOpacity();
7445 _updateOpacity: function () {
7446 var opacity = this.options.opacity;
7448 setOpacity(this._icon, opacity);
7451 setOpacity(this._shadow, opacity);
7455 _bringToFront: function () {
7456 this._updateZIndex(this.options.riseOffset);
7459 _resetZIndex: function () {
7460 this._updateZIndex(0);
7463 _getPopupAnchor: function () {
7464 return this.options.icon.options.popupAnchor || [0, 0];
7467 _getTooltipAnchor: function () {
7468 return this.options.icon.options.tooltipAnchor || [0, 0];
7473 // factory L.marker(latlng: LatLng, options? : Marker options)
7475 // @factory L.marker(latlng: LatLng, options? : Marker options)
7476 // Instantiates a Marker object given a geographical point and optionally an options object.
7477 function marker(latlng, options) {
7478 return new Marker(latlng, options);
7484 * @inherits Interactive layer
7486 * An abstract class that contains options and constants shared between vector
7487 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
7490 var Path = Layer.extend({
7493 // @aka Path options
7495 // @option stroke: Boolean = true
7496 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
7499 // @option color: String = '#3388ff'
7503 // @option weight: Number = 3
7504 // Stroke width in pixels
7507 // @option opacity: Number = 1.0
7511 // @option lineCap: String= 'round'
7512 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
7515 // @option lineJoin: String = 'round'
7516 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
7519 // @option dashArray: String = null
7520 // 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).
7523 // @option dashOffset: String = null
7524 // 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).
7527 // @option fill: Boolean = depends
7528 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
7531 // @option fillColor: String = *
7532 // Fill color. Defaults to the value of the [`color`](#path-color) option
7535 // @option fillOpacity: Number = 0.2
7539 // @option fillRule: String = 'evenodd'
7540 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
7541 fillRule: 'evenodd',
7545 // Option inherited from "Interactive layer" abstract class
7548 // @option bubblingMouseEvents: Boolean = true
7549 // When `true`, a mouse event on this path will trigger the same event on the map
7550 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7551 bubblingMouseEvents: true
7554 beforeAdd: function (map) {
7555 // Renderer is set here because we need to call renderer.getEvents
7556 // before this.getEvents.
7557 this._renderer = map.getRenderer(this);
7560 onAdd: function () {
7561 this._renderer._initPath(this);
7563 this._renderer._addPath(this);
7566 onRemove: function () {
7567 this._renderer._removePath(this);
7570 // @method redraw(): this
7571 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
7572 redraw: function () {
7574 this._renderer._updatePath(this);
7579 // @method setStyle(style: Path options): this
7580 // Changes the appearance of a Path based on the options in the `Path options` object.
7581 setStyle: function (style) {
7582 setOptions(this, style);
7583 if (this._renderer) {
7584 this._renderer._updateStyle(this);
7589 // @method bringToFront(): this
7590 // Brings the layer to the top of all path layers.
7591 bringToFront: function () {
7592 if (this._renderer) {
7593 this._renderer._bringToFront(this);
7598 // @method bringToBack(): this
7599 // Brings the layer to the bottom of all path layers.
7600 bringToBack: function () {
7601 if (this._renderer) {
7602 this._renderer._bringToBack(this);
7607 getElement: function () {
7611 _reset: function () {
7612 // defined in child classes
7617 _clickTolerance: function () {
7618 // used when doing hit detection for Canvas layers
7619 return (this.options.stroke ? this.options.weight / 2 : 0) + (touch ? 10 : 0);
7624 * @class CircleMarker
7625 * @aka L.CircleMarker
7628 * A circle of a fixed size with radius specified in pixels. Extends `Path`.
7631 var CircleMarker = Path.extend({
7634 // @aka CircleMarker options
7638 // @option radius: Number = 10
7639 // Radius of the circle marker, in pixels
7643 initialize: function (latlng, options) {
7644 setOptions(this, options);
7645 this._latlng = toLatLng(latlng);
7646 this._radius = this.options.radius;
7649 // @method setLatLng(latLng: LatLng): this
7650 // Sets the position of a circle marker to a new location.
7651 setLatLng: function (latlng) {
7652 this._latlng = toLatLng(latlng);
7654 return this.fire('move', {latlng: this._latlng});
7657 // @method getLatLng(): LatLng
7658 // Returns the current geographical position of the circle marker
7659 getLatLng: function () {
7660 return this._latlng;
7663 // @method setRadius(radius: Number): this
7664 // Sets the radius of a circle marker. Units are in pixels.
7665 setRadius: function (radius) {
7666 this.options.radius = this._radius = radius;
7667 return this.redraw();
7670 // @method getRadius(): Number
7671 // Returns the current radius of the circle
7672 getRadius: function () {
7673 return this._radius;
7676 setStyle : function (options) {
7677 var radius = options && options.radius || this._radius;
7678 Path.prototype.setStyle.call(this, options);
7679 this.setRadius(radius);
7683 _project: function () {
7684 this._point = this._map.latLngToLayerPoint(this._latlng);
7685 this._updateBounds();
7688 _updateBounds: function () {
7689 var r = this._radius,
7690 r2 = this._radiusY || r,
7691 w = this._clickTolerance(),
7692 p = [r + w, r2 + w];
7693 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
7696 _update: function () {
7702 _updatePath: function () {
7703 this._renderer._updateCircle(this);
7706 _empty: function () {
7707 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
7710 // Needed by the `Canvas` renderer for interactivity
7711 _containsPoint: function (p) {
7712 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
7717 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
7718 // Instantiates a circle marker object given a geographical point, and an optional options object.
7719 function circleMarker(latlng, options) {
7720 return new CircleMarker(latlng, options);
7726 * @inherits CircleMarker
7728 * A class for drawing circle overlays on a map. Extends `CircleMarker`.
7730 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
7735 * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
7739 var Circle = CircleMarker.extend({
7741 initialize: function (latlng, options, legacyOptions) {
7742 if (typeof options === 'number') {
7743 // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
7744 options = extend({}, legacyOptions, {radius: options});
7746 setOptions(this, options);
7747 this._latlng = toLatLng(latlng);
7749 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
7752 // @aka Circle options
7753 // @option radius: Number; Radius of the circle, in meters.
7754 this._mRadius = this.options.radius;
7757 // @method setRadius(radius: Number): this
7758 // Sets the radius of a circle. Units are in meters.
7759 setRadius: function (radius) {
7760 this._mRadius = radius;
7761 return this.redraw();
7764 // @method getRadius(): Number
7765 // Returns the current radius of a circle. Units are in meters.
7766 getRadius: function () {
7767 return this._mRadius;
7770 // @method getBounds(): LatLngBounds
7771 // Returns the `LatLngBounds` of the path.
7772 getBounds: function () {
7773 var half = [this._radius, this._radiusY || this._radius];
7775 return new LatLngBounds(
7776 this._map.layerPointToLatLng(this._point.subtract(half)),
7777 this._map.layerPointToLatLng(this._point.add(half)));
7780 setStyle: Path.prototype.setStyle,
7782 _project: function () {
7784 var lng = this._latlng.lng,
7785 lat = this._latlng.lat,
7787 crs = map.options.crs;
7789 if (crs.distance === Earth.distance) {
7790 var d = Math.PI / 180,
7791 latR = (this._mRadius / Earth.R) / d,
7792 top = map.project([lat + latR, lng]),
7793 bottom = map.project([lat - latR, lng]),
7794 p = top.add(bottom).divideBy(2),
7795 lat2 = map.unproject(p).lat,
7796 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
7797 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
7799 if (isNaN(lngR) || lngR === 0) {
7800 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
7803 this._point = p.subtract(map.getPixelOrigin());
7804 this._radius = isNaN(lngR) ? 0 : Math.max(Math.round(p.x - map.project([lat2, lng - lngR]).x), 1);
7805 this._radiusY = Math.max(Math.round(p.y - top.y), 1);
7808 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
7810 this._point = map.latLngToLayerPoint(this._latlng);
7811 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
7814 this._updateBounds();
7818 // @factory L.circle(latlng: LatLng, options?: Circle options)
7819 // Instantiates a circle object given a geographical point, and an options object
7820 // which contains the circle radius.
7822 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
7823 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
7824 // Do not use in new applications or plugins.
7825 function circle(latlng, options, legacyOptions) {
7826 return new Circle(latlng, options, legacyOptions);
7834 * A class for drawing polyline overlays on a map. Extends `Path`.
7839 * // create a red polyline from an array of LatLng points
7846 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
7848 * // zoom the map to the polyline
7849 * map.fitBounds(polyline.getBounds());
7852 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
7855 * // create a red polyline from an array of arrays of LatLng points
7857 * [[45.51, -122.68],
7868 var Polyline = Path.extend({
7871 // @aka Polyline options
7873 // @option smoothFactor: Number = 1.0
7874 // How much to simplify the polyline on each zoom level. More means
7875 // better performance and smoother look, and less means more accurate representation.
7878 // @option noClip: Boolean = false
7879 // Disable polyline clipping.
7883 initialize: function (latlngs, options) {
7884 setOptions(this, options);
7885 this._setLatLngs(latlngs);
7888 // @method getLatLngs(): LatLng[]
7889 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
7890 getLatLngs: function () {
7891 return this._latlngs;
7894 // @method setLatLngs(latlngs: LatLng[]): this
7895 // Replaces all the points in the polyline with the given array of geographical points.
7896 setLatLngs: function (latlngs) {
7897 this._setLatLngs(latlngs);
7898 return this.redraw();
7901 // @method isEmpty(): Boolean
7902 // Returns `true` if the Polyline has no LatLngs.
7903 isEmpty: function () {
7904 return !this._latlngs.length;
7907 closestLayerPoint: function (p) {
7908 var minDistance = Infinity,
7910 closest = _sqClosestPointOnSegment,
7913 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
7914 var points = this._parts[j];
7916 for (var i = 1, len = points.length; i < len; i++) {
7920 var sqDist = closest(p, p1, p2, true);
7922 if (sqDist < minDistance) {
7923 minDistance = sqDist;
7924 minPoint = closest(p, p1, p2);
7929 minPoint.distance = Math.sqrt(minDistance);
7934 // @method getCenter(): LatLng
7935 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
7936 getCenter: function () {
7937 // throws error when not yet added to map as this center calculation requires projected coordinates
7939 throw new Error('Must add layer to map before using getCenter()');
7942 var i, halfDist, segDist, dist, p1, p2, ratio,
7943 points = this._rings[0],
7944 len = points.length;
7946 if (!len) { return null; }
7948 // polyline centroid algorithm; only uses the first ring if there are multiple
7950 for (i = 0, halfDist = 0; i < len - 1; i++) {
7951 halfDist += points[i].distanceTo(points[i + 1]) / 2;
7954 // The line is so small in the current view that all points are on the same pixel.
7955 if (halfDist === 0) {
7956 return this._map.layerPointToLatLng(points[0]);
7959 for (i = 0, dist = 0; i < len - 1; i++) {
7962 segDist = p1.distanceTo(p2);
7965 if (dist > halfDist) {
7966 ratio = (dist - halfDist) / segDist;
7967 return this._map.layerPointToLatLng([
7968 p2.x - ratio * (p2.x - p1.x),
7969 p2.y - ratio * (p2.y - p1.y)
7975 // @method getBounds(): LatLngBounds
7976 // Returns the `LatLngBounds` of the path.
7977 getBounds: function () {
7978 return this._bounds;
7981 // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
7982 // Adds a given point to the polyline. By default, adds to the first ring of
7983 // the polyline in case of a multi-polyline, but can be overridden by passing
7984 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
7985 addLatLng: function (latlng, latlngs) {
7986 latlngs = latlngs || this._defaultShape();
7987 latlng = toLatLng(latlng);
7988 latlngs.push(latlng);
7989 this._bounds.extend(latlng);
7990 return this.redraw();
7993 _setLatLngs: function (latlngs) {
7994 this._bounds = new LatLngBounds();
7995 this._latlngs = this._convertLatLngs(latlngs);
7998 _defaultShape: function () {
7999 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
8002 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8003 _convertLatLngs: function (latlngs) {
8005 flat = isFlat(latlngs);
8007 for (var i = 0, len = latlngs.length; i < len; i++) {
8009 result[i] = toLatLng(latlngs[i]);
8010 this._bounds.extend(result[i]);
8012 result[i] = this._convertLatLngs(latlngs[i]);
8019 _project: function () {
8020 var pxBounds = new Bounds();
8022 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8024 var w = this._clickTolerance(),
8025 p = new Point(w, w);
8027 if (this._bounds.isValid() && pxBounds.isValid()) {
8028 pxBounds.min._subtract(p);
8029 pxBounds.max._add(p);
8030 this._pxBounds = pxBounds;
8034 // recursively turns latlngs into a set of rings with projected coordinates
8035 _projectLatlngs: function (latlngs, result, projectedBounds) {
8036 var flat = latlngs[0] instanceof LatLng,
8037 len = latlngs.length,
8042 for (i = 0; i < len; i++) {
8043 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8044 projectedBounds.extend(ring[i]);
8048 for (i = 0; i < len; i++) {
8049 this._projectLatlngs(latlngs[i], result, projectedBounds);
8054 // clip polyline by renderer bounds so that we have less to render for performance
8055 _clipPoints: function () {
8056 var bounds = this._renderer._bounds;
8059 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8063 if (this.options.noClip) {
8064 this._parts = this._rings;
8068 var parts = this._parts,
8069 i, j, k, len, len2, segment, points;
8071 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8072 points = this._rings[i];
8074 for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8075 segment = clipSegment(points[j], points[j + 1], bounds, j, true);
8077 if (!segment) { continue; }
8079 parts[k] = parts[k] || [];
8080 parts[k].push(segment[0]);
8082 // if segment goes out of screen, or it's the last one, it's the end of the line part
8083 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8084 parts[k].push(segment[1]);
8091 // simplify each clipped part of the polyline for performance
8092 _simplifyPoints: function () {
8093 var parts = this._parts,
8094 tolerance = this.options.smoothFactor;
8096 for (var i = 0, len = parts.length; i < len; i++) {
8097 parts[i] = simplify(parts[i], tolerance);
8101 _update: function () {
8102 if (!this._map) { return; }
8105 this._simplifyPoints();
8109 _updatePath: function () {
8110 this._renderer._updatePoly(this);
8113 // Needed by the `Canvas` renderer for interactivity
8114 _containsPoint: function (p, closed) {
8115 var i, j, k, len, len2, part,
8116 w = this._clickTolerance();
8118 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8120 // hit detection for polylines
8121 for (i = 0, len = this._parts.length; i < len; i++) {
8122 part = this._parts[i];
8124 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8125 if (!closed && (j === 0)) { continue; }
8127 if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
8136 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8137 // Instantiates a polyline object given an array of geographical points and
8138 // optionally an options object. You can create a `Polyline` object with
8139 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8140 // of geographic points.
8141 function polyline(latlngs, options) {
8142 return new Polyline(latlngs, options);
8145 // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
8146 Polyline._flat = _flat;
8151 * @inherits Polyline
8153 * A class for drawing polygon overlays on a map. Extends `Polyline`.
8155 * 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.
8161 * // create a red polygon from an array of LatLng points
8162 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
8164 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8166 * // zoom the map to the polygon
8167 * map.fitBounds(polygon.getBounds());
8170 * 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:
8174 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8175 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8179 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8183 * [ // first polygon
8184 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8185 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8187 * [ // second polygon
8188 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
8194 var Polygon = Polyline.extend({
8200 isEmpty: function () {
8201 return !this._latlngs.length || !this._latlngs[0].length;
8204 getCenter: function () {
8205 // throws error when not yet added to map as this center calculation requires projected coordinates
8207 throw new Error('Must add layer to map before using getCenter()');
8210 var i, j, p1, p2, f, area, x, y, center,
8211 points = this._rings[0],
8212 len = points.length;
8214 if (!len) { return null; }
8216 // polygon centroid algorithm; only uses the first ring if there are multiple
8220 for (i = 0, j = len - 1; i < len; j = i++) {
8224 f = p1.y * p2.x - p2.y * p1.x;
8225 x += (p1.x + p2.x) * f;
8226 y += (p1.y + p2.y) * f;
8231 // Polygon is so small that all points are on same pixel.
8234 center = [x / area, y / area];
8236 return this._map.layerPointToLatLng(center);
8239 _convertLatLngs: function (latlngs) {
8240 var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
8241 len = result.length;
8243 // remove last point if it equals first one
8244 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
8250 _setLatLngs: function (latlngs) {
8251 Polyline.prototype._setLatLngs.call(this, latlngs);
8252 if (isFlat(this._latlngs)) {
8253 this._latlngs = [this._latlngs];
8257 _defaultShape: function () {
8258 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8261 _clipPoints: function () {
8262 // polygons need a different clipping algorithm so we redefine that
8264 var bounds = this._renderer._bounds,
8265 w = this.options.weight,
8266 p = new Point(w, w);
8268 // increase clip padding by stroke width to avoid stroke on clip edges
8269 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
8272 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8276 if (this.options.noClip) {
8277 this._parts = this._rings;
8281 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8282 clipped = clipPolygon(this._rings[i], bounds, true);
8283 if (clipped.length) {
8284 this._parts.push(clipped);
8289 _updatePath: function () {
8290 this._renderer._updatePoly(this, true);
8293 // Needed by the `Canvas` renderer for interactivity
8294 _containsPoint: function (p) {
8296 part, p1, p2, i, j, k, len, len2;
8298 if (!this._pxBounds.contains(p)) { return false; }
8300 // ray casting algorithm for detecting if point is in polygon
8301 for (i = 0, len = this._parts.length; i < len; i++) {
8302 part = this._parts[i];
8304 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8308 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)) {
8314 // also check if it's on polygon stroke
8315 return inside || Polyline.prototype._containsPoint.call(this, p, true);
8321 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8322 function polygon(latlngs, options) {
8323 return new Polygon(latlngs, options);
8329 * @inherits FeatureGroup
8331 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
8332 * GeoJSON data and display it on the map. Extends `FeatureGroup`.
8338 * style: function (feature) {
8339 * return {color: feature.properties.color};
8341 * }).bindPopup(function (layer) {
8342 * return layer.feature.properties.description;
8347 var GeoJSON = FeatureGroup.extend({
8350 * @aka GeoJSON options
8352 * @option pointToLayer: Function = *
8353 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
8354 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
8355 * The default is to spawn a default `Marker`:
8357 * function(geoJsonPoint, latlng) {
8358 * return L.marker(latlng);
8362 * @option style: Function = *
8363 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
8364 * called internally when data is added.
8365 * The default value is to not override any defaults:
8367 * function (geoJsonFeature) {
8372 * @option onEachFeature: Function = *
8373 * A `Function` that will be called once for each created `Feature`, after it has
8374 * been created and styled. Useful for attaching events and popups to features.
8375 * The default is to do nothing with the newly created layers:
8377 * function (feature, layer) {}
8380 * @option filter: Function = *
8381 * A `Function` that will be used to decide whether to include a feature or not.
8382 * The default is to include all features:
8384 * function (geoJsonFeature) {
8388 * Note: dynamically changing the `filter` option will have effect only on newly
8389 * added data. It will _not_ re-evaluate already included features.
8391 * @option coordsToLatLng: Function = *
8392 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
8393 * The default is the `coordsToLatLng` static method.
8396 initialize: function (geojson, options) {
8397 setOptions(this, options);
8402 this.addData(geojson);
8406 // @method addData( <GeoJSON> data ): this
8407 // Adds a GeoJSON object to the layer.
8408 addData: function (geojson) {
8409 var features = isArray(geojson) ? geojson : geojson.features,
8413 for (i = 0, len = features.length; i < len; i++) {
8414 // only add this if geometry or geometries are set and not null
8415 feature = features[i];
8416 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
8417 this.addData(feature);
8423 var options = this.options;
8425 if (options.filter && !options.filter(geojson)) { return this; }
8427 var layer = geometryToLayer(geojson, options);
8431 layer.feature = asFeature(geojson);
8433 layer.defaultOptions = layer.options;
8434 this.resetStyle(layer);
8436 if (options.onEachFeature) {
8437 options.onEachFeature(geojson, layer);
8440 return this.addLayer(layer);
8443 // @method resetStyle( <Path> layer ): this
8444 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
8445 resetStyle: function (layer) {
8446 // reset any custom styles
8447 layer.options = extend({}, layer.defaultOptions);
8448 this._setLayerStyle(layer, this.options.style);
8452 // @method setStyle( <Function> style ): this
8453 // Changes styles of GeoJSON vector layers with the given style function.
8454 setStyle: function (style) {
8455 return this.eachLayer(function (layer) {
8456 this._setLayerStyle(layer, style);
8460 _setLayerStyle: function (layer, style) {
8461 if (typeof style === 'function') {
8462 style = style(layer.feature);
8464 if (layer.setStyle) {
8465 layer.setStyle(style);
8471 // There are several static functions which can be called without instantiating L.GeoJSON:
8473 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
8474 // Creates a `Layer` from a given GeoJSON feature. Can use a custom
8475 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
8476 // functions if provided as options.
8477 function geometryToLayer(geojson, options) {
8479 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
8480 coords = geometry ? geometry.coordinates : null,
8482 pointToLayer = options && options.pointToLayer,
8483 _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
8484 latlng, latlngs, i, len;
8486 if (!coords && !geometry) {
8490 switch (geometry.type) {
8492 latlng = _coordsToLatLng(coords);
8493 return pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng);
8496 for (i = 0, len = coords.length; i < len; i++) {
8497 latlng = _coordsToLatLng(coords[i]);
8498 layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng));
8500 return new FeatureGroup(layers);
8503 case 'MultiLineString':
8504 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
8505 return new Polyline(latlngs, options);
8508 case 'MultiPolygon':
8509 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
8510 return new Polygon(latlngs, options);
8512 case 'GeometryCollection':
8513 for (i = 0, len = geometry.geometries.length; i < len; i++) {
8514 var layer = geometryToLayer({
8515 geometry: geometry.geometries[i],
8517 properties: geojson.properties
8524 return new FeatureGroup(layers);
8527 throw new Error('Invalid GeoJSON object.');
8531 // @function coordsToLatLng(coords: Array): LatLng
8532 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
8533 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
8534 function coordsToLatLng(coords) {
8535 return new LatLng(coords[1], coords[0], coords[2]);
8538 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
8539 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
8540 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
8541 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
8542 function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
8545 for (var i = 0, len = coords.length, latlng; i < len; i++) {
8546 latlng = levelsDeep ?
8547 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
8548 (_coordsToLatLng || coordsToLatLng)(coords[i]);
8550 latlngs.push(latlng);
8556 // @function latLngToCoords(latlng: LatLng, precision?: Number): Array
8557 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
8558 function latLngToCoords(latlng, precision) {
8559 precision = typeof precision === 'number' ? precision : 6;
8560 return latlng.alt !== undefined ?
8561 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
8562 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
8565 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
8566 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
8567 // `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.
8568 function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
8571 for (var i = 0, len = latlngs.length; i < len; i++) {
8572 coords.push(levelsDeep ?
8573 latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) :
8574 latLngToCoords(latlngs[i], precision));
8577 if (!levelsDeep && closed) {
8578 coords.push(coords[0]);
8584 function getFeature(layer, newGeometry) {
8585 return layer.feature ?
8586 extend({}, layer.feature, {geometry: newGeometry}) :
8587 asFeature(newGeometry);
8590 // @function asFeature(geojson: Object): Object
8591 // Normalize GeoJSON geometries/features into GeoJSON features.
8592 function asFeature(geojson) {
8593 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
8604 var PointToGeoJSON = {
8605 toGeoJSON: function (precision) {
8606 return getFeature(this, {
8608 coordinates: latLngToCoords(this.getLatLng(), precision)
8613 // @namespace Marker
8614 // @method toGeoJSON(): Object
8615 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
8616 Marker.include(PointToGeoJSON);
8618 // @namespace CircleMarker
8619 // @method toGeoJSON(): Object
8620 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
8621 Circle.include(PointToGeoJSON);
8622 CircleMarker.include(PointToGeoJSON);
8625 // @namespace Polyline
8626 // @method toGeoJSON(): Object
8627 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
8629 toGeoJSON: function (precision) {
8630 var multi = !isFlat(this._latlngs);
8632 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
8634 return getFeature(this, {
8635 type: (multi ? 'Multi' : '') + 'LineString',
8641 // @namespace Polygon
8642 // @method toGeoJSON(): Object
8643 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
8645 toGeoJSON: function (precision) {
8646 var holes = !isFlat(this._latlngs),
8647 multi = holes && !isFlat(this._latlngs[0]);
8649 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
8655 return getFeature(this, {
8656 type: (multi ? 'Multi' : '') + 'Polygon',
8663 // @namespace LayerGroup
8664 LayerGroup.include({
8665 toMultiPoint: function (precision) {
8668 this.eachLayer(function (layer) {
8669 coords.push(layer.toGeoJSON(precision).geometry.coordinates);
8672 return getFeature(this, {
8678 // @method toGeoJSON(): Object
8679 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
8680 toGeoJSON: function (precision) {
8682 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
8684 if (type === 'MultiPoint') {
8685 return this.toMultiPoint(precision);
8688 var isGeometryCollection = type === 'GeometryCollection',
8691 this.eachLayer(function (layer) {
8692 if (layer.toGeoJSON) {
8693 var json = layer.toGeoJSON(precision);
8694 if (isGeometryCollection) {
8695 jsons.push(json.geometry);
8697 var feature = asFeature(json);
8698 // Squash nested feature collections
8699 if (feature.type === 'FeatureCollection') {
8700 jsons.push.apply(jsons, feature.features);
8702 jsons.push(feature);
8708 if (isGeometryCollection) {
8709 return getFeature(this, {
8711 type: 'GeometryCollection'
8716 type: 'FeatureCollection',
8722 // @namespace GeoJSON
8723 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
8724 // Creates a GeoJSON layer. Optionally accepts an object in
8725 // [GeoJSON format](http://geojson.org/geojson-spec.html) to display on the map
8726 // (you can alternatively add it later with `addData` method) and an `options` object.
8727 function geoJSON(geojson, options) {
8728 return new GeoJSON(geojson, options);
8731 // Backward compatibility.
8732 var geoJson = geoJSON;
8735 * @class ImageOverlay
8736 * @aka L.ImageOverlay
8737 * @inherits Interactive layer
8739 * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
8744 * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
8745 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
8746 * L.imageOverlay(imageUrl, imageBounds).addTo(map);
8750 var ImageOverlay = Layer.extend({
8753 // @aka ImageOverlay options
8755 // @option opacity: Number = 1.0
8756 // The opacity of the image overlay.
8759 // @option alt: String = ''
8760 // Text for the `alt` attribute of the image (useful for accessibility).
8763 // @option interactive: Boolean = false
8764 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
8767 // @option crossOrigin: Boolean = false
8768 // If true, the image will have its crossOrigin attribute set to ''. This is needed if you want to access image pixel data.
8771 // @option errorOverlayUrl: String = ''
8772 // URL to the overlay image to show in place of the overlay that failed to load.
8773 errorOverlayUrl: '',
8775 // @option zIndex: Number = 1
8776 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the tile layer.
8779 // @option className: String = ''
8780 // A custom class name to assign to the image. Empty by default.
8784 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
8786 this._bounds = toLatLngBounds(bounds);
8788 setOptions(this, options);
8791 onAdd: function () {
8795 if (this.options.opacity < 1) {
8796 this._updateOpacity();
8800 if (this.options.interactive) {
8801 addClass(this._image, 'leaflet-interactive');
8802 this.addInteractiveTarget(this._image);
8805 this.getPane().appendChild(this._image);
8809 onRemove: function () {
8810 remove(this._image);
8811 if (this.options.interactive) {
8812 this.removeInteractiveTarget(this._image);
8816 // @method setOpacity(opacity: Number): this
8817 // Sets the opacity of the overlay.
8818 setOpacity: function (opacity) {
8819 this.options.opacity = opacity;
8822 this._updateOpacity();
8827 setStyle: function (styleOpts) {
8828 if (styleOpts.opacity) {
8829 this.setOpacity(styleOpts.opacity);
8834 // @method bringToFront(): this
8835 // Brings the layer to the top of all overlays.
8836 bringToFront: function () {
8838 toFront(this._image);
8843 // @method bringToBack(): this
8844 // Brings the layer to the bottom of all overlays.
8845 bringToBack: function () {
8847 toBack(this._image);
8852 // @method setUrl(url: String): this
8853 // Changes the URL of the image.
8854 setUrl: function (url) {
8858 this._image.src = url;
8863 // @method setBounds(bounds: LatLngBounds): this
8864 // Update the bounds that this ImageOverlay covers
8865 setBounds: function (bounds) {
8866 this._bounds = toLatLngBounds(bounds);
8874 getEvents: function () {
8877 viewreset: this._reset
8880 if (this._zoomAnimated) {
8881 events.zoomanim = this._animateZoom;
8887 // @method: setZIndex(value: Number) : this
8888 // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
8889 setZIndex: function (value) {
8890 this.options.zIndex = value;
8891 this._updateZIndex();
8895 // @method getBounds(): LatLngBounds
8896 // Get the bounds that this ImageOverlay covers
8897 getBounds: function () {
8898 return this._bounds;
8901 // @method getElement(): HTMLElement
8902 // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
8903 // used by this overlay.
8904 getElement: function () {
8908 _initImage: function () {
8909 var img = this._image = create$1('img',
8910 'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : '') +
8911 (this.options.className || ''));
8913 img.onselectstart = falseFn;
8914 img.onmousemove = falseFn;
8916 // @event load: Event
8917 // Fired when the ImageOverlay layer has loaded its image
8918 img.onload = bind(this.fire, this, 'load');
8919 img.onerror = bind(this._overlayOnError, this, 'error');
8921 if (this.options.crossOrigin) {
8922 img.crossOrigin = '';
8925 if (this.options.zIndex) {
8926 this._updateZIndex();
8929 img.src = this._url;
8930 img.alt = this.options.alt;
8933 _animateZoom: function (e) {
8934 var scale = this._map.getZoomScale(e.zoom),
8935 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
8937 setTransform(this._image, offset, scale);
8940 _reset: function () {
8941 var image = this._image,
8942 bounds = new Bounds(
8943 this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
8944 this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
8945 size = bounds.getSize();
8947 setPosition(image, bounds.min);
8949 image.style.width = size.x + 'px';
8950 image.style.height = size.y + 'px';
8953 _updateOpacity: function () {
8954 setOpacity(this._image, this.options.opacity);
8957 _updateZIndex: function () {
8958 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
8959 this._image.style.zIndex = this.options.zIndex;
8963 _overlayOnError: function () {
8964 // @event error: Event
8965 // Fired when the ImageOverlay layer has loaded its image
8968 var errorUrl = this.options.errorOverlayUrl;
8969 if (errorUrl && this._url !== errorUrl) {
8970 this._url = errorUrl;
8971 this._image.src = errorUrl;
8976 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
8977 // Instantiates an image overlay object given the URL of the image and the
8978 // geographical bounds it is tied to.
8979 var imageOverlay = function (url, bounds, options) {
8980 return new ImageOverlay(url, bounds, options);
8984 * @class VideoOverlay
8985 * @aka L.VideoOverlay
8986 * @inherits ImageOverlay
8988 * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
8990 * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
8996 * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
8997 * videoBounds = [[ 32, -130], [ 13, -100]];
8998 * L.VideoOverlay(videoUrl, videoBounds ).addTo(map);
9002 var VideoOverlay = ImageOverlay.extend({
9005 // @aka VideoOverlay options
9007 // @option autoplay: Boolean = true
9008 // Whether the video starts playing automatically when loaded.
9011 // @option loop: Boolean = true
9012 // Whether the video will loop back to the beginning when played.
9016 _initImage: function () {
9017 var wasElementSupplied = this._url.tagName === 'VIDEO';
9018 var vid = this._image = wasElementSupplied ? this._url : create$1('video');
9020 vid.class = vid.class || '';
9021 vid.class += 'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : '');
9023 vid.onselectstart = falseFn;
9024 vid.onmousemove = falseFn;
9026 // @event load: Event
9027 // Fired when the video has finished loading the first frame
9028 vid.onloadeddata = bind(this.fire, this, 'load');
9030 if (wasElementSupplied) { return; }
9032 if (!isArray(this._url)) { this._url = [this._url]; }
9034 vid.autoplay = !!this.options.autoplay;
9035 vid.loop = !!this.options.loop;
9036 for (var i = 0; i < this._url.length; i++) {
9037 var source = create$1('source');
9038 source.src = this._url[i];
9039 vid.appendChild(source);
9043 // @method getElement(): HTMLVideoElement
9044 // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
9045 // used by this overlay.
9049 // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
9050 // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
9051 // geographical bounds it is tied to.
9053 function videoOverlay(video, bounds, options) {
9054 return new VideoOverlay(video, bounds, options);
9061 * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
9064 // @namespace DivOverlay
9065 var DivOverlay = Layer.extend({
9068 // @aka DivOverlay options
9070 // @option offset: Point = Point(0, 7)
9071 // The offset of the popup position. Useful to control the anchor
9072 // of the popup when opening it on some overlays.
9075 // @option className: String = ''
9076 // A custom CSS class name to assign to the popup.
9079 // @option pane: String = 'popupPane'
9080 // `Map pane` where the popup will be added.
9084 initialize: function (options, source) {
9085 setOptions(this, options);
9087 this._source = source;
9090 onAdd: function (map) {
9091 this._zoomAnimated = map._zoomAnimated;
9093 if (!this._container) {
9097 if (map._fadeAnimated) {
9098 setOpacity(this._container, 0);
9101 clearTimeout(this._removeTimeout);
9102 this.getPane().appendChild(this._container);
9105 if (map._fadeAnimated) {
9106 setOpacity(this._container, 1);
9109 this.bringToFront();
9112 onRemove: function (map) {
9113 if (map._fadeAnimated) {
9114 setOpacity(this._container, 0);
9115 this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
9117 remove(this._container);
9122 // @method getLatLng: LatLng
9123 // Returns the geographical point of popup.
9124 getLatLng: function () {
9125 return this._latlng;
9128 // @method setLatLng(latlng: LatLng): this
9129 // Sets the geographical point where the popup will open.
9130 setLatLng: function (latlng) {
9131 this._latlng = toLatLng(latlng);
9133 this._updatePosition();
9139 // @method getContent: String|HTMLElement
9140 // Returns the content of the popup.
9141 getContent: function () {
9142 return this._content;
9145 // @method setContent(htmlContent: String|HTMLElement|Function): this
9146 // 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.
9147 setContent: function (content) {
9148 this._content = content;
9153 // @method getElement: String|HTMLElement
9154 // Alias for [getContent()](#popup-getcontent)
9155 getElement: function () {
9156 return this._container;
9159 // @method update: null
9160 // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
9161 update: function () {
9162 if (!this._map) { return; }
9164 this._container.style.visibility = 'hidden';
9166 this._updateContent();
9167 this._updateLayout();
9168 this._updatePosition();
9170 this._container.style.visibility = '';
9175 getEvents: function () {
9177 zoom: this._updatePosition,
9178 viewreset: this._updatePosition
9181 if (this._zoomAnimated) {
9182 events.zoomanim = this._animateZoom;
9187 // @method isOpen: Boolean
9188 // Returns `true` when the popup is visible on the map.
9189 isOpen: function () {
9190 return !!this._map && this._map.hasLayer(this);
9193 // @method bringToFront: this
9194 // Brings this popup in front of other popups (in the same map pane).
9195 bringToFront: function () {
9197 toFront(this._container);
9202 // @method bringToBack: this
9203 // Brings this popup to the back of other popups (in the same map pane).
9204 bringToBack: function () {
9206 toBack(this._container);
9211 _updateContent: function () {
9212 if (!this._content) { return; }
9214 var node = this._contentNode;
9215 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
9217 if (typeof content === 'string') {
9218 node.innerHTML = content;
9220 while (node.hasChildNodes()) {
9221 node.removeChild(node.firstChild);
9223 node.appendChild(content);
9225 this.fire('contentupdate');
9228 _updatePosition: function () {
9229 if (!this._map) { return; }
9231 var pos = this._map.latLngToLayerPoint(this._latlng),
9232 offset = toPoint(this.options.offset),
9233 anchor = this._getAnchor();
9235 if (this._zoomAnimated) {
9236 setPosition(this._container, pos.add(anchor));
9238 offset = offset.add(pos).add(anchor);
9241 var bottom = this._containerBottom = -offset.y,
9242 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
9244 // bottom position the popup in case the height of the popup changes (images loading etc)
9245 this._container.style.bottom = bottom + 'px';
9246 this._container.style.left = left + 'px';
9249 _getAnchor: function () {
9257 * @inherits DivOverlay
9259 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
9260 * open popups while making sure that only one popup is open at one time
9261 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
9265 * If you want to just bind a popup to marker click and then open it, it's really easy:
9268 * marker.bindPopup(popupContent).openPopup();
9270 * Path overlays like polylines also have a `bindPopup` method.
9271 * Here's a more complicated way to open a popup on a map:
9274 * var popup = L.popup()
9275 * .setLatLng(latlng)
9276 * .setContent('<p>Hello world!<br />This is a nice popup.</p>')
9283 var Popup = DivOverlay.extend({
9286 // @aka Popup options
9288 // @option maxWidth: Number = 300
9289 // Max width of the popup, in pixels.
9292 // @option minWidth: Number = 50
9293 // Min width of the popup, in pixels.
9296 // @option maxHeight: Number = null
9297 // If set, creates a scrollable container of the given height
9298 // inside a popup if its content exceeds it.
9301 // @option autoPan: Boolean = true
9302 // Set it to `false` if you don't want the map to do panning animation
9303 // to fit the opened popup.
9306 // @option autoPanPaddingTopLeft: Point = null
9307 // The margin between the popup and the top left corner of the map
9308 // view after autopanning was performed.
9309 autoPanPaddingTopLeft: null,
9311 // @option autoPanPaddingBottomRight: Point = null
9312 // The margin between the popup and the bottom right corner of the map
9313 // view after autopanning was performed.
9314 autoPanPaddingBottomRight: null,
9316 // @option autoPanPadding: Point = Point(5, 5)
9317 // Equivalent of setting both top left and bottom right autopan padding to the same value.
9318 autoPanPadding: [5, 5],
9320 // @option keepInView: Boolean = false
9321 // Set it to `true` if you want to prevent users from panning the popup
9322 // off of the screen while it is open.
9325 // @option closeButton: Boolean = true
9326 // Controls the presence of a close button in the popup.
9329 // @option autoClose: Boolean = true
9330 // Set it to `false` if you want to override the default behavior of
9331 // the popup closing when another popup is opened.
9334 // @option closeOnClick: Boolean = *
9335 // Set it if you want to override the default behavior of the popup closing when user clicks
9336 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
9338 // @option className: String = ''
9339 // A custom CSS class name to assign to the popup.
9344 // @method openOn(map: Map): this
9345 // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
9346 openOn: function (map) {
9347 map.openPopup(this);
9351 onAdd: function (map) {
9352 DivOverlay.prototype.onAdd.call(this, map);
9355 // @section Popup events
9356 // @event popupopen: PopupEvent
9357 // Fired when a popup is opened in the map
9358 map.fire('popupopen', {popup: this});
9362 // @section Popup events
9363 // @event popupopen: PopupEvent
9364 // Fired when a popup bound to this layer is opened
9365 this._source.fire('popupopen', {popup: this}, true);
9366 // For non-path layers, we toggle the popup when clicking
9367 // again the layer, so prevent the map to reopen it.
9368 if (!(this._source instanceof Path)) {
9369 this._source.on('preclick', stopPropagation);
9374 onRemove: function (map) {
9375 DivOverlay.prototype.onRemove.call(this, map);
9378 // @section Popup events
9379 // @event popupclose: PopupEvent
9380 // Fired when a popup in the map is closed
9381 map.fire('popupclose', {popup: this});
9385 // @section Popup events
9386 // @event popupclose: PopupEvent
9387 // Fired when a popup bound to this layer is closed
9388 this._source.fire('popupclose', {popup: this}, true);
9389 if (!(this._source instanceof Path)) {
9390 this._source.off('preclick', stopPropagation);
9395 getEvents: function () {
9396 var events = DivOverlay.prototype.getEvents.call(this);
9398 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
9399 events.preclick = this._close;
9402 if (this.options.keepInView) {
9403 events.moveend = this._adjustPan;
9409 _close: function () {
9411 this._map.closePopup(this);
9415 _initLayout: function () {
9416 var prefix = 'leaflet-popup',
9417 container = this._container = create$1('div',
9418 prefix + ' ' + (this.options.className || '') +
9419 ' leaflet-zoom-animated');
9421 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
9422 this._contentNode = create$1('div', prefix + '-content', wrapper);
9424 disableClickPropagation(wrapper);
9425 disableScrollPropagation(this._contentNode);
9426 on(wrapper, 'contextmenu', stopPropagation);
9428 this._tipContainer = create$1('div', prefix + '-tip-container', container);
9429 this._tip = create$1('div', prefix + '-tip', this._tipContainer);
9431 if (this.options.closeButton) {
9432 var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
9433 closeButton.href = '#close';
9434 closeButton.innerHTML = '×';
9436 on(closeButton, 'click', this._onCloseButtonClick, this);
9440 _updateLayout: function () {
9441 var container = this._contentNode,
9442 style = container.style;
9445 style.whiteSpace = 'nowrap';
9447 var width = container.offsetWidth;
9448 width = Math.min(width, this.options.maxWidth);
9449 width = Math.max(width, this.options.minWidth);
9451 style.width = (width + 1) + 'px';
9452 style.whiteSpace = '';
9456 var height = container.offsetHeight,
9457 maxHeight = this.options.maxHeight,
9458 scrolledClass = 'leaflet-popup-scrolled';
9460 if (maxHeight && height > maxHeight) {
9461 style.height = maxHeight + 'px';
9462 addClass(container, scrolledClass);
9464 removeClass(container, scrolledClass);
9467 this._containerWidth = this._container.offsetWidth;
9470 _animateZoom: function (e) {
9471 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
9472 anchor = this._getAnchor();
9473 setPosition(this._container, pos.add(anchor));
9476 _adjustPan: function () {
9477 if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; }
9479 var map = this._map,
9480 marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
9481 containerHeight = this._container.offsetHeight + marginBottom,
9482 containerWidth = this._containerWidth,
9483 layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
9485 layerPos._add(getPosition(this._container));
9487 var containerPos = map.layerPointToContainerPoint(layerPos),
9488 padding = toPoint(this.options.autoPanPadding),
9489 paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
9490 paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
9491 size = map.getSize(),
9495 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
9496 dx = containerPos.x + containerWidth - size.x + paddingBR.x;
9498 if (containerPos.x - dx - paddingTL.x < 0) { // left
9499 dx = containerPos.x - paddingTL.x;
9501 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
9502 dy = containerPos.y + containerHeight - size.y + paddingBR.y;
9504 if (containerPos.y - dy - paddingTL.y < 0) { // top
9505 dy = containerPos.y - paddingTL.y;
9509 // @section Popup events
9510 // @event autopanstart: Event
9511 // Fired when the map starts autopanning when opening a popup.
9514 .fire('autopanstart')
9519 _onCloseButtonClick: function (e) {
9524 _getAnchor: function () {
9525 // Where should we anchor the popup on the source layer?
9526 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
9532 // @factory L.popup(options?: Popup options, source?: Layer)
9533 // 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.
9534 var popup = function (options, source) {
9535 return new Popup(options, source);
9540 * @section Interaction Options
9541 * @option closePopupOnClick: Boolean = true
9542 * Set it to `false` if you don't want popups to close when user clicks the map.
9545 closePopupOnClick: true
9550 // @section Methods for Layers and Controls
9552 // @method openPopup(popup: Popup): this
9553 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
9555 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
9556 // Creates a popup with the specified content and options and opens it in the given point on a map.
9557 openPopup: function (popup, latlng, options) {
9558 if (!(popup instanceof Popup)) {
9559 popup = new Popup(options).setContent(popup);
9563 popup.setLatLng(latlng);
9566 if (this.hasLayer(popup)) {
9570 if (this._popup && this._popup.options.autoClose) {
9574 this._popup = popup;
9575 return this.addLayer(popup);
9578 // @method closePopup(popup?: Popup): this
9579 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
9580 closePopup: function (popup) {
9581 if (!popup || popup === this._popup) {
9582 popup = this._popup;
9586 this.removeLayer(popup);
9594 * @section Popup methods example
9596 * All layers share a set of methods convenient for binding popups to it.
9599 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
9600 * layer.openPopup();
9601 * layer.closePopup();
9604 * 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.
9607 // @section Popup methods
9610 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
9611 // Binds a popup to the layer with the passed `content` and sets up the
9612 // necessary event listeners. If a `Function` is passed it will receive
9613 // the layer as the first argument and should return a `String` or `HTMLElement`.
9614 bindPopup: function (content, options) {
9616 if (content instanceof Popup) {
9617 setOptions(content, options);
9618 this._popup = content;
9619 content._source = this;
9621 if (!this._popup || options) {
9622 this._popup = new Popup(options, this);
9624 this._popup.setContent(content);
9627 if (!this._popupHandlersAdded) {
9629 click: this._openPopup,
9630 keypress: this._onKeyPress,
9631 remove: this.closePopup,
9632 move: this._movePopup
9634 this._popupHandlersAdded = true;
9640 // @method unbindPopup(): this
9641 // Removes the popup previously bound with `bindPopup`.
9642 unbindPopup: function () {
9645 click: this._openPopup,
9646 keypress: this._onKeyPress,
9647 remove: this.closePopup,
9648 move: this._movePopup
9650 this._popupHandlersAdded = false;
9656 // @method openPopup(latlng?: LatLng): this
9657 // Opens the bound popup at the specificed `latlng` or at the default popup anchor if no `latlng` is passed.
9658 openPopup: function (layer, latlng) {
9659 if (!(layer instanceof Layer)) {
9664 if (layer instanceof FeatureGroup) {
9665 for (var id in this._layers) {
9666 layer = this._layers[id];
9672 latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
9675 if (this._popup && this._map) {
9676 // set popup source to this layer
9677 this._popup._source = layer;
9679 // update the popup (content, layout, ect...)
9680 this._popup.update();
9682 // open the popup on the map
9683 this._map.openPopup(this._popup, latlng);
9689 // @method closePopup(): this
9690 // Closes the popup bound to this layer if it is open.
9691 closePopup: function () {
9693 this._popup._close();
9698 // @method togglePopup(): this
9699 // Opens or closes the popup bound to this layer depending on its current state.
9700 togglePopup: function (target) {
9702 if (this._popup._map) {
9705 this.openPopup(target);
9711 // @method isPopupOpen(): boolean
9712 // Returns `true` if the popup bound to this layer is currently open.
9713 isPopupOpen: function () {
9714 return (this._popup ? this._popup.isOpen() : false);
9717 // @method setPopupContent(content: String|HTMLElement|Popup): this
9718 // Sets the content of the popup bound to this layer.
9719 setPopupContent: function (content) {
9721 this._popup.setContent(content);
9726 // @method getPopup(): Popup
9727 // Returns the popup bound to this layer.
9728 getPopup: function () {
9732 _openPopup: function (e) {
9733 var layer = e.layer || e.target;
9743 // prevent map click
9746 // if this inherits from Path its a vector and we can just
9747 // open the popup at the new location
9748 if (layer instanceof Path) {
9749 this.openPopup(e.layer || e.target, e.latlng);
9753 // otherwise treat it like a marker and figure out
9754 // if we should toggle it open/closed
9755 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
9758 this.openPopup(layer, e.latlng);
9762 _movePopup: function (e) {
9763 this._popup.setLatLng(e.latlng);
9766 _onKeyPress: function (e) {
9767 if (e.originalEvent.keyCode === 13) {
9775 * @inherits DivOverlay
9777 * Used to display small texts on top of map layers.
9782 * marker.bindTooltip("my tooltip text").openTooltip();
9784 * Note about tooltip offset. Leaflet takes two options in consideration
9785 * for computing tooltip offseting:
9786 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
9787 * Add a positive x offset to move the tooltip to the right, and a positive y offset to
9788 * move it to the bottom. Negatives will move to the left and top.
9789 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
9790 * should adapt this value if you use a custom icon.
9794 // @namespace Tooltip
9795 var Tooltip = DivOverlay.extend({
9798 // @aka Tooltip options
9800 // @option pane: String = 'tooltipPane'
9801 // `Map pane` where the tooltip will be added.
9802 pane: 'tooltipPane',
9804 // @option offset: Point = Point(0, 0)
9805 // Optional offset of the tooltip position.
9808 // @option direction: String = 'auto'
9809 // Direction where to open the tooltip. Possible values are: `right`, `left`,
9810 // `top`, `bottom`, `center`, `auto`.
9811 // `auto` will dynamicaly switch between `right` and `left` according to the tooltip
9812 // position on the map.
9815 // @option permanent: Boolean = false
9816 // Whether to open the tooltip permanently or only on mouseover.
9819 // @option sticky: Boolean = false
9820 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
9823 // @option interactive: Boolean = false
9824 // If true, the tooltip will listen to the feature events.
9827 // @option opacity: Number = 0.9
9828 // Tooltip container opacity.
9832 onAdd: function (map) {
9833 DivOverlay.prototype.onAdd.call(this, map);
9834 this.setOpacity(this.options.opacity);
9837 // @section Tooltip events
9838 // @event tooltipopen: TooltipEvent
9839 // Fired when a tooltip is opened in the map.
9840 map.fire('tooltipopen', {tooltip: this});
9844 // @section Tooltip events
9845 // @event tooltipopen: TooltipEvent
9846 // Fired when a tooltip bound to this layer is opened.
9847 this._source.fire('tooltipopen', {tooltip: this}, true);
9851 onRemove: function (map) {
9852 DivOverlay.prototype.onRemove.call(this, map);
9855 // @section Tooltip events
9856 // @event tooltipclose: TooltipEvent
9857 // Fired when a tooltip in the map is closed.
9858 map.fire('tooltipclose', {tooltip: this});
9862 // @section Tooltip events
9863 // @event tooltipclose: TooltipEvent
9864 // Fired when a tooltip bound to this layer is closed.
9865 this._source.fire('tooltipclose', {tooltip: this}, true);
9869 getEvents: function () {
9870 var events = DivOverlay.prototype.getEvents.call(this);
9872 if (touch && !this.options.permanent) {
9873 events.preclick = this._close;
9879 _close: function () {
9881 this._map.closeTooltip(this);
9885 _initLayout: function () {
9886 var prefix = 'leaflet-tooltip',
9887 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
9889 this._contentNode = this._container = create$1('div', className);
9892 _updateLayout: function () {},
9894 _adjustPan: function () {},
9896 _setPosition: function (pos) {
9897 var map = this._map,
9898 container = this._container,
9899 centerPoint = map.latLngToContainerPoint(map.getCenter()),
9900 tooltipPoint = map.layerPointToContainerPoint(pos),
9901 direction = this.options.direction,
9902 tooltipWidth = container.offsetWidth,
9903 tooltipHeight = container.offsetHeight,
9904 offset = toPoint(this.options.offset),
9905 anchor = this._getAnchor();
9907 if (direction === 'top') {
9908 pos = pos.add(toPoint(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true));
9909 } else if (direction === 'bottom') {
9910 pos = pos.subtract(toPoint(tooltipWidth / 2 - offset.x, -offset.y, true));
9911 } else if (direction === 'center') {
9912 pos = pos.subtract(toPoint(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true));
9913 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
9914 direction = 'right';
9915 pos = pos.add(toPoint(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true));
9918 pos = pos.subtract(toPoint(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true));
9921 removeClass(container, 'leaflet-tooltip-right');
9922 removeClass(container, 'leaflet-tooltip-left');
9923 removeClass(container, 'leaflet-tooltip-top');
9924 removeClass(container, 'leaflet-tooltip-bottom');
9925 addClass(container, 'leaflet-tooltip-' + direction);
9926 setPosition(container, pos);
9929 _updatePosition: function () {
9930 var pos = this._map.latLngToLayerPoint(this._latlng);
9931 this._setPosition(pos);
9934 setOpacity: function (opacity) {
9935 this.options.opacity = opacity;
9937 if (this._container) {
9938 setOpacity(this._container, opacity);
9942 _animateZoom: function (e) {
9943 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
9944 this._setPosition(pos);
9947 _getAnchor: function () {
9948 // Where should we anchor the tooltip on the source layer?
9949 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
9954 // @namespace Tooltip
9955 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
9956 // 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.
9957 var tooltip = function (options, source) {
9958 return new Tooltip(options, source);
9962 // @section Methods for Layers and Controls
9965 // @method openTooltip(tooltip: Tooltip): this
9966 // Opens the specified tooltip.
9968 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
9969 // Creates a tooltip with the specified content and options and open it.
9970 openTooltip: function (tooltip, latlng, options) {
9971 if (!(tooltip instanceof Tooltip)) {
9972 tooltip = new Tooltip(options).setContent(tooltip);
9976 tooltip.setLatLng(latlng);
9979 if (this.hasLayer(tooltip)) {
9983 return this.addLayer(tooltip);
9986 // @method closeTooltip(tooltip?: Tooltip): this
9987 // Closes the tooltip given as parameter.
9988 closeTooltip: function (tooltip) {
9990 this.removeLayer(tooltip);
9999 * @section Tooltip methods example
10001 * All layers share a set of methods convenient for binding tooltips to it.
10004 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
10005 * layer.openTooltip();
10006 * layer.closeTooltip();
10010 // @section Tooltip methods
10013 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
10014 // Binds a tooltip to the layer with the passed `content` and sets up the
10015 // necessary event listeners. If a `Function` is passed it will receive
10016 // the layer as the first argument and should return a `String` or `HTMLElement`.
10017 bindTooltip: function (content, options) {
10019 if (content instanceof Tooltip) {
10020 setOptions(content, options);
10021 this._tooltip = content;
10022 content._source = this;
10024 if (!this._tooltip || options) {
10025 this._tooltip = new Tooltip(options, this);
10027 this._tooltip.setContent(content);
10031 this._initTooltipInteractions();
10033 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
10034 this.openTooltip();
10040 // @method unbindTooltip(): this
10041 // Removes the tooltip previously bound with `bindTooltip`.
10042 unbindTooltip: function () {
10043 if (this._tooltip) {
10044 this._initTooltipInteractions(true);
10045 this.closeTooltip();
10046 this._tooltip = null;
10051 _initTooltipInteractions: function (remove$$1) {
10052 if (!remove$$1 && this._tooltipHandlersAdded) { return; }
10053 var onOff = remove$$1 ? 'off' : 'on',
10055 remove: this.closeTooltip,
10056 move: this._moveTooltip
10058 if (!this._tooltip.options.permanent) {
10059 events.mouseover = this._openTooltip;
10060 events.mouseout = this.closeTooltip;
10061 if (this._tooltip.options.sticky) {
10062 events.mousemove = this._moveTooltip;
10065 events.click = this._openTooltip;
10068 events.add = this._openTooltip;
10070 this[onOff](events);
10071 this._tooltipHandlersAdded = !remove$$1;
10074 // @method openTooltip(latlng?: LatLng): this
10075 // Opens the bound tooltip at the specificed `latlng` or at the default tooltip anchor if no `latlng` is passed.
10076 openTooltip: function (layer, latlng) {
10077 if (!(layer instanceof Layer)) {
10082 if (layer instanceof FeatureGroup) {
10083 for (var id in this._layers) {
10084 layer = this._layers[id];
10090 latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
10093 if (this._tooltip && this._map) {
10095 // set tooltip source to this layer
10096 this._tooltip._source = layer;
10098 // update the tooltip (content, layout, ect...)
10099 this._tooltip.update();
10101 // open the tooltip on the map
10102 this._map.openTooltip(this._tooltip, latlng);
10104 // Tooltip container may not be defined if not permanent and never
10106 if (this._tooltip.options.interactive && this._tooltip._container) {
10107 addClass(this._tooltip._container, 'leaflet-clickable');
10108 this.addInteractiveTarget(this._tooltip._container);
10115 // @method closeTooltip(): this
10116 // Closes the tooltip bound to this layer if it is open.
10117 closeTooltip: function () {
10118 if (this._tooltip) {
10119 this._tooltip._close();
10120 if (this._tooltip.options.interactive && this._tooltip._container) {
10121 removeClass(this._tooltip._container, 'leaflet-clickable');
10122 this.removeInteractiveTarget(this._tooltip._container);
10128 // @method toggleTooltip(): this
10129 // Opens or closes the tooltip bound to this layer depending on its current state.
10130 toggleTooltip: function (target) {
10131 if (this._tooltip) {
10132 if (this._tooltip._map) {
10133 this.closeTooltip();
10135 this.openTooltip(target);
10141 // @method isTooltipOpen(): boolean
10142 // Returns `true` if the tooltip bound to this layer is currently open.
10143 isTooltipOpen: function () {
10144 return this._tooltip.isOpen();
10147 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10148 // Sets the content of the tooltip bound to this layer.
10149 setTooltipContent: function (content) {
10150 if (this._tooltip) {
10151 this._tooltip.setContent(content);
10156 // @method getTooltip(): Tooltip
10157 // Returns the tooltip bound to this layer.
10158 getTooltip: function () {
10159 return this._tooltip;
10162 _openTooltip: function (e) {
10163 var layer = e.layer || e.target;
10165 if (!this._tooltip || !this._map) {
10168 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
10171 _moveTooltip: function (e) {
10172 var latlng = e.latlng, containerPoint, layerPoint;
10173 if (this._tooltip.options.sticky && e.originalEvent) {
10174 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
10175 layerPoint = this._map.containerPointToLayerPoint(containerPoint);
10176 latlng = this._map.layerPointToLatLng(layerPoint);
10178 this._tooltip.setLatLng(latlng);
10187 * Represents a lightweight icon for markers that uses a simple `<div>`
10188 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
10192 * var myIcon = L.divIcon({className: 'my-div-icon'});
10193 * // you can set .my-div-icon styles in CSS
10195 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
10198 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
10201 var DivIcon = Icon.extend({
10204 // @aka DivIcon options
10205 iconSize: [12, 12], // also can be set through CSS
10207 // iconAnchor: (Point),
10208 // popupAnchor: (Point),
10210 // @option html: String = ''
10211 // Custom HTML code to put inside the div element, empty by default.
10214 // @option bgPos: Point = [0, 0]
10215 // Optional relative position of the background, in pixels
10218 className: 'leaflet-div-icon'
10221 createIcon: function (oldIcon) {
10222 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
10223 options = this.options;
10225 div.innerHTML = options.html !== false ? options.html : '';
10227 if (options.bgPos) {
10228 var bgPos = toPoint(options.bgPos);
10229 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
10231 this._setIconStyles(div, 'icon');
10236 createShadow: function () {
10241 // @factory L.divIcon(options: DivIcon options)
10242 // Creates a `DivIcon` instance with the given options.
10243 function divIcon(options) {
10244 return new DivIcon(options);
10247 Icon.Default = IconDefault;
10254 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
10255 * 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.
10258 * @section Synchronous usage
10261 * 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.
10264 * var CanvasLayer = L.GridLayer.extend({
10265 * createTile: function(coords){
10266 * // create a <canvas> element for drawing
10267 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10269 * // setup tile width and height according to the options
10270 * var size = this.getTileSize();
10271 * tile.width = size.x;
10272 * tile.height = size.y;
10274 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z
10275 * var ctx = tile.getContext('2d');
10277 * // return the tile so it can be rendered on screen
10283 * @section Asynchronous usage
10286 * 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.
10289 * var CanvasLayer = L.GridLayer.extend({
10290 * createTile: function(coords, done){
10293 * // create a <canvas> element for drawing
10294 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10296 * // setup tile width and height according to the options
10297 * var size = this.getTileSize();
10298 * tile.width = size.x;
10299 * tile.height = size.y;
10301 * // draw something asynchronously and pass the tile to the done() callback
10302 * setTimeout(function() {
10303 * done(error, tile);
10315 var GridLayer = Layer.extend({
10318 // @aka GridLayer options
10320 // @option tileSize: Number|Point = 256
10321 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
10324 // @option opacity: Number = 1.0
10325 // Opacity of the tiles. Can be used in the `createTile()` function.
10328 // @option updateWhenIdle: Boolean = (depends)
10329 // Load new tiles only when panning ends.
10330 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
10331 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
10332 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
10333 updateWhenIdle: mobile,
10335 // @option updateWhenZooming: Boolean = true
10336 // 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.
10337 updateWhenZooming: true,
10339 // @option updateInterval: Number = 200
10340 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
10341 updateInterval: 200,
10343 // @option zIndex: Number = 1
10344 // The explicit zIndex of the tile layer.
10347 // @option bounds: LatLngBounds = undefined
10348 // If set, tiles will only be loaded inside the set `LatLngBounds`.
10351 // @option minZoom: Number = 0
10352 // The minimum zoom level down to which this layer will be displayed (inclusive).
10355 // @option maxZoom: Number = undefined
10356 // The maximum zoom level up to which this layer will be displayed (inclusive).
10357 maxZoom: undefined,
10359 // @option maxNativeZoom: Number = undefined
10360 // Maximum zoom number the tile source has available. If it is specified,
10361 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
10362 // from `maxNativeZoom` level and auto-scaled.
10363 maxNativeZoom: undefined,
10365 // @option minNativeZoom: Number = undefined
10366 // Minimum zoom number the tile source has available. If it is specified,
10367 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
10368 // from `minNativeZoom` level and auto-scaled.
10369 minNativeZoom: undefined,
10371 // @option noWrap: Boolean = false
10372 // Whether the layer is wrapped around the antimeridian. If `true`, the
10373 // GridLayer will only be displayed once at low zoom levels. Has no
10374 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
10375 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
10376 // tiles outside the CRS limits.
10379 // @option pane: String = 'tilePane'
10380 // `Map pane` where the grid layer will be added.
10383 // @option className: String = ''
10384 // A custom class name to assign to the tile layer. Empty by default.
10387 // @option keepBuffer: Number = 2
10388 // When panning the map, keep this many rows and columns of tiles before unloading them.
10392 initialize: function (options) {
10393 setOptions(this, options);
10396 onAdd: function () {
10397 this._initContainer();
10406 beforeAdd: function (map) {
10407 map._addZoomLimit(this);
10410 onRemove: function (map) {
10411 this._removeAllTiles();
10412 remove(this._container);
10413 map._removeZoomLimit(this);
10414 this._container = null;
10415 this._tileZoom = null;
10418 // @method bringToFront: this
10419 // Brings the tile layer to the top of all tile layers.
10420 bringToFront: function () {
10422 toFront(this._container);
10423 this._setAutoZIndex(Math.max);
10428 // @method bringToBack: this
10429 // Brings the tile layer to the bottom of all tile layers.
10430 bringToBack: function () {
10432 toBack(this._container);
10433 this._setAutoZIndex(Math.min);
10438 // @method getContainer: HTMLElement
10439 // Returns the HTML element that contains the tiles for this layer.
10440 getContainer: function () {
10441 return this._container;
10444 // @method setOpacity(opacity: Number): this
10445 // Changes the [opacity](#gridlayer-opacity) of the grid layer.
10446 setOpacity: function (opacity) {
10447 this.options.opacity = opacity;
10448 this._updateOpacity();
10452 // @method setZIndex(zIndex: Number): this
10453 // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
10454 setZIndex: function (zIndex) {
10455 this.options.zIndex = zIndex;
10456 this._updateZIndex();
10461 // @method isLoading: Boolean
10462 // Returns `true` if any tile in the grid layer has not finished loading.
10463 isLoading: function () {
10464 return this._loading;
10467 // @method redraw: this
10468 // Causes the layer to clear all the tiles and request them again.
10469 redraw: function () {
10471 this._removeAllTiles();
10477 getEvents: function () {
10479 viewprereset: this._invalidateAll,
10480 viewreset: this._resetView,
10481 zoom: this._resetView,
10482 moveend: this._onMoveEnd
10485 if (!this.options.updateWhenIdle) {
10486 // update tiles on move, but not more often than once per given interval
10487 if (!this._onMove) {
10488 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
10491 events.move = this._onMove;
10494 if (this._zoomAnimated) {
10495 events.zoomanim = this._animateZoom;
10501 // @section Extension methods
10502 // Layers extending `GridLayer` shall reimplement the following method.
10503 // @method createTile(coords: Object, done?: Function): HTMLElement
10504 // Called only internally, must be overriden by classes extending `GridLayer`.
10505 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
10506 // is specified, it must be called when the tile has finished loading and drawing.
10507 createTile: function () {
10508 return document.createElement('div');
10512 // @method getTileSize: Point
10513 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
10514 getTileSize: function () {
10515 var s = this.options.tileSize;
10516 return s instanceof Point ? s : new Point(s, s);
10519 _updateZIndex: function () {
10520 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
10521 this._container.style.zIndex = this.options.zIndex;
10525 _setAutoZIndex: function (compare) {
10526 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
10528 var layers = this.getPane().children,
10529 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
10531 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
10533 zIndex = layers[i].style.zIndex;
10535 if (layers[i] !== this._container && zIndex) {
10536 edgeZIndex = compare(edgeZIndex, +zIndex);
10540 if (isFinite(edgeZIndex)) {
10541 this.options.zIndex = edgeZIndex + compare(-1, 1);
10542 this._updateZIndex();
10546 _updateOpacity: function () {
10547 if (!this._map) { return; }
10549 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
10550 if (ielt9) { return; }
10552 setOpacity(this._container, this.options.opacity);
10554 var now = +new Date(),
10558 for (var key in this._tiles) {
10559 var tile = this._tiles[key];
10560 if (!tile.current || !tile.loaded) { continue; }
10562 var fade = Math.min(1, (now - tile.loaded) / 200);
10564 setOpacity(tile.el, fade);
10571 this._onOpaqueTile(tile);
10573 tile.active = true;
10577 if (willPrune && !this._noPrune) { this._pruneTiles(); }
10580 cancelAnimFrame(this._fadeFrame);
10581 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
10585 _onOpaqueTile: falseFn,
10587 _initContainer: function () {
10588 if (this._container) { return; }
10590 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
10591 this._updateZIndex();
10593 if (this.options.opacity < 1) {
10594 this._updateOpacity();
10597 this.getPane().appendChild(this._container);
10600 _updateLevels: function () {
10602 var zoom = this._tileZoom,
10603 maxZoom = this.options.maxZoom;
10605 if (zoom === undefined) { return undefined; }
10607 for (var z in this._levels) {
10608 if (this._levels[z].el.children.length || z === zoom) {
10609 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
10610 this._onUpdateLevel(z);
10612 remove(this._levels[z].el);
10613 this._removeTilesAtZoom(z);
10614 this._onRemoveLevel(z);
10615 delete this._levels[z];
10619 var level = this._levels[zoom],
10623 level = this._levels[zoom] = {};
10625 level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
10626 level.el.style.zIndex = maxZoom;
10628 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
10631 this._setZoomTransform(level, map.getCenter(), map.getZoom());
10633 // force the browser to consider the newly added element for transition
10634 falseFn(level.el.offsetWidth);
10636 this._onCreateLevel(level);
10639 this._level = level;
10644 _onUpdateLevel: falseFn,
10646 _onRemoveLevel: falseFn,
10648 _onCreateLevel: falseFn,
10650 _pruneTiles: function () {
10657 var zoom = this._map.getZoom();
10658 if (zoom > this.options.maxZoom ||
10659 zoom < this.options.minZoom) {
10660 this._removeAllTiles();
10664 for (key in this._tiles) {
10665 tile = this._tiles[key];
10666 tile.retain = tile.current;
10669 for (key in this._tiles) {
10670 tile = this._tiles[key];
10671 if (tile.current && !tile.active) {
10672 var coords = tile.coords;
10673 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
10674 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
10679 for (key in this._tiles) {
10680 if (!this._tiles[key].retain) {
10681 this._removeTile(key);
10686 _removeTilesAtZoom: function (zoom) {
10687 for (var key in this._tiles) {
10688 if (this._tiles[key].coords.z !== zoom) {
10691 this._removeTile(key);
10695 _removeAllTiles: function () {
10696 for (var key in this._tiles) {
10697 this._removeTile(key);
10701 _invalidateAll: function () {
10702 for (var z in this._levels) {
10703 remove(this._levels[z].el);
10704 this._onRemoveLevel(z);
10705 delete this._levels[z];
10707 this._removeAllTiles();
10709 this._tileZoom = null;
10712 _retainParent: function (x, y, z, minZoom) {
10713 var x2 = Math.floor(x / 2),
10714 y2 = Math.floor(y / 2),
10716 coords2 = new Point(+x2, +y2);
10719 var key = this._tileCoordsToKey(coords2),
10720 tile = this._tiles[key];
10722 if (tile && tile.active) {
10723 tile.retain = true;
10726 } else if (tile && tile.loaded) {
10727 tile.retain = true;
10730 if (z2 > minZoom) {
10731 return this._retainParent(x2, y2, z2, minZoom);
10737 _retainChildren: function (x, y, z, maxZoom) {
10739 for (var i = 2 * x; i < 2 * x + 2; i++) {
10740 for (var j = 2 * y; j < 2 * y + 2; j++) {
10742 var coords = new Point(i, j);
10745 var key = this._tileCoordsToKey(coords),
10746 tile = this._tiles[key];
10748 if (tile && tile.active) {
10749 tile.retain = true;
10752 } else if (tile && tile.loaded) {
10753 tile.retain = true;
10756 if (z + 1 < maxZoom) {
10757 this._retainChildren(i, j, z + 1, maxZoom);
10763 _resetView: function (e) {
10764 var animating = e && (e.pinch || e.flyTo);
10765 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
10768 _animateZoom: function (e) {
10769 this._setView(e.center, e.zoom, true, e.noUpdate);
10772 _clampZoom: function (zoom) {
10773 var options = this.options;
10775 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
10776 return options.minNativeZoom;
10779 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
10780 return options.maxNativeZoom;
10786 _setView: function (center, zoom, noPrune, noUpdate) {
10787 var tileZoom = this._clampZoom(Math.round(zoom));
10788 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
10789 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
10790 tileZoom = undefined;
10793 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
10795 if (!noUpdate || tileZoomChanged) {
10797 this._tileZoom = tileZoom;
10799 if (this._abortLoading) {
10800 this._abortLoading();
10803 this._updateLevels();
10806 if (tileZoom !== undefined) {
10807 this._update(center);
10811 this._pruneTiles();
10814 // Flag to prevent _updateOpacity from pruning tiles during
10815 // a zoom anim or a pinch gesture
10816 this._noPrune = !!noPrune;
10819 this._setZoomTransforms(center, zoom);
10822 _setZoomTransforms: function (center, zoom) {
10823 for (var i in this._levels) {
10824 this._setZoomTransform(this._levels[i], center, zoom);
10828 _setZoomTransform: function (level, center, zoom) {
10829 var scale = this._map.getZoomScale(zoom, level.zoom),
10830 translate = level.origin.multiplyBy(scale)
10831 .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
10834 setTransform(level.el, translate, scale);
10836 setPosition(level.el, translate);
10840 _resetGrid: function () {
10841 var map = this._map,
10842 crs = map.options.crs,
10843 tileSize = this._tileSize = this.getTileSize(),
10844 tileZoom = this._tileZoom;
10846 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
10848 this._globalTileRange = this._pxBoundsToTileRange(bounds);
10851 this._wrapX = crs.wrapLng && !this.options.noWrap && [
10852 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
10853 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
10855 this._wrapY = crs.wrapLat && !this.options.noWrap && [
10856 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
10857 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
10861 _onMoveEnd: function () {
10862 if (!this._map || this._map._animatingZoom) { return; }
10867 _getTiledPixelBounds: function (center) {
10868 var map = this._map,
10869 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
10870 scale = map.getZoomScale(mapZoom, this._tileZoom),
10871 pixelCenter = map.project(center, this._tileZoom).floor(),
10872 halfSize = map.getSize().divideBy(scale * 2);
10874 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
10877 // Private method to load tiles in the grid's active zoom level according to map bounds
10878 _update: function (center) {
10879 var map = this._map;
10880 if (!map) { return; }
10881 var zoom = this._clampZoom(map.getZoom());
10883 if (center === undefined) { center = map.getCenter(); }
10884 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom
10886 var pixelBounds = this._getTiledPixelBounds(center),
10887 tileRange = this._pxBoundsToTileRange(pixelBounds),
10888 tileCenter = tileRange.getCenter(),
10890 margin = this.options.keepBuffer,
10891 noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
10892 tileRange.getTopRight().add([margin, -margin]));
10894 // Sanity check: panic if the tile range contains Infinity somewhere.
10895 if (!(isFinite(tileRange.min.x) &&
10896 isFinite(tileRange.min.y) &&
10897 isFinite(tileRange.max.x) &&
10898 isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
10900 for (var key in this._tiles) {
10901 var c = this._tiles[key].coords;
10902 if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
10903 this._tiles[key].current = false;
10907 // _update just loads more tiles. If the tile zoom level differs too much
10908 // from the map's, let _setView reset levels and prune old tiles.
10909 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
10911 // create a queue of coordinates to load tiles from
10912 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
10913 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
10914 var coords = new Point(i, j);
10915 coords.z = this._tileZoom;
10917 if (!this._isValidTile(coords)) { continue; }
10919 if (!this._tiles[this._tileCoordsToKey(coords)]) {
10920 queue.push(coords);
10925 // sort tile queue to load tiles in order of their distance to center
10926 queue.sort(function (a, b) {
10927 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
10930 if (queue.length !== 0) {
10931 // if it's the first batch of tiles to load
10932 if (!this._loading) {
10933 this._loading = true;
10934 // @event loading: Event
10935 // Fired when the grid layer starts loading tiles.
10936 this.fire('loading');
10939 // create DOM fragment to append tiles in one batch
10940 var fragment = document.createDocumentFragment();
10942 for (i = 0; i < queue.length; i++) {
10943 this._addTile(queue[i], fragment);
10946 this._level.el.appendChild(fragment);
10950 _isValidTile: function (coords) {
10951 var crs = this._map.options.crs;
10953 if (!crs.infinite) {
10954 // don't load tile if it's out of bounds and not wrapped
10955 var bounds = this._globalTileRange;
10956 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
10957 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
10960 if (!this.options.bounds) { return true; }
10962 // don't load tile if it doesn't intersect the bounds in options
10963 var tileBounds = this._tileCoordsToBounds(coords);
10964 return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
10967 _keyToBounds: function (key) {
10968 return this._tileCoordsToBounds(this._keyToTileCoords(key));
10971 // converts tile coordinates to its geographical bounds
10972 _tileCoordsToBounds: function (coords) {
10974 var map = this._map,
10975 tileSize = this.getTileSize(),
10977 nwPoint = coords.scaleBy(tileSize),
10978 sePoint = nwPoint.add(tileSize),
10980 nw = map.unproject(nwPoint, coords.z),
10981 se = map.unproject(sePoint, coords.z),
10982 bounds = new LatLngBounds(nw, se);
10984 if (!this.options.noWrap) {
10985 map.wrapLatLngBounds(bounds);
10991 // converts tile coordinates to key for the tile cache
10992 _tileCoordsToKey: function (coords) {
10993 return coords.x + ':' + coords.y + ':' + coords.z;
10996 // converts tile cache key to coordinates
10997 _keyToTileCoords: function (key) {
10998 var k = key.split(':'),
10999 coords = new Point(+k[0], +k[1]);
11004 _removeTile: function (key) {
11005 var tile = this._tiles[key];
11006 if (!tile) { return; }
11010 delete this._tiles[key];
11012 // @event tileunload: TileEvent
11013 // Fired when a tile is removed (e.g. when a tile goes off the screen).
11014 this.fire('tileunload', {
11016 coords: this._keyToTileCoords(key)
11020 _initTile: function (tile) {
11021 addClass(tile, 'leaflet-tile');
11023 var tileSize = this.getTileSize();
11024 tile.style.width = tileSize.x + 'px';
11025 tile.style.height = tileSize.y + 'px';
11027 tile.onselectstart = falseFn;
11028 tile.onmousemove = falseFn;
11030 // update opacity on tiles in IE7-8 because of filter inheritance problems
11031 if (ielt9 && this.options.opacity < 1) {
11032 setOpacity(tile, this.options.opacity);
11035 // without this hack, tiles disappear after zoom on Chrome for Android
11036 // https://github.com/Leaflet/Leaflet/issues/2078
11037 if (android && !android23) {
11038 tile.style.WebkitBackfaceVisibility = 'hidden';
11042 _addTile: function (coords, container) {
11043 var tilePos = this._getTilePos(coords),
11044 key = this._tileCoordsToKey(coords);
11046 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
11048 this._initTile(tile);
11050 // if createTile is defined with a second argument ("done" callback),
11051 // we know that tile is async and will be ready later; otherwise
11052 if (this.createTile.length < 2) {
11053 // mark tile as ready, but delay one frame for opacity animation to happen
11054 requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
11057 setPosition(tile, tilePos);
11059 // save tile in cache
11060 this._tiles[key] = {
11066 container.appendChild(tile);
11067 // @event tileloadstart: TileEvent
11068 // Fired when a tile is requested and starts loading.
11069 this.fire('tileloadstart', {
11075 _tileReady: function (coords, err, tile) {
11076 if (!this._map) { return; }
11079 // @event tileerror: TileErrorEvent
11080 // Fired when there is an error loading a tile.
11081 this.fire('tileerror', {
11088 var key = this._tileCoordsToKey(coords);
11090 tile = this._tiles[key];
11091 if (!tile) { return; }
11093 tile.loaded = +new Date();
11094 if (this._map._fadeAnimated) {
11095 setOpacity(tile.el, 0);
11096 cancelAnimFrame(this._fadeFrame);
11097 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11099 tile.active = true;
11100 this._pruneTiles();
11104 addClass(tile.el, 'leaflet-tile-loaded');
11106 // @event tileload: TileEvent
11107 // Fired when a tile loads.
11108 this.fire('tileload', {
11114 if (this._noTilesToLoad()) {
11115 this._loading = false;
11116 // @event load: Event
11117 // Fired when the grid layer loaded all visible tiles.
11120 if (ielt9 || !this._map._fadeAnimated) {
11121 requestAnimFrame(this._pruneTiles, this);
11123 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
11124 // to trigger a pruning.
11125 setTimeout(bind(this._pruneTiles, this), 250);
11130 _getTilePos: function (coords) {
11131 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
11134 _wrapCoords: function (coords) {
11135 var newCoords = new Point(
11136 this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
11137 this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
11138 newCoords.z = coords.z;
11142 _pxBoundsToTileRange: function (bounds) {
11143 var tileSize = this.getTileSize();
11145 bounds.min.unscaleBy(tileSize).floor(),
11146 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
11149 _noTilesToLoad: function () {
11150 for (var key in this._tiles) {
11151 if (!this._tiles[key].loaded) { return false; }
11157 // @factory L.gridLayer(options?: GridLayer options)
11158 // Creates a new instance of GridLayer with the supplied options.
11159 function gridLayer(options) {
11160 return new GridLayer(options);
11165 * @inherits GridLayer
11167 * Used to load and display tile layers on the map. Extends `GridLayer`.
11172 * L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map);
11175 * @section URL template
11178 * A string of the following form:
11181 * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
11184 * `{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.
11186 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
11189 * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
11194 var TileLayer = GridLayer.extend({
11197 // @aka TileLayer options
11199 // @option minZoom: Number = 0
11200 // The minimum zoom level down to which this layer will be displayed (inclusive).
11203 // @option maxZoom: Number = 18
11204 // The maximum zoom level up to which this layer will be displayed (inclusive).
11207 // @option subdomains: String|String[] = 'abc'
11208 // 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.
11211 // @option errorTileUrl: String = ''
11212 // URL to the tile image to show in place of the tile that failed to load.
11215 // @option zoomOffset: Number = 0
11216 // The zoom number used in tile URLs will be offset with this value.
11219 // @option tms: Boolean = false
11220 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
11223 // @option zoomReverse: Boolean = false
11224 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
11225 zoomReverse: false,
11227 // @option detectRetina: Boolean = false
11228 // 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.
11229 detectRetina: false,
11231 // @option crossOrigin: Boolean = false
11232 // If true, all tiles will have their crossOrigin attribute set to ''. This is needed if you want to access tile pixel data.
11236 initialize: function (url, options) {
11240 options = setOptions(this, options);
11242 // detecting retina displays, adjusting tileSize and zoom levels
11243 if (options.detectRetina && retina && options.maxZoom > 0) {
11245 options.tileSize = Math.floor(options.tileSize / 2);
11247 if (!options.zoomReverse) {
11248 options.zoomOffset++;
11251 options.zoomOffset--;
11255 options.minZoom = Math.max(0, options.minZoom);
11258 if (typeof options.subdomains === 'string') {
11259 options.subdomains = options.subdomains.split('');
11262 // for https://github.com/Leaflet/Leaflet/issues/137
11264 this.on('tileunload', this._onTileRemove);
11268 // @method setUrl(url: String, noRedraw?: Boolean): this
11269 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
11270 setUrl: function (url, noRedraw) {
11279 // @method createTile(coords: Object, done?: Function): HTMLElement
11280 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
11281 // to return an `<img>` HTML element with the appropiate image URL given `coords`. The `done`
11282 // callback is called when the tile has been loaded.
11283 createTile: function (coords, done) {
11284 var tile = document.createElement('img');
11286 on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
11287 on(tile, 'error', bind(this._tileOnError, this, done, tile));
11289 if (this.options.crossOrigin) {
11290 tile.crossOrigin = '';
11294 Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
11295 http://www.w3.org/TR/WCAG20-TECHS/H67
11300 Set role="presentation" to force screen readers to ignore this
11301 https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
11303 tile.setAttribute('role', 'presentation');
11305 tile.src = this.getTileUrl(coords);
11310 // @section Extension methods
11312 // Layers extending `TileLayer` might reimplement the following method.
11313 // @method getTileUrl(coords: Object): String
11314 // Called only internally, returns the URL for a tile given its coordinates.
11315 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
11316 getTileUrl: function (coords) {
11318 r: retina ? '@2x' : '',
11319 s: this._getSubdomain(coords),
11322 z: this._getZoomForUrl()
11324 if (this._map && !this._map.options.crs.infinite) {
11325 var invertedY = this._globalTileRange.max.y - coords.y;
11326 if (this.options.tms) {
11327 data['y'] = invertedY;
11329 data['-y'] = invertedY;
11332 return template(this._url, extend(data, this.options));
11335 _tileOnLoad: function (done, tile) {
11336 // For https://github.com/Leaflet/Leaflet/issues/3332
11338 setTimeout(bind(done, this, null, tile), 0);
11344 _tileOnError: function (done, tile, e) {
11345 var errorUrl = this.options.errorTileUrl;
11346 if (errorUrl && tile.src !== errorUrl) {
11347 tile.src = errorUrl;
11352 _onTileRemove: function (e) {
11353 e.tile.onload = null;
11356 _getZoomForUrl: function () {
11357 var zoom = this._tileZoom,
11358 maxZoom = this.options.maxZoom,
11359 zoomReverse = this.options.zoomReverse,
11360 zoomOffset = this.options.zoomOffset;
11363 zoom = maxZoom - zoom;
11366 return zoom + zoomOffset;
11369 _getSubdomain: function (tilePoint) {
11370 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
11371 return this.options.subdomains[index];
11374 // stops loading all tiles in the background layer
11375 _abortLoading: function () {
11377 for (i in this._tiles) {
11378 if (this._tiles[i].coords.z !== this._tileZoom) {
11379 tile = this._tiles[i].el;
11381 tile.onload = falseFn;
11382 tile.onerror = falseFn;
11384 if (!tile.complete) {
11385 tile.src = emptyImageUrl;
11394 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
11395 // Instantiates a tile layer object given a `URL template` and optionally an options object.
11397 function tileLayer(url, options) {
11398 return new TileLayer(url, options);
11402 * @class TileLayer.WMS
11403 * @inherits TileLayer
11404 * @aka L.TileLayer.WMS
11405 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
11410 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
11411 * layers: 'nexrad-n0r-900913',
11412 * format: 'image/png',
11413 * transparent: true,
11414 * attribution: "Weather data © 2012 IEM Nexrad"
11419 var TileLayerWMS = TileLayer.extend({
11422 // @aka TileLayer.WMS options
11423 // If any custom options not documented here are used, they will be sent to the
11424 // WMS server as extra parameters in each request URL. This can be useful for
11425 // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
11426 defaultWmsParams: {
11430 // @option layers: String = ''
11431 // **(required)** Comma-separated list of WMS layers to show.
11434 // @option styles: String = ''
11435 // Comma-separated list of WMS styles.
11438 // @option format: String = 'image/jpeg'
11439 // WMS image format (use `'image/png'` for layers with transparency).
11440 format: 'image/jpeg',
11442 // @option transparent: Boolean = false
11443 // If `true`, the WMS service will return images with transparency.
11444 transparent: false,
11446 // @option version: String = '1.1.1'
11447 // Version of the WMS service to use
11452 // @option crs: CRS = null
11453 // Coordinate Reference System to use for the WMS requests, defaults to
11454 // map CRS. Don't change this if you're not sure what it means.
11457 // @option uppercase: Boolean = false
11458 // If `true`, WMS request parameter keys will be uppercase.
11462 initialize: function (url, options) {
11466 var wmsParams = extend({}, this.defaultWmsParams);
11468 // all keys that are not TileLayer options go to WMS params
11469 for (var i in options) {
11470 if (!(i in this.options)) {
11471 wmsParams[i] = options[i];
11475 options = setOptions(this, options);
11477 wmsParams.width = wmsParams.height = options.tileSize * (options.detectRetina && retina ? 2 : 1);
11479 this.wmsParams = wmsParams;
11482 onAdd: function (map) {
11484 this._crs = this.options.crs || map.options.crs;
11485 this._wmsVersion = parseFloat(this.wmsParams.version);
11487 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
11488 this.wmsParams[projectionKey] = this._crs.code;
11490 TileLayer.prototype.onAdd.call(this, map);
11493 getTileUrl: function (coords) {
11495 var tileBounds = this._tileCoordsToBounds(coords),
11496 nw = this._crs.project(tileBounds.getNorthWest()),
11497 se = this._crs.project(tileBounds.getSouthEast()),
11499 bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
11500 [se.y, nw.x, nw.y, se.x] :
11501 [nw.x, se.y, se.x, nw.y]).join(','),
11503 url = TileLayer.prototype.getTileUrl.call(this, coords);
11506 getParamString(this.wmsParams, url, this.options.uppercase) +
11507 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
11510 // @method setParams(params: Object, noRedraw?: Boolean): this
11511 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
11512 setParams: function (params, noRedraw) {
11514 extend(this.wmsParams, params);
11525 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
11526 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
11527 function tileLayerWMS(url, options) {
11528 return new TileLayerWMS(url, options);
11531 TileLayer.WMS = TileLayerWMS;
11532 tileLayer.wms = tileLayerWMS;
11539 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
11540 * DOM container of the renderer, its bounds, and its zoom animation.
11542 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
11543 * itself can be added or removed to the map. All paths use a renderer, which can
11544 * be implicit (the map will decide the type of renderer and use it automatically)
11545 * or explicit (using the [`renderer`](#path-renderer) option of the path).
11547 * Do not use this class directly, use `SVG` and `Canvas` instead.
11549 * @event update: Event
11550 * Fired when the renderer updates its bounds, center and zoom, for example when
11551 * its map has moved
11554 var Renderer = Layer.extend({
11557 // @aka Renderer options
11559 // @option padding: Number = 0.1
11560 // How much to extend the clip area around the map view (relative to its size)
11561 // e.g. 0.1 would be 10% of map view in each direction
11565 initialize: function (options) {
11566 setOptions(this, options);
11568 this._layers = this._layers || {};
11571 onAdd: function () {
11572 if (!this._container) {
11573 this._initContainer(); // defined by renderer implementations
11575 if (this._zoomAnimated) {
11576 addClass(this._container, 'leaflet-zoom-animated');
11580 this.getPane().appendChild(this._container);
11582 this.on('update', this._updatePaths, this);
11585 onRemove: function () {
11586 this.off('update', this._updatePaths, this);
11587 this._destroyContainer();
11590 getEvents: function () {
11592 viewreset: this._reset,
11593 zoom: this._onZoom,
11594 moveend: this._update,
11595 zoomend: this._onZoomEnd
11597 if (this._zoomAnimated) {
11598 events.zoomanim = this._onAnimZoom;
11603 _onAnimZoom: function (ev) {
11604 this._updateTransform(ev.center, ev.zoom);
11607 _onZoom: function () {
11608 this._updateTransform(this._map.getCenter(), this._map.getZoom());
11611 _updateTransform: function (center, zoom) {
11612 var scale = this._map.getZoomScale(zoom, this._zoom),
11613 position = getPosition(this._container),
11614 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
11615 currentCenterPoint = this._map.project(this._center, zoom),
11616 destCenterPoint = this._map.project(center, zoom),
11617 centerOffset = destCenterPoint.subtract(currentCenterPoint),
11619 topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
11622 setTransform(this._container, topLeftOffset, scale);
11624 setPosition(this._container, topLeftOffset);
11628 _reset: function () {
11630 this._updateTransform(this._center, this._zoom);
11632 for (var id in this._layers) {
11633 this._layers[id]._reset();
11637 _onZoomEnd: function () {
11638 for (var id in this._layers) {
11639 this._layers[id]._project();
11643 _updatePaths: function () {
11644 for (var id in this._layers) {
11645 this._layers[id]._update();
11649 _update: function () {
11650 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
11651 // Subclasses are responsible of firing the 'update' event.
11652 var p = this.options.padding,
11653 size = this._map.getSize(),
11654 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
11656 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
11658 this._center = this._map.getCenter();
11659 this._zoom = this._map.getZoom();
11665 * @inherits Renderer
11668 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
11669 * Inherits `Renderer`.
11671 * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
11672 * available in all web browsers, notably IE8, and overlapping geometries might
11673 * not display properly in some edge cases.
11677 * Use Canvas by default for all paths in the map:
11680 * var map = L.map('map', {
11681 * renderer: L.canvas()
11685 * Use a Canvas renderer with extra padding for specific vector geometries:
11688 * var map = L.map('map');
11689 * var myRenderer = L.canvas({ padding: 0.5 });
11690 * var line = L.polyline( coordinates, { renderer: myRenderer } );
11691 * var circle = L.circle( center, { renderer: myRenderer } );
11695 var Canvas = Renderer.extend({
11696 getEvents: function () {
11697 var events = Renderer.prototype.getEvents.call(this);
11698 events.viewprereset = this._onViewPreReset;
11702 _onViewPreReset: function () {
11703 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
11704 this._postponeUpdatePaths = true;
11707 onAdd: function () {
11708 Renderer.prototype.onAdd.call(this);
11710 // Redraw vectors since canvas is cleared upon removal,
11711 // in case of removing the renderer itself from the map.
11715 _initContainer: function () {
11716 var container = this._container = document.createElement('canvas');
11718 on(container, 'mousemove', throttle(this._onMouseMove, 32, this), this);
11719 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
11720 on(container, 'mouseout', this._handleMouseOut, this);
11722 this._ctx = container.getContext('2d');
11725 _destroyContainer: function () {
11727 remove(this._container);
11728 off(this._container);
11729 delete this._container;
11732 _updatePaths: function () {
11733 if (this._postponeUpdatePaths) { return; }
11736 this._redrawBounds = null;
11737 for (var id in this._layers) {
11738 layer = this._layers[id];
11744 _update: function () {
11745 if (this._map._animatingZoom && this._bounds) { return; }
11747 this._drawnLayers = {};
11749 Renderer.prototype._update.call(this);
11751 var b = this._bounds,
11752 container = this._container,
11753 size = b.getSize(),
11754 m = retina ? 2 : 1;
11756 setPosition(container, b.min);
11758 // set canvas size (also clearing it); use double size on retina
11759 container.width = m * size.x;
11760 container.height = m * size.y;
11761 container.style.width = size.x + 'px';
11762 container.style.height = size.y + 'px';
11765 this._ctx.scale(2, 2);
11768 // translate so we use the same path coordinates after canvas element moves
11769 this._ctx.translate(-b.min.x, -b.min.y);
11771 // Tell paths to redraw themselves
11772 this.fire('update');
11775 _reset: function () {
11776 Renderer.prototype._reset.call(this);
11778 if (this._postponeUpdatePaths) {
11779 this._postponeUpdatePaths = false;
11780 this._updatePaths();
11784 _initPath: function (layer) {
11785 this._updateDashArray(layer);
11786 this._layers[stamp(layer)] = layer;
11788 var order = layer._order = {
11790 prev: this._drawLast,
11793 if (this._drawLast) { this._drawLast.next = order; }
11794 this._drawLast = order;
11795 this._drawFirst = this._drawFirst || this._drawLast;
11798 _addPath: function (layer) {
11799 this._requestRedraw(layer);
11802 _removePath: function (layer) {
11803 var order = layer._order;
11804 var next = order.next;
11805 var prev = order.prev;
11810 this._drawLast = prev;
11815 this._drawFirst = next;
11818 delete layer._order;
11820 delete this._layers[L.stamp(layer)];
11822 this._requestRedraw(layer);
11825 _updatePath: function (layer) {
11826 // Redraw the union of the layer's old pixel
11827 // bounds and the new pixel bounds.
11828 this._extendRedrawBounds(layer);
11831 // The redraw will extend the redraw bounds
11832 // with the new pixel bounds.
11833 this._requestRedraw(layer);
11836 _updateStyle: function (layer) {
11837 this._updateDashArray(layer);
11838 this._requestRedraw(layer);
11841 _updateDashArray: function (layer) {
11842 if (layer.options.dashArray) {
11843 var parts = layer.options.dashArray.split(','),
11846 for (i = 0; i < parts.length; i++) {
11847 dashArray.push(Number(parts[i]));
11849 layer.options._dashArray = dashArray;
11853 _requestRedraw: function (layer) {
11854 if (!this._map) { return; }
11856 this._extendRedrawBounds(layer);
11857 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
11860 _extendRedrawBounds: function (layer) {
11861 if (layer._pxBounds) {
11862 var padding = (layer.options.weight || 0) + 1;
11863 this._redrawBounds = this._redrawBounds || new Bounds();
11864 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
11865 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
11869 _redraw: function () {
11870 this._redrawRequest = null;
11872 if (this._redrawBounds) {
11873 this._redrawBounds.min._floor();
11874 this._redrawBounds.max._ceil();
11877 this._clear(); // clear layers in redraw bounds
11878 this._draw(); // draw layers
11880 this._redrawBounds = null;
11883 _clear: function () {
11884 var bounds = this._redrawBounds;
11886 var size = bounds.getSize();
11887 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
11889 this._ctx.clearRect(0, 0, this._container.width, this._container.height);
11893 _draw: function () {
11894 var layer, bounds = this._redrawBounds;
11897 var size = bounds.getSize();
11898 this._ctx.beginPath();
11899 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
11903 this._drawing = true;
11905 for (var order = this._drawFirst; order; order = order.next) {
11906 layer = order.layer;
11907 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
11908 layer._updatePath();
11912 this._drawing = false;
11914 this._ctx.restore(); // Restore state before clipping.
11917 _updatePoly: function (layer, closed) {
11918 if (!this._drawing) { return; }
11921 parts = layer._parts,
11922 len = parts.length,
11925 if (!len) { return; }
11927 this._drawnLayers[layer._leaflet_id] = layer;
11931 for (i = 0; i < len; i++) {
11932 for (j = 0, len2 = parts[i].length; j < len2; j++) {
11934 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
11941 this._fillStroke(ctx, layer);
11943 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
11946 _updateCircle: function (layer) {
11948 if (!this._drawing || layer._empty()) { return; }
11950 var p = layer._point,
11953 s = (layer._radiusY || r) / r;
11955 this._drawnLayers[layer._leaflet_id] = layer;
11963 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
11969 this._fillStroke(ctx, layer);
11972 _fillStroke: function (ctx, layer) {
11973 var options = layer.options;
11975 if (options.fill) {
11976 ctx.globalAlpha = options.fillOpacity;
11977 ctx.fillStyle = options.fillColor || options.color;
11978 ctx.fill(options.fillRule || 'evenodd');
11981 if (options.stroke && options.weight !== 0) {
11982 if (ctx.setLineDash) {
11983 ctx.setLineDash(layer.options && layer.options._dashArray || []);
11985 ctx.globalAlpha = options.opacity;
11986 ctx.lineWidth = options.weight;
11987 ctx.strokeStyle = options.color;
11988 ctx.lineCap = options.lineCap;
11989 ctx.lineJoin = options.lineJoin;
11994 // Canvas obviously doesn't have mouse events for individual drawn objects,
11995 // so we emulate that by calculating what's under the mouse on mousemove/click manually
11997 _onClick: function (e) {
11998 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
12000 for (var order = this._drawFirst; order; order = order.next) {
12001 layer = order.layer;
12002 if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
12003 clickedLayer = layer;
12006 if (clickedLayer) {
12008 this._fireEvent([clickedLayer], e);
12012 _onMouseMove: function (e) {
12013 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
12015 var point = this._map.mouseEventToLayerPoint(e);
12016 this._handleMouseHover(e, point);
12020 _handleMouseOut: function (e) {
12021 var layer = this._hoveredLayer;
12023 // if we're leaving the layer, fire mouseout
12024 removeClass(this._container, 'leaflet-interactive');
12025 this._fireEvent([layer], e, 'mouseout');
12026 this._hoveredLayer = null;
12030 _handleMouseHover: function (e, point) {
12031 var layer, candidateHoveredLayer;
12033 for (var order = this._drawFirst; order; order = order.next) {
12034 layer = order.layer;
12035 if (layer.options.interactive && layer._containsPoint(point)) {
12036 candidateHoveredLayer = layer;
12040 if (candidateHoveredLayer !== this._hoveredLayer) {
12041 this._handleMouseOut(e);
12043 if (candidateHoveredLayer) {
12044 addClass(this._container, 'leaflet-interactive'); // change cursor
12045 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
12046 this._hoveredLayer = candidateHoveredLayer;
12050 if (this._hoveredLayer) {
12051 this._fireEvent([this._hoveredLayer], e);
12055 _fireEvent: function (layers, e, type) {
12056 this._map._fireDOMEvent(e, type || e.type, layers);
12059 _bringToFront: function (layer) {
12060 var order = layer._order;
12061 var next = order.next;
12062 var prev = order.prev;
12073 // Update first entry unless this is the
12075 this._drawFirst = next;
12078 order.prev = this._drawLast;
12079 this._drawLast.next = order;
12082 this._drawLast = order;
12084 this._requestRedraw(layer);
12087 _bringToBack: function (layer) {
12088 var order = layer._order;
12089 var next = order.next;
12090 var prev = order.prev;
12101 // Update last entry unless this is the
12103 this._drawLast = prev;
12108 order.next = this._drawFirst;
12109 this._drawFirst.prev = order;
12110 this._drawFirst = order;
12112 this._requestRedraw(layer);
12116 // @factory L.canvas(options?: Renderer options)
12117 // Creates a Canvas renderer with the given options.
12118 function canvas$1(options) {
12119 return canvas ? new Canvas(options) : null;
12123 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
12127 var vmlCreate = (function () {
12129 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
12130 return function (name) {
12131 return document.createElement('<lvml:' + name + ' class="lvml">');
12134 return function (name) {
12135 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
12144 * 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.
12146 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
12147 * with old versions of Internet Explorer.
12150 // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
12153 _initContainer: function () {
12154 this._container = create$1('div', 'leaflet-vml-container');
12157 _update: function () {
12158 if (this._map._animatingZoom) { return; }
12159 Renderer.prototype._update.call(this);
12160 this.fire('update');
12163 _initPath: function (layer) {
12164 var container = layer._container = vmlCreate('shape');
12166 addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
12168 container.coordsize = '1 1';
12170 layer._path = vmlCreate('path');
12171 container.appendChild(layer._path);
12173 this._updateStyle(layer);
12174 this._layers[stamp(layer)] = layer;
12177 _addPath: function (layer) {
12178 var container = layer._container;
12179 this._container.appendChild(container);
12181 if (layer.options.interactive) {
12182 layer.addInteractiveTarget(container);
12186 _removePath: function (layer) {
12187 var container = layer._container;
12189 layer.removeInteractiveTarget(container);
12190 delete this._layers[stamp(layer)];
12193 _updateStyle: function (layer) {
12194 var stroke = layer._stroke,
12195 fill = layer._fill,
12196 options = layer.options,
12197 container = layer._container;
12199 container.stroked = !!options.stroke;
12200 container.filled = !!options.fill;
12202 if (options.stroke) {
12204 stroke = layer._stroke = vmlCreate('stroke');
12206 container.appendChild(stroke);
12207 stroke.weight = options.weight + 'px';
12208 stroke.color = options.color;
12209 stroke.opacity = options.opacity;
12211 if (options.dashArray) {
12212 stroke.dashStyle = isArray(options.dashArray) ?
12213 options.dashArray.join(' ') :
12214 options.dashArray.replace(/( *, *)/g, ' ');
12216 stroke.dashStyle = '';
12218 stroke.endcap = options.lineCap.replace('butt', 'flat');
12219 stroke.joinstyle = options.lineJoin;
12221 } else if (stroke) {
12222 container.removeChild(stroke);
12223 layer._stroke = null;
12226 if (options.fill) {
12228 fill = layer._fill = vmlCreate('fill');
12230 container.appendChild(fill);
12231 fill.color = options.fillColor || options.color;
12232 fill.opacity = options.fillOpacity;
12235 container.removeChild(fill);
12236 layer._fill = null;
12240 _updateCircle: function (layer) {
12241 var p = layer._point.round(),
12242 r = Math.round(layer._radius),
12243 r2 = Math.round(layer._radiusY || r);
12245 this._setPath(layer, layer._empty() ? 'M0 0' :
12246 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
12249 _setPath: function (layer, path) {
12250 layer._path.v = path;
12253 _bringToFront: function (layer) {
12254 toFront(layer._container);
12257 _bringToBack: function (layer) {
12258 toBack(layer._container);
12262 var create$2 = vml ? vmlCreate : svgCreate;
12266 * @inherits Renderer
12269 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
12270 * Inherits `Renderer`.
12272 * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
12273 * available in all web browsers, notably Android 2.x and 3.x.
12275 * Although SVG is not available on IE7 and IE8, these browsers support
12276 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
12277 * (a now deprecated technology), and the SVG renderer will fall back to VML in
12282 * Use SVG by default for all paths in the map:
12285 * var map = L.map('map', {
12286 * renderer: L.svg()
12290 * Use a SVG renderer with extra padding for specific vector geometries:
12293 * var map = L.map('map');
12294 * var myRenderer = L.svg({ padding: 0.5 });
12295 * var line = L.polyline( coordinates, { renderer: myRenderer } );
12296 * var circle = L.circle( center, { renderer: myRenderer } );
12300 var SVG = Renderer.extend({
12302 getEvents: function () {
12303 var events = Renderer.prototype.getEvents.call(this);
12304 events.zoomstart = this._onZoomStart;
12308 _initContainer: function () {
12309 this._container = create$2('svg');
12311 // makes it possible to click through svg root; we'll reset it back in individual paths
12312 this._container.setAttribute('pointer-events', 'none');
12314 this._rootGroup = create$2('g');
12315 this._container.appendChild(this._rootGroup);
12318 _destroyContainer: function () {
12319 remove(this._container);
12320 off(this._container);
12321 delete this._container;
12322 delete this._rootGroup;
12325 _onZoomStart: function () {
12326 // Drag-then-pinch interactions might mess up the center and zoom.
12327 // In this case, the easiest way to prevent this is re-do the renderer
12328 // bounds and padding when the zooming starts.
12332 _update: function () {
12333 if (this._map._animatingZoom && this._bounds) { return; }
12335 Renderer.prototype._update.call(this);
12337 var b = this._bounds,
12338 size = b.getSize(),
12339 container = this._container;
12341 // set size of svg-container if changed
12342 if (!this._svgSize || !this._svgSize.equals(size)) {
12343 this._svgSize = size;
12344 container.setAttribute('width', size.x);
12345 container.setAttribute('height', size.y);
12348 // movement: update container viewBox so that we don't have to change coordinates of individual layers
12349 setPosition(container, b.min);
12350 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
12352 this.fire('update');
12355 // methods below are called by vector layers implementations
12357 _initPath: function (layer) {
12358 var path = layer._path = create$2('path');
12361 // @option className: String = null
12362 // Custom class name set on an element. Only for SVG renderer.
12363 if (layer.options.className) {
12364 addClass(path, layer.options.className);
12367 if (layer.options.interactive) {
12368 addClass(path, 'leaflet-interactive');
12371 this._updateStyle(layer);
12372 this._layers[stamp(layer)] = layer;
12375 _addPath: function (layer) {
12376 if (!this._rootGroup) { this._initContainer(); }
12377 this._rootGroup.appendChild(layer._path);
12378 layer.addInteractiveTarget(layer._path);
12381 _removePath: function (layer) {
12382 remove(layer._path);
12383 layer.removeInteractiveTarget(layer._path);
12384 delete this._layers[stamp(layer)];
12387 _updatePath: function (layer) {
12392 _updateStyle: function (layer) {
12393 var path = layer._path,
12394 options = layer.options;
12396 if (!path) { return; }
12398 if (options.stroke) {
12399 path.setAttribute('stroke', options.color);
12400 path.setAttribute('stroke-opacity', options.opacity);
12401 path.setAttribute('stroke-width', options.weight);
12402 path.setAttribute('stroke-linecap', options.lineCap);
12403 path.setAttribute('stroke-linejoin', options.lineJoin);
12405 if (options.dashArray) {
12406 path.setAttribute('stroke-dasharray', options.dashArray);
12408 path.removeAttribute('stroke-dasharray');
12411 if (options.dashOffset) {
12412 path.setAttribute('stroke-dashoffset', options.dashOffset);
12414 path.removeAttribute('stroke-dashoffset');
12417 path.setAttribute('stroke', 'none');
12420 if (options.fill) {
12421 path.setAttribute('fill', options.fillColor || options.color);
12422 path.setAttribute('fill-opacity', options.fillOpacity);
12423 path.setAttribute('fill-rule', options.fillRule || 'evenodd');
12425 path.setAttribute('fill', 'none');
12429 _updatePoly: function (layer, closed) {
12430 this._setPath(layer, pointsToPath(layer._parts, closed));
12433 _updateCircle: function (layer) {
12434 var p = layer._point,
12436 r2 = layer._radiusY || r,
12437 arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
12439 // drawing a circle with two half-arcs
12440 var d = layer._empty() ? 'M0 0' :
12441 'M' + (p.x - r) + ',' + p.y +
12442 arc + (r * 2) + ',0 ' +
12443 arc + (-r * 2) + ',0 ';
12445 this._setPath(layer, d);
12448 _setPath: function (layer, path) {
12449 layer._path.setAttribute('d', path);
12452 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
12453 _bringToFront: function (layer) {
12454 toFront(layer._path);
12457 _bringToBack: function (layer) {
12458 toBack(layer._path);
12463 SVG.include(vmlMixin);
12466 // @factory L.svg(options?: Renderer options)
12467 // Creates a SVG renderer with the given options.
12468 function svg$1(options) {
12469 return svg || vml ? new SVG(options) : null;
12473 // @namespace Map; @method getRenderer(layer: Path): Renderer
12474 // Returns the instance of `Renderer` that should be used to render the given
12475 // `Path`. It will ensure that the `renderer` options of the map and paths
12476 // are respected, and that the renderers do exist on the map.
12477 getRenderer: function (layer) {
12478 // @namespace Path; @option renderer: Renderer
12479 // Use this specific instance of `Renderer` for this path. Takes
12480 // precedence over the map's [default renderer](#map-renderer).
12481 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
12484 // @namespace Map; @option preferCanvas: Boolean = false
12485 // Whether `Path`s should be rendered on a `Canvas` renderer.
12486 // By default, all `Path`s are rendered in a `SVG` renderer.
12487 renderer = this._renderer = (this.options.preferCanvas && canvas$1()) || svg$1();
12490 if (!this.hasLayer(renderer)) {
12491 this.addLayer(renderer);
12496 _getPaneRenderer: function (name) {
12497 if (name === 'overlayPane' || name === undefined) {
12501 var renderer = this._paneRenderers[name];
12502 if (renderer === undefined) {
12503 renderer = (SVG && svg$1({pane: name})) || (Canvas && canvas$1({pane: name}));
12504 this._paneRenderers[name] = renderer;
12511 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
12517 * @inherits Polygon
12519 * A class for drawing rectangle overlays on a map. Extends `Polygon`.
12524 * // define rectangle geographical bounds
12525 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
12527 * // create an orange rectangle
12528 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
12530 * // zoom the map to the rectangle bounds
12531 * map.fitBounds(bounds);
12537 var Rectangle = Polygon.extend({
12538 initialize: function (latLngBounds, options) {
12539 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
12542 // @method setBounds(latLngBounds: LatLngBounds): this
12543 // Redraws the rectangle with the passed bounds.
12544 setBounds: function (latLngBounds) {
12545 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
12548 _boundsToLatLngs: function (latLngBounds) {
12549 latLngBounds = toLatLngBounds(latLngBounds);
12551 latLngBounds.getSouthWest(),
12552 latLngBounds.getNorthWest(),
12553 latLngBounds.getNorthEast(),
12554 latLngBounds.getSouthEast()
12560 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
12561 function rectangle(latLngBounds, options) {
12562 return new Rectangle(latLngBounds, options);
12565 SVG.create = create$2;
12566 SVG.pointsToPath = pointsToPath;
12568 GeoJSON.geometryToLayer = geometryToLayer;
12569 GeoJSON.coordsToLatLng = coordsToLatLng;
12570 GeoJSON.coordsToLatLngs = coordsToLatLngs;
12571 GeoJSON.latLngToCoords = latLngToCoords;
12572 GeoJSON.latLngsToCoords = latLngsToCoords;
12573 GeoJSON.getFeature = getFeature;
12574 GeoJSON.asFeature = asFeature;
12577 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
12578 * (zoom to a selected bounding box), enabled by default.
12582 // @section Interaction Options
12584 // @option boxZoom: Boolean = true
12585 // Whether the map can be zoomed to a rectangular area specified by
12586 // dragging the mouse while pressing the shift key.
12590 var BoxZoom = Handler.extend({
12591 initialize: function (map) {
12593 this._container = map._container;
12594 this._pane = map._panes.overlayPane;
12595 this._resetStateTimeout = 0;
12596 map.on('unload', this._destroy, this);
12599 addHooks: function () {
12600 on(this._container, 'mousedown', this._onMouseDown, this);
12603 removeHooks: function () {
12604 off(this._container, 'mousedown', this._onMouseDown, this);
12607 moved: function () {
12608 return this._moved;
12611 _destroy: function () {
12612 remove(this._pane);
12616 _resetState: function () {
12617 this._resetStateTimeout = 0;
12618 this._moved = false;
12621 _clearDeferredResetState: function () {
12622 if (this._resetStateTimeout !== 0) {
12623 clearTimeout(this._resetStateTimeout);
12624 this._resetStateTimeout = 0;
12628 _onMouseDown: function (e) {
12629 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
12631 // Clear the deferred resetState if it hasn't executed yet, otherwise it
12632 // will interrupt the interaction and orphan a box element in the container.
12633 this._clearDeferredResetState();
12634 this._resetState();
12636 disableTextSelection();
12637 disableImageDrag();
12639 this._startPoint = this._map.mouseEventToContainerPoint(e);
12643 mousemove: this._onMouseMove,
12644 mouseup: this._onMouseUp,
12645 keydown: this._onKeyDown
12649 _onMouseMove: function (e) {
12650 if (!this._moved) {
12651 this._moved = true;
12653 this._box = create$1('div', 'leaflet-zoom-box', this._container);
12654 addClass(this._container, 'leaflet-crosshair');
12656 this._map.fire('boxzoomstart');
12659 this._point = this._map.mouseEventToContainerPoint(e);
12661 var bounds = new Bounds(this._point, this._startPoint),
12662 size = bounds.getSize();
12664 setPosition(this._box, bounds.min);
12666 this._box.style.width = size.x + 'px';
12667 this._box.style.height = size.y + 'px';
12670 _finish: function () {
12673 removeClass(this._container, 'leaflet-crosshair');
12676 enableTextSelection();
12681 mousemove: this._onMouseMove,
12682 mouseup: this._onMouseUp,
12683 keydown: this._onKeyDown
12687 _onMouseUp: function (e) {
12688 if ((e.which !== 1) && (e.button !== 1)) { return; }
12692 if (!this._moved) { return; }
12693 // Postpone to next JS tick so internal click event handling
12694 // still see it as "moved".
12695 this._clearDeferredResetState();
12696 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
12698 var bounds = new LatLngBounds(
12699 this._map.containerPointToLatLng(this._startPoint),
12700 this._map.containerPointToLatLng(this._point));
12704 .fire('boxzoomend', {boxZoomBounds: bounds});
12707 _onKeyDown: function (e) {
12708 if (e.keyCode === 27) {
12714 // @section Handlers
12715 // @property boxZoom: Handler
12716 // Box (shift-drag with mouse) zoom handler.
12717 Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
12720 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
12724 // @section Interaction Options
12727 // @option doubleClickZoom: Boolean|String = true
12728 // Whether the map can be zoomed in by double clicking on it and
12729 // zoomed out by double clicking while holding shift. If passed
12730 // `'center'`, double-click zoom will zoom to the center of the
12731 // view regardless of where the mouse was.
12732 doubleClickZoom: true
12735 var DoubleClickZoom = Handler.extend({
12736 addHooks: function () {
12737 this._map.on('dblclick', this._onDoubleClick, this);
12740 removeHooks: function () {
12741 this._map.off('dblclick', this._onDoubleClick, this);
12744 _onDoubleClick: function (e) {
12745 var map = this._map,
12746 oldZoom = map.getZoom(),
12747 delta = map.options.zoomDelta,
12748 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
12750 if (map.options.doubleClickZoom === 'center') {
12753 map.setZoomAround(e.containerPoint, zoom);
12758 // @section Handlers
12760 // Map properties include interaction handlers that allow you to control
12761 // interaction behavior in runtime, enabling or disabling certain features such
12762 // as dragging or touch zoom (see `Handler` methods). For example:
12765 // map.doubleClickZoom.disable();
12768 // @property doubleClickZoom: Handler
12769 // Double click zoom handler.
12770 Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
12773 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
12777 // @section Interaction Options
12779 // @option dragging: Boolean = true
12780 // Whether the map be draggable with mouse/touch or not.
12783 // @section Panning Inertia Options
12784 // @option inertia: Boolean = *
12785 // If enabled, panning of the map will have an inertia effect where
12786 // the map builds momentum while dragging and continues moving in
12787 // the same direction for some time. Feels especially nice on touch
12788 // devices. Enabled by default unless running on old Android devices.
12789 inertia: !android23,
12791 // @option inertiaDeceleration: Number = 3000
12792 // The rate with which the inertial movement slows down, in pixels/second².
12793 inertiaDeceleration: 3400, // px/s^2
12795 // @option inertiaMaxSpeed: Number = Infinity
12796 // Max speed of the inertial movement, in pixels/second.
12797 inertiaMaxSpeed: Infinity, // px/s
12799 // @option easeLinearity: Number = 0.2
12800 easeLinearity: 0.2,
12802 // TODO refactor, move to CRS
12803 // @option worldCopyJump: Boolean = false
12804 // With this option enabled, the map tracks when you pan to another "copy"
12805 // of the world and seamlessly jumps to the original one so that all overlays
12806 // like markers and vector layers are still visible.
12807 worldCopyJump: false,
12809 // @option maxBoundsViscosity: Number = 0.0
12810 // If `maxBounds` is set, this option will control how solid the bounds
12811 // are when dragging the map around. The default value of `0.0` allows the
12812 // user to drag outside the bounds at normal speed, higher values will
12813 // slow down map dragging outside bounds, and `1.0` makes the bounds fully
12814 // solid, preventing the user from dragging outside the bounds.
12815 maxBoundsViscosity: 0.0
12818 var Drag = Handler.extend({
12819 addHooks: function () {
12820 if (!this._draggable) {
12821 var map = this._map;
12823 this._draggable = new Draggable(map._mapPane, map._container);
12825 this._draggable.on({
12826 dragstart: this._onDragStart,
12827 drag: this._onDrag,
12828 dragend: this._onDragEnd
12831 this._draggable.on('predrag', this._onPreDragLimit, this);
12832 if (map.options.worldCopyJump) {
12833 this._draggable.on('predrag', this._onPreDragWrap, this);
12834 map.on('zoomend', this._onZoomEnd, this);
12836 map.whenReady(this._onZoomEnd, this);
12839 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
12840 this._draggable.enable();
12841 this._positions = [];
12845 removeHooks: function () {
12846 removeClass(this._map._container, 'leaflet-grab');
12847 removeClass(this._map._container, 'leaflet-touch-drag');
12848 this._draggable.disable();
12851 moved: function () {
12852 return this._draggable && this._draggable._moved;
12855 moving: function () {
12856 return this._draggable && this._draggable._moving;
12859 _onDragStart: function () {
12860 var map = this._map;
12863 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
12864 var bounds = toLatLngBounds(this._map.options.maxBounds);
12866 this._offsetLimit = toBounds(
12867 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
12868 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
12869 .add(this._map.getSize()));
12871 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
12873 this._offsetLimit = null;
12878 .fire('dragstart');
12880 if (map.options.inertia) {
12881 this._positions = [];
12886 _onDrag: function (e) {
12887 if (this._map.options.inertia) {
12888 var time = this._lastTime = +new Date(),
12889 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
12891 this._positions.push(pos);
12892 this._times.push(time);
12894 if (time - this._times[0] > 50) {
12895 this._positions.shift();
12896 this._times.shift();
12905 _onZoomEnd: function () {
12906 var pxCenter = this._map.getSize().divideBy(2),
12907 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
12909 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
12910 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
12913 _viscousLimit: function (value, threshold) {
12914 return value - (value - threshold) * this._viscosity;
12917 _onPreDragLimit: function () {
12918 if (!this._viscosity || !this._offsetLimit) { return; }
12920 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
12922 var limit = this._offsetLimit;
12923 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
12924 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
12925 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
12926 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
12928 this._draggable._newPos = this._draggable._startPos.add(offset);
12931 _onPreDragWrap: function () {
12932 // TODO refactor to be able to adjust map pane position after zoom
12933 var worldWidth = this._worldWidth,
12934 halfWidth = Math.round(worldWidth / 2),
12935 dx = this._initialWorldOffset,
12936 x = this._draggable._newPos.x,
12937 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
12938 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
12939 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
12941 this._draggable._absPos = this._draggable._newPos.clone();
12942 this._draggable._newPos.x = newX;
12945 _onDragEnd: function (e) {
12946 var map = this._map,
12947 options = map.options,
12949 noInertia = !options.inertia || this._times.length < 2;
12951 map.fire('dragend', e);
12954 map.fire('moveend');
12958 var direction = this._lastPos.subtract(this._positions[0]),
12959 duration = (this._lastTime - this._times[0]) / 1000,
12960 ease = options.easeLinearity,
12962 speedVector = direction.multiplyBy(ease / duration),
12963 speed = speedVector.distanceTo([0, 0]),
12965 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
12966 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
12968 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
12969 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
12971 if (!offset.x && !offset.y) {
12972 map.fire('moveend');
12975 offset = map._limitOffset(offset, map.options.maxBounds);
12977 requestAnimFrame(function () {
12978 map.panBy(offset, {
12979 duration: decelerationDuration,
12980 easeLinearity: ease,
12990 // @section Handlers
12991 // @property dragging: Handler
12992 // Map dragging handler (by both mouse and touch).
12993 Map.addInitHook('addHandler', 'dragging', Drag);
12996 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
13000 // @section Keyboard Navigation Options
13002 // @option keyboard: Boolean = true
13003 // Makes the map focusable and allows users to navigate the map with keyboard
13004 // arrows and `+`/`-` keys.
13007 // @option keyboardPanDelta: Number = 80
13008 // Amount of pixels to pan when pressing an arrow key.
13009 keyboardPanDelta: 80
13012 var Keyboard = Handler.extend({
13019 zoomIn: [187, 107, 61, 171],
13020 zoomOut: [189, 109, 54, 173]
13023 initialize: function (map) {
13026 this._setPanDelta(map.options.keyboardPanDelta);
13027 this._setZoomDelta(map.options.zoomDelta);
13030 addHooks: function () {
13031 var container = this._map._container;
13033 // make the container focusable by tabbing
13034 if (container.tabIndex <= 0) {
13035 container.tabIndex = '0';
13039 focus: this._onFocus,
13040 blur: this._onBlur,
13041 mousedown: this._onMouseDown
13045 focus: this._addHooks,
13046 blur: this._removeHooks
13050 removeHooks: function () {
13051 this._removeHooks();
13053 off(this._map._container, {
13054 focus: this._onFocus,
13055 blur: this._onBlur,
13056 mousedown: this._onMouseDown
13060 focus: this._addHooks,
13061 blur: this._removeHooks
13065 _onMouseDown: function () {
13066 if (this._focused) { return; }
13068 var body = document.body,
13069 docEl = document.documentElement,
13070 top = body.scrollTop || docEl.scrollTop,
13071 left = body.scrollLeft || docEl.scrollLeft;
13073 this._map._container.focus();
13075 window.scrollTo(left, top);
13078 _onFocus: function () {
13079 this._focused = true;
13080 this._map.fire('focus');
13083 _onBlur: function () {
13084 this._focused = false;
13085 this._map.fire('blur');
13088 _setPanDelta: function (panDelta) {
13089 var keys = this._panKeys = {},
13090 codes = this.keyCodes,
13093 for (i = 0, len = codes.left.length; i < len; i++) {
13094 keys[codes.left[i]] = [-1 * panDelta, 0];
13096 for (i = 0, len = codes.right.length; i < len; i++) {
13097 keys[codes.right[i]] = [panDelta, 0];
13099 for (i = 0, len = codes.down.length; i < len; i++) {
13100 keys[codes.down[i]] = [0, panDelta];
13102 for (i = 0, len = codes.up.length; i < len; i++) {
13103 keys[codes.up[i]] = [0, -1 * panDelta];
13107 _setZoomDelta: function (zoomDelta) {
13108 var keys = this._zoomKeys = {},
13109 codes = this.keyCodes,
13112 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
13113 keys[codes.zoomIn[i]] = zoomDelta;
13115 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
13116 keys[codes.zoomOut[i]] = -zoomDelta;
13120 _addHooks: function () {
13121 on(document, 'keydown', this._onKeyDown, this);
13124 _removeHooks: function () {
13125 off(document, 'keydown', this._onKeyDown, this);
13128 _onKeyDown: function (e) {
13129 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
13131 var key = e.keyCode,
13135 if (key in this._panKeys) {
13137 if (map._panAnim && map._panAnim._inProgress) { return; }
13139 offset = this._panKeys[key];
13141 offset = toPoint(offset).multiplyBy(3);
13146 if (map.options.maxBounds) {
13147 map.panInsideBounds(map.options.maxBounds);
13150 } else if (key in this._zoomKeys) {
13151 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
13153 } else if (key === 27 && map._popup) {
13164 // @section Handlers
13165 // @section Handlers
13166 // @property keyboard: Handler
13167 // Keyboard navigation handler.
13168 Map.addInitHook('addHandler', 'keyboard', Keyboard);
13171 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
13175 // @section Interaction Options
13177 // @section Mousewheel options
13178 // @option scrollWheelZoom: Boolean|String = true
13179 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
13180 // it will zoom to the center of the view regardless of where the mouse was.
13181 scrollWheelZoom: true,
13183 // @option wheelDebounceTime: Number = 40
13184 // Limits the rate at which a wheel can fire (in milliseconds). By default
13185 // user can't zoom via wheel more often than once per 40 ms.
13186 wheelDebounceTime: 40,
13188 // @option wheelPxPerZoomLevel: Number = 60
13189 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
13190 // mean a change of one full zoom level. Smaller values will make wheel-zooming
13191 // faster (and vice versa).
13192 wheelPxPerZoomLevel: 60
13195 var ScrollWheelZoom = Handler.extend({
13196 addHooks: function () {
13197 on(this._map._container, 'mousewheel', this._onWheelScroll, this);
13202 removeHooks: function () {
13203 off(this._map._container, 'mousewheel', this._onWheelScroll, this);
13206 _onWheelScroll: function (e) {
13207 var delta = getWheelDelta(e);
13209 var debounce = this._map.options.wheelDebounceTime;
13211 this._delta += delta;
13212 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
13214 if (!this._startTime) {
13215 this._startTime = +new Date();
13218 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
13220 clearTimeout(this._timer);
13221 this._timer = setTimeout(bind(this._performZoom, this), left);
13226 _performZoom: function () {
13227 var map = this._map,
13228 zoom = map.getZoom(),
13229 snap = this._map.options.zoomSnap || 0;
13231 map._stop(); // stop panning and fly animations if any
13233 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
13234 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
13235 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
13236 d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
13237 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
13240 this._startTime = null;
13242 if (!delta) { return; }
13244 if (map.options.scrollWheelZoom === 'center') {
13245 map.setZoom(zoom + delta);
13247 map.setZoomAround(this._lastMousePos, zoom + delta);
13252 // @section Handlers
13253 // @property scrollWheelZoom: Handler
13254 // Scroll wheel zoom handler.
13255 Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
13258 * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
13262 // @section Interaction Options
13264 // @section Touch interaction options
13265 // @option tap: Boolean = true
13266 // Enables mobile hacks for supporting instant taps (fixing 200ms click
13267 // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
13270 // @option tapTolerance: Number = 15
13271 // The max number of pixels a user can shift his finger during touch
13272 // for it to be considered a valid tap.
13276 var Tap = Handler.extend({
13277 addHooks: function () {
13278 on(this._map._container, 'touchstart', this._onDown, this);
13281 removeHooks: function () {
13282 off(this._map._container, 'touchstart', this._onDown, this);
13285 _onDown: function (e) {
13286 if (!e.touches) { return; }
13290 this._fireClick = true;
13292 // don't simulate click or track longpress if more than 1 touch
13293 if (e.touches.length > 1) {
13294 this._fireClick = false;
13295 clearTimeout(this._holdTimeout);
13299 var first = e.touches[0],
13302 this._startPos = this._newPos = new Point(first.clientX, first.clientY);
13304 // if touching a link, highlight it
13305 if (el.tagName && el.tagName.toLowerCase() === 'a') {
13306 addClass(el, 'leaflet-active');
13309 // simulate long hold but setting a timeout
13310 this._holdTimeout = setTimeout(bind(function () {
13311 if (this._isTapValid()) {
13312 this._fireClick = false;
13314 this._simulateEvent('contextmenu', first);
13318 this._simulateEvent('mousedown', first);
13321 touchmove: this._onMove,
13322 touchend: this._onUp
13326 _onUp: function (e) {
13327 clearTimeout(this._holdTimeout);
13330 touchmove: this._onMove,
13331 touchend: this._onUp
13334 if (this._fireClick && e && e.changedTouches) {
13336 var first = e.changedTouches[0],
13339 if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
13340 removeClass(el, 'leaflet-active');
13343 this._simulateEvent('mouseup', first);
13345 // simulate click if the touch didn't move too much
13346 if (this._isTapValid()) {
13347 this._simulateEvent('click', first);
13352 _isTapValid: function () {
13353 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
13356 _onMove: function (e) {
13357 var first = e.touches[0];
13358 this._newPos = new Point(first.clientX, first.clientY);
13359 this._simulateEvent('mousemove', first);
13362 _simulateEvent: function (type, e) {
13363 var simulatedEvent = document.createEvent('MouseEvents');
13365 simulatedEvent._simulated = true;
13366 e.target._simulatedClick = true;
13368 simulatedEvent.initMouseEvent(
13369 type, true, true, window, 1,
13370 e.screenX, e.screenY,
13371 e.clientX, e.clientY,
13372 false, false, false, false, 0, null);
13374 e.target.dispatchEvent(simulatedEvent);
13378 // @section Handlers
13379 // @property tap: Handler
13380 // Mobile touch hacks (quick tap and touch hold) handler.
13381 if (touch && !pointer) {
13382 Map.addInitHook('addHandler', 'tap', Tap);
13386 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
13390 // @section Interaction Options
13392 // @section Touch interaction options
13393 // @option touchZoom: Boolean|String = *
13394 // Whether the map can be zoomed by touch-dragging with two fingers. If
13395 // passed `'center'`, it will zoom to the center of the view regardless of
13396 // where the touch events (fingers) were. Enabled for touch-capable web
13397 // browsers except for old Androids.
13398 touchZoom: touch && !android23,
13400 // @option bounceAtZoomLimits: Boolean = true
13401 // Set it to false if you don't want the map to zoom beyond min/max zoom
13402 // and then bounce back when pinch-zooming.
13403 bounceAtZoomLimits: true
13406 var TouchZoom = Handler.extend({
13407 addHooks: function () {
13408 addClass(this._map._container, 'leaflet-touch-zoom');
13409 on(this._map._container, 'touchstart', this._onTouchStart, this);
13412 removeHooks: function () {
13413 removeClass(this._map._container, 'leaflet-touch-zoom');
13414 off(this._map._container, 'touchstart', this._onTouchStart, this);
13417 _onTouchStart: function (e) {
13418 var map = this._map;
13419 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
13421 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
13422 p2 = map.mouseEventToContainerPoint(e.touches[1]);
13424 this._centerPoint = map.getSize()._divideBy(2);
13425 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
13426 if (map.options.touchZoom !== 'center') {
13427 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
13430 this._startDist = p1.distanceTo(p2);
13431 this._startZoom = map.getZoom();
13433 this._moved = false;
13434 this._zooming = true;
13438 on(document, 'touchmove', this._onTouchMove, this);
13439 on(document, 'touchend', this._onTouchEnd, this);
13444 _onTouchMove: function (e) {
13445 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
13447 var map = this._map,
13448 p1 = map.mouseEventToContainerPoint(e.touches[0]),
13449 p2 = map.mouseEventToContainerPoint(e.touches[1]),
13450 scale = p1.distanceTo(p2) / this._startDist;
13452 this._zoom = map.getScaleZoom(scale, this._startZoom);
13454 if (!map.options.bounceAtZoomLimits && (
13455 (this._zoom < map.getMinZoom() && scale < 1) ||
13456 (this._zoom > map.getMaxZoom() && scale > 1))) {
13457 this._zoom = map._limitZoom(this._zoom);
13460 if (map.options.touchZoom === 'center') {
13461 this._center = this._startLatLng;
13462 if (scale === 1) { return; }
13464 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
13465 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
13466 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
13467 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
13470 if (!this._moved) {
13471 map._moveStart(true);
13472 this._moved = true;
13475 cancelAnimFrame(this._animRequest);
13477 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
13478 this._animRequest = requestAnimFrame(moveFn, this, true);
13483 _onTouchEnd: function () {
13484 if (!this._moved || !this._zooming) {
13485 this._zooming = false;
13489 this._zooming = false;
13490 cancelAnimFrame(this._animRequest);
13492 off(document, 'touchmove', this._onTouchMove);
13493 off(document, 'touchend', this._onTouchEnd);
13495 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
13496 if (this._map.options.zoomAnimation) {
13497 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
13499 this._map._resetView(this._center, this._map._limitZoom(this._zoom));
13504 // @section Handlers
13505 // @property touchZoom: Handler
13506 // Touch zoom handler.
13507 Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
13509 Map.BoxZoom = BoxZoom;
13510 Map.DoubleClickZoom = DoubleClickZoom;
13512 Map.Keyboard = Keyboard;
13513 Map.ScrollWheelZoom = ScrollWheelZoom;
13515 Map.TouchZoom = TouchZoom;
13519 var oldL = window.L;
13520 function noConflict() {
13525 // Always export us to window global (see #2364)
13526 window.L = exports;
13528 Object.freeze = freeze;
13530 exports.version = version;
13531 exports.noConflict = noConflict;
13532 exports.Control = Control;
13533 exports.control = control;
13534 exports.Browser = Browser;
13535 exports.Evented = Evented;
13536 exports.Mixin = Mixin;
13537 exports.Util = Util;
13538 exports.Class = Class;
13539 exports.Handler = Handler;
13540 exports.extend = extend;
13541 exports.bind = bind;
13542 exports.stamp = stamp;
13543 exports.setOptions = setOptions;
13544 exports.DomEvent = DomEvent;
13545 exports.DomUtil = DomUtil;
13546 exports.PosAnimation = PosAnimation;
13547 exports.Draggable = Draggable;
13548 exports.LineUtil = LineUtil;
13549 exports.PolyUtil = PolyUtil;
13550 exports.Point = Point;
13551 exports.point = toPoint;
13552 exports.Bounds = Bounds;
13553 exports.bounds = toBounds;
13554 exports.Transformation = Transformation;
13555 exports.transformation = toTransformation;
13556 exports.Projection = index;
13557 exports.LatLng = LatLng;
13558 exports.latLng = toLatLng;
13559 exports.LatLngBounds = LatLngBounds;
13560 exports.latLngBounds = toLatLngBounds;
13562 exports.GeoJSON = GeoJSON;
13563 exports.geoJSON = geoJSON;
13564 exports.geoJson = geoJson;
13565 exports.Layer = Layer;
13566 exports.LayerGroup = LayerGroup;
13567 exports.layerGroup = layerGroup;
13568 exports.FeatureGroup = FeatureGroup;
13569 exports.featureGroup = featureGroup;
13570 exports.ImageOverlay = ImageOverlay;
13571 exports.imageOverlay = imageOverlay;
13572 exports.VideoOverlay = VideoOverlay;
13573 exports.videoOverlay = videoOverlay;
13574 exports.DivOverlay = DivOverlay;
13575 exports.Popup = Popup;
13576 exports.popup = popup;
13577 exports.Tooltip = Tooltip;
13578 exports.tooltip = tooltip;
13579 exports.Icon = Icon;
13580 exports.icon = icon;
13581 exports.DivIcon = DivIcon;
13582 exports.divIcon = divIcon;
13583 exports.Marker = Marker;
13584 exports.marker = marker;
13585 exports.TileLayer = TileLayer;
13586 exports.tileLayer = tileLayer;
13587 exports.GridLayer = GridLayer;
13588 exports.gridLayer = gridLayer;
13590 exports.svg = svg$1;
13591 exports.Renderer = Renderer;
13592 exports.Canvas = Canvas;
13593 exports.canvas = canvas$1;
13594 exports.Path = Path;
13595 exports.CircleMarker = CircleMarker;
13596 exports.circleMarker = circleMarker;
13597 exports.Circle = Circle;
13598 exports.circle = circle;
13599 exports.Polyline = Polyline;
13600 exports.polyline = polyline;
13601 exports.Polygon = Polygon;
13602 exports.polygon = polygon;
13603 exports.Rectangle = Rectangle;
13604 exports.rectangle = rectangle;
13606 exports.map = createMap;
13609 //# sourceMappingURL=leaflet-src.js.map