2 * Leaflet 1.3.2, a JS library for interactive maps. http://leafletjs.com
3 * (c) 2010-2018 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.2";
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 Leaflet'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 or on any screen when browser's display zoom is more than 100%.
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 DomUtil
2212 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
2213 * tree, used by Leaflet internally.
2215 * Most functions expecting or returning a `HTMLElement` also work for
2216 * SVG elements. The only difference is that classes refer to CSS classes
2217 * in HTML and SVG classes in SVG.
2221 // @property TRANSFORM: String
2222 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
2223 var TRANSFORM = testProp(
2224 ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
2226 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
2227 // the same for the transitionend event, in particular the Android 4.1 stock browser
2229 // @property TRANSITION: String
2230 // Vendor-prefixed transition style name.
2231 var TRANSITION = testProp(
2232 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
2234 // @property TRANSITION_END: String
2235 // Vendor-prefixed transitionend event name.
2236 var TRANSITION_END =
2237 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
2240 // @function get(id: String|HTMLElement): HTMLElement
2241 // Returns an element given its DOM id, or returns the element itself
2242 // if it was passed directly.
2244 return typeof id === 'string' ? document.getElementById(id) : id;
2247 // @function getStyle(el: HTMLElement, styleAttrib: String): String
2248 // Returns the value for a certain style attribute on an element,
2249 // including computed values or values set through CSS.
2250 function getStyle(el, style) {
2251 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
2253 if ((!value || value === 'auto') && document.defaultView) {
2254 var css = document.defaultView.getComputedStyle(el, null);
2255 value = css ? css[style] : null;
2257 return value === 'auto' ? null : value;
2260 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
2261 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
2262 function create$1(tagName, className, container) {
2263 var el = document.createElement(tagName);
2264 el.className = className || '';
2267 container.appendChild(el);
2272 // @function remove(el: HTMLElement)
2273 // Removes `el` from its parent element
2274 function remove(el) {
2275 var parent = el.parentNode;
2277 parent.removeChild(el);
2281 // @function empty(el: HTMLElement)
2282 // Removes all of `el`'s children elements from `el`
2283 function empty(el) {
2284 while (el.firstChild) {
2285 el.removeChild(el.firstChild);
2289 // @function toFront(el: HTMLElement)
2290 // Makes `el` the last child of its parent, so it renders in front of the other children.
2291 function toFront(el) {
2292 var parent = el.parentNode;
2293 if (parent.lastChild !== el) {
2294 parent.appendChild(el);
2298 // @function toBack(el: HTMLElement)
2299 // Makes `el` the first child of its parent, so it renders behind the other children.
2300 function toBack(el) {
2301 var parent = el.parentNode;
2302 if (parent.firstChild !== el) {
2303 parent.insertBefore(el, parent.firstChild);
2307 // @function hasClass(el: HTMLElement, name: String): Boolean
2308 // Returns `true` if the element's class attribute contains `name`.
2309 function hasClass(el, name) {
2310 if (el.classList !== undefined) {
2311 return el.classList.contains(name);
2313 var className = getClass(el);
2314 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
2317 // @function addClass(el: HTMLElement, name: String)
2318 // Adds `name` to the element's class attribute.
2319 function addClass(el, name) {
2320 if (el.classList !== undefined) {
2321 var classes = splitWords(name);
2322 for (var i = 0, len = classes.length; i < len; i++) {
2323 el.classList.add(classes[i]);
2325 } else if (!hasClass(el, name)) {
2326 var className = getClass(el);
2327 setClass(el, (className ? className + ' ' : '') + name);
2331 // @function removeClass(el: HTMLElement, name: String)
2332 // Removes `name` from the element's class attribute.
2333 function removeClass(el, name) {
2334 if (el.classList !== undefined) {
2335 el.classList.remove(name);
2337 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
2341 // @function setClass(el: HTMLElement, name: String)
2342 // Sets the element's class.
2343 function setClass(el, name) {
2344 if (el.className.baseVal === undefined) {
2345 el.className = name;
2347 // in case of SVG element
2348 el.className.baseVal = name;
2352 // @function getClass(el: HTMLElement): String
2353 // Returns the element's class.
2354 function getClass(el) {
2355 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
2358 // @function setOpacity(el: HTMLElement, opacity: Number)
2359 // Set the opacity of an element (including old IE support).
2360 // `opacity` must be a number from `0` to `1`.
2361 function setOpacity(el, value) {
2362 if ('opacity' in el.style) {
2363 el.style.opacity = value;
2364 } else if ('filter' in el.style) {
2365 _setOpacityIE(el, value);
2369 function _setOpacityIE(el, value) {
2371 filterName = 'DXImageTransform.Microsoft.Alpha';
2373 // filters collection throws an error if we try to retrieve a filter that doesn't exist
2375 filter = el.filters.item(filterName);
2377 // don't set opacity to 1 if we haven't already set an opacity,
2378 // it isn't needed and breaks transparent pngs.
2379 if (value === 1) { return; }
2382 value = Math.round(value * 100);
2385 filter.Enabled = (value !== 100);
2386 filter.Opacity = value;
2388 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
2392 // @function testProp(props: String[]): String|false
2393 // Goes through the array of style names and returns the first name
2394 // that is a valid style name for an element. If no such name is found,
2395 // it returns false. Useful for vendor-prefixed styles like `transform`.
2396 function testProp(props) {
2397 var style = document.documentElement.style;
2399 for (var i = 0; i < props.length; i++) {
2400 if (props[i] in style) {
2407 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
2408 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
2409 // and optionally scaled by `scale`. Does not have an effect if the
2410 // browser doesn't support 3D CSS transforms.
2411 function setTransform(el, offset, scale) {
2412 var pos = offset || new Point(0, 0);
2414 el.style[TRANSFORM] =
2416 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
2417 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
2418 (scale ? ' scale(' + scale + ')' : '');
2421 // @function setPosition(el: HTMLElement, position: Point)
2422 // Sets the position of `el` to coordinates specified by `position`,
2423 // using CSS translate or top/left positioning depending on the browser
2424 // (used by Leaflet internally to position its layers).
2425 function setPosition(el, point) {
2428 el._leaflet_pos = point;
2432 setTransform(el, point);
2434 el.style.left = point.x + 'px';
2435 el.style.top = point.y + 'px';
2439 // @function getPosition(el: HTMLElement): Point
2440 // Returns the coordinates of an element previously positioned with setPosition.
2441 function getPosition(el) {
2442 // this method is only used for elements previously positioned using setPosition,
2443 // so it's safe to cache the position for performance
2445 return el._leaflet_pos || new Point(0, 0);
2448 // @function disableTextSelection()
2449 // Prevents the user from generating `selectstart` DOM events, usually generated
2450 // when the user drags the mouse through a page with text. Used internally
2451 // by Leaflet to override the behaviour of any click-and-drag interaction on
2452 // the map. Affects drag interactions on the whole document.
2454 // @function enableTextSelection()
2455 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
2456 var disableTextSelection;
2457 var enableTextSelection;
2459 if ('onselectstart' in document) {
2460 disableTextSelection = function () {
2461 on(window, 'selectstart', preventDefault);
2463 enableTextSelection = function () {
2464 off(window, 'selectstart', preventDefault);
2467 var userSelectProperty = testProp(
2468 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
2470 disableTextSelection = function () {
2471 if (userSelectProperty) {
2472 var style = document.documentElement.style;
2473 _userSelect = style[userSelectProperty];
2474 style[userSelectProperty] = 'none';
2477 enableTextSelection = function () {
2478 if (userSelectProperty) {
2479 document.documentElement.style[userSelectProperty] = _userSelect;
2480 _userSelect = undefined;
2485 // @function disableImageDrag()
2486 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
2487 // for `dragstart` DOM events, usually generated when the user drags an image.
2488 function disableImageDrag() {
2489 on(window, 'dragstart', preventDefault);
2492 // @function enableImageDrag()
2493 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
2494 function enableImageDrag() {
2495 off(window, 'dragstart', preventDefault);
2498 var _outlineElement;
2500 // @function preventOutline(el: HTMLElement)
2501 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
2502 // of the element `el` invisible. Used internally by Leaflet to prevent
2503 // focusable elements from displaying an outline when the user performs a
2504 // drag interaction on them.
2505 function preventOutline(element) {
2506 while (element.tabIndex === -1) {
2507 element = element.parentNode;
2509 if (!element.style) { return; }
2511 _outlineElement = element;
2512 _outlineStyle = element.style.outline;
2513 element.style.outline = 'none';
2514 on(window, 'keydown', restoreOutline);
2517 // @function restoreOutline()
2518 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
2519 function restoreOutline() {
2520 if (!_outlineElement) { return; }
2521 _outlineElement.style.outline = _outlineStyle;
2522 _outlineElement = undefined;
2523 _outlineStyle = undefined;
2524 off(window, 'keydown', restoreOutline);
2527 // @function getSizedParentNode(el: HTMLElement): HTMLElement
2528 // Finds the closest parent node which size (width and height) is not null.
2529 function getSizedParentNode(element) {
2531 element = element.parentNode;
2532 } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
2536 // @function getScale(el: HTMLElement): Object
2537 // Computes the CSS scale currently applied on the element.
2538 // Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
2539 // and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
2540 function getScale(element) {
2541 var rect = element.getBoundingClientRect(); // Read-only in old browsers.
2544 x: rect.width / element.offsetWidth || 1,
2545 y: rect.height / element.offsetHeight || 1,
2546 boundingClientRect: rect
2551 var DomUtil = (Object.freeze || Object)({
2552 TRANSFORM: TRANSFORM,
2553 TRANSITION: TRANSITION,
2554 TRANSITION_END: TRANSITION_END,
2564 removeClass: removeClass,
2567 setOpacity: setOpacity,
2569 setTransform: setTransform,
2570 setPosition: setPosition,
2571 getPosition: getPosition,
2572 disableTextSelection: disableTextSelection,
2573 enableTextSelection: enableTextSelection,
2574 disableImageDrag: disableImageDrag,
2575 enableImageDrag: enableImageDrag,
2576 preventOutline: preventOutline,
2577 restoreOutline: restoreOutline,
2578 getSizedParentNode: getSizedParentNode,
2583 * @namespace DomEvent
2584 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
2587 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
2589 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
2590 // Adds a listener function (`fn`) to a particular DOM event type of the
2591 // element `el`. You can optionally specify the context of the listener
2592 // (object the `this` keyword will point to). You can also pass several
2593 // space-separated types (e.g. `'click dblclick'`).
2596 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
2597 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2598 function on(obj, types, fn, context) {
2600 if (typeof types === 'object') {
2601 for (var type in types) {
2602 addOne(obj, type, types[type], fn);
2605 types = splitWords(types);
2607 for (var i = 0, len = types.length; i < len; i++) {
2608 addOne(obj, types[i], fn, context);
2615 var eventsKey = '_leaflet_events';
2617 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
2618 // Removes a previously added listener function.
2619 // Note that if you passed a custom context to on, you must pass the same
2620 // context to `off` in order to remove the listener.
2623 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
2624 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2625 function off(obj, types, fn, context) {
2627 if (typeof types === 'object') {
2628 for (var type in types) {
2629 removeOne(obj, type, types[type], fn);
2632 types = splitWords(types);
2634 for (var i = 0, len = types.length; i < len; i++) {
2635 removeOne(obj, types[i], fn, context);
2638 for (var j in obj[eventsKey]) {
2639 removeOne(obj, j, obj[eventsKey][j]);
2641 delete obj[eventsKey];
2647 function addOne(obj, type, fn, context) {
2648 var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
2650 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
2652 var handler = function (e) {
2653 return fn.call(context || obj, e || window.event);
2656 var originalHandler = handler;
2658 if (pointer && type.indexOf('touch') === 0) {
2659 // Needs DomEvent.Pointer.js
2660 addPointerListener(obj, type, handler, id);
2662 } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
2663 !(pointer && chrome)) {
2664 // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
2666 addDoubleTapListener(obj, handler, id);
2668 } else if ('addEventListener' in obj) {
2670 if (type === 'mousewheel') {
2671 obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2673 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
2674 handler = function (e) {
2675 e = e || window.event;
2676 if (isExternalTarget(obj, e)) {
2680 obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
2683 if (type === 'click' && android) {
2684 handler = function (e) {
2685 filterClick(e, originalHandler);
2688 obj.addEventListener(type, handler, false);
2691 } else if ('attachEvent' in obj) {
2692 obj.attachEvent('on' + type, handler);
2695 obj[eventsKey] = obj[eventsKey] || {};
2696 obj[eventsKey][id] = handler;
2699 function removeOne(obj, type, fn, context) {
2701 var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
2702 handler = obj[eventsKey] && obj[eventsKey][id];
2704 if (!handler) { return this; }
2706 if (pointer && type.indexOf('touch') === 0) {
2707 removePointerListener(obj, type, id);
2709 } else if (touch && (type === 'dblclick') && removeDoubleTapListener &&
2710 !(pointer && chrome)) {
2711 removeDoubleTapListener(obj, id);
2713 } else if ('removeEventListener' in obj) {
2715 if (type === 'mousewheel') {
2716 obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2719 obj.removeEventListener(
2720 type === 'mouseenter' ? 'mouseover' :
2721 type === 'mouseleave' ? 'mouseout' : type, handler, false);
2724 } else if ('detachEvent' in obj) {
2725 obj.detachEvent('on' + type, handler);
2728 obj[eventsKey][id] = null;
2731 // @function stopPropagation(ev: DOMEvent): this
2732 // Stop the given event from propagation to parent elements. Used inside the listener functions:
2734 // L.DomEvent.on(div, 'click', function (ev) {
2735 // L.DomEvent.stopPropagation(ev);
2738 function stopPropagation(e) {
2740 if (e.stopPropagation) {
2741 e.stopPropagation();
2742 } else if (e.originalEvent) { // In case of Leaflet event.
2743 e.originalEvent._stopped = true;
2745 e.cancelBubble = true;
2752 // @function disableScrollPropagation(el: HTMLElement): this
2753 // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
2754 function disableScrollPropagation(el) {
2755 addOne(el, 'mousewheel', stopPropagation);
2759 // @function disableClickPropagation(el: HTMLElement): this
2760 // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
2761 // `'mousedown'` and `'touchstart'` events (plus browser variants).
2762 function disableClickPropagation(el) {
2763 on(el, 'mousedown touchstart dblclick', stopPropagation);
2764 addOne(el, 'click', fakeStop);
2768 // @function preventDefault(ev: DOMEvent): this
2769 // Prevents the default action of the DOM Event `ev` from happening (such as
2770 // following a link in the href of the a element, or doing a POST request
2771 // with page reload when a `<form>` is submitted).
2772 // Use it inside listener functions.
2773 function preventDefault(e) {
2774 if (e.preventDefault) {
2777 e.returnValue = false;
2782 // @function stop(ev: DOMEvent): this
2783 // Does `stopPropagation` and `preventDefault` at the same time.
2790 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
2791 // Gets normalized mouse position from a DOM event relative to the
2792 // `container` (border excluded) or to the whole page if not specified.
2793 function getMousePosition(e, container) {
2795 return new Point(e.clientX, e.clientY);
2798 var scale = getScale(container),
2799 offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y)
2802 // offset.left/top values are in page scale (like clientX/Y),
2803 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
2804 (e.clientX - offset.left) / scale.x - container.clientLeft,
2805 (e.clientY - offset.top) / scale.y - container.clientTop
2809 // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
2810 // and Firefox scrolls device pixels, not CSS pixels
2812 (win && chrome) ? 2 * window.devicePixelRatio :
2813 gecko ? window.devicePixelRatio : 1;
2815 // @function getWheelDelta(ev: DOMEvent): Number
2816 // Gets normalized wheel delta from a mousewheel DOM event, in vertical
2817 // pixels scrolled (negative if scrolling down).
2818 // Events from pointing devices without precise scrolling are mapped to
2819 // a best guess of 60 pixels.
2820 function getWheelDelta(e) {
2821 return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
2822 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
2823 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
2824 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
2825 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
2826 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
2827 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
2828 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
2832 var skipEvents = {};
2834 function fakeStop(e) {
2835 // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
2836 skipEvents[e.type] = true;
2839 function skipped(e) {
2840 var events = skipEvents[e.type];
2841 // reset when checking, as it's only used in map container and propagates outside of the map
2842 skipEvents[e.type] = false;
2846 // check if element really left/entered the event target (for mouseenter/mouseleave)
2847 function isExternalTarget(el, e) {
2849 var related = e.relatedTarget;
2851 if (!related) { return true; }
2854 while (related && (related !== el)) {
2855 related = related.parentNode;
2860 return (related !== el);
2865 // this is a horrible workaround for a bug in Android where a single touch triggers two click events
2866 function filterClick(e, handler) {
2867 var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
2868 elapsed = lastClick && (timeStamp - lastClick);
2870 // are they closer together than 500ms yet more than 100ms?
2871 // Android typically triggers them ~300ms apart while multiple listeners
2872 // on the same event should be triggered far faster;
2873 // or check if click is simulated on the element, and if it is, reject any non-simulated events
2875 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
2879 lastClick = timeStamp;
2887 var DomEvent = (Object.freeze || Object)({
2890 stopPropagation: stopPropagation,
2891 disableScrollPropagation: disableScrollPropagation,
2892 disableClickPropagation: disableClickPropagation,
2893 preventDefault: preventDefault,
2895 getMousePosition: getMousePosition,
2896 getWheelDelta: getWheelDelta,
2899 isExternalTarget: isExternalTarget,
2905 * @class PosAnimation
2906 * @aka L.PosAnimation
2908 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
2912 * var fx = new L.PosAnimation();
2913 * fx.run(el, [300, 500], 0.5);
2916 * @constructor L.PosAnimation()
2917 * Creates a `PosAnimation` object.
2921 var PosAnimation = Evented.extend({
2923 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
2924 // Run an animation of a given element to a new position, optionally setting
2925 // duration in seconds (`0.25` by default) and easing linearity factor (3rd
2926 // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
2927 // `0.5` by default).
2928 run: function (el, newPos, duration, easeLinearity) {
2932 this._inProgress = true;
2933 this._duration = duration || 0.25;
2934 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
2936 this._startPos = getPosition(el);
2937 this._offset = newPos.subtract(this._startPos);
2938 this._startTime = +new Date();
2940 // @event start: Event
2941 // Fired when the animation starts
2948 // Stops the animation (if currently running).
2950 if (!this._inProgress) { return; }
2956 _animate: function () {
2958 this._animId = requestAnimFrame(this._animate, this);
2962 _step: function (round) {
2963 var elapsed = (+new Date()) - this._startTime,
2964 duration = this._duration * 1000;
2966 if (elapsed < duration) {
2967 this._runFrame(this._easeOut(elapsed / duration), round);
2974 _runFrame: function (progress, round) {
2975 var pos = this._startPos.add(this._offset.multiplyBy(progress));
2979 setPosition(this._el, pos);
2981 // @event step: Event
2982 // Fired continuously during the animation.
2986 _complete: function () {
2987 cancelAnimFrame(this._animId);
2989 this._inProgress = false;
2990 // @event end: Event
2991 // Fired when the animation ends.
2995 _easeOut: function (t) {
2996 return 1 - Math.pow(1 - t, this._easeOutPower);
3005 * The central class of the API — it is used to create a map on a page and manipulate it.
3010 * // initialize the map on the "map" div with a given center and zoom
3011 * var map = L.map('map', {
3012 * center: [51.505, -0.09],
3019 var Map = Evented.extend({
3022 // @section Map State Options
3023 // @option crs: CRS = L.CRS.EPSG3857
3024 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
3025 // sure what it means.
3028 // @option center: LatLng = undefined
3029 // Initial geographic center of the map
3032 // @option zoom: Number = undefined
3033 // Initial map zoom level
3036 // @option minZoom: Number = *
3037 // Minimum zoom level of the map.
3038 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3039 // the lowest of their `minZoom` options will be used instead.
3042 // @option maxZoom: Number = *
3043 // Maximum zoom level of the map.
3044 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3045 // the highest of their `maxZoom` options will be used instead.
3048 // @option layers: Layer[] = []
3049 // Array of layers that will be added to the map initially
3052 // @option maxBounds: LatLngBounds = null
3053 // When this option is set, the map restricts the view to the given
3054 // geographical bounds, bouncing the user back if the user tries to pan
3055 // outside the view. To set the restriction dynamically, use
3056 // [`setMaxBounds`](#map-setmaxbounds) method.
3057 maxBounds: undefined,
3059 // @option renderer: Renderer = *
3060 // The default method for drawing vector layers on the map. `L.SVG`
3061 // or `L.Canvas` by default depending on browser support.
3062 renderer: undefined,
3065 // @section Animation Options
3066 // @option zoomAnimation: Boolean = true
3067 // Whether the map zoom animation is enabled. By default it's enabled
3068 // in all browsers that support CSS3 Transitions except Android.
3069 zoomAnimation: true,
3071 // @option zoomAnimationThreshold: Number = 4
3072 // Won't animate zoom if the zoom difference exceeds this value.
3073 zoomAnimationThreshold: 4,
3075 // @option fadeAnimation: Boolean = true
3076 // Whether the tile fade animation is enabled. By default it's enabled
3077 // in all browsers that support CSS3 Transitions except Android.
3078 fadeAnimation: true,
3080 // @option markerZoomAnimation: Boolean = true
3081 // Whether markers animate their zoom with the zoom animation, if disabled
3082 // they will disappear for the length of the animation. By default it's
3083 // enabled in all browsers that support CSS3 Transitions except Android.
3084 markerZoomAnimation: true,
3086 // @option transform3DLimit: Number = 2^23
3087 // Defines the maximum size of a CSS translation transform. The default
3088 // value should not be changed unless a web browser positions layers in
3089 // the wrong place after doing a large `panBy`.
3090 transform3DLimit: 8388608, // Precision limit of a 32-bit float
3092 // @section Interaction Options
3093 // @option zoomSnap: Number = 1
3094 // Forces the map's zoom level to always be a multiple of this, particularly
3095 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
3096 // By default, the zoom level snaps to the nearest integer; lower values
3097 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
3098 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
3101 // @option zoomDelta: Number = 1
3102 // Controls how much the map's zoom level will change after a
3103 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
3104 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
3105 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
3108 // @option trackResize: Boolean = true
3109 // Whether the map automatically handles browser window resize to update itself.
3113 initialize: function (id, options) { // (HTMLElement or String, Object)
3114 options = setOptions(this, options);
3116 this._initContainer(id);
3119 // hack for https://github.com/Leaflet/Leaflet/issues/1980
3120 this._onResize = bind(this._onResize, this);
3124 if (options.maxBounds) {
3125 this.setMaxBounds(options.maxBounds);
3128 if (options.zoom !== undefined) {
3129 this._zoom = this._limitZoom(options.zoom);
3132 if (options.center && options.zoom !== undefined) {
3133 this.setView(toLatLng(options.center), options.zoom, {reset: true});
3136 this._handlers = [];
3138 this._zoomBoundLayers = {};
3139 this._sizeChanged = true;
3141 this.callInitHooks();
3143 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
3144 this._zoomAnimated = TRANSITION && any3d && !mobileOpera &&
3145 this.options.zoomAnimation;
3147 // zoom transitions run with the same duration for all layers, so if one of transitionend events
3148 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
3149 if (this._zoomAnimated) {
3150 this._createAnimProxy();
3151 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
3154 this._addLayers(this.options.layers);
3158 // @section Methods for modifying map state
3160 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
3161 // Sets the view of the map (geographical center and zoom) with the given
3162 // animation options.
3163 setView: function (center, zoom, options) {
3165 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
3166 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
3167 options = options || {};
3171 if (this._loaded && !options.reset && options !== true) {
3173 if (options.animate !== undefined) {
3174 options.zoom = extend({animate: options.animate}, options.zoom);
3175 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
3178 // try animating pan or zoom
3179 var moved = (this._zoom !== zoom) ?
3180 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
3181 this._tryAnimatedPan(center, options.pan);
3184 // prevent resize handler call, the view will refresh after animation anyway
3185 clearTimeout(this._sizeTimer);
3190 // animation didn't start, just reset the map view
3191 this._resetView(center, zoom);
3196 // @method setZoom(zoom: Number, options?: Zoom/pan options): this
3197 // Sets the zoom of the map.
3198 setZoom: function (zoom, options) {
3199 if (!this._loaded) {
3203 return this.setView(this.getCenter(), zoom, {zoom: options});
3206 // @method zoomIn(delta?: Number, options?: Zoom options): this
3207 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3208 zoomIn: function (delta, options) {
3209 delta = delta || (any3d ? this.options.zoomDelta : 1);
3210 return this.setZoom(this._zoom + delta, options);
3213 // @method zoomOut(delta?: Number, options?: Zoom options): this
3214 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3215 zoomOut: function (delta, options) {
3216 delta = delta || (any3d ? this.options.zoomDelta : 1);
3217 return this.setZoom(this._zoom - delta, options);
3220 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
3221 // Zooms the map while keeping a specified geographical point on the map
3222 // stationary (e.g. used internally for scroll zoom and double-click zoom).
3224 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
3225 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
3226 setZoomAround: function (latlng, zoom, options) {
3227 var scale = this.getZoomScale(zoom),
3228 viewHalf = this.getSize().divideBy(2),
3229 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
3231 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
3232 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
3234 return this.setView(newCenter, zoom, {zoom: options});
3237 _getBoundsCenterZoom: function (bounds, options) {
3239 options = options || {};
3240 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
3242 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3243 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3245 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
3247 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
3249 if (zoom === Infinity) {
3251 center: bounds.getCenter(),
3256 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
3258 swPoint = this.project(bounds.getSouthWest(), zoom),
3259 nePoint = this.project(bounds.getNorthEast(), zoom),
3260 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
3268 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
3269 // Sets a map view that contains the given geographical bounds with the
3270 // maximum zoom level possible.
3271 fitBounds: function (bounds, options) {
3273 bounds = toLatLngBounds(bounds);
3275 if (!bounds.isValid()) {
3276 throw new Error('Bounds are not valid.');
3279 var target = this._getBoundsCenterZoom(bounds, options);
3280 return this.setView(target.center, target.zoom, options);
3283 // @method fitWorld(options?: fitBounds options): this
3284 // Sets a map view that mostly contains the whole world with the maximum
3285 // zoom level possible.
3286 fitWorld: function (options) {
3287 return this.fitBounds([[-90, -180], [90, 180]], options);
3290 // @method panTo(latlng: LatLng, options?: Pan options): this
3291 // Pans the map to a given center.
3292 panTo: function (center, options) { // (LatLng)
3293 return this.setView(center, this._zoom, {pan: options});
3296 // @method panBy(offset: Point, options?: Pan options): this
3297 // Pans the map by a given number of pixels (animated).
3298 panBy: function (offset, options) {
3299 offset = toPoint(offset).round();
3300 options = options || {};
3302 if (!offset.x && !offset.y) {
3303 return this.fire('moveend');
3305 // If we pan too far, Chrome gets issues with tiles
3306 // and makes them disappear or appear in the wrong place (slightly offset) #2602
3307 if (options.animate !== true && !this.getSize().contains(offset)) {
3308 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
3312 if (!this._panAnim) {
3313 this._panAnim = new PosAnimation();
3316 'step': this._onPanTransitionStep,
3317 'end': this._onPanTransitionEnd
3321 // don't fire movestart if animating inertia
3322 if (!options.noMoveStart) {
3323 this.fire('movestart');
3326 // animate pan unless animate: false specified
3327 if (options.animate !== false) {
3328 addClass(this._mapPane, 'leaflet-pan-anim');
3330 var newPos = this._getMapPanePos().subtract(offset).round();
3331 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
3333 this._rawPanBy(offset);
3334 this.fire('move').fire('moveend');
3340 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
3341 // Sets the view of the map (geographical center and zoom) performing a smooth
3342 // pan-zoom animation.
3343 flyTo: function (targetCenter, targetZoom, options) {
3345 options = options || {};
3346 if (options.animate === false || !any3d) {
3347 return this.setView(targetCenter, targetZoom, options);
3352 var from = this.project(this.getCenter()),
3353 to = this.project(targetCenter),
3354 size = this.getSize(),
3355 startZoom = this._zoom;
3357 targetCenter = toLatLng(targetCenter);
3358 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
3360 var w0 = Math.max(size.x, size.y),
3361 w1 = w0 * this.getZoomScale(startZoom, targetZoom),
3362 u1 = (to.distanceTo(from)) || 1,
3367 var s1 = i ? -1 : 1,
3369 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
3370 b1 = 2 * s2 * rho2 * u1,
3372 sq = Math.sqrt(b * b + 1) - b;
3374 // workaround for floating point precision bug when sq = 0, log = -Infinite,
3375 // thus triggering an infinite loop in flyTo
3376 var log = sq < 0.000000001 ? -18 : Math.log(sq);
3381 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
3382 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
3383 function tanh(n) { return sinh(n) / cosh(n); }
3387 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
3388 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
3390 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
3392 var start = Date.now(),
3393 S = (r(1) - r0) / rho,
3394 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
3397 var t = (Date.now() - start) / duration,
3401 this._flyToFrame = requestAnimFrame(frame, this);
3404 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
3405 this.getScaleZoom(w0 / w(s), startZoom),
3410 ._move(targetCenter, targetZoom)
3415 this._moveStart(true, options.noMoveStart);
3421 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
3422 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
3423 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
3424 flyToBounds: function (bounds, options) {
3425 var target = this._getBoundsCenterZoom(bounds, options);
3426 return this.flyTo(target.center, target.zoom, options);
3429 // @method setMaxBounds(bounds: Bounds): this
3430 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
3431 setMaxBounds: function (bounds) {
3432 bounds = toLatLngBounds(bounds);
3434 if (!bounds.isValid()) {
3435 this.options.maxBounds = null;
3436 return this.off('moveend', this._panInsideMaxBounds);
3437 } else if (this.options.maxBounds) {
3438 this.off('moveend', this._panInsideMaxBounds);
3441 this.options.maxBounds = bounds;
3444 this._panInsideMaxBounds();
3447 return this.on('moveend', this._panInsideMaxBounds);
3450 // @method setMinZoom(zoom: Number): this
3451 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
3452 setMinZoom: function (zoom) {
3453 var oldZoom = this.options.minZoom;
3454 this.options.minZoom = zoom;
3456 if (this._loaded && oldZoom !== zoom) {
3457 this.fire('zoomlevelschange');
3459 if (this.getZoom() < this.options.minZoom) {
3460 return this.setZoom(zoom);
3467 // @method setMaxZoom(zoom: Number): this
3468 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
3469 setMaxZoom: function (zoom) {
3470 var oldZoom = this.options.maxZoom;
3471 this.options.maxZoom = zoom;
3473 if (this._loaded && oldZoom !== zoom) {
3474 this.fire('zoomlevelschange');
3476 if (this.getZoom() > this.options.maxZoom) {
3477 return this.setZoom(zoom);
3484 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
3485 // 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.
3486 panInsideBounds: function (bounds, options) {
3487 this._enforcingBounds = true;
3488 var center = this.getCenter(),
3489 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
3491 if (!center.equals(newCenter)) {
3492 this.panTo(newCenter, options);
3495 this._enforcingBounds = false;
3499 // @method invalidateSize(options: Zoom/pan options): this
3500 // Checks if the map container size changed and updates the map if so —
3501 // call it after you've changed the map size dynamically, also animating
3502 // pan by default. If `options.pan` is `false`, panning will not occur.
3503 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
3504 // that it doesn't happen often even if the method is called many
3508 // @method invalidateSize(animate: Boolean): this
3509 // Checks if the map container size changed and updates the map if so —
3510 // call it after you've changed the map size dynamically, also animating
3512 invalidateSize: function (options) {
3513 if (!this._loaded) { return this; }
3518 }, options === true ? {animate: true} : options);
3520 var oldSize = this.getSize();
3521 this._sizeChanged = true;
3522 this._lastCenter = null;
3524 var newSize = this.getSize(),
3525 oldCenter = oldSize.divideBy(2).round(),
3526 newCenter = newSize.divideBy(2).round(),
3527 offset = oldCenter.subtract(newCenter);
3529 if (!offset.x && !offset.y) { return this; }
3531 if (options.animate && options.pan) {
3536 this._rawPanBy(offset);
3541 if (options.debounceMoveend) {
3542 clearTimeout(this._sizeTimer);
3543 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
3545 this.fire('moveend');
3549 // @section Map state change events
3550 // @event resize: ResizeEvent
3551 // Fired when the map is resized.
3552 return this.fire('resize', {
3558 // @section Methods for modifying map state
3559 // @method stop(): this
3560 // Stops the currently running `panTo` or `flyTo` animation, if any.
3562 this.setZoom(this._limitZoom(this._zoom));
3563 if (!this.options.zoomSnap) {
3564 this.fire('viewreset');
3566 return this._stop();
3569 // @section Geolocation methods
3570 // @method locate(options?: Locate options): this
3571 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
3572 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
3573 // and optionally sets the map view to the user's location with respect to
3574 // detection accuracy (or to the world view if geolocation failed).
3575 // Note that, if your page doesn't use HTTPS, this method will fail in
3576 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
3577 // See `Locate options` for more details.
3578 locate: function (options) {
3580 options = this._locateOptions = extend({
3584 // maxZoom: <Number>
3586 // enableHighAccuracy: false
3589 if (!('geolocation' in navigator)) {
3590 this._handleGeolocationError({
3592 message: 'Geolocation not supported.'
3597 var onResponse = bind(this._handleGeolocationResponse, this),
3598 onError = bind(this._handleGeolocationError, this);
3600 if (options.watch) {
3601 this._locationWatchId =
3602 navigator.geolocation.watchPosition(onResponse, onError, options);
3604 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
3609 // @method stopLocate(): this
3610 // Stops watching location previously initiated by `map.locate({watch: true})`
3611 // and aborts resetting the map view if map.locate was called with
3612 // `{setView: true}`.
3613 stopLocate: function () {
3614 if (navigator.geolocation && navigator.geolocation.clearWatch) {
3615 navigator.geolocation.clearWatch(this._locationWatchId);
3617 if (this._locateOptions) {
3618 this._locateOptions.setView = false;
3623 _handleGeolocationError: function (error) {
3625 message = error.message ||
3626 (c === 1 ? 'permission denied' :
3627 (c === 2 ? 'position unavailable' : 'timeout'));
3629 if (this._locateOptions.setView && !this._loaded) {
3633 // @section Location events
3634 // @event locationerror: ErrorEvent
3635 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
3636 this.fire('locationerror', {
3638 message: 'Geolocation error: ' + message + '.'
3642 _handleGeolocationResponse: function (pos) {
3643 var lat = pos.coords.latitude,
3644 lng = pos.coords.longitude,
3645 latlng = new LatLng(lat, lng),
3646 bounds = latlng.toBounds(pos.coords.accuracy * 2),
3647 options = this._locateOptions;
3649 if (options.setView) {
3650 var zoom = this.getBoundsZoom(bounds);
3651 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
3657 timestamp: pos.timestamp
3660 for (var i in pos.coords) {
3661 if (typeof pos.coords[i] === 'number') {
3662 data[i] = pos.coords[i];
3666 // @event locationfound: LocationEvent
3667 // Fired when geolocation (using the [`locate`](#map-locate) method)
3668 // went successfully.
3669 this.fire('locationfound', data);
3672 // TODO Appropriate docs section?
3673 // @section Other Methods
3674 // @method addHandler(name: String, HandlerClass: Function): this
3675 // Adds a new `Handler` to the map, given its name and constructor function.
3676 addHandler: function (name, HandlerClass) {
3677 if (!HandlerClass) { return this; }
3679 var handler = this[name] = new HandlerClass(this);
3681 this._handlers.push(handler);
3683 if (this.options[name]) {
3690 // @method remove(): this
3691 // Destroys the map and clears all related event listeners.
3692 remove: function () {
3694 this._initEvents(true);
3696 if (this._containerId !== this._container._leaflet_id) {
3697 throw new Error('Map container is being reused by another instance');
3701 // throws error in IE6-8
3702 delete this._container._leaflet_id;
3703 delete this._containerId;
3706 this._container._leaflet_id = undefined;
3708 this._containerId = undefined;
3711 if (this._locationWatchId !== undefined) {
3717 remove(this._mapPane);
3719 if (this._clearControlPos) {
3720 this._clearControlPos();
3722 if (this._resizeRequest) {
3723 cancelAnimFrame(this._resizeRequest);
3724 this._resizeRequest = null;
3727 this._clearHandlers();
3730 // @section Map state change events
3731 // @event unload: Event
3732 // Fired when the map is destroyed with [remove](#map-remove) method.
3733 this.fire('unload');
3737 for (i in this._layers) {
3738 this._layers[i].remove();
3740 for (i in this._panes) {
3741 remove(this._panes[i]);
3746 delete this._mapPane;
3747 delete this._renderer;
3752 // @section Other Methods
3753 // @method createPane(name: String, container?: HTMLElement): HTMLElement
3754 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
3755 // then returns it. The pane is created as a child of `container`, or
3756 // as a child of the main map pane if not set.
3757 createPane: function (name, container) {
3758 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3759 pane = create$1('div', className, container || this._mapPane);
3762 this._panes[name] = pane;
3767 // @section Methods for Getting Map State
3769 // @method getCenter(): LatLng
3770 // Returns the geographical center of the map view
3771 getCenter: function () {
3772 this._checkIfLoaded();
3774 if (this._lastCenter && !this._moved()) {
3775 return this._lastCenter;
3777 return this.layerPointToLatLng(this._getCenterLayerPoint());
3780 // @method getZoom(): Number
3781 // Returns the current zoom level of the map view
3782 getZoom: function () {
3786 // @method getBounds(): LatLngBounds
3787 // Returns the geographical bounds visible in the current map view
3788 getBounds: function () {
3789 var bounds = this.getPixelBounds(),
3790 sw = this.unproject(bounds.getBottomLeft()),
3791 ne = this.unproject(bounds.getTopRight());
3793 return new LatLngBounds(sw, ne);
3796 // @method getMinZoom(): Number
3797 // 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.
3798 getMinZoom: function () {
3799 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3802 // @method getMaxZoom(): Number
3803 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3804 getMaxZoom: function () {
3805 return this.options.maxZoom === undefined ?
3806 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3807 this.options.maxZoom;
3810 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
3811 // Returns the maximum zoom level on which the given bounds fit to the map
3812 // view in its entirety. If `inside` (optional) is set to `true`, the method
3813 // instead returns the minimum zoom level on which the map view fits into
3814 // the given bounds in its entirety.
3815 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3816 bounds = toLatLngBounds(bounds);
3817 padding = toPoint(padding || [0, 0]);
3819 var zoom = this.getZoom() || 0,
3820 min = this.getMinZoom(),
3821 max = this.getMaxZoom(),
3822 nw = bounds.getNorthWest(),
3823 se = bounds.getSouthEast(),
3824 size = this.getSize().subtract(padding),
3825 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3826 snap = any3d ? this.options.zoomSnap : 1,
3827 scalex = size.x / boundsSize.x,
3828 scaley = size.y / boundsSize.y,
3829 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
3831 zoom = this.getScaleZoom(scale, zoom);
3834 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3835 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3838 return Math.max(min, Math.min(max, zoom));
3841 // @method getSize(): Point
3842 // Returns the current size of the map container (in pixels).
3843 getSize: function () {
3844 if (!this._size || this._sizeChanged) {
3845 this._size = new Point(
3846 this._container.clientWidth || 0,
3847 this._container.clientHeight || 0);
3849 this._sizeChanged = false;
3851 return this._size.clone();
3854 // @method getPixelBounds(): Bounds
3855 // Returns the bounds of the current map view in projected pixel
3856 // coordinates (sometimes useful in layer and overlay implementations).
3857 getPixelBounds: function (center, zoom) {
3858 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3859 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3862 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3863 // the map pane? "left point of the map layer" can be confusing, specially
3864 // since there can be negative offsets.
3865 // @method getPixelOrigin(): Point
3866 // Returns the projected pixel coordinates of the top left point of
3867 // the map layer (useful in custom layer and overlay implementations).
3868 getPixelOrigin: function () {
3869 this._checkIfLoaded();
3870 return this._pixelOrigin;
3873 // @method getPixelWorldBounds(zoom?: Number): Bounds
3874 // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3875 // If `zoom` is omitted, the map's current zoom level is used.
3876 getPixelWorldBounds: function (zoom) {
3877 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3880 // @section Other Methods
3882 // @method getPane(pane: String|HTMLElement): HTMLElement
3883 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3884 getPane: function (pane) {
3885 return typeof pane === 'string' ? this._panes[pane] : pane;
3888 // @method getPanes(): Object
3889 // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3890 // the panes as values.
3891 getPanes: function () {
3895 // @method getContainer: HTMLElement
3896 // Returns the HTML element that contains the map.
3897 getContainer: function () {
3898 return this._container;
3902 // @section Conversion Methods
3904 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3905 // Returns the scale factor to be applied to a map transition from zoom level
3906 // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3907 getZoomScale: function (toZoom, fromZoom) {
3908 // TODO replace with universal implementation after refactoring projections
3909 var crs = this.options.crs;
3910 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3911 return crs.scale(toZoom) / crs.scale(fromZoom);
3914 // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3915 // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3916 // level and everything is scaled by a factor of `scale`. Inverse of
3917 // [`getZoomScale`](#map-getZoomScale).
3918 getScaleZoom: function (scale, fromZoom) {
3919 var crs = this.options.crs;
3920 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3921 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3922 return isNaN(zoom) ? Infinity : zoom;
3925 // @method project(latlng: LatLng, zoom: Number): Point
3926 // Projects a geographical coordinate `LatLng` according to the projection
3927 // of the map's CRS, then scales it according to `zoom` and the CRS's
3928 // `Transformation`. The result is pixel coordinate relative to
3930 project: function (latlng, zoom) {
3931 zoom = zoom === undefined ? this._zoom : zoom;
3932 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
3935 // @method unproject(point: Point, zoom: Number): LatLng
3936 // Inverse of [`project`](#map-project).
3937 unproject: function (point, zoom) {
3938 zoom = zoom === undefined ? this._zoom : zoom;
3939 return this.options.crs.pointToLatLng(toPoint(point), zoom);
3942 // @method layerPointToLatLng(point: Point): LatLng
3943 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3944 // returns the corresponding geographical coordinate (for the current zoom level).
3945 layerPointToLatLng: function (point) {
3946 var projectedPoint = toPoint(point).add(this.getPixelOrigin());
3947 return this.unproject(projectedPoint);
3950 // @method latLngToLayerPoint(latlng: LatLng): Point
3951 // Given a geographical coordinate, returns the corresponding pixel coordinate
3952 // relative to the [origin pixel](#map-getpixelorigin).
3953 latLngToLayerPoint: function (latlng) {
3954 var projectedPoint = this.project(toLatLng(latlng))._round();
3955 return projectedPoint._subtract(this.getPixelOrigin());
3958 // @method wrapLatLng(latlng: LatLng): LatLng
3959 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
3960 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
3962 // By default this means longitude is wrapped around the dateline so its
3963 // value is between -180 and +180 degrees.
3964 wrapLatLng: function (latlng) {
3965 return this.options.crs.wrapLatLng(toLatLng(latlng));
3968 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
3969 // Returns a `LatLngBounds` with the same size as the given one, ensuring that
3970 // its center is within the CRS's bounds.
3971 // By default this means the center longitude is wrapped around the dateline so its
3972 // value is between -180 and +180 degrees, and the majority of the bounds
3973 // overlaps the CRS's bounds.
3974 wrapLatLngBounds: function (latlng) {
3975 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
3978 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
3979 // Returns the distance between two geographical coordinates according to
3980 // the map's CRS. By default this measures distance in meters.
3981 distance: function (latlng1, latlng2) {
3982 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
3985 // @method containerPointToLayerPoint(point: Point): Point
3986 // Given a pixel coordinate relative to the map container, returns the corresponding
3987 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
3988 containerPointToLayerPoint: function (point) { // (Point)
3989 return toPoint(point).subtract(this._getMapPanePos());
3992 // @method layerPointToContainerPoint(point: Point): Point
3993 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3994 // returns the corresponding pixel coordinate relative to the map container.
3995 layerPointToContainerPoint: function (point) { // (Point)
3996 return toPoint(point).add(this._getMapPanePos());
3999 // @method containerPointToLatLng(point: Point): LatLng
4000 // Given a pixel coordinate relative to the map container, returns
4001 // the corresponding geographical coordinate (for the current zoom level).
4002 containerPointToLatLng: function (point) {
4003 var layerPoint = this.containerPointToLayerPoint(toPoint(point));
4004 return this.layerPointToLatLng(layerPoint);
4007 // @method latLngToContainerPoint(latlng: LatLng): Point
4008 // Given a geographical coordinate, returns the corresponding pixel coordinate
4009 // relative to the map container.
4010 latLngToContainerPoint: function (latlng) {
4011 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
4014 // @method mouseEventToContainerPoint(ev: MouseEvent): Point
4015 // Given a MouseEvent object, returns the pixel coordinate relative to the
4016 // map container where the event took place.
4017 mouseEventToContainerPoint: function (e) {
4018 return getMousePosition(e, this._container);
4021 // @method mouseEventToLayerPoint(ev: MouseEvent): Point
4022 // Given a MouseEvent object, returns the pixel coordinate relative to
4023 // the [origin pixel](#map-getpixelorigin) where the event took place.
4024 mouseEventToLayerPoint: function (e) {
4025 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
4028 // @method mouseEventToLatLng(ev: MouseEvent): LatLng
4029 // Given a MouseEvent object, returns geographical coordinate where the
4030 // event took place.
4031 mouseEventToLatLng: function (e) { // (MouseEvent)
4032 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
4036 // map initialization methods
4038 _initContainer: function (id) {
4039 var container = this._container = get(id);
4042 throw new Error('Map container not found.');
4043 } else if (container._leaflet_id) {
4044 throw new Error('Map container is already initialized.');
4047 on(container, 'scroll', this._onScroll, this);
4048 this._containerId = stamp(container);
4051 _initLayout: function () {
4052 var container = this._container;
4054 this._fadeAnimated = this.options.fadeAnimation && any3d;
4056 addClass(container, 'leaflet-container' +
4057 (touch ? ' leaflet-touch' : '') +
4058 (retina ? ' leaflet-retina' : '') +
4059 (ielt9 ? ' leaflet-oldie' : '') +
4060 (safari ? ' leaflet-safari' : '') +
4061 (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
4063 var position = getStyle(container, 'position');
4065 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
4066 container.style.position = 'relative';
4071 if (this._initControlPos) {
4072 this._initControlPos();
4076 _initPanes: function () {
4077 var panes = this._panes = {};
4078 this._paneRenderers = {};
4082 // Panes are DOM elements used to control the ordering of layers on the map. You
4083 // can access panes with [`map.getPane`](#map-getpane) or
4084 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
4085 // [`map.createPane`](#map-createpane) method.
4087 // Every map has the following default panes that differ only in zIndex.
4089 // @pane mapPane: HTMLElement = 'auto'
4090 // Pane that contains all other map panes
4092 this._mapPane = this.createPane('mapPane', this._container);
4093 setPosition(this._mapPane, new Point(0, 0));
4095 // @pane tilePane: HTMLElement = 200
4096 // Pane for `GridLayer`s and `TileLayer`s
4097 this.createPane('tilePane');
4098 // @pane overlayPane: HTMLElement = 400
4099 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
4100 this.createPane('shadowPane');
4101 // @pane shadowPane: HTMLElement = 500
4102 // Pane for overlay shadows (e.g. `Marker` shadows)
4103 this.createPane('overlayPane');
4104 // @pane markerPane: HTMLElement = 600
4105 // Pane for `Icon`s of `Marker`s
4106 this.createPane('markerPane');
4107 // @pane tooltipPane: HTMLElement = 650
4108 // Pane for `Tooltip`s.
4109 this.createPane('tooltipPane');
4110 // @pane popupPane: HTMLElement = 700
4111 // Pane for `Popup`s.
4112 this.createPane('popupPane');
4114 if (!this.options.markerZoomAnimation) {
4115 addClass(panes.markerPane, 'leaflet-zoom-hide');
4116 addClass(panes.shadowPane, 'leaflet-zoom-hide');
4121 // private methods that modify map state
4123 // @section Map state change events
4124 _resetView: function (center, zoom) {
4125 setPosition(this._mapPane, new Point(0, 0));
4127 var loading = !this._loaded;
4128 this._loaded = true;
4129 zoom = this._limitZoom(zoom);
4131 this.fire('viewprereset');
4133 var zoomChanged = this._zoom !== zoom;
4135 ._moveStart(zoomChanged, false)
4136 ._move(center, zoom)
4137 ._moveEnd(zoomChanged);
4139 // @event viewreset: Event
4140 // Fired when the map needs to redraw its content (this usually happens
4141 // on map zoom or load). Very useful for creating custom overlays.
4142 this.fire('viewreset');
4144 // @event load: Event
4145 // Fired when the map is initialized (when its center and zoom are set
4146 // for the first time).
4152 _moveStart: function (zoomChanged, noMoveStart) {
4153 // @event zoomstart: Event
4154 // Fired when the map zoom is about to change (e.g. before zoom animation).
4155 // @event movestart: Event
4156 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
4158 this.fire('zoomstart');
4161 this.fire('movestart');
4166 _move: function (center, zoom, data) {
4167 if (zoom === undefined) {
4170 var zoomChanged = this._zoom !== zoom;
4173 this._lastCenter = center;
4174 this._pixelOrigin = this._getNewPixelOrigin(center);
4176 // @event zoom: Event
4177 // Fired repeatedly during any change in zoom level, including zoom
4178 // and fly animations.
4179 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530
4180 this.fire('zoom', data);
4183 // @event move: Event
4184 // Fired repeatedly during any movement of the map, including pan and
4186 return this.fire('move', data);
4189 _moveEnd: function (zoomChanged) {
4190 // @event zoomend: Event
4191 // Fired when the map has changed, after any animations.
4193 this.fire('zoomend');
4196 // @event moveend: Event
4197 // Fired when the center of the map stops changing (e.g. user stopped
4198 // dragging the map).
4199 return this.fire('moveend');
4202 _stop: function () {
4203 cancelAnimFrame(this._flyToFrame);
4204 if (this._panAnim) {
4205 this._panAnim.stop();
4210 _rawPanBy: function (offset) {
4211 setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
4214 _getZoomSpan: function () {
4215 return this.getMaxZoom() - this.getMinZoom();
4218 _panInsideMaxBounds: function () {
4219 if (!this._enforcingBounds) {
4220 this.panInsideBounds(this.options.maxBounds);
4224 _checkIfLoaded: function () {
4225 if (!this._loaded) {
4226 throw new Error('Set map center and zoom first.');
4230 // DOM event handling
4232 // @section Interaction events
4233 _initEvents: function (remove$$1) {
4235 this._targets[stamp(this._container)] = this;
4237 var onOff = remove$$1 ? off : on;
4239 // @event click: MouseEvent
4240 // Fired when the user clicks (or taps) the map.
4241 // @event dblclick: MouseEvent
4242 // Fired when the user double-clicks (or double-taps) the map.
4243 // @event mousedown: MouseEvent
4244 // Fired when the user pushes the mouse button on the map.
4245 // @event mouseup: MouseEvent
4246 // Fired when the user releases the mouse button on the map.
4247 // @event mouseover: MouseEvent
4248 // Fired when the mouse enters the map.
4249 // @event mouseout: MouseEvent
4250 // Fired when the mouse leaves the map.
4251 // @event mousemove: MouseEvent
4252 // Fired while the mouse moves over the map.
4253 // @event contextmenu: MouseEvent
4254 // Fired when the user pushes the right mouse button on the map, prevents
4255 // default browser context menu from showing if there are listeners on
4256 // this event. Also fired on mobile when the user holds a single touch
4257 // for a second (also called long press).
4258 // @event keypress: KeyboardEvent
4259 // Fired when the user presses a key from the keyboard while the map is focused.
4260 onOff(this._container, 'click dblclick mousedown mouseup ' +
4261 'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);
4263 if (this.options.trackResize) {
4264 onOff(window, 'resize', this._onResize, this);
4267 if (any3d && this.options.transform3DLimit) {
4268 (remove$$1 ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
4272 _onResize: function () {
4273 cancelAnimFrame(this._resizeRequest);
4274 this._resizeRequest = requestAnimFrame(
4275 function () { this.invalidateSize({debounceMoveend: true}); }, this);
4278 _onScroll: function () {
4279 this._container.scrollTop = 0;
4280 this._container.scrollLeft = 0;
4283 _onMoveEnd: function () {
4284 var pos = this._getMapPanePos();
4285 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
4286 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
4287 // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
4288 this._resetView(this.getCenter(), this.getZoom());
4292 _findEventTargets: function (e, type) {
4295 isHover = type === 'mouseout' || type === 'mouseover',
4296 src = e.target || e.srcElement,
4300 target = this._targets[stamp(src)];
4301 if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
4302 // Prevent firing click after you just dragged an object.
4306 if (target && target.listens(type, true)) {
4307 if (isHover && !isExternalTarget(src, e)) { break; }
4308 targets.push(target);
4309 if (isHover) { break; }
4311 if (src === this._container) { break; }
4312 src = src.parentNode;
4314 if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) {
4320 _handleDOMEvent: function (e) {
4321 if (!this._loaded || skipped(e)) { return; }
4325 if (type === 'mousedown' || type === 'keypress') {
4326 // prevents outline when clicking on keyboard-focusable element
4327 preventOutline(e.target || e.srcElement);
4330 this._fireDOMEvent(e, type);
4333 _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
4335 _fireDOMEvent: function (e, type, targets) {
4337 if (e.type === 'click') {
4338 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
4339 // @event preclick: MouseEvent
4340 // Fired before mouse click on the map (sometimes useful when you
4341 // want something to happen on click before any existing click
4342 // handlers start running).
4343 var synth = extend({}, e);
4344 synth.type = 'preclick';
4345 this._fireDOMEvent(synth, synth.type, targets);
4348 if (e._stopped) { return; }
4350 // Find the layer the event is propagating from and its parents.
4351 targets = (targets || []).concat(this._findEventTargets(e, type));
4353 if (!targets.length) { return; }
4355 var target = targets[0];
4356 if (type === 'contextmenu' && target.listens(type, true)) {
4364 if (e.type !== 'keypress') {
4365 var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
4366 data.containerPoint = isMarker ?
4367 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
4368 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
4369 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
4372 for (var i = 0; i < targets.length; i++) {
4373 targets[i].fire(type, data, true);
4374 if (data.originalEvent._stopped ||
4375 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
4379 _draggableMoved: function (obj) {
4380 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
4381 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
4384 _clearHandlers: function () {
4385 for (var i = 0, len = this._handlers.length; i < len; i++) {
4386 this._handlers[i].disable();
4390 // @section Other Methods
4392 // @method whenReady(fn: Function, context?: Object): this
4393 // Runs the given function `fn` when the map gets initialized with
4394 // a view (center and zoom) and at least one layer, or immediately
4395 // if it's already initialized, optionally passing a function context.
4396 whenReady: function (callback, context) {
4398 callback.call(context || this, {target: this});
4400 this.on('load', callback, context);
4406 // private methods for getting map state
4408 _getMapPanePos: function () {
4409 return getPosition(this._mapPane) || new Point(0, 0);
4412 _moved: function () {
4413 var pos = this._getMapPanePos();
4414 return pos && !pos.equals([0, 0]);
4417 _getTopLeftPoint: function (center, zoom) {
4418 var pixelOrigin = center && zoom !== undefined ?
4419 this._getNewPixelOrigin(center, zoom) :
4420 this.getPixelOrigin();
4421 return pixelOrigin.subtract(this._getMapPanePos());
4424 _getNewPixelOrigin: function (center, zoom) {
4425 var viewHalf = this.getSize()._divideBy(2);
4426 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
4429 _latLngToNewLayerPoint: function (latlng, zoom, center) {
4430 var topLeft = this._getNewPixelOrigin(center, zoom);
4431 return this.project(latlng, zoom)._subtract(topLeft);
4434 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
4435 var topLeft = this._getNewPixelOrigin(center, zoom);
4437 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
4438 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
4439 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
4440 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
4444 // layer point of the current center
4445 _getCenterLayerPoint: function () {
4446 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
4449 // offset of the specified place to the current center in pixels
4450 _getCenterOffset: function (latlng) {
4451 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
4454 // adjust center for view to get inside bounds
4455 _limitCenter: function (center, zoom, bounds) {
4457 if (!bounds) { return center; }
4459 var centerPoint = this.project(center, zoom),
4460 viewHalf = this.getSize().divideBy(2),
4461 viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
4462 offset = this._getBoundsOffset(viewBounds, bounds, zoom);
4464 // If offset is less than a pixel, ignore.
4465 // This prevents unstable projections from getting into
4466 // an infinite loop of tiny offsets.
4467 if (offset.round().equals([0, 0])) {
4471 return this.unproject(centerPoint.add(offset), zoom);
4474 // adjust offset for view to get inside bounds
4475 _limitOffset: function (offset, bounds) {
4476 if (!bounds) { return offset; }
4478 var viewBounds = this.getPixelBounds(),
4479 newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
4481 return offset.add(this._getBoundsOffset(newBounds, bounds));
4484 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
4485 _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
4486 var projectedMaxBounds = toBounds(
4487 this.project(maxBounds.getNorthEast(), zoom),
4488 this.project(maxBounds.getSouthWest(), zoom)
4490 minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
4491 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
4493 dx = this._rebound(minOffset.x, -maxOffset.x),
4494 dy = this._rebound(minOffset.y, -maxOffset.y);
4496 return new Point(dx, dy);
4499 _rebound: function (left, right) {
4500 return left + right > 0 ?
4501 Math.round(left - right) / 2 :
4502 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
4505 _limitZoom: function (zoom) {
4506 var min = this.getMinZoom(),
4507 max = this.getMaxZoom(),
4508 snap = any3d ? this.options.zoomSnap : 1;
4510 zoom = Math.round(zoom / snap) * snap;
4512 return Math.max(min, Math.min(max, zoom));
4515 _onPanTransitionStep: function () {
4519 _onPanTransitionEnd: function () {
4520 removeClass(this._mapPane, 'leaflet-pan-anim');
4521 this.fire('moveend');
4524 _tryAnimatedPan: function (center, options) {
4525 // difference between the new and current centers in pixels
4526 var offset = this._getCenterOffset(center)._trunc();
4528 // don't animate too far unless animate: true specified in options
4529 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
4531 this.panBy(offset, options);
4536 _createAnimProxy: function () {
4538 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
4539 this._panes.mapPane.appendChild(proxy);
4541 this.on('zoomanim', function (e) {
4542 var prop = TRANSFORM,
4543 transform = this._proxy.style[prop];
4545 setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
4547 // workaround for case when transform is the same and so transitionend event is not fired
4548 if (transform === this._proxy.style[prop] && this._animatingZoom) {
4549 this._onZoomTransitionEnd();
4553 this.on('load moveend', function () {
4554 var c = this.getCenter(),
4556 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
4559 this._on('unload', this._destroyAnimProxy, this);
4562 _destroyAnimProxy: function () {
4563 remove(this._proxy);
4567 _catchTransitionEnd: function (e) {
4568 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
4569 this._onZoomTransitionEnd();
4573 _nothingToAnimate: function () {
4574 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
4577 _tryAnimatedZoom: function (center, zoom, options) {
4579 if (this._animatingZoom) { return true; }
4581 options = options || {};
4583 // don't animate if disabled, not supported or zoom difference is too large
4584 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
4585 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
4587 // offset is the pixel coords of the zoom origin relative to the current center
4588 var scale = this.getZoomScale(zoom),
4589 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
4591 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
4592 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
4594 requestAnimFrame(function () {
4596 ._moveStart(true, false)
4597 ._animateZoom(center, zoom, true);
4603 _animateZoom: function (center, zoom, startAnim, noUpdate) {
4604 if (!this._mapPane) { return; }
4607 this._animatingZoom = true;
4609 // remember what center/zoom to set after animation
4610 this._animateToCenter = center;
4611 this._animateToZoom = zoom;
4613 addClass(this._mapPane, 'leaflet-zoom-anim');
4616 // @event zoomanim: ZoomAnimEvent
4617 // Fired on every frame of a zoom animation
4618 this.fire('zoomanim', {
4624 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
4625 setTimeout(bind(this._onZoomTransitionEnd, this), 250);
4628 _onZoomTransitionEnd: function () {
4629 if (!this._animatingZoom) { return; }
4631 if (this._mapPane) {
4632 removeClass(this._mapPane, 'leaflet-zoom-anim');
4635 this._animatingZoom = false;
4637 this._move(this._animateToCenter, this._animateToZoom);
4639 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
4640 requestAnimFrame(function () {
4641 this._moveEnd(true);
4648 // @factory L.map(id: String, options?: Map options)
4649 // Instantiates a map object given the DOM ID of a `<div>` element
4650 // and optionally an object literal with `Map options`.
4653 // @factory L.map(el: HTMLElement, options?: Map options)
4654 // Instantiates a map object given an instance of a `<div>` HTML element
4655 // and optionally an object literal with `Map options`.
4656 function createMap(id, options) {
4657 return new Map(id, options);
4665 * L.Control is a base class for implementing map controls. Handles positioning.
4666 * All other controls extend from this class.
4669 var Control = Class.extend({
4671 // @aka Control options
4673 // @option position: String = 'topright'
4674 // The position of the control (one of the map corners). Possible values are `'topleft'`,
4675 // `'topright'`, `'bottomleft'` or `'bottomright'`
4676 position: 'topright'
4679 initialize: function (options) {
4680 setOptions(this, options);
4684 * Classes extending L.Control will inherit the following methods:
4686 * @method getPosition: string
4687 * Returns the position of the control.
4689 getPosition: function () {
4690 return this.options.position;
4693 // @method setPosition(position: string): this
4694 // Sets the position of the control.
4695 setPosition: function (position) {
4696 var map = this._map;
4699 map.removeControl(this);
4702 this.options.position = position;
4705 map.addControl(this);
4711 // @method getContainer: HTMLElement
4712 // Returns the HTMLElement that contains the control.
4713 getContainer: function () {
4714 return this._container;
4717 // @method addTo(map: Map): this
4718 // Adds the control to the given map.
4719 addTo: function (map) {
4723 var container = this._container = this.onAdd(map),
4724 pos = this.getPosition(),
4725 corner = map._controlCorners[pos];
4727 addClass(container, 'leaflet-control');
4729 if (pos.indexOf('bottom') !== -1) {
4730 corner.insertBefore(container, corner.firstChild);
4732 corner.appendChild(container);
4738 // @method remove: this
4739 // Removes the control from the map it is currently active on.
4740 remove: function () {
4745 remove(this._container);
4747 if (this.onRemove) {
4748 this.onRemove(this._map);
4756 _refocusOnMap: function (e) {
4757 // if map exists and event is not a keyboard event
4758 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
4759 this._map.getContainer().focus();
4764 var control = function (options) {
4765 return new Control(options);
4768 /* @section Extension methods
4771 * Every control should extend from `L.Control` and (re-)implement the following methods.
4773 * @method onAdd(map: Map): HTMLElement
4774 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
4776 * @method onRemove(map: Map)
4777 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
4781 * @section Methods for Layers and Controls
4784 // @method addControl(control: Control): this
4785 // Adds the given control to the map
4786 addControl: function (control) {
4787 control.addTo(this);
4791 // @method removeControl(control: Control): this
4792 // Removes the given control from the map
4793 removeControl: function (control) {
4798 _initControlPos: function () {
4799 var corners = this._controlCorners = {},
4801 container = this._controlContainer =
4802 create$1('div', l + 'control-container', this._container);
4804 function createCorner(vSide, hSide) {
4805 var className = l + vSide + ' ' + l + hSide;
4807 corners[vSide + hSide] = create$1('div', className, container);
4810 createCorner('top', 'left');
4811 createCorner('top', 'right');
4812 createCorner('bottom', 'left');
4813 createCorner('bottom', 'right');
4816 _clearControlPos: function () {
4817 for (var i in this._controlCorners) {
4818 remove(this._controlCorners[i]);
4820 remove(this._controlContainer);
4821 delete this._controlCorners;
4822 delete this._controlContainer;
4827 * @class Control.Layers
4828 * @aka L.Control.Layers
4831 * 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`.
4836 * var baseLayers = {
4838 * "OpenStreetMap": osm
4843 * "Roads": roadsLayer
4846 * L.control.layers(baseLayers, overlays).addTo(map);
4849 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
4853 * "<someName1>": layer1,
4854 * "<someName2>": layer2
4858 * The layer names can contain HTML, which allows you to add additional styling to the items:
4861 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
4865 var Layers = Control.extend({
4867 // @aka Control.Layers options
4869 // @option collapsed: Boolean = true
4870 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
4872 position: 'topright',
4874 // @option autoZIndex: Boolean = true
4875 // 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.
4878 // @option hideSingleBase: Boolean = false
4879 // If `true`, the base layers in the control will be hidden when there is only one.
4880 hideSingleBase: false,
4882 // @option sortLayers: Boolean = false
4883 // Whether to sort the layers. When `false`, layers will keep the order
4884 // in which they were added to the control.
4887 // @option sortFunction: Function = *
4888 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
4889 // that will be used for sorting the layers, when `sortLayers` is `true`.
4890 // The function receives both the `L.Layer` instances and their names, as in
4891 // `sortFunction(layerA, layerB, nameA, nameB)`.
4892 // By default, it sorts layers alphabetically by their name.
4893 sortFunction: function (layerA, layerB, nameA, nameB) {
4894 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
4898 initialize: function (baseLayers, overlays, options) {
4899 setOptions(this, options);
4901 this._layerControlInputs = [];
4903 this._lastZIndex = 0;
4904 this._handlingClick = false;
4906 for (var i in baseLayers) {
4907 this._addLayer(baseLayers[i], i);
4910 for (i in overlays) {
4911 this._addLayer(overlays[i], i, true);
4915 onAdd: function (map) {
4920 map.on('zoomend', this._checkDisabledLayers, this);
4922 for (var i = 0; i < this._layers.length; i++) {
4923 this._layers[i].layer.on('add remove', this._onLayerChange, this);
4926 return this._container;
4929 addTo: function (map) {
4930 Control.prototype.addTo.call(this, map);
4931 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
4932 return this._expandIfNotCollapsed();
4935 onRemove: function () {
4936 this._map.off('zoomend', this._checkDisabledLayers, this);
4938 for (var i = 0; i < this._layers.length; i++) {
4939 this._layers[i].layer.off('add remove', this._onLayerChange, this);
4943 // @method addBaseLayer(layer: Layer, name: String): this
4944 // Adds a base layer (radio button entry) with the given name to the control.
4945 addBaseLayer: function (layer, name) {
4946 this._addLayer(layer, name);
4947 return (this._map) ? this._update() : this;
4950 // @method addOverlay(layer: Layer, name: String): this
4951 // Adds an overlay (checkbox entry) with the given name to the control.
4952 addOverlay: function (layer, name) {
4953 this._addLayer(layer, name, true);
4954 return (this._map) ? this._update() : this;
4957 // @method removeLayer(layer: Layer): this
4958 // Remove the given layer from the control.
4959 removeLayer: function (layer) {
4960 layer.off('add remove', this._onLayerChange, this);
4962 var obj = this._getLayer(stamp(layer));
4964 this._layers.splice(this._layers.indexOf(obj), 1);
4966 return (this._map) ? this._update() : this;
4969 // @method expand(): this
4970 // Expand the control container if collapsed.
4971 expand: function () {
4972 addClass(this._container, 'leaflet-control-layers-expanded');
4973 this._form.style.height = null;
4974 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
4975 if (acceptableHeight < this._form.clientHeight) {
4976 addClass(this._form, 'leaflet-control-layers-scrollbar');
4977 this._form.style.height = acceptableHeight + 'px';
4979 removeClass(this._form, 'leaflet-control-layers-scrollbar');
4981 this._checkDisabledLayers();
4985 // @method collapse(): this
4986 // Collapse the control container if expanded.
4987 collapse: function () {
4988 removeClass(this._container, 'leaflet-control-layers-expanded');
4992 _initLayout: function () {
4993 var className = 'leaflet-control-layers',
4994 container = this._container = create$1('div', className),
4995 collapsed = this.options.collapsed;
4997 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
4998 container.setAttribute('aria-haspopup', true);
5000 disableClickPropagation(container);
5001 disableScrollPropagation(container);
5003 var form = this._form = create$1('form', className + '-list');
5006 this._map.on('click', this.collapse, this);
5010 mouseenter: this.expand,
5011 mouseleave: this.collapse
5016 var link = this._layersLink = create$1('a', className + '-toggle', container);
5018 link.title = 'Layers';
5021 on(link, 'click', stop);
5022 on(link, 'click', this.expand, this);
5024 on(link, 'focus', this.expand, this);
5031 this._baseLayersList = create$1('div', className + '-base', form);
5032 this._separator = create$1('div', className + '-separator', form);
5033 this._overlaysList = create$1('div', className + '-overlays', form);
5035 container.appendChild(form);
5038 _getLayer: function (id) {
5039 for (var i = 0; i < this._layers.length; i++) {
5041 if (this._layers[i] && stamp(this._layers[i].layer) === id) {
5042 return this._layers[i];
5047 _addLayer: function (layer, name, overlay) {
5049 layer.on('add remove', this._onLayerChange, this);
5058 if (this.options.sortLayers) {
5059 this._layers.sort(bind(function (a, b) {
5060 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
5064 if (this.options.autoZIndex && layer.setZIndex) {
5066 layer.setZIndex(this._lastZIndex);
5069 this._expandIfNotCollapsed();
5072 _update: function () {
5073 if (!this._container) { return this; }
5075 empty(this._baseLayersList);
5076 empty(this._overlaysList);
5078 this._layerControlInputs = [];
5079 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
5081 for (i = 0; i < this._layers.length; i++) {
5082 obj = this._layers[i];
5084 overlaysPresent = overlaysPresent || obj.overlay;
5085 baseLayersPresent = baseLayersPresent || !obj.overlay;
5086 baseLayersCount += !obj.overlay ? 1 : 0;
5089 // Hide base layers section if there's only one layer.
5090 if (this.options.hideSingleBase) {
5091 baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
5092 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
5095 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
5100 _onLayerChange: function (e) {
5101 if (!this._handlingClick) {
5105 var obj = this._getLayer(stamp(e.target));
5108 // @section Layer events
5109 // @event baselayerchange: LayersControlEvent
5110 // Fired when the base layer is changed through the [layer control](#control-layers).
5111 // @event overlayadd: LayersControlEvent
5112 // Fired when an overlay is selected through the [layer control](#control-layers).
5113 // @event overlayremove: LayersControlEvent
5114 // Fired when an overlay is deselected through the [layer control](#control-layers).
5115 // @namespace Control.Layers
5116 var type = obj.overlay ?
5117 (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
5118 (e.type === 'add' ? 'baselayerchange' : null);
5121 this._map.fire(type, obj);
5125 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
5126 _createRadioElement: function (name, checked) {
5128 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
5129 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
5131 var radioFragment = document.createElement('div');
5132 radioFragment.innerHTML = radioHtml;
5134 return radioFragment.firstChild;
5137 _addItem: function (obj) {
5138 var label = document.createElement('label'),
5139 checked = this._map.hasLayer(obj.layer),
5143 input = document.createElement('input');
5144 input.type = 'checkbox';
5145 input.className = 'leaflet-control-layers-selector';
5146 input.defaultChecked = checked;
5148 input = this._createRadioElement('leaflet-base-layers', checked);
5151 this._layerControlInputs.push(input);
5152 input.layerId = stamp(obj.layer);
5154 on(input, 'click', this._onInputClick, this);
5156 var name = document.createElement('span');
5157 name.innerHTML = ' ' + obj.name;
5159 // Helps from preventing layer control flicker when checkboxes are disabled
5160 // https://github.com/Leaflet/Leaflet/issues/2771
5161 var holder = document.createElement('div');
5163 label.appendChild(holder);
5164 holder.appendChild(input);
5165 holder.appendChild(name);
5167 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
5168 container.appendChild(label);
5170 this._checkDisabledLayers();
5174 _onInputClick: function () {
5175 var inputs = this._layerControlInputs,
5177 var addedLayers = [],
5180 this._handlingClick = true;
5182 for (var i = inputs.length - 1; i >= 0; i--) {
5184 layer = this._getLayer(input.layerId).layer;
5186 if (input.checked) {
5187 addedLayers.push(layer);
5188 } else if (!input.checked) {
5189 removedLayers.push(layer);
5193 // Bugfix issue 2318: Should remove all old layers before readding new ones
5194 for (i = 0; i < removedLayers.length; i++) {
5195 if (this._map.hasLayer(removedLayers[i])) {
5196 this._map.removeLayer(removedLayers[i]);
5199 for (i = 0; i < addedLayers.length; i++) {
5200 if (!this._map.hasLayer(addedLayers[i])) {
5201 this._map.addLayer(addedLayers[i]);
5205 this._handlingClick = false;
5207 this._refocusOnMap();
5210 _checkDisabledLayers: function () {
5211 var inputs = this._layerControlInputs,
5214 zoom = this._map.getZoom();
5216 for (var i = inputs.length - 1; i >= 0; i--) {
5218 layer = this._getLayer(input.layerId).layer;
5219 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
5220 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
5225 _expandIfNotCollapsed: function () {
5226 if (this._map && !this.options.collapsed) {
5232 _expand: function () {
5233 // Backward compatibility, remove me in 1.1.
5234 return this.expand();
5237 _collapse: function () {
5238 // Backward compatibility, remove me in 1.1.
5239 return this.collapse();
5245 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
5246 // 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.
5247 var layers = function (baseLayers, overlays, options) {
5248 return new Layers(baseLayers, overlays, options);
5252 * @class Control.Zoom
5253 * @aka L.Control.Zoom
5256 * 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`.
5259 var Zoom = Control.extend({
5261 // @aka Control.Zoom options
5263 position: 'topleft',
5265 // @option zoomInText: String = '+'
5266 // The text set on the 'zoom in' button.
5269 // @option zoomInTitle: String = 'Zoom in'
5270 // The title set on the 'zoom in' button.
5271 zoomInTitle: 'Zoom in',
5273 // @option zoomOutText: String = '−'
5274 // The text set on the 'zoom out' button.
5275 zoomOutText: '−',
5277 // @option zoomOutTitle: String = 'Zoom out'
5278 // The title set on the 'zoom out' button.
5279 zoomOutTitle: 'Zoom out'
5282 onAdd: function (map) {
5283 var zoomName = 'leaflet-control-zoom',
5284 container = create$1('div', zoomName + ' leaflet-bar'),
5285 options = this.options;
5287 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle,
5288 zoomName + '-in', container, this._zoomIn);
5289 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
5290 zoomName + '-out', container, this._zoomOut);
5292 this._updateDisabled();
5293 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
5298 onRemove: function (map) {
5299 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
5302 disable: function () {
5303 this._disabled = true;
5304 this._updateDisabled();
5308 enable: function () {
5309 this._disabled = false;
5310 this._updateDisabled();
5314 _zoomIn: function (e) {
5315 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
5316 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5320 _zoomOut: function (e) {
5321 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
5322 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5326 _createButton: function (html, title, className, container, fn) {
5327 var link = create$1('a', className, container);
5328 link.innerHTML = html;
5333 * Will force screen readers like VoiceOver to read this as "Zoom in - button"
5335 link.setAttribute('role', 'button');
5336 link.setAttribute('aria-label', title);
5338 disableClickPropagation(link);
5339 on(link, 'click', stop);
5340 on(link, 'click', fn, this);
5341 on(link, 'click', this._refocusOnMap, this);
5346 _updateDisabled: function () {
5347 var map = this._map,
5348 className = 'leaflet-disabled';
5350 removeClass(this._zoomInButton, className);
5351 removeClass(this._zoomOutButton, className);
5353 if (this._disabled || map._zoom === map.getMinZoom()) {
5354 addClass(this._zoomOutButton, className);
5356 if (this._disabled || map._zoom === map.getMaxZoom()) {
5357 addClass(this._zoomInButton, className);
5363 // @section Control options
5364 // @option zoomControl: Boolean = true
5365 // Whether a [zoom control](#control-zoom) is added to the map by default.
5370 Map.addInitHook(function () {
5371 if (this.options.zoomControl) {
5372 this.zoomControl = new Zoom();
5373 this.addControl(this.zoomControl);
5377 // @namespace Control.Zoom
5378 // @factory L.control.zoom(options: Control.Zoom options)
5379 // Creates a zoom control
5380 var zoom = function (options) {
5381 return new Zoom(options);
5385 * @class Control.Scale
5386 * @aka L.Control.Scale
5389 * 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`.
5394 * L.control.scale().addTo(map);
5398 var Scale = Control.extend({
5400 // @aka Control.Scale options
5402 position: 'bottomleft',
5404 // @option maxWidth: Number = 100
5405 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
5408 // @option metric: Boolean = True
5409 // Whether to show the metric scale line (m/km).
5412 // @option imperial: Boolean = True
5413 // Whether to show the imperial scale line (mi/ft).
5416 // @option updateWhenIdle: Boolean = false
5417 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
5420 onAdd: function (map) {
5421 var className = 'leaflet-control-scale',
5422 container = create$1('div', className),
5423 options = this.options;
5425 this._addScales(options, className + '-line', container);
5427 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5428 map.whenReady(this._update, this);
5433 onRemove: function (map) {
5434 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5437 _addScales: function (options, className, container) {
5438 if (options.metric) {
5439 this._mScale = create$1('div', className, container);
5441 if (options.imperial) {
5442 this._iScale = create$1('div', className, container);
5446 _update: function () {
5447 var map = this._map,
5448 y = map.getSize().y / 2;
5450 var maxMeters = map.distance(
5451 map.containerPointToLatLng([0, y]),
5452 map.containerPointToLatLng([this.options.maxWidth, y]));
5454 this._updateScales(maxMeters);
5457 _updateScales: function (maxMeters) {
5458 if (this.options.metric && maxMeters) {
5459 this._updateMetric(maxMeters);
5461 if (this.options.imperial && maxMeters) {
5462 this._updateImperial(maxMeters);
5466 _updateMetric: function (maxMeters) {
5467 var meters = this._getRoundNum(maxMeters),
5468 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
5470 this._updateScale(this._mScale, label, meters / maxMeters);
5473 _updateImperial: function (maxMeters) {
5474 var maxFeet = maxMeters * 3.2808399,
5475 maxMiles, miles, feet;
5477 if (maxFeet > 5280) {
5478 maxMiles = maxFeet / 5280;
5479 miles = this._getRoundNum(maxMiles);
5480 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
5483 feet = this._getRoundNum(maxFeet);
5484 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
5488 _updateScale: function (scale, text, ratio) {
5489 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
5490 scale.innerHTML = text;
5493 _getRoundNum: function (num) {
5494 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5507 // @factory L.control.scale(options?: Control.Scale options)
5508 // Creates an scale control with the given options.
5509 var scale = function (options) {
5510 return new Scale(options);
5514 * @class Control.Attribution
5515 * @aka L.Control.Attribution
5518 * 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.
5521 var Attribution = Control.extend({
5523 // @aka Control.Attribution options
5525 position: 'bottomright',
5527 // @option prefix: String = 'Leaflet'
5528 // The HTML text shown before the attributions. Pass `false` to disable.
5529 prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
5532 initialize: function (options) {
5533 setOptions(this, options);
5535 this._attributions = {};
5538 onAdd: function (map) {
5539 map.attributionControl = this;
5540 this._container = create$1('div', 'leaflet-control-attribution');
5541 disableClickPropagation(this._container);
5543 // TODO ugly, refactor
5544 for (var i in map._layers) {
5545 if (map._layers[i].getAttribution) {
5546 this.addAttribution(map._layers[i].getAttribution());
5552 return this._container;
5555 // @method setPrefix(prefix: String): this
5556 // Sets the text before the attributions.
5557 setPrefix: function (prefix) {
5558 this.options.prefix = prefix;
5563 // @method addAttribution(text: String): this
5564 // Adds an attribution text (e.g. `'Vector data © Mapbox'`).
5565 addAttribution: function (text) {
5566 if (!text) { return this; }
5568 if (!this._attributions[text]) {
5569 this._attributions[text] = 0;
5571 this._attributions[text]++;
5578 // @method removeAttribution(text: String): this
5579 // Removes an attribution text.
5580 removeAttribution: function (text) {
5581 if (!text) { return this; }
5583 if (this._attributions[text]) {
5584 this._attributions[text]--;
5591 _update: function () {
5592 if (!this._map) { return; }
5596 for (var i in this._attributions) {
5597 if (this._attributions[i]) {
5602 var prefixAndAttribs = [];
5604 if (this.options.prefix) {
5605 prefixAndAttribs.push(this.options.prefix);
5607 if (attribs.length) {
5608 prefixAndAttribs.push(attribs.join(', '));
5611 this._container.innerHTML = prefixAndAttribs.join(' | ');
5616 // @section Control options
5617 // @option attributionControl: Boolean = true
5618 // Whether a [attribution control](#control-attribution) is added to the map by default.
5620 attributionControl: true
5623 Map.addInitHook(function () {
5624 if (this.options.attributionControl) {
5625 new Attribution().addTo(this);
5629 // @namespace Control.Attribution
5630 // @factory L.control.attribution(options: Control.Attribution options)
5631 // Creates an attribution control.
5632 var attribution = function (options) {
5633 return new Attribution(options);
5636 Control.Layers = Layers;
5637 Control.Zoom = Zoom;
5638 Control.Scale = Scale;
5639 Control.Attribution = Attribution;
5641 control.layers = layers;
5642 control.zoom = zoom;
5643 control.scale = scale;
5644 control.attribution = attribution;
5647 L.Handler is a base class for handler classes that are used internally to inject
5648 interaction features like dragging to classes like Map and Marker.
5653 // Abstract class for map interaction handlers
5655 var Handler = Class.extend({
5656 initialize: function (map) {
5660 // @method enable(): this
5661 // Enables the handler
5662 enable: function () {
5663 if (this._enabled) { return this; }
5665 this._enabled = true;
5670 // @method disable(): this
5671 // Disables the handler
5672 disable: function () {
5673 if (!this._enabled) { return this; }
5675 this._enabled = false;
5680 // @method enabled(): Boolean
5681 // Returns `true` if the handler is enabled
5682 enabled: function () {
5683 return !!this._enabled;
5686 // @section Extension methods
5687 // Classes inheriting from `Handler` must implement the two following methods:
5688 // @method addHooks()
5689 // Called when the handler is enabled, should add event hooks.
5690 // @method removeHooks()
5691 // Called when the handler is disabled, should remove the event hooks added previously.
5694 // @section There is static function which can be called without instantiating L.Handler:
5695 // @function addTo(map: Map, name: String): this
5696 // Adds a new Handler to the given map with the given name.
5697 Handler.addTo = function (map, name) {
5698 map.addHandler(name, this);
5702 var Mixin = {Events: Events};
5709 * A class for making DOM elements draggable (including touch support).
5710 * Used internally for map and marker dragging. Only works for elements
5711 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
5715 * var draggable = new L.Draggable(elementToDrag);
5716 * draggable.enable();
5720 var START = touch ? 'touchstart mousedown' : 'mousedown';
5722 mousedown: 'mouseup',
5723 touchstart: 'touchend',
5724 pointerdown: 'touchend',
5725 MSPointerDown: 'touchend'
5728 mousedown: 'mousemove',
5729 touchstart: 'touchmove',
5730 pointerdown: 'touchmove',
5731 MSPointerDown: 'touchmove'
5735 var Draggable = Evented.extend({
5739 // @aka Draggable options
5740 // @option clickTolerance: Number = 3
5741 // The max number of pixels a user can shift the mouse pointer during a click
5742 // for it to be considered a valid click (as opposed to a mouse drag).
5746 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
5747 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
5748 initialize: function (element, dragStartTarget, preventOutline$$1, options) {
5749 setOptions(this, options);
5751 this._element = element;
5752 this._dragStartTarget = dragStartTarget || element;
5753 this._preventOutline = preventOutline$$1;
5757 // Enables the dragging ability
5758 enable: function () {
5759 if (this._enabled) { return; }
5761 on(this._dragStartTarget, START, this._onDown, this);
5763 this._enabled = true;
5766 // @method disable()
5767 // Disables the dragging ability
5768 disable: function () {
5769 if (!this._enabled) { return; }
5771 // If we're currently dragging this draggable,
5772 // disabling it counts as first ending the drag.
5773 if (Draggable._dragging === this) {
5777 off(this._dragStartTarget, START, this._onDown, this);
5779 this._enabled = false;
5780 this._moved = false;
5783 _onDown: function (e) {
5784 // Ignore simulated events, since we handle both touch and
5785 // mouse explicitly; otherwise we risk getting duplicates of
5786 // touch events, see #4315.
5787 // Also ignore the event if disabled; this happens in IE11
5788 // under some circumstances, see #3666.
5789 if (e._simulated || !this._enabled) { return; }
5791 this._moved = false;
5793 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
5795 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
5796 Draggable._dragging = this; // Prevent dragging multiple objects at once.
5798 if (this._preventOutline) {
5799 preventOutline(this._element);
5803 disableTextSelection();
5805 if (this._moving) { return; }
5807 // @event down: Event
5808 // Fired when a drag is about to start.
5811 var first = e.touches ? e.touches[0] : e,
5812 sizedParent = getSizedParentNode(this._element);
5814 this._startPoint = new Point(first.clientX, first.clientY);
5816 // Cache the scale, so that we can continuously compensate for it during drag (_onMove).
5817 this._parentScale = getScale(sizedParent);
5819 on(document, MOVE[e.type], this._onMove, this);
5820 on(document, END[e.type], this._onUp, this);
5823 _onMove: function (e) {
5824 // Ignore simulated events, since we handle both touch and
5825 // mouse explicitly; otherwise we risk getting duplicates of
5826 // touch events, see #4315.
5827 // Also ignore the event if disabled; this happens in IE11
5828 // under some circumstances, see #3666.
5829 if (e._simulated || !this._enabled) { return; }
5831 if (e.touches && e.touches.length > 1) {
5836 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
5837 offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
5839 if (!offset.x && !offset.y) { return; }
5840 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
5842 // We assume that the parent container's position, border and scale do not change for the duration of the drag.
5843 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
5844 // and we can use the cached value for the scale.
5845 offset.x /= this._parentScale.x;
5846 offset.y /= this._parentScale.y;
5851 // @event dragstart: Event
5852 // Fired when a drag starts
5853 this.fire('dragstart');
5856 this._startPos = getPosition(this._element).subtract(offset);
5858 addClass(document.body, 'leaflet-dragging');
5860 this._lastTarget = e.target || e.srcElement;
5861 // IE and Edge do not give the <use> element, so fetch it
5863 if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
5864 this._lastTarget = this._lastTarget.correspondingUseElement;
5866 addClass(this._lastTarget, 'leaflet-drag-target');
5869 this._newPos = this._startPos.add(offset);
5870 this._moving = true;
5872 cancelAnimFrame(this._animRequest);
5873 this._lastEvent = e;
5874 this._animRequest = requestAnimFrame(this._updatePosition, this, true);
5877 _updatePosition: function () {
5878 var e = {originalEvent: this._lastEvent};
5880 // @event predrag: Event
5881 // Fired continuously during dragging *before* each corresponding
5882 // update of the element's position.
5883 this.fire('predrag', e);
5884 setPosition(this._element, this._newPos);
5886 // @event drag: Event
5887 // Fired continuously during dragging.
5888 this.fire('drag', e);
5891 _onUp: function (e) {
5892 // Ignore simulated events, since we handle both touch and
5893 // mouse explicitly; otherwise we risk getting duplicates of
5894 // touch events, see #4315.
5895 // Also ignore the event if disabled; this happens in IE11
5896 // under some circumstances, see #3666.
5897 if (e._simulated || !this._enabled) { return; }
5901 finishDrag: function () {
5902 removeClass(document.body, 'leaflet-dragging');
5904 if (this._lastTarget) {
5905 removeClass(this._lastTarget, 'leaflet-drag-target');
5906 this._lastTarget = null;
5909 for (var i in MOVE) {
5910 off(document, MOVE[i], this._onMove, this);
5911 off(document, END[i], this._onUp, this);
5915 enableTextSelection();
5917 if (this._moved && this._moving) {
5918 // ensure drag is not fired after dragend
5919 cancelAnimFrame(this._animRequest);
5921 // @event dragend: DragEndEvent
5922 // Fired when the drag ends.
5923 this.fire('dragend', {
5924 distance: this._newPos.distanceTo(this._startPos)
5928 this._moving = false;
5929 Draggable._dragging = false;
5935 * @namespace LineUtil
5937 * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
5940 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
5941 // Improves rendering performance dramatically by lessening the number of points to draw.
5943 // @function simplify(points: Point[], tolerance: Number): Point[]
5944 // Dramatically reduces the number of points in a polyline while retaining
5945 // its shape and returns a new array of simplified points, using the
5946 // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
5947 // Used for a huge performance boost when processing/displaying Leaflet polylines for
5948 // each zoom level and also reducing visual noise. tolerance affects the amount of
5949 // simplification (lesser value means higher quality but slower and with more points).
5950 // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
5951 function simplify(points, tolerance) {
5952 if (!tolerance || !points.length) {
5953 return points.slice();
5956 var sqTolerance = tolerance * tolerance;
5958 // stage 1: vertex reduction
5959 points = _reducePoints(points, sqTolerance);
5961 // stage 2: Douglas-Peucker simplification
5962 points = _simplifyDP(points, sqTolerance);
5967 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
5968 // Returns the distance between point `p` and segment `p1` to `p2`.
5969 function pointToSegmentDistance(p, p1, p2) {
5970 return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
5973 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
5974 // Returns the closest point from a point `p` on a segment `p1` to `p2`.
5975 function closestPointOnSegment(p, p1, p2) {
5976 return _sqClosestPointOnSegment(p, p1, p2);
5979 // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
5980 function _simplifyDP(points, sqTolerance) {
5982 var len = points.length,
5983 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
5984 markers = new ArrayConstructor(len);
5986 markers[0] = markers[len - 1] = 1;
5988 _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
5993 for (i = 0; i < len; i++) {
5995 newPoints.push(points[i]);
6002 function _simplifyDPStep(points, markers, sqTolerance, first, last) {
6007 for (i = first + 1; i <= last - 1; i++) {
6008 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
6010 if (sqDist > maxSqDist) {
6016 if (maxSqDist > sqTolerance) {
6019 _simplifyDPStep(points, markers, sqTolerance, first, index);
6020 _simplifyDPStep(points, markers, sqTolerance, index, last);
6024 // reduce points that are too close to each other to a single point
6025 function _reducePoints(points, sqTolerance) {
6026 var reducedPoints = [points[0]];
6028 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
6029 if (_sqDist(points[i], points[prev]) > sqTolerance) {
6030 reducedPoints.push(points[i]);
6034 if (prev < len - 1) {
6035 reducedPoints.push(points[len - 1]);
6037 return reducedPoints;
6042 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
6043 // Clips the segment a to b by rectangular bounds with the
6044 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
6045 // (modifying the segment points directly!). Used by Leaflet to only show polyline
6046 // points that are on the screen or near, increasing performance.
6047 function clipSegment(a, b, bounds, useLastCode, round) {
6048 var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
6049 codeB = _getBitCode(b, bounds),
6051 codeOut, p, newCode;
6053 // save 2nd code to avoid calculating it on the next segment
6057 // if a,b is inside the clip window (trivial accept)
6058 if (!(codeA | codeB)) {
6062 // if a,b is outside the clip window (trivial reject)
6063 if (codeA & codeB) {
6068 codeOut = codeA || codeB;
6069 p = _getEdgeIntersection(a, b, codeOut, bounds, round);
6070 newCode = _getBitCode(p, bounds);
6072 if (codeOut === codeA) {
6082 function _getEdgeIntersection(a, b, code, bounds, round) {
6089 if (code & 8) { // top
6090 x = a.x + dx * (max.y - a.y) / dy;
6093 } else if (code & 4) { // bottom
6094 x = a.x + dx * (min.y - a.y) / dy;
6097 } else if (code & 2) { // right
6099 y = a.y + dy * (max.x - a.x) / dx;
6101 } else if (code & 1) { // left
6103 y = a.y + dy * (min.x - a.x) / dx;
6106 return new Point(x, y, round);
6109 function _getBitCode(p, bounds) {
6112 if (p.x < bounds.min.x) { // left
6114 } else if (p.x > bounds.max.x) { // right
6118 if (p.y < bounds.min.y) { // bottom
6120 } else if (p.y > bounds.max.y) { // top
6127 // square distance (to avoid unnecessary Math.sqrt calls)
6128 function _sqDist(p1, p2) {
6129 var dx = p2.x - p1.x,
6131 return dx * dx + dy * dy;
6134 // return closest point on segment or distance to that point
6135 function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
6140 dot = dx * dx + dy * dy,
6144 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
6158 return sqDist ? dx * dx + dy * dy : new Point(x, y);
6162 // @function isFlat(latlngs: LatLng[]): Boolean
6163 // Returns true if `latlngs` is a flat array, false is nested.
6164 function isFlat(latlngs) {
6165 return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
6168 function _flat(latlngs) {
6169 console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
6170 return isFlat(latlngs);
6174 var LineUtil = (Object.freeze || Object)({
6176 pointToSegmentDistance: pointToSegmentDistance,
6177 closestPointOnSegment: closestPointOnSegment,
6178 clipSegment: clipSegment,
6179 _getEdgeIntersection: _getEdgeIntersection,
6180 _getBitCode: _getBitCode,
6181 _sqClosestPointOnSegment: _sqClosestPointOnSegment,
6187 * @namespace PolyUtil
6188 * Various utility functions for polygon geometries.
6191 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
6192 * 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)).
6193 * Used by Leaflet to only show polygon points that are on the screen or near, increasing
6194 * performance. Note that polygon points needs different algorithm for clipping
6195 * than polyline, so there's a separate method for it.
6197 function clipPolygon(points, bounds, round) {
6199 edges = [1, 4, 2, 8],
6204 for (i = 0, len = points.length; i < len; i++) {
6205 points[i]._code = _getBitCode(points[i], bounds);
6208 // for each edge (left, bottom, right, top)
6209 for (k = 0; k < 4; k++) {
6213 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
6217 // if a is inside the clip window
6218 if (!(a._code & edge)) {
6219 // if b is outside the clip window (a->b goes out of screen)
6220 if (b._code & edge) {
6221 p = _getEdgeIntersection(b, a, edge, bounds, round);
6222 p._code = _getBitCode(p, bounds);
6223 clippedPoints.push(p);
6225 clippedPoints.push(a);
6227 // else if b is inside the clip window (a->b enters the screen)
6228 } else if (!(b._code & edge)) {
6229 p = _getEdgeIntersection(b, a, edge, bounds, round);
6230 p._code = _getBitCode(p, bounds);
6231 clippedPoints.push(p);
6234 points = clippedPoints;
6241 var PolyUtil = (Object.freeze || Object)({
6242 clipPolygon: clipPolygon
6246 * @namespace Projection
6248 * Leaflet comes with a set of already defined Projections out of the box:
6250 * @projection L.Projection.LonLat
6252 * Equirectangular, or Plate Carree projection — the most simple projection,
6253 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
6254 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
6255 * `EPSG:4326` and `Simple` CRS.
6259 project: function (latlng) {
6260 return new Point(latlng.lng, latlng.lat);
6263 unproject: function (point) {
6264 return new LatLng(point.y, point.x);
6267 bounds: new Bounds([-180, -90], [180, 90])
6271 * @namespace Projection
6272 * @projection L.Projection.Mercator
6274 * 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.
6279 R_MINOR: 6356752.314245179,
6281 bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
6283 project: function (latlng) {
6284 var d = Math.PI / 180,
6287 tmp = this.R_MINOR / r,
6288 e = Math.sqrt(1 - tmp * tmp),
6289 con = e * Math.sin(y);
6291 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
6292 y = -r * Math.log(Math.max(ts, 1E-10));
6294 return new Point(latlng.lng * d * r, y);
6297 unproject: function (point) {
6298 var d = 180 / Math.PI,
6300 tmp = this.R_MINOR / r,
6301 e = Math.sqrt(1 - tmp * tmp),
6302 ts = Math.exp(-point.y / r),
6303 phi = Math.PI / 2 - 2 * Math.atan(ts);
6305 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
6306 con = e * Math.sin(phi);
6307 con = Math.pow((1 - con) / (1 + con), e / 2);
6308 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
6312 return new LatLng(phi * d, point.x * d / r);
6319 * An object with methods for projecting geographical coordinates of the world onto
6320 * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection).
6322 * @property bounds: Bounds
6323 * The bounds (specified in CRS units) where the projection is valid
6325 * @method project(latlng: LatLng): Point
6326 * Projects geographical coordinates into a 2D point.
6327 * Only accepts actual `L.LatLng` instances, not arrays.
6329 * @method unproject(point: Point): LatLng
6330 * The inverse of `project`. Projects a 2D point into a geographical location.
6331 * Only accepts actual `L.Point` instances, not arrays.
6333 * Note that the projection instances do not inherit from Leafet's `Class` object,
6334 * and can't be instantiated. Also, new classes can't inherit from them,
6335 * and methods can't be added to them with the `include` function.
6342 var index = (Object.freeze || Object)({
6345 SphericalMercator: SphericalMercator
6350 * @crs L.CRS.EPSG3395
6352 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
6354 var EPSG3395 = extend({}, Earth, {
6356 projection: Mercator,
6358 transformation: (function () {
6359 var scale = 0.5 / (Math.PI * Mercator.R);
6360 return toTransformation(scale, 0.5, -scale, 0.5);
6366 * @crs L.CRS.EPSG4326
6368 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
6370 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
6371 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer`
6372 * with this CRS, ensure that there are two 256x256 pixel tiles covering the
6373 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
6374 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
6377 var EPSG4326 = extend({}, Earth, {
6380 transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
6387 * A simple CRS that maps longitude and latitude into `x` and `y` directly.
6388 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
6389 * axis should still be inverted (going from bottom to top). `distance()` returns
6390 * simple euclidean distance.
6393 var Simple = extend({}, CRS, {
6395 transformation: toTransformation(1, 0, -1, 0),
6397 scale: function (zoom) {
6398 return Math.pow(2, zoom);
6401 zoom: function (scale) {
6402 return Math.log(scale) / Math.LN2;
6405 distance: function (latlng1, latlng2) {
6406 var dx = latlng2.lng - latlng1.lng,
6407 dy = latlng2.lat - latlng1.lat;
6409 return Math.sqrt(dx * dx + dy * dy);
6416 CRS.EPSG3395 = EPSG3395;
6417 CRS.EPSG3857 = EPSG3857;
6418 CRS.EPSG900913 = EPSG900913;
6419 CRS.EPSG4326 = EPSG4326;
6420 CRS.Simple = Simple;
6428 * A set of methods from the Layer base class that all Leaflet layers use.
6429 * Inherits all methods, options and events from `L.Evented`.
6434 * var layer = L.Marker(latlng).addTo(map);
6440 * Fired after the layer is added to a map
6442 * @event remove: Event
6443 * Fired after the layer is removed from a map
6447 var Layer = Evented.extend({
6449 // Classes extending `L.Layer` will inherit the following options:
6451 // @option pane: String = 'overlayPane'
6452 // 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.
6453 pane: 'overlayPane',
6455 // @option attribution: String = null
6456 // String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox".
6459 bubblingMouseEvents: true
6463 * Classes extending `L.Layer` will inherit the following methods:
6465 * @method addTo(map: Map|LayerGroup): this
6466 * Adds the layer to the given map or layer group.
6468 addTo: function (map) {
6473 // @method remove: this
6474 // Removes the layer from the map it is currently active on.
6475 remove: function () {
6476 return this.removeFrom(this._map || this._mapToAdd);
6479 // @method removeFrom(map: Map): this
6480 // Removes the layer from the given map
6481 removeFrom: function (obj) {
6483 obj.removeLayer(this);
6488 // @method getPane(name? : String): HTMLElement
6489 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
6490 getPane: function (name) {
6491 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
6494 addInteractiveTarget: function (targetEl) {
6495 this._map._targets[stamp(targetEl)] = this;
6499 removeInteractiveTarget: function (targetEl) {
6500 delete this._map._targets[stamp(targetEl)];
6504 // @method getAttribution: String
6505 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
6506 getAttribution: function () {
6507 return this.options.attribution;
6510 _layerAdd: function (e) {
6513 // check in case layer gets added and then removed before the map is ready
6514 if (!map.hasLayer(this)) { return; }
6517 this._zoomAnimated = map._zoomAnimated;
6519 if (this.getEvents) {
6520 var events = this.getEvents();
6521 map.on(events, this);
6522 this.once('remove', function () {
6523 map.off(events, this);
6529 if (this.getAttribution && map.attributionControl) {
6530 map.attributionControl.addAttribution(this.getAttribution());
6534 map.fire('layeradd', {layer: this});
6538 /* @section Extension methods
6541 * Every layer should extend from `L.Layer` and (re-)implement the following methods.
6543 * @method onAdd(map: Map): this
6544 * 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).
6546 * @method onRemove(map: Map): this
6547 * 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).
6549 * @method getEvents(): Object
6550 * 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.
6552 * @method getAttribution(): String
6553 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
6555 * @method beforeAdd(map: Map): this
6556 * 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.
6561 * @section Layer events
6563 * @event layeradd: LayerEvent
6564 * Fired when a new layer is added to the map.
6566 * @event layerremove: LayerEvent
6567 * Fired when some layer is removed from the map
6569 * @section Methods for Layers and Controls
6572 // @method addLayer(layer: Layer): this
6573 // Adds the given layer to the map
6574 addLayer: function (layer) {
6575 if (!layer._layerAdd) {
6576 throw new Error('The provided object is not a Layer.');
6579 var id = stamp(layer);
6580 if (this._layers[id]) { return this; }
6581 this._layers[id] = layer;
6583 layer._mapToAdd = this;
6585 if (layer.beforeAdd) {
6586 layer.beforeAdd(this);
6589 this.whenReady(layer._layerAdd, layer);
6594 // @method removeLayer(layer: Layer): this
6595 // Removes the given layer from the map.
6596 removeLayer: function (layer) {
6597 var id = stamp(layer);
6599 if (!this._layers[id]) { return this; }
6602 layer.onRemove(this);
6605 if (layer.getAttribution && this.attributionControl) {
6606 this.attributionControl.removeAttribution(layer.getAttribution());
6609 delete this._layers[id];
6612 this.fire('layerremove', {layer: layer});
6613 layer.fire('remove');
6616 layer._map = layer._mapToAdd = null;
6621 // @method hasLayer(layer: Layer): Boolean
6622 // Returns `true` if the given layer is currently added to the map
6623 hasLayer: function (layer) {
6624 return !!layer && (stamp(layer) in this._layers);
6627 /* @method eachLayer(fn: Function, context?: Object): this
6628 * Iterates over the layers of the map, optionally specifying context of the iterator function.
6630 * map.eachLayer(function(layer){
6631 * layer.bindPopup('Hello');
6635 eachLayer: function (method, context) {
6636 for (var i in this._layers) {
6637 method.call(context, this._layers[i]);
6642 _addLayers: function (layers) {
6643 layers = layers ? (isArray(layers) ? layers : [layers]) : [];
6645 for (var i = 0, len = layers.length; i < len; i++) {
6646 this.addLayer(layers[i]);
6650 _addZoomLimit: function (layer) {
6651 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
6652 this._zoomBoundLayers[stamp(layer)] = layer;
6653 this._updateZoomLevels();
6657 _removeZoomLimit: function (layer) {
6658 var id = stamp(layer);
6660 if (this._zoomBoundLayers[id]) {
6661 delete this._zoomBoundLayers[id];
6662 this._updateZoomLevels();
6666 _updateZoomLevels: function () {
6667 var minZoom = Infinity,
6668 maxZoom = -Infinity,
6669 oldZoomSpan = this._getZoomSpan();
6671 for (var i in this._zoomBoundLayers) {
6672 var options = this._zoomBoundLayers[i].options;
6674 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
6675 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
6678 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
6679 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
6681 // @section Map state change events
6682 // @event zoomlevelschange: Event
6683 // Fired when the number of zoomlevels on the map is changed due
6684 // to adding or removing a layer.
6685 if (oldZoomSpan !== this._getZoomSpan()) {
6686 this.fire('zoomlevelschange');
6689 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
6690 this.setZoom(this._layersMaxZoom);
6692 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
6693 this.setZoom(this._layersMinZoom);
6703 * Used to group several layers and handle them as one. If you add it to the map,
6704 * any layers added or removed from the group will be added/removed on the map as
6705 * well. Extends `Layer`.
6710 * L.layerGroup([marker1, marker2])
6711 * .addLayer(polyline)
6716 var LayerGroup = Layer.extend({
6718 initialize: function (layers, options) {
6719 setOptions(this, options);
6726 for (i = 0, len = layers.length; i < len; i++) {
6727 this.addLayer(layers[i]);
6732 // @method addLayer(layer: Layer): this
6733 // Adds the given layer to the group.
6734 addLayer: function (layer) {
6735 var id = this.getLayerId(layer);
6737 this._layers[id] = layer;
6740 this._map.addLayer(layer);
6746 // @method removeLayer(layer: Layer): this
6747 // Removes the given layer from the group.
6749 // @method removeLayer(id: Number): this
6750 // Removes the layer with the given internal ID from the group.
6751 removeLayer: function (layer) {
6752 var id = layer in this._layers ? layer : this.getLayerId(layer);
6754 if (this._map && this._layers[id]) {
6755 this._map.removeLayer(this._layers[id]);
6758 delete this._layers[id];
6763 // @method hasLayer(layer: Layer): Boolean
6764 // Returns `true` if the given layer is currently added to the group.
6766 // @method hasLayer(id: Number): Boolean
6767 // Returns `true` if the given internal ID is currently added to the group.
6768 hasLayer: function (layer) {
6769 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
6772 // @method clearLayers(): this
6773 // Removes all the layers from the group.
6774 clearLayers: function () {
6775 return this.eachLayer(this.removeLayer, this);
6778 // @method invoke(methodName: String, …): this
6779 // Calls `methodName` on every layer contained in this group, passing any
6780 // additional parameters. Has no effect if the layers contained do not
6781 // implement `methodName`.
6782 invoke: function (methodName) {
6783 var args = Array.prototype.slice.call(arguments, 1),
6786 for (i in this._layers) {
6787 layer = this._layers[i];
6789 if (layer[methodName]) {
6790 layer[methodName].apply(layer, args);
6797 onAdd: function (map) {
6798 this.eachLayer(map.addLayer, map);
6801 onRemove: function (map) {
6802 this.eachLayer(map.removeLayer, map);
6805 // @method eachLayer(fn: Function, context?: Object): this
6806 // Iterates over the layers of the group, optionally specifying context of the iterator function.
6808 // group.eachLayer(function (layer) {
6809 // layer.bindPopup('Hello');
6812 eachLayer: function (method, context) {
6813 for (var i in this._layers) {
6814 method.call(context, this._layers[i]);
6819 // @method getLayer(id: Number): Layer
6820 // Returns the layer with the given internal ID.
6821 getLayer: function (id) {
6822 return this._layers[id];
6825 // @method getLayers(): Layer[]
6826 // Returns an array of all the layers added to the group.
6827 getLayers: function () {
6829 this.eachLayer(layers.push, layers);
6833 // @method setZIndex(zIndex: Number): this
6834 // Calls `setZIndex` on every layer contained in this group, passing the z-index.
6835 setZIndex: function (zIndex) {
6836 return this.invoke('setZIndex', zIndex);
6839 // @method getLayerId(layer: Layer): Number
6840 // Returns the internal ID for a layer
6841 getLayerId: function (layer) {
6842 return stamp(layer);
6847 // @factory L.layerGroup(layers?: Layer[], options?: Object)
6848 // Create a layer group, optionally given an initial set of layers and an `options` object.
6849 var layerGroup = function (layers, options) {
6850 return new LayerGroup(layers, options);
6854 * @class FeatureGroup
6855 * @aka L.FeatureGroup
6856 * @inherits LayerGroup
6858 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
6859 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
6860 * * Events are propagated to the `FeatureGroup`, so if the group has an event
6861 * handler, it will handle events from any of the layers. This includes mouse events
6862 * and custom events.
6863 * * Has `layeradd` and `layerremove` events
6868 * L.featureGroup([marker1, marker2, polyline])
6869 * .bindPopup('Hello world!')
6870 * .on('click', function() { alert('Clicked on a member of the group!'); })
6875 var FeatureGroup = LayerGroup.extend({
6877 addLayer: function (layer) {
6878 if (this.hasLayer(layer)) {
6882 layer.addEventParent(this);
6884 LayerGroup.prototype.addLayer.call(this, layer);
6886 // @event layeradd: LayerEvent
6887 // Fired when a layer is added to this `FeatureGroup`
6888 return this.fire('layeradd', {layer: layer});
6891 removeLayer: function (layer) {
6892 if (!this.hasLayer(layer)) {
6895 if (layer in this._layers) {
6896 layer = this._layers[layer];
6899 layer.removeEventParent(this);
6901 LayerGroup.prototype.removeLayer.call(this, layer);
6903 // @event layerremove: LayerEvent
6904 // Fired when a layer is removed from this `FeatureGroup`
6905 return this.fire('layerremove', {layer: layer});
6908 // @method setStyle(style: Path options): this
6909 // Sets the given path options to each layer of the group that has a `setStyle` method.
6910 setStyle: function (style) {
6911 return this.invoke('setStyle', style);
6914 // @method bringToFront(): this
6915 // Brings the layer group to the top of all other layers
6916 bringToFront: function () {
6917 return this.invoke('bringToFront');
6920 // @method bringToBack(): this
6921 // Brings the layer group to the back of all other layers
6922 bringToBack: function () {
6923 return this.invoke('bringToBack');
6926 // @method getBounds(): LatLngBounds
6927 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
6928 getBounds: function () {
6929 var bounds = new LatLngBounds();
6931 for (var id in this._layers) {
6932 var layer = this._layers[id];
6933 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
6939 // @factory L.featureGroup(layers: Layer[])
6940 // Create a feature group, optionally given an initial set of layers.
6941 var featureGroup = function (layers) {
6942 return new FeatureGroup(layers);
6949 * Represents an icon to provide when creating a marker.
6954 * var myIcon = L.icon({
6955 * iconUrl: 'my-icon.png',
6956 * iconRetinaUrl: 'my-icon@2x.png',
6957 * iconSize: [38, 95],
6958 * iconAnchor: [22, 94],
6959 * popupAnchor: [-3, -76],
6960 * shadowUrl: 'my-icon-shadow.png',
6961 * shadowRetinaUrl: 'my-icon-shadow@2x.png',
6962 * shadowSize: [68, 95],
6963 * shadowAnchor: [22, 94]
6966 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
6969 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
6973 var Icon = Class.extend({
6978 * @option iconUrl: String = null
6979 * **(required)** The URL to the icon image (absolute or relative to your script path).
6981 * @option iconRetinaUrl: String = null
6982 * The URL to a retina sized version of the icon image (absolute or relative to your
6983 * script path). Used for Retina screen devices.
6985 * @option iconSize: Point = null
6986 * Size of the icon image in pixels.
6988 * @option iconAnchor: Point = null
6989 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
6990 * will be aligned so that this point is at the marker's geographical location. Centered
6991 * by default if size is specified, also can be set in CSS with negative margins.
6993 * @option popupAnchor: Point = [0, 0]
6994 * The coordinates of the point from which popups will "open", relative to the icon anchor.
6996 * @option tooltipAnchor: Point = [0, 0]
6997 * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
6999 * @option shadowUrl: String = null
7000 * The URL to the icon shadow image. If not specified, no shadow image will be created.
7002 * @option shadowRetinaUrl: String = null
7004 * @option shadowSize: Point = null
7005 * Size of the shadow image in pixels.
7007 * @option shadowAnchor: Point = null
7008 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
7009 * as iconAnchor if not specified).
7011 * @option className: String = ''
7012 * A custom class name to assign to both icon and shadow images. Empty by default.
7016 popupAnchor: [0, 0],
7017 tooltipAnchor: [0, 0],
7020 initialize: function (options) {
7021 setOptions(this, options);
7024 // @method createIcon(oldIcon?: HTMLElement): HTMLElement
7025 // Called internally when the icon has to be shown, returns a `<img>` HTML element
7026 // styled according to the options.
7027 createIcon: function (oldIcon) {
7028 return this._createIcon('icon', oldIcon);
7031 // @method createShadow(oldIcon?: HTMLElement): HTMLElement
7032 // As `createIcon`, but for the shadow beneath it.
7033 createShadow: function (oldIcon) {
7034 return this._createIcon('shadow', oldIcon);
7037 _createIcon: function (name, oldIcon) {
7038 var src = this._getIconUrl(name);
7041 if (name === 'icon') {
7042 throw new Error('iconUrl not set in Icon options (see the docs).');
7047 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
7048 this._setIconStyles(img, name);
7053 _setIconStyles: function (img, name) {
7054 var options = this.options;
7055 var sizeOption = options[name + 'Size'];
7057 if (typeof sizeOption === 'number') {
7058 sizeOption = [sizeOption, sizeOption];
7061 var size = toPoint(sizeOption),
7062 anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
7063 size && size.divideBy(2, true));
7065 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
7068 img.style.marginLeft = (-anchor.x) + 'px';
7069 img.style.marginTop = (-anchor.y) + 'px';
7073 img.style.width = size.x + 'px';
7074 img.style.height = size.y + 'px';
7078 _createImg: function (src, el) {
7079 el = el || document.createElement('img');
7084 _getIconUrl: function (name) {
7085 return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
7090 // @factory L.icon(options: Icon options)
7091 // Creates an icon instance with the given options.
7092 function icon(options) {
7093 return new Icon(options);
7097 * @miniclass Icon.Default (Icon)
7098 * @aka L.Icon.Default
7101 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
7102 * no icon is specified. Points to the blue marker image distributed with Leaflet
7105 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
7106 * (which is a set of `Icon options`).
7108 * If you want to _completely_ replace the default icon, override the
7109 * `L.Marker.prototype.options.icon` with your own icon instead.
7112 var IconDefault = Icon.extend({
7115 iconUrl: 'marker-icon.png',
7116 iconRetinaUrl: 'marker-icon-2x.png',
7117 shadowUrl: 'marker-shadow.png',
7119 iconAnchor: [12, 41],
7120 popupAnchor: [1, -34],
7121 tooltipAnchor: [16, -28],
7122 shadowSize: [41, 41]
7125 _getIconUrl: function (name) {
7126 if (!IconDefault.imagePath) { // Deprecated, backwards-compatibility only
7127 IconDefault.imagePath = this._detectIconPath();
7130 // @option imagePath: String
7131 // `Icon.Default` will try to auto-detect the location of the
7132 // blue icon images. If you are placing these images in a non-standard
7133 // way, set this option to point to the right path.
7134 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
7137 _detectIconPath: function () {
7138 var el = create$1('div', 'leaflet-default-icon-path', document.body);
7139 var path = getStyle(el, 'background-image') ||
7140 getStyle(el, 'backgroundImage'); // IE8
7142 document.body.removeChild(el);
7144 if (path === null || path.indexOf('url') !== 0) {
7147 path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, '');
7155 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7159 /* @namespace Marker
7160 * @section Interaction handlers
7162 * 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:
7165 * marker.dragging.disable();
7168 * @property dragging: Handler
7169 * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
7172 var MarkerDrag = Handler.extend({
7173 initialize: function (marker) {
7174 this._marker = marker;
7177 addHooks: function () {
7178 var icon = this._marker._icon;
7180 if (!this._draggable) {
7181 this._draggable = new Draggable(icon, icon, true);
7184 this._draggable.on({
7185 dragstart: this._onDragStart,
7186 predrag: this._onPreDrag,
7188 dragend: this._onDragEnd
7191 addClass(icon, 'leaflet-marker-draggable');
7194 removeHooks: function () {
7195 this._draggable.off({
7196 dragstart: this._onDragStart,
7197 predrag: this._onPreDrag,
7199 dragend: this._onDragEnd
7202 if (this._marker._icon) {
7203 removeClass(this._marker._icon, 'leaflet-marker-draggable');
7207 moved: function () {
7208 return this._draggable && this._draggable._moved;
7211 _adjustPan: function (e) {
7212 var marker = this._marker,
7214 speed = this._marker.options.autoPanSpeed,
7215 padding = this._marker.options.autoPanPadding,
7216 iconPos = getPosition(marker._icon),
7217 bounds = map.getPixelBounds(),
7218 origin = map.getPixelOrigin();
7220 var panBounds = toBounds(
7221 bounds.min._subtract(origin).add(padding),
7222 bounds.max._subtract(origin).subtract(padding)
7225 if (!panBounds.contains(iconPos)) {
7226 // Compute incremental movement
7227 var movement = toPoint(
7228 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
7229 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
7231 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
7232 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
7233 ).multiplyBy(speed);
7235 map.panBy(movement, {animate: false});
7237 this._draggable._newPos._add(movement);
7238 this._draggable._startPos._add(movement);
7240 setPosition(marker._icon, this._draggable._newPos);
7243 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7247 _onDragStart: function () {
7248 // @section Dragging events
7249 // @event dragstart: Event
7250 // Fired when the user starts dragging the marker.
7252 // @event movestart: Event
7253 // Fired when the marker starts moving (because of dragging).
7255 this._oldLatLng = this._marker.getLatLng();
7262 _onPreDrag: function (e) {
7263 if (this._marker.options.autoPan) {
7264 cancelAnimFrame(this._panRequest);
7265 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7269 _onDrag: function (e) {
7270 var marker = this._marker,
7271 shadow = marker._shadow,
7272 iconPos = getPosition(marker._icon),
7273 latlng = marker._map.layerPointToLatLng(iconPos);
7275 // update shadow position
7277 setPosition(shadow, iconPos);
7280 marker._latlng = latlng;
7282 e.oldLatLng = this._oldLatLng;
7284 // @event drag: Event
7285 // Fired repeatedly while the user drags the marker.
7291 _onDragEnd: function (e) {
7292 // @event dragend: DragEndEvent
7293 // Fired when the user stops dragging the marker.
7295 cancelAnimFrame(this._panRequest);
7297 // @event moveend: Event
7298 // Fired when the marker stops moving (because of dragging).
7299 delete this._oldLatLng;
7302 .fire('dragend', e);
7308 * @inherits Interactive layer
7310 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
7315 * L.marker([50.5, 30.5]).addTo(map);
7319 var Marker = Layer.extend({
7322 // @aka Marker options
7324 // @option icon: Icon = *
7325 // Icon instance to use for rendering the marker.
7326 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
7327 // If not specified, a common instance of `L.Icon.Default` is used.
7328 icon: new IconDefault(),
7330 // Option inherited from "Interactive layer" abstract class
7333 // @option draggable: Boolean = false
7334 // Whether the marker is draggable with mouse/touch or not.
7337 // @option autoPan: Boolean = false
7338 // Set it to `true` if you want the map to do panning animation when marker hits the edges.
7341 // @option autoPanPadding: Point = Point(50, 50)
7342 // Equivalent of setting both top left and bottom right autopan padding to the same value.
7343 autoPanPadding: [50, 50],
7345 // @option autoPanSpeed: Number = 10
7346 // Number of pixels the map should move by.
7349 // @option keyboard: Boolean = true
7350 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
7353 // @option title: String = ''
7354 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
7357 // @option alt: String = ''
7358 // Text for the `alt` attribute of the icon image (useful for accessibility).
7361 // @option zIndexOffset: Number = 0
7362 // 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).
7365 // @option opacity: Number = 1.0
7366 // The opacity of the marker.
7369 // @option riseOnHover: Boolean = false
7370 // If `true`, the marker will get on top of others when you hover the mouse over it.
7373 // @option riseOffset: Number = 250
7374 // The z-index offset used for the `riseOnHover` feature.
7377 // @option pane: String = 'markerPane'
7378 // `Map pane` where the markers icon will be added.
7381 // @option bubblingMouseEvents: Boolean = false
7382 // When `true`, a mouse event on this marker will trigger the same event on the map
7383 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7384 bubblingMouseEvents: false
7389 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
7392 initialize: function (latlng, options) {
7393 setOptions(this, options);
7394 this._latlng = toLatLng(latlng);
7397 onAdd: function (map) {
7398 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
7400 if (this._zoomAnimated) {
7401 map.on('zoomanim', this._animateZoom, this);
7408 onRemove: function (map) {
7409 if (this.dragging && this.dragging.enabled()) {
7410 this.options.draggable = true;
7411 this.dragging.removeHooks();
7413 delete this.dragging;
7415 if (this._zoomAnimated) {
7416 map.off('zoomanim', this._animateZoom, this);
7420 this._removeShadow();
7423 getEvents: function () {
7426 viewreset: this.update
7430 // @method getLatLng: LatLng
7431 // Returns the current geographical position of the marker.
7432 getLatLng: function () {
7433 return this._latlng;
7436 // @method setLatLng(latlng: LatLng): this
7437 // Changes the marker position to the given point.
7438 setLatLng: function (latlng) {
7439 var oldLatLng = this._latlng;
7440 this._latlng = toLatLng(latlng);
7443 // @event move: Event
7444 // 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`.
7445 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7448 // @method setZIndexOffset(offset: Number): this
7449 // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
7450 setZIndexOffset: function (offset) {
7451 this.options.zIndexOffset = offset;
7452 return this.update();
7455 // @method setIcon(icon: Icon): this
7456 // Changes the marker icon.
7457 setIcon: function (icon) {
7459 this.options.icon = icon;
7467 this.bindPopup(this._popup, this._popup.options);
7473 getElement: function () {
7477 update: function () {
7479 if (this._icon && this._map) {
7480 var pos = this._map.latLngToLayerPoint(this._latlng).round();
7487 _initIcon: function () {
7488 var options = this.options,
7489 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
7491 var icon = options.icon.createIcon(this._icon),
7494 // if we're not reusing the icon, remove the old one and init new one
7495 if (icon !== this._icon) {
7501 if (options.title) {
7502 icon.title = options.title;
7505 if (icon.tagName === 'IMG') {
7506 icon.alt = options.alt || '';
7510 addClass(icon, classToAdd);
7512 if (options.keyboard) {
7513 icon.tabIndex = '0';
7518 if (options.riseOnHover) {
7520 mouseover: this._bringToFront,
7521 mouseout: this._resetZIndex
7525 var newShadow = options.icon.createShadow(this._shadow),
7528 if (newShadow !== this._shadow) {
7529 this._removeShadow();
7534 addClass(newShadow, classToAdd);
7537 this._shadow = newShadow;
7540 if (options.opacity < 1) {
7541 this._updateOpacity();
7546 this.getPane().appendChild(this._icon);
7548 this._initInteraction();
7549 if (newShadow && addShadow) {
7550 this.getPane('shadowPane').appendChild(this._shadow);
7554 _removeIcon: function () {
7555 if (this.options.riseOnHover) {
7557 mouseover: this._bringToFront,
7558 mouseout: this._resetZIndex
7563 this.removeInteractiveTarget(this._icon);
7568 _removeShadow: function () {
7570 remove(this._shadow);
7572 this._shadow = null;
7575 _setPos: function (pos) {
7576 setPosition(this._icon, pos);
7579 setPosition(this._shadow, pos);
7582 this._zIndex = pos.y + this.options.zIndexOffset;
7584 this._resetZIndex();
7587 _updateZIndex: function (offset) {
7588 this._icon.style.zIndex = this._zIndex + offset;
7591 _animateZoom: function (opt) {
7592 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
7597 _initInteraction: function () {
7599 if (!this.options.interactive) { return; }
7601 addClass(this._icon, 'leaflet-interactive');
7603 this.addInteractiveTarget(this._icon);
7606 var draggable = this.options.draggable;
7607 if (this.dragging) {
7608 draggable = this.dragging.enabled();
7609 this.dragging.disable();
7612 this.dragging = new MarkerDrag(this);
7615 this.dragging.enable();
7620 // @method setOpacity(opacity: Number): this
7621 // Changes the opacity of the marker.
7622 setOpacity: function (opacity) {
7623 this.options.opacity = opacity;
7625 this._updateOpacity();
7631 _updateOpacity: function () {
7632 var opacity = this.options.opacity;
7634 setOpacity(this._icon, opacity);
7637 setOpacity(this._shadow, opacity);
7641 _bringToFront: function () {
7642 this._updateZIndex(this.options.riseOffset);
7645 _resetZIndex: function () {
7646 this._updateZIndex(0);
7649 _getPopupAnchor: function () {
7650 return this.options.icon.options.popupAnchor;
7653 _getTooltipAnchor: function () {
7654 return this.options.icon.options.tooltipAnchor;
7659 // factory L.marker(latlng: LatLng, options? : Marker options)
7661 // @factory L.marker(latlng: LatLng, options? : Marker options)
7662 // Instantiates a Marker object given a geographical point and optionally an options object.
7663 function marker(latlng, options) {
7664 return new Marker(latlng, options);
7670 * @inherits Interactive layer
7672 * An abstract class that contains options and constants shared between vector
7673 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
7676 var Path = Layer.extend({
7679 // @aka Path options
7681 // @option stroke: Boolean = true
7682 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
7685 // @option color: String = '#3388ff'
7689 // @option weight: Number = 3
7690 // Stroke width in pixels
7693 // @option opacity: Number = 1.0
7697 // @option lineCap: String= 'round'
7698 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
7701 // @option lineJoin: String = 'round'
7702 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
7705 // @option dashArray: String = null
7706 // 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).
7709 // @option dashOffset: String = null
7710 // 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).
7713 // @option fill: Boolean = depends
7714 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
7717 // @option fillColor: String = *
7718 // Fill color. Defaults to the value of the [`color`](#path-color) option
7721 // @option fillOpacity: Number = 0.2
7725 // @option fillRule: String = 'evenodd'
7726 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
7727 fillRule: 'evenodd',
7731 // Option inherited from "Interactive layer" abstract class
7734 // @option bubblingMouseEvents: Boolean = true
7735 // When `true`, a mouse event on this path will trigger the same event on the map
7736 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7737 bubblingMouseEvents: true
7740 beforeAdd: function (map) {
7741 // Renderer is set here because we need to call renderer.getEvents
7742 // before this.getEvents.
7743 this._renderer = map.getRenderer(this);
7746 onAdd: function () {
7747 this._renderer._initPath(this);
7749 this._renderer._addPath(this);
7752 onRemove: function () {
7753 this._renderer._removePath(this);
7756 // @method redraw(): this
7757 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
7758 redraw: function () {
7760 this._renderer._updatePath(this);
7765 // @method setStyle(style: Path options): this
7766 // Changes the appearance of a Path based on the options in the `Path options` object.
7767 setStyle: function (style) {
7768 setOptions(this, style);
7769 if (this._renderer) {
7770 this._renderer._updateStyle(this);
7775 // @method bringToFront(): this
7776 // Brings the layer to the top of all path layers.
7777 bringToFront: function () {
7778 if (this._renderer) {
7779 this._renderer._bringToFront(this);
7784 // @method bringToBack(): this
7785 // Brings the layer to the bottom of all path layers.
7786 bringToBack: function () {
7787 if (this._renderer) {
7788 this._renderer._bringToBack(this);
7793 getElement: function () {
7797 _reset: function () {
7798 // defined in child classes
7803 _clickTolerance: function () {
7804 // used when doing hit detection for Canvas layers
7805 return (this.options.stroke ? this.options.weight / 2 : 0) + this._renderer.options.tolerance;
7810 * @class CircleMarker
7811 * @aka L.CircleMarker
7814 * A circle of a fixed size with radius specified in pixels. Extends `Path`.
7817 var CircleMarker = Path.extend({
7820 // @aka CircleMarker options
7824 // @option radius: Number = 10
7825 // Radius of the circle marker, in pixels
7829 initialize: function (latlng, options) {
7830 setOptions(this, options);
7831 this._latlng = toLatLng(latlng);
7832 this._radius = this.options.radius;
7835 // @method setLatLng(latLng: LatLng): this
7836 // Sets the position of a circle marker to a new location.
7837 setLatLng: function (latlng) {
7838 this._latlng = toLatLng(latlng);
7840 return this.fire('move', {latlng: this._latlng});
7843 // @method getLatLng(): LatLng
7844 // Returns the current geographical position of the circle marker
7845 getLatLng: function () {
7846 return this._latlng;
7849 // @method setRadius(radius: Number): this
7850 // Sets the radius of a circle marker. Units are in pixels.
7851 setRadius: function (radius) {
7852 this.options.radius = this._radius = radius;
7853 return this.redraw();
7856 // @method getRadius(): Number
7857 // Returns the current radius of the circle
7858 getRadius: function () {
7859 return this._radius;
7862 setStyle : function (options) {
7863 var radius = options && options.radius || this._radius;
7864 Path.prototype.setStyle.call(this, options);
7865 this.setRadius(radius);
7869 _project: function () {
7870 this._point = this._map.latLngToLayerPoint(this._latlng);
7871 this._updateBounds();
7874 _updateBounds: function () {
7875 var r = this._radius,
7876 r2 = this._radiusY || r,
7877 w = this._clickTolerance(),
7878 p = [r + w, r2 + w];
7879 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
7882 _update: function () {
7888 _updatePath: function () {
7889 this._renderer._updateCircle(this);
7892 _empty: function () {
7893 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
7896 // Needed by the `Canvas` renderer for interactivity
7897 _containsPoint: function (p) {
7898 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
7903 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
7904 // Instantiates a circle marker object given a geographical point, and an optional options object.
7905 function circleMarker(latlng, options) {
7906 return new CircleMarker(latlng, options);
7912 * @inherits CircleMarker
7914 * A class for drawing circle overlays on a map. Extends `CircleMarker`.
7916 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
7921 * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
7925 var Circle = CircleMarker.extend({
7927 initialize: function (latlng, options, legacyOptions) {
7928 if (typeof options === 'number') {
7929 // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
7930 options = extend({}, legacyOptions, {radius: options});
7932 setOptions(this, options);
7933 this._latlng = toLatLng(latlng);
7935 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
7938 // @aka Circle options
7939 // @option radius: Number; Radius of the circle, in meters.
7940 this._mRadius = this.options.radius;
7943 // @method setRadius(radius: Number): this
7944 // Sets the radius of a circle. Units are in meters.
7945 setRadius: function (radius) {
7946 this._mRadius = radius;
7947 return this.redraw();
7950 // @method getRadius(): Number
7951 // Returns the current radius of a circle. Units are in meters.
7952 getRadius: function () {
7953 return this._mRadius;
7956 // @method getBounds(): LatLngBounds
7957 // Returns the `LatLngBounds` of the path.
7958 getBounds: function () {
7959 var half = [this._radius, this._radiusY || this._radius];
7961 return new LatLngBounds(
7962 this._map.layerPointToLatLng(this._point.subtract(half)),
7963 this._map.layerPointToLatLng(this._point.add(half)));
7966 setStyle: Path.prototype.setStyle,
7968 _project: function () {
7970 var lng = this._latlng.lng,
7971 lat = this._latlng.lat,
7973 crs = map.options.crs;
7975 if (crs.distance === Earth.distance) {
7976 var d = Math.PI / 180,
7977 latR = (this._mRadius / Earth.R) / d,
7978 top = map.project([lat + latR, lng]),
7979 bottom = map.project([lat - latR, lng]),
7980 p = top.add(bottom).divideBy(2),
7981 lat2 = map.unproject(p).lat,
7982 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
7983 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
7985 if (isNaN(lngR) || lngR === 0) {
7986 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
7989 this._point = p.subtract(map.getPixelOrigin());
7990 this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
7991 this._radiusY = p.y - top.y;
7994 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
7996 this._point = map.latLngToLayerPoint(this._latlng);
7997 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
8000 this._updateBounds();
8004 // @factory L.circle(latlng: LatLng, options?: Circle options)
8005 // Instantiates a circle object given a geographical point, and an options object
8006 // which contains the circle radius.
8008 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
8009 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
8010 // Do not use in new applications or plugins.
8011 function circle(latlng, options, legacyOptions) {
8012 return new Circle(latlng, options, legacyOptions);
8020 * A class for drawing polyline overlays on a map. Extends `Path`.
8025 * // create a red polyline from an array of LatLng points
8032 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
8034 * // zoom the map to the polyline
8035 * map.fitBounds(polyline.getBounds());
8038 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
8041 * // create a red polyline from an array of arrays of LatLng points
8043 * [[45.51, -122.68],
8054 var Polyline = Path.extend({
8057 // @aka Polyline options
8059 // @option smoothFactor: Number = 1.0
8060 // How much to simplify the polyline on each zoom level. More means
8061 // better performance and smoother look, and less means more accurate representation.
8064 // @option noClip: Boolean = false
8065 // Disable polyline clipping.
8069 initialize: function (latlngs, options) {
8070 setOptions(this, options);
8071 this._setLatLngs(latlngs);
8074 // @method getLatLngs(): LatLng[]
8075 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8076 getLatLngs: function () {
8077 return this._latlngs;
8080 // @method setLatLngs(latlngs: LatLng[]): this
8081 // Replaces all the points in the polyline with the given array of geographical points.
8082 setLatLngs: function (latlngs) {
8083 this._setLatLngs(latlngs);
8084 return this.redraw();
8087 // @method isEmpty(): Boolean
8088 // Returns `true` if the Polyline has no LatLngs.
8089 isEmpty: function () {
8090 return !this._latlngs.length;
8093 // @method closestLayerPoint(p: Point): Point
8094 // Returns the point closest to `p` on the Polyline.
8095 closestLayerPoint: function (p) {
8096 var minDistance = Infinity,
8098 closest = _sqClosestPointOnSegment,
8101 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8102 var points = this._parts[j];
8104 for (var i = 1, len = points.length; i < len; i++) {
8108 var sqDist = closest(p, p1, p2, true);
8110 if (sqDist < minDistance) {
8111 minDistance = sqDist;
8112 minPoint = closest(p, p1, p2);
8117 minPoint.distance = Math.sqrt(minDistance);
8122 // @method getCenter(): LatLng
8123 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
8124 getCenter: function () {
8125 // throws error when not yet added to map as this center calculation requires projected coordinates
8127 throw new Error('Must add layer to map before using getCenter()');
8130 var i, halfDist, segDist, dist, p1, p2, ratio,
8131 points = this._rings[0],
8132 len = points.length;
8134 if (!len) { return null; }
8136 // polyline centroid algorithm; only uses the first ring if there are multiple
8138 for (i = 0, halfDist = 0; i < len - 1; i++) {
8139 halfDist += points[i].distanceTo(points[i + 1]) / 2;
8142 // The line is so small in the current view that all points are on the same pixel.
8143 if (halfDist === 0) {
8144 return this._map.layerPointToLatLng(points[0]);
8147 for (i = 0, dist = 0; i < len - 1; i++) {
8150 segDist = p1.distanceTo(p2);
8153 if (dist > halfDist) {
8154 ratio = (dist - halfDist) / segDist;
8155 return this._map.layerPointToLatLng([
8156 p2.x - ratio * (p2.x - p1.x),
8157 p2.y - ratio * (p2.y - p1.y)
8163 // @method getBounds(): LatLngBounds
8164 // Returns the `LatLngBounds` of the path.
8165 getBounds: function () {
8166 return this._bounds;
8169 // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
8170 // Adds a given point to the polyline. By default, adds to the first ring of
8171 // the polyline in case of a multi-polyline, but can be overridden by passing
8172 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8173 addLatLng: function (latlng, latlngs) {
8174 latlngs = latlngs || this._defaultShape();
8175 latlng = toLatLng(latlng);
8176 latlngs.push(latlng);
8177 this._bounds.extend(latlng);
8178 return this.redraw();
8181 _setLatLngs: function (latlngs) {
8182 this._bounds = new LatLngBounds();
8183 this._latlngs = this._convertLatLngs(latlngs);
8186 _defaultShape: function () {
8187 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
8190 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8191 _convertLatLngs: function (latlngs) {
8193 flat = isFlat(latlngs);
8195 for (var i = 0, len = latlngs.length; i < len; i++) {
8197 result[i] = toLatLng(latlngs[i]);
8198 this._bounds.extend(result[i]);
8200 result[i] = this._convertLatLngs(latlngs[i]);
8207 _project: function () {
8208 var pxBounds = new Bounds();
8210 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8212 var w = this._clickTolerance(),
8213 p = new Point(w, w);
8215 if (this._bounds.isValid() && pxBounds.isValid()) {
8216 pxBounds.min._subtract(p);
8217 pxBounds.max._add(p);
8218 this._pxBounds = pxBounds;
8222 // recursively turns latlngs into a set of rings with projected coordinates
8223 _projectLatlngs: function (latlngs, result, projectedBounds) {
8224 var flat = latlngs[0] instanceof LatLng,
8225 len = latlngs.length,
8230 for (i = 0; i < len; i++) {
8231 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8232 projectedBounds.extend(ring[i]);
8236 for (i = 0; i < len; i++) {
8237 this._projectLatlngs(latlngs[i], result, projectedBounds);
8242 // clip polyline by renderer bounds so that we have less to render for performance
8243 _clipPoints: function () {
8244 var bounds = this._renderer._bounds;
8247 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8251 if (this.options.noClip) {
8252 this._parts = this._rings;
8256 var parts = this._parts,
8257 i, j, k, len, len2, segment, points;
8259 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8260 points = this._rings[i];
8262 for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8263 segment = clipSegment(points[j], points[j + 1], bounds, j, true);
8265 if (!segment) { continue; }
8267 parts[k] = parts[k] || [];
8268 parts[k].push(segment[0]);
8270 // if segment goes out of screen, or it's the last one, it's the end of the line part
8271 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8272 parts[k].push(segment[1]);
8279 // simplify each clipped part of the polyline for performance
8280 _simplifyPoints: function () {
8281 var parts = this._parts,
8282 tolerance = this.options.smoothFactor;
8284 for (var i = 0, len = parts.length; i < len; i++) {
8285 parts[i] = simplify(parts[i], tolerance);
8289 _update: function () {
8290 if (!this._map) { return; }
8293 this._simplifyPoints();
8297 _updatePath: function () {
8298 this._renderer._updatePoly(this);
8301 // Needed by the `Canvas` renderer for interactivity
8302 _containsPoint: function (p, closed) {
8303 var i, j, k, len, len2, part,
8304 w = this._clickTolerance();
8306 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8308 // hit detection for polylines
8309 for (i = 0, len = this._parts.length; i < len; i++) {
8310 part = this._parts[i];
8312 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8313 if (!closed && (j === 0)) { continue; }
8315 if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
8324 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8325 // Instantiates a polyline object given an array of geographical points and
8326 // optionally an options object. You can create a `Polyline` object with
8327 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8328 // of geographic points.
8329 function polyline(latlngs, options) {
8330 return new Polyline(latlngs, options);
8333 // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
8334 Polyline._flat = _flat;
8339 * @inherits Polyline
8341 * A class for drawing polygon overlays on a map. Extends `Polyline`.
8343 * 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.
8349 * // create a red polygon from an array of LatLng points
8350 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
8352 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8354 * // zoom the map to the polygon
8355 * map.fitBounds(polygon.getBounds());
8358 * 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:
8362 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8363 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8367 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8371 * [ // first polygon
8372 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8373 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8375 * [ // second polygon
8376 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
8382 var Polygon = Polyline.extend({
8388 isEmpty: function () {
8389 return !this._latlngs.length || !this._latlngs[0].length;
8392 getCenter: function () {
8393 // throws error when not yet added to map as this center calculation requires projected coordinates
8395 throw new Error('Must add layer to map before using getCenter()');
8398 var i, j, p1, p2, f, area, x, y, center,
8399 points = this._rings[0],
8400 len = points.length;
8402 if (!len) { return null; }
8404 // polygon centroid algorithm; only uses the first ring if there are multiple
8408 for (i = 0, j = len - 1; i < len; j = i++) {
8412 f = p1.y * p2.x - p2.y * p1.x;
8413 x += (p1.x + p2.x) * f;
8414 y += (p1.y + p2.y) * f;
8419 // Polygon is so small that all points are on same pixel.
8422 center = [x / area, y / area];
8424 return this._map.layerPointToLatLng(center);
8427 _convertLatLngs: function (latlngs) {
8428 var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
8429 len = result.length;
8431 // remove last point if it equals first one
8432 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
8438 _setLatLngs: function (latlngs) {
8439 Polyline.prototype._setLatLngs.call(this, latlngs);
8440 if (isFlat(this._latlngs)) {
8441 this._latlngs = [this._latlngs];
8445 _defaultShape: function () {
8446 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8449 _clipPoints: function () {
8450 // polygons need a different clipping algorithm so we redefine that
8452 var bounds = this._renderer._bounds,
8453 w = this.options.weight,
8454 p = new Point(w, w);
8456 // increase clip padding by stroke width to avoid stroke on clip edges
8457 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
8460 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8464 if (this.options.noClip) {
8465 this._parts = this._rings;
8469 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8470 clipped = clipPolygon(this._rings[i], bounds, true);
8471 if (clipped.length) {
8472 this._parts.push(clipped);
8477 _updatePath: function () {
8478 this._renderer._updatePoly(this, true);
8481 // Needed by the `Canvas` renderer for interactivity
8482 _containsPoint: function (p) {
8484 part, p1, p2, i, j, k, len, len2;
8486 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8488 // ray casting algorithm for detecting if point is in polygon
8489 for (i = 0, len = this._parts.length; i < len; i++) {
8490 part = this._parts[i];
8492 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8496 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)) {
8502 // also check if it's on polygon stroke
8503 return inside || Polyline.prototype._containsPoint.call(this, p, true);
8509 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8510 function polygon(latlngs, options) {
8511 return new Polygon(latlngs, options);
8517 * @inherits FeatureGroup
8519 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
8520 * GeoJSON data and display it on the map. Extends `FeatureGroup`.
8526 * style: function (feature) {
8527 * return {color: feature.properties.color};
8529 * }).bindPopup(function (layer) {
8530 * return layer.feature.properties.description;
8535 var GeoJSON = FeatureGroup.extend({
8538 * @aka GeoJSON options
8540 * @option pointToLayer: Function = *
8541 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
8542 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
8543 * The default is to spawn a default `Marker`:
8545 * function(geoJsonPoint, latlng) {
8546 * return L.marker(latlng);
8550 * @option style: Function = *
8551 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
8552 * called internally when data is added.
8553 * The default value is to not override any defaults:
8555 * function (geoJsonFeature) {
8560 * @option onEachFeature: Function = *
8561 * A `Function` that will be called once for each created `Feature`, after it has
8562 * been created and styled. Useful for attaching events and popups to features.
8563 * The default is to do nothing with the newly created layers:
8565 * function (feature, layer) {}
8568 * @option filter: Function = *
8569 * A `Function` that will be used to decide whether to include a feature or not.
8570 * The default is to include all features:
8572 * function (geoJsonFeature) {
8576 * Note: dynamically changing the `filter` option will have effect only on newly
8577 * added data. It will _not_ re-evaluate already included features.
8579 * @option coordsToLatLng: Function = *
8580 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
8581 * The default is the `coordsToLatLng` static method.
8584 initialize: function (geojson, options) {
8585 setOptions(this, options);
8590 this.addData(geojson);
8594 // @method addData( <GeoJSON> data ): this
8595 // Adds a GeoJSON object to the layer.
8596 addData: function (geojson) {
8597 var features = isArray(geojson) ? geojson : geojson.features,
8601 for (i = 0, len = features.length; i < len; i++) {
8602 // only add this if geometry or geometries are set and not null
8603 feature = features[i];
8604 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
8605 this.addData(feature);
8611 var options = this.options;
8613 if (options.filter && !options.filter(geojson)) { return this; }
8615 var layer = geometryToLayer(geojson, options);
8619 layer.feature = asFeature(geojson);
8621 layer.defaultOptions = layer.options;
8622 this.resetStyle(layer);
8624 if (options.onEachFeature) {
8625 options.onEachFeature(geojson, layer);
8628 return this.addLayer(layer);
8631 // @method resetStyle( <Path> layer ): this
8632 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
8633 resetStyle: function (layer) {
8634 // reset any custom styles
8635 layer.options = extend({}, layer.defaultOptions);
8636 this._setLayerStyle(layer, this.options.style);
8640 // @method setStyle( <Function> style ): this
8641 // Changes styles of GeoJSON vector layers with the given style function.
8642 setStyle: function (style) {
8643 return this.eachLayer(function (layer) {
8644 this._setLayerStyle(layer, style);
8648 _setLayerStyle: function (layer, style) {
8649 if (typeof style === 'function') {
8650 style = style(layer.feature);
8652 if (layer.setStyle) {
8653 layer.setStyle(style);
8659 // There are several static functions which can be called without instantiating L.GeoJSON:
8661 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
8662 // Creates a `Layer` from a given GeoJSON feature. Can use a custom
8663 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
8664 // functions if provided as options.
8665 function geometryToLayer(geojson, options) {
8667 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
8668 coords = geometry ? geometry.coordinates : null,
8670 pointToLayer = options && options.pointToLayer,
8671 _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
8672 latlng, latlngs, i, len;
8674 if (!coords && !geometry) {
8678 switch (geometry.type) {
8680 latlng = _coordsToLatLng(coords);
8681 return pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng);
8684 for (i = 0, len = coords.length; i < len; i++) {
8685 latlng = _coordsToLatLng(coords[i]);
8686 layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng));
8688 return new FeatureGroup(layers);
8691 case 'MultiLineString':
8692 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
8693 return new Polyline(latlngs, options);
8696 case 'MultiPolygon':
8697 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
8698 return new Polygon(latlngs, options);
8700 case 'GeometryCollection':
8701 for (i = 0, len = geometry.geometries.length; i < len; i++) {
8702 var layer = geometryToLayer({
8703 geometry: geometry.geometries[i],
8705 properties: geojson.properties
8712 return new FeatureGroup(layers);
8715 throw new Error('Invalid GeoJSON object.');
8719 // @function coordsToLatLng(coords: Array): LatLng
8720 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
8721 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
8722 function coordsToLatLng(coords) {
8723 return new LatLng(coords[1], coords[0], coords[2]);
8726 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
8727 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
8728 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
8729 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
8730 function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
8733 for (var i = 0, len = coords.length, latlng; i < len; i++) {
8734 latlng = levelsDeep ?
8735 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
8736 (_coordsToLatLng || coordsToLatLng)(coords[i]);
8738 latlngs.push(latlng);
8744 // @function latLngToCoords(latlng: LatLng, precision?: Number): Array
8745 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
8746 function latLngToCoords(latlng, precision) {
8747 precision = typeof precision === 'number' ? precision : 6;
8748 return latlng.alt !== undefined ?
8749 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
8750 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
8753 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
8754 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
8755 // `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.
8756 function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
8759 for (var i = 0, len = latlngs.length; i < len; i++) {
8760 coords.push(levelsDeep ?
8761 latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) :
8762 latLngToCoords(latlngs[i], precision));
8765 if (!levelsDeep && closed) {
8766 coords.push(coords[0]);
8772 function getFeature(layer, newGeometry) {
8773 return layer.feature ?
8774 extend({}, layer.feature, {geometry: newGeometry}) :
8775 asFeature(newGeometry);
8778 // @function asFeature(geojson: Object): Object
8779 // Normalize GeoJSON geometries/features into GeoJSON features.
8780 function asFeature(geojson) {
8781 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
8792 var PointToGeoJSON = {
8793 toGeoJSON: function (precision) {
8794 return getFeature(this, {
8796 coordinates: latLngToCoords(this.getLatLng(), precision)
8801 // @namespace Marker
8802 // @method toGeoJSON(): Object
8803 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
8804 Marker.include(PointToGeoJSON);
8806 // @namespace CircleMarker
8807 // @method toGeoJSON(): Object
8808 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
8809 Circle.include(PointToGeoJSON);
8810 CircleMarker.include(PointToGeoJSON);
8813 // @namespace Polyline
8814 // @method toGeoJSON(): Object
8815 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
8817 toGeoJSON: function (precision) {
8818 var multi = !isFlat(this._latlngs);
8820 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
8822 return getFeature(this, {
8823 type: (multi ? 'Multi' : '') + 'LineString',
8829 // @namespace Polygon
8830 // @method toGeoJSON(): Object
8831 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
8833 toGeoJSON: function (precision) {
8834 var holes = !isFlat(this._latlngs),
8835 multi = holes && !isFlat(this._latlngs[0]);
8837 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
8843 return getFeature(this, {
8844 type: (multi ? 'Multi' : '') + 'Polygon',
8851 // @namespace LayerGroup
8852 LayerGroup.include({
8853 toMultiPoint: function (precision) {
8856 this.eachLayer(function (layer) {
8857 coords.push(layer.toGeoJSON(precision).geometry.coordinates);
8860 return getFeature(this, {
8866 // @method toGeoJSON(): Object
8867 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
8868 toGeoJSON: function (precision) {
8870 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
8872 if (type === 'MultiPoint') {
8873 return this.toMultiPoint(precision);
8876 var isGeometryCollection = type === 'GeometryCollection',
8879 this.eachLayer(function (layer) {
8880 if (layer.toGeoJSON) {
8881 var json = layer.toGeoJSON(precision);
8882 if (isGeometryCollection) {
8883 jsons.push(json.geometry);
8885 var feature = asFeature(json);
8886 // Squash nested feature collections
8887 if (feature.type === 'FeatureCollection') {
8888 jsons.push.apply(jsons, feature.features);
8890 jsons.push(feature);
8896 if (isGeometryCollection) {
8897 return getFeature(this, {
8899 type: 'GeometryCollection'
8904 type: 'FeatureCollection',
8910 // @namespace GeoJSON
8911 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
8912 // Creates a GeoJSON layer. Optionally accepts an object in
8913 // [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
8914 // (you can alternatively add it later with `addData` method) and an `options` object.
8915 function geoJSON(geojson, options) {
8916 return new GeoJSON(geojson, options);
8919 // Backward compatibility.
8920 var geoJson = geoJSON;
8923 * @class ImageOverlay
8924 * @aka L.ImageOverlay
8925 * @inherits Interactive layer
8927 * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
8932 * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
8933 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
8934 * L.imageOverlay(imageUrl, imageBounds).addTo(map);
8938 var ImageOverlay = Layer.extend({
8941 // @aka ImageOverlay options
8943 // @option opacity: Number = 1.0
8944 // The opacity of the image overlay.
8947 // @option alt: String = ''
8948 // Text for the `alt` attribute of the image (useful for accessibility).
8951 // @option interactive: Boolean = false
8952 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
8955 // @option crossOrigin: Boolean|String = false
8956 // Whether the crossOrigin attribute will be added to the image.
8957 // If a String is provided, the image will have its crossOrigin attribute set to the String provided. This is needed if you want to access image pixel data.
8958 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
8961 // @option errorOverlayUrl: String = ''
8962 // URL to the overlay image to show in place of the overlay that failed to load.
8963 errorOverlayUrl: '',
8965 // @option zIndex: Number = 1
8966 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the tile layer.
8969 // @option className: String = ''
8970 // A custom class name to assign to the image. Empty by default.
8974 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
8976 this._bounds = toLatLngBounds(bounds);
8978 setOptions(this, options);
8981 onAdd: function () {
8985 if (this.options.opacity < 1) {
8986 this._updateOpacity();
8990 if (this.options.interactive) {
8991 addClass(this._image, 'leaflet-interactive');
8992 this.addInteractiveTarget(this._image);
8995 this.getPane().appendChild(this._image);
8999 onRemove: function () {
9000 remove(this._image);
9001 if (this.options.interactive) {
9002 this.removeInteractiveTarget(this._image);
9006 // @method setOpacity(opacity: Number): this
9007 // Sets the opacity of the overlay.
9008 setOpacity: function (opacity) {
9009 this.options.opacity = opacity;
9012 this._updateOpacity();
9017 setStyle: function (styleOpts) {
9018 if (styleOpts.opacity) {
9019 this.setOpacity(styleOpts.opacity);
9024 // @method bringToFront(): this
9025 // Brings the layer to the top of all overlays.
9026 bringToFront: function () {
9028 toFront(this._image);
9033 // @method bringToBack(): this
9034 // Brings the layer to the bottom of all overlays.
9035 bringToBack: function () {
9037 toBack(this._image);
9042 // @method setUrl(url: String): this
9043 // Changes the URL of the image.
9044 setUrl: function (url) {
9048 this._image.src = url;
9053 // @method setBounds(bounds: LatLngBounds): this
9054 // Update the bounds that this ImageOverlay covers
9055 setBounds: function (bounds) {
9056 this._bounds = toLatLngBounds(bounds);
9064 getEvents: function () {
9067 viewreset: this._reset
9070 if (this._zoomAnimated) {
9071 events.zoomanim = this._animateZoom;
9077 // @method: setZIndex(value: Number) : this
9078 // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
9079 setZIndex: function (value) {
9080 this.options.zIndex = value;
9081 this._updateZIndex();
9085 // @method getBounds(): LatLngBounds
9086 // Get the bounds that this ImageOverlay covers
9087 getBounds: function () {
9088 return this._bounds;
9091 // @method getElement(): HTMLElement
9092 // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
9093 // used by this overlay.
9094 getElement: function () {
9098 _initImage: function () {
9099 var wasElementSupplied = this._url.tagName === 'IMG';
9100 var img = this._image = wasElementSupplied ? this._url : create$1('img');
9102 addClass(img, 'leaflet-image-layer');
9103 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
9104 if (this.options.className) { addClass(img, this.options.className); }
9106 img.onselectstart = falseFn;
9107 img.onmousemove = falseFn;
9109 // @event load: Event
9110 // Fired when the ImageOverlay layer has loaded its image
9111 img.onload = bind(this.fire, this, 'load');
9112 img.onerror = bind(this._overlayOnError, this, 'error');
9114 if (this.options.crossOrigin || this.options.crossOrigin === '') {
9115 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
9118 if (this.options.zIndex) {
9119 this._updateZIndex();
9122 if (wasElementSupplied) {
9123 this._url = img.src;
9127 img.src = this._url;
9128 img.alt = this.options.alt;
9131 _animateZoom: function (e) {
9132 var scale = this._map.getZoomScale(e.zoom),
9133 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
9135 setTransform(this._image, offset, scale);
9138 _reset: function () {
9139 var image = this._image,
9140 bounds = new Bounds(
9141 this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
9142 this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
9143 size = bounds.getSize();
9145 setPosition(image, bounds.min);
9147 image.style.width = size.x + 'px';
9148 image.style.height = size.y + 'px';
9151 _updateOpacity: function () {
9152 setOpacity(this._image, this.options.opacity);
9155 _updateZIndex: function () {
9156 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
9157 this._image.style.zIndex = this.options.zIndex;
9161 _overlayOnError: function () {
9162 // @event error: Event
9163 // Fired when the ImageOverlay layer has loaded its image
9166 var errorUrl = this.options.errorOverlayUrl;
9167 if (errorUrl && this._url !== errorUrl) {
9168 this._url = errorUrl;
9169 this._image.src = errorUrl;
9174 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
9175 // Instantiates an image overlay object given the URL of the image and the
9176 // geographical bounds it is tied to.
9177 var imageOverlay = function (url, bounds, options) {
9178 return new ImageOverlay(url, bounds, options);
9182 * @class VideoOverlay
9183 * @aka L.VideoOverlay
9184 * @inherits ImageOverlay
9186 * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
9188 * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
9194 * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
9195 * videoBounds = [[ 32, -130], [ 13, -100]];
9196 * L.videoOverlay(videoUrl, videoBounds ).addTo(map);
9200 var VideoOverlay = ImageOverlay.extend({
9203 // @aka VideoOverlay options
9205 // @option autoplay: Boolean = true
9206 // Whether the video starts playing automatically when loaded.
9209 // @option loop: Boolean = true
9210 // Whether the video will loop back to the beginning when played.
9214 _initImage: function () {
9215 var wasElementSupplied = this._url.tagName === 'VIDEO';
9216 var vid = this._image = wasElementSupplied ? this._url : create$1('video');
9218 addClass(vid, 'leaflet-image-layer');
9219 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
9221 vid.onselectstart = falseFn;
9222 vid.onmousemove = falseFn;
9224 // @event load: Event
9225 // Fired when the video has finished loading the first frame
9226 vid.onloadeddata = bind(this.fire, this, 'load');
9228 if (wasElementSupplied) {
9229 var sourceElements = vid.getElementsByTagName('source');
9231 for (var j = 0; j < sourceElements.length; j++) {
9232 sources.push(sourceElements[j].src);
9235 this._url = (sourceElements.length > 0) ? sources : [vid.src];
9239 if (!isArray(this._url)) { this._url = [this._url]; }
9241 vid.autoplay = !!this.options.autoplay;
9242 vid.loop = !!this.options.loop;
9243 for (var i = 0; i < this._url.length; i++) {
9244 var source = create$1('source');
9245 source.src = this._url[i];
9246 vid.appendChild(source);
9250 // @method getElement(): HTMLVideoElement
9251 // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
9252 // used by this overlay.
9256 // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
9257 // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
9258 // geographical bounds it is tied to.
9260 function videoOverlay(video, bounds, options) {
9261 return new VideoOverlay(video, bounds, options);
9268 * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
9271 // @namespace DivOverlay
9272 var DivOverlay = Layer.extend({
9275 // @aka DivOverlay options
9277 // @option offset: Point = Point(0, 7)
9278 // The offset of the popup position. Useful to control the anchor
9279 // of the popup when opening it on some overlays.
9282 // @option className: String = ''
9283 // A custom CSS class name to assign to the popup.
9286 // @option pane: String = 'popupPane'
9287 // `Map pane` where the popup will be added.
9291 initialize: function (options, source) {
9292 setOptions(this, options);
9294 this._source = source;
9297 onAdd: function (map) {
9298 this._zoomAnimated = map._zoomAnimated;
9300 if (!this._container) {
9304 if (map._fadeAnimated) {
9305 setOpacity(this._container, 0);
9308 clearTimeout(this._removeTimeout);
9309 this.getPane().appendChild(this._container);
9312 if (map._fadeAnimated) {
9313 setOpacity(this._container, 1);
9316 this.bringToFront();
9319 onRemove: function (map) {
9320 if (map._fadeAnimated) {
9321 setOpacity(this._container, 0);
9322 this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
9324 remove(this._container);
9329 // @method getLatLng: LatLng
9330 // Returns the geographical point of popup.
9331 getLatLng: function () {
9332 return this._latlng;
9335 // @method setLatLng(latlng: LatLng): this
9336 // Sets the geographical point where the popup will open.
9337 setLatLng: function (latlng) {
9338 this._latlng = toLatLng(latlng);
9340 this._updatePosition();
9346 // @method getContent: String|HTMLElement
9347 // Returns the content of the popup.
9348 getContent: function () {
9349 return this._content;
9352 // @method setContent(htmlContent: String|HTMLElement|Function): this
9353 // 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.
9354 setContent: function (content) {
9355 this._content = content;
9360 // @method getElement: String|HTMLElement
9361 // Alias for [getContent()](#popup-getcontent)
9362 getElement: function () {
9363 return this._container;
9366 // @method update: null
9367 // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
9368 update: function () {
9369 if (!this._map) { return; }
9371 this._container.style.visibility = 'hidden';
9373 this._updateContent();
9374 this._updateLayout();
9375 this._updatePosition();
9377 this._container.style.visibility = '';
9382 getEvents: function () {
9384 zoom: this._updatePosition,
9385 viewreset: this._updatePosition
9388 if (this._zoomAnimated) {
9389 events.zoomanim = this._animateZoom;
9394 // @method isOpen: Boolean
9395 // Returns `true` when the popup is visible on the map.
9396 isOpen: function () {
9397 return !!this._map && this._map.hasLayer(this);
9400 // @method bringToFront: this
9401 // Brings this popup in front of other popups (in the same map pane).
9402 bringToFront: function () {
9404 toFront(this._container);
9409 // @method bringToBack: this
9410 // Brings this popup to the back of other popups (in the same map pane).
9411 bringToBack: function () {
9413 toBack(this._container);
9418 _updateContent: function () {
9419 if (!this._content) { return; }
9421 var node = this._contentNode;
9422 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
9424 if (typeof content === 'string') {
9425 node.innerHTML = content;
9427 while (node.hasChildNodes()) {
9428 node.removeChild(node.firstChild);
9430 node.appendChild(content);
9432 this.fire('contentupdate');
9435 _updatePosition: function () {
9436 if (!this._map) { return; }
9438 var pos = this._map.latLngToLayerPoint(this._latlng),
9439 offset = toPoint(this.options.offset),
9440 anchor = this._getAnchor();
9442 if (this._zoomAnimated) {
9443 setPosition(this._container, pos.add(anchor));
9445 offset = offset.add(pos).add(anchor);
9448 var bottom = this._containerBottom = -offset.y,
9449 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
9451 // bottom position the popup in case the height of the popup changes (images loading etc)
9452 this._container.style.bottom = bottom + 'px';
9453 this._container.style.left = left + 'px';
9456 _getAnchor: function () {
9464 * @inherits DivOverlay
9466 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
9467 * open popups while making sure that only one popup is open at one time
9468 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
9472 * If you want to just bind a popup to marker click and then open it, it's really easy:
9475 * marker.bindPopup(popupContent).openPopup();
9477 * Path overlays like polylines also have a `bindPopup` method.
9478 * Here's a more complicated way to open a popup on a map:
9481 * var popup = L.popup()
9482 * .setLatLng(latlng)
9483 * .setContent('<p>Hello world!<br />This is a nice popup.</p>')
9490 var Popup = DivOverlay.extend({
9493 // @aka Popup options
9495 // @option maxWidth: Number = 300
9496 // Max width of the popup, in pixels.
9499 // @option minWidth: Number = 50
9500 // Min width of the popup, in pixels.
9503 // @option maxHeight: Number = null
9504 // If set, creates a scrollable container of the given height
9505 // inside a popup if its content exceeds it.
9508 // @option autoPan: Boolean = true
9509 // Set it to `false` if you don't want the map to do panning animation
9510 // to fit the opened popup.
9513 // @option autoPanPaddingTopLeft: Point = null
9514 // The margin between the popup and the top left corner of the map
9515 // view after autopanning was performed.
9516 autoPanPaddingTopLeft: null,
9518 // @option autoPanPaddingBottomRight: Point = null
9519 // The margin between the popup and the bottom right corner of the map
9520 // view after autopanning was performed.
9521 autoPanPaddingBottomRight: null,
9523 // @option autoPanPadding: Point = Point(5, 5)
9524 // Equivalent of setting both top left and bottom right autopan padding to the same value.
9525 autoPanPadding: [5, 5],
9527 // @option keepInView: Boolean = false
9528 // Set it to `true` if you want to prevent users from panning the popup
9529 // off of the screen while it is open.
9532 // @option closeButton: Boolean = true
9533 // Controls the presence of a close button in the popup.
9536 // @option autoClose: Boolean = true
9537 // Set it to `false` if you want to override the default behavior of
9538 // the popup closing when another popup is opened.
9541 // @option closeOnEscapeKey: Boolean = true
9542 // Set it to `false` if you want to override the default behavior of
9543 // the ESC key for closing of the popup.
9544 closeOnEscapeKey: true,
9546 // @option closeOnClick: Boolean = *
9547 // Set it if you want to override the default behavior of the popup closing when user clicks
9548 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
9550 // @option className: String = ''
9551 // A custom CSS class name to assign to the popup.
9556 // @method openOn(map: Map): this
9557 // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
9558 openOn: function (map) {
9559 map.openPopup(this);
9563 onAdd: function (map) {
9564 DivOverlay.prototype.onAdd.call(this, map);
9567 // @section Popup events
9568 // @event popupopen: PopupEvent
9569 // Fired when a popup is opened in the map
9570 map.fire('popupopen', {popup: this});
9574 // @section Popup events
9575 // @event popupopen: PopupEvent
9576 // Fired when a popup bound to this layer is opened
9577 this._source.fire('popupopen', {popup: this}, true);
9578 // For non-path layers, we toggle the popup when clicking
9579 // again the layer, so prevent the map to reopen it.
9580 if (!(this._source instanceof Path)) {
9581 this._source.on('preclick', stopPropagation);
9586 onRemove: function (map) {
9587 DivOverlay.prototype.onRemove.call(this, map);
9590 // @section Popup events
9591 // @event popupclose: PopupEvent
9592 // Fired when a popup in the map is closed
9593 map.fire('popupclose', {popup: this});
9597 // @section Popup events
9598 // @event popupclose: PopupEvent
9599 // Fired when a popup bound to this layer is closed
9600 this._source.fire('popupclose', {popup: this}, true);
9601 if (!(this._source instanceof Path)) {
9602 this._source.off('preclick', stopPropagation);
9607 getEvents: function () {
9608 var events = DivOverlay.prototype.getEvents.call(this);
9610 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
9611 events.preclick = this._close;
9614 if (this.options.keepInView) {
9615 events.moveend = this._adjustPan;
9621 _close: function () {
9623 this._map.closePopup(this);
9627 _initLayout: function () {
9628 var prefix = 'leaflet-popup',
9629 container = this._container = create$1('div',
9630 prefix + ' ' + (this.options.className || '') +
9631 ' leaflet-zoom-animated');
9633 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
9634 this._contentNode = create$1('div', prefix + '-content', wrapper);
9636 disableClickPropagation(wrapper);
9637 disableScrollPropagation(this._contentNode);
9638 on(wrapper, 'contextmenu', stopPropagation);
9640 this._tipContainer = create$1('div', prefix + '-tip-container', container);
9641 this._tip = create$1('div', prefix + '-tip', this._tipContainer);
9643 if (this.options.closeButton) {
9644 var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
9645 closeButton.href = '#close';
9646 closeButton.innerHTML = '×';
9648 on(closeButton, 'click', this._onCloseButtonClick, this);
9652 _updateLayout: function () {
9653 var container = this._contentNode,
9654 style = container.style;
9657 style.whiteSpace = 'nowrap';
9659 var width = container.offsetWidth;
9660 width = Math.min(width, this.options.maxWidth);
9661 width = Math.max(width, this.options.minWidth);
9663 style.width = (width + 1) + 'px';
9664 style.whiteSpace = '';
9668 var height = container.offsetHeight,
9669 maxHeight = this.options.maxHeight,
9670 scrolledClass = 'leaflet-popup-scrolled';
9672 if (maxHeight && height > maxHeight) {
9673 style.height = maxHeight + 'px';
9674 addClass(container, scrolledClass);
9676 removeClass(container, scrolledClass);
9679 this._containerWidth = this._container.offsetWidth;
9682 _animateZoom: function (e) {
9683 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
9684 anchor = this._getAnchor();
9685 setPosition(this._container, pos.add(anchor));
9688 _adjustPan: function () {
9689 if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; }
9691 var map = this._map,
9692 marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
9693 containerHeight = this._container.offsetHeight + marginBottom,
9694 containerWidth = this._containerWidth,
9695 layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
9697 layerPos._add(getPosition(this._container));
9699 var containerPos = map.layerPointToContainerPoint(layerPos),
9700 padding = toPoint(this.options.autoPanPadding),
9701 paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
9702 paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
9703 size = map.getSize(),
9707 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
9708 dx = containerPos.x + containerWidth - size.x + paddingBR.x;
9710 if (containerPos.x - dx - paddingTL.x < 0) { // left
9711 dx = containerPos.x - paddingTL.x;
9713 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
9714 dy = containerPos.y + containerHeight - size.y + paddingBR.y;
9716 if (containerPos.y - dy - paddingTL.y < 0) { // top
9717 dy = containerPos.y - paddingTL.y;
9721 // @section Popup events
9722 // @event autopanstart: Event
9723 // Fired when the map starts autopanning when opening a popup.
9726 .fire('autopanstart')
9731 _onCloseButtonClick: function (e) {
9736 _getAnchor: function () {
9737 // Where should we anchor the popup on the source layer?
9738 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
9744 // @factory L.popup(options?: Popup options, source?: Layer)
9745 // 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.
9746 var popup = function (options, source) {
9747 return new Popup(options, source);
9752 * @section Interaction Options
9753 * @option closePopupOnClick: Boolean = true
9754 * Set it to `false` if you don't want popups to close when user clicks the map.
9757 closePopupOnClick: true
9762 // @section Methods for Layers and Controls
9764 // @method openPopup(popup: Popup): this
9765 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
9767 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
9768 // Creates a popup with the specified content and options and opens it in the given point on a map.
9769 openPopup: function (popup, latlng, options) {
9770 if (!(popup instanceof Popup)) {
9771 popup = new Popup(options).setContent(popup);
9775 popup.setLatLng(latlng);
9778 if (this.hasLayer(popup)) {
9782 if (this._popup && this._popup.options.autoClose) {
9786 this._popup = popup;
9787 return this.addLayer(popup);
9790 // @method closePopup(popup?: Popup): this
9791 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
9792 closePopup: function (popup) {
9793 if (!popup || popup === this._popup) {
9794 popup = this._popup;
9798 this.removeLayer(popup);
9806 * @section Popup methods example
9808 * All layers share a set of methods convenient for binding popups to it.
9811 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
9812 * layer.openPopup();
9813 * layer.closePopup();
9816 * 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.
9819 // @section Popup methods
9822 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
9823 // Binds a popup to the layer with the passed `content` and sets up the
9824 // necessary event listeners. If a `Function` is passed it will receive
9825 // the layer as the first argument and should return a `String` or `HTMLElement`.
9826 bindPopup: function (content, options) {
9828 if (content instanceof Popup) {
9829 setOptions(content, options);
9830 this._popup = content;
9831 content._source = this;
9833 if (!this._popup || options) {
9834 this._popup = new Popup(options, this);
9836 this._popup.setContent(content);
9839 if (!this._popupHandlersAdded) {
9841 click: this._openPopup,
9842 keypress: this._onKeyPress,
9843 remove: this.closePopup,
9844 move: this._movePopup
9846 this._popupHandlersAdded = true;
9852 // @method unbindPopup(): this
9853 // Removes the popup previously bound with `bindPopup`.
9854 unbindPopup: function () {
9857 click: this._openPopup,
9858 keypress: this._onKeyPress,
9859 remove: this.closePopup,
9860 move: this._movePopup
9862 this._popupHandlersAdded = false;
9868 // @method openPopup(latlng?: LatLng): this
9869 // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
9870 openPopup: function (layer, latlng) {
9871 if (!(layer instanceof Layer)) {
9876 if (layer instanceof FeatureGroup) {
9877 for (var id in this._layers) {
9878 layer = this._layers[id];
9884 latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
9887 if (this._popup && this._map) {
9888 // set popup source to this layer
9889 this._popup._source = layer;
9891 // update the popup (content, layout, ect...)
9892 this._popup.update();
9894 // open the popup on the map
9895 this._map.openPopup(this._popup, latlng);
9901 // @method closePopup(): this
9902 // Closes the popup bound to this layer if it is open.
9903 closePopup: function () {
9905 this._popup._close();
9910 // @method togglePopup(): this
9911 // Opens or closes the popup bound to this layer depending on its current state.
9912 togglePopup: function (target) {
9914 if (this._popup._map) {
9917 this.openPopup(target);
9923 // @method isPopupOpen(): boolean
9924 // Returns `true` if the popup bound to this layer is currently open.
9925 isPopupOpen: function () {
9926 return (this._popup ? this._popup.isOpen() : false);
9929 // @method setPopupContent(content: String|HTMLElement|Popup): this
9930 // Sets the content of the popup bound to this layer.
9931 setPopupContent: function (content) {
9933 this._popup.setContent(content);
9938 // @method getPopup(): Popup
9939 // Returns the popup bound to this layer.
9940 getPopup: function () {
9944 _openPopup: function (e) {
9945 var layer = e.layer || e.target;
9955 // prevent map click
9958 // if this inherits from Path its a vector and we can just
9959 // open the popup at the new location
9960 if (layer instanceof Path) {
9961 this.openPopup(e.layer || e.target, e.latlng);
9965 // otherwise treat it like a marker and figure out
9966 // if we should toggle it open/closed
9967 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
9970 this.openPopup(layer, e.latlng);
9974 _movePopup: function (e) {
9975 this._popup.setLatLng(e.latlng);
9978 _onKeyPress: function (e) {
9979 if (e.originalEvent.keyCode === 13) {
9987 * @inherits DivOverlay
9989 * Used to display small texts on top of map layers.
9994 * marker.bindTooltip("my tooltip text").openTooltip();
9996 * Note about tooltip offset. Leaflet takes two options in consideration
9997 * for computing tooltip offsetting:
9998 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
9999 * Add a positive x offset to move the tooltip to the right, and a positive y offset to
10000 * move it to the bottom. Negatives will move to the left and top.
10001 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
10002 * should adapt this value if you use a custom icon.
10006 // @namespace Tooltip
10007 var Tooltip = DivOverlay.extend({
10010 // @aka Tooltip options
10012 // @option pane: String = 'tooltipPane'
10013 // `Map pane` where the tooltip will be added.
10014 pane: 'tooltipPane',
10016 // @option offset: Point = Point(0, 0)
10017 // Optional offset of the tooltip position.
10020 // @option direction: String = 'auto'
10021 // Direction where to open the tooltip. Possible values are: `right`, `left`,
10022 // `top`, `bottom`, `center`, `auto`.
10023 // `auto` will dynamically switch between `right` and `left` according to the tooltip
10024 // position on the map.
10027 // @option permanent: Boolean = false
10028 // Whether to open the tooltip permanently or only on mouseover.
10031 // @option sticky: Boolean = false
10032 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
10035 // @option interactive: Boolean = false
10036 // If true, the tooltip will listen to the feature events.
10037 interactive: false,
10039 // @option opacity: Number = 0.9
10040 // Tooltip container opacity.
10044 onAdd: function (map) {
10045 DivOverlay.prototype.onAdd.call(this, map);
10046 this.setOpacity(this.options.opacity);
10049 // @section Tooltip events
10050 // @event tooltipopen: TooltipEvent
10051 // Fired when a tooltip is opened in the map.
10052 map.fire('tooltipopen', {tooltip: this});
10054 if (this._source) {
10055 // @namespace Layer
10056 // @section Tooltip events
10057 // @event tooltipopen: TooltipEvent
10058 // Fired when a tooltip bound to this layer is opened.
10059 this._source.fire('tooltipopen', {tooltip: this}, true);
10063 onRemove: function (map) {
10064 DivOverlay.prototype.onRemove.call(this, map);
10067 // @section Tooltip events
10068 // @event tooltipclose: TooltipEvent
10069 // Fired when a tooltip in the map is closed.
10070 map.fire('tooltipclose', {tooltip: this});
10072 if (this._source) {
10073 // @namespace Layer
10074 // @section Tooltip events
10075 // @event tooltipclose: TooltipEvent
10076 // Fired when a tooltip bound to this layer is closed.
10077 this._source.fire('tooltipclose', {tooltip: this}, true);
10081 getEvents: function () {
10082 var events = DivOverlay.prototype.getEvents.call(this);
10084 if (touch && !this.options.permanent) {
10085 events.preclick = this._close;
10091 _close: function () {
10093 this._map.closeTooltip(this);
10097 _initLayout: function () {
10098 var prefix = 'leaflet-tooltip',
10099 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
10101 this._contentNode = this._container = create$1('div', className);
10104 _updateLayout: function () {},
10106 _adjustPan: function () {},
10108 _setPosition: function (pos) {
10109 var map = this._map,
10110 container = this._container,
10111 centerPoint = map.latLngToContainerPoint(map.getCenter()),
10112 tooltipPoint = map.layerPointToContainerPoint(pos),
10113 direction = this.options.direction,
10114 tooltipWidth = container.offsetWidth,
10115 tooltipHeight = container.offsetHeight,
10116 offset = toPoint(this.options.offset),
10117 anchor = this._getAnchor();
10119 if (direction === 'top') {
10120 pos = pos.add(toPoint(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true));
10121 } else if (direction === 'bottom') {
10122 pos = pos.subtract(toPoint(tooltipWidth / 2 - offset.x, -offset.y, true));
10123 } else if (direction === 'center') {
10124 pos = pos.subtract(toPoint(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true));
10125 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
10126 direction = 'right';
10127 pos = pos.add(toPoint(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true));
10129 direction = 'left';
10130 pos = pos.subtract(toPoint(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true));
10133 removeClass(container, 'leaflet-tooltip-right');
10134 removeClass(container, 'leaflet-tooltip-left');
10135 removeClass(container, 'leaflet-tooltip-top');
10136 removeClass(container, 'leaflet-tooltip-bottom');
10137 addClass(container, 'leaflet-tooltip-' + direction);
10138 setPosition(container, pos);
10141 _updatePosition: function () {
10142 var pos = this._map.latLngToLayerPoint(this._latlng);
10143 this._setPosition(pos);
10146 setOpacity: function (opacity) {
10147 this.options.opacity = opacity;
10149 if (this._container) {
10150 setOpacity(this._container, opacity);
10154 _animateZoom: function (e) {
10155 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
10156 this._setPosition(pos);
10159 _getAnchor: function () {
10160 // Where should we anchor the tooltip on the source layer?
10161 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
10166 // @namespace Tooltip
10167 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
10168 // 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.
10169 var tooltip = function (options, source) {
10170 return new Tooltip(options, source);
10174 // @section Methods for Layers and Controls
10177 // @method openTooltip(tooltip: Tooltip): this
10178 // Opens the specified tooltip.
10180 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
10181 // Creates a tooltip with the specified content and options and open it.
10182 openTooltip: function (tooltip, latlng, options) {
10183 if (!(tooltip instanceof Tooltip)) {
10184 tooltip = new Tooltip(options).setContent(tooltip);
10188 tooltip.setLatLng(latlng);
10191 if (this.hasLayer(tooltip)) {
10195 return this.addLayer(tooltip);
10198 // @method closeTooltip(tooltip?: Tooltip): this
10199 // Closes the tooltip given as parameter.
10200 closeTooltip: function (tooltip) {
10202 this.removeLayer(tooltip);
10211 * @section Tooltip methods example
10213 * All layers share a set of methods convenient for binding tooltips to it.
10216 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
10217 * layer.openTooltip();
10218 * layer.closeTooltip();
10222 // @section Tooltip methods
10225 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
10226 // Binds a tooltip to the layer with the passed `content` and sets up the
10227 // necessary event listeners. If a `Function` is passed it will receive
10228 // the layer as the first argument and should return a `String` or `HTMLElement`.
10229 bindTooltip: function (content, options) {
10231 if (content instanceof Tooltip) {
10232 setOptions(content, options);
10233 this._tooltip = content;
10234 content._source = this;
10236 if (!this._tooltip || options) {
10237 this._tooltip = new Tooltip(options, this);
10239 this._tooltip.setContent(content);
10243 this._initTooltipInteractions();
10245 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
10246 this.openTooltip();
10252 // @method unbindTooltip(): this
10253 // Removes the tooltip previously bound with `bindTooltip`.
10254 unbindTooltip: function () {
10255 if (this._tooltip) {
10256 this._initTooltipInteractions(true);
10257 this.closeTooltip();
10258 this._tooltip = null;
10263 _initTooltipInteractions: function (remove$$1) {
10264 if (!remove$$1 && this._tooltipHandlersAdded) { return; }
10265 var onOff = remove$$1 ? 'off' : 'on',
10267 remove: this.closeTooltip,
10268 move: this._moveTooltip
10270 if (!this._tooltip.options.permanent) {
10271 events.mouseover = this._openTooltip;
10272 events.mouseout = this.closeTooltip;
10273 if (this._tooltip.options.sticky) {
10274 events.mousemove = this._moveTooltip;
10277 events.click = this._openTooltip;
10280 events.add = this._openTooltip;
10282 this[onOff](events);
10283 this._tooltipHandlersAdded = !remove$$1;
10286 // @method openTooltip(latlng?: LatLng): this
10287 // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
10288 openTooltip: function (layer, latlng) {
10289 if (!(layer instanceof Layer)) {
10294 if (layer instanceof FeatureGroup) {
10295 for (var id in this._layers) {
10296 layer = this._layers[id];
10302 latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
10305 if (this._tooltip && this._map) {
10307 // set tooltip source to this layer
10308 this._tooltip._source = layer;
10310 // update the tooltip (content, layout, ect...)
10311 this._tooltip.update();
10313 // open the tooltip on the map
10314 this._map.openTooltip(this._tooltip, latlng);
10316 // Tooltip container may not be defined if not permanent and never
10318 if (this._tooltip.options.interactive && this._tooltip._container) {
10319 addClass(this._tooltip._container, 'leaflet-clickable');
10320 this.addInteractiveTarget(this._tooltip._container);
10327 // @method closeTooltip(): this
10328 // Closes the tooltip bound to this layer if it is open.
10329 closeTooltip: function () {
10330 if (this._tooltip) {
10331 this._tooltip._close();
10332 if (this._tooltip.options.interactive && this._tooltip._container) {
10333 removeClass(this._tooltip._container, 'leaflet-clickable');
10334 this.removeInteractiveTarget(this._tooltip._container);
10340 // @method toggleTooltip(): this
10341 // Opens or closes the tooltip bound to this layer depending on its current state.
10342 toggleTooltip: function (target) {
10343 if (this._tooltip) {
10344 if (this._tooltip._map) {
10345 this.closeTooltip();
10347 this.openTooltip(target);
10353 // @method isTooltipOpen(): boolean
10354 // Returns `true` if the tooltip bound to this layer is currently open.
10355 isTooltipOpen: function () {
10356 return this._tooltip.isOpen();
10359 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10360 // Sets the content of the tooltip bound to this layer.
10361 setTooltipContent: function (content) {
10362 if (this._tooltip) {
10363 this._tooltip.setContent(content);
10368 // @method getTooltip(): Tooltip
10369 // Returns the tooltip bound to this layer.
10370 getTooltip: function () {
10371 return this._tooltip;
10374 _openTooltip: function (e) {
10375 var layer = e.layer || e.target;
10377 if (!this._tooltip || !this._map) {
10380 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
10383 _moveTooltip: function (e) {
10384 var latlng = e.latlng, containerPoint, layerPoint;
10385 if (this._tooltip.options.sticky && e.originalEvent) {
10386 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
10387 layerPoint = this._map.containerPointToLayerPoint(containerPoint);
10388 latlng = this._map.layerPointToLatLng(layerPoint);
10390 this._tooltip.setLatLng(latlng);
10399 * Represents a lightweight icon for markers that uses a simple `<div>`
10400 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
10404 * var myIcon = L.divIcon({className: 'my-div-icon'});
10405 * // you can set .my-div-icon styles in CSS
10407 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
10410 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
10413 var DivIcon = Icon.extend({
10416 // @aka DivIcon options
10417 iconSize: [12, 12], // also can be set through CSS
10419 // iconAnchor: (Point),
10420 // popupAnchor: (Point),
10422 // @option html: String = ''
10423 // Custom HTML code to put inside the div element, empty by default.
10426 // @option bgPos: Point = [0, 0]
10427 // Optional relative position of the background, in pixels
10430 className: 'leaflet-div-icon'
10433 createIcon: function (oldIcon) {
10434 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
10435 options = this.options;
10437 div.innerHTML = options.html !== false ? options.html : '';
10439 if (options.bgPos) {
10440 var bgPos = toPoint(options.bgPos);
10441 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
10443 this._setIconStyles(div, 'icon');
10448 createShadow: function () {
10453 // @factory L.divIcon(options: DivIcon options)
10454 // Creates a `DivIcon` instance with the given options.
10455 function divIcon(options) {
10456 return new DivIcon(options);
10459 Icon.Default = IconDefault;
10466 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
10467 * 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.
10470 * @section Synchronous usage
10473 * 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.
10476 * var CanvasLayer = L.GridLayer.extend({
10477 * createTile: function(coords){
10478 * // create a <canvas> element for drawing
10479 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10481 * // setup tile width and height according to the options
10482 * var size = this.getTileSize();
10483 * tile.width = size.x;
10484 * tile.height = size.y;
10486 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z
10487 * var ctx = tile.getContext('2d');
10489 * // return the tile so it can be rendered on screen
10495 * @section Asynchronous usage
10498 * 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.
10501 * var CanvasLayer = L.GridLayer.extend({
10502 * createTile: function(coords, done){
10505 * // create a <canvas> element for drawing
10506 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10508 * // setup tile width and height according to the options
10509 * var size = this.getTileSize();
10510 * tile.width = size.x;
10511 * tile.height = size.y;
10513 * // draw something asynchronously and pass the tile to the done() callback
10514 * setTimeout(function() {
10515 * done(error, tile);
10527 var GridLayer = Layer.extend({
10530 // @aka GridLayer options
10532 // @option tileSize: Number|Point = 256
10533 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
10536 // @option opacity: Number = 1.0
10537 // Opacity of the tiles. Can be used in the `createTile()` function.
10540 // @option updateWhenIdle: Boolean = (depends)
10541 // Load new tiles only when panning ends.
10542 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
10543 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
10544 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
10545 updateWhenIdle: mobile,
10547 // @option updateWhenZooming: Boolean = true
10548 // 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.
10549 updateWhenZooming: true,
10551 // @option updateInterval: Number = 200
10552 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
10553 updateInterval: 200,
10555 // @option zIndex: Number = 1
10556 // The explicit zIndex of the tile layer.
10559 // @option bounds: LatLngBounds = undefined
10560 // If set, tiles will only be loaded inside the set `LatLngBounds`.
10563 // @option minZoom: Number = 0
10564 // The minimum zoom level down to which this layer will be displayed (inclusive).
10567 // @option maxZoom: Number = undefined
10568 // The maximum zoom level up to which this layer will be displayed (inclusive).
10569 maxZoom: undefined,
10571 // @option maxNativeZoom: Number = undefined
10572 // Maximum zoom number the tile source has available. If it is specified,
10573 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
10574 // from `maxNativeZoom` level and auto-scaled.
10575 maxNativeZoom: undefined,
10577 // @option minNativeZoom: Number = undefined
10578 // Minimum zoom number the tile source has available. If it is specified,
10579 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
10580 // from `minNativeZoom` level and auto-scaled.
10581 minNativeZoom: undefined,
10583 // @option noWrap: Boolean = false
10584 // Whether the layer is wrapped around the antimeridian. If `true`, the
10585 // GridLayer will only be displayed once at low zoom levels. Has no
10586 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
10587 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
10588 // tiles outside the CRS limits.
10591 // @option pane: String = 'tilePane'
10592 // `Map pane` where the grid layer will be added.
10595 // @option className: String = ''
10596 // A custom class name to assign to the tile layer. Empty by default.
10599 // @option keepBuffer: Number = 2
10600 // When panning the map, keep this many rows and columns of tiles before unloading them.
10604 initialize: function (options) {
10605 setOptions(this, options);
10608 onAdd: function () {
10609 this._initContainer();
10618 beforeAdd: function (map) {
10619 map._addZoomLimit(this);
10622 onRemove: function (map) {
10623 this._removeAllTiles();
10624 remove(this._container);
10625 map._removeZoomLimit(this);
10626 this._container = null;
10627 this._tileZoom = undefined;
10630 // @method bringToFront: this
10631 // Brings the tile layer to the top of all tile layers.
10632 bringToFront: function () {
10634 toFront(this._container);
10635 this._setAutoZIndex(Math.max);
10640 // @method bringToBack: this
10641 // Brings the tile layer to the bottom of all tile layers.
10642 bringToBack: function () {
10644 toBack(this._container);
10645 this._setAutoZIndex(Math.min);
10650 // @method getContainer: HTMLElement
10651 // Returns the HTML element that contains the tiles for this layer.
10652 getContainer: function () {
10653 return this._container;
10656 // @method setOpacity(opacity: Number): this
10657 // Changes the [opacity](#gridlayer-opacity) of the grid layer.
10658 setOpacity: function (opacity) {
10659 this.options.opacity = opacity;
10660 this._updateOpacity();
10664 // @method setZIndex(zIndex: Number): this
10665 // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
10666 setZIndex: function (zIndex) {
10667 this.options.zIndex = zIndex;
10668 this._updateZIndex();
10673 // @method isLoading: Boolean
10674 // Returns `true` if any tile in the grid layer has not finished loading.
10675 isLoading: function () {
10676 return this._loading;
10679 // @method redraw: this
10680 // Causes the layer to clear all the tiles and request them again.
10681 redraw: function () {
10683 this._removeAllTiles();
10689 getEvents: function () {
10691 viewprereset: this._invalidateAll,
10692 viewreset: this._resetView,
10693 zoom: this._resetView,
10694 moveend: this._onMoveEnd
10697 if (!this.options.updateWhenIdle) {
10698 // update tiles on move, but not more often than once per given interval
10699 if (!this._onMove) {
10700 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
10703 events.move = this._onMove;
10706 if (this._zoomAnimated) {
10707 events.zoomanim = this._animateZoom;
10713 // @section Extension methods
10714 // Layers extending `GridLayer` shall reimplement the following method.
10715 // @method createTile(coords: Object, done?: Function): HTMLElement
10716 // Called only internally, must be overridden by classes extending `GridLayer`.
10717 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
10718 // is specified, it must be called when the tile has finished loading and drawing.
10719 createTile: function () {
10720 return document.createElement('div');
10724 // @method getTileSize: Point
10725 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
10726 getTileSize: function () {
10727 var s = this.options.tileSize;
10728 return s instanceof Point ? s : new Point(s, s);
10731 _updateZIndex: function () {
10732 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
10733 this._container.style.zIndex = this.options.zIndex;
10737 _setAutoZIndex: function (compare) {
10738 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
10740 var layers = this.getPane().children,
10741 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
10743 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
10745 zIndex = layers[i].style.zIndex;
10747 if (layers[i] !== this._container && zIndex) {
10748 edgeZIndex = compare(edgeZIndex, +zIndex);
10752 if (isFinite(edgeZIndex)) {
10753 this.options.zIndex = edgeZIndex + compare(-1, 1);
10754 this._updateZIndex();
10758 _updateOpacity: function () {
10759 if (!this._map) { return; }
10761 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
10762 if (ielt9) { return; }
10764 setOpacity(this._container, this.options.opacity);
10766 var now = +new Date(),
10770 for (var key in this._tiles) {
10771 var tile = this._tiles[key];
10772 if (!tile.current || !tile.loaded) { continue; }
10774 var fade = Math.min(1, (now - tile.loaded) / 200);
10776 setOpacity(tile.el, fade);
10783 this._onOpaqueTile(tile);
10785 tile.active = true;
10789 if (willPrune && !this._noPrune) { this._pruneTiles(); }
10792 cancelAnimFrame(this._fadeFrame);
10793 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
10797 _onOpaqueTile: falseFn,
10799 _initContainer: function () {
10800 if (this._container) { return; }
10802 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
10803 this._updateZIndex();
10805 if (this.options.opacity < 1) {
10806 this._updateOpacity();
10809 this.getPane().appendChild(this._container);
10812 _updateLevels: function () {
10814 var zoom = this._tileZoom,
10815 maxZoom = this.options.maxZoom;
10817 if (zoom === undefined) { return undefined; }
10819 for (var z in this._levels) {
10820 if (this._levels[z].el.children.length || z === zoom) {
10821 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
10822 this._onUpdateLevel(z);
10824 remove(this._levels[z].el);
10825 this._removeTilesAtZoom(z);
10826 this._onRemoveLevel(z);
10827 delete this._levels[z];
10831 var level = this._levels[zoom],
10835 level = this._levels[zoom] = {};
10837 level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
10838 level.el.style.zIndex = maxZoom;
10840 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
10843 this._setZoomTransform(level, map.getCenter(), map.getZoom());
10845 // force the browser to consider the newly added element for transition
10846 falseFn(level.el.offsetWidth);
10848 this._onCreateLevel(level);
10851 this._level = level;
10856 _onUpdateLevel: falseFn,
10858 _onRemoveLevel: falseFn,
10860 _onCreateLevel: falseFn,
10862 _pruneTiles: function () {
10869 var zoom = this._map.getZoom();
10870 if (zoom > this.options.maxZoom ||
10871 zoom < this.options.minZoom) {
10872 this._removeAllTiles();
10876 for (key in this._tiles) {
10877 tile = this._tiles[key];
10878 tile.retain = tile.current;
10881 for (key in this._tiles) {
10882 tile = this._tiles[key];
10883 if (tile.current && !tile.active) {
10884 var coords = tile.coords;
10885 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
10886 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
10891 for (key in this._tiles) {
10892 if (!this._tiles[key].retain) {
10893 this._removeTile(key);
10898 _removeTilesAtZoom: function (zoom) {
10899 for (var key in this._tiles) {
10900 if (this._tiles[key].coords.z !== zoom) {
10903 this._removeTile(key);
10907 _removeAllTiles: function () {
10908 for (var key in this._tiles) {
10909 this._removeTile(key);
10913 _invalidateAll: function () {
10914 for (var z in this._levels) {
10915 remove(this._levels[z].el);
10916 this._onRemoveLevel(z);
10917 delete this._levels[z];
10919 this._removeAllTiles();
10921 this._tileZoom = undefined;
10924 _retainParent: function (x, y, z, minZoom) {
10925 var x2 = Math.floor(x / 2),
10926 y2 = Math.floor(y / 2),
10928 coords2 = new Point(+x2, +y2);
10931 var key = this._tileCoordsToKey(coords2),
10932 tile = this._tiles[key];
10934 if (tile && tile.active) {
10935 tile.retain = true;
10938 } else if (tile && tile.loaded) {
10939 tile.retain = true;
10942 if (z2 > minZoom) {
10943 return this._retainParent(x2, y2, z2, minZoom);
10949 _retainChildren: function (x, y, z, maxZoom) {
10951 for (var i = 2 * x; i < 2 * x + 2; i++) {
10952 for (var j = 2 * y; j < 2 * y + 2; j++) {
10954 var coords = new Point(i, j);
10957 var key = this._tileCoordsToKey(coords),
10958 tile = this._tiles[key];
10960 if (tile && tile.active) {
10961 tile.retain = true;
10964 } else if (tile && tile.loaded) {
10965 tile.retain = true;
10968 if (z + 1 < maxZoom) {
10969 this._retainChildren(i, j, z + 1, maxZoom);
10975 _resetView: function (e) {
10976 var animating = e && (e.pinch || e.flyTo);
10977 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
10980 _animateZoom: function (e) {
10981 this._setView(e.center, e.zoom, true, e.noUpdate);
10984 _clampZoom: function (zoom) {
10985 var options = this.options;
10987 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
10988 return options.minNativeZoom;
10991 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
10992 return options.maxNativeZoom;
10998 _setView: function (center, zoom, noPrune, noUpdate) {
10999 var tileZoom = this._clampZoom(Math.round(zoom));
11000 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
11001 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
11002 tileZoom = undefined;
11005 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
11007 if (!noUpdate || tileZoomChanged) {
11009 this._tileZoom = tileZoom;
11011 if (this._abortLoading) {
11012 this._abortLoading();
11015 this._updateLevels();
11018 if (tileZoom !== undefined) {
11019 this._update(center);
11023 this._pruneTiles();
11026 // Flag to prevent _updateOpacity from pruning tiles during
11027 // a zoom anim or a pinch gesture
11028 this._noPrune = !!noPrune;
11031 this._setZoomTransforms(center, zoom);
11034 _setZoomTransforms: function (center, zoom) {
11035 for (var i in this._levels) {
11036 this._setZoomTransform(this._levels[i], center, zoom);
11040 _setZoomTransform: function (level, center, zoom) {
11041 var scale = this._map.getZoomScale(zoom, level.zoom),
11042 translate = level.origin.multiplyBy(scale)
11043 .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
11046 setTransform(level.el, translate, scale);
11048 setPosition(level.el, translate);
11052 _resetGrid: function () {
11053 var map = this._map,
11054 crs = map.options.crs,
11055 tileSize = this._tileSize = this.getTileSize(),
11056 tileZoom = this._tileZoom;
11058 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
11060 this._globalTileRange = this._pxBoundsToTileRange(bounds);
11063 this._wrapX = crs.wrapLng && !this.options.noWrap && [
11064 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
11065 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
11067 this._wrapY = crs.wrapLat && !this.options.noWrap && [
11068 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
11069 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
11073 _onMoveEnd: function () {
11074 if (!this._map || this._map._animatingZoom) { return; }
11079 _getTiledPixelBounds: function (center) {
11080 var map = this._map,
11081 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
11082 scale = map.getZoomScale(mapZoom, this._tileZoom),
11083 pixelCenter = map.project(center, this._tileZoom).floor(),
11084 halfSize = map.getSize().divideBy(scale * 2);
11086 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
11089 // Private method to load tiles in the grid's active zoom level according to map bounds
11090 _update: function (center) {
11091 var map = this._map;
11092 if (!map) { return; }
11093 var zoom = this._clampZoom(map.getZoom());
11095 if (center === undefined) { center = map.getCenter(); }
11096 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom
11098 var pixelBounds = this._getTiledPixelBounds(center),
11099 tileRange = this._pxBoundsToTileRange(pixelBounds),
11100 tileCenter = tileRange.getCenter(),
11102 margin = this.options.keepBuffer,
11103 noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
11104 tileRange.getTopRight().add([margin, -margin]));
11106 // Sanity check: panic if the tile range contains Infinity somewhere.
11107 if (!(isFinite(tileRange.min.x) &&
11108 isFinite(tileRange.min.y) &&
11109 isFinite(tileRange.max.x) &&
11110 isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
11112 for (var key in this._tiles) {
11113 var c = this._tiles[key].coords;
11114 if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
11115 this._tiles[key].current = false;
11119 // _update just loads more tiles. If the tile zoom level differs too much
11120 // from the map's, let _setView reset levels and prune old tiles.
11121 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
11123 // create a queue of coordinates to load tiles from
11124 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
11125 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
11126 var coords = new Point(i, j);
11127 coords.z = this._tileZoom;
11129 if (!this._isValidTile(coords)) { continue; }
11131 var tile = this._tiles[this._tileCoordsToKey(coords)];
11133 tile.current = true;
11135 queue.push(coords);
11140 // sort tile queue to load tiles in order of their distance to center
11141 queue.sort(function (a, b) {
11142 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
11145 if (queue.length !== 0) {
11146 // if it's the first batch of tiles to load
11147 if (!this._loading) {
11148 this._loading = true;
11149 // @event loading: Event
11150 // Fired when the grid layer starts loading tiles.
11151 this.fire('loading');
11154 // create DOM fragment to append tiles in one batch
11155 var fragment = document.createDocumentFragment();
11157 for (i = 0; i < queue.length; i++) {
11158 this._addTile(queue[i], fragment);
11161 this._level.el.appendChild(fragment);
11165 _isValidTile: function (coords) {
11166 var crs = this._map.options.crs;
11168 if (!crs.infinite) {
11169 // don't load tile if it's out of bounds and not wrapped
11170 var bounds = this._globalTileRange;
11171 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
11172 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
11175 if (!this.options.bounds) { return true; }
11177 // don't load tile if it doesn't intersect the bounds in options
11178 var tileBounds = this._tileCoordsToBounds(coords);
11179 return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
11182 _keyToBounds: function (key) {
11183 return this._tileCoordsToBounds(this._keyToTileCoords(key));
11186 _tileCoordsToNwSe: function (coords) {
11187 var map = this._map,
11188 tileSize = this.getTileSize(),
11189 nwPoint = coords.scaleBy(tileSize),
11190 sePoint = nwPoint.add(tileSize),
11191 nw = map.unproject(nwPoint, coords.z),
11192 se = map.unproject(sePoint, coords.z);
11196 // converts tile coordinates to its geographical bounds
11197 _tileCoordsToBounds: function (coords) {
11198 var bp = this._tileCoordsToNwSe(coords),
11199 bounds = new LatLngBounds(bp[0], bp[1]);
11201 if (!this.options.noWrap) {
11202 bounds = this._map.wrapLatLngBounds(bounds);
11206 // converts tile coordinates to key for the tile cache
11207 _tileCoordsToKey: function (coords) {
11208 return coords.x + ':' + coords.y + ':' + coords.z;
11211 // converts tile cache key to coordinates
11212 _keyToTileCoords: function (key) {
11213 var k = key.split(':'),
11214 coords = new Point(+k[0], +k[1]);
11219 _removeTile: function (key) {
11220 var tile = this._tiles[key];
11221 if (!tile) { return; }
11223 // Cancels any pending http requests associated with the tile
11224 // unless we're on Android's stock browser,
11225 // see https://github.com/Leaflet/Leaflet/issues/137
11226 if (!androidStock) {
11227 tile.el.setAttribute('src', emptyImageUrl);
11231 delete this._tiles[key];
11233 // @event tileunload: TileEvent
11234 // Fired when a tile is removed (e.g. when a tile goes off the screen).
11235 this.fire('tileunload', {
11237 coords: this._keyToTileCoords(key)
11241 _initTile: function (tile) {
11242 addClass(tile, 'leaflet-tile');
11244 var tileSize = this.getTileSize();
11245 tile.style.width = tileSize.x + 'px';
11246 tile.style.height = tileSize.y + 'px';
11248 tile.onselectstart = falseFn;
11249 tile.onmousemove = falseFn;
11251 // update opacity on tiles in IE7-8 because of filter inheritance problems
11252 if (ielt9 && this.options.opacity < 1) {
11253 setOpacity(tile, this.options.opacity);
11256 // without this hack, tiles disappear after zoom on Chrome for Android
11257 // https://github.com/Leaflet/Leaflet/issues/2078
11258 if (android && !android23) {
11259 tile.style.WebkitBackfaceVisibility = 'hidden';
11263 _addTile: function (coords, container) {
11264 var tilePos = this._getTilePos(coords),
11265 key = this._tileCoordsToKey(coords);
11267 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
11269 this._initTile(tile);
11271 // if createTile is defined with a second argument ("done" callback),
11272 // we know that tile is async and will be ready later; otherwise
11273 if (this.createTile.length < 2) {
11274 // mark tile as ready, but delay one frame for opacity animation to happen
11275 requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
11278 setPosition(tile, tilePos);
11280 // save tile in cache
11281 this._tiles[key] = {
11287 container.appendChild(tile);
11288 // @event tileloadstart: TileEvent
11289 // Fired when a tile is requested and starts loading.
11290 this.fire('tileloadstart', {
11296 _tileReady: function (coords, err, tile) {
11297 if (!this._map || tile.getAttribute('src') === emptyImageUrl) { return; }
11300 // @event tileerror: TileErrorEvent
11301 // Fired when there is an error loading a tile.
11302 this.fire('tileerror', {
11309 var key = this._tileCoordsToKey(coords);
11311 tile = this._tiles[key];
11312 if (!tile) { return; }
11314 tile.loaded = +new Date();
11315 if (this._map._fadeAnimated) {
11316 setOpacity(tile.el, 0);
11317 cancelAnimFrame(this._fadeFrame);
11318 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11320 tile.active = true;
11321 this._pruneTiles();
11325 addClass(tile.el, 'leaflet-tile-loaded');
11327 // @event tileload: TileEvent
11328 // Fired when a tile loads.
11329 this.fire('tileload', {
11335 if (this._noTilesToLoad()) {
11336 this._loading = false;
11337 // @event load: Event
11338 // Fired when the grid layer loaded all visible tiles.
11341 if (ielt9 || !this._map._fadeAnimated) {
11342 requestAnimFrame(this._pruneTiles, this);
11344 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
11345 // to trigger a pruning.
11346 setTimeout(bind(this._pruneTiles, this), 250);
11351 _getTilePos: function (coords) {
11352 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
11355 _wrapCoords: function (coords) {
11356 var newCoords = new Point(
11357 this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
11358 this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
11359 newCoords.z = coords.z;
11363 _pxBoundsToTileRange: function (bounds) {
11364 var tileSize = this.getTileSize();
11366 bounds.min.unscaleBy(tileSize).floor(),
11367 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
11370 _noTilesToLoad: function () {
11371 for (var key in this._tiles) {
11372 if (!this._tiles[key].loaded) { return false; }
11378 // @factory L.gridLayer(options?: GridLayer options)
11379 // Creates a new instance of GridLayer with the supplied options.
11380 function gridLayer(options) {
11381 return new GridLayer(options);
11386 * @inherits GridLayer
11388 * Used to load and display tile layers on the map. Extends `GridLayer`.
11393 * L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map);
11396 * @section URL template
11399 * A string of the following form:
11402 * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
11405 * `{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.
11407 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
11410 * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
11415 var TileLayer = GridLayer.extend({
11418 // @aka TileLayer options
11420 // @option minZoom: Number = 0
11421 // The minimum zoom level down to which this layer will be displayed (inclusive).
11424 // @option maxZoom: Number = 18
11425 // The maximum zoom level up to which this layer will be displayed (inclusive).
11428 // @option subdomains: String|String[] = 'abc'
11429 // 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.
11432 // @option errorTileUrl: String = ''
11433 // URL to the tile image to show in place of the tile that failed to load.
11436 // @option zoomOffset: Number = 0
11437 // The zoom number used in tile URLs will be offset with this value.
11440 // @option tms: Boolean = false
11441 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
11444 // @option zoomReverse: Boolean = false
11445 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
11446 zoomReverse: false,
11448 // @option detectRetina: Boolean = false
11449 // 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.
11450 detectRetina: false,
11452 // @option crossOrigin: Boolean|String = false
11453 // Whether the crossOrigin attribute will be added to the tiles.
11454 // If a String is provided, all tiles will have their crossOrigin attribute set to the String provided. This is needed if you want to access tile pixel data.
11455 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
11459 initialize: function (url, options) {
11463 options = setOptions(this, options);
11465 // detecting retina displays, adjusting tileSize and zoom levels
11466 if (options.detectRetina && retina && options.maxZoom > 0) {
11468 options.tileSize = Math.floor(options.tileSize / 2);
11470 if (!options.zoomReverse) {
11471 options.zoomOffset++;
11474 options.zoomOffset--;
11478 options.minZoom = Math.max(0, options.minZoom);
11481 if (typeof options.subdomains === 'string') {
11482 options.subdomains = options.subdomains.split('');
11485 // for https://github.com/Leaflet/Leaflet/issues/137
11487 this.on('tileunload', this._onTileRemove);
11491 // @method setUrl(url: String, noRedraw?: Boolean): this
11492 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
11493 setUrl: function (url, noRedraw) {
11502 // @method createTile(coords: Object, done?: Function): HTMLElement
11503 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
11504 // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
11505 // callback is called when the tile has been loaded.
11506 createTile: function (coords, done) {
11507 var tile = document.createElement('img');
11509 on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
11510 on(tile, 'error', bind(this._tileOnError, this, done, tile));
11512 if (this.options.crossOrigin || this.options.crossOrigin === '') {
11513 tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
11517 Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
11518 http://www.w3.org/TR/WCAG20-TECHS/H67
11523 Set role="presentation" to force screen readers to ignore this
11524 https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
11526 tile.setAttribute('role', 'presentation');
11528 tile.src = this.getTileUrl(coords);
11533 // @section Extension methods
11535 // Layers extending `TileLayer` might reimplement the following method.
11536 // @method getTileUrl(coords: Object): String
11537 // Called only internally, returns the URL for a tile given its coordinates.
11538 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
11539 getTileUrl: function (coords) {
11541 r: retina ? '@2x' : '',
11542 s: this._getSubdomain(coords),
11545 z: this._getZoomForUrl()
11547 if (this._map && !this._map.options.crs.infinite) {
11548 var invertedY = this._globalTileRange.max.y - coords.y;
11549 if (this.options.tms) {
11550 data['y'] = invertedY;
11552 data['-y'] = invertedY;
11555 return template(this._url, extend(data, this.options));
11558 _tileOnLoad: function (done, tile) {
11559 // For https://github.com/Leaflet/Leaflet/issues/3332
11561 setTimeout(bind(done, this, null, tile), 0);
11567 _tileOnError: function (done, tile, e) {
11568 var errorUrl = this.options.errorTileUrl;
11569 if (errorUrl && tile.getAttribute('src') !== errorUrl) {
11570 tile.src = errorUrl;
11575 _onTileRemove: function (e) {
11576 e.tile.onload = null;
11579 _getZoomForUrl: function () {
11580 var zoom = this._tileZoom,
11581 maxZoom = this.options.maxZoom,
11582 zoomReverse = this.options.zoomReverse,
11583 zoomOffset = this.options.zoomOffset;
11586 zoom = maxZoom - zoom;
11589 return zoom + zoomOffset;
11592 _getSubdomain: function (tilePoint) {
11593 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
11594 return this.options.subdomains[index];
11597 // stops loading all tiles in the background layer
11598 _abortLoading: function () {
11600 for (i in this._tiles) {
11601 if (this._tiles[i].coords.z !== this._tileZoom) {
11602 tile = this._tiles[i].el;
11604 tile.onload = falseFn;
11605 tile.onerror = falseFn;
11607 if (!tile.complete) {
11608 tile.src = emptyImageUrl;
11610 delete this._tiles[i];
11618 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
11619 // Instantiates a tile layer object given a `URL template` and optionally an options object.
11621 function tileLayer(url, options) {
11622 return new TileLayer(url, options);
11626 * @class TileLayer.WMS
11627 * @inherits TileLayer
11628 * @aka L.TileLayer.WMS
11629 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
11634 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
11635 * layers: 'nexrad-n0r-900913',
11636 * format: 'image/png',
11637 * transparent: true,
11638 * attribution: "Weather data © 2012 IEM Nexrad"
11643 var TileLayerWMS = TileLayer.extend({
11646 // @aka TileLayer.WMS options
11647 // If any custom options not documented here are used, they will be sent to the
11648 // WMS server as extra parameters in each request URL. This can be useful for
11649 // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
11650 defaultWmsParams: {
11654 // @option layers: String = ''
11655 // **(required)** Comma-separated list of WMS layers to show.
11658 // @option styles: String = ''
11659 // Comma-separated list of WMS styles.
11662 // @option format: String = 'image/jpeg'
11663 // WMS image format (use `'image/png'` for layers with transparency).
11664 format: 'image/jpeg',
11666 // @option transparent: Boolean = false
11667 // If `true`, the WMS service will return images with transparency.
11668 transparent: false,
11670 // @option version: String = '1.1.1'
11671 // Version of the WMS service to use
11676 // @option crs: CRS = null
11677 // Coordinate Reference System to use for the WMS requests, defaults to
11678 // map CRS. Don't change this if you're not sure what it means.
11681 // @option uppercase: Boolean = false
11682 // If `true`, WMS request parameter keys will be uppercase.
11686 initialize: function (url, options) {
11690 var wmsParams = extend({}, this.defaultWmsParams);
11692 // all keys that are not TileLayer options go to WMS params
11693 for (var i in options) {
11694 if (!(i in this.options)) {
11695 wmsParams[i] = options[i];
11699 options = setOptions(this, options);
11701 var realRetina = options.detectRetina && retina ? 2 : 1;
11702 var tileSize = this.getTileSize();
11703 wmsParams.width = tileSize.x * realRetina;
11704 wmsParams.height = tileSize.y * realRetina;
11706 this.wmsParams = wmsParams;
11709 onAdd: function (map) {
11711 this._crs = this.options.crs || map.options.crs;
11712 this._wmsVersion = parseFloat(this.wmsParams.version);
11714 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
11715 this.wmsParams[projectionKey] = this._crs.code;
11717 TileLayer.prototype.onAdd.call(this, map);
11720 getTileUrl: function (coords) {
11722 var tileBounds = this._tileCoordsToNwSe(coords),
11724 bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
11727 bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
11728 [min.y, min.x, max.y, max.x] :
11729 [min.x, min.y, max.x, max.y]).join(','),
11730 url = TileLayer.prototype.getTileUrl.call(this, coords);
11732 getParamString(this.wmsParams, url, this.options.uppercase) +
11733 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
11736 // @method setParams(params: Object, noRedraw?: Boolean): this
11737 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
11738 setParams: function (params, noRedraw) {
11740 extend(this.wmsParams, params);
11751 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
11752 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
11753 function tileLayerWMS(url, options) {
11754 return new TileLayerWMS(url, options);
11757 TileLayer.WMS = TileLayerWMS;
11758 tileLayer.wms = tileLayerWMS;
11765 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
11766 * DOM container of the renderer, its bounds, and its zoom animation.
11768 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
11769 * itself can be added or removed to the map. All paths use a renderer, which can
11770 * be implicit (the map will decide the type of renderer and use it automatically)
11771 * or explicit (using the [`renderer`](#path-renderer) option of the path).
11773 * Do not use this class directly, use `SVG` and `Canvas` instead.
11775 * @event update: Event
11776 * Fired when the renderer updates its bounds, center and zoom, for example when
11777 * its map has moved
11780 var Renderer = Layer.extend({
11783 // @aka Renderer options
11785 // @option padding: Number = 0.1
11786 // How much to extend the clip area around the map view (relative to its size)
11787 // e.g. 0.1 would be 10% of map view in each direction
11790 // @option tolerance: Number = 0
11791 // How much to extend click tolerance round a path/object on the map
11795 initialize: function (options) {
11796 setOptions(this, options);
11798 this._layers = this._layers || {};
11801 onAdd: function () {
11802 if (!this._container) {
11803 this._initContainer(); // defined by renderer implementations
11805 if (this._zoomAnimated) {
11806 addClass(this._container, 'leaflet-zoom-animated');
11810 this.getPane().appendChild(this._container);
11812 this.on('update', this._updatePaths, this);
11815 onRemove: function () {
11816 this.off('update', this._updatePaths, this);
11817 this._destroyContainer();
11820 getEvents: function () {
11822 viewreset: this._reset,
11823 zoom: this._onZoom,
11824 moveend: this._update,
11825 zoomend: this._onZoomEnd
11827 if (this._zoomAnimated) {
11828 events.zoomanim = this._onAnimZoom;
11833 _onAnimZoom: function (ev) {
11834 this._updateTransform(ev.center, ev.zoom);
11837 _onZoom: function () {
11838 this._updateTransform(this._map.getCenter(), this._map.getZoom());
11841 _updateTransform: function (center, zoom) {
11842 var scale = this._map.getZoomScale(zoom, this._zoom),
11843 position = getPosition(this._container),
11844 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
11845 currentCenterPoint = this._map.project(this._center, zoom),
11846 destCenterPoint = this._map.project(center, zoom),
11847 centerOffset = destCenterPoint.subtract(currentCenterPoint),
11849 topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
11852 setTransform(this._container, topLeftOffset, scale);
11854 setPosition(this._container, topLeftOffset);
11858 _reset: function () {
11860 this._updateTransform(this._center, this._zoom);
11862 for (var id in this._layers) {
11863 this._layers[id]._reset();
11867 _onZoomEnd: function () {
11868 for (var id in this._layers) {
11869 this._layers[id]._project();
11873 _updatePaths: function () {
11874 for (var id in this._layers) {
11875 this._layers[id]._update();
11879 _update: function () {
11880 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
11881 // Subclasses are responsible of firing the 'update' event.
11882 var p = this.options.padding,
11883 size = this._map.getSize(),
11884 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
11886 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
11888 this._center = this._map.getCenter();
11889 this._zoom = this._map.getZoom();
11895 * @inherits Renderer
11898 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
11899 * Inherits `Renderer`.
11901 * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
11902 * available in all web browsers, notably IE8, and overlapping geometries might
11903 * not display properly in some edge cases.
11907 * Use Canvas by default for all paths in the map:
11910 * var map = L.map('map', {
11911 * renderer: L.canvas()
11915 * Use a Canvas renderer with extra padding for specific vector geometries:
11918 * var map = L.map('map');
11919 * var myRenderer = L.canvas({ padding: 0.5 });
11920 * var line = L.polyline( coordinates, { renderer: myRenderer } );
11921 * var circle = L.circle( center, { renderer: myRenderer } );
11925 var Canvas = Renderer.extend({
11926 getEvents: function () {
11927 var events = Renderer.prototype.getEvents.call(this);
11928 events.viewprereset = this._onViewPreReset;
11932 _onViewPreReset: function () {
11933 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
11934 this._postponeUpdatePaths = true;
11937 onAdd: function () {
11938 Renderer.prototype.onAdd.call(this);
11940 // Redraw vectors since canvas is cleared upon removal,
11941 // in case of removing the renderer itself from the map.
11945 _initContainer: function () {
11946 var container = this._container = document.createElement('canvas');
11948 on(container, 'mousemove', throttle(this._onMouseMove, 32, this), this);
11949 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
11950 on(container, 'mouseout', this._handleMouseOut, this);
11952 this._ctx = container.getContext('2d');
11955 _destroyContainer: function () {
11956 cancelAnimFrame(this._redrawRequest);
11958 remove(this._container);
11959 off(this._container);
11960 delete this._container;
11963 _updatePaths: function () {
11964 if (this._postponeUpdatePaths) { return; }
11967 this._redrawBounds = null;
11968 for (var id in this._layers) {
11969 layer = this._layers[id];
11975 _update: function () {
11976 if (this._map._animatingZoom && this._bounds) { return; }
11978 this._drawnLayers = {};
11980 Renderer.prototype._update.call(this);
11982 var b = this._bounds,
11983 container = this._container,
11984 size = b.getSize(),
11985 m = retina ? 2 : 1;
11987 setPosition(container, b.min);
11989 // set canvas size (also clearing it); use double size on retina
11990 container.width = m * size.x;
11991 container.height = m * size.y;
11992 container.style.width = size.x + 'px';
11993 container.style.height = size.y + 'px';
11996 this._ctx.scale(2, 2);
11999 // translate so we use the same path coordinates after canvas element moves
12000 this._ctx.translate(-b.min.x, -b.min.y);
12002 // Tell paths to redraw themselves
12003 this.fire('update');
12006 _reset: function () {
12007 Renderer.prototype._reset.call(this);
12009 if (this._postponeUpdatePaths) {
12010 this._postponeUpdatePaths = false;
12011 this._updatePaths();
12015 _initPath: function (layer) {
12016 this._updateDashArray(layer);
12017 this._layers[stamp(layer)] = layer;
12019 var order = layer._order = {
12021 prev: this._drawLast,
12024 if (this._drawLast) { this._drawLast.next = order; }
12025 this._drawLast = order;
12026 this._drawFirst = this._drawFirst || this._drawLast;
12029 _addPath: function (layer) {
12030 this._requestRedraw(layer);
12033 _removePath: function (layer) {
12034 var order = layer._order;
12035 var next = order.next;
12036 var prev = order.prev;
12041 this._drawLast = prev;
12046 this._drawFirst = next;
12049 delete this._drawnLayers[layer._leaflet_id];
12051 delete layer._order;
12053 delete this._layers[stamp(layer)];
12055 this._requestRedraw(layer);
12058 _updatePath: function (layer) {
12059 // Redraw the union of the layer's old pixel
12060 // bounds and the new pixel bounds.
12061 this._extendRedrawBounds(layer);
12064 // The redraw will extend the redraw bounds
12065 // with the new pixel bounds.
12066 this._requestRedraw(layer);
12069 _updateStyle: function (layer) {
12070 this._updateDashArray(layer);
12071 this._requestRedraw(layer);
12074 _updateDashArray: function (layer) {
12075 if (typeof layer.options.dashArray === 'string') {
12076 var parts = layer.options.dashArray.split(','),
12079 for (i = 0; i < parts.length; i++) {
12080 dashArray.push(Number(parts[i]));
12082 layer.options._dashArray = dashArray;
12084 layer.options._dashArray = layer.options.dashArray;
12088 _requestRedraw: function (layer) {
12089 if (!this._map) { return; }
12091 this._extendRedrawBounds(layer);
12092 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
12095 _extendRedrawBounds: function (layer) {
12096 if (layer._pxBounds) {
12097 var padding = (layer.options.weight || 0) + 1;
12098 this._redrawBounds = this._redrawBounds || new Bounds();
12099 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
12100 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
12104 _redraw: function () {
12105 this._redrawRequest = null;
12107 if (this._redrawBounds) {
12108 this._redrawBounds.min._floor();
12109 this._redrawBounds.max._ceil();
12112 this._clear(); // clear layers in redraw bounds
12113 this._draw(); // draw layers
12115 this._redrawBounds = null;
12118 _clear: function () {
12119 var bounds = this._redrawBounds;
12121 var size = bounds.getSize();
12122 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
12124 this._ctx.clearRect(0, 0, this._container.width, this._container.height);
12128 _draw: function () {
12129 var layer, bounds = this._redrawBounds;
12132 var size = bounds.getSize();
12133 this._ctx.beginPath();
12134 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
12138 this._drawing = true;
12140 for (var order = this._drawFirst; order; order = order.next) {
12141 layer = order.layer;
12142 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
12143 layer._updatePath();
12147 this._drawing = false;
12149 this._ctx.restore(); // Restore state before clipping.
12152 _updatePoly: function (layer, closed) {
12153 if (!this._drawing) { return; }
12156 parts = layer._parts,
12157 len = parts.length,
12160 if (!len) { return; }
12162 this._drawnLayers[layer._leaflet_id] = layer;
12166 for (i = 0; i < len; i++) {
12167 for (j = 0, len2 = parts[i].length; j < len2; j++) {
12169 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
12176 this._fillStroke(ctx, layer);
12178 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
12181 _updateCircle: function (layer) {
12183 if (!this._drawing || layer._empty()) { return; }
12185 var p = layer._point,
12187 r = Math.max(Math.round(layer._radius), 1),
12188 s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
12190 this._drawnLayers[layer._leaflet_id] = layer;
12198 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
12204 this._fillStroke(ctx, layer);
12207 _fillStroke: function (ctx, layer) {
12208 var options = layer.options;
12210 if (options.fill) {
12211 ctx.globalAlpha = options.fillOpacity;
12212 ctx.fillStyle = options.fillColor || options.color;
12213 ctx.fill(options.fillRule || 'evenodd');
12216 if (options.stroke && options.weight !== 0) {
12217 if (ctx.setLineDash) {
12218 ctx.setLineDash(layer.options && layer.options._dashArray || []);
12220 ctx.globalAlpha = options.opacity;
12221 ctx.lineWidth = options.weight;
12222 ctx.strokeStyle = options.color;
12223 ctx.lineCap = options.lineCap;
12224 ctx.lineJoin = options.lineJoin;
12229 // Canvas obviously doesn't have mouse events for individual drawn objects,
12230 // so we emulate that by calculating what's under the mouse on mousemove/click manually
12232 _onClick: function (e) {
12233 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
12235 for (var order = this._drawFirst; order; order = order.next) {
12236 layer = order.layer;
12237 if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
12238 clickedLayer = layer;
12241 if (clickedLayer) {
12243 this._fireEvent([clickedLayer], e);
12247 _onMouseMove: function (e) {
12248 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
12250 var point = this._map.mouseEventToLayerPoint(e);
12251 this._handleMouseHover(e, point);
12255 _handleMouseOut: function (e) {
12256 var layer = this._hoveredLayer;
12258 // if we're leaving the layer, fire mouseout
12259 removeClass(this._container, 'leaflet-interactive');
12260 this._fireEvent([layer], e, 'mouseout');
12261 this._hoveredLayer = null;
12265 _handleMouseHover: function (e, point) {
12266 var layer, candidateHoveredLayer;
12268 for (var order = this._drawFirst; order; order = order.next) {
12269 layer = order.layer;
12270 if (layer.options.interactive && layer._containsPoint(point)) {
12271 candidateHoveredLayer = layer;
12275 if (candidateHoveredLayer !== this._hoveredLayer) {
12276 this._handleMouseOut(e);
12278 if (candidateHoveredLayer) {
12279 addClass(this._container, 'leaflet-interactive'); // change cursor
12280 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
12281 this._hoveredLayer = candidateHoveredLayer;
12285 if (this._hoveredLayer) {
12286 this._fireEvent([this._hoveredLayer], e);
12290 _fireEvent: function (layers, e, type) {
12291 this._map._fireDOMEvent(e, type || e.type, layers);
12294 _bringToFront: function (layer) {
12295 var order = layer._order;
12296 var next = order.next;
12297 var prev = order.prev;
12308 // Update first entry unless this is the
12310 this._drawFirst = next;
12313 order.prev = this._drawLast;
12314 this._drawLast.next = order;
12317 this._drawLast = order;
12319 this._requestRedraw(layer);
12322 _bringToBack: function (layer) {
12323 var order = layer._order;
12324 var next = order.next;
12325 var prev = order.prev;
12336 // Update last entry unless this is the
12338 this._drawLast = prev;
12343 order.next = this._drawFirst;
12344 this._drawFirst.prev = order;
12345 this._drawFirst = order;
12347 this._requestRedraw(layer);
12351 // @factory L.canvas(options?: Renderer options)
12352 // Creates a Canvas renderer with the given options.
12353 function canvas$1(options) {
12354 return canvas ? new Canvas(options) : null;
12358 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
12362 var vmlCreate = (function () {
12364 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
12365 return function (name) {
12366 return document.createElement('<lvml:' + name + ' class="lvml">');
12369 return function (name) {
12370 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
12379 * 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.
12381 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
12382 * with old versions of Internet Explorer.
12385 // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
12388 _initContainer: function () {
12389 this._container = create$1('div', 'leaflet-vml-container');
12392 _update: function () {
12393 if (this._map._animatingZoom) { return; }
12394 Renderer.prototype._update.call(this);
12395 this.fire('update');
12398 _initPath: function (layer) {
12399 var container = layer._container = vmlCreate('shape');
12401 addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
12403 container.coordsize = '1 1';
12405 layer._path = vmlCreate('path');
12406 container.appendChild(layer._path);
12408 this._updateStyle(layer);
12409 this._layers[stamp(layer)] = layer;
12412 _addPath: function (layer) {
12413 var container = layer._container;
12414 this._container.appendChild(container);
12416 if (layer.options.interactive) {
12417 layer.addInteractiveTarget(container);
12421 _removePath: function (layer) {
12422 var container = layer._container;
12424 layer.removeInteractiveTarget(container);
12425 delete this._layers[stamp(layer)];
12428 _updateStyle: function (layer) {
12429 var stroke = layer._stroke,
12430 fill = layer._fill,
12431 options = layer.options,
12432 container = layer._container;
12434 container.stroked = !!options.stroke;
12435 container.filled = !!options.fill;
12437 if (options.stroke) {
12439 stroke = layer._stroke = vmlCreate('stroke');
12441 container.appendChild(stroke);
12442 stroke.weight = options.weight + 'px';
12443 stroke.color = options.color;
12444 stroke.opacity = options.opacity;
12446 if (options.dashArray) {
12447 stroke.dashStyle = isArray(options.dashArray) ?
12448 options.dashArray.join(' ') :
12449 options.dashArray.replace(/( *, *)/g, ' ');
12451 stroke.dashStyle = '';
12453 stroke.endcap = options.lineCap.replace('butt', 'flat');
12454 stroke.joinstyle = options.lineJoin;
12456 } else if (stroke) {
12457 container.removeChild(stroke);
12458 layer._stroke = null;
12461 if (options.fill) {
12463 fill = layer._fill = vmlCreate('fill');
12465 container.appendChild(fill);
12466 fill.color = options.fillColor || options.color;
12467 fill.opacity = options.fillOpacity;
12470 container.removeChild(fill);
12471 layer._fill = null;
12475 _updateCircle: function (layer) {
12476 var p = layer._point.round(),
12477 r = Math.round(layer._radius),
12478 r2 = Math.round(layer._radiusY || r);
12480 this._setPath(layer, layer._empty() ? 'M0 0' :
12481 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
12484 _setPath: function (layer, path) {
12485 layer._path.v = path;
12488 _bringToFront: function (layer) {
12489 toFront(layer._container);
12492 _bringToBack: function (layer) {
12493 toBack(layer._container);
12497 var create$2 = vml ? vmlCreate : svgCreate;
12501 * @inherits Renderer
12504 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
12505 * Inherits `Renderer`.
12507 * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
12508 * available in all web browsers, notably Android 2.x and 3.x.
12510 * Although SVG is not available on IE7 and IE8, these browsers support
12511 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
12512 * (a now deprecated technology), and the SVG renderer will fall back to VML in
12517 * Use SVG by default for all paths in the map:
12520 * var map = L.map('map', {
12521 * renderer: L.svg()
12525 * Use a SVG renderer with extra padding for specific vector geometries:
12528 * var map = L.map('map');
12529 * var myRenderer = L.svg({ padding: 0.5 });
12530 * var line = L.polyline( coordinates, { renderer: myRenderer } );
12531 * var circle = L.circle( center, { renderer: myRenderer } );
12535 var SVG = Renderer.extend({
12537 getEvents: function () {
12538 var events = Renderer.prototype.getEvents.call(this);
12539 events.zoomstart = this._onZoomStart;
12543 _initContainer: function () {
12544 this._container = create$2('svg');
12546 // makes it possible to click through svg root; we'll reset it back in individual paths
12547 this._container.setAttribute('pointer-events', 'none');
12549 this._rootGroup = create$2('g');
12550 this._container.appendChild(this._rootGroup);
12553 _destroyContainer: function () {
12554 remove(this._container);
12555 off(this._container);
12556 delete this._container;
12557 delete this._rootGroup;
12558 delete this._svgSize;
12561 _onZoomStart: function () {
12562 // Drag-then-pinch interactions might mess up the center and zoom.
12563 // In this case, the easiest way to prevent this is re-do the renderer
12564 // bounds and padding when the zooming starts.
12568 _update: function () {
12569 if (this._map._animatingZoom && this._bounds) { return; }
12571 Renderer.prototype._update.call(this);
12573 var b = this._bounds,
12574 size = b.getSize(),
12575 container = this._container;
12577 // set size of svg-container if changed
12578 if (!this._svgSize || !this._svgSize.equals(size)) {
12579 this._svgSize = size;
12580 container.setAttribute('width', size.x);
12581 container.setAttribute('height', size.y);
12584 // movement: update container viewBox so that we don't have to change coordinates of individual layers
12585 setPosition(container, b.min);
12586 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
12588 this.fire('update');
12591 // methods below are called by vector layers implementations
12593 _initPath: function (layer) {
12594 var path = layer._path = create$2('path');
12597 // @option className: String = null
12598 // Custom class name set on an element. Only for SVG renderer.
12599 if (layer.options.className) {
12600 addClass(path, layer.options.className);
12603 if (layer.options.interactive) {
12604 addClass(path, 'leaflet-interactive');
12607 this._updateStyle(layer);
12608 this._layers[stamp(layer)] = layer;
12611 _addPath: function (layer) {
12612 if (!this._rootGroup) { this._initContainer(); }
12613 this._rootGroup.appendChild(layer._path);
12614 layer.addInteractiveTarget(layer._path);
12617 _removePath: function (layer) {
12618 remove(layer._path);
12619 layer.removeInteractiveTarget(layer._path);
12620 delete this._layers[stamp(layer)];
12623 _updatePath: function (layer) {
12628 _updateStyle: function (layer) {
12629 var path = layer._path,
12630 options = layer.options;
12632 if (!path) { return; }
12634 if (options.stroke) {
12635 path.setAttribute('stroke', options.color);
12636 path.setAttribute('stroke-opacity', options.opacity);
12637 path.setAttribute('stroke-width', options.weight);
12638 path.setAttribute('stroke-linecap', options.lineCap);
12639 path.setAttribute('stroke-linejoin', options.lineJoin);
12641 if (options.dashArray) {
12642 path.setAttribute('stroke-dasharray', options.dashArray);
12644 path.removeAttribute('stroke-dasharray');
12647 if (options.dashOffset) {
12648 path.setAttribute('stroke-dashoffset', options.dashOffset);
12650 path.removeAttribute('stroke-dashoffset');
12653 path.setAttribute('stroke', 'none');
12656 if (options.fill) {
12657 path.setAttribute('fill', options.fillColor || options.color);
12658 path.setAttribute('fill-opacity', options.fillOpacity);
12659 path.setAttribute('fill-rule', options.fillRule || 'evenodd');
12661 path.setAttribute('fill', 'none');
12665 _updatePoly: function (layer, closed) {
12666 this._setPath(layer, pointsToPath(layer._parts, closed));
12669 _updateCircle: function (layer) {
12670 var p = layer._point,
12671 r = Math.max(Math.round(layer._radius), 1),
12672 r2 = Math.max(Math.round(layer._radiusY), 1) || r,
12673 arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
12675 // drawing a circle with two half-arcs
12676 var d = layer._empty() ? 'M0 0' :
12677 'M' + (p.x - r) + ',' + p.y +
12678 arc + (r * 2) + ',0 ' +
12679 arc + (-r * 2) + ',0 ';
12681 this._setPath(layer, d);
12684 _setPath: function (layer, path) {
12685 layer._path.setAttribute('d', path);
12688 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
12689 _bringToFront: function (layer) {
12690 toFront(layer._path);
12693 _bringToBack: function (layer) {
12694 toBack(layer._path);
12699 SVG.include(vmlMixin);
12703 // @factory L.svg(options?: Renderer options)
12704 // Creates a SVG renderer with the given options.
12705 function svg$1(options) {
12706 return svg || vml ? new SVG(options) : null;
12710 // @namespace Map; @method getRenderer(layer: Path): Renderer
12711 // Returns the instance of `Renderer` that should be used to render the given
12712 // `Path`. It will ensure that the `renderer` options of the map and paths
12713 // are respected, and that the renderers do exist on the map.
12714 getRenderer: function (layer) {
12715 // @namespace Path; @option renderer: Renderer
12716 // Use this specific instance of `Renderer` for this path. Takes
12717 // precedence over the map's [default renderer](#map-renderer).
12718 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
12721 renderer = this._renderer = this._createRenderer();
12724 if (!this.hasLayer(renderer)) {
12725 this.addLayer(renderer);
12730 _getPaneRenderer: function (name) {
12731 if (name === 'overlayPane' || name === undefined) {
12735 var renderer = this._paneRenderers[name];
12736 if (renderer === undefined) {
12737 renderer = this._createRenderer({pane: name});
12738 this._paneRenderers[name] = renderer;
12743 _createRenderer: function (options) {
12744 // @namespace Map; @option preferCanvas: Boolean = false
12745 // Whether `Path`s should be rendered on a `Canvas` renderer.
12746 // By default, all `Path`s are rendered in a `SVG` renderer.
12747 return (this.options.preferCanvas && canvas$1(options)) || svg$1(options);
12752 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
12758 * @inherits Polygon
12760 * A class for drawing rectangle overlays on a map. Extends `Polygon`.
12765 * // define rectangle geographical bounds
12766 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
12768 * // create an orange rectangle
12769 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
12771 * // zoom the map to the rectangle bounds
12772 * map.fitBounds(bounds);
12778 var Rectangle = Polygon.extend({
12779 initialize: function (latLngBounds, options) {
12780 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
12783 // @method setBounds(latLngBounds: LatLngBounds): this
12784 // Redraws the rectangle with the passed bounds.
12785 setBounds: function (latLngBounds) {
12786 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
12789 _boundsToLatLngs: function (latLngBounds) {
12790 latLngBounds = toLatLngBounds(latLngBounds);
12792 latLngBounds.getSouthWest(),
12793 latLngBounds.getNorthWest(),
12794 latLngBounds.getNorthEast(),
12795 latLngBounds.getSouthEast()
12801 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
12802 function rectangle(latLngBounds, options) {
12803 return new Rectangle(latLngBounds, options);
12806 SVG.create = create$2;
12807 SVG.pointsToPath = pointsToPath;
12809 GeoJSON.geometryToLayer = geometryToLayer;
12810 GeoJSON.coordsToLatLng = coordsToLatLng;
12811 GeoJSON.coordsToLatLngs = coordsToLatLngs;
12812 GeoJSON.latLngToCoords = latLngToCoords;
12813 GeoJSON.latLngsToCoords = latLngsToCoords;
12814 GeoJSON.getFeature = getFeature;
12815 GeoJSON.asFeature = asFeature;
12818 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
12819 * (zoom to a selected bounding box), enabled by default.
12823 // @section Interaction Options
12825 // @option boxZoom: Boolean = true
12826 // Whether the map can be zoomed to a rectangular area specified by
12827 // dragging the mouse while pressing the shift key.
12831 var BoxZoom = Handler.extend({
12832 initialize: function (map) {
12834 this._container = map._container;
12835 this._pane = map._panes.overlayPane;
12836 this._resetStateTimeout = 0;
12837 map.on('unload', this._destroy, this);
12840 addHooks: function () {
12841 on(this._container, 'mousedown', this._onMouseDown, this);
12844 removeHooks: function () {
12845 off(this._container, 'mousedown', this._onMouseDown, this);
12848 moved: function () {
12849 return this._moved;
12852 _destroy: function () {
12853 remove(this._pane);
12857 _resetState: function () {
12858 this._resetStateTimeout = 0;
12859 this._moved = false;
12862 _clearDeferredResetState: function () {
12863 if (this._resetStateTimeout !== 0) {
12864 clearTimeout(this._resetStateTimeout);
12865 this._resetStateTimeout = 0;
12869 _onMouseDown: function (e) {
12870 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
12872 // Clear the deferred resetState if it hasn't executed yet, otherwise it
12873 // will interrupt the interaction and orphan a box element in the container.
12874 this._clearDeferredResetState();
12875 this._resetState();
12877 disableTextSelection();
12878 disableImageDrag();
12880 this._startPoint = this._map.mouseEventToContainerPoint(e);
12884 mousemove: this._onMouseMove,
12885 mouseup: this._onMouseUp,
12886 keydown: this._onKeyDown
12890 _onMouseMove: function (e) {
12891 if (!this._moved) {
12892 this._moved = true;
12894 this._box = create$1('div', 'leaflet-zoom-box', this._container);
12895 addClass(this._container, 'leaflet-crosshair');
12897 this._map.fire('boxzoomstart');
12900 this._point = this._map.mouseEventToContainerPoint(e);
12902 var bounds = new Bounds(this._point, this._startPoint),
12903 size = bounds.getSize();
12905 setPosition(this._box, bounds.min);
12907 this._box.style.width = size.x + 'px';
12908 this._box.style.height = size.y + 'px';
12911 _finish: function () {
12914 removeClass(this._container, 'leaflet-crosshair');
12917 enableTextSelection();
12922 mousemove: this._onMouseMove,
12923 mouseup: this._onMouseUp,
12924 keydown: this._onKeyDown
12928 _onMouseUp: function (e) {
12929 if ((e.which !== 1) && (e.button !== 1)) { return; }
12933 if (!this._moved) { return; }
12934 // Postpone to next JS tick so internal click event handling
12935 // still see it as "moved".
12936 this._clearDeferredResetState();
12937 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
12939 var bounds = new LatLngBounds(
12940 this._map.containerPointToLatLng(this._startPoint),
12941 this._map.containerPointToLatLng(this._point));
12945 .fire('boxzoomend', {boxZoomBounds: bounds});
12948 _onKeyDown: function (e) {
12949 if (e.keyCode === 27) {
12955 // @section Handlers
12956 // @property boxZoom: Handler
12957 // Box (shift-drag with mouse) zoom handler.
12958 Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
12961 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
12965 // @section Interaction Options
12968 // @option doubleClickZoom: Boolean|String = true
12969 // Whether the map can be zoomed in by double clicking on it and
12970 // zoomed out by double clicking while holding shift. If passed
12971 // `'center'`, double-click zoom will zoom to the center of the
12972 // view regardless of where the mouse was.
12973 doubleClickZoom: true
12976 var DoubleClickZoom = Handler.extend({
12977 addHooks: function () {
12978 this._map.on('dblclick', this._onDoubleClick, this);
12981 removeHooks: function () {
12982 this._map.off('dblclick', this._onDoubleClick, this);
12985 _onDoubleClick: function (e) {
12986 var map = this._map,
12987 oldZoom = map.getZoom(),
12988 delta = map.options.zoomDelta,
12989 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
12991 if (map.options.doubleClickZoom === 'center') {
12994 map.setZoomAround(e.containerPoint, zoom);
12999 // @section Handlers
13001 // Map properties include interaction handlers that allow you to control
13002 // interaction behavior in runtime, enabling or disabling certain features such
13003 // as dragging or touch zoom (see `Handler` methods). For example:
13006 // map.doubleClickZoom.disable();
13009 // @property doubleClickZoom: Handler
13010 // Double click zoom handler.
13011 Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
13014 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
13018 // @section Interaction Options
13020 // @option dragging: Boolean = true
13021 // Whether the map be draggable with mouse/touch or not.
13024 // @section Panning Inertia Options
13025 // @option inertia: Boolean = *
13026 // If enabled, panning of the map will have an inertia effect where
13027 // the map builds momentum while dragging and continues moving in
13028 // the same direction for some time. Feels especially nice on touch
13029 // devices. Enabled by default unless running on old Android devices.
13030 inertia: !android23,
13032 // @option inertiaDeceleration: Number = 3000
13033 // The rate with which the inertial movement slows down, in pixels/second².
13034 inertiaDeceleration: 3400, // px/s^2
13036 // @option inertiaMaxSpeed: Number = Infinity
13037 // Max speed of the inertial movement, in pixels/second.
13038 inertiaMaxSpeed: Infinity, // px/s
13040 // @option easeLinearity: Number = 0.2
13041 easeLinearity: 0.2,
13043 // TODO refactor, move to CRS
13044 // @option worldCopyJump: Boolean = false
13045 // With this option enabled, the map tracks when you pan to another "copy"
13046 // of the world and seamlessly jumps to the original one so that all overlays
13047 // like markers and vector layers are still visible.
13048 worldCopyJump: false,
13050 // @option maxBoundsViscosity: Number = 0.0
13051 // If `maxBounds` is set, this option will control how solid the bounds
13052 // are when dragging the map around. The default value of `0.0` allows the
13053 // user to drag outside the bounds at normal speed, higher values will
13054 // slow down map dragging outside bounds, and `1.0` makes the bounds fully
13055 // solid, preventing the user from dragging outside the bounds.
13056 maxBoundsViscosity: 0.0
13059 var Drag = Handler.extend({
13060 addHooks: function () {
13061 if (!this._draggable) {
13062 var map = this._map;
13064 this._draggable = new Draggable(map._mapPane, map._container);
13066 this._draggable.on({
13067 dragstart: this._onDragStart,
13068 drag: this._onDrag,
13069 dragend: this._onDragEnd
13072 this._draggable.on('predrag', this._onPreDragLimit, this);
13073 if (map.options.worldCopyJump) {
13074 this._draggable.on('predrag', this._onPreDragWrap, this);
13075 map.on('zoomend', this._onZoomEnd, this);
13077 map.whenReady(this._onZoomEnd, this);
13080 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
13081 this._draggable.enable();
13082 this._positions = [];
13086 removeHooks: function () {
13087 removeClass(this._map._container, 'leaflet-grab');
13088 removeClass(this._map._container, 'leaflet-touch-drag');
13089 this._draggable.disable();
13092 moved: function () {
13093 return this._draggable && this._draggable._moved;
13096 moving: function () {
13097 return this._draggable && this._draggable._moving;
13100 _onDragStart: function () {
13101 var map = this._map;
13104 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
13105 var bounds = toLatLngBounds(this._map.options.maxBounds);
13107 this._offsetLimit = toBounds(
13108 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
13109 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
13110 .add(this._map.getSize()));
13112 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
13114 this._offsetLimit = null;
13119 .fire('dragstart');
13121 if (map.options.inertia) {
13122 this._positions = [];
13127 _onDrag: function (e) {
13128 if (this._map.options.inertia) {
13129 var time = this._lastTime = +new Date(),
13130 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
13132 this._positions.push(pos);
13133 this._times.push(time);
13135 this._prunePositions(time);
13143 _prunePositions: function (time) {
13144 while (this._positions.length > 1 && time - this._times[0] > 50) {
13145 this._positions.shift();
13146 this._times.shift();
13150 _onZoomEnd: function () {
13151 var pxCenter = this._map.getSize().divideBy(2),
13152 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
13154 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
13155 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
13158 _viscousLimit: function (value, threshold) {
13159 return value - (value - threshold) * this._viscosity;
13162 _onPreDragLimit: function () {
13163 if (!this._viscosity || !this._offsetLimit) { return; }
13165 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
13167 var limit = this._offsetLimit;
13168 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
13169 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
13170 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
13171 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
13173 this._draggable._newPos = this._draggable._startPos.add(offset);
13176 _onPreDragWrap: function () {
13177 // TODO refactor to be able to adjust map pane position after zoom
13178 var worldWidth = this._worldWidth,
13179 halfWidth = Math.round(worldWidth / 2),
13180 dx = this._initialWorldOffset,
13181 x = this._draggable._newPos.x,
13182 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
13183 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
13184 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
13186 this._draggable._absPos = this._draggable._newPos.clone();
13187 this._draggable._newPos.x = newX;
13190 _onDragEnd: function (e) {
13191 var map = this._map,
13192 options = map.options,
13194 noInertia = !options.inertia || this._times.length < 2;
13196 map.fire('dragend', e);
13199 map.fire('moveend');
13202 this._prunePositions(+new Date());
13204 var direction = this._lastPos.subtract(this._positions[0]),
13205 duration = (this._lastTime - this._times[0]) / 1000,
13206 ease = options.easeLinearity,
13208 speedVector = direction.multiplyBy(ease / duration),
13209 speed = speedVector.distanceTo([0, 0]),
13211 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
13212 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
13214 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
13215 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
13217 if (!offset.x && !offset.y) {
13218 map.fire('moveend');
13221 offset = map._limitOffset(offset, map.options.maxBounds);
13223 requestAnimFrame(function () {
13224 map.panBy(offset, {
13225 duration: decelerationDuration,
13226 easeLinearity: ease,
13236 // @section Handlers
13237 // @property dragging: Handler
13238 // Map dragging handler (by both mouse and touch).
13239 Map.addInitHook('addHandler', 'dragging', Drag);
13242 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
13246 // @section Keyboard Navigation Options
13248 // @option keyboard: Boolean = true
13249 // Makes the map focusable and allows users to navigate the map with keyboard
13250 // arrows and `+`/`-` keys.
13253 // @option keyboardPanDelta: Number = 80
13254 // Amount of pixels to pan when pressing an arrow key.
13255 keyboardPanDelta: 80
13258 var Keyboard = Handler.extend({
13265 zoomIn: [187, 107, 61, 171],
13266 zoomOut: [189, 109, 54, 173]
13269 initialize: function (map) {
13272 this._setPanDelta(map.options.keyboardPanDelta);
13273 this._setZoomDelta(map.options.zoomDelta);
13276 addHooks: function () {
13277 var container = this._map._container;
13279 // make the container focusable by tabbing
13280 if (container.tabIndex <= 0) {
13281 container.tabIndex = '0';
13285 focus: this._onFocus,
13286 blur: this._onBlur,
13287 mousedown: this._onMouseDown
13291 focus: this._addHooks,
13292 blur: this._removeHooks
13296 removeHooks: function () {
13297 this._removeHooks();
13299 off(this._map._container, {
13300 focus: this._onFocus,
13301 blur: this._onBlur,
13302 mousedown: this._onMouseDown
13306 focus: this._addHooks,
13307 blur: this._removeHooks
13311 _onMouseDown: function () {
13312 if (this._focused) { return; }
13314 var body = document.body,
13315 docEl = document.documentElement,
13316 top = body.scrollTop || docEl.scrollTop,
13317 left = body.scrollLeft || docEl.scrollLeft;
13319 this._map._container.focus();
13321 window.scrollTo(left, top);
13324 _onFocus: function () {
13325 this._focused = true;
13326 this._map.fire('focus');
13329 _onBlur: function () {
13330 this._focused = false;
13331 this._map.fire('blur');
13334 _setPanDelta: function (panDelta) {
13335 var keys = this._panKeys = {},
13336 codes = this.keyCodes,
13339 for (i = 0, len = codes.left.length; i < len; i++) {
13340 keys[codes.left[i]] = [-1 * panDelta, 0];
13342 for (i = 0, len = codes.right.length; i < len; i++) {
13343 keys[codes.right[i]] = [panDelta, 0];
13345 for (i = 0, len = codes.down.length; i < len; i++) {
13346 keys[codes.down[i]] = [0, panDelta];
13348 for (i = 0, len = codes.up.length; i < len; i++) {
13349 keys[codes.up[i]] = [0, -1 * panDelta];
13353 _setZoomDelta: function (zoomDelta) {
13354 var keys = this._zoomKeys = {},
13355 codes = this.keyCodes,
13358 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
13359 keys[codes.zoomIn[i]] = zoomDelta;
13361 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
13362 keys[codes.zoomOut[i]] = -zoomDelta;
13366 _addHooks: function () {
13367 on(document, 'keydown', this._onKeyDown, this);
13370 _removeHooks: function () {
13371 off(document, 'keydown', this._onKeyDown, this);
13374 _onKeyDown: function (e) {
13375 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
13377 var key = e.keyCode,
13381 if (key in this._panKeys) {
13382 if (!map._panAnim || !map._panAnim._inProgress) {
13383 offset = this._panKeys[key];
13385 offset = toPoint(offset).multiplyBy(3);
13390 if (map.options.maxBounds) {
13391 map.panInsideBounds(map.options.maxBounds);
13394 } else if (key in this._zoomKeys) {
13395 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
13397 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
13408 // @section Handlers
13409 // @section Handlers
13410 // @property keyboard: Handler
13411 // Keyboard navigation handler.
13412 Map.addInitHook('addHandler', 'keyboard', Keyboard);
13415 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
13419 // @section Interaction Options
13421 // @section Mousewheel options
13422 // @option scrollWheelZoom: Boolean|String = true
13423 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
13424 // it will zoom to the center of the view regardless of where the mouse was.
13425 scrollWheelZoom: true,
13427 // @option wheelDebounceTime: Number = 40
13428 // Limits the rate at which a wheel can fire (in milliseconds). By default
13429 // user can't zoom via wheel more often than once per 40 ms.
13430 wheelDebounceTime: 40,
13432 // @option wheelPxPerZoomLevel: Number = 60
13433 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
13434 // mean a change of one full zoom level. Smaller values will make wheel-zooming
13435 // faster (and vice versa).
13436 wheelPxPerZoomLevel: 60
13439 var ScrollWheelZoom = Handler.extend({
13440 addHooks: function () {
13441 on(this._map._container, 'mousewheel', this._onWheelScroll, this);
13446 removeHooks: function () {
13447 off(this._map._container, 'mousewheel', this._onWheelScroll, this);
13450 _onWheelScroll: function (e) {
13451 var delta = getWheelDelta(e);
13453 var debounce = this._map.options.wheelDebounceTime;
13455 this._delta += delta;
13456 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
13458 if (!this._startTime) {
13459 this._startTime = +new Date();
13462 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
13464 clearTimeout(this._timer);
13465 this._timer = setTimeout(bind(this._performZoom, this), left);
13470 _performZoom: function () {
13471 var map = this._map,
13472 zoom = map.getZoom(),
13473 snap = this._map.options.zoomSnap || 0;
13475 map._stop(); // stop panning and fly animations if any
13477 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
13478 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
13479 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
13480 d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
13481 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
13484 this._startTime = null;
13486 if (!delta) { return; }
13488 if (map.options.scrollWheelZoom === 'center') {
13489 map.setZoom(zoom + delta);
13491 map.setZoomAround(this._lastMousePos, zoom + delta);
13496 // @section Handlers
13497 // @property scrollWheelZoom: Handler
13498 // Scroll wheel zoom handler.
13499 Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
13502 * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
13506 // @section Interaction Options
13508 // @section Touch interaction options
13509 // @option tap: Boolean = true
13510 // Enables mobile hacks for supporting instant taps (fixing 200ms click
13511 // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
13514 // @option tapTolerance: Number = 15
13515 // The max number of pixels a user can shift his finger during touch
13516 // for it to be considered a valid tap.
13520 var Tap = Handler.extend({
13521 addHooks: function () {
13522 on(this._map._container, 'touchstart', this._onDown, this);
13525 removeHooks: function () {
13526 off(this._map._container, 'touchstart', this._onDown, this);
13529 _onDown: function (e) {
13530 if (!e.touches) { return; }
13534 this._fireClick = true;
13536 // don't simulate click or track longpress if more than 1 touch
13537 if (e.touches.length > 1) {
13538 this._fireClick = false;
13539 clearTimeout(this._holdTimeout);
13543 var first = e.touches[0],
13546 this._startPos = this._newPos = new Point(first.clientX, first.clientY);
13548 // if touching a link, highlight it
13549 if (el.tagName && el.tagName.toLowerCase() === 'a') {
13550 addClass(el, 'leaflet-active');
13553 // simulate long hold but setting a timeout
13554 this._holdTimeout = setTimeout(bind(function () {
13555 if (this._isTapValid()) {
13556 this._fireClick = false;
13558 this._simulateEvent('contextmenu', first);
13562 this._simulateEvent('mousedown', first);
13565 touchmove: this._onMove,
13566 touchend: this._onUp
13570 _onUp: function (e) {
13571 clearTimeout(this._holdTimeout);
13574 touchmove: this._onMove,
13575 touchend: this._onUp
13578 if (this._fireClick && e && e.changedTouches) {
13580 var first = e.changedTouches[0],
13583 if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
13584 removeClass(el, 'leaflet-active');
13587 this._simulateEvent('mouseup', first);
13589 // simulate click if the touch didn't move too much
13590 if (this._isTapValid()) {
13591 this._simulateEvent('click', first);
13596 _isTapValid: function () {
13597 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
13600 _onMove: function (e) {
13601 var first = e.touches[0];
13602 this._newPos = new Point(first.clientX, first.clientY);
13603 this._simulateEvent('mousemove', first);
13606 _simulateEvent: function (type, e) {
13607 var simulatedEvent = document.createEvent('MouseEvents');
13609 simulatedEvent._simulated = true;
13610 e.target._simulatedClick = true;
13612 simulatedEvent.initMouseEvent(
13613 type, true, true, window, 1,
13614 e.screenX, e.screenY,
13615 e.clientX, e.clientY,
13616 false, false, false, false, 0, null);
13618 e.target.dispatchEvent(simulatedEvent);
13622 // @section Handlers
13623 // @property tap: Handler
13624 // Mobile touch hacks (quick tap and touch hold) handler.
13625 if (touch && !pointer) {
13626 Map.addInitHook('addHandler', 'tap', Tap);
13630 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
13634 // @section Interaction Options
13636 // @section Touch interaction options
13637 // @option touchZoom: Boolean|String = *
13638 // Whether the map can be zoomed by touch-dragging with two fingers. If
13639 // passed `'center'`, it will zoom to the center of the view regardless of
13640 // where the touch events (fingers) were. Enabled for touch-capable web
13641 // browsers except for old Androids.
13642 touchZoom: touch && !android23,
13644 // @option bounceAtZoomLimits: Boolean = true
13645 // Set it to false if you don't want the map to zoom beyond min/max zoom
13646 // and then bounce back when pinch-zooming.
13647 bounceAtZoomLimits: true
13650 var TouchZoom = Handler.extend({
13651 addHooks: function () {
13652 addClass(this._map._container, 'leaflet-touch-zoom');
13653 on(this._map._container, 'touchstart', this._onTouchStart, this);
13656 removeHooks: function () {
13657 removeClass(this._map._container, 'leaflet-touch-zoom');
13658 off(this._map._container, 'touchstart', this._onTouchStart, this);
13661 _onTouchStart: function (e) {
13662 var map = this._map;
13663 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
13665 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
13666 p2 = map.mouseEventToContainerPoint(e.touches[1]);
13668 this._centerPoint = map.getSize()._divideBy(2);
13669 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
13670 if (map.options.touchZoom !== 'center') {
13671 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
13674 this._startDist = p1.distanceTo(p2);
13675 this._startZoom = map.getZoom();
13677 this._moved = false;
13678 this._zooming = true;
13682 on(document, 'touchmove', this._onTouchMove, this);
13683 on(document, 'touchend', this._onTouchEnd, this);
13688 _onTouchMove: function (e) {
13689 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
13691 var map = this._map,
13692 p1 = map.mouseEventToContainerPoint(e.touches[0]),
13693 p2 = map.mouseEventToContainerPoint(e.touches[1]),
13694 scale = p1.distanceTo(p2) / this._startDist;
13696 this._zoom = map.getScaleZoom(scale, this._startZoom);
13698 if (!map.options.bounceAtZoomLimits && (
13699 (this._zoom < map.getMinZoom() && scale < 1) ||
13700 (this._zoom > map.getMaxZoom() && scale > 1))) {
13701 this._zoom = map._limitZoom(this._zoom);
13704 if (map.options.touchZoom === 'center') {
13705 this._center = this._startLatLng;
13706 if (scale === 1) { return; }
13708 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
13709 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
13710 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
13711 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
13714 if (!this._moved) {
13715 map._moveStart(true, false);
13716 this._moved = true;
13719 cancelAnimFrame(this._animRequest);
13721 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
13722 this._animRequest = requestAnimFrame(moveFn, this, true);
13727 _onTouchEnd: function () {
13728 if (!this._moved || !this._zooming) {
13729 this._zooming = false;
13733 this._zooming = false;
13734 cancelAnimFrame(this._animRequest);
13736 off(document, 'touchmove', this._onTouchMove);
13737 off(document, 'touchend', this._onTouchEnd);
13739 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
13740 if (this._map.options.zoomAnimation) {
13741 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
13743 this._map._resetView(this._center, this._map._limitZoom(this._zoom));
13748 // @section Handlers
13749 // @property touchZoom: Handler
13750 // Touch zoom handler.
13751 Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
13753 Map.BoxZoom = BoxZoom;
13754 Map.DoubleClickZoom = DoubleClickZoom;
13756 Map.Keyboard = Keyboard;
13757 Map.ScrollWheelZoom = ScrollWheelZoom;
13759 Map.TouchZoom = TouchZoom;
13761 Object.freeze = freeze;
13763 exports.version = version;
13764 exports.Control = Control;
13765 exports.control = control;
13766 exports.Browser = Browser;
13767 exports.Evented = Evented;
13768 exports.Mixin = Mixin;
13769 exports.Util = Util;
13770 exports.Class = Class;
13771 exports.Handler = Handler;
13772 exports.extend = extend;
13773 exports.bind = bind;
13774 exports.stamp = stamp;
13775 exports.setOptions = setOptions;
13776 exports.DomEvent = DomEvent;
13777 exports.DomUtil = DomUtil;
13778 exports.PosAnimation = PosAnimation;
13779 exports.Draggable = Draggable;
13780 exports.LineUtil = LineUtil;
13781 exports.PolyUtil = PolyUtil;
13782 exports.Point = Point;
13783 exports.point = toPoint;
13784 exports.Bounds = Bounds;
13785 exports.bounds = toBounds;
13786 exports.Transformation = Transformation;
13787 exports.transformation = toTransformation;
13788 exports.Projection = index;
13789 exports.LatLng = LatLng;
13790 exports.latLng = toLatLng;
13791 exports.LatLngBounds = LatLngBounds;
13792 exports.latLngBounds = toLatLngBounds;
13794 exports.GeoJSON = GeoJSON;
13795 exports.geoJSON = geoJSON;
13796 exports.geoJson = geoJson;
13797 exports.Layer = Layer;
13798 exports.LayerGroup = LayerGroup;
13799 exports.layerGroup = layerGroup;
13800 exports.FeatureGroup = FeatureGroup;
13801 exports.featureGroup = featureGroup;
13802 exports.ImageOverlay = ImageOverlay;
13803 exports.imageOverlay = imageOverlay;
13804 exports.VideoOverlay = VideoOverlay;
13805 exports.videoOverlay = videoOverlay;
13806 exports.DivOverlay = DivOverlay;
13807 exports.Popup = Popup;
13808 exports.popup = popup;
13809 exports.Tooltip = Tooltip;
13810 exports.tooltip = tooltip;
13811 exports.Icon = Icon;
13812 exports.icon = icon;
13813 exports.DivIcon = DivIcon;
13814 exports.divIcon = divIcon;
13815 exports.Marker = Marker;
13816 exports.marker = marker;
13817 exports.TileLayer = TileLayer;
13818 exports.tileLayer = tileLayer;
13819 exports.GridLayer = GridLayer;
13820 exports.gridLayer = gridLayer;
13822 exports.svg = svg$1;
13823 exports.Renderer = Renderer;
13824 exports.Canvas = Canvas;
13825 exports.canvas = canvas$1;
13826 exports.Path = Path;
13827 exports.CircleMarker = CircleMarker;
13828 exports.circleMarker = circleMarker;
13829 exports.Circle = Circle;
13830 exports.circle = circle;
13831 exports.Polyline = Polyline;
13832 exports.polyline = polyline;
13833 exports.Polygon = Polygon;
13834 exports.polygon = polygon;
13835 exports.Rectangle = Rectangle;
13836 exports.rectangle = rectangle;
13838 exports.map = createMap;
13840 var oldL = window.L;
13841 exports.noConflict = function() {
13846 // Always export us to window global (see #2364)
13847 window.L = exports;
13850 //# sourceMappingURL=leaflet-src.js.map