2 * Leaflet 1.3.1, a JS library for interactive maps. http://leafletjs.com
3 * (c) 2010-2017 Vladimir Agafonkin, (c) 2010-2011 CloudMade
6 (function (global, factory) {
7 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
8 typeof define === 'function' && define.amd ? define(['exports'], factory) :
9 (factory((global.L = {})));
10 }(this, (function (exports) { 'use strict';
12 var version = "1.3.1";
17 * Various utility functions, used by Leaflet internally.
20 var freeze = Object.freeze;
21 Object.freeze = function (obj) { return obj; };
23 // @function extend(dest: Object, src?: Object): Object
24 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
25 function extend(dest) {
28 for (j = 1, len = arguments.length; j < len; j++) {
37 // @function create(proto: Object, properties?: Object): Object
38 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
39 var create = Object.create || (function () {
41 return function (proto) {
47 // @function bind(fn: Function, …): Function
48 // 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).
49 // Has a `L.bind()` shortcut.
50 function bind(fn, obj) {
51 var slice = Array.prototype.slice;
54 return fn.bind.apply(fn, slice.call(arguments, 1));
57 var args = slice.call(arguments, 2);
60 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
64 // @property lastId: Number
65 // Last unique ID used by [`stamp()`](#util-stamp)
68 // @function stamp(obj: Object): Number
69 // Returns the unique ID of an object, assigning it one if it doesn't have it.
72 obj._leaflet_id = obj._leaflet_id || ++lastId;
73 return obj._leaflet_id;
77 // @function throttle(fn: Function, time: Number, context: Object): Function
78 // Returns a function which executes function `fn` with the given scope `context`
79 // (so that the `this` keyword refers to `context` inside `fn`'s code). The function
80 // `fn` will be called no more than one time per given amount of `time`. The arguments
81 // received by the bound function will be any arguments passed when binding the
82 // function, followed by any arguments passed when invoking the bound function.
83 // Has an `L.throttle` shortcut.
84 function throttle(fn, time, context) {
85 var lock, args, wrapperFn, later;
88 // reset lock and call if queued
91 wrapperFn.apply(context, args);
96 wrapperFn = function () {
98 // called too soon, queue to call later
102 // call and lock until later
103 fn.apply(context, arguments);
104 setTimeout(later, time);
112 // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
113 // Returns the number `num` modulo `range` in such a way so it lies within
114 // `range[0]` and `range[1]`. The returned value will be always smaller than
115 // `range[1]` unless `includeMax` is set to `true`.
116 function wrapNum(x, range, includeMax) {
120 return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
123 // @function falseFn(): Function
124 // Returns a function which always returns `false`.
125 function falseFn() { return false; }
127 // @function formatNum(num: Number, digits?: Number): Number
128 // Returns the number `num` rounded to `digits` decimals, or to 6 decimals by default.
129 function formatNum(num, digits) {
130 var pow = Math.pow(10, (digits === undefined ? 6 : digits));
131 return Math.round(num * pow) / pow;
134 // @function trim(str: String): String
135 // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
137 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
140 // @function splitWords(str: String): String[]
141 // Trims and splits the string on whitespace and returns the array of parts.
142 function splitWords(str) {
143 return trim(str).split(/\s+/);
146 // @function setOptions(obj: Object, options: Object): Object
147 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
148 function setOptions(obj, options) {
149 if (!obj.hasOwnProperty('options')) {
150 obj.options = obj.options ? create(obj.options) : {};
152 for (var i in options) {
153 obj.options[i] = options[i];
158 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
159 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
160 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
161 // be appended at the end. If `uppercase` is `true`, the parameter names will
162 // be uppercased (e.g. `'?A=foo&B=bar'`)
163 function getParamString(obj, existingUrl, uppercase) {
166 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
168 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
171 var templateRe = /\{ *([\w_-]+) *\}/g;
173 // @function template(str: String, data: Object): String
174 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
175 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
176 // `('Hello foo, bar')`. You can also specify functions instead of strings for
177 // data values — they will be evaluated passing `data` as an argument.
178 function template(str, data) {
179 return str.replace(templateRe, function (str, key) {
180 var value = data[key];
182 if (value === undefined) {
183 throw new Error('No value provided for variable ' + str);
185 } else if (typeof value === 'function') {
192 // @function isArray(obj): Boolean
193 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
194 var isArray = Array.isArray || function (obj) {
195 return (Object.prototype.toString.call(obj) === '[object Array]');
198 // @function indexOf(array: Array, el: Object): Number
199 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
200 function indexOf(array, el) {
201 for (var i = 0; i < array.length; i++) {
202 if (array[i] === el) { return i; }
207 // @property emptyImageUrl: String
208 // Data URI string containing a base64-encoded empty GIF image.
209 // Used as a hack to free memory from unused images on WebKit-powered
210 // mobile devices (by setting image `src` to this string).
211 var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
213 // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
215 function getPrefixed(name) {
216 return window['webkit' + name] || window['moz' + name] || window['ms' + name];
221 // fallback for IE 7-8
222 function timeoutDefer(fn) {
223 var time = +new Date(),
224 timeToCall = Math.max(0, 16 - (time - lastTime));
226 lastTime = time + timeToCall;
227 return window.setTimeout(fn, timeToCall);
230 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
231 var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
232 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
234 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
235 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
236 // `context` if given. When `immediate` is set, `fn` is called immediately if
237 // the browser doesn't have native support for
238 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
239 // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
240 function requestAnimFrame(fn, context, immediate) {
241 if (immediate && requestFn === timeoutDefer) {
244 return requestFn.call(window, bind(fn, context));
248 // @function cancelAnimFrame(id: Number): undefined
249 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
250 function cancelAnimFrame(id) {
252 cancelFn.call(window, id);
257 var Util = (Object.freeze || Object)({
267 formatNum: formatNum,
269 splitWords: splitWords,
270 setOptions: setOptions,
271 getParamString: getParamString,
275 emptyImageUrl: emptyImageUrl,
276 requestFn: requestFn,
278 requestAnimFrame: requestAnimFrame,
279 cancelAnimFrame: cancelAnimFrame
288 // Thanks to John Resig and Dean Edwards for inspiration!
292 Class.extend = function (props) {
294 // @function extend(props: Object): Function
295 // [Extends the current class](#class-inheritance) given the properties to be included.
296 // Returns a Javascript function that is a class constructor (to be called with `new`).
297 var NewClass = function () {
299 // call the constructor
300 if (this.initialize) {
301 this.initialize.apply(this, arguments);
304 // call all constructor hooks
305 this.callInitHooks();
308 var parentProto = NewClass.__super__ = this.prototype;
310 var proto = create(parentProto);
311 proto.constructor = NewClass;
313 NewClass.prototype = proto;
315 // inherit parent's statics
316 for (var i in this) {
317 if (this.hasOwnProperty(i) && i !== 'prototype' && i !== '__super__') {
318 NewClass[i] = this[i];
322 // mix static properties into the class
324 extend(NewClass, props.statics);
325 delete props.statics;
328 // mix includes into the prototype
329 if (props.includes) {
330 checkDeprecatedMixinEvents(props.includes);
331 extend.apply(null, [proto].concat(props.includes));
332 delete props.includes;
337 props.options = extend(create(proto.options), props.options);
340 // mix given properties into the prototype
341 extend(proto, props);
343 proto._initHooks = [];
345 // add method for calling all hooks
346 proto.callInitHooks = function () {
348 if (this._initHooksCalled) { return; }
350 if (parentProto.callInitHooks) {
351 parentProto.callInitHooks.call(this);
354 this._initHooksCalled = true;
356 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
357 proto._initHooks[i].call(this);
365 // @function include(properties: Object): this
366 // [Includes a mixin](#class-includes) into the current class.
367 Class.include = function (props) {
368 extend(this.prototype, props);
372 // @function mergeOptions(options: Object): this
373 // [Merges `options`](#class-options) into the defaults of the class.
374 Class.mergeOptions = function (options) {
375 extend(this.prototype.options, options);
379 // @function addInitHook(fn: Function): this
380 // Adds a [constructor hook](#class-constructor-hooks) to the class.
381 Class.addInitHook = function (fn) { // (Function) || (String, args...)
382 var args = Array.prototype.slice.call(arguments, 1);
384 var init = typeof fn === 'function' ? fn : function () {
385 this[fn].apply(this, args);
388 this.prototype._initHooks = this.prototype._initHooks || [];
389 this.prototype._initHooks.push(init);
393 function checkDeprecatedMixinEvents(includes) {
394 if (typeof L === 'undefined' || !L || !L.Mixin) { return; }
396 includes = isArray(includes) ? includes : [includes];
398 for (var i = 0; i < includes.length; i++) {
399 if (includes[i] === L.Mixin.Events) {
400 console.warn('Deprecated include of L.Mixin.Events: ' +
401 'this property will be removed in future releases, ' +
402 'please inherit from L.Evented instead.', new Error().stack);
412 * 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).
417 * map.on('click', function(e) {
422 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
425 * function onClick(e) { ... }
427 * map.on('click', onClick);
428 * map.off('click', onClick);
433 /* @method on(type: String, fn: Function, context?: Object): this
434 * 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'`).
437 * @method on(eventMap: Object): this
438 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
440 on: function (types, fn, context) {
442 // types can be a map of types/handlers
443 if (typeof types === 'object') {
444 for (var type in types) {
445 // we don't process space-separated events here for performance;
446 // it's a hot path since Layer uses the on(obj) syntax
447 this._on(type, types[type], fn);
451 // types can be a string of space-separated words
452 types = splitWords(types);
454 for (var i = 0, len = types.length; i < len; i++) {
455 this._on(types[i], fn, context);
462 /* @method off(type: String, fn?: Function, context?: Object): this
463 * 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.
466 * @method off(eventMap: Object): this
467 * Removes a set of type/listener pairs.
471 * Removes all listeners to all events on the object.
473 off: function (types, fn, context) {
476 // clear all listeners if called without arguments
479 } else if (typeof types === 'object') {
480 for (var type in types) {
481 this._off(type, types[type], fn);
485 types = splitWords(types);
487 for (var i = 0, len = types.length; i < len; i++) {
488 this._off(types[i], fn, context);
495 // attach listener (without syntactic sugar now)
496 _on: function (type, fn, context) {
497 this._events = this._events || {};
499 /* get/init listeners for type */
500 var typeListeners = this._events[type];
501 if (!typeListeners) {
503 this._events[type] = typeListeners;
506 if (context === this) {
507 // Less memory footprint.
510 var newListener = {fn: fn, ctx: context},
511 listeners = typeListeners;
513 // check if fn already there
514 for (var i = 0, len = listeners.length; i < len; i++) {
515 if (listeners[i].fn === fn && listeners[i].ctx === context) {
520 listeners.push(newListener);
523 _off: function (type, fn, context) {
528 if (!this._events) { return; }
530 listeners = this._events[type];
537 // Set all removed listeners to noop so they are not called if remove happens in fire
538 for (i = 0, len = listeners.length; i < len; i++) {
539 listeners[i].fn = falseFn;
541 // clear all listeners for a type if function isn't specified
542 delete this._events[type];
546 if (context === this) {
552 // find fn and remove it
553 for (i = 0, len = listeners.length; i < len; i++) {
554 var l = listeners[i];
555 if (l.ctx !== context) { continue; }
558 // set the removed listener to noop so that's not called if remove happens in fire
561 if (this._firingCount) {
562 /* copy array in case events are being fired */
563 this._events[type] = listeners = listeners.slice();
565 listeners.splice(i, 1);
573 // @method fire(type: String, data?: Object, propagate?: Boolean): this
574 // Fires an event of the specified type. You can optionally provide an data
575 // object — the first argument of the listener function will contain its
576 // properties. The event can optionally be propagated to event parents.
577 fire: function (type, data, propagate) {
578 if (!this.listens(type, propagate)) { return this; }
580 var event = extend({}, data, {
583 sourceTarget: data && data.sourceTarget || this
587 var listeners = this._events[type];
590 this._firingCount = (this._firingCount + 1) || 1;
591 for (var i = 0, len = listeners.length; i < len; i++) {
592 var l = listeners[i];
593 l.fn.call(l.ctx || this, event);
601 // propagate the event to parents (set with addEventParent)
602 this._propagateEvent(event);
608 // @method listens(type: String): Boolean
609 // Returns `true` if a particular event type has any listeners attached to it.
610 listens: function (type, propagate) {
611 var listeners = this._events && this._events[type];
612 if (listeners && listeners.length) { return true; }
615 // also check parents for listeners if event propagates
616 for (var id in this._eventParents) {
617 if (this._eventParents[id].listens(type, propagate)) { return true; }
623 // @method once(…): this
624 // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
625 once: function (types, fn, context) {
627 if (typeof types === 'object') {
628 for (var type in types) {
629 this.once(type, types[type], fn);
634 var handler = bind(function () {
636 .off(types, fn, context)
637 .off(types, handler, context);
640 // add a listener that's executed once and removed after that
642 .on(types, fn, context)
643 .on(types, handler, context);
646 // @method addEventParent(obj: Evented): this
647 // Adds an event parent - an `Evented` that will receive propagated events
648 addEventParent: function (obj) {
649 this._eventParents = this._eventParents || {};
650 this._eventParents[stamp(obj)] = obj;
654 // @method removeEventParent(obj: Evented): this
655 // Removes an event parent, so it will stop receiving propagated events
656 removeEventParent: function (obj) {
657 if (this._eventParents) {
658 delete this._eventParents[stamp(obj)];
663 _propagateEvent: function (e) {
664 for (var id in this._eventParents) {
665 this._eventParents[id].fire(e.type, extend({
667 propagatedFrom: e.target
673 // aliases; we should ditch those eventually
675 // @method addEventListener(…): this
676 // Alias to [`on(…)`](#evented-on)
677 Events.addEventListener = Events.on;
679 // @method removeEventListener(…): this
680 // Alias to [`off(…)`](#evented-off)
682 // @method clearAllEventListeners(…): this
683 // Alias to [`off()`](#evented-off)
684 Events.removeEventListener = Events.clearAllEventListeners = Events.off;
686 // @method addOneTimeEventListener(…): this
687 // Alias to [`once(…)`](#evented-once)
688 Events.addOneTimeEventListener = Events.once;
690 // @method fireEvent(…): this
691 // Alias to [`fire(…)`](#evented-fire)
692 Events.fireEvent = Events.fire;
694 // @method hasEventListeners(…): Boolean
695 // Alias to [`listens(…)`](#evented-listens)
696 Events.hasEventListeners = Events.listens;
698 var Evented = Class.extend(Events);
704 * Represents a point with `x` and `y` coordinates in pixels.
709 * var point = L.point(200, 300);
712 * 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:
715 * map.panBy([200, 300]);
716 * map.panBy(L.point(200, 300));
719 * Note that `Point` does not inherit from Leafet's `Class` object,
720 * which means new classes can't inherit from it, and new methods
721 * can't be added to it with the `include` function.
724 function Point(x, y, round) {
725 // @property x: Number; The `x` coordinate of the point
726 this.x = (round ? Math.round(x) : x);
727 // @property y: Number; The `y` coordinate of the point
728 this.y = (round ? Math.round(y) : y);
731 var trunc = Math.trunc || function (v) {
732 return v > 0 ? Math.floor(v) : Math.ceil(v);
737 // @method clone(): Point
738 // Returns a copy of the current point.
740 return new Point(this.x, this.y);
743 // @method add(otherPoint: Point): Point
744 // Returns the result of addition of the current and the given points.
745 add: function (point) {
746 // non-destructive, returns a new point
747 return this.clone()._add(toPoint(point));
750 _add: function (point) {
751 // destructive, used directly for performance in situations where it's safe to modify existing point
757 // @method subtract(otherPoint: Point): Point
758 // Returns the result of subtraction of the given point from the current.
759 subtract: function (point) {
760 return this.clone()._subtract(toPoint(point));
763 _subtract: function (point) {
769 // @method divideBy(num: Number): Point
770 // Returns the result of division of the current point by the given number.
771 divideBy: function (num) {
772 return this.clone()._divideBy(num);
775 _divideBy: function (num) {
781 // @method multiplyBy(num: Number): Point
782 // Returns the result of multiplication of the current point by the given number.
783 multiplyBy: function (num) {
784 return this.clone()._multiplyBy(num);
787 _multiplyBy: function (num) {
793 // @method scaleBy(scale: Point): Point
794 // Multiply each coordinate of the current point by each coordinate of
795 // `scale`. In linear algebra terms, multiply the point by the
796 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
797 // defined by `scale`.
798 scaleBy: function (point) {
799 return new Point(this.x * point.x, this.y * point.y);
802 // @method unscaleBy(scale: Point): Point
803 // Inverse of `scaleBy`. Divide each coordinate of the current point by
804 // each coordinate of `scale`.
805 unscaleBy: function (point) {
806 return new Point(this.x / point.x, this.y / point.y);
809 // @method round(): Point
810 // Returns a copy of the current point with rounded coordinates.
812 return this.clone()._round();
815 _round: function () {
816 this.x = Math.round(this.x);
817 this.y = Math.round(this.y);
821 // @method floor(): Point
822 // Returns a copy of the current point with floored coordinates (rounded down).
824 return this.clone()._floor();
827 _floor: function () {
828 this.x = Math.floor(this.x);
829 this.y = Math.floor(this.y);
833 // @method ceil(): Point
834 // Returns a copy of the current point with ceiled coordinates (rounded up).
836 return this.clone()._ceil();
840 this.x = Math.ceil(this.x);
841 this.y = Math.ceil(this.y);
845 // @method trunc(): Point
846 // Returns a copy of the current point with truncated coordinates (rounded towards zero).
848 return this.clone()._trunc();
851 _trunc: function () {
852 this.x = trunc(this.x);
853 this.y = trunc(this.y);
857 // @method distanceTo(otherPoint: Point): Number
858 // Returns the cartesian distance between the current and the given points.
859 distanceTo: function (point) {
860 point = toPoint(point);
862 var x = point.x - this.x,
863 y = point.y - this.y;
865 return Math.sqrt(x * x + y * y);
868 // @method equals(otherPoint: Point): Boolean
869 // Returns `true` if the given point has the same coordinates.
870 equals: function (point) {
871 point = toPoint(point);
873 return point.x === this.x &&
877 // @method contains(otherPoint: Point): Boolean
878 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
879 contains: function (point) {
880 point = toPoint(point);
882 return Math.abs(point.x) <= Math.abs(this.x) &&
883 Math.abs(point.y) <= Math.abs(this.y);
886 // @method toString(): String
887 // Returns a string representation of the point for debugging purposes.
888 toString: function () {
890 formatNum(this.x) + ', ' +
891 formatNum(this.y) + ')';
895 // @factory L.point(x: Number, y: Number, round?: Boolean)
896 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
899 // @factory L.point(coords: Number[])
900 // Expects an array of the form `[x, y]` instead.
903 // @factory L.point(coords: Object)
904 // Expects a plain object of the form `{x: Number, y: Number}` instead.
905 function toPoint(x, y, round) {
906 if (x instanceof Point) {
910 return new Point(x[0], x[1]);
912 if (x === undefined || x === null) {
915 if (typeof x === 'object' && 'x' in x && 'y' in x) {
916 return new Point(x.x, x.y);
918 return new Point(x, y, round);
925 * Represents a rectangular area in pixel coordinates.
930 * var p1 = L.point(10, 10),
931 * p2 = L.point(40, 60),
932 * bounds = L.bounds(p1, p2);
935 * 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:
938 * otherBounds.intersects([[10, 10], [40, 60]]);
941 * Note that `Bounds` does not inherit from Leafet's `Class` object,
942 * which means new classes can't inherit from it, and new methods
943 * can't be added to it with the `include` function.
946 function Bounds(a, b) {
949 var points = b ? [a, b] : a;
951 for (var i = 0, len = points.length; i < len; i++) {
952 this.extend(points[i]);
957 // @method extend(point: Point): this
958 // Extends the bounds to contain the given point.
959 extend: function (point) { // (Point)
960 point = toPoint(point);
962 // @property min: Point
963 // The top left corner of the rectangle.
964 // @property max: Point
965 // The bottom right corner of the rectangle.
966 if (!this.min && !this.max) {
967 this.min = point.clone();
968 this.max = point.clone();
970 this.min.x = Math.min(point.x, this.min.x);
971 this.max.x = Math.max(point.x, this.max.x);
972 this.min.y = Math.min(point.y, this.min.y);
973 this.max.y = Math.max(point.y, this.max.y);
978 // @method getCenter(round?: Boolean): Point
979 // Returns the center point of the bounds.
980 getCenter: function (round) {
982 (this.min.x + this.max.x) / 2,
983 (this.min.y + this.max.y) / 2, round);
986 // @method getBottomLeft(): Point
987 // Returns the bottom-left point of the bounds.
988 getBottomLeft: function () {
989 return new Point(this.min.x, this.max.y);
992 // @method getTopRight(): Point
993 // Returns the top-right point of the bounds.
994 getTopRight: function () { // -> Point
995 return new Point(this.max.x, this.min.y);
998 // @method getTopLeft(): Point
999 // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).
1000 getTopLeft: function () {
1001 return this.min; // left, top
1004 // @method getBottomRight(): Point
1005 // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).
1006 getBottomRight: function () {
1007 return this.max; // right, bottom
1010 // @method getSize(): Point
1011 // Returns the size of the given bounds
1012 getSize: function () {
1013 return this.max.subtract(this.min);
1016 // @method contains(otherBounds: Bounds): Boolean
1017 // Returns `true` if the rectangle contains the given one.
1019 // @method contains(point: Point): Boolean
1020 // Returns `true` if the rectangle contains the given point.
1021 contains: function (obj) {
1024 if (typeof obj[0] === 'number' || obj instanceof Point) {
1027 obj = toBounds(obj);
1030 if (obj instanceof Bounds) {
1037 return (min.x >= this.min.x) &&
1038 (max.x <= this.max.x) &&
1039 (min.y >= this.min.y) &&
1040 (max.y <= this.max.y);
1043 // @method intersects(otherBounds: Bounds): Boolean
1044 // Returns `true` if the rectangle intersects the given bounds. Two bounds
1045 // intersect if they have at least one point in common.
1046 intersects: function (bounds) { // (Bounds) -> Boolean
1047 bounds = toBounds(bounds);
1053 xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1054 yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1056 return xIntersects && yIntersects;
1059 // @method overlaps(otherBounds: Bounds): Boolean
1060 // Returns `true` if the rectangle overlaps the given bounds. Two bounds
1061 // overlap if their intersection is an area.
1062 overlaps: function (bounds) { // (Bounds) -> Boolean
1063 bounds = toBounds(bounds);
1069 xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1070 yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1072 return xOverlaps && yOverlaps;
1075 isValid: function () {
1076 return !!(this.min && this.max);
1081 // @factory L.bounds(corner1: Point, corner2: Point)
1082 // Creates a Bounds object from two corners coordinate pairs.
1084 // @factory L.bounds(points: Point[])
1085 // Creates a Bounds object from the given array of points.
1086 function toBounds(a, b) {
1087 if (!a || a instanceof Bounds) {
1090 return new Bounds(a, b);
1094 * @class LatLngBounds
1095 * @aka L.LatLngBounds
1097 * Represents a rectangular geographical area on a map.
1102 * var corner1 = L.latLng(40.712, -74.227),
1103 * corner2 = L.latLng(40.774, -74.125),
1104 * bounds = L.latLngBounds(corner1, corner2);
1107 * 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:
1111 * [40.712, -74.227],
1116 * 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.
1118 * Note that `LatLngBounds` does not inherit from Leafet's `Class` object,
1119 * which means new classes can't inherit from it, and new methods
1120 * can't be added to it with the `include` function.
1123 function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
1124 if (!corner1) { return; }
1126 var latlngs = corner2 ? [corner1, corner2] : corner1;
1128 for (var i = 0, len = latlngs.length; i < len; i++) {
1129 this.extend(latlngs[i]);
1133 LatLngBounds.prototype = {
1135 // @method extend(latlng: LatLng): this
1136 // Extend the bounds to contain the given point
1139 // @method extend(otherBounds: LatLngBounds): this
1140 // Extend the bounds to contain the given bounds
1141 extend: function (obj) {
1142 var sw = this._southWest,
1143 ne = this._northEast,
1146 if (obj instanceof LatLng) {
1150 } else if (obj instanceof LatLngBounds) {
1151 sw2 = obj._southWest;
1152 ne2 = obj._northEast;
1154 if (!sw2 || !ne2) { return this; }
1157 return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
1161 this._southWest = new LatLng(sw2.lat, sw2.lng);
1162 this._northEast = new LatLng(ne2.lat, ne2.lng);
1164 sw.lat = Math.min(sw2.lat, sw.lat);
1165 sw.lng = Math.min(sw2.lng, sw.lng);
1166 ne.lat = Math.max(ne2.lat, ne.lat);
1167 ne.lng = Math.max(ne2.lng, ne.lng);
1173 // @method pad(bufferRatio: Number): LatLngBounds
1174 // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
1175 // For example, a ratio of 0.5 extends the bounds by 50% in each direction.
1176 // Negative values will retract the bounds.
1177 pad: function (bufferRatio) {
1178 var sw = this._southWest,
1179 ne = this._northEast,
1180 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1181 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1183 return new LatLngBounds(
1184 new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1185 new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1188 // @method getCenter(): LatLng
1189 // Returns the center point of the bounds.
1190 getCenter: function () {
1192 (this._southWest.lat + this._northEast.lat) / 2,
1193 (this._southWest.lng + this._northEast.lng) / 2);
1196 // @method getSouthWest(): LatLng
1197 // Returns the south-west point of the bounds.
1198 getSouthWest: function () {
1199 return this._southWest;
1202 // @method getNorthEast(): LatLng
1203 // Returns the north-east point of the bounds.
1204 getNorthEast: function () {
1205 return this._northEast;
1208 // @method getNorthWest(): LatLng
1209 // Returns the north-west point of the bounds.
1210 getNorthWest: function () {
1211 return new LatLng(this.getNorth(), this.getWest());
1214 // @method getSouthEast(): LatLng
1215 // Returns the south-east point of the bounds.
1216 getSouthEast: function () {
1217 return new LatLng(this.getSouth(), this.getEast());
1220 // @method getWest(): Number
1221 // Returns the west longitude of the bounds
1222 getWest: function () {
1223 return this._southWest.lng;
1226 // @method getSouth(): Number
1227 // Returns the south latitude of the bounds
1228 getSouth: function () {
1229 return this._southWest.lat;
1232 // @method getEast(): Number
1233 // Returns the east longitude of the bounds
1234 getEast: function () {
1235 return this._northEast.lng;
1238 // @method getNorth(): Number
1239 // Returns the north latitude of the bounds
1240 getNorth: function () {
1241 return this._northEast.lat;
1244 // @method contains(otherBounds: LatLngBounds): Boolean
1245 // Returns `true` if the rectangle contains the given one.
1248 // @method contains (latlng: LatLng): Boolean
1249 // Returns `true` if the rectangle contains the given point.
1250 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1251 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
1252 obj = toLatLng(obj);
1254 obj = toLatLngBounds(obj);
1257 var sw = this._southWest,
1258 ne = this._northEast,
1261 if (obj instanceof LatLngBounds) {
1262 sw2 = obj.getSouthWest();
1263 ne2 = obj.getNorthEast();
1268 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1269 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1272 // @method intersects(otherBounds: LatLngBounds): Boolean
1273 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1274 intersects: function (bounds) {
1275 bounds = toLatLngBounds(bounds);
1277 var sw = this._southWest,
1278 ne = this._northEast,
1279 sw2 = bounds.getSouthWest(),
1280 ne2 = bounds.getNorthEast(),
1282 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1283 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1285 return latIntersects && lngIntersects;
1288 // @method overlaps(otherBounds: Bounds): Boolean
1289 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1290 overlaps: function (bounds) {
1291 bounds = toLatLngBounds(bounds);
1293 var sw = this._southWest,
1294 ne = this._northEast,
1295 sw2 = bounds.getSouthWest(),
1296 ne2 = bounds.getNorthEast(),
1298 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1299 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1301 return latOverlaps && lngOverlaps;
1304 // @method toBBoxString(): String
1305 // 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.
1306 toBBoxString: function () {
1307 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1310 // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
1311 // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overridden by setting `maxMargin` to a small number.
1312 equals: function (bounds, maxMargin) {
1313 if (!bounds) { return false; }
1315 bounds = toLatLngBounds(bounds);
1317 return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
1318 this._northEast.equals(bounds.getNorthEast(), maxMargin);
1321 // @method isValid(): Boolean
1322 // Returns `true` if the bounds are properly initialized.
1323 isValid: function () {
1324 return !!(this._southWest && this._northEast);
1328 // TODO International date line?
1330 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
1331 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
1334 // @factory L.latLngBounds(latlngs: LatLng[])
1335 // 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).
1336 function toLatLngBounds(a, b) {
1337 if (a instanceof LatLngBounds) {
1340 return new LatLngBounds(a, b);
1346 * Represents a geographical point with a certain latitude and longitude.
1351 * var latlng = L.latLng(50.5, 30.5);
1354 * 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:
1357 * map.panTo([50, 30]);
1358 * map.panTo({lon: 30, lat: 50});
1359 * map.panTo({lat: 50, lng: 30});
1360 * map.panTo(L.latLng(50, 30));
1363 * Note that `LatLng` does not inherit from Leafet's `Class` object,
1364 * which means new classes can't inherit from it, and new methods
1365 * can't be added to it with the `include` function.
1368 function LatLng(lat, lng, alt) {
1369 if (isNaN(lat) || isNaN(lng)) {
1370 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1373 // @property lat: Number
1374 // Latitude in degrees
1377 // @property lng: Number
1378 // Longitude in degrees
1381 // @property alt: Number
1382 // Altitude in meters (optional)
1383 if (alt !== undefined) {
1388 LatLng.prototype = {
1389 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1390 // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overridden by setting `maxMargin` to a small number.
1391 equals: function (obj, maxMargin) {
1392 if (!obj) { return false; }
1394 obj = toLatLng(obj);
1396 var margin = Math.max(
1397 Math.abs(this.lat - obj.lat),
1398 Math.abs(this.lng - obj.lng));
1400 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1403 // @method toString(): String
1404 // Returns a string representation of the point (for debugging purposes).
1405 toString: function (precision) {
1407 formatNum(this.lat, precision) + ', ' +
1408 formatNum(this.lng, precision) + ')';
1411 // @method distanceTo(otherLatLng: LatLng): Number
1412 // Returns the distance (in meters) to the given `LatLng` calculated using the [Spherical Law of Cosines](https://en.wikipedia.org/wiki/Spherical_law_of_cosines).
1413 distanceTo: function (other) {
1414 return Earth.distance(this, toLatLng(other));
1417 // @method wrap(): LatLng
1418 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1420 return Earth.wrapLatLng(this);
1423 // @method toBounds(sizeInMeters: Number): LatLngBounds
1424 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
1425 toBounds: function (sizeInMeters) {
1426 var latAccuracy = 180 * sizeInMeters / 40075017,
1427 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1429 return toLatLngBounds(
1430 [this.lat - latAccuracy, this.lng - lngAccuracy],
1431 [this.lat + latAccuracy, this.lng + lngAccuracy]);
1434 clone: function () {
1435 return new LatLng(this.lat, this.lng, this.alt);
1441 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1442 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1445 // @factory L.latLng(coords: Array): LatLng
1446 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1449 // @factory L.latLng(coords: Object): LatLng
1450 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1452 function toLatLng(a, b, c) {
1453 if (a instanceof LatLng) {
1456 if (isArray(a) && typeof a[0] !== 'object') {
1457 if (a.length === 3) {
1458 return new LatLng(a[0], a[1], a[2]);
1460 if (a.length === 2) {
1461 return new LatLng(a[0], a[1]);
1465 if (a === undefined || a === null) {
1468 if (typeof a === 'object' && 'lat' in a) {
1469 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1471 if (b === undefined) {
1474 return new LatLng(a, b, c);
1480 * Object that defines coordinate reference systems for projecting
1481 * geographical points into pixel (screen) coordinates and back (and to
1482 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
1483 * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
1485 * Leaflet defines the most usual CRSs by default. If you want to use a
1486 * CRS not defined by default, take a look at the
1487 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
1489 * Note that the CRS instances do not inherit from Leafet's `Class` object,
1490 * and can't be instantiated. Also, new classes can't inherit from them,
1491 * and methods can't be added to them with the `include` function.
1495 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
1496 // Projects geographical coordinates into pixel coordinates for a given zoom.
1497 latLngToPoint: function (latlng, zoom) {
1498 var projectedPoint = this.projection.project(latlng),
1499 scale = this.scale(zoom);
1501 return this.transformation._transform(projectedPoint, scale);
1504 // @method pointToLatLng(point: Point, zoom: Number): LatLng
1505 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
1506 // zoom into geographical coordinates.
1507 pointToLatLng: function (point, zoom) {
1508 var scale = this.scale(zoom),
1509 untransformedPoint = this.transformation.untransform(point, scale);
1511 return this.projection.unproject(untransformedPoint);
1514 // @method project(latlng: LatLng): Point
1515 // Projects geographical coordinates into coordinates in units accepted for
1516 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
1517 project: function (latlng) {
1518 return this.projection.project(latlng);
1521 // @method unproject(point: Point): LatLng
1522 // Given a projected coordinate returns the corresponding LatLng.
1523 // The inverse of `project`.
1524 unproject: function (point) {
1525 return this.projection.unproject(point);
1528 // @method scale(zoom: Number): Number
1529 // Returns the scale used when transforming projected coordinates into
1530 // pixel coordinates for a particular zoom. For example, it returns
1531 // `256 * 2^zoom` for Mercator-based CRS.
1532 scale: function (zoom) {
1533 return 256 * Math.pow(2, zoom);
1536 // @method zoom(scale: Number): Number
1537 // Inverse of `scale()`, returns the zoom level corresponding to a scale
1538 // factor of `scale`.
1539 zoom: function (scale) {
1540 return Math.log(scale / 256) / Math.LN2;
1543 // @method getProjectedBounds(zoom: Number): Bounds
1544 // Returns the projection's bounds scaled and transformed for the provided `zoom`.
1545 getProjectedBounds: function (zoom) {
1546 if (this.infinite) { return null; }
1548 var b = this.projection.bounds,
1549 s = this.scale(zoom),
1550 min = this.transformation.transform(b.min, s),
1551 max = this.transformation.transform(b.max, s);
1553 return new Bounds(min, max);
1556 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
1557 // Returns the distance between two geographical coordinates.
1559 // @property code: String
1560 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
1562 // @property wrapLng: Number[]
1563 // An array of two numbers defining whether the longitude (horizontal) coordinate
1564 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
1565 // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
1567 // @property wrapLat: Number[]
1568 // Like `wrapLng`, but for the latitude (vertical) axis.
1570 // wrapLng: [min, max],
1571 // wrapLat: [min, max],
1573 // @property infinite: Boolean
1574 // If true, the coordinate space will be unbounded (infinite in both axes)
1577 // @method wrapLatLng(latlng: LatLng): LatLng
1578 // Returns a `LatLng` where lat and lng has been wrapped according to the
1579 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
1580 wrapLatLng: function (latlng) {
1581 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
1582 lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
1585 return new LatLng(lat, lng, alt);
1588 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
1589 // Returns a `LatLngBounds` with the same size as the given one, ensuring
1590 // that its center is within the CRS's bounds.
1591 // Only accepts actual `L.LatLngBounds` instances, not arrays.
1592 wrapLatLngBounds: function (bounds) {
1593 var center = bounds.getCenter(),
1594 newCenter = this.wrapLatLng(center),
1595 latShift = center.lat - newCenter.lat,
1596 lngShift = center.lng - newCenter.lng;
1598 if (latShift === 0 && lngShift === 0) {
1602 var sw = bounds.getSouthWest(),
1603 ne = bounds.getNorthEast(),
1604 newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),
1605 newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);
1607 return new LatLngBounds(newSw, newNe);
1615 * Serves as the base for CRS that are global such that they cover the earth.
1616 * Can only be used as the base for other CRS and cannot be used directly,
1617 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
1621 var Earth = extend({}, CRS, {
1622 wrapLng: [-180, 180],
1624 // Mean Earth Radius, as recommended for use by
1625 // the International Union of Geodesy and Geophysics,
1626 // see http://rosettacode.org/wiki/Haversine_formula
1629 // distance between two geographical points using spherical law of cosines approximation
1630 distance: function (latlng1, latlng2) {
1631 var rad = Math.PI / 180,
1632 lat1 = latlng1.lat * rad,
1633 lat2 = latlng2.lat * rad,
1634 sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2),
1635 sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2),
1636 a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon,
1637 c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1643 * @namespace Projection
1644 * @projection L.Projection.SphericalMercator
1646 * Spherical Mercator projection — the most common projection for online maps,
1647 * used by almost all free and commercial tile providers. Assumes that Earth is
1648 * a sphere. Used by the `EPSG:3857` CRS.
1651 var SphericalMercator = {
1654 MAX_LATITUDE: 85.0511287798,
1656 project: function (latlng) {
1657 var d = Math.PI / 180,
1658 max = this.MAX_LATITUDE,
1659 lat = Math.max(Math.min(max, latlng.lat), -max),
1660 sin = Math.sin(lat * d);
1663 this.R * latlng.lng * d,
1664 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
1667 unproject: function (point) {
1668 var d = 180 / Math.PI;
1671 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
1672 point.x * d / this.R);
1675 bounds: (function () {
1676 var d = 6378137 * Math.PI;
1677 return new Bounds([-d, -d], [d, d]);
1682 * @class Transformation
1683 * @aka L.Transformation
1685 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1686 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1687 * the reverse. Used by Leaflet in its projections code.
1692 * var transformation = L.transformation(2, 5, -1, 10),
1693 * p = L.point(1, 2),
1694 * p2 = transformation.transform(p), // L.point(7, 8)
1695 * p3 = transformation.untransform(p2); // L.point(1, 2)
1700 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1701 // Creates a `Transformation` object with the given coefficients.
1702 function Transformation(a, b, c, d) {
1704 // use array properties
1717 Transformation.prototype = {
1718 // @method transform(point: Point, scale?: Number): Point
1719 // Returns a transformed point, optionally multiplied by the given scale.
1720 // Only accepts actual `L.Point` instances, not arrays.
1721 transform: function (point, scale) { // (Point, Number) -> Point
1722 return this._transform(point.clone(), scale);
1725 // destructive transform (faster)
1726 _transform: function (point, scale) {
1728 point.x = scale * (this._a * point.x + this._b);
1729 point.y = scale * (this._c * point.y + this._d);
1733 // @method untransform(point: Point, scale?: Number): Point
1734 // Returns the reverse transformation of the given point, optionally divided
1735 // by the given scale. Only accepts actual `L.Point` instances, not arrays.
1736 untransform: function (point, scale) {
1739 (point.x / scale - this._b) / this._a,
1740 (point.y / scale - this._d) / this._c);
1744 // factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1746 // @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1747 // Instantiates a Transformation object with the given coefficients.
1750 // @factory L.transformation(coefficients: Array): Transformation
1751 // Expects an coefficients array of the form
1752 // `[a: Number, b: Number, c: Number, d: Number]`.
1754 function toTransformation(a, b, c, d) {
1755 return new Transformation(a, b, c, d);
1760 * @crs L.CRS.EPSG3857
1762 * The most common CRS for online maps, used by almost all free and commercial
1763 * tile providers. Uses Spherical Mercator projection. Set in by default in
1764 * Map's `crs` option.
1767 var EPSG3857 = extend({}, Earth, {
1769 projection: SphericalMercator,
1771 transformation: (function () {
1772 var scale = 0.5 / (Math.PI * SphericalMercator.R);
1773 return toTransformation(scale, 0.5, -scale, 0.5);
1777 var EPSG900913 = extend({}, EPSG3857, {
1781 // @namespace SVG; @section
1782 // There are several static functions which can be called without instantiating L.SVG:
1784 // @function create(name: String): SVGElement
1785 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
1786 // corresponding to the class name passed. For example, using 'line' will return
1787 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
1788 function svgCreate(name) {
1789 return document.createElementNS('http://www.w3.org/2000/svg', name);
1792 // @function pointsToPath(rings: Point[], closed: Boolean): String
1793 // Generates a SVG path string for multiple rings, with each ring turning
1794 // into "M..L..L.." instructions
1795 function pointsToPath(rings, closed) {
1797 i, j, len, len2, points, p;
1799 for (i = 0, len = rings.length; i < len; i++) {
1802 for (j = 0, len2 = points.length; j < len2; j++) {
1804 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
1807 // closes the ring for polygons; "x" is VML syntax
1808 str += closed ? (svg ? 'z' : 'x') : '';
1811 // SVG complains about empty path strings
1812 return str || 'M0 0';
1816 * @namespace Browser
1819 * A namespace with static properties for browser/feature detection used by Leaflet internally.
1824 * if (L.Browser.ielt9) {
1825 * alert('Upgrade your browser, dude!');
1830 var style$1 = document.documentElement.style;
1832 // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
1833 var ie = 'ActiveXObject' in window;
1835 // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
1836 var ielt9 = ie && !document.addEventListener;
1838 // @property edge: Boolean; `true` for the Edge web browser.
1839 var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
1841 // @property webkit: Boolean;
1842 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
1843 var webkit = userAgentContains('webkit');
1845 // @property android: Boolean
1846 // `true` for any browser running on an Android platform.
1847 var android = userAgentContains('android');
1849 // @property android23: Boolean; `true` for browsers running on Android 2 or Android 3.
1850 var android23 = userAgentContains('android 2') || userAgentContains('android 3');
1852 /* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */
1853 var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit
1854 // @property androidStock: Boolean; `true` for the Android stock browser (i.e. not Chrome)
1855 var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);
1857 // @property opera: Boolean; `true` for the Opera browser
1858 var opera = !!window.opera;
1860 // @property chrome: Boolean; `true` for the Chrome browser.
1861 var chrome = userAgentContains('chrome');
1863 // @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
1864 var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
1866 // @property safari: Boolean; `true` for the Safari browser.
1867 var safari = !chrome && userAgentContains('safari');
1869 var phantom = userAgentContains('phantom');
1871 // @property opera12: Boolean
1872 // `true` for the Opera browser supporting CSS transforms (version 12 or later).
1873 var opera12 = 'OTransition' in style$1;
1875 // @property win: Boolean; `true` when the browser is running in a Windows platform
1876 var win = navigator.platform.indexOf('Win') === 0;
1878 // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
1879 var ie3d = ie && ('transition' in style$1);
1881 // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
1882 var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;
1884 // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
1885 var gecko3d = 'MozPerspective' in style$1;
1887 // @property any3d: Boolean
1888 // `true` for all browsers supporting CSS transforms.
1889 var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
1891 // @property mobile: Boolean; `true` for all browsers running in a mobile device.
1892 var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
1894 // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
1895 var mobileWebkit = mobile && webkit;
1897 // @property mobileWebkit3d: Boolean
1898 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
1899 var mobileWebkit3d = mobile && webkit3d;
1901 // @property msPointer: Boolean
1902 // `true` for browsers implementing the Microsoft touch events model (notably IE10).
1903 var msPointer = !window.PointerEvent && window.MSPointerEvent;
1905 // @property pointer: Boolean
1906 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
1907 var pointer = !!(window.PointerEvent || msPointer);
1909 // @property touch: Boolean
1910 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
1911 // This does not necessarily mean that the browser is running in a computer with
1912 // a touchscreen, it only means that the browser is capable of understanding
1914 var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
1915 (window.DocumentTouch && document instanceof window.DocumentTouch));
1917 // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
1918 var mobileOpera = mobile && opera;
1920 // @property mobileGecko: Boolean
1921 // `true` for gecko-based browsers running in a mobile device.
1922 var mobileGecko = mobile && gecko;
1924 // @property retina: Boolean
1925 // `true` for browsers on a high-resolution "retina" screen.
1926 var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
1929 // @property canvas: Boolean
1930 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
1931 var canvas = (function () {
1932 return !!document.createElement('canvas').getContext;
1935 // @property svg: Boolean
1936 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
1937 var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect);
1939 // @property vml: Boolean
1940 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
1941 var vml = !svg && (function () {
1943 var div = document.createElement('div');
1944 div.innerHTML = '<v:shape adj="1"/>';
1946 var shape = div.firstChild;
1947 shape.style.behavior = 'url(#default#VML)';
1949 return shape && (typeof shape.adj === 'object');
1957 function userAgentContains(str) {
1958 return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
1962 var Browser = (Object.freeze || Object)({
1968 android23: android23,
1969 androidStock: androidStock,
1982 mobileWebkit: mobileWebkit,
1983 mobileWebkit3d: mobileWebkit3d,
1984 msPointer: msPointer,
1987 mobileOpera: mobileOpera,
1988 mobileGecko: mobileGecko,
1996 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
2000 var POINTER_DOWN = msPointer ? 'MSPointerDown' : 'pointerdown';
2001 var POINTER_MOVE = msPointer ? 'MSPointerMove' : 'pointermove';
2002 var POINTER_UP = msPointer ? 'MSPointerUp' : 'pointerup';
2003 var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel';
2004 var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION'];
2007 var _pointerDocListener = false;
2009 // DomEvent.DoubleTap needs to know about this
2010 var _pointersCount = 0;
2012 // Provides a touch events wrapper for (ms)pointer events.
2013 // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
2015 function addPointerListener(obj, type, handler, id) {
2016 if (type === 'touchstart') {
2017 _addPointerStart(obj, handler, id);
2019 } else if (type === 'touchmove') {
2020 _addPointerMove(obj, handler, id);
2022 } else if (type === 'touchend') {
2023 _addPointerEnd(obj, handler, id);
2029 function removePointerListener(obj, type, id) {
2030 var handler = obj['_leaflet_' + type + id];
2032 if (type === 'touchstart') {
2033 obj.removeEventListener(POINTER_DOWN, handler, false);
2035 } else if (type === 'touchmove') {
2036 obj.removeEventListener(POINTER_MOVE, handler, false);
2038 } else if (type === 'touchend') {
2039 obj.removeEventListener(POINTER_UP, handler, false);
2040 obj.removeEventListener(POINTER_CANCEL, handler, false);
2046 function _addPointerStart(obj, handler, id) {
2047 var onDown = bind(function (e) {
2048 if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
2049 // In IE11, some touch events needs to fire for form controls, or
2050 // the controls will stop working. We keep a whitelist of tag names that
2051 // need these events. For other target tags, we prevent default on the event.
2052 if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
2059 _handlePointer(e, handler);
2062 obj['_leaflet_touchstart' + id] = onDown;
2063 obj.addEventListener(POINTER_DOWN, onDown, false);
2065 // need to keep track of what pointers and how many are active to provide e.touches emulation
2066 if (!_pointerDocListener) {
2067 // we listen documentElement as any drags that end by moving the touch off the screen get fired there
2068 document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true);
2069 document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true);
2070 document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true);
2071 document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
2073 _pointerDocListener = true;
2077 function _globalPointerDown(e) {
2078 _pointers[e.pointerId] = e;
2082 function _globalPointerMove(e) {
2083 if (_pointers[e.pointerId]) {
2084 _pointers[e.pointerId] = e;
2088 function _globalPointerUp(e) {
2089 delete _pointers[e.pointerId];
2093 function _handlePointer(e, handler) {
2095 for (var i in _pointers) {
2096 e.touches.push(_pointers[i]);
2098 e.changedTouches = [e];
2103 function _addPointerMove(obj, handler, id) {
2104 var onMove = function (e) {
2105 // don't fire touch moves when mouse isn't down
2106 if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
2108 _handlePointer(e, handler);
2111 obj['_leaflet_touchmove' + id] = onMove;
2112 obj.addEventListener(POINTER_MOVE, onMove, false);
2115 function _addPointerEnd(obj, handler, id) {
2116 var onUp = function (e) {
2117 _handlePointer(e, handler);
2120 obj['_leaflet_touchend' + id] = onUp;
2121 obj.addEventListener(POINTER_UP, onUp, false);
2122 obj.addEventListener(POINTER_CANCEL, onUp, false);
2126 * Extends the event handling code with double tap support for mobile browsers.
2129 var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart';
2130 var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend';
2131 var _pre = '_leaflet_';
2133 // inspired by Zepto touch code by Thomas Fuchs
2134 function addDoubleTapListener(obj, handler, id) {
2139 function onTouchStart(e) {
2143 if ((!edge) || e.pointerType === 'mouse') { return; }
2144 count = _pointersCount;
2146 count = e.touches.length;
2149 if (count > 1) { return; }
2151 var now = Date.now(),
2152 delta = now - (last || now);
2154 touch$$1 = e.touches ? e.touches[0] : e;
2155 doubleTap = (delta > 0 && delta <= delay);
2159 function onTouchEnd(e) {
2160 if (doubleTap && !touch$$1.cancelBubble) {
2162 if ((!edge) || e.pointerType === 'mouse') { return; }
2163 // work around .type being readonly with MSPointer* events
2167 for (i in touch$$1) {
2169 newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop;
2171 touch$$1 = newTouch;
2173 touch$$1.type = 'dblclick';
2179 obj[_pre + _touchstart + id] = onTouchStart;
2180 obj[_pre + _touchend + id] = onTouchEnd;
2181 obj[_pre + 'dblclick' + id] = handler;
2183 obj.addEventListener(_touchstart, onTouchStart, false);
2184 obj.addEventListener(_touchend, onTouchEnd, false);
2186 // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
2187 // the browser doesn't fire touchend/pointerup events but does fire
2188 // native dblclicks. See #4127.
2189 // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
2190 obj.addEventListener('dblclick', handler, false);
2195 function removeDoubleTapListener(obj, id) {
2196 var touchstart = obj[_pre + _touchstart + id],
2197 touchend = obj[_pre + _touchend + id],
2198 dblclick = obj[_pre + 'dblclick' + id];
2200 obj.removeEventListener(_touchstart, touchstart, false);
2201 obj.removeEventListener(_touchend, touchend, false);
2203 obj.removeEventListener('dblclick', dblclick, false);
2210 * @namespace DomEvent
2211 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
2214 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
2216 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
2217 // Adds a listener function (`fn`) to a particular DOM event type of the
2218 // element `el`. You can optionally specify the context of the listener
2219 // (object the `this` keyword will point to). You can also pass several
2220 // space-separated types (e.g. `'click dblclick'`).
2223 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
2224 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2225 function on(obj, types, fn, context) {
2227 if (typeof types === 'object') {
2228 for (var type in types) {
2229 addOne(obj, type, types[type], fn);
2232 types = splitWords(types);
2234 for (var i = 0, len = types.length; i < len; i++) {
2235 addOne(obj, types[i], fn, context);
2242 var eventsKey = '_leaflet_events';
2244 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
2245 // Removes a previously added listener function.
2246 // Note that if you passed a custom context to on, you must pass the same
2247 // context to `off` in order to remove the listener.
2250 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
2251 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2252 function off(obj, types, fn, context) {
2254 if (typeof types === 'object') {
2255 for (var type in types) {
2256 removeOne(obj, type, types[type], fn);
2259 types = splitWords(types);
2261 for (var i = 0, len = types.length; i < len; i++) {
2262 removeOne(obj, types[i], fn, context);
2265 for (var j in obj[eventsKey]) {
2266 removeOne(obj, j, obj[eventsKey][j]);
2268 delete obj[eventsKey];
2274 function addOne(obj, type, fn, context) {
2275 var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
2277 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
2279 var handler = function (e) {
2280 return fn.call(context || obj, e || window.event);
2283 var originalHandler = handler;
2285 if (pointer && type.indexOf('touch') === 0) {
2286 // Needs DomEvent.Pointer.js
2287 addPointerListener(obj, type, handler, id);
2289 } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
2290 !(pointer && chrome)) {
2291 // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
2293 addDoubleTapListener(obj, handler, id);
2295 } else if ('addEventListener' in obj) {
2297 if (type === 'mousewheel') {
2298 obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2300 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
2301 handler = function (e) {
2302 e = e || window.event;
2303 if (isExternalTarget(obj, e)) {
2307 obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
2310 if (type === 'click' && android) {
2311 handler = function (e) {
2312 filterClick(e, originalHandler);
2315 obj.addEventListener(type, handler, false);
2318 } else if ('attachEvent' in obj) {
2319 obj.attachEvent('on' + type, handler);
2322 obj[eventsKey] = obj[eventsKey] || {};
2323 obj[eventsKey][id] = handler;
2326 function removeOne(obj, type, fn, context) {
2328 var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
2329 handler = obj[eventsKey] && obj[eventsKey][id];
2331 if (!handler) { return this; }
2333 if (pointer && type.indexOf('touch') === 0) {
2334 removePointerListener(obj, type, id);
2336 } else if (touch && (type === 'dblclick') && removeDoubleTapListener &&
2337 !(pointer && chrome)) {
2338 removeDoubleTapListener(obj, id);
2340 } else if ('removeEventListener' in obj) {
2342 if (type === 'mousewheel') {
2343 obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2346 obj.removeEventListener(
2347 type === 'mouseenter' ? 'mouseover' :
2348 type === 'mouseleave' ? 'mouseout' : type, handler, false);
2351 } else if ('detachEvent' in obj) {
2352 obj.detachEvent('on' + type, handler);
2355 obj[eventsKey][id] = null;
2358 // @function stopPropagation(ev: DOMEvent): this
2359 // Stop the given event from propagation to parent elements. Used inside the listener functions:
2361 // L.DomEvent.on(div, 'click', function (ev) {
2362 // L.DomEvent.stopPropagation(ev);
2365 function stopPropagation(e) {
2367 if (e.stopPropagation) {
2368 e.stopPropagation();
2369 } else if (e.originalEvent) { // In case of Leaflet event.
2370 e.originalEvent._stopped = true;
2372 e.cancelBubble = true;
2379 // @function disableScrollPropagation(el: HTMLElement): this
2380 // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
2381 function disableScrollPropagation(el) {
2382 addOne(el, 'mousewheel', stopPropagation);
2386 // @function disableClickPropagation(el: HTMLElement): this
2387 // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
2388 // `'mousedown'` and `'touchstart'` events (plus browser variants).
2389 function disableClickPropagation(el) {
2390 on(el, 'mousedown touchstart dblclick', stopPropagation);
2391 addOne(el, 'click', fakeStop);
2395 // @function preventDefault(ev: DOMEvent): this
2396 // Prevents the default action of the DOM Event `ev` from happening (such as
2397 // following a link in the href of the a element, or doing a POST request
2398 // with page reload when a `<form>` is submitted).
2399 // Use it inside listener functions.
2400 function preventDefault(e) {
2401 if (e.preventDefault) {
2404 e.returnValue = false;
2409 // @function stop(ev: DOMEvent): this
2410 // Does `stopPropagation` and `preventDefault` at the same time.
2417 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
2418 // Gets normalized mouse position from a DOM event relative to the
2419 // `container` or to the whole page if not specified.
2420 function getMousePosition(e, container) {
2422 return new Point(e.clientX, e.clientY);
2425 var rect = container.getBoundingClientRect();
2427 var scaleX = rect.width / container.offsetWidth || 1;
2428 var scaleY = rect.height / container.offsetHeight || 1;
2430 e.clientX / scaleX - rect.left - container.clientLeft,
2431 e.clientY / scaleY - rect.top - container.clientTop);
2434 // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
2435 // and Firefox scrolls device pixels, not CSS pixels
2437 (win && chrome) ? 2 * window.devicePixelRatio :
2438 gecko ? window.devicePixelRatio : 1;
2440 // @function getWheelDelta(ev: DOMEvent): Number
2441 // Gets normalized wheel delta from a mousewheel DOM event, in vertical
2442 // pixels scrolled (negative if scrolling down).
2443 // Events from pointing devices without precise scrolling are mapped to
2444 // a best guess of 60 pixels.
2445 function getWheelDelta(e) {
2446 return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
2447 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
2448 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
2449 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
2450 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
2451 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
2452 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
2453 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
2457 var skipEvents = {};
2459 function fakeStop(e) {
2460 // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
2461 skipEvents[e.type] = true;
2464 function skipped(e) {
2465 var events = skipEvents[e.type];
2466 // reset when checking, as it's only used in map container and propagates outside of the map
2467 skipEvents[e.type] = false;
2471 // check if element really left/entered the event target (for mouseenter/mouseleave)
2472 function isExternalTarget(el, e) {
2474 var related = e.relatedTarget;
2476 if (!related) { return true; }
2479 while (related && (related !== el)) {
2480 related = related.parentNode;
2485 return (related !== el);
2490 // this is a horrible workaround for a bug in Android where a single touch triggers two click events
2491 function filterClick(e, handler) {
2492 var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
2493 elapsed = lastClick && (timeStamp - lastClick);
2495 // are they closer together than 500ms yet more than 100ms?
2496 // Android typically triggers them ~300ms apart while multiple listeners
2497 // on the same event should be triggered far faster;
2498 // or check if click is simulated on the element, and if it is, reject any non-simulated events
2500 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
2504 lastClick = timeStamp;
2512 var DomEvent = (Object.freeze || Object)({
2515 stopPropagation: stopPropagation,
2516 disableScrollPropagation: disableScrollPropagation,
2517 disableClickPropagation: disableClickPropagation,
2518 preventDefault: preventDefault,
2520 getMousePosition: getMousePosition,
2521 getWheelDelta: getWheelDelta,
2524 isExternalTarget: isExternalTarget,
2530 * @namespace DomUtil
2532 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
2533 * tree, used by Leaflet internally.
2535 * Most functions expecting or returning a `HTMLElement` also work for
2536 * SVG elements. The only difference is that classes refer to CSS classes
2537 * in HTML and SVG classes in SVG.
2541 // @property TRANSFORM: String
2542 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
2543 var TRANSFORM = testProp(
2544 ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
2546 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
2547 // the same for the transitionend event, in particular the Android 4.1 stock browser
2549 // @property TRANSITION: String
2550 // Vendor-prefixed transition style name.
2551 var TRANSITION = testProp(
2552 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
2554 // @property TRANSITION_END: String
2555 // Vendor-prefixed transitionend event name.
2556 var TRANSITION_END =
2557 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
2560 // @function get(id: String|HTMLElement): HTMLElement
2561 // Returns an element given its DOM id, or returns the element itself
2562 // if it was passed directly.
2564 return typeof id === 'string' ? document.getElementById(id) : id;
2567 // @function getStyle(el: HTMLElement, styleAttrib: String): String
2568 // Returns the value for a certain style attribute on an element,
2569 // including computed values or values set through CSS.
2570 function getStyle(el, style) {
2571 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
2573 if ((!value || value === 'auto') && document.defaultView) {
2574 var css = document.defaultView.getComputedStyle(el, null);
2575 value = css ? css[style] : null;
2577 return value === 'auto' ? null : value;
2580 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
2581 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
2582 function create$1(tagName, className, container) {
2583 var el = document.createElement(tagName);
2584 el.className = className || '';
2587 container.appendChild(el);
2592 // @function remove(el: HTMLElement)
2593 // Removes `el` from its parent element
2594 function remove(el) {
2595 var parent = el.parentNode;
2597 parent.removeChild(el);
2601 // @function empty(el: HTMLElement)
2602 // Removes all of `el`'s children elements from `el`
2603 function empty(el) {
2604 while (el.firstChild) {
2605 el.removeChild(el.firstChild);
2609 // @function toFront(el: HTMLElement)
2610 // Makes `el` the last child of its parent, so it renders in front of the other children.
2611 function toFront(el) {
2612 var parent = el.parentNode;
2613 if (parent.lastChild !== el) {
2614 parent.appendChild(el);
2618 // @function toBack(el: HTMLElement)
2619 // Makes `el` the first child of its parent, so it renders behind the other children.
2620 function toBack(el) {
2621 var parent = el.parentNode;
2622 if (parent.firstChild !== el) {
2623 parent.insertBefore(el, parent.firstChild);
2627 // @function hasClass(el: HTMLElement, name: String): Boolean
2628 // Returns `true` if the element's class attribute contains `name`.
2629 function hasClass(el, name) {
2630 if (el.classList !== undefined) {
2631 return el.classList.contains(name);
2633 var className = getClass(el);
2634 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
2637 // @function addClass(el: HTMLElement, name: String)
2638 // Adds `name` to the element's class attribute.
2639 function addClass(el, name) {
2640 if (el.classList !== undefined) {
2641 var classes = splitWords(name);
2642 for (var i = 0, len = classes.length; i < len; i++) {
2643 el.classList.add(classes[i]);
2645 } else if (!hasClass(el, name)) {
2646 var className = getClass(el);
2647 setClass(el, (className ? className + ' ' : '') + name);
2651 // @function removeClass(el: HTMLElement, name: String)
2652 // Removes `name` from the element's class attribute.
2653 function removeClass(el, name) {
2654 if (el.classList !== undefined) {
2655 el.classList.remove(name);
2657 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
2661 // @function setClass(el: HTMLElement, name: String)
2662 // Sets the element's class.
2663 function setClass(el, name) {
2664 if (el.className.baseVal === undefined) {
2665 el.className = name;
2667 // in case of SVG element
2668 el.className.baseVal = name;
2672 // @function getClass(el: HTMLElement): String
2673 // Returns the element's class.
2674 function getClass(el) {
2675 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
2678 // @function setOpacity(el: HTMLElement, opacity: Number)
2679 // Set the opacity of an element (including old IE support).
2680 // `opacity` must be a number from `0` to `1`.
2681 function setOpacity(el, value) {
2682 if ('opacity' in el.style) {
2683 el.style.opacity = value;
2684 } else if ('filter' in el.style) {
2685 _setOpacityIE(el, value);
2689 function _setOpacityIE(el, value) {
2691 filterName = 'DXImageTransform.Microsoft.Alpha';
2693 // filters collection throws an error if we try to retrieve a filter that doesn't exist
2695 filter = el.filters.item(filterName);
2697 // don't set opacity to 1 if we haven't already set an opacity,
2698 // it isn't needed and breaks transparent pngs.
2699 if (value === 1) { return; }
2702 value = Math.round(value * 100);
2705 filter.Enabled = (value !== 100);
2706 filter.Opacity = value;
2708 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
2712 // @function testProp(props: String[]): String|false
2713 // Goes through the array of style names and returns the first name
2714 // that is a valid style name for an element. If no such name is found,
2715 // it returns false. Useful for vendor-prefixed styles like `transform`.
2716 function testProp(props) {
2717 var style = document.documentElement.style;
2719 for (var i = 0; i < props.length; i++) {
2720 if (props[i] in style) {
2727 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
2728 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
2729 // and optionally scaled by `scale`. Does not have an effect if the
2730 // browser doesn't support 3D CSS transforms.
2731 function setTransform(el, offset, scale) {
2732 var pos = offset || new Point(0, 0);
2734 el.style[TRANSFORM] =
2736 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
2737 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
2738 (scale ? ' scale(' + scale + ')' : '');
2741 // @function setPosition(el: HTMLElement, position: Point)
2742 // Sets the position of `el` to coordinates specified by `position`,
2743 // using CSS translate or top/left positioning depending on the browser
2744 // (used by Leaflet internally to position its layers).
2745 function setPosition(el, point) {
2748 el._leaflet_pos = point;
2752 setTransform(el, point);
2754 el.style.left = point.x + 'px';
2755 el.style.top = point.y + 'px';
2759 // @function getPosition(el: HTMLElement): Point
2760 // Returns the coordinates of an element previously positioned with setPosition.
2761 function getPosition(el) {
2762 // this method is only used for elements previously positioned using setPosition,
2763 // so it's safe to cache the position for performance
2765 return el._leaflet_pos || new Point(0, 0);
2768 // @function disableTextSelection()
2769 // Prevents the user from generating `selectstart` DOM events, usually generated
2770 // when the user drags the mouse through a page with text. Used internally
2771 // by Leaflet to override the behaviour of any click-and-drag interaction on
2772 // the map. Affects drag interactions on the whole document.
2774 // @function enableTextSelection()
2775 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
2776 var disableTextSelection;
2777 var enableTextSelection;
2779 if ('onselectstart' in document) {
2780 disableTextSelection = function () {
2781 on(window, 'selectstart', preventDefault);
2783 enableTextSelection = function () {
2784 off(window, 'selectstart', preventDefault);
2787 var userSelectProperty = testProp(
2788 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
2790 disableTextSelection = function () {
2791 if (userSelectProperty) {
2792 var style = document.documentElement.style;
2793 _userSelect = style[userSelectProperty];
2794 style[userSelectProperty] = 'none';
2797 enableTextSelection = function () {
2798 if (userSelectProperty) {
2799 document.documentElement.style[userSelectProperty] = _userSelect;
2800 _userSelect = undefined;
2805 // @function disableImageDrag()
2806 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
2807 // for `dragstart` DOM events, usually generated when the user drags an image.
2808 function disableImageDrag() {
2809 on(window, 'dragstart', preventDefault);
2812 // @function enableImageDrag()
2813 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
2814 function enableImageDrag() {
2815 off(window, 'dragstart', preventDefault);
2818 var _outlineElement;
2820 // @function preventOutline(el: HTMLElement)
2821 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
2822 // of the element `el` invisible. Used internally by Leaflet to prevent
2823 // focusable elements from displaying an outline when the user performs a
2824 // drag interaction on them.
2825 function preventOutline(element) {
2826 while (element.tabIndex === -1) {
2827 element = element.parentNode;
2829 if (!element.style) { return; }
2831 _outlineElement = element;
2832 _outlineStyle = element.style.outline;
2833 element.style.outline = 'none';
2834 on(window, 'keydown', restoreOutline);
2837 // @function restoreOutline()
2838 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
2839 function restoreOutline() {
2840 if (!_outlineElement) { return; }
2841 _outlineElement.style.outline = _outlineStyle;
2842 _outlineElement = undefined;
2843 _outlineStyle = undefined;
2844 off(window, 'keydown', restoreOutline);
2848 var DomUtil = (Object.freeze || Object)({
2849 TRANSFORM: TRANSFORM,
2850 TRANSITION: TRANSITION,
2851 TRANSITION_END: TRANSITION_END,
2861 removeClass: removeClass,
2864 setOpacity: setOpacity,
2866 setTransform: setTransform,
2867 setPosition: setPosition,
2868 getPosition: getPosition,
2869 disableTextSelection: disableTextSelection,
2870 enableTextSelection: enableTextSelection,
2871 disableImageDrag: disableImageDrag,
2872 enableImageDrag: enableImageDrag,
2873 preventOutline: preventOutline,
2874 restoreOutline: restoreOutline
2878 * @class PosAnimation
2879 * @aka L.PosAnimation
2881 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
2885 * var fx = new L.PosAnimation();
2886 * fx.run(el, [300, 500], 0.5);
2889 * @constructor L.PosAnimation()
2890 * Creates a `PosAnimation` object.
2894 var PosAnimation = Evented.extend({
2896 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
2897 // Run an animation of a given element to a new position, optionally setting
2898 // duration in seconds (`0.25` by default) and easing linearity factor (3rd
2899 // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
2900 // `0.5` by default).
2901 run: function (el, newPos, duration, easeLinearity) {
2905 this._inProgress = true;
2906 this._duration = duration || 0.25;
2907 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
2909 this._startPos = getPosition(el);
2910 this._offset = newPos.subtract(this._startPos);
2911 this._startTime = +new Date();
2913 // @event start: Event
2914 // Fired when the animation starts
2921 // Stops the animation (if currently running).
2923 if (!this._inProgress) { return; }
2929 _animate: function () {
2931 this._animId = requestAnimFrame(this._animate, this);
2935 _step: function (round) {
2936 var elapsed = (+new Date()) - this._startTime,
2937 duration = this._duration * 1000;
2939 if (elapsed < duration) {
2940 this._runFrame(this._easeOut(elapsed / duration), round);
2947 _runFrame: function (progress, round) {
2948 var pos = this._startPos.add(this._offset.multiplyBy(progress));
2952 setPosition(this._el, pos);
2954 // @event step: Event
2955 // Fired continuously during the animation.
2959 _complete: function () {
2960 cancelAnimFrame(this._animId);
2962 this._inProgress = false;
2963 // @event end: Event
2964 // Fired when the animation ends.
2968 _easeOut: function (t) {
2969 return 1 - Math.pow(1 - t, this._easeOutPower);
2978 * The central class of the API — it is used to create a map on a page and manipulate it.
2983 * // initialize the map on the "map" div with a given center and zoom
2984 * var map = L.map('map', {
2985 * center: [51.505, -0.09],
2992 var Map = Evented.extend({
2995 // @section Map State Options
2996 // @option crs: CRS = L.CRS.EPSG3857
2997 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
2998 // sure what it means.
3001 // @option center: LatLng = undefined
3002 // Initial geographic center of the map
3005 // @option zoom: Number = undefined
3006 // Initial map zoom level
3009 // @option minZoom: Number = *
3010 // Minimum zoom level of the map.
3011 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3012 // the lowest of their `minZoom` options will be used instead.
3015 // @option maxZoom: Number = *
3016 // Maximum zoom level of the map.
3017 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3018 // the highest of their `maxZoom` options will be used instead.
3021 // @option layers: Layer[] = []
3022 // Array of layers that will be added to the map initially
3025 // @option maxBounds: LatLngBounds = null
3026 // When this option is set, the map restricts the view to the given
3027 // geographical bounds, bouncing the user back if the user tries to pan
3028 // outside the view. To set the restriction dynamically, use
3029 // [`setMaxBounds`](#map-setmaxbounds) method.
3030 maxBounds: undefined,
3032 // @option renderer: Renderer = *
3033 // The default method for drawing vector layers on the map. `L.SVG`
3034 // or `L.Canvas` by default depending on browser support.
3035 renderer: undefined,
3038 // @section Animation Options
3039 // @option zoomAnimation: Boolean = true
3040 // Whether the map zoom animation is enabled. By default it's enabled
3041 // in all browsers that support CSS3 Transitions except Android.
3042 zoomAnimation: true,
3044 // @option zoomAnimationThreshold: Number = 4
3045 // Won't animate zoom if the zoom difference exceeds this value.
3046 zoomAnimationThreshold: 4,
3048 // @option fadeAnimation: Boolean = true
3049 // Whether the tile fade animation is enabled. By default it's enabled
3050 // in all browsers that support CSS3 Transitions except Android.
3051 fadeAnimation: true,
3053 // @option markerZoomAnimation: Boolean = true
3054 // Whether markers animate their zoom with the zoom animation, if disabled
3055 // they will disappear for the length of the animation. By default it's
3056 // enabled in all browsers that support CSS3 Transitions except Android.
3057 markerZoomAnimation: true,
3059 // @option transform3DLimit: Number = 2^23
3060 // Defines the maximum size of a CSS translation transform. The default
3061 // value should not be changed unless a web browser positions layers in
3062 // the wrong place after doing a large `panBy`.
3063 transform3DLimit: 8388608, // Precision limit of a 32-bit float
3065 // @section Interaction Options
3066 // @option zoomSnap: Number = 1
3067 // Forces the map's zoom level to always be a multiple of this, particularly
3068 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
3069 // By default, the zoom level snaps to the nearest integer; lower values
3070 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
3071 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
3074 // @option zoomDelta: Number = 1
3075 // Controls how much the map's zoom level will change after a
3076 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
3077 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
3078 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
3081 // @option trackResize: Boolean = true
3082 // Whether the map automatically handles browser window resize to update itself.
3086 initialize: function (id, options) { // (HTMLElement or String, Object)
3087 options = setOptions(this, options);
3089 this._initContainer(id);
3092 // hack for https://github.com/Leaflet/Leaflet/issues/1980
3093 this._onResize = bind(this._onResize, this);
3097 if (options.maxBounds) {
3098 this.setMaxBounds(options.maxBounds);
3101 if (options.zoom !== undefined) {
3102 this._zoom = this._limitZoom(options.zoom);
3105 if (options.center && options.zoom !== undefined) {
3106 this.setView(toLatLng(options.center), options.zoom, {reset: true});
3109 this._handlers = [];
3111 this._zoomBoundLayers = {};
3112 this._sizeChanged = true;
3114 this.callInitHooks();
3116 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
3117 this._zoomAnimated = TRANSITION && any3d && !mobileOpera &&
3118 this.options.zoomAnimation;
3120 // zoom transitions run with the same duration for all layers, so if one of transitionend events
3121 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
3122 if (this._zoomAnimated) {
3123 this._createAnimProxy();
3124 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
3127 this._addLayers(this.options.layers);
3131 // @section Methods for modifying map state
3133 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
3134 // Sets the view of the map (geographical center and zoom) with the given
3135 // animation options.
3136 setView: function (center, zoom, options) {
3138 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
3139 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
3140 options = options || {};
3144 if (this._loaded && !options.reset && options !== true) {
3146 if (options.animate !== undefined) {
3147 options.zoom = extend({animate: options.animate}, options.zoom);
3148 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
3151 // try animating pan or zoom
3152 var moved = (this._zoom !== zoom) ?
3153 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
3154 this._tryAnimatedPan(center, options.pan);
3157 // prevent resize handler call, the view will refresh after animation anyway
3158 clearTimeout(this._sizeTimer);
3163 // animation didn't start, just reset the map view
3164 this._resetView(center, zoom);
3169 // @method setZoom(zoom: Number, options?: Zoom/pan options): this
3170 // Sets the zoom of the map.
3171 setZoom: function (zoom, options) {
3172 if (!this._loaded) {
3176 return this.setView(this.getCenter(), zoom, {zoom: options});
3179 // @method zoomIn(delta?: Number, options?: Zoom options): this
3180 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3181 zoomIn: function (delta, options) {
3182 delta = delta || (any3d ? this.options.zoomDelta : 1);
3183 return this.setZoom(this._zoom + delta, options);
3186 // @method zoomOut(delta?: Number, options?: Zoom options): this
3187 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3188 zoomOut: function (delta, options) {
3189 delta = delta || (any3d ? this.options.zoomDelta : 1);
3190 return this.setZoom(this._zoom - delta, options);
3193 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
3194 // Zooms the map while keeping a specified geographical point on the map
3195 // stationary (e.g. used internally for scroll zoom and double-click zoom).
3197 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
3198 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
3199 setZoomAround: function (latlng, zoom, options) {
3200 var scale = this.getZoomScale(zoom),
3201 viewHalf = this.getSize().divideBy(2),
3202 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
3204 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
3205 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
3207 return this.setView(newCenter, zoom, {zoom: options});
3210 _getBoundsCenterZoom: function (bounds, options) {
3212 options = options || {};
3213 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
3215 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3216 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3218 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
3220 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
3222 if (zoom === Infinity) {
3224 center: bounds.getCenter(),
3229 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
3231 swPoint = this.project(bounds.getSouthWest(), zoom),
3232 nePoint = this.project(bounds.getNorthEast(), zoom),
3233 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
3241 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
3242 // Sets a map view that contains the given geographical bounds with the
3243 // maximum zoom level possible.
3244 fitBounds: function (bounds, options) {
3246 bounds = toLatLngBounds(bounds);
3248 if (!bounds.isValid()) {
3249 throw new Error('Bounds are not valid.');
3252 var target = this._getBoundsCenterZoom(bounds, options);
3253 return this.setView(target.center, target.zoom, options);
3256 // @method fitWorld(options?: fitBounds options): this
3257 // Sets a map view that mostly contains the whole world with the maximum
3258 // zoom level possible.
3259 fitWorld: function (options) {
3260 return this.fitBounds([[-90, -180], [90, 180]], options);
3263 // @method panTo(latlng: LatLng, options?: Pan options): this
3264 // Pans the map to a given center.
3265 panTo: function (center, options) { // (LatLng)
3266 return this.setView(center, this._zoom, {pan: options});
3269 // @method panBy(offset: Point, options?: Pan options): this
3270 // Pans the map by a given number of pixels (animated).
3271 panBy: function (offset, options) {
3272 offset = toPoint(offset).round();
3273 options = options || {};
3275 if (!offset.x && !offset.y) {
3276 return this.fire('moveend');
3278 // If we pan too far, Chrome gets issues with tiles
3279 // and makes them disappear or appear in the wrong place (slightly offset) #2602
3280 if (options.animate !== true && !this.getSize().contains(offset)) {
3281 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
3285 if (!this._panAnim) {
3286 this._panAnim = new PosAnimation();
3289 'step': this._onPanTransitionStep,
3290 'end': this._onPanTransitionEnd
3294 // don't fire movestart if animating inertia
3295 if (!options.noMoveStart) {
3296 this.fire('movestart');
3299 // animate pan unless animate: false specified
3300 if (options.animate !== false) {
3301 addClass(this._mapPane, 'leaflet-pan-anim');
3303 var newPos = this._getMapPanePos().subtract(offset).round();
3304 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
3306 this._rawPanBy(offset);
3307 this.fire('move').fire('moveend');
3313 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
3314 // Sets the view of the map (geographical center and zoom) performing a smooth
3315 // pan-zoom animation.
3316 flyTo: function (targetCenter, targetZoom, options) {
3318 options = options || {};
3319 if (options.animate === false || !any3d) {
3320 return this.setView(targetCenter, targetZoom, options);
3325 var from = this.project(this.getCenter()),
3326 to = this.project(targetCenter),
3327 size = this.getSize(),
3328 startZoom = this._zoom;
3330 targetCenter = toLatLng(targetCenter);
3331 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
3333 var w0 = Math.max(size.x, size.y),
3334 w1 = w0 * this.getZoomScale(startZoom, targetZoom),
3335 u1 = (to.distanceTo(from)) || 1,
3340 var s1 = i ? -1 : 1,
3342 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
3343 b1 = 2 * s2 * rho2 * u1,
3345 sq = Math.sqrt(b * b + 1) - b;
3347 // workaround for floating point precision bug when sq = 0, log = -Infinite,
3348 // thus triggering an infinite loop in flyTo
3349 var log = sq < 0.000000001 ? -18 : Math.log(sq);
3354 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
3355 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
3356 function tanh(n) { return sinh(n) / cosh(n); }
3360 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
3361 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
3363 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
3365 var start = Date.now(),
3366 S = (r(1) - r0) / rho,
3367 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
3370 var t = (Date.now() - start) / duration,
3374 this._flyToFrame = requestAnimFrame(frame, this);
3377 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
3378 this.getScaleZoom(w0 / w(s), startZoom),
3383 ._move(targetCenter, targetZoom)
3388 this._moveStart(true, options.noMoveStart);
3394 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
3395 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
3396 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
3397 flyToBounds: function (bounds, options) {
3398 var target = this._getBoundsCenterZoom(bounds, options);
3399 return this.flyTo(target.center, target.zoom, options);
3402 // @method setMaxBounds(bounds: Bounds): this
3403 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
3404 setMaxBounds: function (bounds) {
3405 bounds = toLatLngBounds(bounds);
3407 if (!bounds.isValid()) {
3408 this.options.maxBounds = null;
3409 return this.off('moveend', this._panInsideMaxBounds);
3410 } else if (this.options.maxBounds) {
3411 this.off('moveend', this._panInsideMaxBounds);
3414 this.options.maxBounds = bounds;
3417 this._panInsideMaxBounds();
3420 return this.on('moveend', this._panInsideMaxBounds);
3423 // @method setMinZoom(zoom: Number): this
3424 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
3425 setMinZoom: function (zoom) {
3426 var oldZoom = this.options.minZoom;
3427 this.options.minZoom = zoom;
3429 if (this._loaded && oldZoom !== zoom) {
3430 this.fire('zoomlevelschange');
3432 if (this.getZoom() < this.options.minZoom) {
3433 return this.setZoom(zoom);
3440 // @method setMaxZoom(zoom: Number): this
3441 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
3442 setMaxZoom: function (zoom) {
3443 var oldZoom = this.options.maxZoom;
3444 this.options.maxZoom = zoom;
3446 if (this._loaded && oldZoom !== zoom) {
3447 this.fire('zoomlevelschange');
3449 if (this.getZoom() > this.options.maxZoom) {
3450 return this.setZoom(zoom);
3457 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
3458 // 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.
3459 panInsideBounds: function (bounds, options) {
3460 this._enforcingBounds = true;
3461 var center = this.getCenter(),
3462 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
3464 if (!center.equals(newCenter)) {
3465 this.panTo(newCenter, options);
3468 this._enforcingBounds = false;
3472 // @method invalidateSize(options: Zoom/pan options): this
3473 // Checks if the map container size changed and updates the map if so —
3474 // call it after you've changed the map size dynamically, also animating
3475 // pan by default. If `options.pan` is `false`, panning will not occur.
3476 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
3477 // that it doesn't happen often even if the method is called many
3481 // @method invalidateSize(animate: Boolean): this
3482 // Checks if the map container size changed and updates the map if so —
3483 // call it after you've changed the map size dynamically, also animating
3485 invalidateSize: function (options) {
3486 if (!this._loaded) { return this; }
3491 }, options === true ? {animate: true} : options);
3493 var oldSize = this.getSize();
3494 this._sizeChanged = true;
3495 this._lastCenter = null;
3497 var newSize = this.getSize(),
3498 oldCenter = oldSize.divideBy(2).round(),
3499 newCenter = newSize.divideBy(2).round(),
3500 offset = oldCenter.subtract(newCenter);
3502 if (!offset.x && !offset.y) { return this; }
3504 if (options.animate && options.pan) {
3509 this._rawPanBy(offset);
3514 if (options.debounceMoveend) {
3515 clearTimeout(this._sizeTimer);
3516 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
3518 this.fire('moveend');
3522 // @section Map state change events
3523 // @event resize: ResizeEvent
3524 // Fired when the map is resized.
3525 return this.fire('resize', {
3531 // @section Methods for modifying map state
3532 // @method stop(): this
3533 // Stops the currently running `panTo` or `flyTo` animation, if any.
3535 this.setZoom(this._limitZoom(this._zoom));
3536 if (!this.options.zoomSnap) {
3537 this.fire('viewreset');
3539 return this._stop();
3542 // @section Geolocation methods
3543 // @method locate(options?: Locate options): this
3544 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
3545 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
3546 // and optionally sets the map view to the user's location with respect to
3547 // detection accuracy (or to the world view if geolocation failed).
3548 // Note that, if your page doesn't use HTTPS, this method will fail in
3549 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
3550 // See `Locate options` for more details.
3551 locate: function (options) {
3553 options = this._locateOptions = extend({
3557 // maxZoom: <Number>
3559 // enableHighAccuracy: false
3562 if (!('geolocation' in navigator)) {
3563 this._handleGeolocationError({
3565 message: 'Geolocation not supported.'
3570 var onResponse = bind(this._handleGeolocationResponse, this),
3571 onError = bind(this._handleGeolocationError, this);
3573 if (options.watch) {
3574 this._locationWatchId =
3575 navigator.geolocation.watchPosition(onResponse, onError, options);
3577 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
3582 // @method stopLocate(): this
3583 // Stops watching location previously initiated by `map.locate({watch: true})`
3584 // and aborts resetting the map view if map.locate was called with
3585 // `{setView: true}`.
3586 stopLocate: function () {
3587 if (navigator.geolocation && navigator.geolocation.clearWatch) {
3588 navigator.geolocation.clearWatch(this._locationWatchId);
3590 if (this._locateOptions) {
3591 this._locateOptions.setView = false;
3596 _handleGeolocationError: function (error) {
3598 message = error.message ||
3599 (c === 1 ? 'permission denied' :
3600 (c === 2 ? 'position unavailable' : 'timeout'));
3602 if (this._locateOptions.setView && !this._loaded) {
3606 // @section Location events
3607 // @event locationerror: ErrorEvent
3608 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
3609 this.fire('locationerror', {
3611 message: 'Geolocation error: ' + message + '.'
3615 _handleGeolocationResponse: function (pos) {
3616 var lat = pos.coords.latitude,
3617 lng = pos.coords.longitude,
3618 latlng = new LatLng(lat, lng),
3619 bounds = latlng.toBounds(pos.coords.accuracy),
3620 options = this._locateOptions;
3622 if (options.setView) {
3623 var zoom = this.getBoundsZoom(bounds);
3624 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
3630 timestamp: pos.timestamp
3633 for (var i in pos.coords) {
3634 if (typeof pos.coords[i] === 'number') {
3635 data[i] = pos.coords[i];
3639 // @event locationfound: LocationEvent
3640 // Fired when geolocation (using the [`locate`](#map-locate) method)
3641 // went successfully.
3642 this.fire('locationfound', data);
3645 // TODO Appropriate docs section?
3646 // @section Other Methods
3647 // @method addHandler(name: String, HandlerClass: Function): this
3648 // Adds a new `Handler` to the map, given its name and constructor function.
3649 addHandler: function (name, HandlerClass) {
3650 if (!HandlerClass) { return this; }
3652 var handler = this[name] = new HandlerClass(this);
3654 this._handlers.push(handler);
3656 if (this.options[name]) {
3663 // @method remove(): this
3664 // Destroys the map and clears all related event listeners.
3665 remove: function () {
3667 this._initEvents(true);
3669 if (this._containerId !== this._container._leaflet_id) {
3670 throw new Error('Map container is being reused by another instance');
3674 // throws error in IE6-8
3675 delete this._container._leaflet_id;
3676 delete this._containerId;
3679 this._container._leaflet_id = undefined;
3681 this._containerId = undefined;
3684 if (this._locationWatchId !== undefined) {
3690 remove(this._mapPane);
3692 if (this._clearControlPos) {
3693 this._clearControlPos();
3696 this._clearHandlers();
3699 // @section Map state change events
3700 // @event unload: Event
3701 // Fired when the map is destroyed with [remove](#map-remove) method.
3702 this.fire('unload');
3706 for (i in this._layers) {
3707 this._layers[i].remove();
3709 for (i in this._panes) {
3710 remove(this._panes[i]);
3715 delete this._mapPane;
3716 delete this._renderer;
3721 // @section Other Methods
3722 // @method createPane(name: String, container?: HTMLElement): HTMLElement
3723 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
3724 // then returns it. The pane is created as a child of `container`, or
3725 // as a child of the main map pane if not set.
3726 createPane: function (name, container) {
3727 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3728 pane = create$1('div', className, container || this._mapPane);
3731 this._panes[name] = pane;
3736 // @section Methods for Getting Map State
3738 // @method getCenter(): LatLng
3739 // Returns the geographical center of the map view
3740 getCenter: function () {
3741 this._checkIfLoaded();
3743 if (this._lastCenter && !this._moved()) {
3744 return this._lastCenter;
3746 return this.layerPointToLatLng(this._getCenterLayerPoint());
3749 // @method getZoom(): Number
3750 // Returns the current zoom level of the map view
3751 getZoom: function () {
3755 // @method getBounds(): LatLngBounds
3756 // Returns the geographical bounds visible in the current map view
3757 getBounds: function () {
3758 var bounds = this.getPixelBounds(),
3759 sw = this.unproject(bounds.getBottomLeft()),
3760 ne = this.unproject(bounds.getTopRight());
3762 return new LatLngBounds(sw, ne);
3765 // @method getMinZoom(): Number
3766 // 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.
3767 getMinZoom: function () {
3768 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3771 // @method getMaxZoom(): Number
3772 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3773 getMaxZoom: function () {
3774 return this.options.maxZoom === undefined ?
3775 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3776 this.options.maxZoom;
3779 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number
3780 // Returns the maximum zoom level on which the given bounds fit to the map
3781 // view in its entirety. If `inside` (optional) is set to `true`, the method
3782 // instead returns the minimum zoom level on which the map view fits into
3783 // the given bounds in its entirety.
3784 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3785 bounds = toLatLngBounds(bounds);
3786 padding = toPoint(padding || [0, 0]);
3788 var zoom = this.getZoom() || 0,
3789 min = this.getMinZoom(),
3790 max = this.getMaxZoom(),
3791 nw = bounds.getNorthWest(),
3792 se = bounds.getSouthEast(),
3793 size = this.getSize().subtract(padding),
3794 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3795 snap = any3d ? this.options.zoomSnap : 1,
3796 scalex = size.x / boundsSize.x,
3797 scaley = size.y / boundsSize.y,
3798 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
3800 zoom = this.getScaleZoom(scale, zoom);
3803 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3804 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3807 return Math.max(min, Math.min(max, zoom));
3810 // @method getSize(): Point
3811 // Returns the current size of the map container (in pixels).
3812 getSize: function () {
3813 if (!this._size || this._sizeChanged) {
3814 this._size = new Point(
3815 this._container.clientWidth || 0,
3816 this._container.clientHeight || 0);
3818 this._sizeChanged = false;
3820 return this._size.clone();
3823 // @method getPixelBounds(): Bounds
3824 // Returns the bounds of the current map view in projected pixel
3825 // coordinates (sometimes useful in layer and overlay implementations).
3826 getPixelBounds: function (center, zoom) {
3827 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3828 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3831 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3832 // the map pane? "left point of the map layer" can be confusing, specially
3833 // since there can be negative offsets.
3834 // @method getPixelOrigin(): Point
3835 // Returns the projected pixel coordinates of the top left point of
3836 // the map layer (useful in custom layer and overlay implementations).
3837 getPixelOrigin: function () {
3838 this._checkIfLoaded();
3839 return this._pixelOrigin;
3842 // @method getPixelWorldBounds(zoom?: Number): Bounds
3843 // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3844 // If `zoom` is omitted, the map's current zoom level is used.
3845 getPixelWorldBounds: function (zoom) {
3846 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3849 // @section Other Methods
3851 // @method getPane(pane: String|HTMLElement): HTMLElement
3852 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3853 getPane: function (pane) {
3854 return typeof pane === 'string' ? this._panes[pane] : pane;
3857 // @method getPanes(): Object
3858 // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3859 // the panes as values.
3860 getPanes: function () {
3864 // @method getContainer: HTMLElement
3865 // Returns the HTML element that contains the map.
3866 getContainer: function () {
3867 return this._container;
3871 // @section Conversion Methods
3873 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3874 // Returns the scale factor to be applied to a map transition from zoom level
3875 // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3876 getZoomScale: function (toZoom, fromZoom) {
3877 // TODO replace with universal implementation after refactoring projections
3878 var crs = this.options.crs;
3879 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3880 return crs.scale(toZoom) / crs.scale(fromZoom);
3883 // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3884 // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3885 // level and everything is scaled by a factor of `scale`. Inverse of
3886 // [`getZoomScale`](#map-getZoomScale).
3887 getScaleZoom: function (scale, fromZoom) {
3888 var crs = this.options.crs;
3889 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3890 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3891 return isNaN(zoom) ? Infinity : zoom;
3894 // @method project(latlng: LatLng, zoom: Number): Point
3895 // Projects a geographical coordinate `LatLng` according to the projection
3896 // of the map's CRS, then scales it according to `zoom` and the CRS's
3897 // `Transformation`. The result is pixel coordinate relative to
3899 project: function (latlng, zoom) {
3900 zoom = zoom === undefined ? this._zoom : zoom;
3901 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
3904 // @method unproject(point: Point, zoom: Number): LatLng
3905 // Inverse of [`project`](#map-project).
3906 unproject: function (point, zoom) {
3907 zoom = zoom === undefined ? this._zoom : zoom;
3908 return this.options.crs.pointToLatLng(toPoint(point), zoom);
3911 // @method layerPointToLatLng(point: Point): LatLng
3912 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3913 // returns the corresponding geographical coordinate (for the current zoom level).
3914 layerPointToLatLng: function (point) {
3915 var projectedPoint = toPoint(point).add(this.getPixelOrigin());
3916 return this.unproject(projectedPoint);
3919 // @method latLngToLayerPoint(latlng: LatLng): Point
3920 // Given a geographical coordinate, returns the corresponding pixel coordinate
3921 // relative to the [origin pixel](#map-getpixelorigin).
3922 latLngToLayerPoint: function (latlng) {
3923 var projectedPoint = this.project(toLatLng(latlng))._round();
3924 return projectedPoint._subtract(this.getPixelOrigin());
3927 // @method wrapLatLng(latlng: LatLng): LatLng
3928 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
3929 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
3931 // By default this means longitude is wrapped around the dateline so its
3932 // value is between -180 and +180 degrees.
3933 wrapLatLng: function (latlng) {
3934 return this.options.crs.wrapLatLng(toLatLng(latlng));
3937 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
3938 // Returns a `LatLngBounds` with the same size as the given one, ensuring that
3939 // its center is within the CRS's bounds.
3940 // By default this means the center longitude is wrapped around the dateline so its
3941 // value is between -180 and +180 degrees, and the majority of the bounds
3942 // overlaps the CRS's bounds.
3943 wrapLatLngBounds: function (latlng) {
3944 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
3947 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
3948 // Returns the distance between two geographical coordinates according to
3949 // the map's CRS. By default this measures distance in meters.
3950 distance: function (latlng1, latlng2) {
3951 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
3954 // @method containerPointToLayerPoint(point: Point): Point
3955 // Given a pixel coordinate relative to the map container, returns the corresponding
3956 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
3957 containerPointToLayerPoint: function (point) { // (Point)
3958 return toPoint(point).subtract(this._getMapPanePos());
3961 // @method layerPointToContainerPoint(point: Point): Point
3962 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3963 // returns the corresponding pixel coordinate relative to the map container.
3964 layerPointToContainerPoint: function (point) { // (Point)
3965 return toPoint(point).add(this._getMapPanePos());
3968 // @method containerPointToLatLng(point: Point): LatLng
3969 // Given a pixel coordinate relative to the map container, returns
3970 // the corresponding geographical coordinate (for the current zoom level).
3971 containerPointToLatLng: function (point) {
3972 var layerPoint = this.containerPointToLayerPoint(toPoint(point));
3973 return this.layerPointToLatLng(layerPoint);
3976 // @method latLngToContainerPoint(latlng: LatLng): Point
3977 // Given a geographical coordinate, returns the corresponding pixel coordinate
3978 // relative to the map container.
3979 latLngToContainerPoint: function (latlng) {
3980 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
3983 // @method mouseEventToContainerPoint(ev: MouseEvent): Point
3984 // Given a MouseEvent object, returns the pixel coordinate relative to the
3985 // map container where the event took place.
3986 mouseEventToContainerPoint: function (e) {
3987 return getMousePosition(e, this._container);
3990 // @method mouseEventToLayerPoint(ev: MouseEvent): Point
3991 // Given a MouseEvent object, returns the pixel coordinate relative to
3992 // the [origin pixel](#map-getpixelorigin) where the event took place.
3993 mouseEventToLayerPoint: function (e) {
3994 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
3997 // @method mouseEventToLatLng(ev: MouseEvent): LatLng
3998 // Given a MouseEvent object, returns geographical coordinate where the
3999 // event took place.
4000 mouseEventToLatLng: function (e) { // (MouseEvent)
4001 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
4005 // map initialization methods
4007 _initContainer: function (id) {
4008 var container = this._container = get(id);
4011 throw new Error('Map container not found.');
4012 } else if (container._leaflet_id) {
4013 throw new Error('Map container is already initialized.');
4016 on(container, 'scroll', this._onScroll, this);
4017 this._containerId = stamp(container);
4020 _initLayout: function () {
4021 var container = this._container;
4023 this._fadeAnimated = this.options.fadeAnimation && any3d;
4025 addClass(container, 'leaflet-container' +
4026 (touch ? ' leaflet-touch' : '') +
4027 (retina ? ' leaflet-retina' : '') +
4028 (ielt9 ? ' leaflet-oldie' : '') +
4029 (safari ? ' leaflet-safari' : '') +
4030 (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
4032 var position = getStyle(container, 'position');
4034 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
4035 container.style.position = 'relative';
4040 if (this._initControlPos) {
4041 this._initControlPos();
4045 _initPanes: function () {
4046 var panes = this._panes = {};
4047 this._paneRenderers = {};
4051 // Panes are DOM elements used to control the ordering of layers on the map. You
4052 // can access panes with [`map.getPane`](#map-getpane) or
4053 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
4054 // [`map.createPane`](#map-createpane) method.
4056 // Every map has the following default panes that differ only in zIndex.
4058 // @pane mapPane: HTMLElement = 'auto'
4059 // Pane that contains all other map panes
4061 this._mapPane = this.createPane('mapPane', this._container);
4062 setPosition(this._mapPane, new Point(0, 0));
4064 // @pane tilePane: HTMLElement = 200
4065 // Pane for `GridLayer`s and `TileLayer`s
4066 this.createPane('tilePane');
4067 // @pane overlayPane: HTMLElement = 400
4068 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
4069 this.createPane('shadowPane');
4070 // @pane shadowPane: HTMLElement = 500
4071 // Pane for overlay shadows (e.g. `Marker` shadows)
4072 this.createPane('overlayPane');
4073 // @pane markerPane: HTMLElement = 600
4074 // Pane for `Icon`s of `Marker`s
4075 this.createPane('markerPane');
4076 // @pane tooltipPane: HTMLElement = 650
4077 // Pane for `Tooltip`s.
4078 this.createPane('tooltipPane');
4079 // @pane popupPane: HTMLElement = 700
4080 // Pane for `Popup`s.
4081 this.createPane('popupPane');
4083 if (!this.options.markerZoomAnimation) {
4084 addClass(panes.markerPane, 'leaflet-zoom-hide');
4085 addClass(panes.shadowPane, 'leaflet-zoom-hide');
4090 // private methods that modify map state
4092 // @section Map state change events
4093 _resetView: function (center, zoom) {
4094 setPosition(this._mapPane, new Point(0, 0));
4096 var loading = !this._loaded;
4097 this._loaded = true;
4098 zoom = this._limitZoom(zoom);
4100 this.fire('viewprereset');
4102 var zoomChanged = this._zoom !== zoom;
4104 ._moveStart(zoomChanged, false)
4105 ._move(center, zoom)
4106 ._moveEnd(zoomChanged);
4108 // @event viewreset: Event
4109 // Fired when the map needs to redraw its content (this usually happens
4110 // on map zoom or load). Very useful for creating custom overlays.
4111 this.fire('viewreset');
4113 // @event load: Event
4114 // Fired when the map is initialized (when its center and zoom are set
4115 // for the first time).
4121 _moveStart: function (zoomChanged, noMoveStart) {
4122 // @event zoomstart: Event
4123 // Fired when the map zoom is about to change (e.g. before zoom animation).
4124 // @event movestart: Event
4125 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
4127 this.fire('zoomstart');
4130 this.fire('movestart');
4135 _move: function (center, zoom, data) {
4136 if (zoom === undefined) {
4139 var zoomChanged = this._zoom !== zoom;
4142 this._lastCenter = center;
4143 this._pixelOrigin = this._getNewPixelOrigin(center);
4145 // @event zoom: Event
4146 // Fired repeatedly during any change in zoom level, including zoom
4147 // and fly animations.
4148 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530
4149 this.fire('zoom', data);
4152 // @event move: Event
4153 // Fired repeatedly during any movement of the map, including pan and
4155 return this.fire('move', data);
4158 _moveEnd: function (zoomChanged) {
4159 // @event zoomend: Event
4160 // Fired when the map has changed, after any animations.
4162 this.fire('zoomend');
4165 // @event moveend: Event
4166 // Fired when the center of the map stops changing (e.g. user stopped
4167 // dragging the map).
4168 return this.fire('moveend');
4171 _stop: function () {
4172 cancelAnimFrame(this._flyToFrame);
4173 if (this._panAnim) {
4174 this._panAnim.stop();
4179 _rawPanBy: function (offset) {
4180 setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
4183 _getZoomSpan: function () {
4184 return this.getMaxZoom() - this.getMinZoom();
4187 _panInsideMaxBounds: function () {
4188 if (!this._enforcingBounds) {
4189 this.panInsideBounds(this.options.maxBounds);
4193 _checkIfLoaded: function () {
4194 if (!this._loaded) {
4195 throw new Error('Set map center and zoom first.');
4199 // DOM event handling
4201 // @section Interaction events
4202 _initEvents: function (remove$$1) {
4204 this._targets[stamp(this._container)] = this;
4206 var onOff = remove$$1 ? off : on;
4208 // @event click: MouseEvent
4209 // Fired when the user clicks (or taps) the map.
4210 // @event dblclick: MouseEvent
4211 // Fired when the user double-clicks (or double-taps) the map.
4212 // @event mousedown: MouseEvent
4213 // Fired when the user pushes the mouse button on the map.
4214 // @event mouseup: MouseEvent
4215 // Fired when the user releases the mouse button on the map.
4216 // @event mouseover: MouseEvent
4217 // Fired when the mouse enters the map.
4218 // @event mouseout: MouseEvent
4219 // Fired when the mouse leaves the map.
4220 // @event mousemove: MouseEvent
4221 // Fired while the mouse moves over the map.
4222 // @event contextmenu: MouseEvent
4223 // Fired when the user pushes the right mouse button on the map, prevents
4224 // default browser context menu from showing if there are listeners on
4225 // this event. Also fired on mobile when the user holds a single touch
4226 // for a second (also called long press).
4227 // @event keypress: KeyboardEvent
4228 // Fired when the user presses a key from the keyboard while the map is focused.
4229 onOff(this._container, 'click dblclick mousedown mouseup ' +
4230 'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);
4232 if (this.options.trackResize) {
4233 onOff(window, 'resize', this._onResize, this);
4236 if (any3d && this.options.transform3DLimit) {
4237 (remove$$1 ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
4241 _onResize: function () {
4242 cancelAnimFrame(this._resizeRequest);
4243 this._resizeRequest = requestAnimFrame(
4244 function () { this.invalidateSize({debounceMoveend: true}); }, this);
4247 _onScroll: function () {
4248 this._container.scrollTop = 0;
4249 this._container.scrollLeft = 0;
4252 _onMoveEnd: function () {
4253 var pos = this._getMapPanePos();
4254 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
4255 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
4256 // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
4257 this._resetView(this.getCenter(), this.getZoom());
4261 _findEventTargets: function (e, type) {
4264 isHover = type === 'mouseout' || type === 'mouseover',
4265 src = e.target || e.srcElement,
4269 target = this._targets[stamp(src)];
4270 if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
4271 // Prevent firing click after you just dragged an object.
4275 if (target && target.listens(type, true)) {
4276 if (isHover && !isExternalTarget(src, e)) { break; }
4277 targets.push(target);
4278 if (isHover) { break; }
4280 if (src === this._container) { break; }
4281 src = src.parentNode;
4283 if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) {
4289 _handleDOMEvent: function (e) {
4290 if (!this._loaded || skipped(e)) { return; }
4294 if (type === 'mousedown' || type === 'keypress') {
4295 // prevents outline when clicking on keyboard-focusable element
4296 preventOutline(e.target || e.srcElement);
4299 this._fireDOMEvent(e, type);
4302 _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
4304 _fireDOMEvent: function (e, type, targets) {
4306 if (e.type === 'click') {
4307 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
4308 // @event preclick: MouseEvent
4309 // Fired before mouse click on the map (sometimes useful when you
4310 // want something to happen on click before any existing click
4311 // handlers start running).
4312 var synth = extend({}, e);
4313 synth.type = 'preclick';
4314 this._fireDOMEvent(synth, synth.type, targets);
4317 if (e._stopped) { return; }
4319 // Find the layer the event is propagating from and its parents.
4320 targets = (targets || []).concat(this._findEventTargets(e, type));
4322 if (!targets.length) { return; }
4324 var target = targets[0];
4325 if (type === 'contextmenu' && target.listens(type, true)) {
4333 if (e.type !== 'keypress') {
4334 var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
4335 data.containerPoint = isMarker ?
4336 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
4337 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
4338 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
4341 for (var i = 0; i < targets.length; i++) {
4342 targets[i].fire(type, data, true);
4343 if (data.originalEvent._stopped ||
4344 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
4348 _draggableMoved: function (obj) {
4349 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
4350 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
4353 _clearHandlers: function () {
4354 for (var i = 0, len = this._handlers.length; i < len; i++) {
4355 this._handlers[i].disable();
4359 // @section Other Methods
4361 // @method whenReady(fn: Function, context?: Object): this
4362 // Runs the given function `fn` when the map gets initialized with
4363 // a view (center and zoom) and at least one layer, or immediately
4364 // if it's already initialized, optionally passing a function context.
4365 whenReady: function (callback, context) {
4367 callback.call(context || this, {target: this});
4369 this.on('load', callback, context);
4375 // private methods for getting map state
4377 _getMapPanePos: function () {
4378 return getPosition(this._mapPane) || new Point(0, 0);
4381 _moved: function () {
4382 var pos = this._getMapPanePos();
4383 return pos && !pos.equals([0, 0]);
4386 _getTopLeftPoint: function (center, zoom) {
4387 var pixelOrigin = center && zoom !== undefined ?
4388 this._getNewPixelOrigin(center, zoom) :
4389 this.getPixelOrigin();
4390 return pixelOrigin.subtract(this._getMapPanePos());
4393 _getNewPixelOrigin: function (center, zoom) {
4394 var viewHalf = this.getSize()._divideBy(2);
4395 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
4398 _latLngToNewLayerPoint: function (latlng, zoom, center) {
4399 var topLeft = this._getNewPixelOrigin(center, zoom);
4400 return this.project(latlng, zoom)._subtract(topLeft);
4403 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
4404 var topLeft = this._getNewPixelOrigin(center, zoom);
4406 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
4407 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
4408 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
4409 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
4413 // layer point of the current center
4414 _getCenterLayerPoint: function () {
4415 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
4418 // offset of the specified place to the current center in pixels
4419 _getCenterOffset: function (latlng) {
4420 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
4423 // adjust center for view to get inside bounds
4424 _limitCenter: function (center, zoom, bounds) {
4426 if (!bounds) { return center; }
4428 var centerPoint = this.project(center, zoom),
4429 viewHalf = this.getSize().divideBy(2),
4430 viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
4431 offset = this._getBoundsOffset(viewBounds, bounds, zoom);
4433 // If offset is less than a pixel, ignore.
4434 // This prevents unstable projections from getting into
4435 // an infinite loop of tiny offsets.
4436 if (offset.round().equals([0, 0])) {
4440 return this.unproject(centerPoint.add(offset), zoom);
4443 // adjust offset for view to get inside bounds
4444 _limitOffset: function (offset, bounds) {
4445 if (!bounds) { return offset; }
4447 var viewBounds = this.getPixelBounds(),
4448 newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
4450 return offset.add(this._getBoundsOffset(newBounds, bounds));
4453 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
4454 _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
4455 var projectedMaxBounds = toBounds(
4456 this.project(maxBounds.getNorthEast(), zoom),
4457 this.project(maxBounds.getSouthWest(), zoom)
4459 minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
4460 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
4462 dx = this._rebound(minOffset.x, -maxOffset.x),
4463 dy = this._rebound(minOffset.y, -maxOffset.y);
4465 return new Point(dx, dy);
4468 _rebound: function (left, right) {
4469 return left + right > 0 ?
4470 Math.round(left - right) / 2 :
4471 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
4474 _limitZoom: function (zoom) {
4475 var min = this.getMinZoom(),
4476 max = this.getMaxZoom(),
4477 snap = any3d ? this.options.zoomSnap : 1;
4479 zoom = Math.round(zoom / snap) * snap;
4481 return Math.max(min, Math.min(max, zoom));
4484 _onPanTransitionStep: function () {
4488 _onPanTransitionEnd: function () {
4489 removeClass(this._mapPane, 'leaflet-pan-anim');
4490 this.fire('moveend');
4493 _tryAnimatedPan: function (center, options) {
4494 // difference between the new and current centers in pixels
4495 var offset = this._getCenterOffset(center)._trunc();
4497 // don't animate too far unless animate: true specified in options
4498 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
4500 this.panBy(offset, options);
4505 _createAnimProxy: function () {
4507 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
4508 this._panes.mapPane.appendChild(proxy);
4510 this.on('zoomanim', function (e) {
4511 var prop = TRANSFORM,
4512 transform = this._proxy.style[prop];
4514 setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
4516 // workaround for case when transform is the same and so transitionend event is not fired
4517 if (transform === this._proxy.style[prop] && this._animatingZoom) {
4518 this._onZoomTransitionEnd();
4522 this.on('load moveend', function () {
4523 var c = this.getCenter(),
4525 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
4528 this._on('unload', this._destroyAnimProxy, this);
4531 _destroyAnimProxy: function () {
4532 remove(this._proxy);
4536 _catchTransitionEnd: function (e) {
4537 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
4538 this._onZoomTransitionEnd();
4542 _nothingToAnimate: function () {
4543 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
4546 _tryAnimatedZoom: function (center, zoom, options) {
4548 if (this._animatingZoom) { return true; }
4550 options = options || {};
4552 // don't animate if disabled, not supported or zoom difference is too large
4553 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
4554 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
4556 // offset is the pixel coords of the zoom origin relative to the current center
4557 var scale = this.getZoomScale(zoom),
4558 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
4560 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
4561 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
4563 requestAnimFrame(function () {
4565 ._moveStart(true, false)
4566 ._animateZoom(center, zoom, true);
4572 _animateZoom: function (center, zoom, startAnim, noUpdate) {
4573 if (!this._mapPane) { return; }
4576 this._animatingZoom = true;
4578 // remember what center/zoom to set after animation
4579 this._animateToCenter = center;
4580 this._animateToZoom = zoom;
4582 addClass(this._mapPane, 'leaflet-zoom-anim');
4585 // @event zoomanim: ZoomAnimEvent
4586 // Fired on every frame of a zoom animation
4587 this.fire('zoomanim', {
4593 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
4594 setTimeout(bind(this._onZoomTransitionEnd, this), 250);
4597 _onZoomTransitionEnd: function () {
4598 if (!this._animatingZoom) { return; }
4600 if (this._mapPane) {
4601 removeClass(this._mapPane, 'leaflet-zoom-anim');
4604 this._animatingZoom = false;
4606 this._move(this._animateToCenter, this._animateToZoom);
4608 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
4609 requestAnimFrame(function () {
4610 this._moveEnd(true);
4617 // @factory L.map(id: String, options?: Map options)
4618 // Instantiates a map object given the DOM ID of a `<div>` element
4619 // and optionally an object literal with `Map options`.
4622 // @factory L.map(el: HTMLElement, options?: Map options)
4623 // Instantiates a map object given an instance of a `<div>` HTML element
4624 // and optionally an object literal with `Map options`.
4625 function createMap(id, options) {
4626 return new Map(id, options);
4634 * L.Control is a base class for implementing map controls. Handles positioning.
4635 * All other controls extend from this class.
4638 var Control = Class.extend({
4640 // @aka Control options
4642 // @option position: String = 'topright'
4643 // The position of the control (one of the map corners). Possible values are `'topleft'`,
4644 // `'topright'`, `'bottomleft'` or `'bottomright'`
4645 position: 'topright'
4648 initialize: function (options) {
4649 setOptions(this, options);
4653 * Classes extending L.Control will inherit the following methods:
4655 * @method getPosition: string
4656 * Returns the position of the control.
4658 getPosition: function () {
4659 return this.options.position;
4662 // @method setPosition(position: string): this
4663 // Sets the position of the control.
4664 setPosition: function (position) {
4665 var map = this._map;
4668 map.removeControl(this);
4671 this.options.position = position;
4674 map.addControl(this);
4680 // @method getContainer: HTMLElement
4681 // Returns the HTMLElement that contains the control.
4682 getContainer: function () {
4683 return this._container;
4686 // @method addTo(map: Map): this
4687 // Adds the control to the given map.
4688 addTo: function (map) {
4692 var container = this._container = this.onAdd(map),
4693 pos = this.getPosition(),
4694 corner = map._controlCorners[pos];
4696 addClass(container, 'leaflet-control');
4698 if (pos.indexOf('bottom') !== -1) {
4699 corner.insertBefore(container, corner.firstChild);
4701 corner.appendChild(container);
4707 // @method remove: this
4708 // Removes the control from the map it is currently active on.
4709 remove: function () {
4714 remove(this._container);
4716 if (this.onRemove) {
4717 this.onRemove(this._map);
4725 _refocusOnMap: function (e) {
4726 // if map exists and event is not a keyboard event
4727 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
4728 this._map.getContainer().focus();
4733 var control = function (options) {
4734 return new Control(options);
4737 /* @section Extension methods
4740 * Every control should extend from `L.Control` and (re-)implement the following methods.
4742 * @method onAdd(map: Map): HTMLElement
4743 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
4745 * @method onRemove(map: Map)
4746 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
4750 * @section Methods for Layers and Controls
4753 // @method addControl(control: Control): this
4754 // Adds the given control to the map
4755 addControl: function (control) {
4756 control.addTo(this);
4760 // @method removeControl(control: Control): this
4761 // Removes the given control from the map
4762 removeControl: function (control) {
4767 _initControlPos: function () {
4768 var corners = this._controlCorners = {},
4770 container = this._controlContainer =
4771 create$1('div', l + 'control-container', this._container);
4773 function createCorner(vSide, hSide) {
4774 var className = l + vSide + ' ' + l + hSide;
4776 corners[vSide + hSide] = create$1('div', className, container);
4779 createCorner('top', 'left');
4780 createCorner('top', 'right');
4781 createCorner('bottom', 'left');
4782 createCorner('bottom', 'right');
4785 _clearControlPos: function () {
4786 for (var i in this._controlCorners) {
4787 remove(this._controlCorners[i]);
4789 remove(this._controlContainer);
4790 delete this._controlCorners;
4791 delete this._controlContainer;
4796 * @class Control.Layers
4797 * @aka L.Control.Layers
4800 * 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`.
4805 * var baseLayers = {
4807 * "OpenStreetMap": osm
4812 * "Roads": roadsLayer
4815 * L.control.layers(baseLayers, overlays).addTo(map);
4818 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
4822 * "<someName1>": layer1,
4823 * "<someName2>": layer2
4827 * The layer names can contain HTML, which allows you to add additional styling to the items:
4830 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
4834 var Layers = Control.extend({
4836 // @aka Control.Layers options
4838 // @option collapsed: Boolean = true
4839 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
4841 position: 'topright',
4843 // @option autoZIndex: Boolean = true
4844 // 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.
4847 // @option hideSingleBase: Boolean = false
4848 // If `true`, the base layers in the control will be hidden when there is only one.
4849 hideSingleBase: false,
4851 // @option sortLayers: Boolean = false
4852 // Whether to sort the layers. When `false`, layers will keep the order
4853 // in which they were added to the control.
4856 // @option sortFunction: Function = *
4857 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
4858 // that will be used for sorting the layers, when `sortLayers` is `true`.
4859 // The function receives both the `L.Layer` instances and their names, as in
4860 // `sortFunction(layerA, layerB, nameA, nameB)`.
4861 // By default, it sorts layers alphabetically by their name.
4862 sortFunction: function (layerA, layerB, nameA, nameB) {
4863 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
4867 initialize: function (baseLayers, overlays, options) {
4868 setOptions(this, options);
4870 this._layerControlInputs = [];
4872 this._lastZIndex = 0;
4873 this._handlingClick = false;
4875 for (var i in baseLayers) {
4876 this._addLayer(baseLayers[i], i);
4879 for (i in overlays) {
4880 this._addLayer(overlays[i], i, true);
4884 onAdd: function (map) {
4889 map.on('zoomend', this._checkDisabledLayers, this);
4891 for (var i = 0; i < this._layers.length; i++) {
4892 this._layers[i].layer.on('add remove', this._onLayerChange, this);
4895 return this._container;
4898 addTo: function (map) {
4899 Control.prototype.addTo.call(this, map);
4900 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
4901 return this._expandIfNotCollapsed();
4904 onRemove: function () {
4905 this._map.off('zoomend', this._checkDisabledLayers, this);
4907 for (var i = 0; i < this._layers.length; i++) {
4908 this._layers[i].layer.off('add remove', this._onLayerChange, this);
4912 // @method addBaseLayer(layer: Layer, name: String): this
4913 // Adds a base layer (radio button entry) with the given name to the control.
4914 addBaseLayer: function (layer, name) {
4915 this._addLayer(layer, name);
4916 return (this._map) ? this._update() : this;
4919 // @method addOverlay(layer: Layer, name: String): this
4920 // Adds an overlay (checkbox entry) with the given name to the control.
4921 addOverlay: function (layer, name) {
4922 this._addLayer(layer, name, true);
4923 return (this._map) ? this._update() : this;
4926 // @method removeLayer(layer: Layer): this
4927 // Remove the given layer from the control.
4928 removeLayer: function (layer) {
4929 layer.off('add remove', this._onLayerChange, this);
4931 var obj = this._getLayer(stamp(layer));
4933 this._layers.splice(this._layers.indexOf(obj), 1);
4935 return (this._map) ? this._update() : this;
4938 // @method expand(): this
4939 // Expand the control container if collapsed.
4940 expand: function () {
4941 addClass(this._container, 'leaflet-control-layers-expanded');
4942 this._form.style.height = null;
4943 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
4944 if (acceptableHeight < this._form.clientHeight) {
4945 addClass(this._form, 'leaflet-control-layers-scrollbar');
4946 this._form.style.height = acceptableHeight + 'px';
4948 removeClass(this._form, 'leaflet-control-layers-scrollbar');
4950 this._checkDisabledLayers();
4954 // @method collapse(): this
4955 // Collapse the control container if expanded.
4956 collapse: function () {
4957 removeClass(this._container, 'leaflet-control-layers-expanded');
4961 _initLayout: function () {
4962 var className = 'leaflet-control-layers',
4963 container = this._container = create$1('div', className),
4964 collapsed = this.options.collapsed;
4966 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
4967 container.setAttribute('aria-haspopup', true);
4969 disableClickPropagation(container);
4970 disableScrollPropagation(container);
4972 var form = this._form = create$1('form', className + '-list');
4975 this._map.on('click', this.collapse, this);
4979 mouseenter: this.expand,
4980 mouseleave: this.collapse
4985 var link = this._layersLink = create$1('a', className + '-toggle', container);
4987 link.title = 'Layers';
4990 on(link, 'click', stop);
4991 on(link, 'click', this.expand, this);
4993 on(link, 'focus', this.expand, this);
5000 this._baseLayersList = create$1('div', className + '-base', form);
5001 this._separator = create$1('div', className + '-separator', form);
5002 this._overlaysList = create$1('div', className + '-overlays', form);
5004 container.appendChild(form);
5007 _getLayer: function (id) {
5008 for (var i = 0; i < this._layers.length; i++) {
5010 if (this._layers[i] && stamp(this._layers[i].layer) === id) {
5011 return this._layers[i];
5016 _addLayer: function (layer, name, overlay) {
5018 layer.on('add remove', this._onLayerChange, this);
5027 if (this.options.sortLayers) {
5028 this._layers.sort(bind(function (a, b) {
5029 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
5033 if (this.options.autoZIndex && layer.setZIndex) {
5035 layer.setZIndex(this._lastZIndex);
5038 this._expandIfNotCollapsed();
5041 _update: function () {
5042 if (!this._container) { return this; }
5044 empty(this._baseLayersList);
5045 empty(this._overlaysList);
5047 this._layerControlInputs = [];
5048 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
5050 for (i = 0; i < this._layers.length; i++) {
5051 obj = this._layers[i];
5053 overlaysPresent = overlaysPresent || obj.overlay;
5054 baseLayersPresent = baseLayersPresent || !obj.overlay;
5055 baseLayersCount += !obj.overlay ? 1 : 0;
5058 // Hide base layers section if there's only one layer.
5059 if (this.options.hideSingleBase) {
5060 baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
5061 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
5064 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
5069 _onLayerChange: function (e) {
5070 if (!this._handlingClick) {
5074 var obj = this._getLayer(stamp(e.target));
5077 // @section Layer events
5078 // @event baselayerchange: LayersControlEvent
5079 // Fired when the base layer is changed through the [layer control](#control-layers).
5080 // @event overlayadd: LayersControlEvent
5081 // Fired when an overlay is selected through the [layer control](#control-layers).
5082 // @event overlayremove: LayersControlEvent
5083 // Fired when an overlay is deselected through the [layer control](#control-layers).
5084 // @namespace Control.Layers
5085 var type = obj.overlay ?
5086 (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
5087 (e.type === 'add' ? 'baselayerchange' : null);
5090 this._map.fire(type, obj);
5094 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
5095 _createRadioElement: function (name, checked) {
5097 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
5098 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
5100 var radioFragment = document.createElement('div');
5101 radioFragment.innerHTML = radioHtml;
5103 return radioFragment.firstChild;
5106 _addItem: function (obj) {
5107 var label = document.createElement('label'),
5108 checked = this._map.hasLayer(obj.layer),
5112 input = document.createElement('input');
5113 input.type = 'checkbox';
5114 input.className = 'leaflet-control-layers-selector';
5115 input.defaultChecked = checked;
5117 input = this._createRadioElement('leaflet-base-layers', checked);
5120 this._layerControlInputs.push(input);
5121 input.layerId = stamp(obj.layer);
5123 on(input, 'click', this._onInputClick, this);
5125 var name = document.createElement('span');
5126 name.innerHTML = ' ' + obj.name;
5128 // Helps from preventing layer control flicker when checkboxes are disabled
5129 // https://github.com/Leaflet/Leaflet/issues/2771
5130 var holder = document.createElement('div');
5132 label.appendChild(holder);
5133 holder.appendChild(input);
5134 holder.appendChild(name);
5136 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
5137 container.appendChild(label);
5139 this._checkDisabledLayers();
5143 _onInputClick: function () {
5144 var inputs = this._layerControlInputs,
5146 var addedLayers = [],
5149 this._handlingClick = true;
5151 for (var i = inputs.length - 1; i >= 0; i--) {
5153 layer = this._getLayer(input.layerId).layer;
5155 if (input.checked) {
5156 addedLayers.push(layer);
5157 } else if (!input.checked) {
5158 removedLayers.push(layer);
5162 // Bugfix issue 2318: Should remove all old layers before readding new ones
5163 for (i = 0; i < removedLayers.length; i++) {
5164 if (this._map.hasLayer(removedLayers[i])) {
5165 this._map.removeLayer(removedLayers[i]);
5168 for (i = 0; i < addedLayers.length; i++) {
5169 if (!this._map.hasLayer(addedLayers[i])) {
5170 this._map.addLayer(addedLayers[i]);
5174 this._handlingClick = false;
5176 this._refocusOnMap();
5179 _checkDisabledLayers: function () {
5180 var inputs = this._layerControlInputs,
5183 zoom = this._map.getZoom();
5185 for (var i = inputs.length - 1; i >= 0; i--) {
5187 layer = this._getLayer(input.layerId).layer;
5188 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
5189 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
5194 _expandIfNotCollapsed: function () {
5195 if (this._map && !this.options.collapsed) {
5201 _expand: function () {
5202 // Backward compatibility, remove me in 1.1.
5203 return this.expand();
5206 _collapse: function () {
5207 // Backward compatibility, remove me in 1.1.
5208 return this.collapse();
5214 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
5215 // 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.
5216 var layers = function (baseLayers, overlays, options) {
5217 return new Layers(baseLayers, overlays, options);
5221 * @class Control.Zoom
5222 * @aka L.Control.Zoom
5225 * 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`.
5228 var Zoom = Control.extend({
5230 // @aka Control.Zoom options
5232 position: 'topleft',
5234 // @option zoomInText: String = '+'
5235 // The text set on the 'zoom in' button.
5238 // @option zoomInTitle: String = 'Zoom in'
5239 // The title set on the 'zoom in' button.
5240 zoomInTitle: 'Zoom in',
5242 // @option zoomOutText: String = '−'
5243 // The text set on the 'zoom out' button.
5244 zoomOutText: '−',
5246 // @option zoomOutTitle: String = 'Zoom out'
5247 // The title set on the 'zoom out' button.
5248 zoomOutTitle: 'Zoom out'
5251 onAdd: function (map) {
5252 var zoomName = 'leaflet-control-zoom',
5253 container = create$1('div', zoomName + ' leaflet-bar'),
5254 options = this.options;
5256 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle,
5257 zoomName + '-in', container, this._zoomIn);
5258 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
5259 zoomName + '-out', container, this._zoomOut);
5261 this._updateDisabled();
5262 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
5267 onRemove: function (map) {
5268 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
5271 disable: function () {
5272 this._disabled = true;
5273 this._updateDisabled();
5277 enable: function () {
5278 this._disabled = false;
5279 this._updateDisabled();
5283 _zoomIn: function (e) {
5284 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
5285 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5289 _zoomOut: function (e) {
5290 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
5291 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5295 _createButton: function (html, title, className, container, fn) {
5296 var link = create$1('a', className, container);
5297 link.innerHTML = html;
5302 * Will force screen readers like VoiceOver to read this as "Zoom in - button"
5304 link.setAttribute('role', 'button');
5305 link.setAttribute('aria-label', title);
5307 disableClickPropagation(link);
5308 on(link, 'click', stop);
5309 on(link, 'click', fn, this);
5310 on(link, 'click', this._refocusOnMap, this);
5315 _updateDisabled: function () {
5316 var map = this._map,
5317 className = 'leaflet-disabled';
5319 removeClass(this._zoomInButton, className);
5320 removeClass(this._zoomOutButton, className);
5322 if (this._disabled || map._zoom === map.getMinZoom()) {
5323 addClass(this._zoomOutButton, className);
5325 if (this._disabled || map._zoom === map.getMaxZoom()) {
5326 addClass(this._zoomInButton, className);
5332 // @section Control options
5333 // @option zoomControl: Boolean = true
5334 // Whether a [zoom control](#control-zoom) is added to the map by default.
5339 Map.addInitHook(function () {
5340 if (this.options.zoomControl) {
5341 this.zoomControl = new Zoom();
5342 this.addControl(this.zoomControl);
5346 // @namespace Control.Zoom
5347 // @factory L.control.zoom(options: Control.Zoom options)
5348 // Creates a zoom control
5349 var zoom = function (options) {
5350 return new Zoom(options);
5354 * @class Control.Scale
5355 * @aka L.Control.Scale
5358 * 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`.
5363 * L.control.scale().addTo(map);
5367 var Scale = Control.extend({
5369 // @aka Control.Scale options
5371 position: 'bottomleft',
5373 // @option maxWidth: Number = 100
5374 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
5377 // @option metric: Boolean = True
5378 // Whether to show the metric scale line (m/km).
5381 // @option imperial: Boolean = True
5382 // Whether to show the imperial scale line (mi/ft).
5385 // @option updateWhenIdle: Boolean = false
5386 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
5389 onAdd: function (map) {
5390 var className = 'leaflet-control-scale',
5391 container = create$1('div', className),
5392 options = this.options;
5394 this._addScales(options, className + '-line', container);
5396 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5397 map.whenReady(this._update, this);
5402 onRemove: function (map) {
5403 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5406 _addScales: function (options, className, container) {
5407 if (options.metric) {
5408 this._mScale = create$1('div', className, container);
5410 if (options.imperial) {
5411 this._iScale = create$1('div', className, container);
5415 _update: function () {
5416 var map = this._map,
5417 y = map.getSize().y / 2;
5419 var maxMeters = map.distance(
5420 map.containerPointToLatLng([0, y]),
5421 map.containerPointToLatLng([this.options.maxWidth, y]));
5423 this._updateScales(maxMeters);
5426 _updateScales: function (maxMeters) {
5427 if (this.options.metric && maxMeters) {
5428 this._updateMetric(maxMeters);
5430 if (this.options.imperial && maxMeters) {
5431 this._updateImperial(maxMeters);
5435 _updateMetric: function (maxMeters) {
5436 var meters = this._getRoundNum(maxMeters),
5437 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
5439 this._updateScale(this._mScale, label, meters / maxMeters);
5442 _updateImperial: function (maxMeters) {
5443 var maxFeet = maxMeters * 3.2808399,
5444 maxMiles, miles, feet;
5446 if (maxFeet > 5280) {
5447 maxMiles = maxFeet / 5280;
5448 miles = this._getRoundNum(maxMiles);
5449 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
5452 feet = this._getRoundNum(maxFeet);
5453 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
5457 _updateScale: function (scale, text, ratio) {
5458 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
5459 scale.innerHTML = text;
5462 _getRoundNum: function (num) {
5463 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5476 // @factory L.control.scale(options?: Control.Scale options)
5477 // Creates an scale control with the given options.
5478 var scale = function (options) {
5479 return new Scale(options);
5483 * @class Control.Attribution
5484 * @aka L.Control.Attribution
5487 * 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.
5490 var Attribution = Control.extend({
5492 // @aka Control.Attribution options
5494 position: 'bottomright',
5496 // @option prefix: String = 'Leaflet'
5497 // The HTML text shown before the attributions. Pass `false` to disable.
5498 prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
5501 initialize: function (options) {
5502 setOptions(this, options);
5504 this._attributions = {};
5507 onAdd: function (map) {
5508 map.attributionControl = this;
5509 this._container = create$1('div', 'leaflet-control-attribution');
5510 disableClickPropagation(this._container);
5512 // TODO ugly, refactor
5513 for (var i in map._layers) {
5514 if (map._layers[i].getAttribution) {
5515 this.addAttribution(map._layers[i].getAttribution());
5521 return this._container;
5524 // @method setPrefix(prefix: String): this
5525 // Sets the text before the attributions.
5526 setPrefix: function (prefix) {
5527 this.options.prefix = prefix;
5532 // @method addAttribution(text: String): this
5533 // Adds an attribution text (e.g. `'Vector data © Mapbox'`).
5534 addAttribution: function (text) {
5535 if (!text) { return this; }
5537 if (!this._attributions[text]) {
5538 this._attributions[text] = 0;
5540 this._attributions[text]++;
5547 // @method removeAttribution(text: String): this
5548 // Removes an attribution text.
5549 removeAttribution: function (text) {
5550 if (!text) { return this; }
5552 if (this._attributions[text]) {
5553 this._attributions[text]--;
5560 _update: function () {
5561 if (!this._map) { return; }
5565 for (var i in this._attributions) {
5566 if (this._attributions[i]) {
5571 var prefixAndAttribs = [];
5573 if (this.options.prefix) {
5574 prefixAndAttribs.push(this.options.prefix);
5576 if (attribs.length) {
5577 prefixAndAttribs.push(attribs.join(', '));
5580 this._container.innerHTML = prefixAndAttribs.join(' | ');
5585 // @section Control options
5586 // @option attributionControl: Boolean = true
5587 // Whether a [attribution control](#control-attribution) is added to the map by default.
5589 attributionControl: true
5592 Map.addInitHook(function () {
5593 if (this.options.attributionControl) {
5594 new Attribution().addTo(this);
5598 // @namespace Control.Attribution
5599 // @factory L.control.attribution(options: Control.Attribution options)
5600 // Creates an attribution control.
5601 var attribution = function (options) {
5602 return new Attribution(options);
5605 Control.Layers = Layers;
5606 Control.Zoom = Zoom;
5607 Control.Scale = Scale;
5608 Control.Attribution = Attribution;
5610 control.layers = layers;
5611 control.zoom = zoom;
5612 control.scale = scale;
5613 control.attribution = attribution;
5616 L.Handler is a base class for handler classes that are used internally to inject
5617 interaction features like dragging to classes like Map and Marker.
5622 // Abstract class for map interaction handlers
5624 var Handler = Class.extend({
5625 initialize: function (map) {
5629 // @method enable(): this
5630 // Enables the handler
5631 enable: function () {
5632 if (this._enabled) { return this; }
5634 this._enabled = true;
5639 // @method disable(): this
5640 // Disables the handler
5641 disable: function () {
5642 if (!this._enabled) { return this; }
5644 this._enabled = false;
5649 // @method enabled(): Boolean
5650 // Returns `true` if the handler is enabled
5651 enabled: function () {
5652 return !!this._enabled;
5655 // @section Extension methods
5656 // Classes inheriting from `Handler` must implement the two following methods:
5657 // @method addHooks()
5658 // Called when the handler is enabled, should add event hooks.
5659 // @method removeHooks()
5660 // Called when the handler is disabled, should remove the event hooks added previously.
5663 // @section There is static function which can be called without instantiating L.Handler:
5664 // @function addTo(map: Map, name: String): this
5665 // Adds a new Handler to the given map with the given name.
5666 Handler.addTo = function (map, name) {
5667 map.addHandler(name, this);
5671 var Mixin = {Events: Events};
5678 * A class for making DOM elements draggable (including touch support).
5679 * Used internally for map and marker dragging. Only works for elements
5680 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
5684 * var draggable = new L.Draggable(elementToDrag);
5685 * draggable.enable();
5689 var START = touch ? 'touchstart mousedown' : 'mousedown';
5691 mousedown: 'mouseup',
5692 touchstart: 'touchend',
5693 pointerdown: 'touchend',
5694 MSPointerDown: 'touchend'
5697 mousedown: 'mousemove',
5698 touchstart: 'touchmove',
5699 pointerdown: 'touchmove',
5700 MSPointerDown: 'touchmove'
5704 var Draggable = Evented.extend({
5708 // @aka Draggable options
5709 // @option clickTolerance: Number = 3
5710 // The max number of pixels a user can shift the mouse pointer during a click
5711 // for it to be considered a valid click (as opposed to a mouse drag).
5715 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
5716 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
5717 initialize: function (element, dragStartTarget, preventOutline$$1, options) {
5718 setOptions(this, options);
5720 this._element = element;
5721 this._dragStartTarget = dragStartTarget || element;
5722 this._preventOutline = preventOutline$$1;
5726 // Enables the dragging ability
5727 enable: function () {
5728 if (this._enabled) { return; }
5730 on(this._dragStartTarget, START, this._onDown, this);
5732 this._enabled = true;
5735 // @method disable()
5736 // Disables the dragging ability
5737 disable: function () {
5738 if (!this._enabled) { return; }
5740 // If we're currently dragging this draggable,
5741 // disabling it counts as first ending the drag.
5742 if (Draggable._dragging === this) {
5746 off(this._dragStartTarget, START, this._onDown, this);
5748 this._enabled = false;
5749 this._moved = false;
5752 _onDown: function (e) {
5753 // Ignore simulated events, since we handle both touch and
5754 // mouse explicitly; otherwise we risk getting duplicates of
5755 // touch events, see #4315.
5756 // Also ignore the event if disabled; this happens in IE11
5757 // under some circumstances, see #3666.
5758 if (e._simulated || !this._enabled) { return; }
5760 this._moved = false;
5762 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
5764 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
5765 Draggable._dragging = this; // Prevent dragging multiple objects at once.
5767 if (this._preventOutline) {
5768 preventOutline(this._element);
5772 disableTextSelection();
5774 if (this._moving) { return; }
5776 // @event down: Event
5777 // Fired when a drag is about to start.
5780 var first = e.touches ? e.touches[0] : e;
5782 this._startPoint = new Point(first.clientX, first.clientY);
5784 on(document, MOVE[e.type], this._onMove, this);
5785 on(document, END[e.type], this._onUp, this);
5788 _onMove: function (e) {
5789 // Ignore simulated events, since we handle both touch and
5790 // mouse explicitly; otherwise we risk getting duplicates of
5791 // touch events, see #4315.
5792 // Also ignore the event if disabled; this happens in IE11
5793 // under some circumstances, see #3666.
5794 if (e._simulated || !this._enabled) { return; }
5796 if (e.touches && e.touches.length > 1) {
5801 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
5802 newPoint = new Point(first.clientX, first.clientY),
5803 offset = newPoint.subtract(this._startPoint);
5805 if (!offset.x && !offset.y) { return; }
5806 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
5811 // @event dragstart: Event
5812 // Fired when a drag starts
5813 this.fire('dragstart');
5816 this._startPos = getPosition(this._element).subtract(offset);
5818 addClass(document.body, 'leaflet-dragging');
5820 this._lastTarget = e.target || e.srcElement;
5821 // IE and Edge do not give the <use> element, so fetch it
5823 if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
5824 this._lastTarget = this._lastTarget.correspondingUseElement;
5826 addClass(this._lastTarget, 'leaflet-drag-target');
5829 this._newPos = this._startPos.add(offset);
5830 this._moving = true;
5832 cancelAnimFrame(this._animRequest);
5833 this._lastEvent = e;
5834 this._animRequest = requestAnimFrame(this._updatePosition, this, true);
5837 _updatePosition: function () {
5838 var e = {originalEvent: this._lastEvent};
5840 // @event predrag: Event
5841 // Fired continuously during dragging *before* each corresponding
5842 // update of the element's position.
5843 this.fire('predrag', e);
5844 setPosition(this._element, this._newPos);
5846 // @event drag: Event
5847 // Fired continuously during dragging.
5848 this.fire('drag', e);
5851 _onUp: function (e) {
5852 // Ignore simulated events, since we handle both touch and
5853 // mouse explicitly; otherwise we risk getting duplicates of
5854 // touch events, see #4315.
5855 // Also ignore the event if disabled; this happens in IE11
5856 // under some circumstances, see #3666.
5857 if (e._simulated || !this._enabled) { return; }
5861 finishDrag: function () {
5862 removeClass(document.body, 'leaflet-dragging');
5864 if (this._lastTarget) {
5865 removeClass(this._lastTarget, 'leaflet-drag-target');
5866 this._lastTarget = null;
5869 for (var i in MOVE) {
5870 off(document, MOVE[i], this._onMove, this);
5871 off(document, END[i], this._onUp, this);
5875 enableTextSelection();
5877 if (this._moved && this._moving) {
5878 // ensure drag is not fired after dragend
5879 cancelAnimFrame(this._animRequest);
5881 // @event dragend: DragEndEvent
5882 // Fired when the drag ends.
5883 this.fire('dragend', {
5884 distance: this._newPos.distanceTo(this._startPos)
5888 this._moving = false;
5889 Draggable._dragging = false;
5895 * @namespace LineUtil
5897 * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
5900 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
5901 // Improves rendering performance dramatically by lessening the number of points to draw.
5903 // @function simplify(points: Point[], tolerance: Number): Point[]
5904 // Dramatically reduces the number of points in a polyline while retaining
5905 // its shape and returns a new array of simplified points, using the
5906 // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
5907 // Used for a huge performance boost when processing/displaying Leaflet polylines for
5908 // each zoom level and also reducing visual noise. tolerance affects the amount of
5909 // simplification (lesser value means higher quality but slower and with more points).
5910 // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
5911 function simplify(points, tolerance) {
5912 if (!tolerance || !points.length) {
5913 return points.slice();
5916 var sqTolerance = tolerance * tolerance;
5918 // stage 1: vertex reduction
5919 points = _reducePoints(points, sqTolerance);
5921 // stage 2: Douglas-Peucker simplification
5922 points = _simplifyDP(points, sqTolerance);
5927 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
5928 // Returns the distance between point `p` and segment `p1` to `p2`.
5929 function pointToSegmentDistance(p, p1, p2) {
5930 return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
5933 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
5934 // Returns the closest point from a point `p` on a segment `p1` to `p2`.
5935 function closestPointOnSegment(p, p1, p2) {
5936 return _sqClosestPointOnSegment(p, p1, p2);
5939 // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
5940 function _simplifyDP(points, sqTolerance) {
5942 var len = points.length,
5943 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
5944 markers = new ArrayConstructor(len);
5946 markers[0] = markers[len - 1] = 1;
5948 _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
5953 for (i = 0; i < len; i++) {
5955 newPoints.push(points[i]);
5962 function _simplifyDPStep(points, markers, sqTolerance, first, last) {
5967 for (i = first + 1; i <= last - 1; i++) {
5968 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
5970 if (sqDist > maxSqDist) {
5976 if (maxSqDist > sqTolerance) {
5979 _simplifyDPStep(points, markers, sqTolerance, first, index);
5980 _simplifyDPStep(points, markers, sqTolerance, index, last);
5984 // reduce points that are too close to each other to a single point
5985 function _reducePoints(points, sqTolerance) {
5986 var reducedPoints = [points[0]];
5988 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
5989 if (_sqDist(points[i], points[prev]) > sqTolerance) {
5990 reducedPoints.push(points[i]);
5994 if (prev < len - 1) {
5995 reducedPoints.push(points[len - 1]);
5997 return reducedPoints;
6002 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
6003 // Clips the segment a to b by rectangular bounds with the
6004 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
6005 // (modifying the segment points directly!). Used by Leaflet to only show polyline
6006 // points that are on the screen or near, increasing performance.
6007 function clipSegment(a, b, bounds, useLastCode, round) {
6008 var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
6009 codeB = _getBitCode(b, bounds),
6011 codeOut, p, newCode;
6013 // save 2nd code to avoid calculating it on the next segment
6017 // if a,b is inside the clip window (trivial accept)
6018 if (!(codeA | codeB)) {
6022 // if a,b is outside the clip window (trivial reject)
6023 if (codeA & codeB) {
6028 codeOut = codeA || codeB;
6029 p = _getEdgeIntersection(a, b, codeOut, bounds, round);
6030 newCode = _getBitCode(p, bounds);
6032 if (codeOut === codeA) {
6042 function _getEdgeIntersection(a, b, code, bounds, round) {
6049 if (code & 8) { // top
6050 x = a.x + dx * (max.y - a.y) / dy;
6053 } else if (code & 4) { // bottom
6054 x = a.x + dx * (min.y - a.y) / dy;
6057 } else if (code & 2) { // right
6059 y = a.y + dy * (max.x - a.x) / dx;
6061 } else if (code & 1) { // left
6063 y = a.y + dy * (min.x - a.x) / dx;
6066 return new Point(x, y, round);
6069 function _getBitCode(p, bounds) {
6072 if (p.x < bounds.min.x) { // left
6074 } else if (p.x > bounds.max.x) { // right
6078 if (p.y < bounds.min.y) { // bottom
6080 } else if (p.y > bounds.max.y) { // top
6087 // square distance (to avoid unnecessary Math.sqrt calls)
6088 function _sqDist(p1, p2) {
6089 var dx = p2.x - p1.x,
6091 return dx * dx + dy * dy;
6094 // return closest point on segment or distance to that point
6095 function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
6100 dot = dx * dx + dy * dy,
6104 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
6118 return sqDist ? dx * dx + dy * dy : new Point(x, y);
6122 // @function isFlat(latlngs: LatLng[]): Boolean
6123 // Returns true if `latlngs` is a flat array, false is nested.
6124 function isFlat(latlngs) {
6125 return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
6128 function _flat(latlngs) {
6129 console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
6130 return isFlat(latlngs);
6134 var LineUtil = (Object.freeze || Object)({
6136 pointToSegmentDistance: pointToSegmentDistance,
6137 closestPointOnSegment: closestPointOnSegment,
6138 clipSegment: clipSegment,
6139 _getEdgeIntersection: _getEdgeIntersection,
6140 _getBitCode: _getBitCode,
6141 _sqClosestPointOnSegment: _sqClosestPointOnSegment,
6147 * @namespace PolyUtil
6148 * Various utility functions for polygon geometries.
6151 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
6152 * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)).
6153 * Used by Leaflet to only show polygon points that are on the screen or near, increasing
6154 * performance. Note that polygon points needs different algorithm for clipping
6155 * than polyline, so there's a separate method for it.
6157 function clipPolygon(points, bounds, round) {
6159 edges = [1, 4, 2, 8],
6164 for (i = 0, len = points.length; i < len; i++) {
6165 points[i]._code = _getBitCode(points[i], bounds);
6168 // for each edge (left, bottom, right, top)
6169 for (k = 0; k < 4; k++) {
6173 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
6177 // if a is inside the clip window
6178 if (!(a._code & edge)) {
6179 // if b is outside the clip window (a->b goes out of screen)
6180 if (b._code & edge) {
6181 p = _getEdgeIntersection(b, a, edge, bounds, round);
6182 p._code = _getBitCode(p, bounds);
6183 clippedPoints.push(p);
6185 clippedPoints.push(a);
6187 // else if b is inside the clip window (a->b enters the screen)
6188 } else if (!(b._code & edge)) {
6189 p = _getEdgeIntersection(b, a, edge, bounds, round);
6190 p._code = _getBitCode(p, bounds);
6191 clippedPoints.push(p);
6194 points = clippedPoints;
6201 var PolyUtil = (Object.freeze || Object)({
6202 clipPolygon: clipPolygon
6206 * @namespace Projection
6208 * Leaflet comes with a set of already defined Projections out of the box:
6210 * @projection L.Projection.LonLat
6212 * Equirectangular, or Plate Carree projection — the most simple projection,
6213 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
6214 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
6215 * `EPSG:4326` and `Simple` CRS.
6219 project: function (latlng) {
6220 return new Point(latlng.lng, latlng.lat);
6223 unproject: function (point) {
6224 return new LatLng(point.y, point.x);
6227 bounds: new Bounds([-180, -90], [180, 90])
6231 * @namespace Projection
6232 * @projection L.Projection.Mercator
6234 * 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.
6239 R_MINOR: 6356752.314245179,
6241 bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
6243 project: function (latlng) {
6244 var d = Math.PI / 180,
6247 tmp = this.R_MINOR / r,
6248 e = Math.sqrt(1 - tmp * tmp),
6249 con = e * Math.sin(y);
6251 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
6252 y = -r * Math.log(Math.max(ts, 1E-10));
6254 return new Point(latlng.lng * d * r, y);
6257 unproject: function (point) {
6258 var d = 180 / Math.PI,
6260 tmp = this.R_MINOR / r,
6261 e = Math.sqrt(1 - tmp * tmp),
6262 ts = Math.exp(-point.y / r),
6263 phi = Math.PI / 2 - 2 * Math.atan(ts);
6265 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
6266 con = e * Math.sin(phi);
6267 con = Math.pow((1 - con) / (1 + con), e / 2);
6268 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
6272 return new LatLng(phi * d, point.x * d / r);
6279 * An object with methods for projecting geographical coordinates of the world onto
6280 * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection).
6282 * @property bounds: Bounds
6283 * The bounds (specified in CRS units) where the projection is valid
6285 * @method project(latlng: LatLng): Point
6286 * Projects geographical coordinates into a 2D point.
6287 * Only accepts actual `L.LatLng` instances, not arrays.
6289 * @method unproject(point: Point): LatLng
6290 * The inverse of `project`. Projects a 2D point into a geographical location.
6291 * Only accepts actual `L.Point` instances, not arrays.
6293 * Note that the projection instances do not inherit from Leafet's `Class` object,
6294 * and can't be instantiated. Also, new classes can't inherit from them,
6295 * and methods can't be added to them with the `include` function.
6302 var index = (Object.freeze || Object)({
6305 SphericalMercator: SphericalMercator
6310 * @crs L.CRS.EPSG3395
6312 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
6314 var EPSG3395 = extend({}, Earth, {
6316 projection: Mercator,
6318 transformation: (function () {
6319 var scale = 0.5 / (Math.PI * Mercator.R);
6320 return toTransformation(scale, 0.5, -scale, 0.5);
6326 * @crs L.CRS.EPSG4326
6328 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
6330 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
6331 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer`
6332 * with this CRS, ensure that there are two 256x256 pixel tiles covering the
6333 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
6334 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
6337 var EPSG4326 = extend({}, Earth, {
6340 transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
6347 * A simple CRS that maps longitude and latitude into `x` and `y` directly.
6348 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
6349 * axis should still be inverted (going from bottom to top). `distance()` returns
6350 * simple euclidean distance.
6353 var Simple = extend({}, CRS, {
6355 transformation: toTransformation(1, 0, -1, 0),
6357 scale: function (zoom) {
6358 return Math.pow(2, zoom);
6361 zoom: function (scale) {
6362 return Math.log(scale) / Math.LN2;
6365 distance: function (latlng1, latlng2) {
6366 var dx = latlng2.lng - latlng1.lng,
6367 dy = latlng2.lat - latlng1.lat;
6369 return Math.sqrt(dx * dx + dy * dy);
6376 CRS.EPSG3395 = EPSG3395;
6377 CRS.EPSG3857 = EPSG3857;
6378 CRS.EPSG900913 = EPSG900913;
6379 CRS.EPSG4326 = EPSG4326;
6380 CRS.Simple = Simple;
6388 * A set of methods from the Layer base class that all Leaflet layers use.
6389 * Inherits all methods, options and events from `L.Evented`.
6394 * var layer = L.Marker(latlng).addTo(map);
6400 * Fired after the layer is added to a map
6402 * @event remove: Event
6403 * Fired after the layer is removed from a map
6407 var Layer = Evented.extend({
6409 // Classes extending `L.Layer` will inherit the following options:
6411 // @option pane: String = 'overlayPane'
6412 // 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.
6413 pane: 'overlayPane',
6415 // @option attribution: String = null
6416 // String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox".
6419 bubblingMouseEvents: true
6423 * Classes extending `L.Layer` will inherit the following methods:
6425 * @method addTo(map: Map|LayerGroup): this
6426 * Adds the layer to the given map or layer group.
6428 addTo: function (map) {
6433 // @method remove: this
6434 // Removes the layer from the map it is currently active on.
6435 remove: function () {
6436 return this.removeFrom(this._map || this._mapToAdd);
6439 // @method removeFrom(map: Map): this
6440 // Removes the layer from the given map
6441 removeFrom: function (obj) {
6443 obj.removeLayer(this);
6448 // @method getPane(name? : String): HTMLElement
6449 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
6450 getPane: function (name) {
6451 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
6454 addInteractiveTarget: function (targetEl) {
6455 this._map._targets[stamp(targetEl)] = this;
6459 removeInteractiveTarget: function (targetEl) {
6460 delete this._map._targets[stamp(targetEl)];
6464 // @method getAttribution: String
6465 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
6466 getAttribution: function () {
6467 return this.options.attribution;
6470 _layerAdd: function (e) {
6473 // check in case layer gets added and then removed before the map is ready
6474 if (!map.hasLayer(this)) { return; }
6477 this._zoomAnimated = map._zoomAnimated;
6479 if (this.getEvents) {
6480 var events = this.getEvents();
6481 map.on(events, this);
6482 this.once('remove', function () {
6483 map.off(events, this);
6489 if (this.getAttribution && map.attributionControl) {
6490 map.attributionControl.addAttribution(this.getAttribution());
6494 map.fire('layeradd', {layer: this});
6498 /* @section Extension methods
6501 * Every layer should extend from `L.Layer` and (re-)implement the following methods.
6503 * @method onAdd(map: Map): this
6504 * 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).
6506 * @method onRemove(map: Map): this
6507 * 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).
6509 * @method getEvents(): Object
6510 * 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.
6512 * @method getAttribution(): String
6513 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
6515 * @method beforeAdd(map: Map): this
6516 * 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.
6521 * @section Layer events
6523 * @event layeradd: LayerEvent
6524 * Fired when a new layer is added to the map.
6526 * @event layerremove: LayerEvent
6527 * Fired when some layer is removed from the map
6529 * @section Methods for Layers and Controls
6532 // @method addLayer(layer: Layer): this
6533 // Adds the given layer to the map
6534 addLayer: function (layer) {
6535 if (!layer._layerAdd) {
6536 throw new Error('The provided object is not a Layer.');
6539 var id = stamp(layer);
6540 if (this._layers[id]) { return this; }
6541 this._layers[id] = layer;
6543 layer._mapToAdd = this;
6545 if (layer.beforeAdd) {
6546 layer.beforeAdd(this);
6549 this.whenReady(layer._layerAdd, layer);
6554 // @method removeLayer(layer: Layer): this
6555 // Removes the given layer from the map.
6556 removeLayer: function (layer) {
6557 var id = stamp(layer);
6559 if (!this._layers[id]) { return this; }
6562 layer.onRemove(this);
6565 if (layer.getAttribution && this.attributionControl) {
6566 this.attributionControl.removeAttribution(layer.getAttribution());
6569 delete this._layers[id];
6572 this.fire('layerremove', {layer: layer});
6573 layer.fire('remove');
6576 layer._map = layer._mapToAdd = null;
6581 // @method hasLayer(layer: Layer): Boolean
6582 // Returns `true` if the given layer is currently added to the map
6583 hasLayer: function (layer) {
6584 return !!layer && (stamp(layer) in this._layers);
6587 /* @method eachLayer(fn: Function, context?: Object): this
6588 * Iterates over the layers of the map, optionally specifying context of the iterator function.
6590 * map.eachLayer(function(layer){
6591 * layer.bindPopup('Hello');
6595 eachLayer: function (method, context) {
6596 for (var i in this._layers) {
6597 method.call(context, this._layers[i]);
6602 _addLayers: function (layers) {
6603 layers = layers ? (isArray(layers) ? layers : [layers]) : [];
6605 for (var i = 0, len = layers.length; i < len; i++) {
6606 this.addLayer(layers[i]);
6610 _addZoomLimit: function (layer) {
6611 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
6612 this._zoomBoundLayers[stamp(layer)] = layer;
6613 this._updateZoomLevels();
6617 _removeZoomLimit: function (layer) {
6618 var id = stamp(layer);
6620 if (this._zoomBoundLayers[id]) {
6621 delete this._zoomBoundLayers[id];
6622 this._updateZoomLevels();
6626 _updateZoomLevels: function () {
6627 var minZoom = Infinity,
6628 maxZoom = -Infinity,
6629 oldZoomSpan = this._getZoomSpan();
6631 for (var i in this._zoomBoundLayers) {
6632 var options = this._zoomBoundLayers[i].options;
6634 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
6635 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
6638 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
6639 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
6641 // @section Map state change events
6642 // @event zoomlevelschange: Event
6643 // Fired when the number of zoomlevels on the map is changed due
6644 // to adding or removing a layer.
6645 if (oldZoomSpan !== this._getZoomSpan()) {
6646 this.fire('zoomlevelschange');
6649 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
6650 this.setZoom(this._layersMaxZoom);
6652 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
6653 this.setZoom(this._layersMinZoom);
6663 * Used to group several layers and handle them as one. If you add it to the map,
6664 * any layers added or removed from the group will be added/removed on the map as
6665 * well. Extends `Layer`.
6670 * L.layerGroup([marker1, marker2])
6671 * .addLayer(polyline)
6676 var LayerGroup = Layer.extend({
6678 initialize: function (layers, options) {
6679 setOptions(this, options);
6686 for (i = 0, len = layers.length; i < len; i++) {
6687 this.addLayer(layers[i]);
6692 // @method addLayer(layer: Layer): this
6693 // Adds the given layer to the group.
6694 addLayer: function (layer) {
6695 var id = this.getLayerId(layer);
6697 this._layers[id] = layer;
6700 this._map.addLayer(layer);
6706 // @method removeLayer(layer: Layer): this
6707 // Removes the given layer from the group.
6709 // @method removeLayer(id: Number): this
6710 // Removes the layer with the given internal ID from the group.
6711 removeLayer: function (layer) {
6712 var id = layer in this._layers ? layer : this.getLayerId(layer);
6714 if (this._map && this._layers[id]) {
6715 this._map.removeLayer(this._layers[id]);
6718 delete this._layers[id];
6723 // @method hasLayer(layer: Layer): Boolean
6724 // Returns `true` if the given layer is currently added to the group.
6726 // @method hasLayer(id: Number): Boolean
6727 // Returns `true` if the given internal ID is currently added to the group.
6728 hasLayer: function (layer) {
6729 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
6732 // @method clearLayers(): this
6733 // Removes all the layers from the group.
6734 clearLayers: function () {
6735 return this.eachLayer(this.removeLayer, this);
6738 // @method invoke(methodName: String, …): this
6739 // Calls `methodName` on every layer contained in this group, passing any
6740 // additional parameters. Has no effect if the layers contained do not
6741 // implement `methodName`.
6742 invoke: function (methodName) {
6743 var args = Array.prototype.slice.call(arguments, 1),
6746 for (i in this._layers) {
6747 layer = this._layers[i];
6749 if (layer[methodName]) {
6750 layer[methodName].apply(layer, args);
6757 onAdd: function (map) {
6758 this.eachLayer(map.addLayer, map);
6761 onRemove: function (map) {
6762 this.eachLayer(map.removeLayer, map);
6765 // @method eachLayer(fn: Function, context?: Object): this
6766 // Iterates over the layers of the group, optionally specifying context of the iterator function.
6768 // group.eachLayer(function (layer) {
6769 // layer.bindPopup('Hello');
6772 eachLayer: function (method, context) {
6773 for (var i in this._layers) {
6774 method.call(context, this._layers[i]);
6779 // @method getLayer(id: Number): Layer
6780 // Returns the layer with the given internal ID.
6781 getLayer: function (id) {
6782 return this._layers[id];
6785 // @method getLayers(): Layer[]
6786 // Returns an array of all the layers added to the group.
6787 getLayers: function () {
6789 this.eachLayer(layers.push, layers);
6793 // @method setZIndex(zIndex: Number): this
6794 // Calls `setZIndex` on every layer contained in this group, passing the z-index.
6795 setZIndex: function (zIndex) {
6796 return this.invoke('setZIndex', zIndex);
6799 // @method getLayerId(layer: Layer): Number
6800 // Returns the internal ID for a layer
6801 getLayerId: function (layer) {
6802 return stamp(layer);
6807 // @factory L.layerGroup(layers?: Layer[], options?: Object)
6808 // Create a layer group, optionally given an initial set of layers and an `options` object.
6809 var layerGroup = function (layers, options) {
6810 return new LayerGroup(layers, options);
6814 * @class FeatureGroup
6815 * @aka L.FeatureGroup
6816 * @inherits LayerGroup
6818 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
6819 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
6820 * * Events are propagated to the `FeatureGroup`, so if the group has an event
6821 * handler, it will handle events from any of the layers. This includes mouse events
6822 * and custom events.
6823 * * Has `layeradd` and `layerremove` events
6828 * L.featureGroup([marker1, marker2, polyline])
6829 * .bindPopup('Hello world!')
6830 * .on('click', function() { alert('Clicked on a member of the group!'); })
6835 var FeatureGroup = LayerGroup.extend({
6837 addLayer: function (layer) {
6838 if (this.hasLayer(layer)) {
6842 layer.addEventParent(this);
6844 LayerGroup.prototype.addLayer.call(this, layer);
6846 // @event layeradd: LayerEvent
6847 // Fired when a layer is added to this `FeatureGroup`
6848 return this.fire('layeradd', {layer: layer});
6851 removeLayer: function (layer) {
6852 if (!this.hasLayer(layer)) {
6855 if (layer in this._layers) {
6856 layer = this._layers[layer];
6859 layer.removeEventParent(this);
6861 LayerGroup.prototype.removeLayer.call(this, layer);
6863 // @event layerremove: LayerEvent
6864 // Fired when a layer is removed from this `FeatureGroup`
6865 return this.fire('layerremove', {layer: layer});
6868 // @method setStyle(style: Path options): this
6869 // Sets the given path options to each layer of the group that has a `setStyle` method.
6870 setStyle: function (style) {
6871 return this.invoke('setStyle', style);
6874 // @method bringToFront(): this
6875 // Brings the layer group to the top of all other layers
6876 bringToFront: function () {
6877 return this.invoke('bringToFront');
6880 // @method bringToBack(): this
6881 // Brings the layer group to the back of all other layers
6882 bringToBack: function () {
6883 return this.invoke('bringToBack');
6886 // @method getBounds(): LatLngBounds
6887 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
6888 getBounds: function () {
6889 var bounds = new LatLngBounds();
6891 for (var id in this._layers) {
6892 var layer = this._layers[id];
6893 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
6899 // @factory L.featureGroup(layers: Layer[])
6900 // Create a feature group, optionally given an initial set of layers.
6901 var featureGroup = function (layers) {
6902 return new FeatureGroup(layers);
6909 * Represents an icon to provide when creating a marker.
6914 * var myIcon = L.icon({
6915 * iconUrl: 'my-icon.png',
6916 * iconRetinaUrl: 'my-icon@2x.png',
6917 * iconSize: [38, 95],
6918 * iconAnchor: [22, 94],
6919 * popupAnchor: [-3, -76],
6920 * shadowUrl: 'my-icon-shadow.png',
6921 * shadowRetinaUrl: 'my-icon-shadow@2x.png',
6922 * shadowSize: [68, 95],
6923 * shadowAnchor: [22, 94]
6926 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
6929 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
6933 var Icon = Class.extend({
6938 * @option iconUrl: String = null
6939 * **(required)** The URL to the icon image (absolute or relative to your script path).
6941 * @option iconRetinaUrl: String = null
6942 * The URL to a retina sized version of the icon image (absolute or relative to your
6943 * script path). Used for Retina screen devices.
6945 * @option iconSize: Point = null
6946 * Size of the icon image in pixels.
6948 * @option iconAnchor: Point = null
6949 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
6950 * will be aligned so that this point is at the marker's geographical location. Centered
6951 * by default if size is specified, also can be set in CSS with negative margins.
6953 * @option popupAnchor: Point = [0, 0]
6954 * The coordinates of the point from which popups will "open", relative to the icon anchor.
6956 * @option tooltipAnchor: Point = [0, 0]
6957 * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
6959 * @option shadowUrl: String = null
6960 * The URL to the icon shadow image. If not specified, no shadow image will be created.
6962 * @option shadowRetinaUrl: String = null
6964 * @option shadowSize: Point = null
6965 * Size of the shadow image in pixels.
6967 * @option shadowAnchor: Point = null
6968 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
6969 * as iconAnchor if not specified).
6971 * @option className: String = ''
6972 * A custom class name to assign to both icon and shadow images. Empty by default.
6976 popupAnchor: [0, 0],
6977 tooltipAnchor: [0, 0],
6980 initialize: function (options) {
6981 setOptions(this, options);
6984 // @method createIcon(oldIcon?: HTMLElement): HTMLElement
6985 // Called internally when the icon has to be shown, returns a `<img>` HTML element
6986 // styled according to the options.
6987 createIcon: function (oldIcon) {
6988 return this._createIcon('icon', oldIcon);
6991 // @method createShadow(oldIcon?: HTMLElement): HTMLElement
6992 // As `createIcon`, but for the shadow beneath it.
6993 createShadow: function (oldIcon) {
6994 return this._createIcon('shadow', oldIcon);
6997 _createIcon: function (name, oldIcon) {
6998 var src = this._getIconUrl(name);
7001 if (name === 'icon') {
7002 throw new Error('iconUrl not set in Icon options (see the docs).');
7007 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
7008 this._setIconStyles(img, name);
7013 _setIconStyles: function (img, name) {
7014 var options = this.options;
7015 var sizeOption = options[name + 'Size'];
7017 if (typeof sizeOption === 'number') {
7018 sizeOption = [sizeOption, sizeOption];
7021 var size = toPoint(sizeOption),
7022 anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
7023 size && size.divideBy(2, true));
7025 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
7028 img.style.marginLeft = (-anchor.x) + 'px';
7029 img.style.marginTop = (-anchor.y) + 'px';
7033 img.style.width = size.x + 'px';
7034 img.style.height = size.y + 'px';
7038 _createImg: function (src, el) {
7039 el = el || document.createElement('img');
7044 _getIconUrl: function (name) {
7045 return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
7050 // @factory L.icon(options: Icon options)
7051 // Creates an icon instance with the given options.
7052 function icon(options) {
7053 return new Icon(options);
7057 * @miniclass Icon.Default (Icon)
7058 * @aka L.Icon.Default
7061 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
7062 * no icon is specified. Points to the blue marker image distributed with Leaflet
7065 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
7066 * (which is a set of `Icon options`).
7068 * If you want to _completely_ replace the default icon, override the
7069 * `L.Marker.prototype.options.icon` with your own icon instead.
7072 var IconDefault = Icon.extend({
7075 iconUrl: 'marker-icon.png',
7076 iconRetinaUrl: 'marker-icon-2x.png',
7077 shadowUrl: 'marker-shadow.png',
7079 iconAnchor: [12, 41],
7080 popupAnchor: [1, -34],
7081 tooltipAnchor: [16, -28],
7082 shadowSize: [41, 41]
7085 _getIconUrl: function (name) {
7086 if (!IconDefault.imagePath) { // Deprecated, backwards-compatibility only
7087 IconDefault.imagePath = this._detectIconPath();
7090 // @option imagePath: String
7091 // `Icon.Default` will try to auto-detect the location of the
7092 // blue icon images. If you are placing these images in a non-standard
7093 // way, set this option to point to the right path.
7094 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
7097 _detectIconPath: function () {
7098 var el = create$1('div', 'leaflet-default-icon-path', document.body);
7099 var path = getStyle(el, 'background-image') ||
7100 getStyle(el, 'backgroundImage'); // IE8
7102 document.body.removeChild(el);
7104 if (path === null || path.indexOf('url') !== 0) {
7107 path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, '');
7115 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7119 /* @namespace Marker
7120 * @section Interaction handlers
7122 * 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:
7125 * marker.dragging.disable();
7128 * @property dragging: Handler
7129 * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
7132 var MarkerDrag = Handler.extend({
7133 initialize: function (marker) {
7134 this._marker = marker;
7137 addHooks: function () {
7138 var icon = this._marker._icon;
7140 if (!this._draggable) {
7141 this._draggable = new Draggable(icon, icon, true);
7144 this._draggable.on({
7145 dragstart: this._onDragStart,
7146 predrag: this._onPreDrag,
7148 dragend: this._onDragEnd
7151 addClass(icon, 'leaflet-marker-draggable');
7154 removeHooks: function () {
7155 this._draggable.off({
7156 dragstart: this._onDragStart,
7157 predrag: this._onPreDrag,
7159 dragend: this._onDragEnd
7162 if (this._marker._icon) {
7163 removeClass(this._marker._icon, 'leaflet-marker-draggable');
7167 moved: function () {
7168 return this._draggable && this._draggable._moved;
7171 _adjustPan: function (e) {
7172 var marker = this._marker,
7174 speed = this._marker.options.autoPanSpeed,
7175 padding = this._marker.options.autoPanPadding,
7176 iconPos = L.DomUtil.getPosition(marker._icon),
7177 bounds = map.getPixelBounds(),
7178 origin = map.getPixelOrigin();
7180 var panBounds = toBounds(
7181 bounds.min._subtract(origin).add(padding),
7182 bounds.max._subtract(origin).subtract(padding)
7185 if (!panBounds.contains(iconPos)) {
7186 // Compute incremental movement
7187 var movement = toPoint(
7188 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
7189 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
7191 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
7192 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
7193 ).multiplyBy(speed);
7195 map.panBy(movement, {animate: false});
7197 this._draggable._newPos._add(movement);
7198 this._draggable._startPos._add(movement);
7200 L.DomUtil.setPosition(marker._icon, this._draggable._newPos);
7203 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7207 _onDragStart: function () {
7208 // @section Dragging events
7209 // @event dragstart: Event
7210 // Fired when the user starts dragging the marker.
7212 // @event movestart: Event
7213 // Fired when the marker starts moving (because of dragging).
7215 this._oldLatLng = this._marker.getLatLng();
7222 _onPreDrag: function (e) {
7223 if (this._marker.options.autoPan) {
7224 cancelAnimFrame(this._panRequest);
7225 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7229 _onDrag: function (e) {
7230 var marker = this._marker,
7231 shadow = marker._shadow,
7232 iconPos = getPosition(marker._icon),
7233 latlng = marker._map.layerPointToLatLng(iconPos);
7235 // update shadow position
7237 setPosition(shadow, iconPos);
7240 marker._latlng = latlng;
7242 e.oldLatLng = this._oldLatLng;
7244 // @event drag: Event
7245 // Fired repeatedly while the user drags the marker.
7251 _onDragEnd: function (e) {
7252 // @event dragend: DragEndEvent
7253 // Fired when the user stops dragging the marker.
7255 cancelAnimFrame(this._panRequest);
7257 // @event moveend: Event
7258 // Fired when the marker stops moving (because of dragging).
7259 delete this._oldLatLng;
7262 .fire('dragend', e);
7268 * @inherits Interactive layer
7270 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
7275 * L.marker([50.5, 30.5]).addTo(map);
7279 var Marker = Layer.extend({
7282 // @aka Marker options
7284 // @option icon: Icon = *
7285 // Icon instance to use for rendering the marker.
7286 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
7287 // If not specified, a common instance of `L.Icon.Default` is used.
7288 icon: new IconDefault(),
7290 // Option inherited from "Interactive layer" abstract class
7293 // @option draggable: Boolean = false
7294 // Whether the marker is draggable with mouse/touch or not.
7297 // @option autoPan: Boolean = false
7298 // Set it to `true` if you want the map to do panning animation when marker hits the edges.
7301 // @option autoPanPadding: Point = Point(50, 50)
7302 // Equivalent of setting both top left and bottom right autopan padding to the same value.
7303 autoPanPadding: [50, 50],
7305 // @option autoPanSpeed: Number = 10
7306 // Number of pixels the map should move by.
7309 // @option keyboard: Boolean = true
7310 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
7313 // @option title: String = ''
7314 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
7317 // @option alt: String = ''
7318 // Text for the `alt` attribute of the icon image (useful for accessibility).
7321 // @option zIndexOffset: Number = 0
7322 // 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).
7325 // @option opacity: Number = 1.0
7326 // The opacity of the marker.
7329 // @option riseOnHover: Boolean = false
7330 // If `true`, the marker will get on top of others when you hover the mouse over it.
7333 // @option riseOffset: Number = 250
7334 // The z-index offset used for the `riseOnHover` feature.
7337 // @option pane: String = 'markerPane'
7338 // `Map pane` where the markers icon will be added.
7341 // @option bubblingMouseEvents: Boolean = false
7342 // When `true`, a mouse event on this marker will trigger the same event on the map
7343 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7344 bubblingMouseEvents: false
7349 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
7352 initialize: function (latlng, options) {
7353 setOptions(this, options);
7354 this._latlng = toLatLng(latlng);
7357 onAdd: function (map) {
7358 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
7360 if (this._zoomAnimated) {
7361 map.on('zoomanim', this._animateZoom, this);
7368 onRemove: function (map) {
7369 if (this.dragging && this.dragging.enabled()) {
7370 this.options.draggable = true;
7371 this.dragging.removeHooks();
7373 delete this.dragging;
7375 if (this._zoomAnimated) {
7376 map.off('zoomanim', this._animateZoom, this);
7380 this._removeShadow();
7383 getEvents: function () {
7386 viewreset: this.update
7390 // @method getLatLng: LatLng
7391 // Returns the current geographical position of the marker.
7392 getLatLng: function () {
7393 return this._latlng;
7396 // @method setLatLng(latlng: LatLng): this
7397 // Changes the marker position to the given point.
7398 setLatLng: function (latlng) {
7399 var oldLatLng = this._latlng;
7400 this._latlng = toLatLng(latlng);
7403 // @event move: Event
7404 // 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`.
7405 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7408 // @method setZIndexOffset(offset: Number): this
7409 // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
7410 setZIndexOffset: function (offset) {
7411 this.options.zIndexOffset = offset;
7412 return this.update();
7415 // @method setIcon(icon: Icon): this
7416 // Changes the marker icon.
7417 setIcon: function (icon) {
7419 this.options.icon = icon;
7427 this.bindPopup(this._popup, this._popup.options);
7433 getElement: function () {
7437 update: function () {
7439 if (this._icon && this._map) {
7440 var pos = this._map.latLngToLayerPoint(this._latlng).round();
7447 _initIcon: function () {
7448 var options = this.options,
7449 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
7451 var icon = options.icon.createIcon(this._icon),
7454 // if we're not reusing the icon, remove the old one and init new one
7455 if (icon !== this._icon) {
7461 if (options.title) {
7462 icon.title = options.title;
7465 if (icon.tagName === 'IMG') {
7466 icon.alt = options.alt || '';
7470 addClass(icon, classToAdd);
7472 if (options.keyboard) {
7473 icon.tabIndex = '0';
7478 if (options.riseOnHover) {
7480 mouseover: this._bringToFront,
7481 mouseout: this._resetZIndex
7485 var newShadow = options.icon.createShadow(this._shadow),
7488 if (newShadow !== this._shadow) {
7489 this._removeShadow();
7494 addClass(newShadow, classToAdd);
7497 this._shadow = newShadow;
7500 if (options.opacity < 1) {
7501 this._updateOpacity();
7506 this.getPane().appendChild(this._icon);
7508 this._initInteraction();
7509 if (newShadow && addShadow) {
7510 this.getPane('shadowPane').appendChild(this._shadow);
7514 _removeIcon: function () {
7515 if (this.options.riseOnHover) {
7517 mouseover: this._bringToFront,
7518 mouseout: this._resetZIndex
7523 this.removeInteractiveTarget(this._icon);
7528 _removeShadow: function () {
7530 remove(this._shadow);
7532 this._shadow = null;
7535 _setPos: function (pos) {
7536 setPosition(this._icon, pos);
7539 setPosition(this._shadow, pos);
7542 this._zIndex = pos.y + this.options.zIndexOffset;
7544 this._resetZIndex();
7547 _updateZIndex: function (offset) {
7548 this._icon.style.zIndex = this._zIndex + offset;
7551 _animateZoom: function (opt) {
7552 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
7557 _initInteraction: function () {
7559 if (!this.options.interactive) { return; }
7561 addClass(this._icon, 'leaflet-interactive');
7563 this.addInteractiveTarget(this._icon);
7566 var draggable = this.options.draggable;
7567 if (this.dragging) {
7568 draggable = this.dragging.enabled();
7569 this.dragging.disable();
7572 this.dragging = new MarkerDrag(this);
7575 this.dragging.enable();
7580 // @method setOpacity(opacity: Number): this
7581 // Changes the opacity of the marker.
7582 setOpacity: function (opacity) {
7583 this.options.opacity = opacity;
7585 this._updateOpacity();
7591 _updateOpacity: function () {
7592 var opacity = this.options.opacity;
7594 setOpacity(this._icon, opacity);
7597 setOpacity(this._shadow, opacity);
7601 _bringToFront: function () {
7602 this._updateZIndex(this.options.riseOffset);
7605 _resetZIndex: function () {
7606 this._updateZIndex(0);
7609 _getPopupAnchor: function () {
7610 return this.options.icon.options.popupAnchor;
7613 _getTooltipAnchor: function () {
7614 return this.options.icon.options.tooltipAnchor;
7619 // factory L.marker(latlng: LatLng, options? : Marker options)
7621 // @factory L.marker(latlng: LatLng, options? : Marker options)
7622 // Instantiates a Marker object given a geographical point and optionally an options object.
7623 function marker(latlng, options) {
7624 return new Marker(latlng, options);
7630 * @inherits Interactive layer
7632 * An abstract class that contains options and constants shared between vector
7633 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
7636 var Path = Layer.extend({
7639 // @aka Path options
7641 // @option stroke: Boolean = true
7642 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
7645 // @option color: String = '#3388ff'
7649 // @option weight: Number = 3
7650 // Stroke width in pixels
7653 // @option opacity: Number = 1.0
7657 // @option lineCap: String= 'round'
7658 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
7661 // @option lineJoin: String = 'round'
7662 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
7665 // @option dashArray: String = null
7666 // 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).
7669 // @option dashOffset: String = null
7670 // 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).
7673 // @option fill: Boolean = depends
7674 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
7677 // @option fillColor: String = *
7678 // Fill color. Defaults to the value of the [`color`](#path-color) option
7681 // @option fillOpacity: Number = 0.2
7685 // @option fillRule: String = 'evenodd'
7686 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
7687 fillRule: 'evenodd',
7691 // Option inherited from "Interactive layer" abstract class
7694 // @option bubblingMouseEvents: Boolean = true
7695 // When `true`, a mouse event on this path will trigger the same event on the map
7696 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7697 bubblingMouseEvents: true
7700 beforeAdd: function (map) {
7701 // Renderer is set here because we need to call renderer.getEvents
7702 // before this.getEvents.
7703 this._renderer = map.getRenderer(this);
7706 onAdd: function () {
7707 this._renderer._initPath(this);
7709 this._renderer._addPath(this);
7712 onRemove: function () {
7713 this._renderer._removePath(this);
7716 // @method redraw(): this
7717 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
7718 redraw: function () {
7720 this._renderer._updatePath(this);
7725 // @method setStyle(style: Path options): this
7726 // Changes the appearance of a Path based on the options in the `Path options` object.
7727 setStyle: function (style) {
7728 setOptions(this, style);
7729 if (this._renderer) {
7730 this._renderer._updateStyle(this);
7735 // @method bringToFront(): this
7736 // Brings the layer to the top of all path layers.
7737 bringToFront: function () {
7738 if (this._renderer) {
7739 this._renderer._bringToFront(this);
7744 // @method bringToBack(): this
7745 // Brings the layer to the bottom of all path layers.
7746 bringToBack: function () {
7747 if (this._renderer) {
7748 this._renderer._bringToBack(this);
7753 getElement: function () {
7757 _reset: function () {
7758 // defined in child classes
7763 _clickTolerance: function () {
7764 // used when doing hit detection for Canvas layers
7765 return (this.options.stroke ? this.options.weight / 2 : 0) + this._renderer.options.tolerance;
7770 * @class CircleMarker
7771 * @aka L.CircleMarker
7774 * A circle of a fixed size with radius specified in pixels. Extends `Path`.
7777 var CircleMarker = Path.extend({
7780 // @aka CircleMarker options
7784 // @option radius: Number = 10
7785 // Radius of the circle marker, in pixels
7789 initialize: function (latlng, options) {
7790 setOptions(this, options);
7791 this._latlng = toLatLng(latlng);
7792 this._radius = this.options.radius;
7795 // @method setLatLng(latLng: LatLng): this
7796 // Sets the position of a circle marker to a new location.
7797 setLatLng: function (latlng) {
7798 this._latlng = toLatLng(latlng);
7800 return this.fire('move', {latlng: this._latlng});
7803 // @method getLatLng(): LatLng
7804 // Returns the current geographical position of the circle marker
7805 getLatLng: function () {
7806 return this._latlng;
7809 // @method setRadius(radius: Number): this
7810 // Sets the radius of a circle marker. Units are in pixels.
7811 setRadius: function (radius) {
7812 this.options.radius = this._radius = radius;
7813 return this.redraw();
7816 // @method getRadius(): Number
7817 // Returns the current radius of the circle
7818 getRadius: function () {
7819 return this._radius;
7822 setStyle : function (options) {
7823 var radius = options && options.radius || this._radius;
7824 Path.prototype.setStyle.call(this, options);
7825 this.setRadius(radius);
7829 _project: function () {
7830 this._point = this._map.latLngToLayerPoint(this._latlng);
7831 this._updateBounds();
7834 _updateBounds: function () {
7835 var r = this._radius,
7836 r2 = this._radiusY || r,
7837 w = this._clickTolerance(),
7838 p = [r + w, r2 + w];
7839 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
7842 _update: function () {
7848 _updatePath: function () {
7849 this._renderer._updateCircle(this);
7852 _empty: function () {
7853 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
7856 // Needed by the `Canvas` renderer for interactivity
7857 _containsPoint: function (p) {
7858 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
7863 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
7864 // Instantiates a circle marker object given a geographical point, and an optional options object.
7865 function circleMarker(latlng, options) {
7866 return new CircleMarker(latlng, options);
7872 * @inherits CircleMarker
7874 * A class for drawing circle overlays on a map. Extends `CircleMarker`.
7876 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
7881 * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
7885 var Circle = CircleMarker.extend({
7887 initialize: function (latlng, options, legacyOptions) {
7888 if (typeof options === 'number') {
7889 // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
7890 options = extend({}, legacyOptions, {radius: options});
7892 setOptions(this, options);
7893 this._latlng = toLatLng(latlng);
7895 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
7898 // @aka Circle options
7899 // @option radius: Number; Radius of the circle, in meters.
7900 this._mRadius = this.options.radius;
7903 // @method setRadius(radius: Number): this
7904 // Sets the radius of a circle. Units are in meters.
7905 setRadius: function (radius) {
7906 this._mRadius = radius;
7907 return this.redraw();
7910 // @method getRadius(): Number
7911 // Returns the current radius of a circle. Units are in meters.
7912 getRadius: function () {
7913 return this._mRadius;
7916 // @method getBounds(): LatLngBounds
7917 // Returns the `LatLngBounds` of the path.
7918 getBounds: function () {
7919 var half = [this._radius, this._radiusY || this._radius];
7921 return new LatLngBounds(
7922 this._map.layerPointToLatLng(this._point.subtract(half)),
7923 this._map.layerPointToLatLng(this._point.add(half)));
7926 setStyle: Path.prototype.setStyle,
7928 _project: function () {
7930 var lng = this._latlng.lng,
7931 lat = this._latlng.lat,
7933 crs = map.options.crs;
7935 if (crs.distance === Earth.distance) {
7936 var d = Math.PI / 180,
7937 latR = (this._mRadius / Earth.R) / d,
7938 top = map.project([lat + latR, lng]),
7939 bottom = map.project([lat - latR, lng]),
7940 p = top.add(bottom).divideBy(2),
7941 lat2 = map.unproject(p).lat,
7942 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
7943 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
7945 if (isNaN(lngR) || lngR === 0) {
7946 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
7949 this._point = p.subtract(map.getPixelOrigin());
7950 this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
7951 this._radiusY = p.y - top.y;
7954 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
7956 this._point = map.latLngToLayerPoint(this._latlng);
7957 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
7960 this._updateBounds();
7964 // @factory L.circle(latlng: LatLng, options?: Circle options)
7965 // Instantiates a circle object given a geographical point, and an options object
7966 // which contains the circle radius.
7968 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
7969 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
7970 // Do not use in new applications or plugins.
7971 function circle(latlng, options, legacyOptions) {
7972 return new Circle(latlng, options, legacyOptions);
7980 * A class for drawing polyline overlays on a map. Extends `Path`.
7985 * // create a red polyline from an array of LatLng points
7992 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
7994 * // zoom the map to the polyline
7995 * map.fitBounds(polyline.getBounds());
7998 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
8001 * // create a red polyline from an array of arrays of LatLng points
8003 * [[45.51, -122.68],
8014 var Polyline = Path.extend({
8017 // @aka Polyline options
8019 // @option smoothFactor: Number = 1.0
8020 // How much to simplify the polyline on each zoom level. More means
8021 // better performance and smoother look, and less means more accurate representation.
8024 // @option noClip: Boolean = false
8025 // Disable polyline clipping.
8029 initialize: function (latlngs, options) {
8030 setOptions(this, options);
8031 this._setLatLngs(latlngs);
8034 // @method getLatLngs(): LatLng[]
8035 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8036 getLatLngs: function () {
8037 return this._latlngs;
8040 // @method setLatLngs(latlngs: LatLng[]): this
8041 // Replaces all the points in the polyline with the given array of geographical points.
8042 setLatLngs: function (latlngs) {
8043 this._setLatLngs(latlngs);
8044 return this.redraw();
8047 // @method isEmpty(): Boolean
8048 // Returns `true` if the Polyline has no LatLngs.
8049 isEmpty: function () {
8050 return !this._latlngs.length;
8053 // @method closestLayerPoint: Point
8054 // Returns the point closest to `p` on the Polyline.
8055 closestLayerPoint: function (p) {
8056 var minDistance = Infinity,
8058 closest = _sqClosestPointOnSegment,
8061 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8062 var points = this._parts[j];
8064 for (var i = 1, len = points.length; i < len; i++) {
8068 var sqDist = closest(p, p1, p2, true);
8070 if (sqDist < minDistance) {
8071 minDistance = sqDist;
8072 minPoint = closest(p, p1, p2);
8077 minPoint.distance = Math.sqrt(minDistance);
8082 // @method getCenter(): LatLng
8083 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
8084 getCenter: function () {
8085 // throws error when not yet added to map as this center calculation requires projected coordinates
8087 throw new Error('Must add layer to map before using getCenter()');
8090 var i, halfDist, segDist, dist, p1, p2, ratio,
8091 points = this._rings[0],
8092 len = points.length;
8094 if (!len) { return null; }
8096 // polyline centroid algorithm; only uses the first ring if there are multiple
8098 for (i = 0, halfDist = 0; i < len - 1; i++) {
8099 halfDist += points[i].distanceTo(points[i + 1]) / 2;
8102 // The line is so small in the current view that all points are on the same pixel.
8103 if (halfDist === 0) {
8104 return this._map.layerPointToLatLng(points[0]);
8107 for (i = 0, dist = 0; i < len - 1; i++) {
8110 segDist = p1.distanceTo(p2);
8113 if (dist > halfDist) {
8114 ratio = (dist - halfDist) / segDist;
8115 return this._map.layerPointToLatLng([
8116 p2.x - ratio * (p2.x - p1.x),
8117 p2.y - ratio * (p2.y - p1.y)
8123 // @method getBounds(): LatLngBounds
8124 // Returns the `LatLngBounds` of the path.
8125 getBounds: function () {
8126 return this._bounds;
8129 // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
8130 // Adds a given point to the polyline. By default, adds to the first ring of
8131 // the polyline in case of a multi-polyline, but can be overridden by passing
8132 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8133 addLatLng: function (latlng, latlngs) {
8134 latlngs = latlngs || this._defaultShape();
8135 latlng = toLatLng(latlng);
8136 latlngs.push(latlng);
8137 this._bounds.extend(latlng);
8138 return this.redraw();
8141 _setLatLngs: function (latlngs) {
8142 this._bounds = new LatLngBounds();
8143 this._latlngs = this._convertLatLngs(latlngs);
8146 _defaultShape: function () {
8147 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
8150 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8151 _convertLatLngs: function (latlngs) {
8153 flat = isFlat(latlngs);
8155 for (var i = 0, len = latlngs.length; i < len; i++) {
8157 result[i] = toLatLng(latlngs[i]);
8158 this._bounds.extend(result[i]);
8160 result[i] = this._convertLatLngs(latlngs[i]);
8167 _project: function () {
8168 var pxBounds = new Bounds();
8170 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8172 var w = this._clickTolerance(),
8173 p = new Point(w, w);
8175 if (this._bounds.isValid() && pxBounds.isValid()) {
8176 pxBounds.min._subtract(p);
8177 pxBounds.max._add(p);
8178 this._pxBounds = pxBounds;
8182 // recursively turns latlngs into a set of rings with projected coordinates
8183 _projectLatlngs: function (latlngs, result, projectedBounds) {
8184 var flat = latlngs[0] instanceof LatLng,
8185 len = latlngs.length,
8190 for (i = 0; i < len; i++) {
8191 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8192 projectedBounds.extend(ring[i]);
8196 for (i = 0; i < len; i++) {
8197 this._projectLatlngs(latlngs[i], result, projectedBounds);
8202 // clip polyline by renderer bounds so that we have less to render for performance
8203 _clipPoints: function () {
8204 var bounds = this._renderer._bounds;
8207 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8211 if (this.options.noClip) {
8212 this._parts = this._rings;
8216 var parts = this._parts,
8217 i, j, k, len, len2, segment, points;
8219 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8220 points = this._rings[i];
8222 for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8223 segment = clipSegment(points[j], points[j + 1], bounds, j, true);
8225 if (!segment) { continue; }
8227 parts[k] = parts[k] || [];
8228 parts[k].push(segment[0]);
8230 // if segment goes out of screen, or it's the last one, it's the end of the line part
8231 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8232 parts[k].push(segment[1]);
8239 // simplify each clipped part of the polyline for performance
8240 _simplifyPoints: function () {
8241 var parts = this._parts,
8242 tolerance = this.options.smoothFactor;
8244 for (var i = 0, len = parts.length; i < len; i++) {
8245 parts[i] = simplify(parts[i], tolerance);
8249 _update: function () {
8250 if (!this._map) { return; }
8253 this._simplifyPoints();
8257 _updatePath: function () {
8258 this._renderer._updatePoly(this);
8261 // Needed by the `Canvas` renderer for interactivity
8262 _containsPoint: function (p, closed) {
8263 var i, j, k, len, len2, part,
8264 w = this._clickTolerance();
8266 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8268 // hit detection for polylines
8269 for (i = 0, len = this._parts.length; i < len; i++) {
8270 part = this._parts[i];
8272 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8273 if (!closed && (j === 0)) { continue; }
8275 if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
8284 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8285 // Instantiates a polyline object given an array of geographical points and
8286 // optionally an options object. You can create a `Polyline` object with
8287 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8288 // of geographic points.
8289 function polyline(latlngs, options) {
8290 return new Polyline(latlngs, options);
8293 // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
8294 Polyline._flat = _flat;
8299 * @inherits Polyline
8301 * A class for drawing polygon overlays on a map. Extends `Polyline`.
8303 * 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.
8309 * // create a red polygon from an array of LatLng points
8310 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
8312 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8314 * // zoom the map to the polygon
8315 * map.fitBounds(polygon.getBounds());
8318 * 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:
8322 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8323 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8327 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8331 * [ // first polygon
8332 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8333 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8335 * [ // second polygon
8336 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
8342 var Polygon = Polyline.extend({
8348 isEmpty: function () {
8349 return !this._latlngs.length || !this._latlngs[0].length;
8352 getCenter: function () {
8353 // throws error when not yet added to map as this center calculation requires projected coordinates
8355 throw new Error('Must add layer to map before using getCenter()');
8358 var i, j, p1, p2, f, area, x, y, center,
8359 points = this._rings[0],
8360 len = points.length;
8362 if (!len) { return null; }
8364 // polygon centroid algorithm; only uses the first ring if there are multiple
8368 for (i = 0, j = len - 1; i < len; j = i++) {
8372 f = p1.y * p2.x - p2.y * p1.x;
8373 x += (p1.x + p2.x) * f;
8374 y += (p1.y + p2.y) * f;
8379 // Polygon is so small that all points are on same pixel.
8382 center = [x / area, y / area];
8384 return this._map.layerPointToLatLng(center);
8387 _convertLatLngs: function (latlngs) {
8388 var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
8389 len = result.length;
8391 // remove last point if it equals first one
8392 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
8398 _setLatLngs: function (latlngs) {
8399 Polyline.prototype._setLatLngs.call(this, latlngs);
8400 if (isFlat(this._latlngs)) {
8401 this._latlngs = [this._latlngs];
8405 _defaultShape: function () {
8406 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8409 _clipPoints: function () {
8410 // polygons need a different clipping algorithm so we redefine that
8412 var bounds = this._renderer._bounds,
8413 w = this.options.weight,
8414 p = new Point(w, w);
8416 // increase clip padding by stroke width to avoid stroke on clip edges
8417 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
8420 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8424 if (this.options.noClip) {
8425 this._parts = this._rings;
8429 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8430 clipped = clipPolygon(this._rings[i], bounds, true);
8431 if (clipped.length) {
8432 this._parts.push(clipped);
8437 _updatePath: function () {
8438 this._renderer._updatePoly(this, true);
8441 // Needed by the `Canvas` renderer for interactivity
8442 _containsPoint: function (p) {
8444 part, p1, p2, i, j, k, len, len2;
8446 if (!this._pxBounds.contains(p)) { return false; }
8448 // ray casting algorithm for detecting if point is in polygon
8449 for (i = 0, len = this._parts.length; i < len; i++) {
8450 part = this._parts[i];
8452 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8456 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)) {
8462 // also check if it's on polygon stroke
8463 return inside || Polyline.prototype._containsPoint.call(this, p, true);
8469 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8470 function polygon(latlngs, options) {
8471 return new Polygon(latlngs, options);
8477 * @inherits FeatureGroup
8479 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
8480 * GeoJSON data and display it on the map. Extends `FeatureGroup`.
8486 * style: function (feature) {
8487 * return {color: feature.properties.color};
8489 * }).bindPopup(function (layer) {
8490 * return layer.feature.properties.description;
8495 var GeoJSON = FeatureGroup.extend({
8498 * @aka GeoJSON options
8500 * @option pointToLayer: Function = *
8501 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
8502 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
8503 * The default is to spawn a default `Marker`:
8505 * function(geoJsonPoint, latlng) {
8506 * return L.marker(latlng);
8510 * @option style: Function = *
8511 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
8512 * called internally when data is added.
8513 * The default value is to not override any defaults:
8515 * function (geoJsonFeature) {
8520 * @option onEachFeature: Function = *
8521 * A `Function` that will be called once for each created `Feature`, after it has
8522 * been created and styled. Useful for attaching events and popups to features.
8523 * The default is to do nothing with the newly created layers:
8525 * function (feature, layer) {}
8528 * @option filter: Function = *
8529 * A `Function` that will be used to decide whether to include a feature or not.
8530 * The default is to include all features:
8532 * function (geoJsonFeature) {
8536 * Note: dynamically changing the `filter` option will have effect only on newly
8537 * added data. It will _not_ re-evaluate already included features.
8539 * @option coordsToLatLng: Function = *
8540 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
8541 * The default is the `coordsToLatLng` static method.
8544 initialize: function (geojson, options) {
8545 setOptions(this, options);
8550 this.addData(geojson);
8554 // @method addData( <GeoJSON> data ): this
8555 // Adds a GeoJSON object to the layer.
8556 addData: function (geojson) {
8557 var features = isArray(geojson) ? geojson : geojson.features,
8561 for (i = 0, len = features.length; i < len; i++) {
8562 // only add this if geometry or geometries are set and not null
8563 feature = features[i];
8564 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
8565 this.addData(feature);
8571 var options = this.options;
8573 if (options.filter && !options.filter(geojson)) { return this; }
8575 var layer = geometryToLayer(geojson, options);
8579 layer.feature = asFeature(geojson);
8581 layer.defaultOptions = layer.options;
8582 this.resetStyle(layer);
8584 if (options.onEachFeature) {
8585 options.onEachFeature(geojson, layer);
8588 return this.addLayer(layer);
8591 // @method resetStyle( <Path> layer ): this
8592 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
8593 resetStyle: function (layer) {
8594 // reset any custom styles
8595 layer.options = extend({}, layer.defaultOptions);
8596 this._setLayerStyle(layer, this.options.style);
8600 // @method setStyle( <Function> style ): this
8601 // Changes styles of GeoJSON vector layers with the given style function.
8602 setStyle: function (style) {
8603 return this.eachLayer(function (layer) {
8604 this._setLayerStyle(layer, style);
8608 _setLayerStyle: function (layer, style) {
8609 if (typeof style === 'function') {
8610 style = style(layer.feature);
8612 if (layer.setStyle) {
8613 layer.setStyle(style);
8619 // There are several static functions which can be called without instantiating L.GeoJSON:
8621 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
8622 // Creates a `Layer` from a given GeoJSON feature. Can use a custom
8623 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
8624 // functions if provided as options.
8625 function geometryToLayer(geojson, options) {
8627 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
8628 coords = geometry ? geometry.coordinates : null,
8630 pointToLayer = options && options.pointToLayer,
8631 _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
8632 latlng, latlngs, i, len;
8634 if (!coords && !geometry) {
8638 switch (geometry.type) {
8640 latlng = _coordsToLatLng(coords);
8641 return pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng);
8644 for (i = 0, len = coords.length; i < len; i++) {
8645 latlng = _coordsToLatLng(coords[i]);
8646 layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng));
8648 return new FeatureGroup(layers);
8651 case 'MultiLineString':
8652 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
8653 return new Polyline(latlngs, options);
8656 case 'MultiPolygon':
8657 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
8658 return new Polygon(latlngs, options);
8660 case 'GeometryCollection':
8661 for (i = 0, len = geometry.geometries.length; i < len; i++) {
8662 var layer = geometryToLayer({
8663 geometry: geometry.geometries[i],
8665 properties: geojson.properties
8672 return new FeatureGroup(layers);
8675 throw new Error('Invalid GeoJSON object.');
8679 // @function coordsToLatLng(coords: Array): LatLng
8680 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
8681 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
8682 function coordsToLatLng(coords) {
8683 return new LatLng(coords[1], coords[0], coords[2]);
8686 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
8687 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
8688 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
8689 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
8690 function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
8693 for (var i = 0, len = coords.length, latlng; i < len; i++) {
8694 latlng = levelsDeep ?
8695 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
8696 (_coordsToLatLng || coordsToLatLng)(coords[i]);
8698 latlngs.push(latlng);
8704 // @function latLngToCoords(latlng: LatLng, precision?: Number): Array
8705 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
8706 function latLngToCoords(latlng, precision) {
8707 precision = typeof precision === 'number' ? precision : 6;
8708 return latlng.alt !== undefined ?
8709 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
8710 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
8713 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
8714 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
8715 // `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.
8716 function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
8719 for (var i = 0, len = latlngs.length; i < len; i++) {
8720 coords.push(levelsDeep ?
8721 latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) :
8722 latLngToCoords(latlngs[i], precision));
8725 if (!levelsDeep && closed) {
8726 coords.push(coords[0]);
8732 function getFeature(layer, newGeometry) {
8733 return layer.feature ?
8734 extend({}, layer.feature, {geometry: newGeometry}) :
8735 asFeature(newGeometry);
8738 // @function asFeature(geojson: Object): Object
8739 // Normalize GeoJSON geometries/features into GeoJSON features.
8740 function asFeature(geojson) {
8741 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
8752 var PointToGeoJSON = {
8753 toGeoJSON: function (precision) {
8754 return getFeature(this, {
8756 coordinates: latLngToCoords(this.getLatLng(), precision)
8761 // @namespace Marker
8762 // @method toGeoJSON(): Object
8763 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
8764 Marker.include(PointToGeoJSON);
8766 // @namespace CircleMarker
8767 // @method toGeoJSON(): Object
8768 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
8769 Circle.include(PointToGeoJSON);
8770 CircleMarker.include(PointToGeoJSON);
8773 // @namespace Polyline
8774 // @method toGeoJSON(): Object
8775 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
8777 toGeoJSON: function (precision) {
8778 var multi = !isFlat(this._latlngs);
8780 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
8782 return getFeature(this, {
8783 type: (multi ? 'Multi' : '') + 'LineString',
8789 // @namespace Polygon
8790 // @method toGeoJSON(): Object
8791 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
8793 toGeoJSON: function (precision) {
8794 var holes = !isFlat(this._latlngs),
8795 multi = holes && !isFlat(this._latlngs[0]);
8797 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
8803 return getFeature(this, {
8804 type: (multi ? 'Multi' : '') + 'Polygon',
8811 // @namespace LayerGroup
8812 LayerGroup.include({
8813 toMultiPoint: function (precision) {
8816 this.eachLayer(function (layer) {
8817 coords.push(layer.toGeoJSON(precision).geometry.coordinates);
8820 return getFeature(this, {
8826 // @method toGeoJSON(): Object
8827 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
8828 toGeoJSON: function (precision) {
8830 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
8832 if (type === 'MultiPoint') {
8833 return this.toMultiPoint(precision);
8836 var isGeometryCollection = type === 'GeometryCollection',
8839 this.eachLayer(function (layer) {
8840 if (layer.toGeoJSON) {
8841 var json = layer.toGeoJSON(precision);
8842 if (isGeometryCollection) {
8843 jsons.push(json.geometry);
8845 var feature = asFeature(json);
8846 // Squash nested feature collections
8847 if (feature.type === 'FeatureCollection') {
8848 jsons.push.apply(jsons, feature.features);
8850 jsons.push(feature);
8856 if (isGeometryCollection) {
8857 return getFeature(this, {
8859 type: 'GeometryCollection'
8864 type: 'FeatureCollection',
8870 // @namespace GeoJSON
8871 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
8872 // Creates a GeoJSON layer. Optionally accepts an object in
8873 // [GeoJSON format](http://geojson.org/geojson-spec.html) to display on the map
8874 // (you can alternatively add it later with `addData` method) and an `options` object.
8875 function geoJSON(geojson, options) {
8876 return new GeoJSON(geojson, options);
8879 // Backward compatibility.
8880 var geoJson = geoJSON;
8883 * @class ImageOverlay
8884 * @aka L.ImageOverlay
8885 * @inherits Interactive layer
8887 * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
8892 * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
8893 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
8894 * L.imageOverlay(imageUrl, imageBounds).addTo(map);
8898 var ImageOverlay = Layer.extend({
8901 // @aka ImageOverlay options
8903 // @option opacity: Number = 1.0
8904 // The opacity of the image overlay.
8907 // @option alt: String = ''
8908 // Text for the `alt` attribute of the image (useful for accessibility).
8911 // @option interactive: Boolean = false
8912 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
8915 // @option crossOrigin: Boolean = false
8916 // If true, the image will have its crossOrigin attribute set to ''. This is needed if you want to access image pixel data.
8919 // @option errorOverlayUrl: String = ''
8920 // URL to the overlay image to show in place of the overlay that failed to load.
8921 errorOverlayUrl: '',
8923 // @option zIndex: Number = 1
8924 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the tile layer.
8927 // @option className: String = ''
8928 // A custom class name to assign to the image. Empty by default.
8932 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
8934 this._bounds = toLatLngBounds(bounds);
8936 setOptions(this, options);
8939 onAdd: function () {
8943 if (this.options.opacity < 1) {
8944 this._updateOpacity();
8948 if (this.options.interactive) {
8949 addClass(this._image, 'leaflet-interactive');
8950 this.addInteractiveTarget(this._image);
8953 this.getPane().appendChild(this._image);
8957 onRemove: function () {
8958 remove(this._image);
8959 if (this.options.interactive) {
8960 this.removeInteractiveTarget(this._image);
8964 // @method setOpacity(opacity: Number): this
8965 // Sets the opacity of the overlay.
8966 setOpacity: function (opacity) {
8967 this.options.opacity = opacity;
8970 this._updateOpacity();
8975 setStyle: function (styleOpts) {
8976 if (styleOpts.opacity) {
8977 this.setOpacity(styleOpts.opacity);
8982 // @method bringToFront(): this
8983 // Brings the layer to the top of all overlays.
8984 bringToFront: function () {
8986 toFront(this._image);
8991 // @method bringToBack(): this
8992 // Brings the layer to the bottom of all overlays.
8993 bringToBack: function () {
8995 toBack(this._image);
9000 // @method setUrl(url: String): this
9001 // Changes the URL of the image.
9002 setUrl: function (url) {
9006 this._image.src = url;
9011 // @method setBounds(bounds: LatLngBounds): this
9012 // Update the bounds that this ImageOverlay covers
9013 setBounds: function (bounds) {
9014 this._bounds = toLatLngBounds(bounds);
9022 getEvents: function () {
9025 viewreset: this._reset
9028 if (this._zoomAnimated) {
9029 events.zoomanim = this._animateZoom;
9035 // @method: setZIndex(value: Number) : this
9036 // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
9037 setZIndex: function (value) {
9038 this.options.zIndex = value;
9039 this._updateZIndex();
9043 // @method getBounds(): LatLngBounds
9044 // Get the bounds that this ImageOverlay covers
9045 getBounds: function () {
9046 return this._bounds;
9049 // @method getElement(): HTMLElement
9050 // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
9051 // used by this overlay.
9052 getElement: function () {
9056 _initImage: function () {
9057 var wasElementSupplied = this._url.tagName === 'IMG';
9058 var img = this._image = wasElementSupplied ? this._url : create$1('img');
9060 addClass(img, 'leaflet-image-layer');
9061 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
9062 if (this.options.className) { addClass(img, this.options.className); }
9064 img.onselectstart = falseFn;
9065 img.onmousemove = falseFn;
9067 // @event load: Event
9068 // Fired when the ImageOverlay layer has loaded its image
9069 img.onload = bind(this.fire, this, 'load');
9070 img.onerror = bind(this._overlayOnError, this, 'error');
9072 if (this.options.crossOrigin) {
9073 img.crossOrigin = '';
9076 if (this.options.zIndex) {
9077 this._updateZIndex();
9080 if (wasElementSupplied) {
9081 this._url = img.src;
9085 img.src = this._url;
9086 img.alt = this.options.alt;
9089 _animateZoom: function (e) {
9090 var scale = this._map.getZoomScale(e.zoom),
9091 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
9093 setTransform(this._image, offset, scale);
9096 _reset: function () {
9097 var image = this._image,
9098 bounds = new Bounds(
9099 this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
9100 this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
9101 size = bounds.getSize();
9103 setPosition(image, bounds.min);
9105 image.style.width = size.x + 'px';
9106 image.style.height = size.y + 'px';
9109 _updateOpacity: function () {
9110 setOpacity(this._image, this.options.opacity);
9113 _updateZIndex: function () {
9114 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
9115 this._image.style.zIndex = this.options.zIndex;
9119 _overlayOnError: function () {
9120 // @event error: Event
9121 // Fired when the ImageOverlay layer has loaded its image
9124 var errorUrl = this.options.errorOverlayUrl;
9125 if (errorUrl && this._url !== errorUrl) {
9126 this._url = errorUrl;
9127 this._image.src = errorUrl;
9132 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
9133 // Instantiates an image overlay object given the URL of the image and the
9134 // geographical bounds it is tied to.
9135 var imageOverlay = function (url, bounds, options) {
9136 return new ImageOverlay(url, bounds, options);
9140 * @class VideoOverlay
9141 * @aka L.VideoOverlay
9142 * @inherits ImageOverlay
9144 * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
9146 * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
9152 * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
9153 * videoBounds = [[ 32, -130], [ 13, -100]];
9154 * L.VideoOverlay(videoUrl, videoBounds ).addTo(map);
9158 var VideoOverlay = ImageOverlay.extend({
9161 // @aka VideoOverlay options
9163 // @option autoplay: Boolean = true
9164 // Whether the video starts playing automatically when loaded.
9167 // @option loop: Boolean = true
9168 // Whether the video will loop back to the beginning when played.
9172 _initImage: function () {
9173 var wasElementSupplied = this._url.tagName === 'VIDEO';
9174 var vid = this._image = wasElementSupplied ? this._url : create$1('video');
9176 addClass(vid, 'leaflet-image-layer');
9177 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
9179 vid.onselectstart = falseFn;
9180 vid.onmousemove = falseFn;
9182 // @event load: Event
9183 // Fired when the video has finished loading the first frame
9184 vid.onloadeddata = bind(this.fire, this, 'load');
9186 if (wasElementSupplied) {
9187 var sourceElements = vid.getElementsByTagName('source');
9189 for (var j = 0; j < sourceElements.length; j++) {
9190 sources.push(sourceElements[j].src);
9193 this._url = (sourceElements.length > 0) ? sources : [vid.src];
9197 if (!isArray(this._url)) { this._url = [this._url]; }
9199 vid.autoplay = !!this.options.autoplay;
9200 vid.loop = !!this.options.loop;
9201 for (var i = 0; i < this._url.length; i++) {
9202 var source = create$1('source');
9203 source.src = this._url[i];
9204 vid.appendChild(source);
9208 // @method getElement(): HTMLVideoElement
9209 // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
9210 // used by this overlay.
9214 // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
9215 // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
9216 // geographical bounds it is tied to.
9218 function videoOverlay(video, bounds, options) {
9219 return new VideoOverlay(video, bounds, options);
9226 * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
9229 // @namespace DivOverlay
9230 var DivOverlay = Layer.extend({
9233 // @aka DivOverlay options
9235 // @option offset: Point = Point(0, 7)
9236 // The offset of the popup position. Useful to control the anchor
9237 // of the popup when opening it on some overlays.
9240 // @option className: String = ''
9241 // A custom CSS class name to assign to the popup.
9244 // @option pane: String = 'popupPane'
9245 // `Map pane` where the popup will be added.
9249 initialize: function (options, source) {
9250 setOptions(this, options);
9252 this._source = source;
9255 onAdd: function (map) {
9256 this._zoomAnimated = map._zoomAnimated;
9258 if (!this._container) {
9262 if (map._fadeAnimated) {
9263 setOpacity(this._container, 0);
9266 clearTimeout(this._removeTimeout);
9267 this.getPane().appendChild(this._container);
9270 if (map._fadeAnimated) {
9271 setOpacity(this._container, 1);
9274 this.bringToFront();
9277 onRemove: function (map) {
9278 if (map._fadeAnimated) {
9279 setOpacity(this._container, 0);
9280 this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
9282 remove(this._container);
9287 // @method getLatLng: LatLng
9288 // Returns the geographical point of popup.
9289 getLatLng: function () {
9290 return this._latlng;
9293 // @method setLatLng(latlng: LatLng): this
9294 // Sets the geographical point where the popup will open.
9295 setLatLng: function (latlng) {
9296 this._latlng = toLatLng(latlng);
9298 this._updatePosition();
9304 // @method getContent: String|HTMLElement
9305 // Returns the content of the popup.
9306 getContent: function () {
9307 return this._content;
9310 // @method setContent(htmlContent: String|HTMLElement|Function): this
9311 // 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.
9312 setContent: function (content) {
9313 this._content = content;
9318 // @method getElement: String|HTMLElement
9319 // Alias for [getContent()](#popup-getcontent)
9320 getElement: function () {
9321 return this._container;
9324 // @method update: null
9325 // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
9326 update: function () {
9327 if (!this._map) { return; }
9329 this._container.style.visibility = 'hidden';
9331 this._updateContent();
9332 this._updateLayout();
9333 this._updatePosition();
9335 this._container.style.visibility = '';
9340 getEvents: function () {
9342 zoom: this._updatePosition,
9343 viewreset: this._updatePosition
9346 if (this._zoomAnimated) {
9347 events.zoomanim = this._animateZoom;
9352 // @method isOpen: Boolean
9353 // Returns `true` when the popup is visible on the map.
9354 isOpen: function () {
9355 return !!this._map && this._map.hasLayer(this);
9358 // @method bringToFront: this
9359 // Brings this popup in front of other popups (in the same map pane).
9360 bringToFront: function () {
9362 toFront(this._container);
9367 // @method bringToBack: this
9368 // Brings this popup to the back of other popups (in the same map pane).
9369 bringToBack: function () {
9371 toBack(this._container);
9376 _updateContent: function () {
9377 if (!this._content) { return; }
9379 var node = this._contentNode;
9380 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
9382 if (typeof content === 'string') {
9383 node.innerHTML = content;
9385 while (node.hasChildNodes()) {
9386 node.removeChild(node.firstChild);
9388 node.appendChild(content);
9390 this.fire('contentupdate');
9393 _updatePosition: function () {
9394 if (!this._map) { return; }
9396 var pos = this._map.latLngToLayerPoint(this._latlng),
9397 offset = toPoint(this.options.offset),
9398 anchor = this._getAnchor();
9400 if (this._zoomAnimated) {
9401 setPosition(this._container, pos.add(anchor));
9403 offset = offset.add(pos).add(anchor);
9406 var bottom = this._containerBottom = -offset.y,
9407 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
9409 // bottom position the popup in case the height of the popup changes (images loading etc)
9410 this._container.style.bottom = bottom + 'px';
9411 this._container.style.left = left + 'px';
9414 _getAnchor: function () {
9422 * @inherits DivOverlay
9424 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
9425 * open popups while making sure that only one popup is open at one time
9426 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
9430 * If you want to just bind a popup to marker click and then open it, it's really easy:
9433 * marker.bindPopup(popupContent).openPopup();
9435 * Path overlays like polylines also have a `bindPopup` method.
9436 * Here's a more complicated way to open a popup on a map:
9439 * var popup = L.popup()
9440 * .setLatLng(latlng)
9441 * .setContent('<p>Hello world!<br />This is a nice popup.</p>')
9448 var Popup = DivOverlay.extend({
9451 // @aka Popup options
9453 // @option maxWidth: Number = 300
9454 // Max width of the popup, in pixels.
9457 // @option minWidth: Number = 50
9458 // Min width of the popup, in pixels.
9461 // @option maxHeight: Number = null
9462 // If set, creates a scrollable container of the given height
9463 // inside a popup if its content exceeds it.
9466 // @option autoPan: Boolean = true
9467 // Set it to `false` if you don't want the map to do panning animation
9468 // to fit the opened popup.
9471 // @option autoPanPaddingTopLeft: Point = null
9472 // The margin between the popup and the top left corner of the map
9473 // view after autopanning was performed.
9474 autoPanPaddingTopLeft: null,
9476 // @option autoPanPaddingBottomRight: Point = null
9477 // The margin between the popup and the bottom right corner of the map
9478 // view after autopanning was performed.
9479 autoPanPaddingBottomRight: null,
9481 // @option autoPanPadding: Point = Point(5, 5)
9482 // Equivalent of setting both top left and bottom right autopan padding to the same value.
9483 autoPanPadding: [5, 5],
9485 // @option keepInView: Boolean = false
9486 // Set it to `true` if you want to prevent users from panning the popup
9487 // off of the screen while it is open.
9490 // @option closeButton: Boolean = true
9491 // Controls the presence of a close button in the popup.
9494 // @option autoClose: Boolean = true
9495 // Set it to `false` if you want to override the default behavior of
9496 // the popup closing when another popup is opened.
9499 // @option closeOnEscapeKey: Boolean = true
9500 // Set it to `false` if you want to override the default behavior of
9501 // the ESC key for closing of the popup.
9502 closeOnEscapeKey: true,
9504 // @option closeOnClick: Boolean = *
9505 // Set it if you want to override the default behavior of the popup closing when user clicks
9506 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
9508 // @option className: String = ''
9509 // A custom CSS class name to assign to the popup.
9514 // @method openOn(map: Map): this
9515 // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
9516 openOn: function (map) {
9517 map.openPopup(this);
9521 onAdd: function (map) {
9522 DivOverlay.prototype.onAdd.call(this, map);
9525 // @section Popup events
9526 // @event popupopen: PopupEvent
9527 // Fired when a popup is opened in the map
9528 map.fire('popupopen', {popup: this});
9532 // @section Popup events
9533 // @event popupopen: PopupEvent
9534 // Fired when a popup bound to this layer is opened
9535 this._source.fire('popupopen', {popup: this}, true);
9536 // For non-path layers, we toggle the popup when clicking
9537 // again the layer, so prevent the map to reopen it.
9538 if (!(this._source instanceof Path)) {
9539 this._source.on('preclick', stopPropagation);
9544 onRemove: function (map) {
9545 DivOverlay.prototype.onRemove.call(this, map);
9548 // @section Popup events
9549 // @event popupclose: PopupEvent
9550 // Fired when a popup in the map is closed
9551 map.fire('popupclose', {popup: this});
9555 // @section Popup events
9556 // @event popupclose: PopupEvent
9557 // Fired when a popup bound to this layer is closed
9558 this._source.fire('popupclose', {popup: this}, true);
9559 if (!(this._source instanceof Path)) {
9560 this._source.off('preclick', stopPropagation);
9565 getEvents: function () {
9566 var events = DivOverlay.prototype.getEvents.call(this);
9568 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
9569 events.preclick = this._close;
9572 if (this.options.keepInView) {
9573 events.moveend = this._adjustPan;
9579 _close: function () {
9581 this._map.closePopup(this);
9585 _initLayout: function () {
9586 var prefix = 'leaflet-popup',
9587 container = this._container = create$1('div',
9588 prefix + ' ' + (this.options.className || '') +
9589 ' leaflet-zoom-animated');
9591 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
9592 this._contentNode = create$1('div', prefix + '-content', wrapper);
9594 disableClickPropagation(wrapper);
9595 disableScrollPropagation(this._contentNode);
9596 on(wrapper, 'contextmenu', stopPropagation);
9598 this._tipContainer = create$1('div', prefix + '-tip-container', container);
9599 this._tip = create$1('div', prefix + '-tip', this._tipContainer);
9601 if (this.options.closeButton) {
9602 var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
9603 closeButton.href = '#close';
9604 closeButton.innerHTML = '×';
9606 on(closeButton, 'click', this._onCloseButtonClick, this);
9610 _updateLayout: function () {
9611 var container = this._contentNode,
9612 style = container.style;
9615 style.whiteSpace = 'nowrap';
9617 var width = container.offsetWidth;
9618 width = Math.min(width, this.options.maxWidth);
9619 width = Math.max(width, this.options.minWidth);
9621 style.width = (width + 1) + 'px';
9622 style.whiteSpace = '';
9626 var height = container.offsetHeight,
9627 maxHeight = this.options.maxHeight,
9628 scrolledClass = 'leaflet-popup-scrolled';
9630 if (maxHeight && height > maxHeight) {
9631 style.height = maxHeight + 'px';
9632 addClass(container, scrolledClass);
9634 removeClass(container, scrolledClass);
9637 this._containerWidth = this._container.offsetWidth;
9640 _animateZoom: function (e) {
9641 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
9642 anchor = this._getAnchor();
9643 setPosition(this._container, pos.add(anchor));
9646 _adjustPan: function () {
9647 if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; }
9649 var map = this._map,
9650 marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
9651 containerHeight = this._container.offsetHeight + marginBottom,
9652 containerWidth = this._containerWidth,
9653 layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
9655 layerPos._add(getPosition(this._container));
9657 var containerPos = map.layerPointToContainerPoint(layerPos),
9658 padding = toPoint(this.options.autoPanPadding),
9659 paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
9660 paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
9661 size = map.getSize(),
9665 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
9666 dx = containerPos.x + containerWidth - size.x + paddingBR.x;
9668 if (containerPos.x - dx - paddingTL.x < 0) { // left
9669 dx = containerPos.x - paddingTL.x;
9671 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
9672 dy = containerPos.y + containerHeight - size.y + paddingBR.y;
9674 if (containerPos.y - dy - paddingTL.y < 0) { // top
9675 dy = containerPos.y - paddingTL.y;
9679 // @section Popup events
9680 // @event autopanstart: Event
9681 // Fired when the map starts autopanning when opening a popup.
9684 .fire('autopanstart')
9689 _onCloseButtonClick: function (e) {
9694 _getAnchor: function () {
9695 // Where should we anchor the popup on the source layer?
9696 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
9702 // @factory L.popup(options?: Popup options, source?: Layer)
9703 // 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.
9704 var popup = function (options, source) {
9705 return new Popup(options, source);
9710 * @section Interaction Options
9711 * @option closePopupOnClick: Boolean = true
9712 * Set it to `false` if you don't want popups to close when user clicks the map.
9715 closePopupOnClick: true
9720 // @section Methods for Layers and Controls
9722 // @method openPopup(popup: Popup): this
9723 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
9725 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
9726 // Creates a popup with the specified content and options and opens it in the given point on a map.
9727 openPopup: function (popup, latlng, options) {
9728 if (!(popup instanceof Popup)) {
9729 popup = new Popup(options).setContent(popup);
9733 popup.setLatLng(latlng);
9736 if (this.hasLayer(popup)) {
9740 if (this._popup && this._popup.options.autoClose) {
9744 this._popup = popup;
9745 return this.addLayer(popup);
9748 // @method closePopup(popup?: Popup): this
9749 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
9750 closePopup: function (popup) {
9751 if (!popup || popup === this._popup) {
9752 popup = this._popup;
9756 this.removeLayer(popup);
9764 * @section Popup methods example
9766 * All layers share a set of methods convenient for binding popups to it.
9769 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
9770 * layer.openPopup();
9771 * layer.closePopup();
9774 * 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.
9777 // @section Popup methods
9780 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
9781 // Binds a popup to the layer with the passed `content` and sets up the
9782 // necessary event listeners. If a `Function` is passed it will receive
9783 // the layer as the first argument and should return a `String` or `HTMLElement`.
9784 bindPopup: function (content, options) {
9786 if (content instanceof Popup) {
9787 setOptions(content, options);
9788 this._popup = content;
9789 content._source = this;
9791 if (!this._popup || options) {
9792 this._popup = new Popup(options, this);
9794 this._popup.setContent(content);
9797 if (!this._popupHandlersAdded) {
9799 click: this._openPopup,
9800 keypress: this._onKeyPress,
9801 remove: this.closePopup,
9802 move: this._movePopup
9804 this._popupHandlersAdded = true;
9810 // @method unbindPopup(): this
9811 // Removes the popup previously bound with `bindPopup`.
9812 unbindPopup: function () {
9815 click: this._openPopup,
9816 keypress: this._onKeyPress,
9817 remove: this.closePopup,
9818 move: this._movePopup
9820 this._popupHandlersAdded = false;
9826 // @method openPopup(latlng?: LatLng): this
9827 // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
9828 openPopup: function (layer, latlng) {
9829 if (!(layer instanceof Layer)) {
9834 if (layer instanceof FeatureGroup) {
9835 for (var id in this._layers) {
9836 layer = this._layers[id];
9842 latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
9845 if (this._popup && this._map) {
9846 // set popup source to this layer
9847 this._popup._source = layer;
9849 // update the popup (content, layout, ect...)
9850 this._popup.update();
9852 // open the popup on the map
9853 this._map.openPopup(this._popup, latlng);
9859 // @method closePopup(): this
9860 // Closes the popup bound to this layer if it is open.
9861 closePopup: function () {
9863 this._popup._close();
9868 // @method togglePopup(): this
9869 // Opens or closes the popup bound to this layer depending on its current state.
9870 togglePopup: function (target) {
9872 if (this._popup._map) {
9875 this.openPopup(target);
9881 // @method isPopupOpen(): boolean
9882 // Returns `true` if the popup bound to this layer is currently open.
9883 isPopupOpen: function () {
9884 return (this._popup ? this._popup.isOpen() : false);
9887 // @method setPopupContent(content: String|HTMLElement|Popup): this
9888 // Sets the content of the popup bound to this layer.
9889 setPopupContent: function (content) {
9891 this._popup.setContent(content);
9896 // @method getPopup(): Popup
9897 // Returns the popup bound to this layer.
9898 getPopup: function () {
9902 _openPopup: function (e) {
9903 var layer = e.layer || e.target;
9913 // prevent map click
9916 // if this inherits from Path its a vector and we can just
9917 // open the popup at the new location
9918 if (layer instanceof Path) {
9919 this.openPopup(e.layer || e.target, e.latlng);
9923 // otherwise treat it like a marker and figure out
9924 // if we should toggle it open/closed
9925 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
9928 this.openPopup(layer, e.latlng);
9932 _movePopup: function (e) {
9933 this._popup.setLatLng(e.latlng);
9936 _onKeyPress: function (e) {
9937 if (e.originalEvent.keyCode === 13) {
9945 * @inherits DivOverlay
9947 * Used to display small texts on top of map layers.
9952 * marker.bindTooltip("my tooltip text").openTooltip();
9954 * Note about tooltip offset. Leaflet takes two options in consideration
9955 * for computing tooltip offsetting:
9956 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
9957 * Add a positive x offset to move the tooltip to the right, and a positive y offset to
9958 * move it to the bottom. Negatives will move to the left and top.
9959 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
9960 * should adapt this value if you use a custom icon.
9964 // @namespace Tooltip
9965 var Tooltip = DivOverlay.extend({
9968 // @aka Tooltip options
9970 // @option pane: String = 'tooltipPane'
9971 // `Map pane` where the tooltip will be added.
9972 pane: 'tooltipPane',
9974 // @option offset: Point = Point(0, 0)
9975 // Optional offset of the tooltip position.
9978 // @option direction: String = 'auto'
9979 // Direction where to open the tooltip. Possible values are: `right`, `left`,
9980 // `top`, `bottom`, `center`, `auto`.
9981 // `auto` will dynamically switch between `right` and `left` according to the tooltip
9982 // position on the map.
9985 // @option permanent: Boolean = false
9986 // Whether to open the tooltip permanently or only on mouseover.
9989 // @option sticky: Boolean = false
9990 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
9993 // @option interactive: Boolean = false
9994 // If true, the tooltip will listen to the feature events.
9997 // @option opacity: Number = 0.9
9998 // Tooltip container opacity.
10002 onAdd: function (map) {
10003 DivOverlay.prototype.onAdd.call(this, map);
10004 this.setOpacity(this.options.opacity);
10007 // @section Tooltip events
10008 // @event tooltipopen: TooltipEvent
10009 // Fired when a tooltip is opened in the map.
10010 map.fire('tooltipopen', {tooltip: this});
10012 if (this._source) {
10013 // @namespace Layer
10014 // @section Tooltip events
10015 // @event tooltipopen: TooltipEvent
10016 // Fired when a tooltip bound to this layer is opened.
10017 this._source.fire('tooltipopen', {tooltip: this}, true);
10021 onRemove: function (map) {
10022 DivOverlay.prototype.onRemove.call(this, map);
10025 // @section Tooltip events
10026 // @event tooltipclose: TooltipEvent
10027 // Fired when a tooltip in the map is closed.
10028 map.fire('tooltipclose', {tooltip: this});
10030 if (this._source) {
10031 // @namespace Layer
10032 // @section Tooltip events
10033 // @event tooltipclose: TooltipEvent
10034 // Fired when a tooltip bound to this layer is closed.
10035 this._source.fire('tooltipclose', {tooltip: this}, true);
10039 getEvents: function () {
10040 var events = DivOverlay.prototype.getEvents.call(this);
10042 if (touch && !this.options.permanent) {
10043 events.preclick = this._close;
10049 _close: function () {
10051 this._map.closeTooltip(this);
10055 _initLayout: function () {
10056 var prefix = 'leaflet-tooltip',
10057 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
10059 this._contentNode = this._container = create$1('div', className);
10062 _updateLayout: function () {},
10064 _adjustPan: function () {},
10066 _setPosition: function (pos) {
10067 var map = this._map,
10068 container = this._container,
10069 centerPoint = map.latLngToContainerPoint(map.getCenter()),
10070 tooltipPoint = map.layerPointToContainerPoint(pos),
10071 direction = this.options.direction,
10072 tooltipWidth = container.offsetWidth,
10073 tooltipHeight = container.offsetHeight,
10074 offset = toPoint(this.options.offset),
10075 anchor = this._getAnchor();
10077 if (direction === 'top') {
10078 pos = pos.add(toPoint(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true));
10079 } else if (direction === 'bottom') {
10080 pos = pos.subtract(toPoint(tooltipWidth / 2 - offset.x, -offset.y, true));
10081 } else if (direction === 'center') {
10082 pos = pos.subtract(toPoint(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true));
10083 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
10084 direction = 'right';
10085 pos = pos.add(toPoint(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true));
10087 direction = 'left';
10088 pos = pos.subtract(toPoint(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true));
10091 removeClass(container, 'leaflet-tooltip-right');
10092 removeClass(container, 'leaflet-tooltip-left');
10093 removeClass(container, 'leaflet-tooltip-top');
10094 removeClass(container, 'leaflet-tooltip-bottom');
10095 addClass(container, 'leaflet-tooltip-' + direction);
10096 setPosition(container, pos);
10099 _updatePosition: function () {
10100 var pos = this._map.latLngToLayerPoint(this._latlng);
10101 this._setPosition(pos);
10104 setOpacity: function (opacity) {
10105 this.options.opacity = opacity;
10107 if (this._container) {
10108 setOpacity(this._container, opacity);
10112 _animateZoom: function (e) {
10113 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
10114 this._setPosition(pos);
10117 _getAnchor: function () {
10118 // Where should we anchor the tooltip on the source layer?
10119 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
10124 // @namespace Tooltip
10125 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
10126 // 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.
10127 var tooltip = function (options, source) {
10128 return new Tooltip(options, source);
10132 // @section Methods for Layers and Controls
10135 // @method openTooltip(tooltip: Tooltip): this
10136 // Opens the specified tooltip.
10138 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
10139 // Creates a tooltip with the specified content and options and open it.
10140 openTooltip: function (tooltip, latlng, options) {
10141 if (!(tooltip instanceof Tooltip)) {
10142 tooltip = new Tooltip(options).setContent(tooltip);
10146 tooltip.setLatLng(latlng);
10149 if (this.hasLayer(tooltip)) {
10153 return this.addLayer(tooltip);
10156 // @method closeTooltip(tooltip?: Tooltip): this
10157 // Closes the tooltip given as parameter.
10158 closeTooltip: function (tooltip) {
10160 this.removeLayer(tooltip);
10169 * @section Tooltip methods example
10171 * All layers share a set of methods convenient for binding tooltips to it.
10174 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
10175 * layer.openTooltip();
10176 * layer.closeTooltip();
10180 // @section Tooltip methods
10183 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
10184 // Binds a tooltip to the layer with the passed `content` and sets up the
10185 // necessary event listeners. If a `Function` is passed it will receive
10186 // the layer as the first argument and should return a `String` or `HTMLElement`.
10187 bindTooltip: function (content, options) {
10189 if (content instanceof Tooltip) {
10190 setOptions(content, options);
10191 this._tooltip = content;
10192 content._source = this;
10194 if (!this._tooltip || options) {
10195 this._tooltip = new Tooltip(options, this);
10197 this._tooltip.setContent(content);
10201 this._initTooltipInteractions();
10203 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
10204 this.openTooltip();
10210 // @method unbindTooltip(): this
10211 // Removes the tooltip previously bound with `bindTooltip`.
10212 unbindTooltip: function () {
10213 if (this._tooltip) {
10214 this._initTooltipInteractions(true);
10215 this.closeTooltip();
10216 this._tooltip = null;
10221 _initTooltipInteractions: function (remove$$1) {
10222 if (!remove$$1 && this._tooltipHandlersAdded) { return; }
10223 var onOff = remove$$1 ? 'off' : 'on',
10225 remove: this.closeTooltip,
10226 move: this._moveTooltip
10228 if (!this._tooltip.options.permanent) {
10229 events.mouseover = this._openTooltip;
10230 events.mouseout = this.closeTooltip;
10231 if (this._tooltip.options.sticky) {
10232 events.mousemove = this._moveTooltip;
10235 events.click = this._openTooltip;
10238 events.add = this._openTooltip;
10240 this[onOff](events);
10241 this._tooltipHandlersAdded = !remove$$1;
10244 // @method openTooltip(latlng?: LatLng): this
10245 // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
10246 openTooltip: function (layer, latlng) {
10247 if (!(layer instanceof Layer)) {
10252 if (layer instanceof FeatureGroup) {
10253 for (var id in this._layers) {
10254 layer = this._layers[id];
10260 latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
10263 if (this._tooltip && this._map) {
10265 // set tooltip source to this layer
10266 this._tooltip._source = layer;
10268 // update the tooltip (content, layout, ect...)
10269 this._tooltip.update();
10271 // open the tooltip on the map
10272 this._map.openTooltip(this._tooltip, latlng);
10274 // Tooltip container may not be defined if not permanent and never
10276 if (this._tooltip.options.interactive && this._tooltip._container) {
10277 addClass(this._tooltip._container, 'leaflet-clickable');
10278 this.addInteractiveTarget(this._tooltip._container);
10285 // @method closeTooltip(): this
10286 // Closes the tooltip bound to this layer if it is open.
10287 closeTooltip: function () {
10288 if (this._tooltip) {
10289 this._tooltip._close();
10290 if (this._tooltip.options.interactive && this._tooltip._container) {
10291 removeClass(this._tooltip._container, 'leaflet-clickable');
10292 this.removeInteractiveTarget(this._tooltip._container);
10298 // @method toggleTooltip(): this
10299 // Opens or closes the tooltip bound to this layer depending on its current state.
10300 toggleTooltip: function (target) {
10301 if (this._tooltip) {
10302 if (this._tooltip._map) {
10303 this.closeTooltip();
10305 this.openTooltip(target);
10311 // @method isTooltipOpen(): boolean
10312 // Returns `true` if the tooltip bound to this layer is currently open.
10313 isTooltipOpen: function () {
10314 return this._tooltip.isOpen();
10317 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10318 // Sets the content of the tooltip bound to this layer.
10319 setTooltipContent: function (content) {
10320 if (this._tooltip) {
10321 this._tooltip.setContent(content);
10326 // @method getTooltip(): Tooltip
10327 // Returns the tooltip bound to this layer.
10328 getTooltip: function () {
10329 return this._tooltip;
10332 _openTooltip: function (e) {
10333 var layer = e.layer || e.target;
10335 if (!this._tooltip || !this._map) {
10338 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
10341 _moveTooltip: function (e) {
10342 var latlng = e.latlng, containerPoint, layerPoint;
10343 if (this._tooltip.options.sticky && e.originalEvent) {
10344 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
10345 layerPoint = this._map.containerPointToLayerPoint(containerPoint);
10346 latlng = this._map.layerPointToLatLng(layerPoint);
10348 this._tooltip.setLatLng(latlng);
10357 * Represents a lightweight icon for markers that uses a simple `<div>`
10358 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
10362 * var myIcon = L.divIcon({className: 'my-div-icon'});
10363 * // you can set .my-div-icon styles in CSS
10365 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
10368 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
10371 var DivIcon = Icon.extend({
10374 // @aka DivIcon options
10375 iconSize: [12, 12], // also can be set through CSS
10377 // iconAnchor: (Point),
10378 // popupAnchor: (Point),
10380 // @option html: String = ''
10381 // Custom HTML code to put inside the div element, empty by default.
10384 // @option bgPos: Point = [0, 0]
10385 // Optional relative position of the background, in pixels
10388 className: 'leaflet-div-icon'
10391 createIcon: function (oldIcon) {
10392 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
10393 options = this.options;
10395 div.innerHTML = options.html !== false ? options.html : '';
10397 if (options.bgPos) {
10398 var bgPos = toPoint(options.bgPos);
10399 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
10401 this._setIconStyles(div, 'icon');
10406 createShadow: function () {
10411 // @factory L.divIcon(options: DivIcon options)
10412 // Creates a `DivIcon` instance with the given options.
10413 function divIcon(options) {
10414 return new DivIcon(options);
10417 Icon.Default = IconDefault;
10424 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
10425 * 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.
10428 * @section Synchronous usage
10431 * 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.
10434 * var CanvasLayer = L.GridLayer.extend({
10435 * createTile: function(coords){
10436 * // create a <canvas> element for drawing
10437 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10439 * // setup tile width and height according to the options
10440 * var size = this.getTileSize();
10441 * tile.width = size.x;
10442 * tile.height = size.y;
10444 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z
10445 * var ctx = tile.getContext('2d');
10447 * // return the tile so it can be rendered on screen
10453 * @section Asynchronous usage
10456 * 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.
10459 * var CanvasLayer = L.GridLayer.extend({
10460 * createTile: function(coords, done){
10463 * // create a <canvas> element for drawing
10464 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10466 * // setup tile width and height according to the options
10467 * var size = this.getTileSize();
10468 * tile.width = size.x;
10469 * tile.height = size.y;
10471 * // draw something asynchronously and pass the tile to the done() callback
10472 * setTimeout(function() {
10473 * done(error, tile);
10485 var GridLayer = Layer.extend({
10488 // @aka GridLayer options
10490 // @option tileSize: Number|Point = 256
10491 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
10494 // @option opacity: Number = 1.0
10495 // Opacity of the tiles. Can be used in the `createTile()` function.
10498 // @option updateWhenIdle: Boolean = (depends)
10499 // Load new tiles only when panning ends.
10500 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
10501 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
10502 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
10503 updateWhenIdle: mobile,
10505 // @option updateWhenZooming: Boolean = true
10506 // 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.
10507 updateWhenZooming: true,
10509 // @option updateInterval: Number = 200
10510 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
10511 updateInterval: 200,
10513 // @option zIndex: Number = 1
10514 // The explicit zIndex of the tile layer.
10517 // @option bounds: LatLngBounds = undefined
10518 // If set, tiles will only be loaded inside the set `LatLngBounds`.
10521 // @option minZoom: Number = 0
10522 // The minimum zoom level down to which this layer will be displayed (inclusive).
10525 // @option maxZoom: Number = undefined
10526 // The maximum zoom level up to which this layer will be displayed (inclusive).
10527 maxZoom: undefined,
10529 // @option maxNativeZoom: Number = undefined
10530 // Maximum zoom number the tile source has available. If it is specified,
10531 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
10532 // from `maxNativeZoom` level and auto-scaled.
10533 maxNativeZoom: undefined,
10535 // @option minNativeZoom: Number = undefined
10536 // Minimum zoom number the tile source has available. If it is specified,
10537 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
10538 // from `minNativeZoom` level and auto-scaled.
10539 minNativeZoom: undefined,
10541 // @option noWrap: Boolean = false
10542 // Whether the layer is wrapped around the antimeridian. If `true`, the
10543 // GridLayer will only be displayed once at low zoom levels. Has no
10544 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
10545 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
10546 // tiles outside the CRS limits.
10549 // @option pane: String = 'tilePane'
10550 // `Map pane` where the grid layer will be added.
10553 // @option className: String = ''
10554 // A custom class name to assign to the tile layer. Empty by default.
10557 // @option keepBuffer: Number = 2
10558 // When panning the map, keep this many rows and columns of tiles before unloading them.
10562 initialize: function (options) {
10563 setOptions(this, options);
10566 onAdd: function () {
10567 this._initContainer();
10576 beforeAdd: function (map) {
10577 map._addZoomLimit(this);
10580 onRemove: function (map) {
10581 this._removeAllTiles();
10582 remove(this._container);
10583 map._removeZoomLimit(this);
10584 this._container = null;
10585 this._tileZoom = undefined;
10588 // @method bringToFront: this
10589 // Brings the tile layer to the top of all tile layers.
10590 bringToFront: function () {
10592 toFront(this._container);
10593 this._setAutoZIndex(Math.max);
10598 // @method bringToBack: this
10599 // Brings the tile layer to the bottom of all tile layers.
10600 bringToBack: function () {
10602 toBack(this._container);
10603 this._setAutoZIndex(Math.min);
10608 // @method getContainer: HTMLElement
10609 // Returns the HTML element that contains the tiles for this layer.
10610 getContainer: function () {
10611 return this._container;
10614 // @method setOpacity(opacity: Number): this
10615 // Changes the [opacity](#gridlayer-opacity) of the grid layer.
10616 setOpacity: function (opacity) {
10617 this.options.opacity = opacity;
10618 this._updateOpacity();
10622 // @method setZIndex(zIndex: Number): this
10623 // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
10624 setZIndex: function (zIndex) {
10625 this.options.zIndex = zIndex;
10626 this._updateZIndex();
10631 // @method isLoading: Boolean
10632 // Returns `true` if any tile in the grid layer has not finished loading.
10633 isLoading: function () {
10634 return this._loading;
10637 // @method redraw: this
10638 // Causes the layer to clear all the tiles and request them again.
10639 redraw: function () {
10641 this._removeAllTiles();
10647 getEvents: function () {
10649 viewprereset: this._invalidateAll,
10650 viewreset: this._resetView,
10651 zoom: this._resetView,
10652 moveend: this._onMoveEnd
10655 if (!this.options.updateWhenIdle) {
10656 // update tiles on move, but not more often than once per given interval
10657 if (!this._onMove) {
10658 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
10661 events.move = this._onMove;
10664 if (this._zoomAnimated) {
10665 events.zoomanim = this._animateZoom;
10671 // @section Extension methods
10672 // Layers extending `GridLayer` shall reimplement the following method.
10673 // @method createTile(coords: Object, done?: Function): HTMLElement
10674 // Called only internally, must be overridden by classes extending `GridLayer`.
10675 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
10676 // is specified, it must be called when the tile has finished loading and drawing.
10677 createTile: function () {
10678 return document.createElement('div');
10682 // @method getTileSize: Point
10683 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
10684 getTileSize: function () {
10685 var s = this.options.tileSize;
10686 return s instanceof Point ? s : new Point(s, s);
10689 _updateZIndex: function () {
10690 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
10691 this._container.style.zIndex = this.options.zIndex;
10695 _setAutoZIndex: function (compare) {
10696 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
10698 var layers = this.getPane().children,
10699 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
10701 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
10703 zIndex = layers[i].style.zIndex;
10705 if (layers[i] !== this._container && zIndex) {
10706 edgeZIndex = compare(edgeZIndex, +zIndex);
10710 if (isFinite(edgeZIndex)) {
10711 this.options.zIndex = edgeZIndex + compare(-1, 1);
10712 this._updateZIndex();
10716 _updateOpacity: function () {
10717 if (!this._map) { return; }
10719 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
10720 if (ielt9) { return; }
10722 setOpacity(this._container, this.options.opacity);
10724 var now = +new Date(),
10728 for (var key in this._tiles) {
10729 var tile = this._tiles[key];
10730 if (!tile.current || !tile.loaded) { continue; }
10732 var fade = Math.min(1, (now - tile.loaded) / 200);
10734 setOpacity(tile.el, fade);
10741 this._onOpaqueTile(tile);
10743 tile.active = true;
10747 if (willPrune && !this._noPrune) { this._pruneTiles(); }
10750 cancelAnimFrame(this._fadeFrame);
10751 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
10755 _onOpaqueTile: falseFn,
10757 _initContainer: function () {
10758 if (this._container) { return; }
10760 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
10761 this._updateZIndex();
10763 if (this.options.opacity < 1) {
10764 this._updateOpacity();
10767 this.getPane().appendChild(this._container);
10770 _updateLevels: function () {
10772 var zoom = this._tileZoom,
10773 maxZoom = this.options.maxZoom;
10775 if (zoom === undefined) { return undefined; }
10777 for (var z in this._levels) {
10778 if (this._levels[z].el.children.length || z === zoom) {
10779 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
10780 this._onUpdateLevel(z);
10782 remove(this._levels[z].el);
10783 this._removeTilesAtZoom(z);
10784 this._onRemoveLevel(z);
10785 delete this._levels[z];
10789 var level = this._levels[zoom],
10793 level = this._levels[zoom] = {};
10795 level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
10796 level.el.style.zIndex = maxZoom;
10798 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
10801 this._setZoomTransform(level, map.getCenter(), map.getZoom());
10803 // force the browser to consider the newly added element for transition
10804 falseFn(level.el.offsetWidth);
10806 this._onCreateLevel(level);
10809 this._level = level;
10814 _onUpdateLevel: falseFn,
10816 _onRemoveLevel: falseFn,
10818 _onCreateLevel: falseFn,
10820 _pruneTiles: function () {
10827 var zoom = this._map.getZoom();
10828 if (zoom > this.options.maxZoom ||
10829 zoom < this.options.minZoom) {
10830 this._removeAllTiles();
10834 for (key in this._tiles) {
10835 tile = this._tiles[key];
10836 tile.retain = tile.current;
10839 for (key in this._tiles) {
10840 tile = this._tiles[key];
10841 if (tile.current && !tile.active) {
10842 var coords = tile.coords;
10843 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
10844 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
10849 for (key in this._tiles) {
10850 if (!this._tiles[key].retain) {
10851 this._removeTile(key);
10856 _removeTilesAtZoom: function (zoom) {
10857 for (var key in this._tiles) {
10858 if (this._tiles[key].coords.z !== zoom) {
10861 this._removeTile(key);
10865 _removeAllTiles: function () {
10866 for (var key in this._tiles) {
10867 this._removeTile(key);
10871 _invalidateAll: function () {
10872 for (var z in this._levels) {
10873 remove(this._levels[z].el);
10874 this._onRemoveLevel(z);
10875 delete this._levels[z];
10877 this._removeAllTiles();
10879 this._tileZoom = undefined;
10882 _retainParent: function (x, y, z, minZoom) {
10883 var x2 = Math.floor(x / 2),
10884 y2 = Math.floor(y / 2),
10886 coords2 = new Point(+x2, +y2);
10889 var key = this._tileCoordsToKey(coords2),
10890 tile = this._tiles[key];
10892 if (tile && tile.active) {
10893 tile.retain = true;
10896 } else if (tile && tile.loaded) {
10897 tile.retain = true;
10900 if (z2 > minZoom) {
10901 return this._retainParent(x2, y2, z2, minZoom);
10907 _retainChildren: function (x, y, z, maxZoom) {
10909 for (var i = 2 * x; i < 2 * x + 2; i++) {
10910 for (var j = 2 * y; j < 2 * y + 2; j++) {
10912 var coords = new Point(i, j);
10915 var key = this._tileCoordsToKey(coords),
10916 tile = this._tiles[key];
10918 if (tile && tile.active) {
10919 tile.retain = true;
10922 } else if (tile && tile.loaded) {
10923 tile.retain = true;
10926 if (z + 1 < maxZoom) {
10927 this._retainChildren(i, j, z + 1, maxZoom);
10933 _resetView: function (e) {
10934 var animating = e && (e.pinch || e.flyTo);
10935 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
10938 _animateZoom: function (e) {
10939 this._setView(e.center, e.zoom, true, e.noUpdate);
10942 _clampZoom: function (zoom) {
10943 var options = this.options;
10945 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
10946 return options.minNativeZoom;
10949 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
10950 return options.maxNativeZoom;
10956 _setView: function (center, zoom, noPrune, noUpdate) {
10957 var tileZoom = this._clampZoom(Math.round(zoom));
10958 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
10959 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
10960 tileZoom = undefined;
10963 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
10965 if (!noUpdate || tileZoomChanged) {
10967 this._tileZoom = tileZoom;
10969 if (this._abortLoading) {
10970 this._abortLoading();
10973 this._updateLevels();
10976 if (tileZoom !== undefined) {
10977 this._update(center);
10981 this._pruneTiles();
10984 // Flag to prevent _updateOpacity from pruning tiles during
10985 // a zoom anim or a pinch gesture
10986 this._noPrune = !!noPrune;
10989 this._setZoomTransforms(center, zoom);
10992 _setZoomTransforms: function (center, zoom) {
10993 for (var i in this._levels) {
10994 this._setZoomTransform(this._levels[i], center, zoom);
10998 _setZoomTransform: function (level, center, zoom) {
10999 var scale = this._map.getZoomScale(zoom, level.zoom),
11000 translate = level.origin.multiplyBy(scale)
11001 .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
11004 setTransform(level.el, translate, scale);
11006 setPosition(level.el, translate);
11010 _resetGrid: function () {
11011 var map = this._map,
11012 crs = map.options.crs,
11013 tileSize = this._tileSize = this.getTileSize(),
11014 tileZoom = this._tileZoom;
11016 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
11018 this._globalTileRange = this._pxBoundsToTileRange(bounds);
11021 this._wrapX = crs.wrapLng && !this.options.noWrap && [
11022 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
11023 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
11025 this._wrapY = crs.wrapLat && !this.options.noWrap && [
11026 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
11027 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
11031 _onMoveEnd: function () {
11032 if (!this._map || this._map._animatingZoom) { return; }
11037 _getTiledPixelBounds: function (center) {
11038 var map = this._map,
11039 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
11040 scale = map.getZoomScale(mapZoom, this._tileZoom),
11041 pixelCenter = map.project(center, this._tileZoom).floor(),
11042 halfSize = map.getSize().divideBy(scale * 2);
11044 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
11047 // Private method to load tiles in the grid's active zoom level according to map bounds
11048 _update: function (center) {
11049 var map = this._map;
11050 if (!map) { return; }
11051 var zoom = this._clampZoom(map.getZoom());
11053 if (center === undefined) { center = map.getCenter(); }
11054 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom
11056 var pixelBounds = this._getTiledPixelBounds(center),
11057 tileRange = this._pxBoundsToTileRange(pixelBounds),
11058 tileCenter = tileRange.getCenter(),
11060 margin = this.options.keepBuffer,
11061 noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
11062 tileRange.getTopRight().add([margin, -margin]));
11064 // Sanity check: panic if the tile range contains Infinity somewhere.
11065 if (!(isFinite(tileRange.min.x) &&
11066 isFinite(tileRange.min.y) &&
11067 isFinite(tileRange.max.x) &&
11068 isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
11070 for (var key in this._tiles) {
11071 var c = this._tiles[key].coords;
11072 if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
11073 this._tiles[key].current = false;
11077 // _update just loads more tiles. If the tile zoom level differs too much
11078 // from the map's, let _setView reset levels and prune old tiles.
11079 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
11081 // create a queue of coordinates to load tiles from
11082 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
11083 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
11084 var coords = new Point(i, j);
11085 coords.z = this._tileZoom;
11087 if (!this._isValidTile(coords)) { continue; }
11089 var tile = this._tiles[this._tileCoordsToKey(coords)];
11091 tile.current = true;
11093 queue.push(coords);
11098 // sort tile queue to load tiles in order of their distance to center
11099 queue.sort(function (a, b) {
11100 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
11103 if (queue.length !== 0) {
11104 // if it's the first batch of tiles to load
11105 if (!this._loading) {
11106 this._loading = true;
11107 // @event loading: Event
11108 // Fired when the grid layer starts loading tiles.
11109 this.fire('loading');
11112 // create DOM fragment to append tiles in one batch
11113 var fragment = document.createDocumentFragment();
11115 for (i = 0; i < queue.length; i++) {
11116 this._addTile(queue[i], fragment);
11119 this._level.el.appendChild(fragment);
11123 _isValidTile: function (coords) {
11124 var crs = this._map.options.crs;
11126 if (!crs.infinite) {
11127 // don't load tile if it's out of bounds and not wrapped
11128 var bounds = this._globalTileRange;
11129 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
11130 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
11133 if (!this.options.bounds) { return true; }
11135 // don't load tile if it doesn't intersect the bounds in options
11136 var tileBounds = this._tileCoordsToBounds(coords);
11137 return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
11140 _keyToBounds: function (key) {
11141 return this._tileCoordsToBounds(this._keyToTileCoords(key));
11144 _tileCoordsToNwSe: function (coords) {
11145 var map = this._map,
11146 tileSize = this.getTileSize(),
11147 nwPoint = coords.scaleBy(tileSize),
11148 sePoint = nwPoint.add(tileSize),
11149 nw = map.unproject(nwPoint, coords.z),
11150 se = map.unproject(sePoint, coords.z);
11154 // converts tile coordinates to its geographical bounds
11155 _tileCoordsToBounds: function (coords) {
11156 var bp = this._tileCoordsToNwSe(coords),
11157 bounds = new LatLngBounds(bp[0], bp[1]);
11159 if (!this.options.noWrap) {
11160 bounds = this._map.wrapLatLngBounds(bounds);
11164 // converts tile coordinates to key for the tile cache
11165 _tileCoordsToKey: function (coords) {
11166 return coords.x + ':' + coords.y + ':' + coords.z;
11169 // converts tile cache key to coordinates
11170 _keyToTileCoords: function (key) {
11171 var k = key.split(':'),
11172 coords = new Point(+k[0], +k[1]);
11177 _removeTile: function (key) {
11178 var tile = this._tiles[key];
11179 if (!tile) { return; }
11181 // Cancels any pending http requests associated with the tile
11182 // unless we're on Android's stock browser,
11183 // see https://github.com/Leaflet/Leaflet/issues/137
11184 if (!androidStock) {
11185 tile.el.setAttribute('src', emptyImageUrl);
11189 delete this._tiles[key];
11191 // @event tileunload: TileEvent
11192 // Fired when a tile is removed (e.g. when a tile goes off the screen).
11193 this.fire('tileunload', {
11195 coords: this._keyToTileCoords(key)
11199 _initTile: function (tile) {
11200 addClass(tile, 'leaflet-tile');
11202 var tileSize = this.getTileSize();
11203 tile.style.width = tileSize.x + 'px';
11204 tile.style.height = tileSize.y + 'px';
11206 tile.onselectstart = falseFn;
11207 tile.onmousemove = falseFn;
11209 // update opacity on tiles in IE7-8 because of filter inheritance problems
11210 if (ielt9 && this.options.opacity < 1) {
11211 setOpacity(tile, this.options.opacity);
11214 // without this hack, tiles disappear after zoom on Chrome for Android
11215 // https://github.com/Leaflet/Leaflet/issues/2078
11216 if (android && !android23) {
11217 tile.style.WebkitBackfaceVisibility = 'hidden';
11221 _addTile: function (coords, container) {
11222 var tilePos = this._getTilePos(coords),
11223 key = this._tileCoordsToKey(coords);
11225 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
11227 this._initTile(tile);
11229 // if createTile is defined with a second argument ("done" callback),
11230 // we know that tile is async and will be ready later; otherwise
11231 if (this.createTile.length < 2) {
11232 // mark tile as ready, but delay one frame for opacity animation to happen
11233 requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
11236 setPosition(tile, tilePos);
11238 // save tile in cache
11239 this._tiles[key] = {
11245 container.appendChild(tile);
11246 // @event tileloadstart: TileEvent
11247 // Fired when a tile is requested and starts loading.
11248 this.fire('tileloadstart', {
11254 _tileReady: function (coords, err, tile) {
11255 if (!this._map) { return; }
11258 // @event tileerror: TileErrorEvent
11259 // Fired when there is an error loading a tile.
11260 this.fire('tileerror', {
11267 var key = this._tileCoordsToKey(coords);
11269 tile = this._tiles[key];
11270 if (!tile) { return; }
11272 tile.loaded = +new Date();
11273 if (this._map._fadeAnimated) {
11274 setOpacity(tile.el, 0);
11275 cancelAnimFrame(this._fadeFrame);
11276 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11278 tile.active = true;
11279 this._pruneTiles();
11283 addClass(tile.el, 'leaflet-tile-loaded');
11285 // @event tileload: TileEvent
11286 // Fired when a tile loads.
11287 this.fire('tileload', {
11293 if (this._noTilesToLoad()) {
11294 this._loading = false;
11295 // @event load: Event
11296 // Fired when the grid layer loaded all visible tiles.
11299 if (ielt9 || !this._map._fadeAnimated) {
11300 requestAnimFrame(this._pruneTiles, this);
11302 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
11303 // to trigger a pruning.
11304 setTimeout(bind(this._pruneTiles, this), 250);
11309 _getTilePos: function (coords) {
11310 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
11313 _wrapCoords: function (coords) {
11314 var newCoords = new Point(
11315 this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
11316 this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
11317 newCoords.z = coords.z;
11321 _pxBoundsToTileRange: function (bounds) {
11322 var tileSize = this.getTileSize();
11324 bounds.min.unscaleBy(tileSize).floor(),
11325 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
11328 _noTilesToLoad: function () {
11329 for (var key in this._tiles) {
11330 if (!this._tiles[key].loaded) { return false; }
11336 // @factory L.gridLayer(options?: GridLayer options)
11337 // Creates a new instance of GridLayer with the supplied options.
11338 function gridLayer(options) {
11339 return new GridLayer(options);
11344 * @inherits GridLayer
11346 * Used to load and display tile layers on the map. Extends `GridLayer`.
11351 * L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map);
11354 * @section URL template
11357 * A string of the following form:
11360 * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
11363 * `{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.
11365 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
11368 * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
11373 var TileLayer = GridLayer.extend({
11376 // @aka TileLayer options
11378 // @option minZoom: Number = 0
11379 // The minimum zoom level down to which this layer will be displayed (inclusive).
11382 // @option maxZoom: Number = 18
11383 // The maximum zoom level up to which this layer will be displayed (inclusive).
11386 // @option subdomains: String|String[] = 'abc'
11387 // 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.
11390 // @option errorTileUrl: String = ''
11391 // URL to the tile image to show in place of the tile that failed to load.
11394 // @option zoomOffset: Number = 0
11395 // The zoom number used in tile URLs will be offset with this value.
11398 // @option tms: Boolean = false
11399 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
11402 // @option zoomReverse: Boolean = false
11403 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
11404 zoomReverse: false,
11406 // @option detectRetina: Boolean = false
11407 // 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.
11408 detectRetina: false,
11410 // @option crossOrigin: Boolean = false
11411 // If true, all tiles will have their crossOrigin attribute set to ''. This is needed if you want to access tile pixel data.
11415 initialize: function (url, options) {
11419 options = setOptions(this, options);
11421 // detecting retina displays, adjusting tileSize and zoom levels
11422 if (options.detectRetina && retina && options.maxZoom > 0) {
11424 options.tileSize = Math.floor(options.tileSize / 2);
11426 if (!options.zoomReverse) {
11427 options.zoomOffset++;
11430 options.zoomOffset--;
11434 options.minZoom = Math.max(0, options.minZoom);
11437 if (typeof options.subdomains === 'string') {
11438 options.subdomains = options.subdomains.split('');
11441 // for https://github.com/Leaflet/Leaflet/issues/137
11443 this.on('tileunload', this._onTileRemove);
11447 // @method setUrl(url: String, noRedraw?: Boolean): this
11448 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
11449 setUrl: function (url, noRedraw) {
11458 // @method createTile(coords: Object, done?: Function): HTMLElement
11459 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
11460 // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
11461 // callback is called when the tile has been loaded.
11462 createTile: function (coords, done) {
11463 var tile = document.createElement('img');
11465 on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
11466 on(tile, 'error', bind(this._tileOnError, this, done, tile));
11468 if (this.options.crossOrigin) {
11469 tile.crossOrigin = '';
11473 Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
11474 http://www.w3.org/TR/WCAG20-TECHS/H67
11479 Set role="presentation" to force screen readers to ignore this
11480 https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
11482 tile.setAttribute('role', 'presentation');
11484 tile.src = this.getTileUrl(coords);
11489 // @section Extension methods
11491 // Layers extending `TileLayer` might reimplement the following method.
11492 // @method getTileUrl(coords: Object): String
11493 // Called only internally, returns the URL for a tile given its coordinates.
11494 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
11495 getTileUrl: function (coords) {
11497 r: retina ? '@2x' : '',
11498 s: this._getSubdomain(coords),
11501 z: this._getZoomForUrl()
11503 if (this._map && !this._map.options.crs.infinite) {
11504 var invertedY = this._globalTileRange.max.y - coords.y;
11505 if (this.options.tms) {
11506 data['y'] = invertedY;
11508 data['-y'] = invertedY;
11511 return template(this._url, extend(data, this.options));
11514 _tileOnLoad: function (done, tile) {
11515 // For https://github.com/Leaflet/Leaflet/issues/3332
11517 setTimeout(bind(done, this, null, tile), 0);
11523 _tileOnError: function (done, tile, e) {
11524 var errorUrl = this.options.errorTileUrl;
11525 if (errorUrl && tile.getAttribute('src') !== errorUrl) {
11526 tile.src = errorUrl;
11531 _onTileRemove: function (e) {
11532 e.tile.onload = null;
11535 _getZoomForUrl: function () {
11536 var zoom = this._tileZoom,
11537 maxZoom = this.options.maxZoom,
11538 zoomReverse = this.options.zoomReverse,
11539 zoomOffset = this.options.zoomOffset;
11542 zoom = maxZoom - zoom;
11545 return zoom + zoomOffset;
11548 _getSubdomain: function (tilePoint) {
11549 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
11550 return this.options.subdomains[index];
11553 // stops loading all tiles in the background layer
11554 _abortLoading: function () {
11556 for (i in this._tiles) {
11557 if (this._tiles[i].coords.z !== this._tileZoom) {
11558 tile = this._tiles[i].el;
11560 tile.onload = falseFn;
11561 tile.onerror = falseFn;
11563 if (!tile.complete) {
11564 tile.src = emptyImageUrl;
11566 delete this._tiles[i];
11574 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
11575 // Instantiates a tile layer object given a `URL template` and optionally an options object.
11577 function tileLayer(url, options) {
11578 return new TileLayer(url, options);
11582 * @class TileLayer.WMS
11583 * @inherits TileLayer
11584 * @aka L.TileLayer.WMS
11585 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
11590 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
11591 * layers: 'nexrad-n0r-900913',
11592 * format: 'image/png',
11593 * transparent: true,
11594 * attribution: "Weather data © 2012 IEM Nexrad"
11599 var TileLayerWMS = TileLayer.extend({
11602 // @aka TileLayer.WMS options
11603 // If any custom options not documented here are used, they will be sent to the
11604 // WMS server as extra parameters in each request URL. This can be useful for
11605 // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
11606 defaultWmsParams: {
11610 // @option layers: String = ''
11611 // **(required)** Comma-separated list of WMS layers to show.
11614 // @option styles: String = ''
11615 // Comma-separated list of WMS styles.
11618 // @option format: String = 'image/jpeg'
11619 // WMS image format (use `'image/png'` for layers with transparency).
11620 format: 'image/jpeg',
11622 // @option transparent: Boolean = false
11623 // If `true`, the WMS service will return images with transparency.
11624 transparent: false,
11626 // @option version: String = '1.1.1'
11627 // Version of the WMS service to use
11632 // @option crs: CRS = null
11633 // Coordinate Reference System to use for the WMS requests, defaults to
11634 // map CRS. Don't change this if you're not sure what it means.
11637 // @option uppercase: Boolean = false
11638 // If `true`, WMS request parameter keys will be uppercase.
11642 initialize: function (url, options) {
11646 var wmsParams = extend({}, this.defaultWmsParams);
11648 // all keys that are not TileLayer options go to WMS params
11649 for (var i in options) {
11650 if (!(i in this.options)) {
11651 wmsParams[i] = options[i];
11655 options = setOptions(this, options);
11657 var realRetina = options.detectRetina && retina ? 2 : 1;
11658 var tileSize = this.getTileSize();
11659 wmsParams.width = tileSize.x * realRetina;
11660 wmsParams.height = tileSize.y * realRetina;
11662 this.wmsParams = wmsParams;
11665 onAdd: function (map) {
11667 this._crs = this.options.crs || map.options.crs;
11668 this._wmsVersion = parseFloat(this.wmsParams.version);
11670 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
11671 this.wmsParams[projectionKey] = this._crs.code;
11673 TileLayer.prototype.onAdd.call(this, map);
11676 getTileUrl: function (coords) {
11678 var tileBounds = this._tileCoordsToNwSe(coords),
11680 bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
11683 bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
11684 [min.y, min.x, max.y, max.x] :
11685 [min.x, min.y, max.x, max.y]).join(','),
11686 url = L.TileLayer.prototype.getTileUrl.call(this, coords);
11688 getParamString(this.wmsParams, url, this.options.uppercase) +
11689 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
11692 // @method setParams(params: Object, noRedraw?: Boolean): this
11693 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
11694 setParams: function (params, noRedraw) {
11696 extend(this.wmsParams, params);
11707 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
11708 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
11709 function tileLayerWMS(url, options) {
11710 return new TileLayerWMS(url, options);
11713 TileLayer.WMS = TileLayerWMS;
11714 tileLayer.wms = tileLayerWMS;
11721 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
11722 * DOM container of the renderer, its bounds, and its zoom animation.
11724 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
11725 * itself can be added or removed to the map. All paths use a renderer, which can
11726 * be implicit (the map will decide the type of renderer and use it automatically)
11727 * or explicit (using the [`renderer`](#path-renderer) option of the path).
11729 * Do not use this class directly, use `SVG` and `Canvas` instead.
11731 * @event update: Event
11732 * Fired when the renderer updates its bounds, center and zoom, for example when
11733 * its map has moved
11736 var Renderer = Layer.extend({
11739 // @aka Renderer options
11741 // @option padding: Number = 0.1
11742 // How much to extend the clip area around the map view (relative to its size)
11743 // e.g. 0.1 would be 10% of map view in each direction
11746 // @option tolerance: Number = 0
11747 // How much to extend click tolerance round a path/object on the map
11751 initialize: function (options) {
11752 setOptions(this, options);
11754 this._layers = this._layers || {};
11757 onAdd: function () {
11758 if (!this._container) {
11759 this._initContainer(); // defined by renderer implementations
11761 if (this._zoomAnimated) {
11762 addClass(this._container, 'leaflet-zoom-animated');
11766 this.getPane().appendChild(this._container);
11768 this.on('update', this._updatePaths, this);
11771 onRemove: function () {
11772 this.off('update', this._updatePaths, this);
11773 this._destroyContainer();
11776 getEvents: function () {
11778 viewreset: this._reset,
11779 zoom: this._onZoom,
11780 moveend: this._update,
11781 zoomend: this._onZoomEnd
11783 if (this._zoomAnimated) {
11784 events.zoomanim = this._onAnimZoom;
11789 _onAnimZoom: function (ev) {
11790 this._updateTransform(ev.center, ev.zoom);
11793 _onZoom: function () {
11794 this._updateTransform(this._map.getCenter(), this._map.getZoom());
11797 _updateTransform: function (center, zoom) {
11798 var scale = this._map.getZoomScale(zoom, this._zoom),
11799 position = getPosition(this._container),
11800 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
11801 currentCenterPoint = this._map.project(this._center, zoom),
11802 destCenterPoint = this._map.project(center, zoom),
11803 centerOffset = destCenterPoint.subtract(currentCenterPoint),
11805 topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
11808 setTransform(this._container, topLeftOffset, scale);
11810 setPosition(this._container, topLeftOffset);
11814 _reset: function () {
11816 this._updateTransform(this._center, this._zoom);
11818 for (var id in this._layers) {
11819 this._layers[id]._reset();
11823 _onZoomEnd: function () {
11824 for (var id in this._layers) {
11825 this._layers[id]._project();
11829 _updatePaths: function () {
11830 for (var id in this._layers) {
11831 this._layers[id]._update();
11835 _update: function () {
11836 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
11837 // Subclasses are responsible of firing the 'update' event.
11838 var p = this.options.padding,
11839 size = this._map.getSize(),
11840 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
11842 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
11844 this._center = this._map.getCenter();
11845 this._zoom = this._map.getZoom();
11851 * @inherits Renderer
11854 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
11855 * Inherits `Renderer`.
11857 * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
11858 * available in all web browsers, notably IE8, and overlapping geometries might
11859 * not display properly in some edge cases.
11863 * Use Canvas by default for all paths in the map:
11866 * var map = L.map('map', {
11867 * renderer: L.canvas()
11871 * Use a Canvas renderer with extra padding for specific vector geometries:
11874 * var map = L.map('map');
11875 * var myRenderer = L.canvas({ padding: 0.5 });
11876 * var line = L.polyline( coordinates, { renderer: myRenderer } );
11877 * var circle = L.circle( center, { renderer: myRenderer } );
11881 var Canvas = Renderer.extend({
11882 getEvents: function () {
11883 var events = Renderer.prototype.getEvents.call(this);
11884 events.viewprereset = this._onViewPreReset;
11888 _onViewPreReset: function () {
11889 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
11890 this._postponeUpdatePaths = true;
11893 onAdd: function () {
11894 Renderer.prototype.onAdd.call(this);
11896 // Redraw vectors since canvas is cleared upon removal,
11897 // in case of removing the renderer itself from the map.
11901 _initContainer: function () {
11902 var container = this._container = document.createElement('canvas');
11904 on(container, 'mousemove', throttle(this._onMouseMove, 32, this), this);
11905 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
11906 on(container, 'mouseout', this._handleMouseOut, this);
11908 this._ctx = container.getContext('2d');
11911 _destroyContainer: function () {
11913 remove(this._container);
11914 off(this._container);
11915 delete this._container;
11918 _updatePaths: function () {
11919 if (this._postponeUpdatePaths) { return; }
11922 this._redrawBounds = null;
11923 for (var id in this._layers) {
11924 layer = this._layers[id];
11930 _update: function () {
11931 if (this._map._animatingZoom && this._bounds) { return; }
11933 this._drawnLayers = {};
11935 Renderer.prototype._update.call(this);
11937 var b = this._bounds,
11938 container = this._container,
11939 size = b.getSize(),
11940 m = retina ? 2 : 1;
11942 setPosition(container, b.min);
11944 // set canvas size (also clearing it); use double size on retina
11945 container.width = m * size.x;
11946 container.height = m * size.y;
11947 container.style.width = size.x + 'px';
11948 container.style.height = size.y + 'px';
11951 this._ctx.scale(2, 2);
11954 // translate so we use the same path coordinates after canvas element moves
11955 this._ctx.translate(-b.min.x, -b.min.y);
11957 // Tell paths to redraw themselves
11958 this.fire('update');
11961 _reset: function () {
11962 Renderer.prototype._reset.call(this);
11964 if (this._postponeUpdatePaths) {
11965 this._postponeUpdatePaths = false;
11966 this._updatePaths();
11970 _initPath: function (layer) {
11971 this._updateDashArray(layer);
11972 this._layers[stamp(layer)] = layer;
11974 var order = layer._order = {
11976 prev: this._drawLast,
11979 if (this._drawLast) { this._drawLast.next = order; }
11980 this._drawLast = order;
11981 this._drawFirst = this._drawFirst || this._drawLast;
11984 _addPath: function (layer) {
11985 this._requestRedraw(layer);
11988 _removePath: function (layer) {
11989 var order = layer._order;
11990 var next = order.next;
11991 var prev = order.prev;
11996 this._drawLast = prev;
12001 this._drawFirst = next;
12004 delete layer._order;
12006 delete this._layers[L.stamp(layer)];
12008 this._requestRedraw(layer);
12011 _updatePath: function (layer) {
12012 // Redraw the union of the layer's old pixel
12013 // bounds and the new pixel bounds.
12014 this._extendRedrawBounds(layer);
12017 // The redraw will extend the redraw bounds
12018 // with the new pixel bounds.
12019 this._requestRedraw(layer);
12022 _updateStyle: function (layer) {
12023 this._updateDashArray(layer);
12024 this._requestRedraw(layer);
12027 _updateDashArray: function (layer) {
12028 if (layer.options.dashArray) {
12029 var parts = layer.options.dashArray.split(','),
12032 for (i = 0; i < parts.length; i++) {
12033 dashArray.push(Number(parts[i]));
12035 layer.options._dashArray = dashArray;
12039 _requestRedraw: function (layer) {
12040 if (!this._map) { return; }
12042 this._extendRedrawBounds(layer);
12043 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
12046 _extendRedrawBounds: function (layer) {
12047 if (layer._pxBounds) {
12048 var padding = (layer.options.weight || 0) + 1;
12049 this._redrawBounds = this._redrawBounds || new Bounds();
12050 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
12051 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
12055 _redraw: function () {
12056 this._redrawRequest = null;
12058 if (this._redrawBounds) {
12059 this._redrawBounds.min._floor();
12060 this._redrawBounds.max._ceil();
12063 this._clear(); // clear layers in redraw bounds
12064 this._draw(); // draw layers
12066 this._redrawBounds = null;
12069 _clear: function () {
12070 var bounds = this._redrawBounds;
12072 var size = bounds.getSize();
12073 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
12075 this._ctx.clearRect(0, 0, this._container.width, this._container.height);
12079 _draw: function () {
12080 var layer, bounds = this._redrawBounds;
12083 var size = bounds.getSize();
12084 this._ctx.beginPath();
12085 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
12089 this._drawing = true;
12091 for (var order = this._drawFirst; order; order = order.next) {
12092 layer = order.layer;
12093 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
12094 layer._updatePath();
12098 this._drawing = false;
12100 this._ctx.restore(); // Restore state before clipping.
12103 _updatePoly: function (layer, closed) {
12104 if (!this._drawing) { return; }
12107 parts = layer._parts,
12108 len = parts.length,
12111 if (!len) { return; }
12113 this._drawnLayers[layer._leaflet_id] = layer;
12117 for (i = 0; i < len; i++) {
12118 for (j = 0, len2 = parts[i].length; j < len2; j++) {
12120 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
12127 this._fillStroke(ctx, layer);
12129 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
12132 _updateCircle: function (layer) {
12134 if (!this._drawing || layer._empty()) { return; }
12136 var p = layer._point,
12138 r = Math.max(Math.round(layer._radius), 1),
12139 s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
12141 this._drawnLayers[layer._leaflet_id] = layer;
12149 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
12155 this._fillStroke(ctx, layer);
12158 _fillStroke: function (ctx, layer) {
12159 var options = layer.options;
12161 if (options.fill) {
12162 ctx.globalAlpha = options.fillOpacity;
12163 ctx.fillStyle = options.fillColor || options.color;
12164 ctx.fill(options.fillRule || 'evenodd');
12167 if (options.stroke && options.weight !== 0) {
12168 if (ctx.setLineDash) {
12169 ctx.setLineDash(layer.options && layer.options._dashArray || []);
12171 ctx.globalAlpha = options.opacity;
12172 ctx.lineWidth = options.weight;
12173 ctx.strokeStyle = options.color;
12174 ctx.lineCap = options.lineCap;
12175 ctx.lineJoin = options.lineJoin;
12180 // Canvas obviously doesn't have mouse events for individual drawn objects,
12181 // so we emulate that by calculating what's under the mouse on mousemove/click manually
12183 _onClick: function (e) {
12184 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
12186 for (var order = this._drawFirst; order; order = order.next) {
12187 layer = order.layer;
12188 if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
12189 clickedLayer = layer;
12192 if (clickedLayer) {
12194 this._fireEvent([clickedLayer], e);
12198 _onMouseMove: function (e) {
12199 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
12201 var point = this._map.mouseEventToLayerPoint(e);
12202 this._handleMouseHover(e, point);
12206 _handleMouseOut: function (e) {
12207 var layer = this._hoveredLayer;
12209 // if we're leaving the layer, fire mouseout
12210 removeClass(this._container, 'leaflet-interactive');
12211 this._fireEvent([layer], e, 'mouseout');
12212 this._hoveredLayer = null;
12216 _handleMouseHover: function (e, point) {
12217 var layer, candidateHoveredLayer;
12219 for (var order = this._drawFirst; order; order = order.next) {
12220 layer = order.layer;
12221 if (layer.options.interactive && layer._containsPoint(point)) {
12222 candidateHoveredLayer = layer;
12226 if (candidateHoveredLayer !== this._hoveredLayer) {
12227 this._handleMouseOut(e);
12229 if (candidateHoveredLayer) {
12230 addClass(this._container, 'leaflet-interactive'); // change cursor
12231 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
12232 this._hoveredLayer = candidateHoveredLayer;
12236 if (this._hoveredLayer) {
12237 this._fireEvent([this._hoveredLayer], e);
12241 _fireEvent: function (layers, e, type) {
12242 this._map._fireDOMEvent(e, type || e.type, layers);
12245 _bringToFront: function (layer) {
12246 var order = layer._order;
12247 var next = order.next;
12248 var prev = order.prev;
12259 // Update first entry unless this is the
12261 this._drawFirst = next;
12264 order.prev = this._drawLast;
12265 this._drawLast.next = order;
12268 this._drawLast = order;
12270 this._requestRedraw(layer);
12273 _bringToBack: function (layer) {
12274 var order = layer._order;
12275 var next = order.next;
12276 var prev = order.prev;
12287 // Update last entry unless this is the
12289 this._drawLast = prev;
12294 order.next = this._drawFirst;
12295 this._drawFirst.prev = order;
12296 this._drawFirst = order;
12298 this._requestRedraw(layer);
12302 // @factory L.canvas(options?: Renderer options)
12303 // Creates a Canvas renderer with the given options.
12304 function canvas$1(options) {
12305 return canvas ? new Canvas(options) : null;
12309 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
12313 var vmlCreate = (function () {
12315 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
12316 return function (name) {
12317 return document.createElement('<lvml:' + name + ' class="lvml">');
12320 return function (name) {
12321 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
12330 * 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.
12332 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
12333 * with old versions of Internet Explorer.
12336 // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
12339 _initContainer: function () {
12340 this._container = create$1('div', 'leaflet-vml-container');
12343 _update: function () {
12344 if (this._map._animatingZoom) { return; }
12345 Renderer.prototype._update.call(this);
12346 this.fire('update');
12349 _initPath: function (layer) {
12350 var container = layer._container = vmlCreate('shape');
12352 addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
12354 container.coordsize = '1 1';
12356 layer._path = vmlCreate('path');
12357 container.appendChild(layer._path);
12359 this._updateStyle(layer);
12360 this._layers[stamp(layer)] = layer;
12363 _addPath: function (layer) {
12364 var container = layer._container;
12365 this._container.appendChild(container);
12367 if (layer.options.interactive) {
12368 layer.addInteractiveTarget(container);
12372 _removePath: function (layer) {
12373 var container = layer._container;
12375 layer.removeInteractiveTarget(container);
12376 delete this._layers[stamp(layer)];
12379 _updateStyle: function (layer) {
12380 var stroke = layer._stroke,
12381 fill = layer._fill,
12382 options = layer.options,
12383 container = layer._container;
12385 container.stroked = !!options.stroke;
12386 container.filled = !!options.fill;
12388 if (options.stroke) {
12390 stroke = layer._stroke = vmlCreate('stroke');
12392 container.appendChild(stroke);
12393 stroke.weight = options.weight + 'px';
12394 stroke.color = options.color;
12395 stroke.opacity = options.opacity;
12397 if (options.dashArray) {
12398 stroke.dashStyle = isArray(options.dashArray) ?
12399 options.dashArray.join(' ') :
12400 options.dashArray.replace(/( *, *)/g, ' ');
12402 stroke.dashStyle = '';
12404 stroke.endcap = options.lineCap.replace('butt', 'flat');
12405 stroke.joinstyle = options.lineJoin;
12407 } else if (stroke) {
12408 container.removeChild(stroke);
12409 layer._stroke = null;
12412 if (options.fill) {
12414 fill = layer._fill = vmlCreate('fill');
12416 container.appendChild(fill);
12417 fill.color = options.fillColor || options.color;
12418 fill.opacity = options.fillOpacity;
12421 container.removeChild(fill);
12422 layer._fill = null;
12426 _updateCircle: function (layer) {
12427 var p = layer._point.round(),
12428 r = Math.round(layer._radius),
12429 r2 = Math.round(layer._radiusY || r);
12431 this._setPath(layer, layer._empty() ? 'M0 0' :
12432 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
12435 _setPath: function (layer, path) {
12436 layer._path.v = path;
12439 _bringToFront: function (layer) {
12440 toFront(layer._container);
12443 _bringToBack: function (layer) {
12444 toBack(layer._container);
12448 var create$2 = vml ? vmlCreate : svgCreate;
12452 * @inherits Renderer
12455 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
12456 * Inherits `Renderer`.
12458 * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
12459 * available in all web browsers, notably Android 2.x and 3.x.
12461 * Although SVG is not available on IE7 and IE8, these browsers support
12462 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
12463 * (a now deprecated technology), and the SVG renderer will fall back to VML in
12468 * Use SVG by default for all paths in the map:
12471 * var map = L.map('map', {
12472 * renderer: L.svg()
12476 * Use a SVG renderer with extra padding for specific vector geometries:
12479 * var map = L.map('map');
12480 * var myRenderer = L.svg({ padding: 0.5 });
12481 * var line = L.polyline( coordinates, { renderer: myRenderer } );
12482 * var circle = L.circle( center, { renderer: myRenderer } );
12486 var SVG = Renderer.extend({
12488 getEvents: function () {
12489 var events = Renderer.prototype.getEvents.call(this);
12490 events.zoomstart = this._onZoomStart;
12494 _initContainer: function () {
12495 this._container = create$2('svg');
12497 // makes it possible to click through svg root; we'll reset it back in individual paths
12498 this._container.setAttribute('pointer-events', 'none');
12500 this._rootGroup = create$2('g');
12501 this._container.appendChild(this._rootGroup);
12504 _destroyContainer: function () {
12505 remove(this._container);
12506 off(this._container);
12507 delete this._container;
12508 delete this._rootGroup;
12509 delete this._svgSize;
12512 _onZoomStart: function () {
12513 // Drag-then-pinch interactions might mess up the center and zoom.
12514 // In this case, the easiest way to prevent this is re-do the renderer
12515 // bounds and padding when the zooming starts.
12519 _update: function () {
12520 if (this._map._animatingZoom && this._bounds) { return; }
12522 Renderer.prototype._update.call(this);
12524 var b = this._bounds,
12525 size = b.getSize(),
12526 container = this._container;
12528 // set size of svg-container if changed
12529 if (!this._svgSize || !this._svgSize.equals(size)) {
12530 this._svgSize = size;
12531 container.setAttribute('width', size.x);
12532 container.setAttribute('height', size.y);
12535 // movement: update container viewBox so that we don't have to change coordinates of individual layers
12536 setPosition(container, b.min);
12537 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
12539 this.fire('update');
12542 // methods below are called by vector layers implementations
12544 _initPath: function (layer) {
12545 var path = layer._path = create$2('path');
12548 // @option className: String = null
12549 // Custom class name set on an element. Only for SVG renderer.
12550 if (layer.options.className) {
12551 addClass(path, layer.options.className);
12554 if (layer.options.interactive) {
12555 addClass(path, 'leaflet-interactive');
12558 this._updateStyle(layer);
12559 this._layers[stamp(layer)] = layer;
12562 _addPath: function (layer) {
12563 if (!this._rootGroup) { this._initContainer(); }
12564 this._rootGroup.appendChild(layer._path);
12565 layer.addInteractiveTarget(layer._path);
12568 _removePath: function (layer) {
12569 remove(layer._path);
12570 layer.removeInteractiveTarget(layer._path);
12571 delete this._layers[stamp(layer)];
12574 _updatePath: function (layer) {
12579 _updateStyle: function (layer) {
12580 var path = layer._path,
12581 options = layer.options;
12583 if (!path) { return; }
12585 if (options.stroke) {
12586 path.setAttribute('stroke', options.color);
12587 path.setAttribute('stroke-opacity', options.opacity);
12588 path.setAttribute('stroke-width', options.weight);
12589 path.setAttribute('stroke-linecap', options.lineCap);
12590 path.setAttribute('stroke-linejoin', options.lineJoin);
12592 if (options.dashArray) {
12593 path.setAttribute('stroke-dasharray', options.dashArray);
12595 path.removeAttribute('stroke-dasharray');
12598 if (options.dashOffset) {
12599 path.setAttribute('stroke-dashoffset', options.dashOffset);
12601 path.removeAttribute('stroke-dashoffset');
12604 path.setAttribute('stroke', 'none');
12607 if (options.fill) {
12608 path.setAttribute('fill', options.fillColor || options.color);
12609 path.setAttribute('fill-opacity', options.fillOpacity);
12610 path.setAttribute('fill-rule', options.fillRule || 'evenodd');
12612 path.setAttribute('fill', 'none');
12616 _updatePoly: function (layer, closed) {
12617 this._setPath(layer, pointsToPath(layer._parts, closed));
12620 _updateCircle: function (layer) {
12621 var p = layer._point,
12622 r = Math.max(Math.round(layer._radius), 1),
12623 r2 = Math.max(Math.round(layer._radiusY), 1) || r,
12624 arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
12626 // drawing a circle with two half-arcs
12627 var d = layer._empty() ? 'M0 0' :
12628 'M' + (p.x - r) + ',' + p.y +
12629 arc + (r * 2) + ',0 ' +
12630 arc + (-r * 2) + ',0 ';
12632 this._setPath(layer, d);
12635 _setPath: function (layer, path) {
12636 layer._path.setAttribute('d', path);
12639 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
12640 _bringToFront: function (layer) {
12641 toFront(layer._path);
12644 _bringToBack: function (layer) {
12645 toBack(layer._path);
12650 SVG.include(vmlMixin);
12654 // @factory L.svg(options?: Renderer options)
12655 // Creates a SVG renderer with the given options.
12656 function svg$1(options) {
12657 return svg || vml ? new SVG(options) : null;
12661 // @namespace Map; @method getRenderer(layer: Path): Renderer
12662 // Returns the instance of `Renderer` that should be used to render the given
12663 // `Path`. It will ensure that the `renderer` options of the map and paths
12664 // are respected, and that the renderers do exist on the map.
12665 getRenderer: function (layer) {
12666 // @namespace Path; @option renderer: Renderer
12667 // Use this specific instance of `Renderer` for this path. Takes
12668 // precedence over the map's [default renderer](#map-renderer).
12669 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
12672 // @namespace Map; @option preferCanvas: Boolean = false
12673 // Whether `Path`s should be rendered on a `Canvas` renderer.
12674 // By default, all `Path`s are rendered in a `SVG` renderer.
12675 renderer = this._renderer = (this.options.preferCanvas && canvas$1()) || svg$1();
12678 if (!this.hasLayer(renderer)) {
12679 this.addLayer(renderer);
12684 _getPaneRenderer: function (name) {
12685 if (name === 'overlayPane' || name === undefined) {
12689 var renderer = this._paneRenderers[name];
12690 if (renderer === undefined) {
12691 renderer = (SVG && svg$1({pane: name})) || (Canvas && canvas$1({pane: name}));
12692 this._paneRenderers[name] = renderer;
12699 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
12705 * @inherits Polygon
12707 * A class for drawing rectangle overlays on a map. Extends `Polygon`.
12712 * // define rectangle geographical bounds
12713 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
12715 * // create an orange rectangle
12716 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
12718 * // zoom the map to the rectangle bounds
12719 * map.fitBounds(bounds);
12725 var Rectangle = Polygon.extend({
12726 initialize: function (latLngBounds, options) {
12727 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
12730 // @method setBounds(latLngBounds: LatLngBounds): this
12731 // Redraws the rectangle with the passed bounds.
12732 setBounds: function (latLngBounds) {
12733 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
12736 _boundsToLatLngs: function (latLngBounds) {
12737 latLngBounds = toLatLngBounds(latLngBounds);
12739 latLngBounds.getSouthWest(),
12740 latLngBounds.getNorthWest(),
12741 latLngBounds.getNorthEast(),
12742 latLngBounds.getSouthEast()
12748 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
12749 function rectangle(latLngBounds, options) {
12750 return new Rectangle(latLngBounds, options);
12753 SVG.create = create$2;
12754 SVG.pointsToPath = pointsToPath;
12756 GeoJSON.geometryToLayer = geometryToLayer;
12757 GeoJSON.coordsToLatLng = coordsToLatLng;
12758 GeoJSON.coordsToLatLngs = coordsToLatLngs;
12759 GeoJSON.latLngToCoords = latLngToCoords;
12760 GeoJSON.latLngsToCoords = latLngsToCoords;
12761 GeoJSON.getFeature = getFeature;
12762 GeoJSON.asFeature = asFeature;
12765 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
12766 * (zoom to a selected bounding box), enabled by default.
12770 // @section Interaction Options
12772 // @option boxZoom: Boolean = true
12773 // Whether the map can be zoomed to a rectangular area specified by
12774 // dragging the mouse while pressing the shift key.
12778 var BoxZoom = Handler.extend({
12779 initialize: function (map) {
12781 this._container = map._container;
12782 this._pane = map._panes.overlayPane;
12783 this._resetStateTimeout = 0;
12784 map.on('unload', this._destroy, this);
12787 addHooks: function () {
12788 on(this._container, 'mousedown', this._onMouseDown, this);
12791 removeHooks: function () {
12792 off(this._container, 'mousedown', this._onMouseDown, this);
12795 moved: function () {
12796 return this._moved;
12799 _destroy: function () {
12800 remove(this._pane);
12804 _resetState: function () {
12805 this._resetStateTimeout = 0;
12806 this._moved = false;
12809 _clearDeferredResetState: function () {
12810 if (this._resetStateTimeout !== 0) {
12811 clearTimeout(this._resetStateTimeout);
12812 this._resetStateTimeout = 0;
12816 _onMouseDown: function (e) {
12817 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
12819 // Clear the deferred resetState if it hasn't executed yet, otherwise it
12820 // will interrupt the interaction and orphan a box element in the container.
12821 this._clearDeferredResetState();
12822 this._resetState();
12824 disableTextSelection();
12825 disableImageDrag();
12827 this._startPoint = this._map.mouseEventToContainerPoint(e);
12831 mousemove: this._onMouseMove,
12832 mouseup: this._onMouseUp,
12833 keydown: this._onKeyDown
12837 _onMouseMove: function (e) {
12838 if (!this._moved) {
12839 this._moved = true;
12841 this._box = create$1('div', 'leaflet-zoom-box', this._container);
12842 addClass(this._container, 'leaflet-crosshair');
12844 this._map.fire('boxzoomstart');
12847 this._point = this._map.mouseEventToContainerPoint(e);
12849 var bounds = new Bounds(this._point, this._startPoint),
12850 size = bounds.getSize();
12852 setPosition(this._box, bounds.min);
12854 this._box.style.width = size.x + 'px';
12855 this._box.style.height = size.y + 'px';
12858 _finish: function () {
12861 removeClass(this._container, 'leaflet-crosshair');
12864 enableTextSelection();
12869 mousemove: this._onMouseMove,
12870 mouseup: this._onMouseUp,
12871 keydown: this._onKeyDown
12875 _onMouseUp: function (e) {
12876 if ((e.which !== 1) && (e.button !== 1)) { return; }
12880 if (!this._moved) { return; }
12881 // Postpone to next JS tick so internal click event handling
12882 // still see it as "moved".
12883 this._clearDeferredResetState();
12884 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
12886 var bounds = new LatLngBounds(
12887 this._map.containerPointToLatLng(this._startPoint),
12888 this._map.containerPointToLatLng(this._point));
12892 .fire('boxzoomend', {boxZoomBounds: bounds});
12895 _onKeyDown: function (e) {
12896 if (e.keyCode === 27) {
12902 // @section Handlers
12903 // @property boxZoom: Handler
12904 // Box (shift-drag with mouse) zoom handler.
12905 Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
12908 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
12912 // @section Interaction Options
12915 // @option doubleClickZoom: Boolean|String = true
12916 // Whether the map can be zoomed in by double clicking on it and
12917 // zoomed out by double clicking while holding shift. If passed
12918 // `'center'`, double-click zoom will zoom to the center of the
12919 // view regardless of where the mouse was.
12920 doubleClickZoom: true
12923 var DoubleClickZoom = Handler.extend({
12924 addHooks: function () {
12925 this._map.on('dblclick', this._onDoubleClick, this);
12928 removeHooks: function () {
12929 this._map.off('dblclick', this._onDoubleClick, this);
12932 _onDoubleClick: function (e) {
12933 var map = this._map,
12934 oldZoom = map.getZoom(),
12935 delta = map.options.zoomDelta,
12936 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
12938 if (map.options.doubleClickZoom === 'center') {
12941 map.setZoomAround(e.containerPoint, zoom);
12946 // @section Handlers
12948 // Map properties include interaction handlers that allow you to control
12949 // interaction behavior in runtime, enabling or disabling certain features such
12950 // as dragging or touch zoom (see `Handler` methods). For example:
12953 // map.doubleClickZoom.disable();
12956 // @property doubleClickZoom: Handler
12957 // Double click zoom handler.
12958 Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
12961 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
12965 // @section Interaction Options
12967 // @option dragging: Boolean = true
12968 // Whether the map be draggable with mouse/touch or not.
12971 // @section Panning Inertia Options
12972 // @option inertia: Boolean = *
12973 // If enabled, panning of the map will have an inertia effect where
12974 // the map builds momentum while dragging and continues moving in
12975 // the same direction for some time. Feels especially nice on touch
12976 // devices. Enabled by default unless running on old Android devices.
12977 inertia: !android23,
12979 // @option inertiaDeceleration: Number = 3000
12980 // The rate with which the inertial movement slows down, in pixels/second².
12981 inertiaDeceleration: 3400, // px/s^2
12983 // @option inertiaMaxSpeed: Number = Infinity
12984 // Max speed of the inertial movement, in pixels/second.
12985 inertiaMaxSpeed: Infinity, // px/s
12987 // @option easeLinearity: Number = 0.2
12988 easeLinearity: 0.2,
12990 // TODO refactor, move to CRS
12991 // @option worldCopyJump: Boolean = false
12992 // With this option enabled, the map tracks when you pan to another "copy"
12993 // of the world and seamlessly jumps to the original one so that all overlays
12994 // like markers and vector layers are still visible.
12995 worldCopyJump: false,
12997 // @option maxBoundsViscosity: Number = 0.0
12998 // If `maxBounds` is set, this option will control how solid the bounds
12999 // are when dragging the map around. The default value of `0.0` allows the
13000 // user to drag outside the bounds at normal speed, higher values will
13001 // slow down map dragging outside bounds, and `1.0` makes the bounds fully
13002 // solid, preventing the user from dragging outside the bounds.
13003 maxBoundsViscosity: 0.0
13006 var Drag = Handler.extend({
13007 addHooks: function () {
13008 if (!this._draggable) {
13009 var map = this._map;
13011 this._draggable = new Draggable(map._mapPane, map._container);
13013 this._draggable.on({
13014 dragstart: this._onDragStart,
13015 drag: this._onDrag,
13016 dragend: this._onDragEnd
13019 this._draggable.on('predrag', this._onPreDragLimit, this);
13020 if (map.options.worldCopyJump) {
13021 this._draggable.on('predrag', this._onPreDragWrap, this);
13022 map.on('zoomend', this._onZoomEnd, this);
13024 map.whenReady(this._onZoomEnd, this);
13027 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
13028 this._draggable.enable();
13029 this._positions = [];
13033 removeHooks: function () {
13034 removeClass(this._map._container, 'leaflet-grab');
13035 removeClass(this._map._container, 'leaflet-touch-drag');
13036 this._draggable.disable();
13039 moved: function () {
13040 return this._draggable && this._draggable._moved;
13043 moving: function () {
13044 return this._draggable && this._draggable._moving;
13047 _onDragStart: function () {
13048 var map = this._map;
13051 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
13052 var bounds = toLatLngBounds(this._map.options.maxBounds);
13054 this._offsetLimit = toBounds(
13055 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
13056 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
13057 .add(this._map.getSize()));
13059 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
13061 this._offsetLimit = null;
13066 .fire('dragstart');
13068 if (map.options.inertia) {
13069 this._positions = [];
13074 _onDrag: function (e) {
13075 if (this._map.options.inertia) {
13076 var time = this._lastTime = +new Date(),
13077 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
13079 this._positions.push(pos);
13080 this._times.push(time);
13082 this._prunePositions(time);
13090 _prunePositions: function (time) {
13091 while (this._positions.length > 1 && time - this._times[0] > 50) {
13092 this._positions.shift();
13093 this._times.shift();
13097 _onZoomEnd: function () {
13098 var pxCenter = this._map.getSize().divideBy(2),
13099 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
13101 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
13102 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
13105 _viscousLimit: function (value, threshold) {
13106 return value - (value - threshold) * this._viscosity;
13109 _onPreDragLimit: function () {
13110 if (!this._viscosity || !this._offsetLimit) { return; }
13112 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
13114 var limit = this._offsetLimit;
13115 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
13116 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
13117 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
13118 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
13120 this._draggable._newPos = this._draggable._startPos.add(offset);
13123 _onPreDragWrap: function () {
13124 // TODO refactor to be able to adjust map pane position after zoom
13125 var worldWidth = this._worldWidth,
13126 halfWidth = Math.round(worldWidth / 2),
13127 dx = this._initialWorldOffset,
13128 x = this._draggable._newPos.x,
13129 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
13130 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
13131 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
13133 this._draggable._absPos = this._draggable._newPos.clone();
13134 this._draggable._newPos.x = newX;
13137 _onDragEnd: function (e) {
13138 var map = this._map,
13139 options = map.options,
13141 noInertia = !options.inertia || this._times.length < 2;
13143 map.fire('dragend', e);
13146 map.fire('moveend');
13149 this._prunePositions(+new Date());
13151 var direction = this._lastPos.subtract(this._positions[0]),
13152 duration = (this._lastTime - this._times[0]) / 1000,
13153 ease = options.easeLinearity,
13155 speedVector = direction.multiplyBy(ease / duration),
13156 speed = speedVector.distanceTo([0, 0]),
13158 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
13159 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
13161 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
13162 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
13164 if (!offset.x && !offset.y) {
13165 map.fire('moveend');
13168 offset = map._limitOffset(offset, map.options.maxBounds);
13170 requestAnimFrame(function () {
13171 map.panBy(offset, {
13172 duration: decelerationDuration,
13173 easeLinearity: ease,
13183 // @section Handlers
13184 // @property dragging: Handler
13185 // Map dragging handler (by both mouse and touch).
13186 Map.addInitHook('addHandler', 'dragging', Drag);
13189 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
13193 // @section Keyboard Navigation Options
13195 // @option keyboard: Boolean = true
13196 // Makes the map focusable and allows users to navigate the map with keyboard
13197 // arrows and `+`/`-` keys.
13200 // @option keyboardPanDelta: Number = 80
13201 // Amount of pixels to pan when pressing an arrow key.
13202 keyboardPanDelta: 80
13205 var Keyboard = Handler.extend({
13212 zoomIn: [187, 107, 61, 171],
13213 zoomOut: [189, 109, 54, 173]
13216 initialize: function (map) {
13219 this._setPanDelta(map.options.keyboardPanDelta);
13220 this._setZoomDelta(map.options.zoomDelta);
13223 addHooks: function () {
13224 var container = this._map._container;
13226 // make the container focusable by tabbing
13227 if (container.tabIndex <= 0) {
13228 container.tabIndex = '0';
13232 focus: this._onFocus,
13233 blur: this._onBlur,
13234 mousedown: this._onMouseDown
13238 focus: this._addHooks,
13239 blur: this._removeHooks
13243 removeHooks: function () {
13244 this._removeHooks();
13246 off(this._map._container, {
13247 focus: this._onFocus,
13248 blur: this._onBlur,
13249 mousedown: this._onMouseDown
13253 focus: this._addHooks,
13254 blur: this._removeHooks
13258 _onMouseDown: function () {
13259 if (this._focused) { return; }
13261 var body = document.body,
13262 docEl = document.documentElement,
13263 top = body.scrollTop || docEl.scrollTop,
13264 left = body.scrollLeft || docEl.scrollLeft;
13266 this._map._container.focus();
13268 window.scrollTo(left, top);
13271 _onFocus: function () {
13272 this._focused = true;
13273 this._map.fire('focus');
13276 _onBlur: function () {
13277 this._focused = false;
13278 this._map.fire('blur');
13281 _setPanDelta: function (panDelta) {
13282 var keys = this._panKeys = {},
13283 codes = this.keyCodes,
13286 for (i = 0, len = codes.left.length; i < len; i++) {
13287 keys[codes.left[i]] = [-1 * panDelta, 0];
13289 for (i = 0, len = codes.right.length; i < len; i++) {
13290 keys[codes.right[i]] = [panDelta, 0];
13292 for (i = 0, len = codes.down.length; i < len; i++) {
13293 keys[codes.down[i]] = [0, panDelta];
13295 for (i = 0, len = codes.up.length; i < len; i++) {
13296 keys[codes.up[i]] = [0, -1 * panDelta];
13300 _setZoomDelta: function (zoomDelta) {
13301 var keys = this._zoomKeys = {},
13302 codes = this.keyCodes,
13305 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
13306 keys[codes.zoomIn[i]] = zoomDelta;
13308 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
13309 keys[codes.zoomOut[i]] = -zoomDelta;
13313 _addHooks: function () {
13314 on(document, 'keydown', this._onKeyDown, this);
13317 _removeHooks: function () {
13318 off(document, 'keydown', this._onKeyDown, this);
13321 _onKeyDown: function (e) {
13322 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
13324 var key = e.keyCode,
13328 if (key in this._panKeys) {
13330 if (map._panAnim && map._panAnim._inProgress) { return; }
13332 offset = this._panKeys[key];
13334 offset = toPoint(offset).multiplyBy(3);
13339 if (map.options.maxBounds) {
13340 map.panInsideBounds(map.options.maxBounds);
13343 } else if (key in this._zoomKeys) {
13344 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
13346 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
13357 // @section Handlers
13358 // @section Handlers
13359 // @property keyboard: Handler
13360 // Keyboard navigation handler.
13361 Map.addInitHook('addHandler', 'keyboard', Keyboard);
13364 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
13368 // @section Interaction Options
13370 // @section Mousewheel options
13371 // @option scrollWheelZoom: Boolean|String = true
13372 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
13373 // it will zoom to the center of the view regardless of where the mouse was.
13374 scrollWheelZoom: true,
13376 // @option wheelDebounceTime: Number = 40
13377 // Limits the rate at which a wheel can fire (in milliseconds). By default
13378 // user can't zoom via wheel more often than once per 40 ms.
13379 wheelDebounceTime: 40,
13381 // @option wheelPxPerZoomLevel: Number = 60
13382 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
13383 // mean a change of one full zoom level. Smaller values will make wheel-zooming
13384 // faster (and vice versa).
13385 wheelPxPerZoomLevel: 60
13388 var ScrollWheelZoom = Handler.extend({
13389 addHooks: function () {
13390 on(this._map._container, 'mousewheel', this._onWheelScroll, this);
13395 removeHooks: function () {
13396 off(this._map._container, 'mousewheel', this._onWheelScroll, this);
13399 _onWheelScroll: function (e) {
13400 var delta = getWheelDelta(e);
13402 var debounce = this._map.options.wheelDebounceTime;
13404 this._delta += delta;
13405 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
13407 if (!this._startTime) {
13408 this._startTime = +new Date();
13411 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
13413 clearTimeout(this._timer);
13414 this._timer = setTimeout(bind(this._performZoom, this), left);
13419 _performZoom: function () {
13420 var map = this._map,
13421 zoom = map.getZoom(),
13422 snap = this._map.options.zoomSnap || 0;
13424 map._stop(); // stop panning and fly animations if any
13426 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
13427 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
13428 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
13429 d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
13430 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
13433 this._startTime = null;
13435 if (!delta) { return; }
13437 if (map.options.scrollWheelZoom === 'center') {
13438 map.setZoom(zoom + delta);
13440 map.setZoomAround(this._lastMousePos, zoom + delta);
13445 // @section Handlers
13446 // @property scrollWheelZoom: Handler
13447 // Scroll wheel zoom handler.
13448 Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
13451 * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
13455 // @section Interaction Options
13457 // @section Touch interaction options
13458 // @option tap: Boolean = true
13459 // Enables mobile hacks for supporting instant taps (fixing 200ms click
13460 // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
13463 // @option tapTolerance: Number = 15
13464 // The max number of pixels a user can shift his finger during touch
13465 // for it to be considered a valid tap.
13469 var Tap = Handler.extend({
13470 addHooks: function () {
13471 on(this._map._container, 'touchstart', this._onDown, this);
13474 removeHooks: function () {
13475 off(this._map._container, 'touchstart', this._onDown, this);
13478 _onDown: function (e) {
13479 if (!e.touches) { return; }
13483 this._fireClick = true;
13485 // don't simulate click or track longpress if more than 1 touch
13486 if (e.touches.length > 1) {
13487 this._fireClick = false;
13488 clearTimeout(this._holdTimeout);
13492 var first = e.touches[0],
13495 this._startPos = this._newPos = new Point(first.clientX, first.clientY);
13497 // if touching a link, highlight it
13498 if (el.tagName && el.tagName.toLowerCase() === 'a') {
13499 addClass(el, 'leaflet-active');
13502 // simulate long hold but setting a timeout
13503 this._holdTimeout = setTimeout(bind(function () {
13504 if (this._isTapValid()) {
13505 this._fireClick = false;
13507 this._simulateEvent('contextmenu', first);
13511 this._simulateEvent('mousedown', first);
13514 touchmove: this._onMove,
13515 touchend: this._onUp
13519 _onUp: function (e) {
13520 clearTimeout(this._holdTimeout);
13523 touchmove: this._onMove,
13524 touchend: this._onUp
13527 if (this._fireClick && e && e.changedTouches) {
13529 var first = e.changedTouches[0],
13532 if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
13533 removeClass(el, 'leaflet-active');
13536 this._simulateEvent('mouseup', first);
13538 // simulate click if the touch didn't move too much
13539 if (this._isTapValid()) {
13540 this._simulateEvent('click', first);
13545 _isTapValid: function () {
13546 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
13549 _onMove: function (e) {
13550 var first = e.touches[0];
13551 this._newPos = new Point(first.clientX, first.clientY);
13552 this._simulateEvent('mousemove', first);
13555 _simulateEvent: function (type, e) {
13556 var simulatedEvent = document.createEvent('MouseEvents');
13558 simulatedEvent._simulated = true;
13559 e.target._simulatedClick = true;
13561 simulatedEvent.initMouseEvent(
13562 type, true, true, window, 1,
13563 e.screenX, e.screenY,
13564 e.clientX, e.clientY,
13565 false, false, false, false, 0, null);
13567 e.target.dispatchEvent(simulatedEvent);
13571 // @section Handlers
13572 // @property tap: Handler
13573 // Mobile touch hacks (quick tap and touch hold) handler.
13574 if (touch && !pointer) {
13575 Map.addInitHook('addHandler', 'tap', Tap);
13579 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
13583 // @section Interaction Options
13585 // @section Touch interaction options
13586 // @option touchZoom: Boolean|String = *
13587 // Whether the map can be zoomed by touch-dragging with two fingers. If
13588 // passed `'center'`, it will zoom to the center of the view regardless of
13589 // where the touch events (fingers) were. Enabled for touch-capable web
13590 // browsers except for old Androids.
13591 touchZoom: touch && !android23,
13593 // @option bounceAtZoomLimits: Boolean = true
13594 // Set it to false if you don't want the map to zoom beyond min/max zoom
13595 // and then bounce back when pinch-zooming.
13596 bounceAtZoomLimits: true
13599 var TouchZoom = Handler.extend({
13600 addHooks: function () {
13601 addClass(this._map._container, 'leaflet-touch-zoom');
13602 on(this._map._container, 'touchstart', this._onTouchStart, this);
13605 removeHooks: function () {
13606 removeClass(this._map._container, 'leaflet-touch-zoom');
13607 off(this._map._container, 'touchstart', this._onTouchStart, this);
13610 _onTouchStart: function (e) {
13611 var map = this._map;
13612 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
13614 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
13615 p2 = map.mouseEventToContainerPoint(e.touches[1]);
13617 this._centerPoint = map.getSize()._divideBy(2);
13618 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
13619 if (map.options.touchZoom !== 'center') {
13620 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
13623 this._startDist = p1.distanceTo(p2);
13624 this._startZoom = map.getZoom();
13626 this._moved = false;
13627 this._zooming = true;
13631 on(document, 'touchmove', this._onTouchMove, this);
13632 on(document, 'touchend', this._onTouchEnd, this);
13637 _onTouchMove: function (e) {
13638 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
13640 var map = this._map,
13641 p1 = map.mouseEventToContainerPoint(e.touches[0]),
13642 p2 = map.mouseEventToContainerPoint(e.touches[1]),
13643 scale = p1.distanceTo(p2) / this._startDist;
13645 this._zoom = map.getScaleZoom(scale, this._startZoom);
13647 if (!map.options.bounceAtZoomLimits && (
13648 (this._zoom < map.getMinZoom() && scale < 1) ||
13649 (this._zoom > map.getMaxZoom() && scale > 1))) {
13650 this._zoom = map._limitZoom(this._zoom);
13653 if (map.options.touchZoom === 'center') {
13654 this._center = this._startLatLng;
13655 if (scale === 1) { return; }
13657 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
13658 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
13659 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
13660 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
13663 if (!this._moved) {
13664 map._moveStart(true, false);
13665 this._moved = true;
13668 cancelAnimFrame(this._animRequest);
13670 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
13671 this._animRequest = requestAnimFrame(moveFn, this, true);
13676 _onTouchEnd: function () {
13677 if (!this._moved || !this._zooming) {
13678 this._zooming = false;
13682 this._zooming = false;
13683 cancelAnimFrame(this._animRequest);
13685 off(document, 'touchmove', this._onTouchMove);
13686 off(document, 'touchend', this._onTouchEnd);
13688 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
13689 if (this._map.options.zoomAnimation) {
13690 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
13692 this._map._resetView(this._center, this._map._limitZoom(this._zoom));
13697 // @section Handlers
13698 // @property touchZoom: Handler
13699 // Touch zoom handler.
13700 Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
13702 Map.BoxZoom = BoxZoom;
13703 Map.DoubleClickZoom = DoubleClickZoom;
13705 Map.Keyboard = Keyboard;
13706 Map.ScrollWheelZoom = ScrollWheelZoom;
13708 Map.TouchZoom = TouchZoom;
13712 var oldL = window.L;
13713 function noConflict() {
13718 // Always export us to window global (see #2364)
13719 window.L = exports;
13721 Object.freeze = freeze;
13723 exports.version = version;
13724 exports.noConflict = noConflict;
13725 exports.Control = Control;
13726 exports.control = control;
13727 exports.Browser = Browser;
13728 exports.Evented = Evented;
13729 exports.Mixin = Mixin;
13730 exports.Util = Util;
13731 exports.Class = Class;
13732 exports.Handler = Handler;
13733 exports.extend = extend;
13734 exports.bind = bind;
13735 exports.stamp = stamp;
13736 exports.setOptions = setOptions;
13737 exports.DomEvent = DomEvent;
13738 exports.DomUtil = DomUtil;
13739 exports.PosAnimation = PosAnimation;
13740 exports.Draggable = Draggable;
13741 exports.LineUtil = LineUtil;
13742 exports.PolyUtil = PolyUtil;
13743 exports.Point = Point;
13744 exports.point = toPoint;
13745 exports.Bounds = Bounds;
13746 exports.bounds = toBounds;
13747 exports.Transformation = Transformation;
13748 exports.transformation = toTransformation;
13749 exports.Projection = index;
13750 exports.LatLng = LatLng;
13751 exports.latLng = toLatLng;
13752 exports.LatLngBounds = LatLngBounds;
13753 exports.latLngBounds = toLatLngBounds;
13755 exports.GeoJSON = GeoJSON;
13756 exports.geoJSON = geoJSON;
13757 exports.geoJson = geoJson;
13758 exports.Layer = Layer;
13759 exports.LayerGroup = LayerGroup;
13760 exports.layerGroup = layerGroup;
13761 exports.FeatureGroup = FeatureGroup;
13762 exports.featureGroup = featureGroup;
13763 exports.ImageOverlay = ImageOverlay;
13764 exports.imageOverlay = imageOverlay;
13765 exports.VideoOverlay = VideoOverlay;
13766 exports.videoOverlay = videoOverlay;
13767 exports.DivOverlay = DivOverlay;
13768 exports.Popup = Popup;
13769 exports.popup = popup;
13770 exports.Tooltip = Tooltip;
13771 exports.tooltip = tooltip;
13772 exports.Icon = Icon;
13773 exports.icon = icon;
13774 exports.DivIcon = DivIcon;
13775 exports.divIcon = divIcon;
13776 exports.Marker = Marker;
13777 exports.marker = marker;
13778 exports.TileLayer = TileLayer;
13779 exports.tileLayer = tileLayer;
13780 exports.GridLayer = GridLayer;
13781 exports.gridLayer = gridLayer;
13783 exports.svg = svg$1;
13784 exports.Renderer = Renderer;
13785 exports.Canvas = Canvas;
13786 exports.canvas = canvas$1;
13787 exports.Path = Path;
13788 exports.CircleMarker = CircleMarker;
13789 exports.circleMarker = circleMarker;
13790 exports.Circle = Circle;
13791 exports.circle = circle;
13792 exports.Polyline = Polyline;
13793 exports.polyline = polyline;
13794 exports.Polygon = Polygon;
13795 exports.polygon = polygon;
13796 exports.Rectangle = Rectangle;
13797 exports.rectangle = rectangle;
13799 exports.map = createMap;
13802 //# sourceMappingURL=leaflet-src.js.map