2 * Leaflet 1.5.1+build.2e3e0ff, 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.5.1+build.2e3e0ffb";
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 digits = (digits === undefined ? 6 : digits);
131 return +(Math.round(num + ('e+' + digits)) + ('e-' + digits));
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. This includes implicitly attached events.
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 earthRadius = 6378137;
1653 var SphericalMercator = {
1656 MAX_LATITUDE: 85.0511287798,
1658 project: function (latlng) {
1659 var d = Math.PI / 180,
1660 max = this.MAX_LATITUDE,
1661 lat = Math.max(Math.min(max, latlng.lat), -max),
1662 sin = Math.sin(lat * d);
1665 this.R * latlng.lng * d,
1666 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
1669 unproject: function (point) {
1670 var d = 180 / Math.PI;
1673 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
1674 point.x * d / this.R);
1677 bounds: (function () {
1678 var d = earthRadius * Math.PI;
1679 return new Bounds([-d, -d], [d, d]);
1684 * @class Transformation
1685 * @aka L.Transformation
1687 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1688 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1689 * the reverse. Used by Leaflet in its projections code.
1694 * var transformation = L.transformation(2, 5, -1, 10),
1695 * p = L.point(1, 2),
1696 * p2 = transformation.transform(p), // L.point(7, 8)
1697 * p3 = transformation.untransform(p2); // L.point(1, 2)
1702 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1703 // Creates a `Transformation` object with the given coefficients.
1704 function Transformation(a, b, c, d) {
1706 // use array properties
1719 Transformation.prototype = {
1720 // @method transform(point: Point, scale?: Number): Point
1721 // Returns a transformed point, optionally multiplied by the given scale.
1722 // Only accepts actual `L.Point` instances, not arrays.
1723 transform: function (point, scale) { // (Point, Number) -> Point
1724 return this._transform(point.clone(), scale);
1727 // destructive transform (faster)
1728 _transform: function (point, scale) {
1730 point.x = scale * (this._a * point.x + this._b);
1731 point.y = scale * (this._c * point.y + this._d);
1735 // @method untransform(point: Point, scale?: Number): Point
1736 // Returns the reverse transformation of the given point, optionally divided
1737 // by the given scale. Only accepts actual `L.Point` instances, not arrays.
1738 untransform: function (point, scale) {
1741 (point.x / scale - this._b) / this._a,
1742 (point.y / scale - this._d) / this._c);
1746 // factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1748 // @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1749 // Instantiates a Transformation object with the given coefficients.
1752 // @factory L.transformation(coefficients: Array): Transformation
1753 // Expects an coefficients array of the form
1754 // `[a: Number, b: Number, c: Number, d: Number]`.
1756 function toTransformation(a, b, c, d) {
1757 return new Transformation(a, b, c, d);
1762 * @crs L.CRS.EPSG3857
1764 * The most common CRS for online maps, used by almost all free and commercial
1765 * tile providers. Uses Spherical Mercator projection. Set in by default in
1766 * Map's `crs` option.
1769 var EPSG3857 = extend({}, Earth, {
1771 projection: SphericalMercator,
1773 transformation: (function () {
1774 var scale = 0.5 / (Math.PI * SphericalMercator.R);
1775 return toTransformation(scale, 0.5, -scale, 0.5);
1779 var EPSG900913 = extend({}, EPSG3857, {
1783 // @namespace SVG; @section
1784 // There are several static functions which can be called without instantiating L.SVG:
1786 // @function create(name: String): SVGElement
1787 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
1788 // corresponding to the class name passed. For example, using 'line' will return
1789 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
1790 function svgCreate(name) {
1791 return document.createElementNS('http://www.w3.org/2000/svg', name);
1794 // @function pointsToPath(rings: Point[], closed: Boolean): String
1795 // Generates a SVG path string for multiple rings, with each ring turning
1796 // into "M..L..L.." instructions
1797 function pointsToPath(rings, closed) {
1799 i, j, len, len2, points, p;
1801 for (i = 0, len = rings.length; i < len; i++) {
1804 for (j = 0, len2 = points.length; j < len2; j++) {
1806 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
1809 // closes the ring for polygons; "x" is VML syntax
1810 str += closed ? (svg ? 'z' : 'x') : '';
1813 // SVG complains about empty path strings
1814 return str || 'M0 0';
1818 * @namespace Browser
1821 * A namespace with static properties for browser/feature detection used by Leaflet internally.
1826 * if (L.Browser.ielt9) {
1827 * alert('Upgrade your browser, dude!');
1832 var style$1 = document.documentElement.style;
1834 // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
1835 var ie = 'ActiveXObject' in window;
1837 // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
1838 var ielt9 = ie && !document.addEventListener;
1840 // @property edge: Boolean; `true` for the Edge web browser.
1841 var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
1843 // @property webkit: Boolean;
1844 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
1845 var webkit = userAgentContains('webkit');
1847 // @property android: Boolean
1848 // `true` for any browser running on an Android platform.
1849 var android = userAgentContains('android');
1851 // @property android23: Boolean; `true` for browsers running on Android 2 or Android 3.
1852 var android23 = userAgentContains('android 2') || userAgentContains('android 3');
1854 /* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */
1855 var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit
1856 // @property androidStock: Boolean; `true` for the Android stock browser (i.e. not Chrome)
1857 var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);
1859 // @property opera: Boolean; `true` for the Opera browser
1860 var opera = !!window.opera;
1862 // @property chrome: Boolean; `true` for the Chrome browser.
1863 var chrome = userAgentContains('chrome');
1865 // @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
1866 var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
1868 // @property safari: Boolean; `true` for the Safari browser.
1869 var safari = !chrome && userAgentContains('safari');
1871 var phantom = userAgentContains('phantom');
1873 // @property opera12: Boolean
1874 // `true` for the Opera browser supporting CSS transforms (version 12 or later).
1875 var opera12 = 'OTransition' in style$1;
1877 // @property win: Boolean; `true` when the browser is running in a Windows platform
1878 var win = navigator.platform.indexOf('Win') === 0;
1880 // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
1881 var ie3d = ie && ('transition' in style$1);
1883 // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
1884 var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;
1886 // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
1887 var gecko3d = 'MozPerspective' in style$1;
1889 // @property any3d: Boolean
1890 // `true` for all browsers supporting CSS transforms.
1891 var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
1893 // @property mobile: Boolean; `true` for all browsers running in a mobile device.
1894 var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
1896 // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
1897 var mobileWebkit = mobile && webkit;
1899 // @property mobileWebkit3d: Boolean
1900 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
1901 var mobileWebkit3d = mobile && webkit3d;
1903 // @property msPointer: Boolean
1904 // `true` for browsers implementing the Microsoft touch events model (notably IE10).
1905 var msPointer = !window.PointerEvent && window.MSPointerEvent;
1907 // @property pointer: Boolean
1908 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
1909 var pointer = !!(window.PointerEvent || msPointer);
1911 // @property touch: Boolean
1912 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
1913 // This does not necessarily mean that the browser is running in a computer with
1914 // a touchscreen, it only means that the browser is capable of understanding
1916 var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
1917 (window.DocumentTouch && document instanceof window.DocumentTouch));
1919 // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
1920 var mobileOpera = mobile && opera;
1922 // @property mobileGecko: Boolean
1923 // `true` for gecko-based browsers running in a mobile device.
1924 var mobileGecko = mobile && gecko;
1926 // @property retina: Boolean
1927 // `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%.
1928 var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
1931 // @property canvas: Boolean
1932 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
1933 var canvas = (function () {
1934 return !!document.createElement('canvas').getContext;
1937 // @property svg: Boolean
1938 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
1939 var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect);
1941 // @property vml: Boolean
1942 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
1943 var vml = !svg && (function () {
1945 var div = document.createElement('div');
1946 div.innerHTML = '<v:shape adj="1"/>';
1948 var shape = div.firstChild;
1949 shape.style.behavior = 'url(#default#VML)';
1951 return shape && (typeof shape.adj === 'object');
1959 function userAgentContains(str) {
1960 return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
1964 var Browser = (Object.freeze || Object)({
1970 android23: android23,
1971 androidStock: androidStock,
1984 mobileWebkit: mobileWebkit,
1985 mobileWebkit3d: mobileWebkit3d,
1986 msPointer: msPointer,
1989 mobileOpera: mobileOpera,
1990 mobileGecko: mobileGecko,
1998 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
2002 var POINTER_DOWN = msPointer ? 'MSPointerDown' : 'pointerdown';
2003 var POINTER_MOVE = msPointer ? 'MSPointerMove' : 'pointermove';
2004 var POINTER_UP = msPointer ? 'MSPointerUp' : 'pointerup';
2005 var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel';
2006 var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION'];
2009 var _pointerDocListener = false;
2011 // DomEvent.DoubleTap needs to know about this
2012 var _pointersCount = 0;
2014 // Provides a touch events wrapper for (ms)pointer events.
2015 // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
2017 function addPointerListener(obj, type, handler, id) {
2018 if (type === 'touchstart') {
2019 _addPointerStart(obj, handler, id);
2021 } else if (type === 'touchmove') {
2022 _addPointerMove(obj, handler, id);
2024 } else if (type === 'touchend') {
2025 _addPointerEnd(obj, handler, id);
2031 function removePointerListener(obj, type, id) {
2032 var handler = obj['_leaflet_' + type + id];
2034 if (type === 'touchstart') {
2035 obj.removeEventListener(POINTER_DOWN, handler, false);
2037 } else if (type === 'touchmove') {
2038 obj.removeEventListener(POINTER_MOVE, handler, false);
2040 } else if (type === 'touchend') {
2041 obj.removeEventListener(POINTER_UP, handler, false);
2042 obj.removeEventListener(POINTER_CANCEL, handler, false);
2048 function _addPointerStart(obj, handler, id) {
2049 var onDown = bind(function (e) {
2050 if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
2051 // In IE11, some touch events needs to fire for form controls, or
2052 // the controls will stop working. We keep a whitelist of tag names that
2053 // need these events. For other target tags, we prevent default on the event.
2054 if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
2061 _handlePointer(e, handler);
2064 obj['_leaflet_touchstart' + id] = onDown;
2065 obj.addEventListener(POINTER_DOWN, onDown, false);
2067 // need to keep track of what pointers and how many are active to provide e.touches emulation
2068 if (!_pointerDocListener) {
2069 // we listen documentElement as any drags that end by moving the touch off the screen get fired there
2070 document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true);
2071 document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true);
2072 document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true);
2073 document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
2075 _pointerDocListener = true;
2079 function _globalPointerDown(e) {
2080 _pointers[e.pointerId] = e;
2084 function _globalPointerMove(e) {
2085 if (_pointers[e.pointerId]) {
2086 _pointers[e.pointerId] = e;
2090 function _globalPointerUp(e) {
2091 delete _pointers[e.pointerId];
2095 function _handlePointer(e, handler) {
2097 for (var i in _pointers) {
2098 e.touches.push(_pointers[i]);
2100 e.changedTouches = [e];
2105 function _addPointerMove(obj, handler, id) {
2106 var onMove = function (e) {
2107 // don't fire touch moves when mouse isn't down
2108 if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
2110 _handlePointer(e, handler);
2113 obj['_leaflet_touchmove' + id] = onMove;
2114 obj.addEventListener(POINTER_MOVE, onMove, false);
2117 function _addPointerEnd(obj, handler, id) {
2118 var onUp = function (e) {
2119 _handlePointer(e, handler);
2122 obj['_leaflet_touchend' + id] = onUp;
2123 obj.addEventListener(POINTER_UP, onUp, false);
2124 obj.addEventListener(POINTER_CANCEL, onUp, false);
2128 * Extends the event handling code with double tap support for mobile browsers.
2131 var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart';
2132 var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend';
2133 var _pre = '_leaflet_';
2135 // inspired by Zepto touch code by Thomas Fuchs
2136 function addDoubleTapListener(obj, handler, id) {
2141 function onTouchStart(e) {
2145 if ((!edge) || e.pointerType === 'mouse') { return; }
2146 count = _pointersCount;
2148 count = e.touches.length;
2151 if (count > 1) { return; }
2153 var now = Date.now(),
2154 delta = now - (last || now);
2156 touch$$1 = e.touches ? e.touches[0] : e;
2157 doubleTap = (delta > 0 && delta <= delay);
2161 function onTouchEnd(e) {
2162 if (doubleTap && !touch$$1.cancelBubble) {
2164 if ((!edge) || e.pointerType === 'mouse') { return; }
2165 // work around .type being readonly with MSPointer* events
2169 for (i in touch$$1) {
2171 newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop;
2173 touch$$1 = newTouch;
2175 touch$$1.type = 'dblclick';
2176 touch$$1.button = 0;
2182 obj[_pre + _touchstart + id] = onTouchStart;
2183 obj[_pre + _touchend + id] = onTouchEnd;
2184 obj[_pre + 'dblclick' + id] = handler;
2186 obj.addEventListener(_touchstart, onTouchStart, false);
2187 obj.addEventListener(_touchend, onTouchEnd, false);
2189 // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
2190 // the browser doesn't fire touchend/pointerup events but does fire
2191 // native dblclicks. See #4127.
2192 // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
2193 obj.addEventListener('dblclick', handler, false);
2198 function removeDoubleTapListener(obj, id) {
2199 var touchstart = obj[_pre + _touchstart + id],
2200 touchend = obj[_pre + _touchend + id],
2201 dblclick = obj[_pre + 'dblclick' + id];
2203 obj.removeEventListener(_touchstart, touchstart, false);
2204 obj.removeEventListener(_touchend, touchend, false);
2206 obj.removeEventListener('dblclick', dblclick, false);
2213 * @namespace DomUtil
2215 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
2216 * tree, used by Leaflet internally.
2218 * Most functions expecting or returning a `HTMLElement` also work for
2219 * SVG elements. The only difference is that classes refer to CSS classes
2220 * in HTML and SVG classes in SVG.
2224 // @property TRANSFORM: String
2225 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
2226 var TRANSFORM = testProp(
2227 ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
2229 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
2230 // the same for the transitionend event, in particular the Android 4.1 stock browser
2232 // @property TRANSITION: String
2233 // Vendor-prefixed transition style name.
2234 var TRANSITION = testProp(
2235 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
2237 // @property TRANSITION_END: String
2238 // Vendor-prefixed transitionend event name.
2239 var TRANSITION_END =
2240 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
2243 // @function get(id: String|HTMLElement): HTMLElement
2244 // Returns an element given its DOM id, or returns the element itself
2245 // if it was passed directly.
2247 return typeof id === 'string' ? document.getElementById(id) : id;
2250 // @function getStyle(el: HTMLElement, styleAttrib: String): String
2251 // Returns the value for a certain style attribute on an element,
2252 // including computed values or values set through CSS.
2253 function getStyle(el, style) {
2254 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
2256 if ((!value || value === 'auto') && document.defaultView) {
2257 var css = document.defaultView.getComputedStyle(el, null);
2258 value = css ? css[style] : null;
2260 return value === 'auto' ? null : value;
2263 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
2264 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
2265 function create$1(tagName, className, container) {
2266 var el = document.createElement(tagName);
2267 el.className = className || '';
2270 container.appendChild(el);
2275 // @function remove(el: HTMLElement)
2276 // Removes `el` from its parent element
2277 function remove(el) {
2278 var parent = el.parentNode;
2280 parent.removeChild(el);
2284 // @function empty(el: HTMLElement)
2285 // Removes all of `el`'s children elements from `el`
2286 function empty(el) {
2287 while (el.firstChild) {
2288 el.removeChild(el.firstChild);
2292 // @function toFront(el: HTMLElement)
2293 // Makes `el` the last child of its parent, so it renders in front of the other children.
2294 function toFront(el) {
2295 var parent = el.parentNode;
2296 if (parent && parent.lastChild !== el) {
2297 parent.appendChild(el);
2301 // @function toBack(el: HTMLElement)
2302 // Makes `el` the first child of its parent, so it renders behind the other children.
2303 function toBack(el) {
2304 var parent = el.parentNode;
2305 if (parent && parent.firstChild !== el) {
2306 parent.insertBefore(el, parent.firstChild);
2310 // @function hasClass(el: HTMLElement, name: String): Boolean
2311 // Returns `true` if the element's class attribute contains `name`.
2312 function hasClass(el, name) {
2313 if (el.classList !== undefined) {
2314 return el.classList.contains(name);
2316 var className = getClass(el);
2317 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
2320 // @function addClass(el: HTMLElement, name: String)
2321 // Adds `name` to the element's class attribute.
2322 function addClass(el, name) {
2323 if (el.classList !== undefined) {
2324 var classes = splitWords(name);
2325 for (var i = 0, len = classes.length; i < len; i++) {
2326 el.classList.add(classes[i]);
2328 } else if (!hasClass(el, name)) {
2329 var className = getClass(el);
2330 setClass(el, (className ? className + ' ' : '') + name);
2334 // @function removeClass(el: HTMLElement, name: String)
2335 // Removes `name` from the element's class attribute.
2336 function removeClass(el, name) {
2337 if (el.classList !== undefined) {
2338 el.classList.remove(name);
2340 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
2344 // @function setClass(el: HTMLElement, name: String)
2345 // Sets the element's class.
2346 function setClass(el, name) {
2347 if (el.className.baseVal === undefined) {
2348 el.className = name;
2350 // in case of SVG element
2351 el.className.baseVal = name;
2355 // @function getClass(el: HTMLElement): String
2356 // Returns the element's class.
2357 function getClass(el) {
2358 // Check if the element is an SVGElementInstance and use the correspondingElement instead
2359 // (Required for linked SVG elements in IE11.)
2360 if (el.correspondingElement) {
2361 el = el.correspondingElement;
2363 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
2366 // @function setOpacity(el: HTMLElement, opacity: Number)
2367 // Set the opacity of an element (including old IE support).
2368 // `opacity` must be a number from `0` to `1`.
2369 function setOpacity(el, value) {
2370 if ('opacity' in el.style) {
2371 el.style.opacity = value;
2372 } else if ('filter' in el.style) {
2373 _setOpacityIE(el, value);
2377 function _setOpacityIE(el, value) {
2379 filterName = 'DXImageTransform.Microsoft.Alpha';
2381 // filters collection throws an error if we try to retrieve a filter that doesn't exist
2383 filter = el.filters.item(filterName);
2385 // don't set opacity to 1 if we haven't already set an opacity,
2386 // it isn't needed and breaks transparent pngs.
2387 if (value === 1) { return; }
2390 value = Math.round(value * 100);
2393 filter.Enabled = (value !== 100);
2394 filter.Opacity = value;
2396 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
2400 // @function testProp(props: String[]): String|false
2401 // Goes through the array of style names and returns the first name
2402 // that is a valid style name for an element. If no such name is found,
2403 // it returns false. Useful for vendor-prefixed styles like `transform`.
2404 function testProp(props) {
2405 var style = document.documentElement.style;
2407 for (var i = 0; i < props.length; i++) {
2408 if (props[i] in style) {
2415 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
2416 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
2417 // and optionally scaled by `scale`. Does not have an effect if the
2418 // browser doesn't support 3D CSS transforms.
2419 function setTransform(el, offset, scale) {
2420 var pos = offset || new Point(0, 0);
2422 el.style[TRANSFORM] =
2424 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
2425 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
2426 (scale ? ' scale(' + scale + ')' : '');
2429 // @function setPosition(el: HTMLElement, position: Point)
2430 // Sets the position of `el` to coordinates specified by `position`,
2431 // using CSS translate or top/left positioning depending on the browser
2432 // (used by Leaflet internally to position its layers).
2433 function setPosition(el, point) {
2436 el._leaflet_pos = point;
2440 setTransform(el, point);
2442 el.style.left = point.x + 'px';
2443 el.style.top = point.y + 'px';
2447 // @function getPosition(el: HTMLElement): Point
2448 // Returns the coordinates of an element previously positioned with setPosition.
2449 function getPosition(el) {
2450 // this method is only used for elements previously positioned using setPosition,
2451 // so it's safe to cache the position for performance
2453 return el._leaflet_pos || new Point(0, 0);
2456 // @function disableTextSelection()
2457 // Prevents the user from generating `selectstart` DOM events, usually generated
2458 // when the user drags the mouse through a page with text. Used internally
2459 // by Leaflet to override the behaviour of any click-and-drag interaction on
2460 // the map. Affects drag interactions on the whole document.
2462 // @function enableTextSelection()
2463 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
2464 var disableTextSelection;
2465 var enableTextSelection;
2467 if ('onselectstart' in document) {
2468 disableTextSelection = function () {
2469 on(window, 'selectstart', preventDefault);
2471 enableTextSelection = function () {
2472 off(window, 'selectstart', preventDefault);
2475 var userSelectProperty = testProp(
2476 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
2478 disableTextSelection = function () {
2479 if (userSelectProperty) {
2480 var style = document.documentElement.style;
2481 _userSelect = style[userSelectProperty];
2482 style[userSelectProperty] = 'none';
2485 enableTextSelection = function () {
2486 if (userSelectProperty) {
2487 document.documentElement.style[userSelectProperty] = _userSelect;
2488 _userSelect = undefined;
2493 // @function disableImageDrag()
2494 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
2495 // for `dragstart` DOM events, usually generated when the user drags an image.
2496 function disableImageDrag() {
2497 on(window, 'dragstart', preventDefault);
2500 // @function enableImageDrag()
2501 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
2502 function enableImageDrag() {
2503 off(window, 'dragstart', preventDefault);
2506 var _outlineElement;
2508 // @function preventOutline(el: HTMLElement)
2509 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
2510 // of the element `el` invisible. Used internally by Leaflet to prevent
2511 // focusable elements from displaying an outline when the user performs a
2512 // drag interaction on them.
2513 function preventOutline(element) {
2514 while (element.tabIndex === -1) {
2515 element = element.parentNode;
2517 if (!element.style) { return; }
2519 _outlineElement = element;
2520 _outlineStyle = element.style.outline;
2521 element.style.outline = 'none';
2522 on(window, 'keydown', restoreOutline);
2525 // @function restoreOutline()
2526 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
2527 function restoreOutline() {
2528 if (!_outlineElement) { return; }
2529 _outlineElement.style.outline = _outlineStyle;
2530 _outlineElement = undefined;
2531 _outlineStyle = undefined;
2532 off(window, 'keydown', restoreOutline);
2535 // @function getSizedParentNode(el: HTMLElement): HTMLElement
2536 // Finds the closest parent node which size (width and height) is not null.
2537 function getSizedParentNode(element) {
2539 element = element.parentNode;
2540 } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
2544 // @function getScale(el: HTMLElement): Object
2545 // Computes the CSS scale currently applied on the element.
2546 // Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
2547 // and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
2548 function getScale(element) {
2549 var rect = element.getBoundingClientRect(); // Read-only in old browsers.
2552 x: rect.width / element.offsetWidth || 1,
2553 y: rect.height / element.offsetHeight || 1,
2554 boundingClientRect: rect
2559 var DomUtil = (Object.freeze || Object)({
2560 TRANSFORM: TRANSFORM,
2561 TRANSITION: TRANSITION,
2562 TRANSITION_END: TRANSITION_END,
2572 removeClass: removeClass,
2575 setOpacity: setOpacity,
2577 setTransform: setTransform,
2578 setPosition: setPosition,
2579 getPosition: getPosition,
2580 disableTextSelection: disableTextSelection,
2581 enableTextSelection: enableTextSelection,
2582 disableImageDrag: disableImageDrag,
2583 enableImageDrag: enableImageDrag,
2584 preventOutline: preventOutline,
2585 restoreOutline: restoreOutline,
2586 getSizedParentNode: getSizedParentNode,
2591 * @namespace DomEvent
2592 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
2595 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
2597 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
2598 // Adds a listener function (`fn`) to a particular DOM event type of the
2599 // element `el`. You can optionally specify the context of the listener
2600 // (object the `this` keyword will point to). You can also pass several
2601 // space-separated types (e.g. `'click dblclick'`).
2604 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
2605 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2606 function on(obj, types, fn, context) {
2608 if (typeof types === 'object') {
2609 for (var type in types) {
2610 addOne(obj, type, types[type], fn);
2613 types = splitWords(types);
2615 for (var i = 0, len = types.length; i < len; i++) {
2616 addOne(obj, types[i], fn, context);
2623 var eventsKey = '_leaflet_events';
2625 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
2626 // Removes a previously added listener function.
2627 // Note that if you passed a custom context to on, you must pass the same
2628 // context to `off` in order to remove the listener.
2631 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
2632 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2633 function off(obj, types, fn, context) {
2635 if (typeof types === 'object') {
2636 for (var type in types) {
2637 removeOne(obj, type, types[type], fn);
2640 types = splitWords(types);
2642 for (var i = 0, len = types.length; i < len; i++) {
2643 removeOne(obj, types[i], fn, context);
2646 for (var j in obj[eventsKey]) {
2647 removeOne(obj, j, obj[eventsKey][j]);
2649 delete obj[eventsKey];
2655 function addOne(obj, type, fn, context) {
2656 var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
2658 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
2660 var handler = function (e) {
2661 return fn.call(context || obj, e || window.event);
2664 var originalHandler = handler;
2666 if (pointer && type.indexOf('touch') === 0) {
2667 // Needs DomEvent.Pointer.js
2668 addPointerListener(obj, type, handler, id);
2670 } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
2671 !(pointer && chrome)) {
2672 // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
2674 addDoubleTapListener(obj, handler, id);
2676 } else if ('addEventListener' in obj) {
2678 if (type === 'mousewheel') {
2679 obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2681 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
2682 handler = function (e) {
2683 e = e || window.event;
2684 if (isExternalTarget(obj, e)) {
2688 obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
2691 if (type === 'click' && android) {
2692 handler = function (e) {
2693 filterClick(e, originalHandler);
2696 obj.addEventListener(type, handler, false);
2699 } else if ('attachEvent' in obj) {
2700 obj.attachEvent('on' + type, handler);
2703 obj[eventsKey] = obj[eventsKey] || {};
2704 obj[eventsKey][id] = handler;
2707 function removeOne(obj, type, fn, context) {
2709 var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
2710 handler = obj[eventsKey] && obj[eventsKey][id];
2712 if (!handler) { return this; }
2714 if (pointer && type.indexOf('touch') === 0) {
2715 removePointerListener(obj, type, id);
2717 } else if (touch && (type === 'dblclick') && removeDoubleTapListener &&
2718 !(pointer && chrome)) {
2719 removeDoubleTapListener(obj, id);
2721 } else if ('removeEventListener' in obj) {
2723 if (type === 'mousewheel') {
2724 obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2727 obj.removeEventListener(
2728 type === 'mouseenter' ? 'mouseover' :
2729 type === 'mouseleave' ? 'mouseout' : type, handler, false);
2732 } else if ('detachEvent' in obj) {
2733 obj.detachEvent('on' + type, handler);
2736 obj[eventsKey][id] = null;
2739 // @function stopPropagation(ev: DOMEvent): this
2740 // Stop the given event from propagation to parent elements. Used inside the listener functions:
2742 // L.DomEvent.on(div, 'click', function (ev) {
2743 // L.DomEvent.stopPropagation(ev);
2746 function stopPropagation(e) {
2748 if (e.stopPropagation) {
2749 e.stopPropagation();
2750 } else if (e.originalEvent) { // In case of Leaflet event.
2751 e.originalEvent._stopped = true;
2753 e.cancelBubble = true;
2760 // @function disableScrollPropagation(el: HTMLElement): this
2761 // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
2762 function disableScrollPropagation(el) {
2763 addOne(el, 'mousewheel', stopPropagation);
2767 // @function disableClickPropagation(el: HTMLElement): this
2768 // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
2769 // `'mousedown'` and `'touchstart'` events (plus browser variants).
2770 function disableClickPropagation(el) {
2771 on(el, 'mousedown touchstart dblclick', stopPropagation);
2772 addOne(el, 'click', fakeStop);
2776 // @function preventDefault(ev: DOMEvent): this
2777 // Prevents the default action of the DOM Event `ev` from happening (such as
2778 // following a link in the href of the a element, or doing a POST request
2779 // with page reload when a `<form>` is submitted).
2780 // Use it inside listener functions.
2781 function preventDefault(e) {
2782 if (e.preventDefault) {
2785 e.returnValue = false;
2790 // @function stop(ev: DOMEvent): this
2791 // Does `stopPropagation` and `preventDefault` at the same time.
2798 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
2799 // Gets normalized mouse position from a DOM event relative to the
2800 // `container` (border excluded) or to the whole page if not specified.
2801 function getMousePosition(e, container) {
2803 return new Point(e.clientX, e.clientY);
2806 var scale = getScale(container),
2807 offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y)
2810 // offset.left/top values are in page scale (like clientX/Y),
2811 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
2812 (e.clientX - offset.left) / scale.x - container.clientLeft,
2813 (e.clientY - offset.top) / scale.y - container.clientTop
2817 // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
2818 // and Firefox scrolls device pixels, not CSS pixels
2820 (win && chrome) ? 2 * window.devicePixelRatio :
2821 gecko ? window.devicePixelRatio : 1;
2823 // @function getWheelDelta(ev: DOMEvent): Number
2824 // Gets normalized wheel delta from a mousewheel DOM event, in vertical
2825 // pixels scrolled (negative if scrolling down).
2826 // Events from pointing devices without precise scrolling are mapped to
2827 // a best guess of 60 pixels.
2828 function getWheelDelta(e) {
2829 return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
2830 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
2831 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
2832 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
2833 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
2834 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
2835 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
2836 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
2840 var skipEvents = {};
2842 function fakeStop(e) {
2843 // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
2844 skipEvents[e.type] = true;
2847 function skipped(e) {
2848 var events = skipEvents[e.type];
2849 // reset when checking, as it's only used in map container and propagates outside of the map
2850 skipEvents[e.type] = false;
2854 // check if element really left/entered the event target (for mouseenter/mouseleave)
2855 function isExternalTarget(el, e) {
2857 var related = e.relatedTarget;
2859 if (!related) { return true; }
2862 while (related && (related !== el)) {
2863 related = related.parentNode;
2868 return (related !== el);
2873 // this is a horrible workaround for a bug in Android where a single touch triggers two click events
2874 function filterClick(e, handler) {
2875 var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
2876 elapsed = lastClick && (timeStamp - lastClick);
2878 // are they closer together than 500ms yet more than 100ms?
2879 // Android typically triggers them ~300ms apart while multiple listeners
2880 // on the same event should be triggered far faster;
2881 // or check if click is simulated on the element, and if it is, reject any non-simulated events
2883 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
2887 lastClick = timeStamp;
2895 var DomEvent = (Object.freeze || Object)({
2898 stopPropagation: stopPropagation,
2899 disableScrollPropagation: disableScrollPropagation,
2900 disableClickPropagation: disableClickPropagation,
2901 preventDefault: preventDefault,
2903 getMousePosition: getMousePosition,
2904 getWheelDelta: getWheelDelta,
2907 isExternalTarget: isExternalTarget,
2913 * @class PosAnimation
2914 * @aka L.PosAnimation
2916 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
2920 * var fx = new L.PosAnimation();
2921 * fx.run(el, [300, 500], 0.5);
2924 * @constructor L.PosAnimation()
2925 * Creates a `PosAnimation` object.
2929 var PosAnimation = Evented.extend({
2931 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
2932 // Run an animation of a given element to a new position, optionally setting
2933 // duration in seconds (`0.25` by default) and easing linearity factor (3rd
2934 // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
2935 // `0.5` by default).
2936 run: function (el, newPos, duration, easeLinearity) {
2940 this._inProgress = true;
2941 this._duration = duration || 0.25;
2942 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
2944 this._startPos = getPosition(el);
2945 this._offset = newPos.subtract(this._startPos);
2946 this._startTime = +new Date();
2948 // @event start: Event
2949 // Fired when the animation starts
2956 // Stops the animation (if currently running).
2958 if (!this._inProgress) { return; }
2964 _animate: function () {
2966 this._animId = requestAnimFrame(this._animate, this);
2970 _step: function (round) {
2971 var elapsed = (+new Date()) - this._startTime,
2972 duration = this._duration * 1000;
2974 if (elapsed < duration) {
2975 this._runFrame(this._easeOut(elapsed / duration), round);
2982 _runFrame: function (progress, round) {
2983 var pos = this._startPos.add(this._offset.multiplyBy(progress));
2987 setPosition(this._el, pos);
2989 // @event step: Event
2990 // Fired continuously during the animation.
2994 _complete: function () {
2995 cancelAnimFrame(this._animId);
2997 this._inProgress = false;
2998 // @event end: Event
2999 // Fired when the animation ends.
3003 _easeOut: function (t) {
3004 return 1 - Math.pow(1 - t, this._easeOutPower);
3013 * The central class of the API — it is used to create a map on a page and manipulate it.
3018 * // initialize the map on the "map" div with a given center and zoom
3019 * var map = L.map('map', {
3020 * center: [51.505, -0.09],
3027 var Map = Evented.extend({
3030 // @section Map State Options
3031 // @option crs: CRS = L.CRS.EPSG3857
3032 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
3033 // sure what it means.
3036 // @option center: LatLng = undefined
3037 // Initial geographic center of the map
3040 // @option zoom: Number = undefined
3041 // Initial map zoom level
3044 // @option minZoom: Number = *
3045 // Minimum zoom level of the map.
3046 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3047 // the lowest of their `minZoom` options will be used instead.
3050 // @option maxZoom: Number = *
3051 // Maximum zoom level of the map.
3052 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3053 // the highest of their `maxZoom` options will be used instead.
3056 // @option layers: Layer[] = []
3057 // Array of layers that will be added to the map initially
3060 // @option maxBounds: LatLngBounds = null
3061 // When this option is set, the map restricts the view to the given
3062 // geographical bounds, bouncing the user back if the user tries to pan
3063 // outside the view. To set the restriction dynamically, use
3064 // [`setMaxBounds`](#map-setmaxbounds) method.
3065 maxBounds: undefined,
3067 // @option renderer: Renderer = *
3068 // The default method for drawing vector layers on the map. `L.SVG`
3069 // or `L.Canvas` by default depending on browser support.
3070 renderer: undefined,
3073 // @section Animation Options
3074 // @option zoomAnimation: Boolean = true
3075 // Whether the map zoom animation is enabled. By default it's enabled
3076 // in all browsers that support CSS3 Transitions except Android.
3077 zoomAnimation: true,
3079 // @option zoomAnimationThreshold: Number = 4
3080 // Won't animate zoom if the zoom difference exceeds this value.
3081 zoomAnimationThreshold: 4,
3083 // @option fadeAnimation: Boolean = true
3084 // Whether the tile fade animation is enabled. By default it's enabled
3085 // in all browsers that support CSS3 Transitions except Android.
3086 fadeAnimation: true,
3088 // @option markerZoomAnimation: Boolean = true
3089 // Whether markers animate their zoom with the zoom animation, if disabled
3090 // they will disappear for the length of the animation. By default it's
3091 // enabled in all browsers that support CSS3 Transitions except Android.
3092 markerZoomAnimation: true,
3094 // @option transform3DLimit: Number = 2^23
3095 // Defines the maximum size of a CSS translation transform. The default
3096 // value should not be changed unless a web browser positions layers in
3097 // the wrong place after doing a large `panBy`.
3098 transform3DLimit: 8388608, // Precision limit of a 32-bit float
3100 // @section Interaction Options
3101 // @option zoomSnap: Number = 1
3102 // Forces the map's zoom level to always be a multiple of this, particularly
3103 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
3104 // By default, the zoom level snaps to the nearest integer; lower values
3105 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
3106 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
3109 // @option zoomDelta: Number = 1
3110 // Controls how much the map's zoom level will change after a
3111 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
3112 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
3113 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
3116 // @option trackResize: Boolean = true
3117 // Whether the map automatically handles browser window resize to update itself.
3121 initialize: function (id, options) { // (HTMLElement or String, Object)
3122 options = setOptions(this, options);
3124 // Make sure to assign internal flags at the beginning,
3125 // to avoid inconsistent state in some edge cases.
3126 this._handlers = [];
3128 this._zoomBoundLayers = {};
3129 this._sizeChanged = true;
3131 this._initContainer(id);
3134 // hack for https://github.com/Leaflet/Leaflet/issues/1980
3135 this._onResize = bind(this._onResize, this);
3139 if (options.maxBounds) {
3140 this.setMaxBounds(options.maxBounds);
3143 if (options.zoom !== undefined) {
3144 this._zoom = this._limitZoom(options.zoom);
3147 if (options.center && options.zoom !== undefined) {
3148 this.setView(toLatLng(options.center), options.zoom, {reset: true});
3151 this.callInitHooks();
3153 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
3154 this._zoomAnimated = TRANSITION && any3d && !mobileOpera &&
3155 this.options.zoomAnimation;
3157 // zoom transitions run with the same duration for all layers, so if one of transitionend events
3158 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
3159 if (this._zoomAnimated) {
3160 this._createAnimProxy();
3161 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
3164 this._addLayers(this.options.layers);
3168 // @section Methods for modifying map state
3170 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
3171 // Sets the view of the map (geographical center and zoom) with the given
3172 // animation options.
3173 setView: function (center, zoom, options) {
3175 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
3176 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
3177 options = options || {};
3181 if (this._loaded && !options.reset && options !== true) {
3183 if (options.animate !== undefined) {
3184 options.zoom = extend({animate: options.animate}, options.zoom);
3185 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
3188 // try animating pan or zoom
3189 var moved = (this._zoom !== zoom) ?
3190 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
3191 this._tryAnimatedPan(center, options.pan);
3194 // prevent resize handler call, the view will refresh after animation anyway
3195 clearTimeout(this._sizeTimer);
3200 // animation didn't start, just reset the map view
3201 this._resetView(center, zoom);
3206 // @method setZoom(zoom: Number, options?: Zoom/pan options): this
3207 // Sets the zoom of the map.
3208 setZoom: function (zoom, options) {
3209 if (!this._loaded) {
3213 return this.setView(this.getCenter(), zoom, {zoom: options});
3216 // @method zoomIn(delta?: Number, options?: Zoom options): this
3217 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3218 zoomIn: function (delta, options) {
3219 delta = delta || (any3d ? this.options.zoomDelta : 1);
3220 return this.setZoom(this._zoom + delta, options);
3223 // @method zoomOut(delta?: Number, options?: Zoom options): this
3224 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3225 zoomOut: function (delta, options) {
3226 delta = delta || (any3d ? this.options.zoomDelta : 1);
3227 return this.setZoom(this._zoom - delta, options);
3230 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
3231 // Zooms the map while keeping a specified geographical point on the map
3232 // stationary (e.g. used internally for scroll zoom and double-click zoom).
3234 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
3235 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
3236 setZoomAround: function (latlng, zoom, options) {
3237 var scale = this.getZoomScale(zoom),
3238 viewHalf = this.getSize().divideBy(2),
3239 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
3241 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
3242 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
3244 return this.setView(newCenter, zoom, {zoom: options});
3247 _getBoundsCenterZoom: function (bounds, options) {
3249 options = options || {};
3250 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
3252 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3253 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3255 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
3257 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
3259 if (zoom === Infinity) {
3261 center: bounds.getCenter(),
3266 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
3268 swPoint = this.project(bounds.getSouthWest(), zoom),
3269 nePoint = this.project(bounds.getNorthEast(), zoom),
3270 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
3278 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
3279 // Sets a map view that contains the given geographical bounds with the
3280 // maximum zoom level possible.
3281 fitBounds: function (bounds, options) {
3283 bounds = toLatLngBounds(bounds);
3285 if (!bounds.isValid()) {
3286 throw new Error('Bounds are not valid.');
3289 var target = this._getBoundsCenterZoom(bounds, options);
3290 return this.setView(target.center, target.zoom, options);
3293 // @method fitWorld(options?: fitBounds options): this
3294 // Sets a map view that mostly contains the whole world with the maximum
3295 // zoom level possible.
3296 fitWorld: function (options) {
3297 return this.fitBounds([[-90, -180], [90, 180]], options);
3300 // @method panTo(latlng: LatLng, options?: Pan options): this
3301 // Pans the map to a given center.
3302 panTo: function (center, options) { // (LatLng)
3303 return this.setView(center, this._zoom, {pan: options});
3306 // @method panBy(offset: Point, options?: Pan options): this
3307 // Pans the map by a given number of pixels (animated).
3308 panBy: function (offset, options) {
3309 offset = toPoint(offset).round();
3310 options = options || {};
3312 if (!offset.x && !offset.y) {
3313 return this.fire('moveend');
3315 // If we pan too far, Chrome gets issues with tiles
3316 // and makes them disappear or appear in the wrong place (slightly offset) #2602
3317 if (options.animate !== true && !this.getSize().contains(offset)) {
3318 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
3322 if (!this._panAnim) {
3323 this._panAnim = new PosAnimation();
3326 'step': this._onPanTransitionStep,
3327 'end': this._onPanTransitionEnd
3331 // don't fire movestart if animating inertia
3332 if (!options.noMoveStart) {
3333 this.fire('movestart');
3336 // animate pan unless animate: false specified
3337 if (options.animate !== false) {
3338 addClass(this._mapPane, 'leaflet-pan-anim');
3340 var newPos = this._getMapPanePos().subtract(offset).round();
3341 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
3343 this._rawPanBy(offset);
3344 this.fire('move').fire('moveend');
3350 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
3351 // Sets the view of the map (geographical center and zoom) performing a smooth
3352 // pan-zoom animation.
3353 flyTo: function (targetCenter, targetZoom, options) {
3355 options = options || {};
3356 if (options.animate === false || !any3d) {
3357 return this.setView(targetCenter, targetZoom, options);
3362 var from = this.project(this.getCenter()),
3363 to = this.project(targetCenter),
3364 size = this.getSize(),
3365 startZoom = this._zoom;
3367 targetCenter = toLatLng(targetCenter);
3368 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
3370 var w0 = Math.max(size.x, size.y),
3371 w1 = w0 * this.getZoomScale(startZoom, targetZoom),
3372 u1 = (to.distanceTo(from)) || 1,
3377 var s1 = i ? -1 : 1,
3379 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
3380 b1 = 2 * s2 * rho2 * u1,
3382 sq = Math.sqrt(b * b + 1) - b;
3384 // workaround for floating point precision bug when sq = 0, log = -Infinite,
3385 // thus triggering an infinite loop in flyTo
3386 var log = sq < 0.000000001 ? -18 : Math.log(sq);
3391 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
3392 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
3393 function tanh(n) { return sinh(n) / cosh(n); }
3397 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
3398 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
3400 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
3402 var start = Date.now(),
3403 S = (r(1) - r0) / rho,
3404 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
3407 var t = (Date.now() - start) / duration,
3411 this._flyToFrame = requestAnimFrame(frame, this);
3414 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
3415 this.getScaleZoom(w0 / w(s), startZoom),
3420 ._move(targetCenter, targetZoom)
3425 this._moveStart(true, options.noMoveStart);
3431 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
3432 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
3433 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
3434 flyToBounds: function (bounds, options) {
3435 var target = this._getBoundsCenterZoom(bounds, options);
3436 return this.flyTo(target.center, target.zoom, options);
3439 // @method setMaxBounds(bounds: Bounds): this
3440 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
3441 setMaxBounds: function (bounds) {
3442 bounds = toLatLngBounds(bounds);
3444 if (!bounds.isValid()) {
3445 this.options.maxBounds = null;
3446 return this.off('moveend', this._panInsideMaxBounds);
3447 } else if (this.options.maxBounds) {
3448 this.off('moveend', this._panInsideMaxBounds);
3451 this.options.maxBounds = bounds;
3454 this._panInsideMaxBounds();
3457 return this.on('moveend', this._panInsideMaxBounds);
3460 // @method setMinZoom(zoom: Number): this
3461 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
3462 setMinZoom: function (zoom) {
3463 var oldZoom = this.options.minZoom;
3464 this.options.minZoom = zoom;
3466 if (this._loaded && oldZoom !== zoom) {
3467 this.fire('zoomlevelschange');
3469 if (this.getZoom() < this.options.minZoom) {
3470 return this.setZoom(zoom);
3477 // @method setMaxZoom(zoom: Number): this
3478 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
3479 setMaxZoom: function (zoom) {
3480 var oldZoom = this.options.maxZoom;
3481 this.options.maxZoom = zoom;
3483 if (this._loaded && oldZoom !== zoom) {
3484 this.fire('zoomlevelschange');
3486 if (this.getZoom() > this.options.maxZoom) {
3487 return this.setZoom(zoom);
3494 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
3495 // 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.
3496 panInsideBounds: function (bounds, options) {
3497 this._enforcingBounds = true;
3498 var center = this.getCenter(),
3499 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
3501 if (!center.equals(newCenter)) {
3502 this.panTo(newCenter, options);
3505 this._enforcingBounds = false;
3509 // @method panInside(latlng: LatLng, options?: options): this
3510 // Pans the map the minimum amount to make the `latlng` visible. Use
3511 // `padding`, `paddingTopLeft` and `paddingTopRight` options to fit
3512 // the display to more restricted bounds, like [`fitBounds`](#map-fitbounds).
3513 // If `latlng` is already within the (optionally padded) display bounds,
3514 // the map will not be panned.
3515 panInside: function (latlng, options) {
3516 options = options || {};
3518 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3519 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3520 center = this.getCenter(),
3521 pixelCenter = this.project(center),
3522 pixelPoint = this.project(latlng),
3523 pixelBounds = this.getPixelBounds(),
3524 halfPixelBounds = pixelBounds.getSize().divideBy(2),
3525 paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]);
3527 if (!paddedBounds.contains(pixelPoint)) {
3528 this._enforcingBounds = true;
3529 var diff = pixelCenter.subtract(pixelPoint),
3530 newCenter = toPoint(pixelPoint.x + diff.x, pixelPoint.y + diff.y);
3532 if (pixelPoint.x < paddedBounds.min.x || pixelPoint.x > paddedBounds.max.x) {
3533 newCenter.x = pixelCenter.x - diff.x;
3535 newCenter.x += halfPixelBounds.x - paddingTL.x;
3537 newCenter.x -= halfPixelBounds.x - paddingBR.x;
3540 if (pixelPoint.y < paddedBounds.min.y || pixelPoint.y > paddedBounds.max.y) {
3541 newCenter.y = pixelCenter.y - diff.y;
3543 newCenter.y += halfPixelBounds.y - paddingTL.y;
3545 newCenter.y -= halfPixelBounds.y - paddingBR.y;
3548 this.panTo(this.unproject(newCenter), options);
3549 this._enforcingBounds = false;
3554 // @method invalidateSize(options: Zoom/pan options): this
3555 // Checks if the map container size changed and updates the map if so —
3556 // call it after you've changed the map size dynamically, also animating
3557 // pan by default. If `options.pan` is `false`, panning will not occur.
3558 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
3559 // that it doesn't happen often even if the method is called many
3563 // @method invalidateSize(animate: Boolean): this
3564 // Checks if the map container size changed and updates the map if so —
3565 // call it after you've changed the map size dynamically, also animating
3567 invalidateSize: function (options) {
3568 if (!this._loaded) { return this; }
3573 }, options === true ? {animate: true} : options);
3575 var oldSize = this.getSize();
3576 this._sizeChanged = true;
3577 this._lastCenter = null;
3579 var newSize = this.getSize(),
3580 oldCenter = oldSize.divideBy(2).round(),
3581 newCenter = newSize.divideBy(2).round(),
3582 offset = oldCenter.subtract(newCenter);
3584 if (!offset.x && !offset.y) { return this; }
3586 if (options.animate && options.pan) {
3591 this._rawPanBy(offset);
3596 if (options.debounceMoveend) {
3597 clearTimeout(this._sizeTimer);
3598 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
3600 this.fire('moveend');
3604 // @section Map state change events
3605 // @event resize: ResizeEvent
3606 // Fired when the map is resized.
3607 return this.fire('resize', {
3613 // @section Methods for modifying map state
3614 // @method stop(): this
3615 // Stops the currently running `panTo` or `flyTo` animation, if any.
3617 this.setZoom(this._limitZoom(this._zoom));
3618 if (!this.options.zoomSnap) {
3619 this.fire('viewreset');
3621 return this._stop();
3624 // @section Geolocation methods
3625 // @method locate(options?: Locate options): this
3626 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
3627 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
3628 // and optionally sets the map view to the user's location with respect to
3629 // detection accuracy (or to the world view if geolocation failed).
3630 // Note that, if your page doesn't use HTTPS, this method will fail in
3631 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
3632 // See `Locate options` for more details.
3633 locate: function (options) {
3635 options = this._locateOptions = extend({
3639 // maxZoom: <Number>
3641 // enableHighAccuracy: false
3644 if (!('geolocation' in navigator)) {
3645 this._handleGeolocationError({
3647 message: 'Geolocation not supported.'
3652 var onResponse = bind(this._handleGeolocationResponse, this),
3653 onError = bind(this._handleGeolocationError, this);
3655 if (options.watch) {
3656 this._locationWatchId =
3657 navigator.geolocation.watchPosition(onResponse, onError, options);
3659 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
3664 // @method stopLocate(): this
3665 // Stops watching location previously initiated by `map.locate({watch: true})`
3666 // and aborts resetting the map view if map.locate was called with
3667 // `{setView: true}`.
3668 stopLocate: function () {
3669 if (navigator.geolocation && navigator.geolocation.clearWatch) {
3670 navigator.geolocation.clearWatch(this._locationWatchId);
3672 if (this._locateOptions) {
3673 this._locateOptions.setView = false;
3678 _handleGeolocationError: function (error) {
3680 message = error.message ||
3681 (c === 1 ? 'permission denied' :
3682 (c === 2 ? 'position unavailable' : 'timeout'));
3684 if (this._locateOptions.setView && !this._loaded) {
3688 // @section Location events
3689 // @event locationerror: ErrorEvent
3690 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
3691 this.fire('locationerror', {
3693 message: 'Geolocation error: ' + message + '.'
3697 _handleGeolocationResponse: function (pos) {
3698 var lat = pos.coords.latitude,
3699 lng = pos.coords.longitude,
3700 latlng = new LatLng(lat, lng),
3701 bounds = latlng.toBounds(pos.coords.accuracy * 2),
3702 options = this._locateOptions;
3704 if (options.setView) {
3705 var zoom = this.getBoundsZoom(bounds);
3706 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
3712 timestamp: pos.timestamp
3715 for (var i in pos.coords) {
3716 if (typeof pos.coords[i] === 'number') {
3717 data[i] = pos.coords[i];
3721 // @event locationfound: LocationEvent
3722 // Fired when geolocation (using the [`locate`](#map-locate) method)
3723 // went successfully.
3724 this.fire('locationfound', data);
3727 // TODO Appropriate docs section?
3728 // @section Other Methods
3729 // @method addHandler(name: String, HandlerClass: Function): this
3730 // Adds a new `Handler` to the map, given its name and constructor function.
3731 addHandler: function (name, HandlerClass) {
3732 if (!HandlerClass) { return this; }
3734 var handler = this[name] = new HandlerClass(this);
3736 this._handlers.push(handler);
3738 if (this.options[name]) {
3745 // @method remove(): this
3746 // Destroys the map and clears all related event listeners.
3747 remove: function () {
3749 this._initEvents(true);
3751 if (this._containerId !== this._container._leaflet_id) {
3752 throw new Error('Map container is being reused by another instance');
3756 // throws error in IE6-8
3757 delete this._container._leaflet_id;
3758 delete this._containerId;
3761 this._container._leaflet_id = undefined;
3763 this._containerId = undefined;
3766 if (this._locationWatchId !== undefined) {
3772 remove(this._mapPane);
3774 if (this._clearControlPos) {
3775 this._clearControlPos();
3777 if (this._resizeRequest) {
3778 cancelAnimFrame(this._resizeRequest);
3779 this._resizeRequest = null;
3782 this._clearHandlers();
3785 // @section Map state change events
3786 // @event unload: Event
3787 // Fired when the map is destroyed with [remove](#map-remove) method.
3788 this.fire('unload');
3792 for (i in this._layers) {
3793 this._layers[i].remove();
3795 for (i in this._panes) {
3796 remove(this._panes[i]);
3801 delete this._mapPane;
3802 delete this._renderer;
3807 // @section Other Methods
3808 // @method createPane(name: String, container?: HTMLElement): HTMLElement
3809 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
3810 // then returns it. The pane is created as a child of `container`, or
3811 // as a child of the main map pane if not set.
3812 createPane: function (name, container) {
3813 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3814 pane = create$1('div', className, container || this._mapPane);
3817 this._panes[name] = pane;
3822 // @section Methods for Getting Map State
3824 // @method getCenter(): LatLng
3825 // Returns the geographical center of the map view
3826 getCenter: function () {
3827 this._checkIfLoaded();
3829 if (this._lastCenter && !this._moved()) {
3830 return this._lastCenter;
3832 return this.layerPointToLatLng(this._getCenterLayerPoint());
3835 // @method getZoom(): Number
3836 // Returns the current zoom level of the map view
3837 getZoom: function () {
3841 // @method getBounds(): LatLngBounds
3842 // Returns the geographical bounds visible in the current map view
3843 getBounds: function () {
3844 var bounds = this.getPixelBounds(),
3845 sw = this.unproject(bounds.getBottomLeft()),
3846 ne = this.unproject(bounds.getTopRight());
3848 return new LatLngBounds(sw, ne);
3851 // @method getMinZoom(): Number
3852 // 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.
3853 getMinZoom: function () {
3854 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3857 // @method getMaxZoom(): Number
3858 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3859 getMaxZoom: function () {
3860 return this.options.maxZoom === undefined ?
3861 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3862 this.options.maxZoom;
3865 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
3866 // Returns the maximum zoom level on which the given bounds fit to the map
3867 // view in its entirety. If `inside` (optional) is set to `true`, the method
3868 // instead returns the minimum zoom level on which the map view fits into
3869 // the given bounds in its entirety.
3870 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3871 bounds = toLatLngBounds(bounds);
3872 padding = toPoint(padding || [0, 0]);
3874 var zoom = this.getZoom() || 0,
3875 min = this.getMinZoom(),
3876 max = this.getMaxZoom(),
3877 nw = bounds.getNorthWest(),
3878 se = bounds.getSouthEast(),
3879 size = this.getSize().subtract(padding),
3880 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3881 snap = any3d ? this.options.zoomSnap : 1,
3882 scalex = size.x / boundsSize.x,
3883 scaley = size.y / boundsSize.y,
3884 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
3886 zoom = this.getScaleZoom(scale, zoom);
3889 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3890 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3893 return Math.max(min, Math.min(max, zoom));
3896 // @method getSize(): Point
3897 // Returns the current size of the map container (in pixels).
3898 getSize: function () {
3899 if (!this._size || this._sizeChanged) {
3900 this._size = new Point(
3901 this._container.clientWidth || 0,
3902 this._container.clientHeight || 0);
3904 this._sizeChanged = false;
3906 return this._size.clone();
3909 // @method getPixelBounds(): Bounds
3910 // Returns the bounds of the current map view in projected pixel
3911 // coordinates (sometimes useful in layer and overlay implementations).
3912 getPixelBounds: function (center, zoom) {
3913 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3914 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3917 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3918 // the map pane? "left point of the map layer" can be confusing, specially
3919 // since there can be negative offsets.
3920 // @method getPixelOrigin(): Point
3921 // Returns the projected pixel coordinates of the top left point of
3922 // the map layer (useful in custom layer and overlay implementations).
3923 getPixelOrigin: function () {
3924 this._checkIfLoaded();
3925 return this._pixelOrigin;
3928 // @method getPixelWorldBounds(zoom?: Number): Bounds
3929 // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3930 // If `zoom` is omitted, the map's current zoom level is used.
3931 getPixelWorldBounds: function (zoom) {
3932 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3935 // @section Other Methods
3937 // @method getPane(pane: String|HTMLElement): HTMLElement
3938 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3939 getPane: function (pane) {
3940 return typeof pane === 'string' ? this._panes[pane] : pane;
3943 // @method getPanes(): Object
3944 // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3945 // the panes as values.
3946 getPanes: function () {
3950 // @method getContainer: HTMLElement
3951 // Returns the HTML element that contains the map.
3952 getContainer: function () {
3953 return this._container;
3957 // @section Conversion Methods
3959 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3960 // Returns the scale factor to be applied to a map transition from zoom level
3961 // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3962 getZoomScale: function (toZoom, fromZoom) {
3963 // TODO replace with universal implementation after refactoring projections
3964 var crs = this.options.crs;
3965 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3966 return crs.scale(toZoom) / crs.scale(fromZoom);
3969 // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3970 // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3971 // level and everything is scaled by a factor of `scale`. Inverse of
3972 // [`getZoomScale`](#map-getZoomScale).
3973 getScaleZoom: function (scale, fromZoom) {
3974 var crs = this.options.crs;
3975 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3976 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3977 return isNaN(zoom) ? Infinity : zoom;
3980 // @method project(latlng: LatLng, zoom: Number): Point
3981 // Projects a geographical coordinate `LatLng` according to the projection
3982 // of the map's CRS, then scales it according to `zoom` and the CRS's
3983 // `Transformation`. The result is pixel coordinate relative to
3985 project: function (latlng, zoom) {
3986 zoom = zoom === undefined ? this._zoom : zoom;
3987 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
3990 // @method unproject(point: Point, zoom: Number): LatLng
3991 // Inverse of [`project`](#map-project).
3992 unproject: function (point, zoom) {
3993 zoom = zoom === undefined ? this._zoom : zoom;
3994 return this.options.crs.pointToLatLng(toPoint(point), zoom);
3997 // @method layerPointToLatLng(point: Point): LatLng
3998 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3999 // returns the corresponding geographical coordinate (for the current zoom level).
4000 layerPointToLatLng: function (point) {
4001 var projectedPoint = toPoint(point).add(this.getPixelOrigin());
4002 return this.unproject(projectedPoint);
4005 // @method latLngToLayerPoint(latlng: LatLng): Point
4006 // Given a geographical coordinate, returns the corresponding pixel coordinate
4007 // relative to the [origin pixel](#map-getpixelorigin).
4008 latLngToLayerPoint: function (latlng) {
4009 var projectedPoint = this.project(toLatLng(latlng))._round();
4010 return projectedPoint._subtract(this.getPixelOrigin());
4013 // @method wrapLatLng(latlng: LatLng): LatLng
4014 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
4015 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
4017 // By default this means longitude is wrapped around the dateline so its
4018 // value is between -180 and +180 degrees.
4019 wrapLatLng: function (latlng) {
4020 return this.options.crs.wrapLatLng(toLatLng(latlng));
4023 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
4024 // Returns a `LatLngBounds` with the same size as the given one, ensuring that
4025 // its center is within the CRS's bounds.
4026 // By default this means the center longitude is wrapped around the dateline so its
4027 // value is between -180 and +180 degrees, and the majority of the bounds
4028 // overlaps the CRS's bounds.
4029 wrapLatLngBounds: function (latlng) {
4030 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
4033 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
4034 // Returns the distance between two geographical coordinates according to
4035 // the map's CRS. By default this measures distance in meters.
4036 distance: function (latlng1, latlng2) {
4037 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
4040 // @method containerPointToLayerPoint(point: Point): Point
4041 // Given a pixel coordinate relative to the map container, returns the corresponding
4042 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
4043 containerPointToLayerPoint: function (point) { // (Point)
4044 return toPoint(point).subtract(this._getMapPanePos());
4047 // @method layerPointToContainerPoint(point: Point): Point
4048 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
4049 // returns the corresponding pixel coordinate relative to the map container.
4050 layerPointToContainerPoint: function (point) { // (Point)
4051 return toPoint(point).add(this._getMapPanePos());
4054 // @method containerPointToLatLng(point: Point): LatLng
4055 // Given a pixel coordinate relative to the map container, returns
4056 // the corresponding geographical coordinate (for the current zoom level).
4057 containerPointToLatLng: function (point) {
4058 var layerPoint = this.containerPointToLayerPoint(toPoint(point));
4059 return this.layerPointToLatLng(layerPoint);
4062 // @method latLngToContainerPoint(latlng: LatLng): Point
4063 // Given a geographical coordinate, returns the corresponding pixel coordinate
4064 // relative to the map container.
4065 latLngToContainerPoint: function (latlng) {
4066 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
4069 // @method mouseEventToContainerPoint(ev: MouseEvent): Point
4070 // Given a MouseEvent object, returns the pixel coordinate relative to the
4071 // map container where the event took place.
4072 mouseEventToContainerPoint: function (e) {
4073 return getMousePosition(e, this._container);
4076 // @method mouseEventToLayerPoint(ev: MouseEvent): Point
4077 // Given a MouseEvent object, returns the pixel coordinate relative to
4078 // the [origin pixel](#map-getpixelorigin) where the event took place.
4079 mouseEventToLayerPoint: function (e) {
4080 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
4083 // @method mouseEventToLatLng(ev: MouseEvent): LatLng
4084 // Given a MouseEvent object, returns geographical coordinate where the
4085 // event took place.
4086 mouseEventToLatLng: function (e) { // (MouseEvent)
4087 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
4091 // map initialization methods
4093 _initContainer: function (id) {
4094 var container = this._container = get(id);
4097 throw new Error('Map container not found.');
4098 } else if (container._leaflet_id) {
4099 throw new Error('Map container is already initialized.');
4102 on(container, 'scroll', this._onScroll, this);
4103 this._containerId = stamp(container);
4106 _initLayout: function () {
4107 var container = this._container;
4109 this._fadeAnimated = this.options.fadeAnimation && any3d;
4111 addClass(container, 'leaflet-container' +
4112 (touch ? ' leaflet-touch' : '') +
4113 (retina ? ' leaflet-retina' : '') +
4114 (ielt9 ? ' leaflet-oldie' : '') +
4115 (safari ? ' leaflet-safari' : '') +
4116 (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
4118 var position = getStyle(container, 'position');
4120 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
4121 container.style.position = 'relative';
4126 if (this._initControlPos) {
4127 this._initControlPos();
4131 _initPanes: function () {
4132 var panes = this._panes = {};
4133 this._paneRenderers = {};
4137 // Panes are DOM elements used to control the ordering of layers on the map. You
4138 // can access panes with [`map.getPane`](#map-getpane) or
4139 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
4140 // [`map.createPane`](#map-createpane) method.
4142 // Every map has the following default panes that differ only in zIndex.
4144 // @pane mapPane: HTMLElement = 'auto'
4145 // Pane that contains all other map panes
4147 this._mapPane = this.createPane('mapPane', this._container);
4148 setPosition(this._mapPane, new Point(0, 0));
4150 // @pane tilePane: HTMLElement = 200
4151 // Pane for `GridLayer`s and `TileLayer`s
4152 this.createPane('tilePane');
4153 // @pane overlayPane: HTMLElement = 400
4154 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
4155 this.createPane('shadowPane');
4156 // @pane shadowPane: HTMLElement = 500
4157 // Pane for overlay shadows (e.g. `Marker` shadows)
4158 this.createPane('overlayPane');
4159 // @pane markerPane: HTMLElement = 600
4160 // Pane for `Icon`s of `Marker`s
4161 this.createPane('markerPane');
4162 // @pane tooltipPane: HTMLElement = 650
4163 // Pane for `Tooltip`s.
4164 this.createPane('tooltipPane');
4165 // @pane popupPane: HTMLElement = 700
4166 // Pane for `Popup`s.
4167 this.createPane('popupPane');
4169 if (!this.options.markerZoomAnimation) {
4170 addClass(panes.markerPane, 'leaflet-zoom-hide');
4171 addClass(panes.shadowPane, 'leaflet-zoom-hide');
4176 // private methods that modify map state
4178 // @section Map state change events
4179 _resetView: function (center, zoom) {
4180 setPosition(this._mapPane, new Point(0, 0));
4182 var loading = !this._loaded;
4183 this._loaded = true;
4184 zoom = this._limitZoom(zoom);
4186 this.fire('viewprereset');
4188 var zoomChanged = this._zoom !== zoom;
4190 ._moveStart(zoomChanged, false)
4191 ._move(center, zoom)
4192 ._moveEnd(zoomChanged);
4194 // @event viewreset: Event
4195 // Fired when the map needs to redraw its content (this usually happens
4196 // on map zoom or load). Very useful for creating custom overlays.
4197 this.fire('viewreset');
4199 // @event load: Event
4200 // Fired when the map is initialized (when its center and zoom are set
4201 // for the first time).
4207 _moveStart: function (zoomChanged, noMoveStart) {
4208 // @event zoomstart: Event
4209 // Fired when the map zoom is about to change (e.g. before zoom animation).
4210 // @event movestart: Event
4211 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
4213 this.fire('zoomstart');
4216 this.fire('movestart');
4221 _move: function (center, zoom, data) {
4222 if (zoom === undefined) {
4225 var zoomChanged = this._zoom !== zoom;
4228 this._lastCenter = center;
4229 this._pixelOrigin = this._getNewPixelOrigin(center);
4231 // @event zoom: Event
4232 // Fired repeatedly during any change in zoom level, including zoom
4233 // and fly animations.
4234 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530
4235 this.fire('zoom', data);
4238 // @event move: Event
4239 // Fired repeatedly during any movement of the map, including pan and
4241 return this.fire('move', data);
4244 _moveEnd: function (zoomChanged) {
4245 // @event zoomend: Event
4246 // Fired when the map has changed, after any animations.
4248 this.fire('zoomend');
4251 // @event moveend: Event
4252 // Fired when the center of the map stops changing (e.g. user stopped
4253 // dragging the map).
4254 return this.fire('moveend');
4257 _stop: function () {
4258 cancelAnimFrame(this._flyToFrame);
4259 if (this._panAnim) {
4260 this._panAnim.stop();
4265 _rawPanBy: function (offset) {
4266 setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
4269 _getZoomSpan: function () {
4270 return this.getMaxZoom() - this.getMinZoom();
4273 _panInsideMaxBounds: function () {
4274 if (!this._enforcingBounds) {
4275 this.panInsideBounds(this.options.maxBounds);
4279 _checkIfLoaded: function () {
4280 if (!this._loaded) {
4281 throw new Error('Set map center and zoom first.');
4285 // DOM event handling
4287 // @section Interaction events
4288 _initEvents: function (remove$$1) {
4290 this._targets[stamp(this._container)] = this;
4292 var onOff = remove$$1 ? off : on;
4294 // @event click: MouseEvent
4295 // Fired when the user clicks (or taps) the map.
4296 // @event dblclick: MouseEvent
4297 // Fired when the user double-clicks (or double-taps) the map.
4298 // @event mousedown: MouseEvent
4299 // Fired when the user pushes the mouse button on the map.
4300 // @event mouseup: MouseEvent
4301 // Fired when the user releases the mouse button on the map.
4302 // @event mouseover: MouseEvent
4303 // Fired when the mouse enters the map.
4304 // @event mouseout: MouseEvent
4305 // Fired when the mouse leaves the map.
4306 // @event mousemove: MouseEvent
4307 // Fired while the mouse moves over the map.
4308 // @event contextmenu: MouseEvent
4309 // Fired when the user pushes the right mouse button on the map, prevents
4310 // default browser context menu from showing if there are listeners on
4311 // this event. Also fired on mobile when the user holds a single touch
4312 // for a second (also called long press).
4313 // @event keypress: KeyboardEvent
4314 // Fired when the user presses a key from the keyboard that produces a character value while the map is focused.
4315 // @event keydown: KeyboardEvent
4316 // Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event,
4317 // the `keydown` event is fired for keys that produce a character value and for keys
4318 // that do not produce a character value.
4319 // @event keyup: KeyboardEvent
4320 // Fired when the user releases a key from the keyboard while the map is focused.
4321 onOff(this._container, 'click dblclick mousedown mouseup ' +
4322 'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this);
4324 if (this.options.trackResize) {
4325 onOff(window, 'resize', this._onResize, this);
4328 if (any3d && this.options.transform3DLimit) {
4329 (remove$$1 ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
4333 _onResize: function () {
4334 cancelAnimFrame(this._resizeRequest);
4335 this._resizeRequest = requestAnimFrame(
4336 function () { this.invalidateSize({debounceMoveend: true}); }, this);
4339 _onScroll: function () {
4340 this._container.scrollTop = 0;
4341 this._container.scrollLeft = 0;
4344 _onMoveEnd: function () {
4345 var pos = this._getMapPanePos();
4346 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
4347 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
4348 // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
4349 this._resetView(this.getCenter(), this.getZoom());
4353 _findEventTargets: function (e, type) {
4356 isHover = type === 'mouseout' || type === 'mouseover',
4357 src = e.target || e.srcElement,
4361 target = this._targets[stamp(src)];
4362 if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
4363 // Prevent firing click after you just dragged an object.
4367 if (target && target.listens(type, true)) {
4368 if (isHover && !isExternalTarget(src, e)) { break; }
4369 targets.push(target);
4370 if (isHover) { break; }
4372 if (src === this._container) { break; }
4373 src = src.parentNode;
4375 if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) {
4381 _handleDOMEvent: function (e) {
4382 if (!this._loaded || skipped(e)) { return; }
4386 if (type === 'mousedown' || type === 'keypress' || type === 'keyup' || type === 'keydown') {
4387 // prevents outline when clicking on keyboard-focusable element
4388 preventOutline(e.target || e.srcElement);
4391 this._fireDOMEvent(e, type);
4394 _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
4396 _fireDOMEvent: function (e, type, targets) {
4398 if (e.type === 'click') {
4399 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
4400 // @event preclick: MouseEvent
4401 // Fired before mouse click on the map (sometimes useful when you
4402 // want something to happen on click before any existing click
4403 // handlers start running).
4404 var synth = extend({}, e);
4405 synth.type = 'preclick';
4406 this._fireDOMEvent(synth, synth.type, targets);
4409 if (e._stopped) { return; }
4411 // Find the layer the event is propagating from and its parents.
4412 targets = (targets || []).concat(this._findEventTargets(e, type));
4414 if (!targets.length) { return; }
4416 var target = targets[0];
4417 if (type === 'contextmenu' && target.listens(type, true)) {
4425 if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') {
4426 var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
4427 data.containerPoint = isMarker ?
4428 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
4429 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
4430 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
4433 for (var i = 0; i < targets.length; i++) {
4434 targets[i].fire(type, data, true);
4435 if (data.originalEvent._stopped ||
4436 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
4440 _draggableMoved: function (obj) {
4441 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
4442 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
4445 _clearHandlers: function () {
4446 for (var i = 0, len = this._handlers.length; i < len; i++) {
4447 this._handlers[i].disable();
4451 // @section Other Methods
4453 // @method whenReady(fn: Function, context?: Object): this
4454 // Runs the given function `fn` when the map gets initialized with
4455 // a view (center and zoom) and at least one layer, or immediately
4456 // if it's already initialized, optionally passing a function context.
4457 whenReady: function (callback, context) {
4459 callback.call(context || this, {target: this});
4461 this.on('load', callback, context);
4467 // private methods for getting map state
4469 _getMapPanePos: function () {
4470 return getPosition(this._mapPane) || new Point(0, 0);
4473 _moved: function () {
4474 var pos = this._getMapPanePos();
4475 return pos && !pos.equals([0, 0]);
4478 _getTopLeftPoint: function (center, zoom) {
4479 var pixelOrigin = center && zoom !== undefined ?
4480 this._getNewPixelOrigin(center, zoom) :
4481 this.getPixelOrigin();
4482 return pixelOrigin.subtract(this._getMapPanePos());
4485 _getNewPixelOrigin: function (center, zoom) {
4486 var viewHalf = this.getSize()._divideBy(2);
4487 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
4490 _latLngToNewLayerPoint: function (latlng, zoom, center) {
4491 var topLeft = this._getNewPixelOrigin(center, zoom);
4492 return this.project(latlng, zoom)._subtract(topLeft);
4495 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
4496 var topLeft = this._getNewPixelOrigin(center, zoom);
4498 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
4499 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
4500 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
4501 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
4505 // layer point of the current center
4506 _getCenterLayerPoint: function () {
4507 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
4510 // offset of the specified place to the current center in pixels
4511 _getCenterOffset: function (latlng) {
4512 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
4515 // adjust center for view to get inside bounds
4516 _limitCenter: function (center, zoom, bounds) {
4518 if (!bounds) { return center; }
4520 var centerPoint = this.project(center, zoom),
4521 viewHalf = this.getSize().divideBy(2),
4522 viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
4523 offset = this._getBoundsOffset(viewBounds, bounds, zoom);
4525 // If offset is less than a pixel, ignore.
4526 // This prevents unstable projections from getting into
4527 // an infinite loop of tiny offsets.
4528 if (offset.round().equals([0, 0])) {
4532 return this.unproject(centerPoint.add(offset), zoom);
4535 // adjust offset for view to get inside bounds
4536 _limitOffset: function (offset, bounds) {
4537 if (!bounds) { return offset; }
4539 var viewBounds = this.getPixelBounds(),
4540 newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
4542 return offset.add(this._getBoundsOffset(newBounds, bounds));
4545 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
4546 _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
4547 var projectedMaxBounds = toBounds(
4548 this.project(maxBounds.getNorthEast(), zoom),
4549 this.project(maxBounds.getSouthWest(), zoom)
4551 minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
4552 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
4554 dx = this._rebound(minOffset.x, -maxOffset.x),
4555 dy = this._rebound(minOffset.y, -maxOffset.y);
4557 return new Point(dx, dy);
4560 _rebound: function (left, right) {
4561 return left + right > 0 ?
4562 Math.round(left - right) / 2 :
4563 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
4566 _limitZoom: function (zoom) {
4567 var min = this.getMinZoom(),
4568 max = this.getMaxZoom(),
4569 snap = any3d ? this.options.zoomSnap : 1;
4571 zoom = Math.round(zoom / snap) * snap;
4573 return Math.max(min, Math.min(max, zoom));
4576 _onPanTransitionStep: function () {
4580 _onPanTransitionEnd: function () {
4581 removeClass(this._mapPane, 'leaflet-pan-anim');
4582 this.fire('moveend');
4585 _tryAnimatedPan: function (center, options) {
4586 // difference between the new and current centers in pixels
4587 var offset = this._getCenterOffset(center)._trunc();
4589 // don't animate too far unless animate: true specified in options
4590 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
4592 this.panBy(offset, options);
4597 _createAnimProxy: function () {
4599 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
4600 this._panes.mapPane.appendChild(proxy);
4602 this.on('zoomanim', function (e) {
4603 var prop = TRANSFORM,
4604 transform = this._proxy.style[prop];
4606 setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
4608 // workaround for case when transform is the same and so transitionend event is not fired
4609 if (transform === this._proxy.style[prop] && this._animatingZoom) {
4610 this._onZoomTransitionEnd();
4614 this.on('load moveend', function () {
4615 var c = this.getCenter(),
4617 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
4620 this._on('unload', this._destroyAnimProxy, this);
4623 _destroyAnimProxy: function () {
4624 remove(this._proxy);
4628 _catchTransitionEnd: function (e) {
4629 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
4630 this._onZoomTransitionEnd();
4634 _nothingToAnimate: function () {
4635 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
4638 _tryAnimatedZoom: function (center, zoom, options) {
4640 if (this._animatingZoom) { return true; }
4642 options = options || {};
4644 // don't animate if disabled, not supported or zoom difference is too large
4645 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
4646 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
4648 // offset is the pixel coords of the zoom origin relative to the current center
4649 var scale = this.getZoomScale(zoom),
4650 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
4652 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
4653 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
4655 requestAnimFrame(function () {
4657 ._moveStart(true, false)
4658 ._animateZoom(center, zoom, true);
4664 _animateZoom: function (center, zoom, startAnim, noUpdate) {
4665 if (!this._mapPane) { return; }
4668 this._animatingZoom = true;
4670 // remember what center/zoom to set after animation
4671 this._animateToCenter = center;
4672 this._animateToZoom = zoom;
4674 addClass(this._mapPane, 'leaflet-zoom-anim');
4677 // @event zoomanim: ZoomAnimEvent
4678 // Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom.
4679 this.fire('zoomanim', {
4685 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
4686 setTimeout(bind(this._onZoomTransitionEnd, this), 250);
4689 _onZoomTransitionEnd: function () {
4690 if (!this._animatingZoom) { return; }
4692 if (this._mapPane) {
4693 removeClass(this._mapPane, 'leaflet-zoom-anim');
4696 this._animatingZoom = false;
4698 this._move(this._animateToCenter, this._animateToZoom);
4700 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
4701 requestAnimFrame(function () {
4702 this._moveEnd(true);
4709 // @factory L.map(id: String, options?: Map options)
4710 // Instantiates a map object given the DOM ID of a `<div>` element
4711 // and optionally an object literal with `Map options`.
4714 // @factory L.map(el: HTMLElement, options?: Map options)
4715 // Instantiates a map object given an instance of a `<div>` HTML element
4716 // and optionally an object literal with `Map options`.
4717 function createMap(id, options) {
4718 return new Map(id, options);
4726 * L.Control is a base class for implementing map controls. Handles positioning.
4727 * All other controls extend from this class.
4730 var Control = Class.extend({
4732 // @aka Control options
4734 // @option position: String = 'topright'
4735 // The position of the control (one of the map corners). Possible values are `'topleft'`,
4736 // `'topright'`, `'bottomleft'` or `'bottomright'`
4737 position: 'topright'
4740 initialize: function (options) {
4741 setOptions(this, options);
4745 * Classes extending L.Control will inherit the following methods:
4747 * @method getPosition: string
4748 * Returns the position of the control.
4750 getPosition: function () {
4751 return this.options.position;
4754 // @method setPosition(position: string): this
4755 // Sets the position of the control.
4756 setPosition: function (position) {
4757 var map = this._map;
4760 map.removeControl(this);
4763 this.options.position = position;
4766 map.addControl(this);
4772 // @method getContainer: HTMLElement
4773 // Returns the HTMLElement that contains the control.
4774 getContainer: function () {
4775 return this._container;
4778 // @method addTo(map: Map): this
4779 // Adds the control to the given map.
4780 addTo: function (map) {
4784 var container = this._container = this.onAdd(map),
4785 pos = this.getPosition(),
4786 corner = map._controlCorners[pos];
4788 addClass(container, 'leaflet-control');
4790 if (pos.indexOf('bottom') !== -1) {
4791 corner.insertBefore(container, corner.firstChild);
4793 corner.appendChild(container);
4796 this._map.on('unload', this.remove, this);
4801 // @method remove: this
4802 // Removes the control from the map it is currently active on.
4803 remove: function () {
4808 remove(this._container);
4810 if (this.onRemove) {
4811 this.onRemove(this._map);
4814 this._map.off('unload', this.remove, this);
4820 _refocusOnMap: function (e) {
4821 // if map exists and event is not a keyboard event
4822 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
4823 this._map.getContainer().focus();
4828 var control = function (options) {
4829 return new Control(options);
4832 /* @section Extension methods
4835 * Every control should extend from `L.Control` and (re-)implement the following methods.
4837 * @method onAdd(map: Map): HTMLElement
4838 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
4840 * @method onRemove(map: Map)
4841 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
4845 * @section Methods for Layers and Controls
4848 // @method addControl(control: Control): this
4849 // Adds the given control to the map
4850 addControl: function (control) {
4851 control.addTo(this);
4855 // @method removeControl(control: Control): this
4856 // Removes the given control from the map
4857 removeControl: function (control) {
4862 _initControlPos: function () {
4863 var corners = this._controlCorners = {},
4865 container = this._controlContainer =
4866 create$1('div', l + 'control-container', this._container);
4868 function createCorner(vSide, hSide) {
4869 var className = l + vSide + ' ' + l + hSide;
4871 corners[vSide + hSide] = create$1('div', className, container);
4874 createCorner('top', 'left');
4875 createCorner('top', 'right');
4876 createCorner('bottom', 'left');
4877 createCorner('bottom', 'right');
4880 _clearControlPos: function () {
4881 for (var i in this._controlCorners) {
4882 remove(this._controlCorners[i]);
4884 remove(this._controlContainer);
4885 delete this._controlCorners;
4886 delete this._controlContainer;
4891 * @class Control.Layers
4892 * @aka L.Control.Layers
4895 * 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`.
4900 * var baseLayers = {
4902 * "OpenStreetMap": osm
4907 * "Roads": roadsLayer
4910 * L.control.layers(baseLayers, overlays).addTo(map);
4913 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
4917 * "<someName1>": layer1,
4918 * "<someName2>": layer2
4922 * The layer names can contain HTML, which allows you to add additional styling to the items:
4925 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
4929 var Layers = Control.extend({
4931 // @aka Control.Layers options
4933 // @option collapsed: Boolean = true
4934 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
4936 position: 'topright',
4938 // @option autoZIndex: Boolean = true
4939 // 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.
4942 // @option hideSingleBase: Boolean = false
4943 // If `true`, the base layers in the control will be hidden when there is only one.
4944 hideSingleBase: false,
4946 // @option sortLayers: Boolean = false
4947 // Whether to sort the layers. When `false`, layers will keep the order
4948 // in which they were added to the control.
4951 // @option sortFunction: Function = *
4952 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
4953 // that will be used for sorting the layers, when `sortLayers` is `true`.
4954 // The function receives both the `L.Layer` instances and their names, as in
4955 // `sortFunction(layerA, layerB, nameA, nameB)`.
4956 // By default, it sorts layers alphabetically by their name.
4957 sortFunction: function (layerA, layerB, nameA, nameB) {
4958 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
4962 initialize: function (baseLayers, overlays, options) {
4963 setOptions(this, options);
4965 this._layerControlInputs = [];
4967 this._lastZIndex = 0;
4968 this._handlingClick = false;
4970 for (var i in baseLayers) {
4971 this._addLayer(baseLayers[i], i);
4974 for (i in overlays) {
4975 this._addLayer(overlays[i], i, true);
4979 onAdd: function (map) {
4984 map.on('zoomend', this._checkDisabledLayers, this);
4986 for (var i = 0; i < this._layers.length; i++) {
4987 this._layers[i].layer.on('add remove', this._onLayerChange, this);
4990 return this._container;
4993 addTo: function (map) {
4994 Control.prototype.addTo.call(this, map);
4995 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
4996 return this._expandIfNotCollapsed();
4999 onRemove: function () {
5000 this._map.off('zoomend', this._checkDisabledLayers, this);
5002 for (var i = 0; i < this._layers.length; i++) {
5003 this._layers[i].layer.off('add remove', this._onLayerChange, this);
5007 // @method addBaseLayer(layer: Layer, name: String): this
5008 // Adds a base layer (radio button entry) with the given name to the control.
5009 addBaseLayer: function (layer, name) {
5010 this._addLayer(layer, name);
5011 return (this._map) ? this._update() : this;
5014 // @method addOverlay(layer: Layer, name: String): this
5015 // Adds an overlay (checkbox entry) with the given name to the control.
5016 addOverlay: function (layer, name) {
5017 this._addLayer(layer, name, true);
5018 return (this._map) ? this._update() : this;
5021 // @method removeLayer(layer: Layer): this
5022 // Remove the given layer from the control.
5023 removeLayer: function (layer) {
5024 layer.off('add remove', this._onLayerChange, this);
5026 var obj = this._getLayer(stamp(layer));
5028 this._layers.splice(this._layers.indexOf(obj), 1);
5030 return (this._map) ? this._update() : this;
5033 // @method expand(): this
5034 // Expand the control container if collapsed.
5035 expand: function () {
5036 addClass(this._container, 'leaflet-control-layers-expanded');
5037 this._section.style.height = null;
5038 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
5039 if (acceptableHeight < this._section.clientHeight) {
5040 addClass(this._section, 'leaflet-control-layers-scrollbar');
5041 this._section.style.height = acceptableHeight + 'px';
5043 removeClass(this._section, 'leaflet-control-layers-scrollbar');
5045 this._checkDisabledLayers();
5049 // @method collapse(): this
5050 // Collapse the control container if expanded.
5051 collapse: function () {
5052 removeClass(this._container, 'leaflet-control-layers-expanded');
5056 _initLayout: function () {
5057 var className = 'leaflet-control-layers',
5058 container = this._container = create$1('div', className),
5059 collapsed = this.options.collapsed;
5061 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
5062 container.setAttribute('aria-haspopup', true);
5064 disableClickPropagation(container);
5065 disableScrollPropagation(container);
5067 var section = this._section = create$1('section', className + '-list');
5070 this._map.on('click', this.collapse, this);
5074 mouseenter: this.expand,
5075 mouseleave: this.collapse
5080 var link = this._layersLink = create$1('a', className + '-toggle', container);
5082 link.title = 'Layers';
5085 on(link, 'click', stop);
5086 on(link, 'click', this.expand, this);
5088 on(link, 'focus', this.expand, this);
5095 this._baseLayersList = create$1('div', className + '-base', section);
5096 this._separator = create$1('div', className + '-separator', section);
5097 this._overlaysList = create$1('div', className + '-overlays', section);
5099 container.appendChild(section);
5102 _getLayer: function (id) {
5103 for (var i = 0; i < this._layers.length; i++) {
5105 if (this._layers[i] && stamp(this._layers[i].layer) === id) {
5106 return this._layers[i];
5111 _addLayer: function (layer, name, overlay) {
5113 layer.on('add remove', this._onLayerChange, this);
5122 if (this.options.sortLayers) {
5123 this._layers.sort(bind(function (a, b) {
5124 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
5128 if (this.options.autoZIndex && layer.setZIndex) {
5130 layer.setZIndex(this._lastZIndex);
5133 this._expandIfNotCollapsed();
5136 _update: function () {
5137 if (!this._container) { return this; }
5139 empty(this._baseLayersList);
5140 empty(this._overlaysList);
5142 this._layerControlInputs = [];
5143 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
5145 for (i = 0; i < this._layers.length; i++) {
5146 obj = this._layers[i];
5148 overlaysPresent = overlaysPresent || obj.overlay;
5149 baseLayersPresent = baseLayersPresent || !obj.overlay;
5150 baseLayersCount += !obj.overlay ? 1 : 0;
5153 // Hide base layers section if there's only one layer.
5154 if (this.options.hideSingleBase) {
5155 baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
5156 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
5159 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
5164 _onLayerChange: function (e) {
5165 if (!this._handlingClick) {
5169 var obj = this._getLayer(stamp(e.target));
5172 // @section Layer events
5173 // @event baselayerchange: LayersControlEvent
5174 // Fired when the base layer is changed through the [layer control](#control-layers).
5175 // @event overlayadd: LayersControlEvent
5176 // Fired when an overlay is selected through the [layer control](#control-layers).
5177 // @event overlayremove: LayersControlEvent
5178 // Fired when an overlay is deselected through the [layer control](#control-layers).
5179 // @namespace Control.Layers
5180 var type = obj.overlay ?
5181 (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
5182 (e.type === 'add' ? 'baselayerchange' : null);
5185 this._map.fire(type, obj);
5189 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
5190 _createRadioElement: function (name, checked) {
5192 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
5193 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
5195 var radioFragment = document.createElement('div');
5196 radioFragment.innerHTML = radioHtml;
5198 return radioFragment.firstChild;
5201 _addItem: function (obj) {
5202 var label = document.createElement('label'),
5203 checked = this._map.hasLayer(obj.layer),
5207 input = document.createElement('input');
5208 input.type = 'checkbox';
5209 input.className = 'leaflet-control-layers-selector';
5210 input.defaultChecked = checked;
5212 input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked);
5215 this._layerControlInputs.push(input);
5216 input.layerId = stamp(obj.layer);
5218 on(input, 'click', this._onInputClick, this);
5220 var name = document.createElement('span');
5221 name.innerHTML = ' ' + obj.name;
5223 // Helps from preventing layer control flicker when checkboxes are disabled
5224 // https://github.com/Leaflet/Leaflet/issues/2771
5225 var holder = document.createElement('div');
5227 label.appendChild(holder);
5228 holder.appendChild(input);
5229 holder.appendChild(name);
5231 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
5232 container.appendChild(label);
5234 this._checkDisabledLayers();
5238 _onInputClick: function () {
5239 var inputs = this._layerControlInputs,
5241 var addedLayers = [],
5244 this._handlingClick = true;
5246 for (var i = inputs.length - 1; i >= 0; i--) {
5248 layer = this._getLayer(input.layerId).layer;
5250 if (input.checked) {
5251 addedLayers.push(layer);
5252 } else if (!input.checked) {
5253 removedLayers.push(layer);
5257 // Bugfix issue 2318: Should remove all old layers before readding new ones
5258 for (i = 0; i < removedLayers.length; i++) {
5259 if (this._map.hasLayer(removedLayers[i])) {
5260 this._map.removeLayer(removedLayers[i]);
5263 for (i = 0; i < addedLayers.length; i++) {
5264 if (!this._map.hasLayer(addedLayers[i])) {
5265 this._map.addLayer(addedLayers[i]);
5269 this._handlingClick = false;
5271 this._refocusOnMap();
5274 _checkDisabledLayers: function () {
5275 var inputs = this._layerControlInputs,
5278 zoom = this._map.getZoom();
5280 for (var i = inputs.length - 1; i >= 0; i--) {
5282 layer = this._getLayer(input.layerId).layer;
5283 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
5284 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
5289 _expandIfNotCollapsed: function () {
5290 if (this._map && !this.options.collapsed) {
5296 _expand: function () {
5297 // Backward compatibility, remove me in 1.1.
5298 return this.expand();
5301 _collapse: function () {
5302 // Backward compatibility, remove me in 1.1.
5303 return this.collapse();
5309 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
5310 // 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.
5311 var layers = function (baseLayers, overlays, options) {
5312 return new Layers(baseLayers, overlays, options);
5316 * @class Control.Zoom
5317 * @aka L.Control.Zoom
5320 * 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`.
5323 var Zoom = Control.extend({
5325 // @aka Control.Zoom options
5327 position: 'topleft',
5329 // @option zoomInText: String = '+'
5330 // The text set on the 'zoom in' button.
5333 // @option zoomInTitle: String = 'Zoom in'
5334 // The title set on the 'zoom in' button.
5335 zoomInTitle: 'Zoom in',
5337 // @option zoomOutText: String = '−'
5338 // The text set on the 'zoom out' button.
5339 zoomOutText: '−',
5341 // @option zoomOutTitle: String = 'Zoom out'
5342 // The title set on the 'zoom out' button.
5343 zoomOutTitle: 'Zoom out'
5346 onAdd: function (map) {
5347 var zoomName = 'leaflet-control-zoom',
5348 container = create$1('div', zoomName + ' leaflet-bar'),
5349 options = this.options;
5351 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle,
5352 zoomName + '-in', container, this._zoomIn);
5353 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
5354 zoomName + '-out', container, this._zoomOut);
5356 this._updateDisabled();
5357 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
5362 onRemove: function (map) {
5363 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
5366 disable: function () {
5367 this._disabled = true;
5368 this._updateDisabled();
5372 enable: function () {
5373 this._disabled = false;
5374 this._updateDisabled();
5378 _zoomIn: function (e) {
5379 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
5380 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5384 _zoomOut: function (e) {
5385 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
5386 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5390 _createButton: function (html, title, className, container, fn) {
5391 var link = create$1('a', className, container);
5392 link.innerHTML = html;
5397 * Will force screen readers like VoiceOver to read this as "Zoom in - button"
5399 link.setAttribute('role', 'button');
5400 link.setAttribute('aria-label', title);
5402 disableClickPropagation(link);
5403 on(link, 'click', stop);
5404 on(link, 'click', fn, this);
5405 on(link, 'click', this._refocusOnMap, this);
5410 _updateDisabled: function () {
5411 var map = this._map,
5412 className = 'leaflet-disabled';
5414 removeClass(this._zoomInButton, className);
5415 removeClass(this._zoomOutButton, className);
5417 if (this._disabled || map._zoom === map.getMinZoom()) {
5418 addClass(this._zoomOutButton, className);
5420 if (this._disabled || map._zoom === map.getMaxZoom()) {
5421 addClass(this._zoomInButton, className);
5427 // @section Control options
5428 // @option zoomControl: Boolean = true
5429 // Whether a [zoom control](#control-zoom) is added to the map by default.
5434 Map.addInitHook(function () {
5435 if (this.options.zoomControl) {
5436 // @section Controls
5437 // @property zoomControl: Control.Zoom
5438 // The default zoom control (only available if the
5439 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
5440 this.zoomControl = new Zoom();
5441 this.addControl(this.zoomControl);
5445 // @namespace Control.Zoom
5446 // @factory L.control.zoom(options: Control.Zoom options)
5447 // Creates a zoom control
5448 var zoom = function (options) {
5449 return new Zoom(options);
5453 * @class Control.Scale
5454 * @aka L.Control.Scale
5457 * 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`.
5462 * L.control.scale().addTo(map);
5466 var Scale = Control.extend({
5468 // @aka Control.Scale options
5470 position: 'bottomleft',
5472 // @option maxWidth: Number = 100
5473 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
5476 // @option metric: Boolean = True
5477 // Whether to show the metric scale line (m/km).
5480 // @option imperial: Boolean = True
5481 // Whether to show the imperial scale line (mi/ft).
5484 // @option updateWhenIdle: Boolean = false
5485 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
5488 onAdd: function (map) {
5489 var className = 'leaflet-control-scale',
5490 container = create$1('div', className),
5491 options = this.options;
5493 this._addScales(options, className + '-line', container);
5495 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5496 map.whenReady(this._update, this);
5501 onRemove: function (map) {
5502 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5505 _addScales: function (options, className, container) {
5506 if (options.metric) {
5507 this._mScale = create$1('div', className, container);
5509 if (options.imperial) {
5510 this._iScale = create$1('div', className, container);
5514 _update: function () {
5515 var map = this._map,
5516 y = map.getSize().y / 2;
5518 var maxMeters = map.distance(
5519 map.containerPointToLatLng([0, y]),
5520 map.containerPointToLatLng([this.options.maxWidth, y]));
5522 this._updateScales(maxMeters);
5525 _updateScales: function (maxMeters) {
5526 if (this.options.metric && maxMeters) {
5527 this._updateMetric(maxMeters);
5529 if (this.options.imperial && maxMeters) {
5530 this._updateImperial(maxMeters);
5534 _updateMetric: function (maxMeters) {
5535 var meters = this._getRoundNum(maxMeters),
5536 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
5538 this._updateScale(this._mScale, label, meters / maxMeters);
5541 _updateImperial: function (maxMeters) {
5542 var maxFeet = maxMeters * 3.2808399,
5543 maxMiles, miles, feet;
5545 if (maxFeet > 5280) {
5546 maxMiles = maxFeet / 5280;
5547 miles = this._getRoundNum(maxMiles);
5548 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
5551 feet = this._getRoundNum(maxFeet);
5552 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
5556 _updateScale: function (scale, text, ratio) {
5557 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
5558 scale.innerHTML = text;
5561 _getRoundNum: function (num) {
5562 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5575 // @factory L.control.scale(options?: Control.Scale options)
5576 // Creates an scale control with the given options.
5577 var scale = function (options) {
5578 return new Scale(options);
5582 * @class Control.Attribution
5583 * @aka L.Control.Attribution
5586 * 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.
5589 var Attribution = Control.extend({
5591 // @aka Control.Attribution options
5593 position: 'bottomright',
5595 // @option prefix: String = 'Leaflet'
5596 // The HTML text shown before the attributions. Pass `false` to disable.
5597 prefix: '<a href="https://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
5600 initialize: function (options) {
5601 setOptions(this, options);
5603 this._attributions = {};
5606 onAdd: function (map) {
5607 map.attributionControl = this;
5608 this._container = create$1('div', 'leaflet-control-attribution');
5609 disableClickPropagation(this._container);
5611 // TODO ugly, refactor
5612 for (var i in map._layers) {
5613 if (map._layers[i].getAttribution) {
5614 this.addAttribution(map._layers[i].getAttribution());
5620 return this._container;
5623 // @method setPrefix(prefix: String): this
5624 // Sets the text before the attributions.
5625 setPrefix: function (prefix) {
5626 this.options.prefix = prefix;
5631 // @method addAttribution(text: String): this
5632 // Adds an attribution text (e.g. `'Vector data © Mapbox'`).
5633 addAttribution: function (text) {
5634 if (!text) { return this; }
5636 if (!this._attributions[text]) {
5637 this._attributions[text] = 0;
5639 this._attributions[text]++;
5646 // @method removeAttribution(text: String): this
5647 // Removes an attribution text.
5648 removeAttribution: function (text) {
5649 if (!text) { return this; }
5651 if (this._attributions[text]) {
5652 this._attributions[text]--;
5659 _update: function () {
5660 if (!this._map) { return; }
5664 for (var i in this._attributions) {
5665 if (this._attributions[i]) {
5670 var prefixAndAttribs = [];
5672 if (this.options.prefix) {
5673 prefixAndAttribs.push(this.options.prefix);
5675 if (attribs.length) {
5676 prefixAndAttribs.push(attribs.join(', '));
5679 this._container.innerHTML = prefixAndAttribs.join(' | ');
5684 // @section Control options
5685 // @option attributionControl: Boolean = true
5686 // Whether a [attribution control](#control-attribution) is added to the map by default.
5688 attributionControl: true
5691 Map.addInitHook(function () {
5692 if (this.options.attributionControl) {
5693 new Attribution().addTo(this);
5697 // @namespace Control.Attribution
5698 // @factory L.control.attribution(options: Control.Attribution options)
5699 // Creates an attribution control.
5700 var attribution = function (options) {
5701 return new Attribution(options);
5704 Control.Layers = Layers;
5705 Control.Zoom = Zoom;
5706 Control.Scale = Scale;
5707 Control.Attribution = Attribution;
5709 control.layers = layers;
5710 control.zoom = zoom;
5711 control.scale = scale;
5712 control.attribution = attribution;
5715 L.Handler is a base class for handler classes that are used internally to inject
5716 interaction features like dragging to classes like Map and Marker.
5721 // Abstract class for map interaction handlers
5723 var Handler = Class.extend({
5724 initialize: function (map) {
5728 // @method enable(): this
5729 // Enables the handler
5730 enable: function () {
5731 if (this._enabled) { return this; }
5733 this._enabled = true;
5738 // @method disable(): this
5739 // Disables the handler
5740 disable: function () {
5741 if (!this._enabled) { return this; }
5743 this._enabled = false;
5748 // @method enabled(): Boolean
5749 // Returns `true` if the handler is enabled
5750 enabled: function () {
5751 return !!this._enabled;
5754 // @section Extension methods
5755 // Classes inheriting from `Handler` must implement the two following methods:
5756 // @method addHooks()
5757 // Called when the handler is enabled, should add event hooks.
5758 // @method removeHooks()
5759 // Called when the handler is disabled, should remove the event hooks added previously.
5762 // @section There is static function which can be called without instantiating L.Handler:
5763 // @function addTo(map: Map, name: String): this
5764 // Adds a new Handler to the given map with the given name.
5765 Handler.addTo = function (map, name) {
5766 map.addHandler(name, this);
5770 var Mixin = {Events: Events};
5777 * A class for making DOM elements draggable (including touch support).
5778 * Used internally for map and marker dragging. Only works for elements
5779 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
5783 * var draggable = new L.Draggable(elementToDrag);
5784 * draggable.enable();
5788 var START = touch ? 'touchstart mousedown' : 'mousedown';
5790 mousedown: 'mouseup',
5791 touchstart: 'touchend',
5792 pointerdown: 'touchend',
5793 MSPointerDown: 'touchend'
5796 mousedown: 'mousemove',
5797 touchstart: 'touchmove',
5798 pointerdown: 'touchmove',
5799 MSPointerDown: 'touchmove'
5803 var Draggable = Evented.extend({
5807 // @aka Draggable options
5808 // @option clickTolerance: Number = 3
5809 // The max number of pixels a user can shift the mouse pointer during a click
5810 // for it to be considered a valid click (as opposed to a mouse drag).
5814 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
5815 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
5816 initialize: function (element, dragStartTarget, preventOutline$$1, options) {
5817 setOptions(this, options);
5819 this._element = element;
5820 this._dragStartTarget = dragStartTarget || element;
5821 this._preventOutline = preventOutline$$1;
5825 // Enables the dragging ability
5826 enable: function () {
5827 if (this._enabled) { return; }
5829 on(this._dragStartTarget, START, this._onDown, this);
5831 this._enabled = true;
5834 // @method disable()
5835 // Disables the dragging ability
5836 disable: function () {
5837 if (!this._enabled) { return; }
5839 // If we're currently dragging this draggable,
5840 // disabling it counts as first ending the drag.
5841 if (Draggable._dragging === this) {
5845 off(this._dragStartTarget, START, this._onDown, this);
5847 this._enabled = false;
5848 this._moved = false;
5851 _onDown: function (e) {
5852 // Ignore simulated events, since we handle both touch and
5853 // mouse explicitly; otherwise we risk getting duplicates of
5854 // touch events, see #4315.
5855 // Also ignore the event if disabled; this happens in IE11
5856 // under some circumstances, see #3666.
5857 if (e._simulated || !this._enabled) { return; }
5859 this._moved = false;
5861 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
5863 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
5864 Draggable._dragging = this; // Prevent dragging multiple objects at once.
5866 if (this._preventOutline) {
5867 preventOutline(this._element);
5871 disableTextSelection();
5873 if (this._moving) { return; }
5875 // @event down: Event
5876 // Fired when a drag is about to start.
5879 var first = e.touches ? e.touches[0] : e,
5880 sizedParent = getSizedParentNode(this._element);
5882 this._startPoint = new Point(first.clientX, first.clientY);
5884 // Cache the scale, so that we can continuously compensate for it during drag (_onMove).
5885 this._parentScale = getScale(sizedParent);
5887 on(document, MOVE[e.type], this._onMove, this);
5888 on(document, END[e.type], this._onUp, this);
5891 _onMove: 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; }
5899 if (e.touches && e.touches.length > 1) {
5904 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
5905 offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
5907 if (!offset.x && !offset.y) { return; }
5908 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
5910 // We assume that the parent container's position, border and scale do not change for the duration of the drag.
5911 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
5912 // and we can use the cached value for the scale.
5913 offset.x /= this._parentScale.x;
5914 offset.y /= this._parentScale.y;
5919 // @event dragstart: Event
5920 // Fired when a drag starts
5921 this.fire('dragstart');
5924 this._startPos = getPosition(this._element).subtract(offset);
5926 addClass(document.body, 'leaflet-dragging');
5928 this._lastTarget = e.target || e.srcElement;
5929 // IE and Edge do not give the <use> element, so fetch it
5931 if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
5932 this._lastTarget = this._lastTarget.correspondingUseElement;
5934 addClass(this._lastTarget, 'leaflet-drag-target');
5937 this._newPos = this._startPos.add(offset);
5938 this._moving = true;
5940 cancelAnimFrame(this._animRequest);
5941 this._lastEvent = e;
5942 this._animRequest = requestAnimFrame(this._updatePosition, this, true);
5945 _updatePosition: function () {
5946 var e = {originalEvent: this._lastEvent};
5948 // @event predrag: Event
5949 // Fired continuously during dragging *before* each corresponding
5950 // update of the element's position.
5951 this.fire('predrag', e);
5952 setPosition(this._element, this._newPos);
5954 // @event drag: Event
5955 // Fired continuously during dragging.
5956 this.fire('drag', e);
5959 _onUp: function (e) {
5960 // Ignore simulated events, since we handle both touch and
5961 // mouse explicitly; otherwise we risk getting duplicates of
5962 // touch events, see #4315.
5963 // Also ignore the event if disabled; this happens in IE11
5964 // under some circumstances, see #3666.
5965 if (e._simulated || !this._enabled) { return; }
5969 finishDrag: function () {
5970 removeClass(document.body, 'leaflet-dragging');
5972 if (this._lastTarget) {
5973 removeClass(this._lastTarget, 'leaflet-drag-target');
5974 this._lastTarget = null;
5977 for (var i in MOVE) {
5978 off(document, MOVE[i], this._onMove, this);
5979 off(document, END[i], this._onUp, this);
5983 enableTextSelection();
5985 if (this._moved && this._moving) {
5986 // ensure drag is not fired after dragend
5987 cancelAnimFrame(this._animRequest);
5989 // @event dragend: DragEndEvent
5990 // Fired when the drag ends.
5991 this.fire('dragend', {
5992 distance: this._newPos.distanceTo(this._startPos)
5996 this._moving = false;
5997 Draggable._dragging = false;
6003 * @namespace LineUtil
6005 * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
6008 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
6009 // Improves rendering performance dramatically by lessening the number of points to draw.
6011 // @function simplify(points: Point[], tolerance: Number): Point[]
6012 // Dramatically reduces the number of points in a polyline while retaining
6013 // its shape and returns a new array of simplified points, using the
6014 // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
6015 // Used for a huge performance boost when processing/displaying Leaflet polylines for
6016 // each zoom level and also reducing visual noise. tolerance affects the amount of
6017 // simplification (lesser value means higher quality but slower and with more points).
6018 // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
6019 function simplify(points, tolerance) {
6020 if (!tolerance || !points.length) {
6021 return points.slice();
6024 var sqTolerance = tolerance * tolerance;
6026 // stage 1: vertex reduction
6027 points = _reducePoints(points, sqTolerance);
6029 // stage 2: Douglas-Peucker simplification
6030 points = _simplifyDP(points, sqTolerance);
6035 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
6036 // Returns the distance between point `p` and segment `p1` to `p2`.
6037 function pointToSegmentDistance(p, p1, p2) {
6038 return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
6041 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
6042 // Returns the closest point from a point `p` on a segment `p1` to `p2`.
6043 function closestPointOnSegment(p, p1, p2) {
6044 return _sqClosestPointOnSegment(p, p1, p2);
6047 // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
6048 function _simplifyDP(points, sqTolerance) {
6050 var len = points.length,
6051 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
6052 markers = new ArrayConstructor(len);
6054 markers[0] = markers[len - 1] = 1;
6056 _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
6061 for (i = 0; i < len; i++) {
6063 newPoints.push(points[i]);
6070 function _simplifyDPStep(points, markers, sqTolerance, first, last) {
6075 for (i = first + 1; i <= last - 1; i++) {
6076 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
6078 if (sqDist > maxSqDist) {
6084 if (maxSqDist > sqTolerance) {
6087 _simplifyDPStep(points, markers, sqTolerance, first, index);
6088 _simplifyDPStep(points, markers, sqTolerance, index, last);
6092 // reduce points that are too close to each other to a single point
6093 function _reducePoints(points, sqTolerance) {
6094 var reducedPoints = [points[0]];
6096 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
6097 if (_sqDist(points[i], points[prev]) > sqTolerance) {
6098 reducedPoints.push(points[i]);
6102 if (prev < len - 1) {
6103 reducedPoints.push(points[len - 1]);
6105 return reducedPoints;
6110 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
6111 // Clips the segment a to b by rectangular bounds with the
6112 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
6113 // (modifying the segment points directly!). Used by Leaflet to only show polyline
6114 // points that are on the screen or near, increasing performance.
6115 function clipSegment(a, b, bounds, useLastCode, round) {
6116 var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
6117 codeB = _getBitCode(b, bounds),
6119 codeOut, p, newCode;
6121 // save 2nd code to avoid calculating it on the next segment
6125 // if a,b is inside the clip window (trivial accept)
6126 if (!(codeA | codeB)) {
6130 // if a,b is outside the clip window (trivial reject)
6131 if (codeA & codeB) {
6136 codeOut = codeA || codeB;
6137 p = _getEdgeIntersection(a, b, codeOut, bounds, round);
6138 newCode = _getBitCode(p, bounds);
6140 if (codeOut === codeA) {
6150 function _getEdgeIntersection(a, b, code, bounds, round) {
6157 if (code & 8) { // top
6158 x = a.x + dx * (max.y - a.y) / dy;
6161 } else if (code & 4) { // bottom
6162 x = a.x + dx * (min.y - a.y) / dy;
6165 } else if (code & 2) { // right
6167 y = a.y + dy * (max.x - a.x) / dx;
6169 } else if (code & 1) { // left
6171 y = a.y + dy * (min.x - a.x) / dx;
6174 return new Point(x, y, round);
6177 function _getBitCode(p, bounds) {
6180 if (p.x < bounds.min.x) { // left
6182 } else if (p.x > bounds.max.x) { // right
6186 if (p.y < bounds.min.y) { // bottom
6188 } else if (p.y > bounds.max.y) { // top
6195 // square distance (to avoid unnecessary Math.sqrt calls)
6196 function _sqDist(p1, p2) {
6197 var dx = p2.x - p1.x,
6199 return dx * dx + dy * dy;
6202 // return closest point on segment or distance to that point
6203 function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
6208 dot = dx * dx + dy * dy,
6212 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
6226 return sqDist ? dx * dx + dy * dy : new Point(x, y);
6230 // @function isFlat(latlngs: LatLng[]): Boolean
6231 // Returns true if `latlngs` is a flat array, false is nested.
6232 function isFlat(latlngs) {
6233 return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
6236 function _flat(latlngs) {
6237 console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
6238 return isFlat(latlngs);
6242 var LineUtil = (Object.freeze || Object)({
6244 pointToSegmentDistance: pointToSegmentDistance,
6245 closestPointOnSegment: closestPointOnSegment,
6246 clipSegment: clipSegment,
6247 _getEdgeIntersection: _getEdgeIntersection,
6248 _getBitCode: _getBitCode,
6249 _sqClosestPointOnSegment: _sqClosestPointOnSegment,
6255 * @namespace PolyUtil
6256 * Various utility functions for polygon geometries.
6259 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
6260 * 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)).
6261 * Used by Leaflet to only show polygon points that are on the screen or near, increasing
6262 * performance. Note that polygon points needs different algorithm for clipping
6263 * than polyline, so there's a separate method for it.
6265 function clipPolygon(points, bounds, round) {
6267 edges = [1, 4, 2, 8],
6272 for (i = 0, len = points.length; i < len; i++) {
6273 points[i]._code = _getBitCode(points[i], bounds);
6276 // for each edge (left, bottom, right, top)
6277 for (k = 0; k < 4; k++) {
6281 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
6285 // if a is inside the clip window
6286 if (!(a._code & edge)) {
6287 // if b is outside the clip window (a->b goes out of screen)
6288 if (b._code & edge) {
6289 p = _getEdgeIntersection(b, a, edge, bounds, round);
6290 p._code = _getBitCode(p, bounds);
6291 clippedPoints.push(p);
6293 clippedPoints.push(a);
6295 // else if b is inside the clip window (a->b enters the screen)
6296 } else if (!(b._code & edge)) {
6297 p = _getEdgeIntersection(b, a, edge, bounds, round);
6298 p._code = _getBitCode(p, bounds);
6299 clippedPoints.push(p);
6302 points = clippedPoints;
6309 var PolyUtil = (Object.freeze || Object)({
6310 clipPolygon: clipPolygon
6314 * @namespace Projection
6316 * Leaflet comes with a set of already defined Projections out of the box:
6318 * @projection L.Projection.LonLat
6320 * Equirectangular, or Plate Carree projection — the most simple projection,
6321 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
6322 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
6323 * `EPSG:4326` and `Simple` CRS.
6327 project: function (latlng) {
6328 return new Point(latlng.lng, latlng.lat);
6331 unproject: function (point) {
6332 return new LatLng(point.y, point.x);
6335 bounds: new Bounds([-180, -90], [180, 90])
6339 * @namespace Projection
6340 * @projection L.Projection.Mercator
6342 * Elliptical Mercator projection — more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS.
6347 R_MINOR: 6356752.314245179,
6349 bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
6351 project: function (latlng) {
6352 var d = Math.PI / 180,
6355 tmp = this.R_MINOR / r,
6356 e = Math.sqrt(1 - tmp * tmp),
6357 con = e * Math.sin(y);
6359 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
6360 y = -r * Math.log(Math.max(ts, 1E-10));
6362 return new Point(latlng.lng * d * r, y);
6365 unproject: function (point) {
6366 var d = 180 / Math.PI,
6368 tmp = this.R_MINOR / r,
6369 e = Math.sqrt(1 - tmp * tmp),
6370 ts = Math.exp(-point.y / r),
6371 phi = Math.PI / 2 - 2 * Math.atan(ts);
6373 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
6374 con = e * Math.sin(phi);
6375 con = Math.pow((1 - con) / (1 + con), e / 2);
6376 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
6380 return new LatLng(phi * d, point.x * d / r);
6387 * An object with methods for projecting geographical coordinates of the world onto
6388 * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection).
6390 * @property bounds: Bounds
6391 * The bounds (specified in CRS units) where the projection is valid
6393 * @method project(latlng: LatLng): Point
6394 * Projects geographical coordinates into a 2D point.
6395 * Only accepts actual `L.LatLng` instances, not arrays.
6397 * @method unproject(point: Point): LatLng
6398 * The inverse of `project`. Projects a 2D point into a geographical location.
6399 * Only accepts actual `L.Point` instances, not arrays.
6401 * Note that the projection instances do not inherit from Leafet's `Class` object,
6402 * and can't be instantiated. Also, new classes can't inherit from them,
6403 * and methods can't be added to them with the `include` function.
6410 var index = (Object.freeze || Object)({
6413 SphericalMercator: SphericalMercator
6418 * @crs L.CRS.EPSG3395
6420 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
6422 var EPSG3395 = extend({}, Earth, {
6424 projection: Mercator,
6426 transformation: (function () {
6427 var scale = 0.5 / (Math.PI * Mercator.R);
6428 return toTransformation(scale, 0.5, -scale, 0.5);
6434 * @crs L.CRS.EPSG4326
6436 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
6438 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
6439 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer`
6440 * with this CRS, ensure that there are two 256x256 pixel tiles covering the
6441 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
6442 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
6445 var EPSG4326 = extend({}, Earth, {
6448 transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
6455 * A simple CRS that maps longitude and latitude into `x` and `y` directly.
6456 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
6457 * axis should still be inverted (going from bottom to top). `distance()` returns
6458 * simple euclidean distance.
6461 var Simple = extend({}, CRS, {
6463 transformation: toTransformation(1, 0, -1, 0),
6465 scale: function (zoom) {
6466 return Math.pow(2, zoom);
6469 zoom: function (scale) {
6470 return Math.log(scale) / Math.LN2;
6473 distance: function (latlng1, latlng2) {
6474 var dx = latlng2.lng - latlng1.lng,
6475 dy = latlng2.lat - latlng1.lat;
6477 return Math.sqrt(dx * dx + dy * dy);
6484 CRS.EPSG3395 = EPSG3395;
6485 CRS.EPSG3857 = EPSG3857;
6486 CRS.EPSG900913 = EPSG900913;
6487 CRS.EPSG4326 = EPSG4326;
6488 CRS.Simple = Simple;
6496 * A set of methods from the Layer base class that all Leaflet layers use.
6497 * Inherits all methods, options and events from `L.Evented`.
6502 * var layer = L.marker(latlng).addTo(map);
6508 * Fired after the layer is added to a map
6510 * @event remove: Event
6511 * Fired after the layer is removed from a map
6515 var Layer = Evented.extend({
6517 // Classes extending `L.Layer` will inherit the following options:
6519 // @option pane: String = 'overlayPane'
6520 // 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.
6521 pane: 'overlayPane',
6523 // @option attribution: String = null
6524 // String to be shown in the attribution control, e.g. "© OpenStreetMap contributors". It describes the layer data and is often a legal obligation towards copyright holders and tile providers.
6527 bubblingMouseEvents: true
6531 * Classes extending `L.Layer` will inherit the following methods:
6533 * @method addTo(map: Map|LayerGroup): this
6534 * Adds the layer to the given map or layer group.
6536 addTo: function (map) {
6541 // @method remove: this
6542 // Removes the layer from the map it is currently active on.
6543 remove: function () {
6544 return this.removeFrom(this._map || this._mapToAdd);
6547 // @method removeFrom(map: Map): this
6548 // Removes the layer from the given map
6549 removeFrom: function (obj) {
6551 obj.removeLayer(this);
6556 // @method getPane(name? : String): HTMLElement
6557 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
6558 getPane: function (name) {
6559 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
6562 addInteractiveTarget: function (targetEl) {
6563 this._map._targets[stamp(targetEl)] = this;
6567 removeInteractiveTarget: function (targetEl) {
6568 delete this._map._targets[stamp(targetEl)];
6572 // @method getAttribution: String
6573 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
6574 getAttribution: function () {
6575 return this.options.attribution;
6578 _layerAdd: function (e) {
6581 // check in case layer gets added and then removed before the map is ready
6582 if (!map.hasLayer(this)) { return; }
6585 this._zoomAnimated = map._zoomAnimated;
6587 if (this.getEvents) {
6588 var events = this.getEvents();
6589 map.on(events, this);
6590 this.once('remove', function () {
6591 map.off(events, this);
6597 if (this.getAttribution && map.attributionControl) {
6598 map.attributionControl.addAttribution(this.getAttribution());
6602 map.fire('layeradd', {layer: this});
6606 /* @section Extension methods
6609 * Every layer should extend from `L.Layer` and (re-)implement the following methods.
6611 * @method onAdd(map: Map): this
6612 * 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).
6614 * @method onRemove(map: Map): this
6615 * 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).
6617 * @method getEvents(): Object
6618 * 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.
6620 * @method getAttribution(): String
6621 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
6623 * @method beforeAdd(map: Map): this
6624 * 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.
6629 * @section Layer events
6631 * @event layeradd: LayerEvent
6632 * Fired when a new layer is added to the map.
6634 * @event layerremove: LayerEvent
6635 * Fired when some layer is removed from the map
6637 * @section Methods for Layers and Controls
6640 // @method addLayer(layer: Layer): this
6641 // Adds the given layer to the map
6642 addLayer: function (layer) {
6643 if (!layer._layerAdd) {
6644 throw new Error('The provided object is not a Layer.');
6647 var id = stamp(layer);
6648 if (this._layers[id]) { return this; }
6649 this._layers[id] = layer;
6651 layer._mapToAdd = this;
6653 if (layer.beforeAdd) {
6654 layer.beforeAdd(this);
6657 this.whenReady(layer._layerAdd, layer);
6662 // @method removeLayer(layer: Layer): this
6663 // Removes the given layer from the map.
6664 removeLayer: function (layer) {
6665 var id = stamp(layer);
6667 if (!this._layers[id]) { return this; }
6670 layer.onRemove(this);
6673 if (layer.getAttribution && this.attributionControl) {
6674 this.attributionControl.removeAttribution(layer.getAttribution());
6677 delete this._layers[id];
6680 this.fire('layerremove', {layer: layer});
6681 layer.fire('remove');
6684 layer._map = layer._mapToAdd = null;
6689 // @method hasLayer(layer: Layer): Boolean
6690 // Returns `true` if the given layer is currently added to the map
6691 hasLayer: function (layer) {
6692 return !!layer && (stamp(layer) in this._layers);
6695 /* @method eachLayer(fn: Function, context?: Object): this
6696 * Iterates over the layers of the map, optionally specifying context of the iterator function.
6698 * map.eachLayer(function(layer){
6699 * layer.bindPopup('Hello');
6703 eachLayer: function (method, context) {
6704 for (var i in this._layers) {
6705 method.call(context, this._layers[i]);
6710 _addLayers: function (layers) {
6711 layers = layers ? (isArray(layers) ? layers : [layers]) : [];
6713 for (var i = 0, len = layers.length; i < len; i++) {
6714 this.addLayer(layers[i]);
6718 _addZoomLimit: function (layer) {
6719 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
6720 this._zoomBoundLayers[stamp(layer)] = layer;
6721 this._updateZoomLevels();
6725 _removeZoomLimit: function (layer) {
6726 var id = stamp(layer);
6728 if (this._zoomBoundLayers[id]) {
6729 delete this._zoomBoundLayers[id];
6730 this._updateZoomLevels();
6734 _updateZoomLevels: function () {
6735 var minZoom = Infinity,
6736 maxZoom = -Infinity,
6737 oldZoomSpan = this._getZoomSpan();
6739 for (var i in this._zoomBoundLayers) {
6740 var options = this._zoomBoundLayers[i].options;
6742 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
6743 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
6746 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
6747 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
6749 // @section Map state change events
6750 // @event zoomlevelschange: Event
6751 // Fired when the number of zoomlevels on the map is changed due
6752 // to adding or removing a layer.
6753 if (oldZoomSpan !== this._getZoomSpan()) {
6754 this.fire('zoomlevelschange');
6757 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
6758 this.setZoom(this._layersMaxZoom);
6760 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
6761 this.setZoom(this._layersMinZoom);
6771 * Used to group several layers and handle them as one. If you add it to the map,
6772 * any layers added or removed from the group will be added/removed on the map as
6773 * well. Extends `Layer`.
6778 * L.layerGroup([marker1, marker2])
6779 * .addLayer(polyline)
6784 var LayerGroup = Layer.extend({
6786 initialize: function (layers, options) {
6787 setOptions(this, options);
6794 for (i = 0, len = layers.length; i < len; i++) {
6795 this.addLayer(layers[i]);
6800 // @method addLayer(layer: Layer): this
6801 // Adds the given layer to the group.
6802 addLayer: function (layer) {
6803 var id = this.getLayerId(layer);
6805 this._layers[id] = layer;
6808 this._map.addLayer(layer);
6814 // @method removeLayer(layer: Layer): this
6815 // Removes the given layer from the group.
6817 // @method removeLayer(id: Number): this
6818 // Removes the layer with the given internal ID from the group.
6819 removeLayer: function (layer) {
6820 var id = layer in this._layers ? layer : this.getLayerId(layer);
6822 if (this._map && this._layers[id]) {
6823 this._map.removeLayer(this._layers[id]);
6826 delete this._layers[id];
6831 // @method hasLayer(layer: Layer): Boolean
6832 // Returns `true` if the given layer is currently added to the group.
6834 // @method hasLayer(id: Number): Boolean
6835 // Returns `true` if the given internal ID is currently added to the group.
6836 hasLayer: function (layer) {
6837 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
6840 // @method clearLayers(): this
6841 // Removes all the layers from the group.
6842 clearLayers: function () {
6843 return this.eachLayer(this.removeLayer, this);
6846 // @method invoke(methodName: String, …): this
6847 // Calls `methodName` on every layer contained in this group, passing any
6848 // additional parameters. Has no effect if the layers contained do not
6849 // implement `methodName`.
6850 invoke: function (methodName) {
6851 var args = Array.prototype.slice.call(arguments, 1),
6854 for (i in this._layers) {
6855 layer = this._layers[i];
6857 if (layer[methodName]) {
6858 layer[methodName].apply(layer, args);
6865 onAdd: function (map) {
6866 this.eachLayer(map.addLayer, map);
6869 onRemove: function (map) {
6870 this.eachLayer(map.removeLayer, map);
6873 // @method eachLayer(fn: Function, context?: Object): this
6874 // Iterates over the layers of the group, optionally specifying context of the iterator function.
6876 // group.eachLayer(function (layer) {
6877 // layer.bindPopup('Hello');
6880 eachLayer: function (method, context) {
6881 for (var i in this._layers) {
6882 method.call(context, this._layers[i]);
6887 // @method getLayer(id: Number): Layer
6888 // Returns the layer with the given internal ID.
6889 getLayer: function (id) {
6890 return this._layers[id];
6893 // @method getLayers(): Layer[]
6894 // Returns an array of all the layers added to the group.
6895 getLayers: function () {
6897 this.eachLayer(layers.push, layers);
6901 // @method setZIndex(zIndex: Number): this
6902 // Calls `setZIndex` on every layer contained in this group, passing the z-index.
6903 setZIndex: function (zIndex) {
6904 return this.invoke('setZIndex', zIndex);
6907 // @method getLayerId(layer: Layer): Number
6908 // Returns the internal ID for a layer
6909 getLayerId: function (layer) {
6910 return stamp(layer);
6915 // @factory L.layerGroup(layers?: Layer[], options?: Object)
6916 // Create a layer group, optionally given an initial set of layers and an `options` object.
6917 var layerGroup = function (layers, options) {
6918 return new LayerGroup(layers, options);
6922 * @class FeatureGroup
6923 * @aka L.FeatureGroup
6924 * @inherits LayerGroup
6926 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
6927 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
6928 * * Events are propagated to the `FeatureGroup`, so if the group has an event
6929 * handler, it will handle events from any of the layers. This includes mouse events
6930 * and custom events.
6931 * * Has `layeradd` and `layerremove` events
6936 * L.featureGroup([marker1, marker2, polyline])
6937 * .bindPopup('Hello world!')
6938 * .on('click', function() { alert('Clicked on a member of the group!'); })
6943 var FeatureGroup = LayerGroup.extend({
6945 addLayer: function (layer) {
6946 if (this.hasLayer(layer)) {
6950 layer.addEventParent(this);
6952 LayerGroup.prototype.addLayer.call(this, layer);
6954 // @event layeradd: LayerEvent
6955 // Fired when a layer is added to this `FeatureGroup`
6956 return this.fire('layeradd', {layer: layer});
6959 removeLayer: function (layer) {
6960 if (!this.hasLayer(layer)) {
6963 if (layer in this._layers) {
6964 layer = this._layers[layer];
6967 layer.removeEventParent(this);
6969 LayerGroup.prototype.removeLayer.call(this, layer);
6971 // @event layerremove: LayerEvent
6972 // Fired when a layer is removed from this `FeatureGroup`
6973 return this.fire('layerremove', {layer: layer});
6976 // @method setStyle(style: Path options): this
6977 // Sets the given path options to each layer of the group that has a `setStyle` method.
6978 setStyle: function (style) {
6979 return this.invoke('setStyle', style);
6982 // @method bringToFront(): this
6983 // Brings the layer group to the top of all other layers
6984 bringToFront: function () {
6985 return this.invoke('bringToFront');
6988 // @method bringToBack(): this
6989 // Brings the layer group to the back of all other layers
6990 bringToBack: function () {
6991 return this.invoke('bringToBack');
6994 // @method getBounds(): LatLngBounds
6995 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
6996 getBounds: function () {
6997 var bounds = new LatLngBounds();
6999 for (var id in this._layers) {
7000 var layer = this._layers[id];
7001 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
7007 // @factory L.featureGroup(layers: Layer[])
7008 // Create a feature group, optionally given an initial set of layers.
7009 var featureGroup = function (layers) {
7010 return new FeatureGroup(layers);
7017 * Represents an icon to provide when creating a marker.
7022 * var myIcon = L.icon({
7023 * iconUrl: 'my-icon.png',
7024 * iconRetinaUrl: 'my-icon@2x.png',
7025 * iconSize: [38, 95],
7026 * iconAnchor: [22, 94],
7027 * popupAnchor: [-3, -76],
7028 * shadowUrl: 'my-icon-shadow.png',
7029 * shadowRetinaUrl: 'my-icon-shadow@2x.png',
7030 * shadowSize: [68, 95],
7031 * shadowAnchor: [22, 94]
7034 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
7037 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
7041 var Icon = Class.extend({
7046 * @option iconUrl: String = null
7047 * **(required)** The URL to the icon image (absolute or relative to your script path).
7049 * @option iconRetinaUrl: String = null
7050 * The URL to a retina sized version of the icon image (absolute or relative to your
7051 * script path). Used for Retina screen devices.
7053 * @option iconSize: Point = null
7054 * Size of the icon image in pixels.
7056 * @option iconAnchor: Point = null
7057 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
7058 * will be aligned so that this point is at the marker's geographical location. Centered
7059 * by default if size is specified, also can be set in CSS with negative margins.
7061 * @option popupAnchor: Point = [0, 0]
7062 * The coordinates of the point from which popups will "open", relative to the icon anchor.
7064 * @option tooltipAnchor: Point = [0, 0]
7065 * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
7067 * @option shadowUrl: String = null
7068 * The URL to the icon shadow image. If not specified, no shadow image will be created.
7070 * @option shadowRetinaUrl: String = null
7072 * @option shadowSize: Point = null
7073 * Size of the shadow image in pixels.
7075 * @option shadowAnchor: Point = null
7076 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
7077 * as iconAnchor if not specified).
7079 * @option className: String = ''
7080 * A custom class name to assign to both icon and shadow images. Empty by default.
7084 popupAnchor: [0, 0],
7085 tooltipAnchor: [0, 0]
7088 initialize: function (options) {
7089 setOptions(this, options);
7092 // @method createIcon(oldIcon?: HTMLElement): HTMLElement
7093 // Called internally when the icon has to be shown, returns a `<img>` HTML element
7094 // styled according to the options.
7095 createIcon: function (oldIcon) {
7096 return this._createIcon('icon', oldIcon);
7099 // @method createShadow(oldIcon?: HTMLElement): HTMLElement
7100 // As `createIcon`, but for the shadow beneath it.
7101 createShadow: function (oldIcon) {
7102 return this._createIcon('shadow', oldIcon);
7105 _createIcon: function (name, oldIcon) {
7106 var src = this._getIconUrl(name);
7109 if (name === 'icon') {
7110 throw new Error('iconUrl not set in Icon options (see the docs).');
7115 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
7116 this._setIconStyles(img, name);
7121 _setIconStyles: function (img, name) {
7122 var options = this.options;
7123 var sizeOption = options[name + 'Size'];
7125 if (typeof sizeOption === 'number') {
7126 sizeOption = [sizeOption, sizeOption];
7129 var size = toPoint(sizeOption),
7130 anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
7131 size && size.divideBy(2, true));
7133 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
7136 img.style.marginLeft = (-anchor.x) + 'px';
7137 img.style.marginTop = (-anchor.y) + 'px';
7141 img.style.width = size.x + 'px';
7142 img.style.height = size.y + 'px';
7146 _createImg: function (src, el) {
7147 el = el || document.createElement('img');
7152 _getIconUrl: function (name) {
7153 return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
7158 // @factory L.icon(options: Icon options)
7159 // Creates an icon instance with the given options.
7160 function icon(options) {
7161 return new Icon(options);
7165 * @miniclass Icon.Default (Icon)
7166 * @aka L.Icon.Default
7169 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
7170 * no icon is specified. Points to the blue marker image distributed with Leaflet
7173 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
7174 * (which is a set of `Icon options`).
7176 * If you want to _completely_ replace the default icon, override the
7177 * `L.Marker.prototype.options.icon` with your own icon instead.
7180 var IconDefault = Icon.extend({
7183 iconUrl: 'marker-icon.png',
7184 iconRetinaUrl: 'marker-icon-2x.png',
7185 shadowUrl: 'marker-shadow.png',
7187 iconAnchor: [12, 41],
7188 popupAnchor: [1, -34],
7189 tooltipAnchor: [16, -28],
7190 shadowSize: [41, 41]
7193 _getIconUrl: function (name) {
7194 if (!IconDefault.imagePath) { // Deprecated, backwards-compatibility only
7195 IconDefault.imagePath = this._detectIconPath();
7198 // @option imagePath: String
7199 // `Icon.Default` will try to auto-detect the location of the
7200 // blue icon images. If you are placing these images in a non-standard
7201 // way, set this option to point to the right path.
7202 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
7205 _detectIconPath: function () {
7206 var el = create$1('div', 'leaflet-default-icon-path', document.body);
7207 var path = getStyle(el, 'background-image') ||
7208 getStyle(el, 'backgroundImage'); // IE8
7210 document.body.removeChild(el);
7212 if (path === null || path.indexOf('url') !== 0) {
7215 path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, '');
7223 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7227 /* @namespace Marker
7228 * @section Interaction handlers
7230 * 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:
7233 * marker.dragging.disable();
7236 * @property dragging: Handler
7237 * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
7240 var MarkerDrag = Handler.extend({
7241 initialize: function (marker) {
7242 this._marker = marker;
7245 addHooks: function () {
7246 var icon = this._marker._icon;
7248 if (!this._draggable) {
7249 this._draggable = new Draggable(icon, icon, true);
7252 this._draggable.on({
7253 dragstart: this._onDragStart,
7254 predrag: this._onPreDrag,
7256 dragend: this._onDragEnd
7259 addClass(icon, 'leaflet-marker-draggable');
7262 removeHooks: function () {
7263 this._draggable.off({
7264 dragstart: this._onDragStart,
7265 predrag: this._onPreDrag,
7267 dragend: this._onDragEnd
7270 if (this._marker._icon) {
7271 removeClass(this._marker._icon, 'leaflet-marker-draggable');
7275 moved: function () {
7276 return this._draggable && this._draggable._moved;
7279 _adjustPan: function (e) {
7280 var marker = this._marker,
7282 speed = this._marker.options.autoPanSpeed,
7283 padding = this._marker.options.autoPanPadding,
7284 iconPos = getPosition(marker._icon),
7285 bounds = map.getPixelBounds(),
7286 origin = map.getPixelOrigin();
7288 var panBounds = toBounds(
7289 bounds.min._subtract(origin).add(padding),
7290 bounds.max._subtract(origin).subtract(padding)
7293 if (!panBounds.contains(iconPos)) {
7294 // Compute incremental movement
7295 var movement = toPoint(
7296 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
7297 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
7299 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
7300 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
7301 ).multiplyBy(speed);
7303 map.panBy(movement, {animate: false});
7305 this._draggable._newPos._add(movement);
7306 this._draggable._startPos._add(movement);
7308 setPosition(marker._icon, this._draggable._newPos);
7311 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7315 _onDragStart: function () {
7316 // @section Dragging events
7317 // @event dragstart: Event
7318 // Fired when the user starts dragging the marker.
7320 // @event movestart: Event
7321 // Fired when the marker starts moving (because of dragging).
7323 this._oldLatLng = this._marker.getLatLng();
7330 _onPreDrag: function (e) {
7331 if (this._marker.options.autoPan) {
7332 cancelAnimFrame(this._panRequest);
7333 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7337 _onDrag: function (e) {
7338 var marker = this._marker,
7339 shadow = marker._shadow,
7340 iconPos = getPosition(marker._icon),
7341 latlng = marker._map.layerPointToLatLng(iconPos);
7343 // update shadow position
7345 setPosition(shadow, iconPos);
7348 marker._latlng = latlng;
7350 e.oldLatLng = this._oldLatLng;
7352 // @event drag: Event
7353 // Fired repeatedly while the user drags the marker.
7359 _onDragEnd: function (e) {
7360 // @event dragend: DragEndEvent
7361 // Fired when the user stops dragging the marker.
7363 cancelAnimFrame(this._panRequest);
7365 // @event moveend: Event
7366 // Fired when the marker stops moving (because of dragging).
7367 delete this._oldLatLng;
7370 .fire('dragend', e);
7376 * @inherits Interactive layer
7378 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
7383 * L.marker([50.5, 30.5]).addTo(map);
7387 var Marker = Layer.extend({
7390 // @aka Marker options
7392 // @option icon: Icon = *
7393 // Icon instance to use for rendering the marker.
7394 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
7395 // If not specified, a common instance of `L.Icon.Default` is used.
7396 icon: new IconDefault(),
7398 // Option inherited from "Interactive layer" abstract class
7401 // @option keyboard: Boolean = true
7402 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
7405 // @option title: String = ''
7406 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
7409 // @option alt: String = ''
7410 // Text for the `alt` attribute of the icon image (useful for accessibility).
7413 // @option zIndexOffset: Number = 0
7414 // 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).
7417 // @option opacity: Number = 1.0
7418 // The opacity of the marker.
7421 // @option riseOnHover: Boolean = false
7422 // If `true`, the marker will get on top of others when you hover the mouse over it.
7425 // @option riseOffset: Number = 250
7426 // The z-index offset used for the `riseOnHover` feature.
7429 // @option pane: String = 'markerPane'
7430 // `Map pane` where the markers icon will be added.
7433 // @option pane: String = 'shadowPane'
7434 // `Map pane` where the markers shadow will be added.
7435 shadowPane: 'shadowPane',
7437 // @option bubblingMouseEvents: Boolean = false
7438 // When `true`, a mouse event on this marker will trigger the same event on the map
7439 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7440 bubblingMouseEvents: false,
7442 // @section Draggable marker options
7443 // @option draggable: Boolean = false
7444 // Whether the marker is draggable with mouse/touch or not.
7447 // @option autoPan: Boolean = false
7448 // Whether to pan the map when dragging this marker near its edge or not.
7451 // @option autoPanPadding: Point = Point(50, 50)
7452 // Distance (in pixels to the left/right and to the top/bottom) of the
7453 // map edge to start panning the map.
7454 autoPanPadding: [50, 50],
7456 // @option autoPanSpeed: Number = 10
7457 // Number of pixels the map should pan by.
7463 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
7466 initialize: function (latlng, options) {
7467 setOptions(this, options);
7468 this._latlng = toLatLng(latlng);
7471 onAdd: function (map) {
7472 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
7474 if (this._zoomAnimated) {
7475 map.on('zoomanim', this._animateZoom, this);
7482 onRemove: function (map) {
7483 if (this.dragging && this.dragging.enabled()) {
7484 this.options.draggable = true;
7485 this.dragging.removeHooks();
7487 delete this.dragging;
7489 if (this._zoomAnimated) {
7490 map.off('zoomanim', this._animateZoom, this);
7494 this._removeShadow();
7497 getEvents: function () {
7500 viewreset: this.update
7504 // @method getLatLng: LatLng
7505 // Returns the current geographical position of the marker.
7506 getLatLng: function () {
7507 return this._latlng;
7510 // @method setLatLng(latlng: LatLng): this
7511 // Changes the marker position to the given point.
7512 setLatLng: function (latlng) {
7513 var oldLatLng = this._latlng;
7514 this._latlng = toLatLng(latlng);
7517 // @event move: Event
7518 // 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`.
7519 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7522 // @method setZIndexOffset(offset: Number): this
7523 // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
7524 setZIndexOffset: function (offset) {
7525 this.options.zIndexOffset = offset;
7526 return this.update();
7529 // @method getIcon: Icon
7530 // Returns the current icon used by the marker
7531 getIcon: function () {
7532 return this.options.icon;
7535 // @method setIcon(icon: Icon): this
7536 // Changes the marker icon.
7537 setIcon: function (icon) {
7539 this.options.icon = icon;
7547 this.bindPopup(this._popup, this._popup.options);
7553 getElement: function () {
7557 update: function () {
7559 if (this._icon && this._map) {
7560 var pos = this._map.latLngToLayerPoint(this._latlng).round();
7567 _initIcon: function () {
7568 var options = this.options,
7569 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
7571 var icon = options.icon.createIcon(this._icon),
7574 // if we're not reusing the icon, remove the old one and init new one
7575 if (icon !== this._icon) {
7581 if (options.title) {
7582 icon.title = options.title;
7585 if (icon.tagName === 'IMG') {
7586 icon.alt = options.alt || '';
7590 addClass(icon, classToAdd);
7592 if (options.keyboard) {
7593 icon.tabIndex = '0';
7598 if (options.riseOnHover) {
7600 mouseover: this._bringToFront,
7601 mouseout: this._resetZIndex
7605 var newShadow = options.icon.createShadow(this._shadow),
7608 if (newShadow !== this._shadow) {
7609 this._removeShadow();
7614 addClass(newShadow, classToAdd);
7617 this._shadow = newShadow;
7620 if (options.opacity < 1) {
7621 this._updateOpacity();
7626 this.getPane().appendChild(this._icon);
7628 this._initInteraction();
7629 if (newShadow && addShadow) {
7630 this.getPane(options.shadowPane).appendChild(this._shadow);
7634 _removeIcon: function () {
7635 if (this.options.riseOnHover) {
7637 mouseover: this._bringToFront,
7638 mouseout: this._resetZIndex
7643 this.removeInteractiveTarget(this._icon);
7648 _removeShadow: function () {
7650 remove(this._shadow);
7652 this._shadow = null;
7655 _setPos: function (pos) {
7656 setPosition(this._icon, pos);
7659 setPosition(this._shadow, pos);
7662 this._zIndex = pos.y + this.options.zIndexOffset;
7664 this._resetZIndex();
7667 _updateZIndex: function (offset) {
7668 this._icon.style.zIndex = this._zIndex + offset;
7671 _animateZoom: function (opt) {
7672 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
7677 _initInteraction: function () {
7679 if (!this.options.interactive) { return; }
7681 addClass(this._icon, 'leaflet-interactive');
7683 this.addInteractiveTarget(this._icon);
7686 var draggable = this.options.draggable;
7687 if (this.dragging) {
7688 draggable = this.dragging.enabled();
7689 this.dragging.disable();
7692 this.dragging = new MarkerDrag(this);
7695 this.dragging.enable();
7700 // @method setOpacity(opacity: Number): this
7701 // Changes the opacity of the marker.
7702 setOpacity: function (opacity) {
7703 this.options.opacity = opacity;
7705 this._updateOpacity();
7711 _updateOpacity: function () {
7712 var opacity = this.options.opacity;
7715 setOpacity(this._icon, opacity);
7719 setOpacity(this._shadow, opacity);
7723 _bringToFront: function () {
7724 this._updateZIndex(this.options.riseOffset);
7727 _resetZIndex: function () {
7728 this._updateZIndex(0);
7731 _getPopupAnchor: function () {
7732 return this.options.icon.options.popupAnchor;
7735 _getTooltipAnchor: function () {
7736 return this.options.icon.options.tooltipAnchor;
7741 // factory L.marker(latlng: LatLng, options? : Marker options)
7743 // @factory L.marker(latlng: LatLng, options? : Marker options)
7744 // Instantiates a Marker object given a geographical point and optionally an options object.
7745 function marker(latlng, options) {
7746 return new Marker(latlng, options);
7752 * @inherits Interactive layer
7754 * An abstract class that contains options and constants shared between vector
7755 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
7758 var Path = Layer.extend({
7761 // @aka Path options
7763 // @option stroke: Boolean = true
7764 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
7767 // @option color: String = '#3388ff'
7771 // @option weight: Number = 3
7772 // Stroke width in pixels
7775 // @option opacity: Number = 1.0
7779 // @option lineCap: String= 'round'
7780 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
7783 // @option lineJoin: String = 'round'
7784 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
7787 // @option dashArray: String = null
7788 // 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).
7791 // @option dashOffset: String = null
7792 // 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).
7795 // @option fill: Boolean = depends
7796 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
7799 // @option fillColor: String = *
7800 // Fill color. Defaults to the value of the [`color`](#path-color) option
7803 // @option fillOpacity: Number = 0.2
7807 // @option fillRule: String = 'evenodd'
7808 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
7809 fillRule: 'evenodd',
7813 // Option inherited from "Interactive layer" abstract class
7816 // @option bubblingMouseEvents: Boolean = true
7817 // When `true`, a mouse event on this path will trigger the same event on the map
7818 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7819 bubblingMouseEvents: true
7822 beforeAdd: function (map) {
7823 // Renderer is set here because we need to call renderer.getEvents
7824 // before this.getEvents.
7825 this._renderer = map.getRenderer(this);
7828 onAdd: function () {
7829 this._renderer._initPath(this);
7831 this._renderer._addPath(this);
7834 onRemove: function () {
7835 this._renderer._removePath(this);
7838 // @method redraw(): this
7839 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
7840 redraw: function () {
7842 this._renderer._updatePath(this);
7847 // @method setStyle(style: Path options): this
7848 // Changes the appearance of a Path based on the options in the `Path options` object.
7849 setStyle: function (style) {
7850 setOptions(this, style);
7851 if (this._renderer) {
7852 this._renderer._updateStyle(this);
7853 if (this.options.stroke && style.hasOwnProperty('weight')) {
7854 this._updateBounds();
7860 // @method bringToFront(): this
7861 // Brings the layer to the top of all path layers.
7862 bringToFront: function () {
7863 if (this._renderer) {
7864 this._renderer._bringToFront(this);
7869 // @method bringToBack(): this
7870 // Brings the layer to the bottom of all path layers.
7871 bringToBack: function () {
7872 if (this._renderer) {
7873 this._renderer._bringToBack(this);
7878 getElement: function () {
7882 _reset: function () {
7883 // defined in child classes
7888 _clickTolerance: function () {
7889 // used when doing hit detection for Canvas layers
7890 return (this.options.stroke ? this.options.weight / 2 : 0) + this._renderer.options.tolerance;
7895 * @class CircleMarker
7896 * @aka L.CircleMarker
7899 * A circle of a fixed size with radius specified in pixels. Extends `Path`.
7902 var CircleMarker = Path.extend({
7905 // @aka CircleMarker options
7909 // @option radius: Number = 10
7910 // Radius of the circle marker, in pixels
7914 initialize: function (latlng, options) {
7915 setOptions(this, options);
7916 this._latlng = toLatLng(latlng);
7917 this._radius = this.options.radius;
7920 // @method setLatLng(latLng: LatLng): this
7921 // Sets the position of a circle marker to a new location.
7922 setLatLng: function (latlng) {
7923 this._latlng = toLatLng(latlng);
7925 return this.fire('move', {latlng: this._latlng});
7928 // @method getLatLng(): LatLng
7929 // Returns the current geographical position of the circle marker
7930 getLatLng: function () {
7931 return this._latlng;
7934 // @method setRadius(radius: Number): this
7935 // Sets the radius of a circle marker. Units are in pixels.
7936 setRadius: function (radius) {
7937 this.options.radius = this._radius = radius;
7938 return this.redraw();
7941 // @method getRadius(): Number
7942 // Returns the current radius of the circle
7943 getRadius: function () {
7944 return this._radius;
7947 setStyle : function (options) {
7948 var radius = options && options.radius || this._radius;
7949 Path.prototype.setStyle.call(this, options);
7950 this.setRadius(radius);
7954 _project: function () {
7955 this._point = this._map.latLngToLayerPoint(this._latlng);
7956 this._updateBounds();
7959 _updateBounds: function () {
7960 var r = this._radius,
7961 r2 = this._radiusY || r,
7962 w = this._clickTolerance(),
7963 p = [r + w, r2 + w];
7964 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
7967 _update: function () {
7973 _updatePath: function () {
7974 this._renderer._updateCircle(this);
7977 _empty: function () {
7978 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
7981 // Needed by the `Canvas` renderer for interactivity
7982 _containsPoint: function (p) {
7983 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
7988 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
7989 // Instantiates a circle marker object given a geographical point, and an optional options object.
7990 function circleMarker(latlng, options) {
7991 return new CircleMarker(latlng, options);
7997 * @inherits CircleMarker
7999 * A class for drawing circle overlays on a map. Extends `CircleMarker`.
8001 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
8006 * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
8010 var Circle = CircleMarker.extend({
8012 initialize: function (latlng, options, legacyOptions) {
8013 if (typeof options === 'number') {
8014 // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
8015 options = extend({}, legacyOptions, {radius: options});
8017 setOptions(this, options);
8018 this._latlng = toLatLng(latlng);
8020 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
8023 // @aka Circle options
8024 // @option radius: Number; Radius of the circle, in meters.
8025 this._mRadius = this.options.radius;
8028 // @method setRadius(radius: Number): this
8029 // Sets the radius of a circle. Units are in meters.
8030 setRadius: function (radius) {
8031 this._mRadius = radius;
8032 return this.redraw();
8035 // @method getRadius(): Number
8036 // Returns the current radius of a circle. Units are in meters.
8037 getRadius: function () {
8038 return this._mRadius;
8041 // @method getBounds(): LatLngBounds
8042 // Returns the `LatLngBounds` of the path.
8043 getBounds: function () {
8044 var half = [this._radius, this._radiusY || this._radius];
8046 return new LatLngBounds(
8047 this._map.layerPointToLatLng(this._point.subtract(half)),
8048 this._map.layerPointToLatLng(this._point.add(half)));
8051 setStyle: Path.prototype.setStyle,
8053 _project: function () {
8055 var lng = this._latlng.lng,
8056 lat = this._latlng.lat,
8058 crs = map.options.crs;
8060 if (crs.distance === Earth.distance) {
8061 var d = Math.PI / 180,
8062 latR = (this._mRadius / Earth.R) / d,
8063 top = map.project([lat + latR, lng]),
8064 bottom = map.project([lat - latR, lng]),
8065 p = top.add(bottom).divideBy(2),
8066 lat2 = map.unproject(p).lat,
8067 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
8068 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
8070 if (isNaN(lngR) || lngR === 0) {
8071 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
8074 this._point = p.subtract(map.getPixelOrigin());
8075 this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
8076 this._radiusY = p.y - top.y;
8079 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
8081 this._point = map.latLngToLayerPoint(this._latlng);
8082 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
8085 this._updateBounds();
8089 // @factory L.circle(latlng: LatLng, options?: Circle options)
8090 // Instantiates a circle object given a geographical point, and an options object
8091 // which contains the circle radius.
8093 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
8094 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
8095 // Do not use in new applications or plugins.
8096 function circle(latlng, options, legacyOptions) {
8097 return new Circle(latlng, options, legacyOptions);
8105 * A class for drawing polyline overlays on a map. Extends `Path`.
8110 * // create a red polyline from an array of LatLng points
8117 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
8119 * // zoom the map to the polyline
8120 * map.fitBounds(polyline.getBounds());
8123 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
8126 * // create a red polyline from an array of arrays of LatLng points
8128 * [[45.51, -122.68],
8139 var Polyline = Path.extend({
8142 // @aka Polyline options
8144 // @option smoothFactor: Number = 1.0
8145 // How much to simplify the polyline on each zoom level. More means
8146 // better performance and smoother look, and less means more accurate representation.
8149 // @option noClip: Boolean = false
8150 // Disable polyline clipping.
8154 initialize: function (latlngs, options) {
8155 setOptions(this, options);
8156 this._setLatLngs(latlngs);
8159 // @method getLatLngs(): LatLng[]
8160 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8161 getLatLngs: function () {
8162 return this._latlngs;
8165 // @method setLatLngs(latlngs: LatLng[]): this
8166 // Replaces all the points in the polyline with the given array of geographical points.
8167 setLatLngs: function (latlngs) {
8168 this._setLatLngs(latlngs);
8169 return this.redraw();
8172 // @method isEmpty(): Boolean
8173 // Returns `true` if the Polyline has no LatLngs.
8174 isEmpty: function () {
8175 return !this._latlngs.length;
8178 // @method closestLayerPoint(p: Point): Point
8179 // Returns the point closest to `p` on the Polyline.
8180 closestLayerPoint: function (p) {
8181 var minDistance = Infinity,
8183 closest = _sqClosestPointOnSegment,
8186 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8187 var points = this._parts[j];
8189 for (var i = 1, len = points.length; i < len; i++) {
8193 var sqDist = closest(p, p1, p2, true);
8195 if (sqDist < minDistance) {
8196 minDistance = sqDist;
8197 minPoint = closest(p, p1, p2);
8202 minPoint.distance = Math.sqrt(minDistance);
8207 // @method getCenter(): LatLng
8208 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
8209 getCenter: function () {
8210 // throws error when not yet added to map as this center calculation requires projected coordinates
8212 throw new Error('Must add layer to map before using getCenter()');
8215 var i, halfDist, segDist, dist, p1, p2, ratio,
8216 points = this._rings[0],
8217 len = points.length;
8219 if (!len) { return null; }
8221 // polyline centroid algorithm; only uses the first ring if there are multiple
8223 for (i = 0, halfDist = 0; i < len - 1; i++) {
8224 halfDist += points[i].distanceTo(points[i + 1]) / 2;
8227 // The line is so small in the current view that all points are on the same pixel.
8228 if (halfDist === 0) {
8229 return this._map.layerPointToLatLng(points[0]);
8232 for (i = 0, dist = 0; i < len - 1; i++) {
8235 segDist = p1.distanceTo(p2);
8238 if (dist > halfDist) {
8239 ratio = (dist - halfDist) / segDist;
8240 return this._map.layerPointToLatLng([
8241 p2.x - ratio * (p2.x - p1.x),
8242 p2.y - ratio * (p2.y - p1.y)
8248 // @method getBounds(): LatLngBounds
8249 // Returns the `LatLngBounds` of the path.
8250 getBounds: function () {
8251 return this._bounds;
8254 // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
8255 // Adds a given point to the polyline. By default, adds to the first ring of
8256 // the polyline in case of a multi-polyline, but can be overridden by passing
8257 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8258 addLatLng: function (latlng, latlngs) {
8259 latlngs = latlngs || this._defaultShape();
8260 latlng = toLatLng(latlng);
8261 latlngs.push(latlng);
8262 this._bounds.extend(latlng);
8263 return this.redraw();
8266 _setLatLngs: function (latlngs) {
8267 this._bounds = new LatLngBounds();
8268 this._latlngs = this._convertLatLngs(latlngs);
8271 _defaultShape: function () {
8272 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
8275 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8276 _convertLatLngs: function (latlngs) {
8278 flat = isFlat(latlngs);
8280 for (var i = 0, len = latlngs.length; i < len; i++) {
8282 result[i] = toLatLng(latlngs[i]);
8283 this._bounds.extend(result[i]);
8285 result[i] = this._convertLatLngs(latlngs[i]);
8292 _project: function () {
8293 var pxBounds = new Bounds();
8295 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8297 if (this._bounds.isValid() && pxBounds.isValid()) {
8298 this._rawPxBounds = pxBounds;
8299 this._updateBounds();
8303 _updateBounds: function () {
8304 var w = this._clickTolerance(),
8305 p = new Point(w, w);
8306 this._pxBounds = new Bounds([
8307 this._rawPxBounds.min.subtract(p),
8308 this._rawPxBounds.max.add(p)
8312 // recursively turns latlngs into a set of rings with projected coordinates
8313 _projectLatlngs: function (latlngs, result, projectedBounds) {
8314 var flat = latlngs[0] instanceof LatLng,
8315 len = latlngs.length,
8320 for (i = 0; i < len; i++) {
8321 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8322 projectedBounds.extend(ring[i]);
8326 for (i = 0; i < len; i++) {
8327 this._projectLatlngs(latlngs[i], result, projectedBounds);
8332 // clip polyline by renderer bounds so that we have less to render for performance
8333 _clipPoints: function () {
8334 var bounds = this._renderer._bounds;
8337 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8341 if (this.options.noClip) {
8342 this._parts = this._rings;
8346 var parts = this._parts,
8347 i, j, k, len, len2, segment, points;
8349 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8350 points = this._rings[i];
8352 for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8353 segment = clipSegment(points[j], points[j + 1], bounds, j, true);
8355 if (!segment) { continue; }
8357 parts[k] = parts[k] || [];
8358 parts[k].push(segment[0]);
8360 // if segment goes out of screen, or it's the last one, it's the end of the line part
8361 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8362 parts[k].push(segment[1]);
8369 // simplify each clipped part of the polyline for performance
8370 _simplifyPoints: function () {
8371 var parts = this._parts,
8372 tolerance = this.options.smoothFactor;
8374 for (var i = 0, len = parts.length; i < len; i++) {
8375 parts[i] = simplify(parts[i], tolerance);
8379 _update: function () {
8380 if (!this._map) { return; }
8383 this._simplifyPoints();
8387 _updatePath: function () {
8388 this._renderer._updatePoly(this);
8391 // Needed by the `Canvas` renderer for interactivity
8392 _containsPoint: function (p, closed) {
8393 var i, j, k, len, len2, part,
8394 w = this._clickTolerance();
8396 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8398 // hit detection for polylines
8399 for (i = 0, len = this._parts.length; i < len; i++) {
8400 part = this._parts[i];
8402 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8403 if (!closed && (j === 0)) { continue; }
8405 if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
8414 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8415 // Instantiates a polyline object given an array of geographical points and
8416 // optionally an options object. You can create a `Polyline` object with
8417 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8418 // of geographic points.
8419 function polyline(latlngs, options) {
8420 return new Polyline(latlngs, options);
8423 // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
8424 Polyline._flat = _flat;
8429 * @inherits Polyline
8431 * A class for drawing polygon overlays on a map. Extends `Polyline`.
8433 * 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.
8439 * // create a red polygon from an array of LatLng points
8440 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
8442 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8444 * // zoom the map to the polygon
8445 * map.fitBounds(polygon.getBounds());
8448 * 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:
8452 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8453 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8457 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8461 * [ // first polygon
8462 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8463 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8465 * [ // second polygon
8466 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
8472 var Polygon = Polyline.extend({
8478 isEmpty: function () {
8479 return !this._latlngs.length || !this._latlngs[0].length;
8482 getCenter: function () {
8483 // throws error when not yet added to map as this center calculation requires projected coordinates
8485 throw new Error('Must add layer to map before using getCenter()');
8488 var i, j, p1, p2, f, area, x, y, center,
8489 points = this._rings[0],
8490 len = points.length;
8492 if (!len) { return null; }
8494 // polygon centroid algorithm; only uses the first ring if there are multiple
8498 for (i = 0, j = len - 1; i < len; j = i++) {
8502 f = p1.y * p2.x - p2.y * p1.x;
8503 x += (p1.x + p2.x) * f;
8504 y += (p1.y + p2.y) * f;
8509 // Polygon is so small that all points are on same pixel.
8512 center = [x / area, y / area];
8514 return this._map.layerPointToLatLng(center);
8517 _convertLatLngs: function (latlngs) {
8518 var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
8519 len = result.length;
8521 // remove last point if it equals first one
8522 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
8528 _setLatLngs: function (latlngs) {
8529 Polyline.prototype._setLatLngs.call(this, latlngs);
8530 if (isFlat(this._latlngs)) {
8531 this._latlngs = [this._latlngs];
8535 _defaultShape: function () {
8536 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8539 _clipPoints: function () {
8540 // polygons need a different clipping algorithm so we redefine that
8542 var bounds = this._renderer._bounds,
8543 w = this.options.weight,
8544 p = new Point(w, w);
8546 // increase clip padding by stroke width to avoid stroke on clip edges
8547 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
8550 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8554 if (this.options.noClip) {
8555 this._parts = this._rings;
8559 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8560 clipped = clipPolygon(this._rings[i], bounds, true);
8561 if (clipped.length) {
8562 this._parts.push(clipped);
8567 _updatePath: function () {
8568 this._renderer._updatePoly(this, true);
8571 // Needed by the `Canvas` renderer for interactivity
8572 _containsPoint: function (p) {
8574 part, p1, p2, i, j, k, len, len2;
8576 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8578 // ray casting algorithm for detecting if point is in polygon
8579 for (i = 0, len = this._parts.length; i < len; i++) {
8580 part = this._parts[i];
8582 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8586 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)) {
8592 // also check if it's on polygon stroke
8593 return inside || Polyline.prototype._containsPoint.call(this, p, true);
8599 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8600 function polygon(latlngs, options) {
8601 return new Polygon(latlngs, options);
8607 * @inherits FeatureGroup
8609 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
8610 * GeoJSON data and display it on the map. Extends `FeatureGroup`.
8616 * style: function (feature) {
8617 * return {color: feature.properties.color};
8619 * }).bindPopup(function (layer) {
8620 * return layer.feature.properties.description;
8625 var GeoJSON = FeatureGroup.extend({
8628 * @aka GeoJSON options
8630 * @option pointToLayer: Function = *
8631 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
8632 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
8633 * The default is to spawn a default `Marker`:
8635 * function(geoJsonPoint, latlng) {
8636 * return L.marker(latlng);
8640 * @option style: Function = *
8641 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
8642 * called internally when data is added.
8643 * The default value is to not override any defaults:
8645 * function (geoJsonFeature) {
8650 * @option onEachFeature: Function = *
8651 * A `Function` that will be called once for each created `Feature`, after it has
8652 * been created and styled. Useful for attaching events and popups to features.
8653 * The default is to do nothing with the newly created layers:
8655 * function (feature, layer) {}
8658 * @option filter: Function = *
8659 * A `Function` that will be used to decide whether to include a feature or not.
8660 * The default is to include all features:
8662 * function (geoJsonFeature) {
8666 * Note: dynamically changing the `filter` option will have effect only on newly
8667 * added data. It will _not_ re-evaluate already included features.
8669 * @option coordsToLatLng: Function = *
8670 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
8671 * The default is the `coordsToLatLng` static method.
8674 initialize: function (geojson, options) {
8675 setOptions(this, options);
8680 this.addData(geojson);
8684 // @method addData( <GeoJSON> data ): this
8685 // Adds a GeoJSON object to the layer.
8686 addData: function (geojson) {
8687 var features = isArray(geojson) ? geojson : geojson.features,
8691 for (i = 0, len = features.length; i < len; i++) {
8692 // only add this if geometry or geometries are set and not null
8693 feature = features[i];
8694 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
8695 this.addData(feature);
8701 var options = this.options;
8703 if (options.filter && !options.filter(geojson)) { return this; }
8705 var layer = geometryToLayer(geojson, options);
8709 layer.feature = asFeature(geojson);
8711 layer.defaultOptions = layer.options;
8712 this.resetStyle(layer);
8714 if (options.onEachFeature) {
8715 options.onEachFeature(geojson, layer);
8718 return this.addLayer(layer);
8721 // @method resetStyle( <Path> layer ): this
8722 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
8723 resetStyle: function (layer) {
8724 // reset any custom styles
8725 layer.options = extend({}, layer.defaultOptions);
8726 this._setLayerStyle(layer, this.options.style);
8730 // @method setStyle( <Function> style ): this
8731 // Changes styles of GeoJSON vector layers with the given style function.
8732 setStyle: function (style) {
8733 return this.eachLayer(function (layer) {
8734 this._setLayerStyle(layer, style);
8738 _setLayerStyle: function (layer, style) {
8739 if (layer.setStyle) {
8740 if (typeof style === 'function') {
8741 style = style(layer.feature);
8743 layer.setStyle(style);
8749 // There are several static functions which can be called without instantiating L.GeoJSON:
8751 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
8752 // Creates a `Layer` from a given GeoJSON feature. Can use a custom
8753 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
8754 // functions if provided as options.
8755 function geometryToLayer(geojson, options) {
8757 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
8758 coords = geometry ? geometry.coordinates : null,
8760 pointToLayer = options && options.pointToLayer,
8761 _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
8762 latlng, latlngs, i, len;
8764 if (!coords && !geometry) {
8768 switch (geometry.type) {
8770 latlng = _coordsToLatLng(coords);
8771 return pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng);
8774 for (i = 0, len = coords.length; i < len; i++) {
8775 latlng = _coordsToLatLng(coords[i]);
8776 layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng));
8778 return new FeatureGroup(layers);
8781 case 'MultiLineString':
8782 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
8783 return new Polyline(latlngs, options);
8786 case 'MultiPolygon':
8787 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
8788 return new Polygon(latlngs, options);
8790 case 'GeometryCollection':
8791 for (i = 0, len = geometry.geometries.length; i < len; i++) {
8792 var layer = geometryToLayer({
8793 geometry: geometry.geometries[i],
8795 properties: geojson.properties
8802 return new FeatureGroup(layers);
8805 throw new Error('Invalid GeoJSON object.');
8809 // @function coordsToLatLng(coords: Array): LatLng
8810 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
8811 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
8812 function coordsToLatLng(coords) {
8813 return new LatLng(coords[1], coords[0], coords[2]);
8816 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
8817 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
8818 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
8819 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
8820 function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
8823 for (var i = 0, len = coords.length, latlng; i < len; i++) {
8824 latlng = levelsDeep ?
8825 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
8826 (_coordsToLatLng || coordsToLatLng)(coords[i]);
8828 latlngs.push(latlng);
8834 // @function latLngToCoords(latlng: LatLng, precision?: Number): Array
8835 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
8836 function latLngToCoords(latlng, precision) {
8837 precision = typeof precision === 'number' ? precision : 6;
8838 return latlng.alt !== undefined ?
8839 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
8840 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
8843 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
8844 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
8845 // `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.
8846 function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
8849 for (var i = 0, len = latlngs.length; i < len; i++) {
8850 coords.push(levelsDeep ?
8851 latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) :
8852 latLngToCoords(latlngs[i], precision));
8855 if (!levelsDeep && closed) {
8856 coords.push(coords[0]);
8862 function getFeature(layer, newGeometry) {
8863 return layer.feature ?
8864 extend({}, layer.feature, {geometry: newGeometry}) :
8865 asFeature(newGeometry);
8868 // @function asFeature(geojson: Object): Object
8869 // Normalize GeoJSON geometries/features into GeoJSON features.
8870 function asFeature(geojson) {
8871 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
8882 var PointToGeoJSON = {
8883 toGeoJSON: function (precision) {
8884 return getFeature(this, {
8886 coordinates: latLngToCoords(this.getLatLng(), precision)
8891 // @namespace Marker
8892 // @method toGeoJSON(precision?: Number): Object
8893 // `precision` is the number of decimal places for coordinates.
8894 // The default value is 6 places.
8895 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
8896 Marker.include(PointToGeoJSON);
8898 // @namespace CircleMarker
8899 // @method toGeoJSON(precision?: Number): Object
8900 // `precision` is the number of decimal places for coordinates.
8901 // The default value is 6 places.
8902 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
8903 Circle.include(PointToGeoJSON);
8904 CircleMarker.include(PointToGeoJSON);
8907 // @namespace Polyline
8908 // @method toGeoJSON(precision?: Number): Object
8909 // `precision` is the number of decimal places for coordinates.
8910 // The default value is 6 places.
8911 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
8913 toGeoJSON: function (precision) {
8914 var multi = !isFlat(this._latlngs);
8916 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
8918 return getFeature(this, {
8919 type: (multi ? 'Multi' : '') + 'LineString',
8925 // @namespace Polygon
8926 // @method toGeoJSON(precision?: Number): Object
8927 // `precision` is the number of decimal places for coordinates.
8928 // The default value is 6 places.
8929 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
8931 toGeoJSON: function (precision) {
8932 var holes = !isFlat(this._latlngs),
8933 multi = holes && !isFlat(this._latlngs[0]);
8935 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
8941 return getFeature(this, {
8942 type: (multi ? 'Multi' : '') + 'Polygon',
8949 // @namespace LayerGroup
8950 LayerGroup.include({
8951 toMultiPoint: function (precision) {
8954 this.eachLayer(function (layer) {
8955 coords.push(layer.toGeoJSON(precision).geometry.coordinates);
8958 return getFeature(this, {
8964 // @method toGeoJSON(precision?: Number): Object
8965 // `precision` is the number of decimal places for coordinates.
8966 // The default value is 6 places.
8967 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
8968 toGeoJSON: function (precision) {
8970 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
8972 if (type === 'MultiPoint') {
8973 return this.toMultiPoint(precision);
8976 var isGeometryCollection = type === 'GeometryCollection',
8979 this.eachLayer(function (layer) {
8980 if (layer.toGeoJSON) {
8981 var json = layer.toGeoJSON(precision);
8982 if (isGeometryCollection) {
8983 jsons.push(json.geometry);
8985 var feature = asFeature(json);
8986 // Squash nested feature collections
8987 if (feature.type === 'FeatureCollection') {
8988 jsons.push.apply(jsons, feature.features);
8990 jsons.push(feature);
8996 if (isGeometryCollection) {
8997 return getFeature(this, {
8999 type: 'GeometryCollection'
9004 type: 'FeatureCollection',
9010 // @namespace GeoJSON
9011 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
9012 // Creates a GeoJSON layer. Optionally accepts an object in
9013 // [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
9014 // (you can alternatively add it later with `addData` method) and an `options` object.
9015 function geoJSON(geojson, options) {
9016 return new GeoJSON(geojson, options);
9019 // Backward compatibility.
9020 var geoJson = geoJSON;
9023 * @class ImageOverlay
9024 * @aka L.ImageOverlay
9025 * @inherits Interactive layer
9027 * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
9032 * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
9033 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
9034 * L.imageOverlay(imageUrl, imageBounds).addTo(map);
9038 var ImageOverlay = Layer.extend({
9041 // @aka ImageOverlay options
9043 // @option opacity: Number = 1.0
9044 // The opacity of the image overlay.
9047 // @option alt: String = ''
9048 // Text for the `alt` attribute of the image (useful for accessibility).
9051 // @option interactive: Boolean = false
9052 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
9055 // @option crossOrigin: Boolean|String = false
9056 // Whether the crossOrigin attribute will be added to the image.
9057 // 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.
9058 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
9061 // @option errorOverlayUrl: String = ''
9062 // URL to the overlay image to show in place of the overlay that failed to load.
9063 errorOverlayUrl: '',
9065 // @option zIndex: Number = 1
9066 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
9069 // @option className: String = ''
9070 // A custom class name to assign to the image. Empty by default.
9074 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
9076 this._bounds = toLatLngBounds(bounds);
9078 setOptions(this, options);
9081 onAdd: function () {
9085 if (this.options.opacity < 1) {
9086 this._updateOpacity();
9090 if (this.options.interactive) {
9091 addClass(this._image, 'leaflet-interactive');
9092 this.addInteractiveTarget(this._image);
9095 this.getPane().appendChild(this._image);
9099 onRemove: function () {
9100 remove(this._image);
9101 if (this.options.interactive) {
9102 this.removeInteractiveTarget(this._image);
9106 // @method setOpacity(opacity: Number): this
9107 // Sets the opacity of the overlay.
9108 setOpacity: function (opacity) {
9109 this.options.opacity = opacity;
9112 this._updateOpacity();
9117 setStyle: function (styleOpts) {
9118 if (styleOpts.opacity) {
9119 this.setOpacity(styleOpts.opacity);
9124 // @method bringToFront(): this
9125 // Brings the layer to the top of all overlays.
9126 bringToFront: function () {
9128 toFront(this._image);
9133 // @method bringToBack(): this
9134 // Brings the layer to the bottom of all overlays.
9135 bringToBack: function () {
9137 toBack(this._image);
9142 // @method setUrl(url: String): this
9143 // Changes the URL of the image.
9144 setUrl: function (url) {
9148 this._image.src = url;
9153 // @method setBounds(bounds: LatLngBounds): this
9154 // Update the bounds that this ImageOverlay covers
9155 setBounds: function (bounds) {
9156 this._bounds = toLatLngBounds(bounds);
9164 getEvents: function () {
9167 viewreset: this._reset
9170 if (this._zoomAnimated) {
9171 events.zoomanim = this._animateZoom;
9177 // @method setZIndex(value: Number): this
9178 // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
9179 setZIndex: function (value) {
9180 this.options.zIndex = value;
9181 this._updateZIndex();
9185 // @method getBounds(): LatLngBounds
9186 // Get the bounds that this ImageOverlay covers
9187 getBounds: function () {
9188 return this._bounds;
9191 // @method getElement(): HTMLElement
9192 // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
9193 // used by this overlay.
9194 getElement: function () {
9198 _initImage: function () {
9199 var wasElementSupplied = this._url.tagName === 'IMG';
9200 var img = this._image = wasElementSupplied ? this._url : create$1('img');
9202 addClass(img, 'leaflet-image-layer');
9203 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
9204 if (this.options.className) { addClass(img, this.options.className); }
9206 img.onselectstart = falseFn;
9207 img.onmousemove = falseFn;
9209 // @event load: Event
9210 // Fired when the ImageOverlay layer has loaded its image
9211 img.onload = bind(this.fire, this, 'load');
9212 img.onerror = bind(this._overlayOnError, this, 'error');
9214 if (this.options.crossOrigin || this.options.crossOrigin === '') {
9215 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
9218 if (this.options.zIndex) {
9219 this._updateZIndex();
9222 if (wasElementSupplied) {
9223 this._url = img.src;
9227 img.src = this._url;
9228 img.alt = this.options.alt;
9231 _animateZoom: function (e) {
9232 var scale = this._map.getZoomScale(e.zoom),
9233 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
9235 setTransform(this._image, offset, scale);
9238 _reset: function () {
9239 var image = this._image,
9240 bounds = new Bounds(
9241 this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
9242 this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
9243 size = bounds.getSize();
9245 setPosition(image, bounds.min);
9247 image.style.width = size.x + 'px';
9248 image.style.height = size.y + 'px';
9251 _updateOpacity: function () {
9252 setOpacity(this._image, this.options.opacity);
9255 _updateZIndex: function () {
9256 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
9257 this._image.style.zIndex = this.options.zIndex;
9261 _overlayOnError: function () {
9262 // @event error: Event
9263 // Fired when the ImageOverlay layer fails to load its image
9266 var errorUrl = this.options.errorOverlayUrl;
9267 if (errorUrl && this._url !== errorUrl) {
9268 this._url = errorUrl;
9269 this._image.src = errorUrl;
9274 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
9275 // Instantiates an image overlay object given the URL of the image and the
9276 // geographical bounds it is tied to.
9277 var imageOverlay = function (url, bounds, options) {
9278 return new ImageOverlay(url, bounds, options);
9282 * @class VideoOverlay
9283 * @aka L.VideoOverlay
9284 * @inherits ImageOverlay
9286 * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
9288 * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
9294 * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
9295 * videoBounds = [[ 32, -130], [ 13, -100]];
9296 * L.videoOverlay(videoUrl, videoBounds ).addTo(map);
9300 var VideoOverlay = ImageOverlay.extend({
9303 // @aka VideoOverlay options
9305 // @option autoplay: Boolean = true
9306 // Whether the video starts playing automatically when loaded.
9309 // @option loop: Boolean = true
9310 // Whether the video will loop back to the beginning when played.
9313 // @option keepAspectRatio: Boolean = true
9314 // Whether the video will save aspect ratio after the projection.
9315 // Relevant for supported browsers. Browser compatibility- https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit
9316 keepAspectRatio: true
9319 _initImage: function () {
9320 var wasElementSupplied = this._url.tagName === 'VIDEO';
9321 var vid = this._image = wasElementSupplied ? this._url : create$1('video');
9323 addClass(vid, 'leaflet-image-layer');
9324 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
9326 vid.onselectstart = falseFn;
9327 vid.onmousemove = falseFn;
9329 // @event load: Event
9330 // Fired when the video has finished loading the first frame
9331 vid.onloadeddata = bind(this.fire, this, 'load');
9333 if (wasElementSupplied) {
9334 var sourceElements = vid.getElementsByTagName('source');
9336 for (var j = 0; j < sourceElements.length; j++) {
9337 sources.push(sourceElements[j].src);
9340 this._url = (sourceElements.length > 0) ? sources : [vid.src];
9344 if (!isArray(this._url)) { this._url = [this._url]; }
9346 if (!this.options.keepAspectRatio && vid.style.hasOwnProperty('objectFit')) { vid.style['objectFit'] = 'fill'; }
9347 vid.autoplay = !!this.options.autoplay;
9348 vid.loop = !!this.options.loop;
9349 for (var i = 0; i < this._url.length; i++) {
9350 var source = create$1('source');
9351 source.src = this._url[i];
9352 vid.appendChild(source);
9356 // @method getElement(): HTMLVideoElement
9357 // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
9358 // used by this overlay.
9362 // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
9363 // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
9364 // geographical bounds it is tied to.
9366 function videoOverlay(video, bounds, options) {
9367 return new VideoOverlay(video, bounds, options);
9373 * @inherits ImageOverlay
9375 * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`.
9377 * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element.
9382 * var element = '<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><image xlink:href="https://mdn.mozillademos.org/files/6457/mdn_logo_only_color.png" height="200" width="200"/></svg>',
9383 * elementBounds = [ [ 32, -130 ], [ 13, -100 ] ];
9384 * L.svgOverlay(element, elementBounds).addTo(map);
9388 var SVGOverlay = ImageOverlay.extend({
9389 _initImage: function () {
9390 var el = this._image = this._url;
9392 addClass(el, 'leaflet-image-layer');
9393 if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); }
9395 el.onselectstart = falseFn;
9396 el.onmousemove = falseFn;
9399 // @method getElement(): SVGElement
9400 // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement)
9401 // used by this overlay.
9405 // @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options)
9406 // Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to.
9407 // A viewBox attribute is required on the SVG element to zoom in and out properly.
9409 function svgOverlay(el, bounds, options) {
9410 return new SVGOverlay(el, bounds, options);
9417 * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
9420 // @namespace DivOverlay
9421 var DivOverlay = Layer.extend({
9424 // @aka DivOverlay options
9426 // @option offset: Point = Point(0, 7)
9427 // The offset of the popup position. Useful to control the anchor
9428 // of the popup when opening it on some overlays.
9431 // @option className: String = ''
9432 // A custom CSS class name to assign to the popup.
9435 // @option pane: String = 'popupPane'
9436 // `Map pane` where the popup will be added.
9440 initialize: function (options, source) {
9441 setOptions(this, options);
9443 this._source = source;
9446 onAdd: function (map) {
9447 this._zoomAnimated = map._zoomAnimated;
9449 if (!this._container) {
9453 if (map._fadeAnimated) {
9454 setOpacity(this._container, 0);
9457 clearTimeout(this._removeTimeout);
9458 this.getPane().appendChild(this._container);
9461 if (map._fadeAnimated) {
9462 setOpacity(this._container, 1);
9465 this.bringToFront();
9468 onRemove: function (map) {
9469 if (map._fadeAnimated) {
9470 setOpacity(this._container, 0);
9471 this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
9473 remove(this._container);
9478 // @method getLatLng: LatLng
9479 // Returns the geographical point of popup.
9480 getLatLng: function () {
9481 return this._latlng;
9484 // @method setLatLng(latlng: LatLng): this
9485 // Sets the geographical point where the popup will open.
9486 setLatLng: function (latlng) {
9487 this._latlng = toLatLng(latlng);
9489 this._updatePosition();
9495 // @method getContent: String|HTMLElement
9496 // Returns the content of the popup.
9497 getContent: function () {
9498 return this._content;
9501 // @method setContent(htmlContent: String|HTMLElement|Function): this
9502 // 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.
9503 setContent: function (content) {
9504 this._content = content;
9509 // @method getElement: String|HTMLElement
9510 // Alias for [getContent()](#popup-getcontent)
9511 getElement: function () {
9512 return this._container;
9515 // @method update: null
9516 // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
9517 update: function () {
9518 if (!this._map) { return; }
9520 this._container.style.visibility = 'hidden';
9522 this._updateContent();
9523 this._updateLayout();
9524 this._updatePosition();
9526 this._container.style.visibility = '';
9531 getEvents: function () {
9533 zoom: this._updatePosition,
9534 viewreset: this._updatePosition
9537 if (this._zoomAnimated) {
9538 events.zoomanim = this._animateZoom;
9543 // @method isOpen: Boolean
9544 // Returns `true` when the popup is visible on the map.
9545 isOpen: function () {
9546 return !!this._map && this._map.hasLayer(this);
9549 // @method bringToFront: this
9550 // Brings this popup in front of other popups (in the same map pane).
9551 bringToFront: function () {
9553 toFront(this._container);
9558 // @method bringToBack: this
9559 // Brings this popup to the back of other popups (in the same map pane).
9560 bringToBack: function () {
9562 toBack(this._container);
9567 _prepareOpen: function (parent, layer, latlng) {
9568 if (!(layer instanceof Layer)) {
9573 if (layer instanceof FeatureGroup) {
9574 for (var id in parent._layers) {
9575 layer = parent._layers[id];
9581 if (layer.getCenter) {
9582 latlng = layer.getCenter();
9583 } else if (layer.getLatLng) {
9584 latlng = layer.getLatLng();
9586 throw new Error('Unable to get source layer LatLng.');
9590 // set overlay source to this layer
9591 this._source = layer;
9593 // update the overlay (content, layout, ect...)
9599 _updateContent: function () {
9600 if (!this._content) { return; }
9602 var node = this._contentNode;
9603 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
9605 if (typeof content === 'string') {
9606 node.innerHTML = content;
9608 while (node.hasChildNodes()) {
9609 node.removeChild(node.firstChild);
9611 node.appendChild(content);
9613 this.fire('contentupdate');
9616 _updatePosition: function () {
9617 if (!this._map) { return; }
9619 var pos = this._map.latLngToLayerPoint(this._latlng),
9620 offset = toPoint(this.options.offset),
9621 anchor = this._getAnchor();
9623 if (this._zoomAnimated) {
9624 setPosition(this._container, pos.add(anchor));
9626 offset = offset.add(pos).add(anchor);
9629 var bottom = this._containerBottom = -offset.y,
9630 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
9632 // bottom position the popup in case the height of the popup changes (images loading etc)
9633 this._container.style.bottom = bottom + 'px';
9634 this._container.style.left = left + 'px';
9637 _getAnchor: function () {
9645 * @inherits DivOverlay
9647 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
9648 * open popups while making sure that only one popup is open at one time
9649 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
9653 * If you want to just bind a popup to marker click and then open it, it's really easy:
9656 * marker.bindPopup(popupContent).openPopup();
9658 * Path overlays like polylines also have a `bindPopup` method.
9659 * Here's a more complicated way to open a popup on a map:
9662 * var popup = L.popup()
9663 * .setLatLng(latlng)
9664 * .setContent('<p>Hello world!<br />This is a nice popup.</p>')
9671 var Popup = DivOverlay.extend({
9674 // @aka Popup options
9676 // @option maxWidth: Number = 300
9677 // Max width of the popup, in pixels.
9680 // @option minWidth: Number = 50
9681 // Min width of the popup, in pixels.
9684 // @option maxHeight: Number = null
9685 // If set, creates a scrollable container of the given height
9686 // inside a popup if its content exceeds it.
9689 // @option autoPan: Boolean = true
9690 // Set it to `false` if you don't want the map to do panning animation
9691 // to fit the opened popup.
9694 // @option autoPanPaddingTopLeft: Point = null
9695 // The margin between the popup and the top left corner of the map
9696 // view after autopanning was performed.
9697 autoPanPaddingTopLeft: null,
9699 // @option autoPanPaddingBottomRight: Point = null
9700 // The margin between the popup and the bottom right corner of the map
9701 // view after autopanning was performed.
9702 autoPanPaddingBottomRight: null,
9704 // @option autoPanPadding: Point = Point(5, 5)
9705 // Equivalent of setting both top left and bottom right autopan padding to the same value.
9706 autoPanPadding: [5, 5],
9708 // @option keepInView: Boolean = false
9709 // Set it to `true` if you want to prevent users from panning the popup
9710 // off of the screen while it is open.
9713 // @option closeButton: Boolean = true
9714 // Controls the presence of a close button in the popup.
9717 // @option autoClose: Boolean = true
9718 // Set it to `false` if you want to override the default behavior of
9719 // the popup closing when another popup is opened.
9722 // @option closeOnEscapeKey: Boolean = true
9723 // Set it to `false` if you want to override the default behavior of
9724 // the ESC key for closing of the popup.
9725 closeOnEscapeKey: true,
9727 // @option closeOnClick: Boolean = *
9728 // Set it if you want to override the default behavior of the popup closing when user clicks
9729 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
9731 // @option className: String = ''
9732 // A custom CSS class name to assign to the popup.
9737 // @method openOn(map: Map): this
9738 // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
9739 openOn: function (map) {
9740 map.openPopup(this);
9744 onAdd: function (map) {
9745 DivOverlay.prototype.onAdd.call(this, map);
9748 // @section Popup events
9749 // @event popupopen: PopupEvent
9750 // Fired when a popup is opened in the map
9751 map.fire('popupopen', {popup: this});
9755 // @section Popup events
9756 // @event popupopen: PopupEvent
9757 // Fired when a popup bound to this layer is opened
9758 this._source.fire('popupopen', {popup: this}, true);
9759 // For non-path layers, we toggle the popup when clicking
9760 // again the layer, so prevent the map to reopen it.
9761 if (!(this._source instanceof Path)) {
9762 this._source.on('preclick', stopPropagation);
9767 onRemove: function (map) {
9768 DivOverlay.prototype.onRemove.call(this, map);
9771 // @section Popup events
9772 // @event popupclose: PopupEvent
9773 // Fired when a popup in the map is closed
9774 map.fire('popupclose', {popup: this});
9778 // @section Popup events
9779 // @event popupclose: PopupEvent
9780 // Fired when a popup bound to this layer is closed
9781 this._source.fire('popupclose', {popup: this}, true);
9782 if (!(this._source instanceof Path)) {
9783 this._source.off('preclick', stopPropagation);
9788 getEvents: function () {
9789 var events = DivOverlay.prototype.getEvents.call(this);
9791 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
9792 events.preclick = this._close;
9795 if (this.options.keepInView) {
9796 events.moveend = this._adjustPan;
9802 _close: function () {
9804 this._map.closePopup(this);
9808 _initLayout: function () {
9809 var prefix = 'leaflet-popup',
9810 container = this._container = create$1('div',
9811 prefix + ' ' + (this.options.className || '') +
9812 ' leaflet-zoom-animated');
9814 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
9815 this._contentNode = create$1('div', prefix + '-content', wrapper);
9817 disableClickPropagation(wrapper);
9818 disableScrollPropagation(this._contentNode);
9819 on(wrapper, 'contextmenu', stopPropagation);
9821 this._tipContainer = create$1('div', prefix + '-tip-container', container);
9822 this._tip = create$1('div', prefix + '-tip', this._tipContainer);
9824 if (this.options.closeButton) {
9825 var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
9826 closeButton.href = '#close';
9827 closeButton.innerHTML = '×';
9829 on(closeButton, 'click', this._onCloseButtonClick, this);
9833 _updateLayout: function () {
9834 var container = this._contentNode,
9835 style = container.style;
9838 style.whiteSpace = 'nowrap';
9840 var width = container.offsetWidth;
9841 width = Math.min(width, this.options.maxWidth);
9842 width = Math.max(width, this.options.minWidth);
9844 style.width = (width + 1) + 'px';
9845 style.whiteSpace = '';
9849 var height = container.offsetHeight,
9850 maxHeight = this.options.maxHeight,
9851 scrolledClass = 'leaflet-popup-scrolled';
9853 if (maxHeight && height > maxHeight) {
9854 style.height = maxHeight + 'px';
9855 addClass(container, scrolledClass);
9857 removeClass(container, scrolledClass);
9860 this._containerWidth = this._container.offsetWidth;
9863 _animateZoom: function (e) {
9864 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
9865 anchor = this._getAnchor();
9866 setPosition(this._container, pos.add(anchor));
9869 _adjustPan: function () {
9870 if (!this.options.autoPan) { return; }
9871 if (this._map._panAnim) { this._map._panAnim.stop(); }
9873 var map = this._map,
9874 marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
9875 containerHeight = this._container.offsetHeight + marginBottom,
9876 containerWidth = this._containerWidth,
9877 layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
9879 layerPos._add(getPosition(this._container));
9881 var containerPos = map.layerPointToContainerPoint(layerPos),
9882 padding = toPoint(this.options.autoPanPadding),
9883 paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
9884 paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
9885 size = map.getSize(),
9889 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
9890 dx = containerPos.x + containerWidth - size.x + paddingBR.x;
9892 if (containerPos.x - dx - paddingTL.x < 0) { // left
9893 dx = containerPos.x - paddingTL.x;
9895 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
9896 dy = containerPos.y + containerHeight - size.y + paddingBR.y;
9898 if (containerPos.y - dy - paddingTL.y < 0) { // top
9899 dy = containerPos.y - paddingTL.y;
9903 // @section Popup events
9904 // @event autopanstart: Event
9905 // Fired when the map starts autopanning when opening a popup.
9908 .fire('autopanstart')
9913 _onCloseButtonClick: function (e) {
9918 _getAnchor: function () {
9919 // Where should we anchor the popup on the source layer?
9920 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
9926 // @factory L.popup(options?: Popup options, source?: Layer)
9927 // 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.
9928 var popup = function (options, source) {
9929 return new Popup(options, source);
9934 * @section Interaction Options
9935 * @option closePopupOnClick: Boolean = true
9936 * Set it to `false` if you don't want popups to close when user clicks the map.
9939 closePopupOnClick: true
9944 // @section Methods for Layers and Controls
9946 // @method openPopup(popup: Popup): this
9947 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
9949 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
9950 // Creates a popup with the specified content and options and opens it in the given point on a map.
9951 openPopup: function (popup, latlng, options) {
9952 if (!(popup instanceof Popup)) {
9953 popup = new Popup(options).setContent(popup);
9957 popup.setLatLng(latlng);
9960 if (this.hasLayer(popup)) {
9964 if (this._popup && this._popup.options.autoClose) {
9968 this._popup = popup;
9969 return this.addLayer(popup);
9972 // @method closePopup(popup?: Popup): this
9973 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
9974 closePopup: function (popup) {
9975 if (!popup || popup === this._popup) {
9976 popup = this._popup;
9980 this.removeLayer(popup);
9988 * @section Popup methods example
9990 * All layers share a set of methods convenient for binding popups to it.
9993 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
9994 * layer.openPopup();
9995 * layer.closePopup();
9998 * 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.
10001 // @section Popup methods
10004 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
10005 // Binds a popup to the layer with the passed `content` and sets up the
10006 // necessary event listeners. If a `Function` is passed it will receive
10007 // the layer as the first argument and should return a `String` or `HTMLElement`.
10008 bindPopup: function (content, options) {
10010 if (content instanceof Popup) {
10011 setOptions(content, options);
10012 this._popup = content;
10013 content._source = this;
10015 if (!this._popup || options) {
10016 this._popup = new Popup(options, this);
10018 this._popup.setContent(content);
10021 if (!this._popupHandlersAdded) {
10023 click: this._openPopup,
10024 keypress: this._onKeyPress,
10025 remove: this.closePopup,
10026 move: this._movePopup
10028 this._popupHandlersAdded = true;
10034 // @method unbindPopup(): this
10035 // Removes the popup previously bound with `bindPopup`.
10036 unbindPopup: function () {
10039 click: this._openPopup,
10040 keypress: this._onKeyPress,
10041 remove: this.closePopup,
10042 move: this._movePopup
10044 this._popupHandlersAdded = false;
10045 this._popup = null;
10050 // @method openPopup(latlng?: LatLng): this
10051 // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
10052 openPopup: function (layer, latlng) {
10053 if (this._popup && this._map) {
10054 latlng = this._popup._prepareOpen(this, layer, latlng);
10056 // open the popup on the map
10057 this._map.openPopup(this._popup, latlng);
10063 // @method closePopup(): this
10064 // Closes the popup bound to this layer if it is open.
10065 closePopup: function () {
10067 this._popup._close();
10072 // @method togglePopup(): this
10073 // Opens or closes the popup bound to this layer depending on its current state.
10074 togglePopup: function (target) {
10076 if (this._popup._map) {
10079 this.openPopup(target);
10085 // @method isPopupOpen(): boolean
10086 // Returns `true` if the popup bound to this layer is currently open.
10087 isPopupOpen: function () {
10088 return (this._popup ? this._popup.isOpen() : false);
10091 // @method setPopupContent(content: String|HTMLElement|Popup): this
10092 // Sets the content of the popup bound to this layer.
10093 setPopupContent: function (content) {
10095 this._popup.setContent(content);
10100 // @method getPopup(): Popup
10101 // Returns the popup bound to this layer.
10102 getPopup: function () {
10103 return this._popup;
10106 _openPopup: function (e) {
10107 var layer = e.layer || e.target;
10109 if (!this._popup) {
10117 // prevent map click
10120 // if this inherits from Path its a vector and we can just
10121 // open the popup at the new location
10122 if (layer instanceof Path) {
10123 this.openPopup(e.layer || e.target, e.latlng);
10127 // otherwise treat it like a marker and figure out
10128 // if we should toggle it open/closed
10129 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
10132 this.openPopup(layer, e.latlng);
10136 _movePopup: function (e) {
10137 this._popup.setLatLng(e.latlng);
10140 _onKeyPress: function (e) {
10141 if (e.originalEvent.keyCode === 13) {
10142 this._openPopup(e);
10149 * @inherits DivOverlay
10151 * Used to display small texts on top of map layers.
10156 * marker.bindTooltip("my tooltip text").openTooltip();
10158 * Note about tooltip offset. Leaflet takes two options in consideration
10159 * for computing tooltip offsetting:
10160 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
10161 * Add a positive x offset to move the tooltip to the right, and a positive y offset to
10162 * move it to the bottom. Negatives will move to the left and top.
10163 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
10164 * should adapt this value if you use a custom icon.
10168 // @namespace Tooltip
10169 var Tooltip = DivOverlay.extend({
10172 // @aka Tooltip options
10174 // @option pane: String = 'tooltipPane'
10175 // `Map pane` where the tooltip will be added.
10176 pane: 'tooltipPane',
10178 // @option offset: Point = Point(0, 0)
10179 // Optional offset of the tooltip position.
10182 // @option direction: String = 'auto'
10183 // Direction where to open the tooltip. Possible values are: `right`, `left`,
10184 // `top`, `bottom`, `center`, `auto`.
10185 // `auto` will dynamically switch between `right` and `left` according to the tooltip
10186 // position on the map.
10189 // @option permanent: Boolean = false
10190 // Whether to open the tooltip permanently or only on mouseover.
10193 // @option sticky: Boolean = false
10194 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
10197 // @option interactive: Boolean = false
10198 // If true, the tooltip will listen to the feature events.
10199 interactive: false,
10201 // @option opacity: Number = 0.9
10202 // Tooltip container opacity.
10206 onAdd: function (map) {
10207 DivOverlay.prototype.onAdd.call(this, map);
10208 this.setOpacity(this.options.opacity);
10211 // @section Tooltip events
10212 // @event tooltipopen: TooltipEvent
10213 // Fired when a tooltip is opened in the map.
10214 map.fire('tooltipopen', {tooltip: this});
10216 if (this._source) {
10217 // @namespace Layer
10218 // @section Tooltip events
10219 // @event tooltipopen: TooltipEvent
10220 // Fired when a tooltip bound to this layer is opened.
10221 this._source.fire('tooltipopen', {tooltip: this}, true);
10225 onRemove: function (map) {
10226 DivOverlay.prototype.onRemove.call(this, map);
10229 // @section Tooltip events
10230 // @event tooltipclose: TooltipEvent
10231 // Fired when a tooltip in the map is closed.
10232 map.fire('tooltipclose', {tooltip: this});
10234 if (this._source) {
10235 // @namespace Layer
10236 // @section Tooltip events
10237 // @event tooltipclose: TooltipEvent
10238 // Fired when a tooltip bound to this layer is closed.
10239 this._source.fire('tooltipclose', {tooltip: this}, true);
10243 getEvents: function () {
10244 var events = DivOverlay.prototype.getEvents.call(this);
10246 if (touch && !this.options.permanent) {
10247 events.preclick = this._close;
10253 _close: function () {
10255 this._map.closeTooltip(this);
10259 _initLayout: function () {
10260 var prefix = 'leaflet-tooltip',
10261 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
10263 this._contentNode = this._container = create$1('div', className);
10266 _updateLayout: function () {},
10268 _adjustPan: function () {},
10270 _setPosition: function (pos) {
10271 var map = this._map,
10272 container = this._container,
10273 centerPoint = map.latLngToContainerPoint(map.getCenter()),
10274 tooltipPoint = map.layerPointToContainerPoint(pos),
10275 direction = this.options.direction,
10276 tooltipWidth = container.offsetWidth,
10277 tooltipHeight = container.offsetHeight,
10278 offset = toPoint(this.options.offset),
10279 anchor = this._getAnchor();
10281 if (direction === 'top') {
10282 pos = pos.add(toPoint(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true));
10283 } else if (direction === 'bottom') {
10284 pos = pos.subtract(toPoint(tooltipWidth / 2 - offset.x, -offset.y, true));
10285 } else if (direction === 'center') {
10286 pos = pos.subtract(toPoint(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true));
10287 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
10288 direction = 'right';
10289 pos = pos.add(toPoint(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true));
10291 direction = 'left';
10292 pos = pos.subtract(toPoint(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true));
10295 removeClass(container, 'leaflet-tooltip-right');
10296 removeClass(container, 'leaflet-tooltip-left');
10297 removeClass(container, 'leaflet-tooltip-top');
10298 removeClass(container, 'leaflet-tooltip-bottom');
10299 addClass(container, 'leaflet-tooltip-' + direction);
10300 setPosition(container, pos);
10303 _updatePosition: function () {
10304 var pos = this._map.latLngToLayerPoint(this._latlng);
10305 this._setPosition(pos);
10308 setOpacity: function (opacity) {
10309 this.options.opacity = opacity;
10311 if (this._container) {
10312 setOpacity(this._container, opacity);
10316 _animateZoom: function (e) {
10317 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
10318 this._setPosition(pos);
10321 _getAnchor: function () {
10322 // Where should we anchor the tooltip on the source layer?
10323 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
10328 // @namespace Tooltip
10329 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
10330 // 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.
10331 var tooltip = function (options, source) {
10332 return new Tooltip(options, source);
10336 // @section Methods for Layers and Controls
10339 // @method openTooltip(tooltip: Tooltip): this
10340 // Opens the specified tooltip.
10342 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
10343 // Creates a tooltip with the specified content and options and open it.
10344 openTooltip: function (tooltip, latlng, options) {
10345 if (!(tooltip instanceof Tooltip)) {
10346 tooltip = new Tooltip(options).setContent(tooltip);
10350 tooltip.setLatLng(latlng);
10353 if (this.hasLayer(tooltip)) {
10357 return this.addLayer(tooltip);
10360 // @method closeTooltip(tooltip?: Tooltip): this
10361 // Closes the tooltip given as parameter.
10362 closeTooltip: function (tooltip) {
10364 this.removeLayer(tooltip);
10373 * @section Tooltip methods example
10375 * All layers share a set of methods convenient for binding tooltips to it.
10378 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
10379 * layer.openTooltip();
10380 * layer.closeTooltip();
10384 // @section Tooltip methods
10387 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
10388 // Binds a tooltip to the layer with the passed `content` and sets up the
10389 // necessary event listeners. If a `Function` is passed it will receive
10390 // the layer as the first argument and should return a `String` or `HTMLElement`.
10391 bindTooltip: function (content, options) {
10393 if (content instanceof Tooltip) {
10394 setOptions(content, options);
10395 this._tooltip = content;
10396 content._source = this;
10398 if (!this._tooltip || options) {
10399 this._tooltip = new Tooltip(options, this);
10401 this._tooltip.setContent(content);
10405 this._initTooltipInteractions();
10407 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
10408 this.openTooltip();
10414 // @method unbindTooltip(): this
10415 // Removes the tooltip previously bound with `bindTooltip`.
10416 unbindTooltip: function () {
10417 if (this._tooltip) {
10418 this._initTooltipInteractions(true);
10419 this.closeTooltip();
10420 this._tooltip = null;
10425 _initTooltipInteractions: function (remove$$1) {
10426 if (!remove$$1 && this._tooltipHandlersAdded) { return; }
10427 var onOff = remove$$1 ? 'off' : 'on',
10429 remove: this.closeTooltip,
10430 move: this._moveTooltip
10432 if (!this._tooltip.options.permanent) {
10433 events.mouseover = this._openTooltip;
10434 events.mouseout = this.closeTooltip;
10435 if (this._tooltip.options.sticky) {
10436 events.mousemove = this._moveTooltip;
10439 events.click = this._openTooltip;
10442 events.add = this._openTooltip;
10444 this[onOff](events);
10445 this._tooltipHandlersAdded = !remove$$1;
10448 // @method openTooltip(latlng?: LatLng): this
10449 // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
10450 openTooltip: function (layer, latlng) {
10451 if (this._tooltip && this._map) {
10452 latlng = this._tooltip._prepareOpen(this, layer, latlng);
10454 // open the tooltip on the map
10455 this._map.openTooltip(this._tooltip, latlng);
10457 // Tooltip container may not be defined if not permanent and never
10459 if (this._tooltip.options.interactive && this._tooltip._container) {
10460 addClass(this._tooltip._container, 'leaflet-clickable');
10461 this.addInteractiveTarget(this._tooltip._container);
10468 // @method closeTooltip(): this
10469 // Closes the tooltip bound to this layer if it is open.
10470 closeTooltip: function () {
10471 if (this._tooltip) {
10472 this._tooltip._close();
10473 if (this._tooltip.options.interactive && this._tooltip._container) {
10474 removeClass(this._tooltip._container, 'leaflet-clickable');
10475 this.removeInteractiveTarget(this._tooltip._container);
10481 // @method toggleTooltip(): this
10482 // Opens or closes the tooltip bound to this layer depending on its current state.
10483 toggleTooltip: function (target) {
10484 if (this._tooltip) {
10485 if (this._tooltip._map) {
10486 this.closeTooltip();
10488 this.openTooltip(target);
10494 // @method isTooltipOpen(): boolean
10495 // Returns `true` if the tooltip bound to this layer is currently open.
10496 isTooltipOpen: function () {
10497 return this._tooltip.isOpen();
10500 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10501 // Sets the content of the tooltip bound to this layer.
10502 setTooltipContent: function (content) {
10503 if (this._tooltip) {
10504 this._tooltip.setContent(content);
10509 // @method getTooltip(): Tooltip
10510 // Returns the tooltip bound to this layer.
10511 getTooltip: function () {
10512 return this._tooltip;
10515 _openTooltip: function (e) {
10516 var layer = e.layer || e.target;
10518 if (!this._tooltip || !this._map) {
10521 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
10524 _moveTooltip: function (e) {
10525 var latlng = e.latlng, containerPoint, layerPoint;
10526 if (this._tooltip.options.sticky && e.originalEvent) {
10527 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
10528 layerPoint = this._map.containerPointToLayerPoint(containerPoint);
10529 latlng = this._map.layerPointToLatLng(layerPoint);
10531 this._tooltip.setLatLng(latlng);
10540 * Represents a lightweight icon for markers that uses a simple `<div>`
10541 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
10545 * var myIcon = L.divIcon({className: 'my-div-icon'});
10546 * // you can set .my-div-icon styles in CSS
10548 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
10551 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
10554 var DivIcon = Icon.extend({
10557 // @aka DivIcon options
10558 iconSize: [12, 12], // also can be set through CSS
10560 // iconAnchor: (Point),
10561 // popupAnchor: (Point),
10563 // @option html: String|HTMLElement = ''
10564 // Custom HTML code to put inside the div element, empty by default. Alternatively,
10565 // an instance of `HTMLElement`.
10568 // @option bgPos: Point = [0, 0]
10569 // Optional relative position of the background, in pixels
10572 className: 'leaflet-div-icon'
10575 createIcon: function (oldIcon) {
10576 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
10577 options = this.options;
10579 if (options.html instanceof Element) {
10581 div.appendChild(options.html);
10583 div.innerHTML = options.html !== false ? options.html : '';
10586 if (options.bgPos) {
10587 var bgPos = toPoint(options.bgPos);
10588 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
10590 this._setIconStyles(div, 'icon');
10595 createShadow: function () {
10600 // @factory L.divIcon(options: DivIcon options)
10601 // Creates a `DivIcon` instance with the given options.
10602 function divIcon(options) {
10603 return new DivIcon(options);
10606 Icon.Default = IconDefault;
10613 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
10614 * 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.
10617 * @section Synchronous usage
10620 * 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.
10623 * var CanvasLayer = L.GridLayer.extend({
10624 * createTile: function(coords){
10625 * // create a <canvas> element for drawing
10626 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10628 * // setup tile width and height according to the options
10629 * var size = this.getTileSize();
10630 * tile.width = size.x;
10631 * tile.height = size.y;
10633 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z
10634 * var ctx = tile.getContext('2d');
10636 * // return the tile so it can be rendered on screen
10642 * @section Asynchronous usage
10645 * 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.
10648 * var CanvasLayer = L.GridLayer.extend({
10649 * createTile: function(coords, done){
10652 * // create a <canvas> element for drawing
10653 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10655 * // setup tile width and height according to the options
10656 * var size = this.getTileSize();
10657 * tile.width = size.x;
10658 * tile.height = size.y;
10660 * // draw something asynchronously and pass the tile to the done() callback
10661 * setTimeout(function() {
10662 * done(error, tile);
10674 var GridLayer = Layer.extend({
10677 // @aka GridLayer options
10679 // @option tileSize: Number|Point = 256
10680 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
10683 // @option opacity: Number = 1.0
10684 // Opacity of the tiles. Can be used in the `createTile()` function.
10687 // @option updateWhenIdle: Boolean = (depends)
10688 // Load new tiles only when panning ends.
10689 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
10690 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
10691 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
10692 updateWhenIdle: mobile,
10694 // @option updateWhenZooming: Boolean = true
10695 // 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.
10696 updateWhenZooming: true,
10698 // @option updateInterval: Number = 200
10699 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
10700 updateInterval: 200,
10702 // @option zIndex: Number = 1
10703 // The explicit zIndex of the tile layer.
10706 // @option bounds: LatLngBounds = undefined
10707 // If set, tiles will only be loaded inside the set `LatLngBounds`.
10710 // @option minZoom: Number = 0
10711 // The minimum zoom level down to which this layer will be displayed (inclusive).
10714 // @option maxZoom: Number = undefined
10715 // The maximum zoom level up to which this layer will be displayed (inclusive).
10716 maxZoom: undefined,
10718 // @option maxNativeZoom: Number = undefined
10719 // Maximum zoom number the tile source has available. If it is specified,
10720 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
10721 // from `maxNativeZoom` level and auto-scaled.
10722 maxNativeZoom: undefined,
10724 // @option minNativeZoom: Number = undefined
10725 // Minimum zoom number the tile source has available. If it is specified,
10726 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
10727 // from `minNativeZoom` level and auto-scaled.
10728 minNativeZoom: undefined,
10730 // @option noWrap: Boolean = false
10731 // Whether the layer is wrapped around the antimeridian. If `true`, the
10732 // GridLayer will only be displayed once at low zoom levels. Has no
10733 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
10734 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
10735 // tiles outside the CRS limits.
10738 // @option pane: String = 'tilePane'
10739 // `Map pane` where the grid layer will be added.
10742 // @option className: String = ''
10743 // A custom class name to assign to the tile layer. Empty by default.
10746 // @option keepBuffer: Number = 2
10747 // When panning the map, keep this many rows and columns of tiles before unloading them.
10751 initialize: function (options) {
10752 setOptions(this, options);
10755 onAdd: function () {
10756 this._initContainer();
10765 beforeAdd: function (map) {
10766 map._addZoomLimit(this);
10769 onRemove: function (map) {
10770 this._removeAllTiles();
10771 remove(this._container);
10772 map._removeZoomLimit(this);
10773 this._container = null;
10774 this._tileZoom = undefined;
10777 // @method bringToFront: this
10778 // Brings the tile layer to the top of all tile layers.
10779 bringToFront: function () {
10781 toFront(this._container);
10782 this._setAutoZIndex(Math.max);
10787 // @method bringToBack: this
10788 // Brings the tile layer to the bottom of all tile layers.
10789 bringToBack: function () {
10791 toBack(this._container);
10792 this._setAutoZIndex(Math.min);
10797 // @method getContainer: HTMLElement
10798 // Returns the HTML element that contains the tiles for this layer.
10799 getContainer: function () {
10800 return this._container;
10803 // @method setOpacity(opacity: Number): this
10804 // Changes the [opacity](#gridlayer-opacity) of the grid layer.
10805 setOpacity: function (opacity) {
10806 this.options.opacity = opacity;
10807 this._updateOpacity();
10811 // @method setZIndex(zIndex: Number): this
10812 // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
10813 setZIndex: function (zIndex) {
10814 this.options.zIndex = zIndex;
10815 this._updateZIndex();
10820 // @method isLoading: Boolean
10821 // Returns `true` if any tile in the grid layer has not finished loading.
10822 isLoading: function () {
10823 return this._loading;
10826 // @method redraw: this
10827 // Causes the layer to clear all the tiles and request them again.
10828 redraw: function () {
10830 this._removeAllTiles();
10836 getEvents: function () {
10838 viewprereset: this._invalidateAll,
10839 viewreset: this._resetView,
10840 zoom: this._resetView,
10841 moveend: this._onMoveEnd
10844 if (!this.options.updateWhenIdle) {
10845 // update tiles on move, but not more often than once per given interval
10846 if (!this._onMove) {
10847 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
10850 events.move = this._onMove;
10853 if (this._zoomAnimated) {
10854 events.zoomanim = this._animateZoom;
10860 // @section Extension methods
10861 // Layers extending `GridLayer` shall reimplement the following method.
10862 // @method createTile(coords: Object, done?: Function): HTMLElement
10863 // Called only internally, must be overridden by classes extending `GridLayer`.
10864 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
10865 // is specified, it must be called when the tile has finished loading and drawing.
10866 createTile: function () {
10867 return document.createElement('div');
10871 // @method getTileSize: Point
10872 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
10873 getTileSize: function () {
10874 var s = this.options.tileSize;
10875 return s instanceof Point ? s : new Point(s, s);
10878 _updateZIndex: function () {
10879 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
10880 this._container.style.zIndex = this.options.zIndex;
10884 _setAutoZIndex: function (compare) {
10885 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
10887 var layers = this.getPane().children,
10888 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
10890 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
10892 zIndex = layers[i].style.zIndex;
10894 if (layers[i] !== this._container && zIndex) {
10895 edgeZIndex = compare(edgeZIndex, +zIndex);
10899 if (isFinite(edgeZIndex)) {
10900 this.options.zIndex = edgeZIndex + compare(-1, 1);
10901 this._updateZIndex();
10905 _updateOpacity: function () {
10906 if (!this._map) { return; }
10908 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
10909 if (ielt9) { return; }
10911 setOpacity(this._container, this.options.opacity);
10913 var now = +new Date(),
10917 for (var key in this._tiles) {
10918 var tile = this._tiles[key];
10919 if (!tile.current || !tile.loaded) { continue; }
10921 var fade = Math.min(1, (now - tile.loaded) / 200);
10923 setOpacity(tile.el, fade);
10930 this._onOpaqueTile(tile);
10932 tile.active = true;
10936 if (willPrune && !this._noPrune) { this._pruneTiles(); }
10939 cancelAnimFrame(this._fadeFrame);
10940 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
10944 _onOpaqueTile: falseFn,
10946 _initContainer: function () {
10947 if (this._container) { return; }
10949 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
10950 this._updateZIndex();
10952 if (this.options.opacity < 1) {
10953 this._updateOpacity();
10956 this.getPane().appendChild(this._container);
10959 _updateLevels: function () {
10961 var zoom = this._tileZoom,
10962 maxZoom = this.options.maxZoom;
10964 if (zoom === undefined) { return undefined; }
10966 for (var z in this._levels) {
10967 if (this._levels[z].el.children.length || z === zoom) {
10968 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
10969 this._onUpdateLevel(z);
10971 remove(this._levels[z].el);
10972 this._removeTilesAtZoom(z);
10973 this._onRemoveLevel(z);
10974 delete this._levels[z];
10978 var level = this._levels[zoom],
10982 level = this._levels[zoom] = {};
10984 level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
10985 level.el.style.zIndex = maxZoom;
10987 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
10990 this._setZoomTransform(level, map.getCenter(), map.getZoom());
10992 // force the browser to consider the newly added element for transition
10993 falseFn(level.el.offsetWidth);
10995 this._onCreateLevel(level);
10998 this._level = level;
11003 _onUpdateLevel: falseFn,
11005 _onRemoveLevel: falseFn,
11007 _onCreateLevel: falseFn,
11009 _pruneTiles: function () {
11016 var zoom = this._map.getZoom();
11017 if (zoom > this.options.maxZoom ||
11018 zoom < this.options.minZoom) {
11019 this._removeAllTiles();
11023 for (key in this._tiles) {
11024 tile = this._tiles[key];
11025 tile.retain = tile.current;
11028 for (key in this._tiles) {
11029 tile = this._tiles[key];
11030 if (tile.current && !tile.active) {
11031 var coords = tile.coords;
11032 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
11033 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
11038 for (key in this._tiles) {
11039 if (!this._tiles[key].retain) {
11040 this._removeTile(key);
11045 _removeTilesAtZoom: function (zoom) {
11046 for (var key in this._tiles) {
11047 if (this._tiles[key].coords.z !== zoom) {
11050 this._removeTile(key);
11054 _removeAllTiles: function () {
11055 for (var key in this._tiles) {
11056 this._removeTile(key);
11060 _invalidateAll: function () {
11061 for (var z in this._levels) {
11062 remove(this._levels[z].el);
11063 this._onRemoveLevel(z);
11064 delete this._levels[z];
11066 this._removeAllTiles();
11068 this._tileZoom = undefined;
11071 _retainParent: function (x, y, z, minZoom) {
11072 var x2 = Math.floor(x / 2),
11073 y2 = Math.floor(y / 2),
11075 coords2 = new Point(+x2, +y2);
11078 var key = this._tileCoordsToKey(coords2),
11079 tile = this._tiles[key];
11081 if (tile && tile.active) {
11082 tile.retain = true;
11085 } else if (tile && tile.loaded) {
11086 tile.retain = true;
11089 if (z2 > minZoom) {
11090 return this._retainParent(x2, y2, z2, minZoom);
11096 _retainChildren: function (x, y, z, maxZoom) {
11098 for (var i = 2 * x; i < 2 * x + 2; i++) {
11099 for (var j = 2 * y; j < 2 * y + 2; j++) {
11101 var coords = new Point(i, j);
11104 var key = this._tileCoordsToKey(coords),
11105 tile = this._tiles[key];
11107 if (tile && tile.active) {
11108 tile.retain = true;
11111 } else if (tile && tile.loaded) {
11112 tile.retain = true;
11115 if (z + 1 < maxZoom) {
11116 this._retainChildren(i, j, z + 1, maxZoom);
11122 _resetView: function (e) {
11123 var animating = e && (e.pinch || e.flyTo);
11124 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
11127 _animateZoom: function (e) {
11128 this._setView(e.center, e.zoom, true, e.noUpdate);
11131 _clampZoom: function (zoom) {
11132 var options = this.options;
11134 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
11135 return options.minNativeZoom;
11138 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
11139 return options.maxNativeZoom;
11145 _setView: function (center, zoom, noPrune, noUpdate) {
11146 var tileZoom = this._clampZoom(Math.round(zoom));
11147 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
11148 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
11149 tileZoom = undefined;
11152 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
11154 if (!noUpdate || tileZoomChanged) {
11156 this._tileZoom = tileZoom;
11158 if (this._abortLoading) {
11159 this._abortLoading();
11162 this._updateLevels();
11165 if (tileZoom !== undefined) {
11166 this._update(center);
11170 this._pruneTiles();
11173 // Flag to prevent _updateOpacity from pruning tiles during
11174 // a zoom anim or a pinch gesture
11175 this._noPrune = !!noPrune;
11178 this._setZoomTransforms(center, zoom);
11181 _setZoomTransforms: function (center, zoom) {
11182 for (var i in this._levels) {
11183 this._setZoomTransform(this._levels[i], center, zoom);
11187 _setZoomTransform: function (level, center, zoom) {
11188 var scale = this._map.getZoomScale(zoom, level.zoom),
11189 translate = level.origin.multiplyBy(scale)
11190 .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
11193 setTransform(level.el, translate, scale);
11195 setPosition(level.el, translate);
11199 _resetGrid: function () {
11200 var map = this._map,
11201 crs = map.options.crs,
11202 tileSize = this._tileSize = this.getTileSize(),
11203 tileZoom = this._tileZoom;
11205 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
11207 this._globalTileRange = this._pxBoundsToTileRange(bounds);
11210 this._wrapX = crs.wrapLng && !this.options.noWrap && [
11211 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
11212 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
11214 this._wrapY = crs.wrapLat && !this.options.noWrap && [
11215 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
11216 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
11220 _onMoveEnd: function () {
11221 if (!this._map || this._map._animatingZoom) { return; }
11226 _getTiledPixelBounds: function (center) {
11227 var map = this._map,
11228 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
11229 scale = map.getZoomScale(mapZoom, this._tileZoom),
11230 pixelCenter = map.project(center, this._tileZoom).floor(),
11231 halfSize = map.getSize().divideBy(scale * 2);
11233 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
11236 // Private method to load tiles in the grid's active zoom level according to map bounds
11237 _update: function (center) {
11238 var map = this._map;
11239 if (!map) { return; }
11240 var zoom = this._clampZoom(map.getZoom());
11242 if (center === undefined) { center = map.getCenter(); }
11243 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom
11245 var pixelBounds = this._getTiledPixelBounds(center),
11246 tileRange = this._pxBoundsToTileRange(pixelBounds),
11247 tileCenter = tileRange.getCenter(),
11249 margin = this.options.keepBuffer,
11250 noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
11251 tileRange.getTopRight().add([margin, -margin]));
11253 // Sanity check: panic if the tile range contains Infinity somewhere.
11254 if (!(isFinite(tileRange.min.x) &&
11255 isFinite(tileRange.min.y) &&
11256 isFinite(tileRange.max.x) &&
11257 isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
11259 for (var key in this._tiles) {
11260 var c = this._tiles[key].coords;
11261 if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
11262 this._tiles[key].current = false;
11266 // _update just loads more tiles. If the tile zoom level differs too much
11267 // from the map's, let _setView reset levels and prune old tiles.
11268 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
11270 // create a queue of coordinates to load tiles from
11271 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
11272 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
11273 var coords = new Point(i, j);
11274 coords.z = this._tileZoom;
11276 if (!this._isValidTile(coords)) { continue; }
11278 var tile = this._tiles[this._tileCoordsToKey(coords)];
11280 tile.current = true;
11282 queue.push(coords);
11287 // sort tile queue to load tiles in order of their distance to center
11288 queue.sort(function (a, b) {
11289 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
11292 if (queue.length !== 0) {
11293 // if it's the first batch of tiles to load
11294 if (!this._loading) {
11295 this._loading = true;
11296 // @event loading: Event
11297 // Fired when the grid layer starts loading tiles.
11298 this.fire('loading');
11301 // create DOM fragment to append tiles in one batch
11302 var fragment = document.createDocumentFragment();
11304 for (i = 0; i < queue.length; i++) {
11305 this._addTile(queue[i], fragment);
11308 this._level.el.appendChild(fragment);
11312 _isValidTile: function (coords) {
11313 var crs = this._map.options.crs;
11315 if (!crs.infinite) {
11316 // don't load tile if it's out of bounds and not wrapped
11317 var bounds = this._globalTileRange;
11318 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
11319 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
11322 if (!this.options.bounds) { return true; }
11324 // don't load tile if it doesn't intersect the bounds in options
11325 var tileBounds = this._tileCoordsToBounds(coords);
11326 return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
11329 _keyToBounds: function (key) {
11330 return this._tileCoordsToBounds(this._keyToTileCoords(key));
11333 _tileCoordsToNwSe: function (coords) {
11334 var map = this._map,
11335 tileSize = this.getTileSize(),
11336 nwPoint = coords.scaleBy(tileSize),
11337 sePoint = nwPoint.add(tileSize),
11338 nw = map.unproject(nwPoint, coords.z),
11339 se = map.unproject(sePoint, coords.z);
11343 // converts tile coordinates to its geographical bounds
11344 _tileCoordsToBounds: function (coords) {
11345 var bp = this._tileCoordsToNwSe(coords),
11346 bounds = new LatLngBounds(bp[0], bp[1]);
11348 if (!this.options.noWrap) {
11349 bounds = this._map.wrapLatLngBounds(bounds);
11353 // converts tile coordinates to key for the tile cache
11354 _tileCoordsToKey: function (coords) {
11355 return coords.x + ':' + coords.y + ':' + coords.z;
11358 // converts tile cache key to coordinates
11359 _keyToTileCoords: function (key) {
11360 var k = key.split(':'),
11361 coords = new Point(+k[0], +k[1]);
11366 _removeTile: function (key) {
11367 var tile = this._tiles[key];
11368 if (!tile) { return; }
11372 delete this._tiles[key];
11374 // @event tileunload: TileEvent
11375 // Fired when a tile is removed (e.g. when a tile goes off the screen).
11376 this.fire('tileunload', {
11378 coords: this._keyToTileCoords(key)
11382 _initTile: function (tile) {
11383 addClass(tile, 'leaflet-tile');
11385 var tileSize = this.getTileSize();
11386 tile.style.width = tileSize.x + 'px';
11387 tile.style.height = tileSize.y + 'px';
11389 tile.onselectstart = falseFn;
11390 tile.onmousemove = falseFn;
11392 // update opacity on tiles in IE7-8 because of filter inheritance problems
11393 if (ielt9 && this.options.opacity < 1) {
11394 setOpacity(tile, this.options.opacity);
11397 // without this hack, tiles disappear after zoom on Chrome for Android
11398 // https://github.com/Leaflet/Leaflet/issues/2078
11399 if (android && !android23) {
11400 tile.style.WebkitBackfaceVisibility = 'hidden';
11404 _addTile: function (coords, container) {
11405 var tilePos = this._getTilePos(coords),
11406 key = this._tileCoordsToKey(coords);
11408 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
11410 this._initTile(tile);
11412 // if createTile is defined with a second argument ("done" callback),
11413 // we know that tile is async and will be ready later; otherwise
11414 if (this.createTile.length < 2) {
11415 // mark tile as ready, but delay one frame for opacity animation to happen
11416 requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
11419 setPosition(tile, tilePos);
11421 // save tile in cache
11422 this._tiles[key] = {
11428 container.appendChild(tile);
11429 // @event tileloadstart: TileEvent
11430 // Fired when a tile is requested and starts loading.
11431 this.fire('tileloadstart', {
11437 _tileReady: function (coords, err, tile) {
11439 // @event tileerror: TileErrorEvent
11440 // Fired when there is an error loading a tile.
11441 this.fire('tileerror', {
11448 var key = this._tileCoordsToKey(coords);
11450 tile = this._tiles[key];
11451 if (!tile) { return; }
11453 tile.loaded = +new Date();
11454 if (this._map._fadeAnimated) {
11455 setOpacity(tile.el, 0);
11456 cancelAnimFrame(this._fadeFrame);
11457 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11459 tile.active = true;
11460 this._pruneTiles();
11464 addClass(tile.el, 'leaflet-tile-loaded');
11466 // @event tileload: TileEvent
11467 // Fired when a tile loads.
11468 this.fire('tileload', {
11474 if (this._noTilesToLoad()) {
11475 this._loading = false;
11476 // @event load: Event
11477 // Fired when the grid layer loaded all visible tiles.
11480 if (ielt9 || !this._map._fadeAnimated) {
11481 requestAnimFrame(this._pruneTiles, this);
11483 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
11484 // to trigger a pruning.
11485 setTimeout(bind(this._pruneTiles, this), 250);
11490 _getTilePos: function (coords) {
11491 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
11494 _wrapCoords: function (coords) {
11495 var newCoords = new Point(
11496 this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
11497 this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
11498 newCoords.z = coords.z;
11502 _pxBoundsToTileRange: function (bounds) {
11503 var tileSize = this.getTileSize();
11505 bounds.min.unscaleBy(tileSize).floor(),
11506 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
11509 _noTilesToLoad: function () {
11510 for (var key in this._tiles) {
11511 if (!this._tiles[key].loaded) { return false; }
11517 // @factory L.gridLayer(options?: GridLayer options)
11518 // Creates a new instance of GridLayer with the supplied options.
11519 function gridLayer(options) {
11520 return new GridLayer(options);
11525 * @inherits GridLayer
11527 * Used to load and display tile layers on the map. Note that most tile servers require attribution, which you can set under `Layer`. Extends `GridLayer`.
11532 * L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar', attribution: 'Map data © <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'}).addTo(map);
11535 * @section URL template
11538 * A string of the following form:
11541 * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
11544 * `{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.
11546 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
11549 * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
11554 var TileLayer = GridLayer.extend({
11557 // @aka TileLayer options
11559 // @option minZoom: Number = 0
11560 // The minimum zoom level down to which this layer will be displayed (inclusive).
11563 // @option maxZoom: Number = 18
11564 // The maximum zoom level up to which this layer will be displayed (inclusive).
11567 // @option subdomains: String|String[] = 'abc'
11568 // 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.
11571 // @option errorTileUrl: String = ''
11572 // URL to the tile image to show in place of the tile that failed to load.
11575 // @option zoomOffset: Number = 0
11576 // The zoom number used in tile URLs will be offset with this value.
11579 // @option tms: Boolean = false
11580 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
11583 // @option zoomReverse: Boolean = false
11584 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
11585 zoomReverse: false,
11587 // @option detectRetina: Boolean = false
11588 // 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.
11589 detectRetina: false,
11591 // @option crossOrigin: Boolean|String = false
11592 // Whether the crossOrigin attribute will be added to the tiles.
11593 // 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.
11594 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
11598 initialize: function (url, options) {
11602 options = setOptions(this, options);
11604 // detecting retina displays, adjusting tileSize and zoom levels
11605 if (options.detectRetina && retina && options.maxZoom > 0) {
11607 options.tileSize = Math.floor(options.tileSize / 2);
11609 if (!options.zoomReverse) {
11610 options.zoomOffset++;
11613 options.zoomOffset--;
11617 options.minZoom = Math.max(0, options.minZoom);
11620 if (typeof options.subdomains === 'string') {
11621 options.subdomains = options.subdomains.split('');
11624 // for https://github.com/Leaflet/Leaflet/issues/137
11626 this.on('tileunload', this._onTileRemove);
11630 // @method setUrl(url: String, noRedraw?: Boolean): this
11631 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
11632 // If the URL does not change, the layer will not be redrawn unless
11633 // the noRedraw parameter is set to false.
11634 setUrl: function (url, noRedraw) {
11635 if (this._url === url && noRedraw === undefined) {
11647 // @method createTile(coords: Object, done?: Function): HTMLElement
11648 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
11649 // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
11650 // callback is called when the tile has been loaded.
11651 createTile: function (coords, done) {
11652 var tile = document.createElement('img');
11654 on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
11655 on(tile, 'error', bind(this._tileOnError, this, done, tile));
11657 if (this.options.crossOrigin || this.options.crossOrigin === '') {
11658 tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
11662 Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
11663 http://www.w3.org/TR/WCAG20-TECHS/H67
11668 Set role="presentation" to force screen readers to ignore this
11669 https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
11671 tile.setAttribute('role', 'presentation');
11673 tile.src = this.getTileUrl(coords);
11678 // @section Extension methods
11680 // Layers extending `TileLayer` might reimplement the following method.
11681 // @method getTileUrl(coords: Object): String
11682 // Called only internally, returns the URL for a tile given its coordinates.
11683 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
11684 getTileUrl: function (coords) {
11686 r: retina ? '@2x' : '',
11687 s: this._getSubdomain(coords),
11690 z: this._getZoomForUrl()
11692 if (this._map && !this._map.options.crs.infinite) {
11693 var invertedY = this._globalTileRange.max.y - coords.y;
11694 if (this.options.tms) {
11695 data['y'] = invertedY;
11697 data['-y'] = invertedY;
11700 return template(this._url, extend(data, this.options));
11703 _tileOnLoad: function (done, tile) {
11704 // For https://github.com/Leaflet/Leaflet/issues/3332
11706 setTimeout(bind(done, this, null, tile), 0);
11712 _tileOnError: function (done, tile, e) {
11713 var errorUrl = this.options.errorTileUrl;
11714 if (errorUrl && tile.getAttribute('src') !== errorUrl) {
11715 tile.src = errorUrl;
11720 _onTileRemove: function (e) {
11721 e.tile.onload = null;
11724 _getZoomForUrl: function () {
11725 var zoom = this._tileZoom,
11726 maxZoom = this.options.maxZoom,
11727 zoomReverse = this.options.zoomReverse,
11728 zoomOffset = this.options.zoomOffset;
11731 zoom = maxZoom - zoom;
11734 return zoom + zoomOffset;
11737 _getSubdomain: function (tilePoint) {
11738 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
11739 return this.options.subdomains[index];
11742 // stops loading all tiles in the background layer
11743 _abortLoading: function () {
11745 for (i in this._tiles) {
11746 if (this._tiles[i].coords.z !== this._tileZoom) {
11747 tile = this._tiles[i].el;
11749 tile.onload = falseFn;
11750 tile.onerror = falseFn;
11752 if (!tile.complete) {
11753 tile.src = emptyImageUrl;
11755 delete this._tiles[i];
11761 _removeTile: function (key) {
11762 var tile = this._tiles[key];
11763 if (!tile) { return; }
11765 // Cancels any pending http requests associated with the tile
11766 // unless we're on Android's stock browser,
11767 // see https://github.com/Leaflet/Leaflet/issues/137
11768 if (!androidStock) {
11769 tile.el.setAttribute('src', emptyImageUrl);
11772 return GridLayer.prototype._removeTile.call(this, key);
11775 _tileReady: function (coords, err, tile) {
11776 if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) {
11780 return GridLayer.prototype._tileReady.call(this, coords, err, tile);
11785 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
11786 // Instantiates a tile layer object given a `URL template` and optionally an options object.
11788 function tileLayer(url, options) {
11789 return new TileLayer(url, options);
11793 * @class TileLayer.WMS
11794 * @inherits TileLayer
11795 * @aka L.TileLayer.WMS
11796 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
11801 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
11802 * layers: 'nexrad-n0r-900913',
11803 * format: 'image/png',
11804 * transparent: true,
11805 * attribution: "Weather data © 2012 IEM Nexrad"
11810 var TileLayerWMS = TileLayer.extend({
11813 // @aka TileLayer.WMS options
11814 // If any custom options not documented here are used, they will be sent to the
11815 // WMS server as extra parameters in each request URL. This can be useful for
11816 // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
11817 defaultWmsParams: {
11821 // @option layers: String = ''
11822 // **(required)** Comma-separated list of WMS layers to show.
11825 // @option styles: String = ''
11826 // Comma-separated list of WMS styles.
11829 // @option format: String = 'image/jpeg'
11830 // WMS image format (use `'image/png'` for layers with transparency).
11831 format: 'image/jpeg',
11833 // @option transparent: Boolean = false
11834 // If `true`, the WMS service will return images with transparency.
11835 transparent: false,
11837 // @option version: String = '1.1.1'
11838 // Version of the WMS service to use
11843 // @option crs: CRS = null
11844 // Coordinate Reference System to use for the WMS requests, defaults to
11845 // map CRS. Don't change this if you're not sure what it means.
11848 // @option uppercase: Boolean = false
11849 // If `true`, WMS request parameter keys will be uppercase.
11853 initialize: function (url, options) {
11857 var wmsParams = extend({}, this.defaultWmsParams);
11859 // all keys that are not TileLayer options go to WMS params
11860 for (var i in options) {
11861 if (!(i in this.options)) {
11862 wmsParams[i] = options[i];
11866 options = setOptions(this, options);
11868 var realRetina = options.detectRetina && retina ? 2 : 1;
11869 var tileSize = this.getTileSize();
11870 wmsParams.width = tileSize.x * realRetina;
11871 wmsParams.height = tileSize.y * realRetina;
11873 this.wmsParams = wmsParams;
11876 onAdd: function (map) {
11878 this._crs = this.options.crs || map.options.crs;
11879 this._wmsVersion = parseFloat(this.wmsParams.version);
11881 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
11882 this.wmsParams[projectionKey] = this._crs.code;
11884 TileLayer.prototype.onAdd.call(this, map);
11887 getTileUrl: function (coords) {
11889 var tileBounds = this._tileCoordsToNwSe(coords),
11891 bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
11894 bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
11895 [min.y, min.x, max.y, max.x] :
11896 [min.x, min.y, max.x, max.y]).join(','),
11897 url = TileLayer.prototype.getTileUrl.call(this, coords);
11899 getParamString(this.wmsParams, url, this.options.uppercase) +
11900 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
11903 // @method setParams(params: Object, noRedraw?: Boolean): this
11904 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
11905 setParams: function (params, noRedraw) {
11907 extend(this.wmsParams, params);
11918 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
11919 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
11920 function tileLayerWMS(url, options) {
11921 return new TileLayerWMS(url, options);
11924 TileLayer.WMS = TileLayerWMS;
11925 tileLayer.wms = tileLayerWMS;
11932 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
11933 * DOM container of the renderer, its bounds, and its zoom animation.
11935 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
11936 * itself can be added or removed to the map. All paths use a renderer, which can
11937 * be implicit (the map will decide the type of renderer and use it automatically)
11938 * or explicit (using the [`renderer`](#path-renderer) option of the path).
11940 * Do not use this class directly, use `SVG` and `Canvas` instead.
11942 * @event update: Event
11943 * Fired when the renderer updates its bounds, center and zoom, for example when
11944 * its map has moved
11947 var Renderer = Layer.extend({
11950 // @aka Renderer options
11952 // @option padding: Number = 0.1
11953 // How much to extend the clip area around the map view (relative to its size)
11954 // e.g. 0.1 would be 10% of map view in each direction
11957 // @option tolerance: Number = 0
11958 // How much to extend click tolerance round a path/object on the map
11962 initialize: function (options) {
11963 setOptions(this, options);
11965 this._layers = this._layers || {};
11968 onAdd: function () {
11969 if (!this._container) {
11970 this._initContainer(); // defined by renderer implementations
11972 if (this._zoomAnimated) {
11973 addClass(this._container, 'leaflet-zoom-animated');
11977 this.getPane().appendChild(this._container);
11979 this.on('update', this._updatePaths, this);
11982 onRemove: function () {
11983 this.off('update', this._updatePaths, this);
11984 this._destroyContainer();
11987 getEvents: function () {
11989 viewreset: this._reset,
11990 zoom: this._onZoom,
11991 moveend: this._update,
11992 zoomend: this._onZoomEnd
11994 if (this._zoomAnimated) {
11995 events.zoomanim = this._onAnimZoom;
12000 _onAnimZoom: function (ev) {
12001 this._updateTransform(ev.center, ev.zoom);
12004 _onZoom: function () {
12005 this._updateTransform(this._map.getCenter(), this._map.getZoom());
12008 _updateTransform: function (center, zoom) {
12009 var scale = this._map.getZoomScale(zoom, this._zoom),
12010 position = getPosition(this._container),
12011 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
12012 currentCenterPoint = this._map.project(this._center, zoom),
12013 destCenterPoint = this._map.project(center, zoom),
12014 centerOffset = destCenterPoint.subtract(currentCenterPoint),
12016 topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
12019 setTransform(this._container, topLeftOffset, scale);
12021 setPosition(this._container, topLeftOffset);
12025 _reset: function () {
12027 this._updateTransform(this._center, this._zoom);
12029 for (var id in this._layers) {
12030 this._layers[id]._reset();
12034 _onZoomEnd: function () {
12035 for (var id in this._layers) {
12036 this._layers[id]._project();
12040 _updatePaths: function () {
12041 for (var id in this._layers) {
12042 this._layers[id]._update();
12046 _update: function () {
12047 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
12048 // Subclasses are responsible of firing the 'update' event.
12049 var p = this.options.padding,
12050 size = this._map.getSize(),
12051 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
12053 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
12055 this._center = this._map.getCenter();
12056 this._zoom = this._map.getZoom();
12062 * @inherits Renderer
12065 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
12066 * Inherits `Renderer`.
12068 * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
12069 * available in all web browsers, notably IE8, and overlapping geometries might
12070 * not display properly in some edge cases.
12074 * Use Canvas by default for all paths in the map:
12077 * var map = L.map('map', {
12078 * renderer: L.canvas()
12082 * Use a Canvas renderer with extra padding for specific vector geometries:
12085 * var map = L.map('map');
12086 * var myRenderer = L.canvas({ padding: 0.5 });
12087 * var line = L.polyline( coordinates, { renderer: myRenderer } );
12088 * var circle = L.circle( center, { renderer: myRenderer } );
12092 var Canvas = Renderer.extend({
12093 getEvents: function () {
12094 var events = Renderer.prototype.getEvents.call(this);
12095 events.viewprereset = this._onViewPreReset;
12099 _onViewPreReset: function () {
12100 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
12101 this._postponeUpdatePaths = true;
12104 onAdd: function () {
12105 Renderer.prototype.onAdd.call(this);
12107 // Redraw vectors since canvas is cleared upon removal,
12108 // in case of removing the renderer itself from the map.
12112 _initContainer: function () {
12113 var container = this._container = document.createElement('canvas');
12115 on(container, 'mousemove', throttle(this._onMouseMove, 32, this), this);
12116 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
12117 on(container, 'mouseout', this._handleMouseOut, this);
12119 this._ctx = container.getContext('2d');
12122 _destroyContainer: function () {
12123 cancelAnimFrame(this._redrawRequest);
12125 remove(this._container);
12126 off(this._container);
12127 delete this._container;
12130 _updatePaths: function () {
12131 if (this._postponeUpdatePaths) { return; }
12134 this._redrawBounds = null;
12135 for (var id in this._layers) {
12136 layer = this._layers[id];
12142 _update: function () {
12143 if (this._map._animatingZoom && this._bounds) { return; }
12145 Renderer.prototype._update.call(this);
12147 var b = this._bounds,
12148 container = this._container,
12149 size = b.getSize(),
12150 m = retina ? 2 : 1;
12152 setPosition(container, b.min);
12154 // set canvas size (also clearing it); use double size on retina
12155 container.width = m * size.x;
12156 container.height = m * size.y;
12157 container.style.width = size.x + 'px';
12158 container.style.height = size.y + 'px';
12161 this._ctx.scale(2, 2);
12164 // translate so we use the same path coordinates after canvas element moves
12165 this._ctx.translate(-b.min.x, -b.min.y);
12167 // Tell paths to redraw themselves
12168 this.fire('update');
12171 _reset: function () {
12172 Renderer.prototype._reset.call(this);
12174 if (this._postponeUpdatePaths) {
12175 this._postponeUpdatePaths = false;
12176 this._updatePaths();
12180 _initPath: function (layer) {
12181 this._updateDashArray(layer);
12182 this._layers[stamp(layer)] = layer;
12184 var order = layer._order = {
12186 prev: this._drawLast,
12189 if (this._drawLast) { this._drawLast.next = order; }
12190 this._drawLast = order;
12191 this._drawFirst = this._drawFirst || this._drawLast;
12194 _addPath: function (layer) {
12195 this._requestRedraw(layer);
12198 _removePath: function (layer) {
12199 var order = layer._order;
12200 var next = order.next;
12201 var prev = order.prev;
12206 this._drawLast = prev;
12211 this._drawFirst = next;
12214 delete layer._order;
12216 delete this._layers[stamp(layer)];
12218 this._requestRedraw(layer);
12221 _updatePath: function (layer) {
12222 // Redraw the union of the layer's old pixel
12223 // bounds and the new pixel bounds.
12224 this._extendRedrawBounds(layer);
12227 // The redraw will extend the redraw bounds
12228 // with the new pixel bounds.
12229 this._requestRedraw(layer);
12232 _updateStyle: function (layer) {
12233 this._updateDashArray(layer);
12234 this._requestRedraw(layer);
12237 _updateDashArray: function (layer) {
12238 if (typeof layer.options.dashArray === 'string') {
12239 var parts = layer.options.dashArray.split(/[, ]+/),
12243 for (i = 0; i < parts.length; i++) {
12244 dashValue = Number(parts[i]);
12245 // Ignore dash array containing invalid lengths
12246 if (isNaN(dashValue)) { return; }
12247 dashArray.push(dashValue);
12249 layer.options._dashArray = dashArray;
12251 layer.options._dashArray = layer.options.dashArray;
12255 _requestRedraw: function (layer) {
12256 if (!this._map) { return; }
12258 this._extendRedrawBounds(layer);
12259 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
12262 _extendRedrawBounds: function (layer) {
12263 if (layer._pxBounds) {
12264 var padding = (layer.options.weight || 0) + 1;
12265 this._redrawBounds = this._redrawBounds || new Bounds();
12266 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
12267 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
12271 _redraw: function () {
12272 this._redrawRequest = null;
12274 if (this._redrawBounds) {
12275 this._redrawBounds.min._floor();
12276 this._redrawBounds.max._ceil();
12279 this._clear(); // clear layers in redraw bounds
12280 this._draw(); // draw layers
12282 this._redrawBounds = null;
12285 _clear: function () {
12286 var bounds = this._redrawBounds;
12288 var size = bounds.getSize();
12289 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
12291 this._ctx.clearRect(0, 0, this._container.width, this._container.height);
12295 _draw: function () {
12296 var layer, bounds = this._redrawBounds;
12299 var size = bounds.getSize();
12300 this._ctx.beginPath();
12301 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
12305 this._drawing = true;
12307 for (var order = this._drawFirst; order; order = order.next) {
12308 layer = order.layer;
12309 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
12310 layer._updatePath();
12314 this._drawing = false;
12316 this._ctx.restore(); // Restore state before clipping.
12319 _updatePoly: function (layer, closed) {
12320 if (!this._drawing) { return; }
12323 parts = layer._parts,
12324 len = parts.length,
12327 if (!len) { return; }
12331 for (i = 0; i < len; i++) {
12332 for (j = 0, len2 = parts[i].length; j < len2; j++) {
12334 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
12341 this._fillStroke(ctx, layer);
12343 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
12346 _updateCircle: function (layer) {
12348 if (!this._drawing || layer._empty()) { return; }
12350 var p = layer._point,
12352 r = Math.max(Math.round(layer._radius), 1),
12353 s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
12361 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
12367 this._fillStroke(ctx, layer);
12370 _fillStroke: function (ctx, layer) {
12371 var options = layer.options;
12373 if (options.fill) {
12374 ctx.globalAlpha = options.fillOpacity;
12375 ctx.fillStyle = options.fillColor || options.color;
12376 ctx.fill(options.fillRule || 'evenodd');
12379 if (options.stroke && options.weight !== 0) {
12380 if (ctx.setLineDash) {
12381 ctx.setLineDash(layer.options && layer.options._dashArray || []);
12383 ctx.globalAlpha = options.opacity;
12384 ctx.lineWidth = options.weight;
12385 ctx.strokeStyle = options.color;
12386 ctx.lineCap = options.lineCap;
12387 ctx.lineJoin = options.lineJoin;
12392 // Canvas obviously doesn't have mouse events for individual drawn objects,
12393 // so we emulate that by calculating what's under the mouse on mousemove/click manually
12395 _onClick: function (e) {
12396 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
12398 for (var order = this._drawFirst; order; order = order.next) {
12399 layer = order.layer;
12400 if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
12401 clickedLayer = layer;
12404 if (clickedLayer) {
12406 this._fireEvent([clickedLayer], e);
12410 _onMouseMove: function (e) {
12411 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
12413 var point = this._map.mouseEventToLayerPoint(e);
12414 this._handleMouseHover(e, point);
12418 _handleMouseOut: function (e) {
12419 var layer = this._hoveredLayer;
12421 // if we're leaving the layer, fire mouseout
12422 removeClass(this._container, 'leaflet-interactive');
12423 this._fireEvent([layer], e, 'mouseout');
12424 this._hoveredLayer = null;
12428 _handleMouseHover: function (e, point) {
12429 var layer, candidateHoveredLayer;
12431 for (var order = this._drawFirst; order; order = order.next) {
12432 layer = order.layer;
12433 if (layer.options.interactive && layer._containsPoint(point)) {
12434 candidateHoveredLayer = layer;
12438 if (candidateHoveredLayer !== this._hoveredLayer) {
12439 this._handleMouseOut(e);
12441 if (candidateHoveredLayer) {
12442 addClass(this._container, 'leaflet-interactive'); // change cursor
12443 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
12444 this._hoveredLayer = candidateHoveredLayer;
12448 if (this._hoveredLayer) {
12449 this._fireEvent([this._hoveredLayer], e);
12453 _fireEvent: function (layers, e, type) {
12454 this._map._fireDOMEvent(e, type || e.type, layers);
12457 _bringToFront: function (layer) {
12458 var order = layer._order;
12460 if (!order) { return; }
12462 var next = order.next;
12463 var prev = order.prev;
12474 // Update first entry unless this is the
12476 this._drawFirst = next;
12479 order.prev = this._drawLast;
12480 this._drawLast.next = order;
12483 this._drawLast = order;
12485 this._requestRedraw(layer);
12488 _bringToBack: function (layer) {
12489 var order = layer._order;
12491 if (!order) { return; }
12493 var next = order.next;
12494 var prev = order.prev;
12505 // Update last entry unless this is the
12507 this._drawLast = prev;
12512 order.next = this._drawFirst;
12513 this._drawFirst.prev = order;
12514 this._drawFirst = order;
12516 this._requestRedraw(layer);
12520 // @factory L.canvas(options?: Renderer options)
12521 // Creates a Canvas renderer with the given options.
12522 function canvas$1(options) {
12523 return canvas ? new Canvas(options) : null;
12527 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
12531 var vmlCreate = (function () {
12533 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
12534 return function (name) {
12535 return document.createElement('<lvml:' + name + ' class="lvml">');
12538 return function (name) {
12539 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
12549 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
12550 * with old versions of Internet Explorer.
12553 // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
12556 _initContainer: function () {
12557 this._container = create$1('div', 'leaflet-vml-container');
12560 _update: function () {
12561 if (this._map._animatingZoom) { return; }
12562 Renderer.prototype._update.call(this);
12563 this.fire('update');
12566 _initPath: function (layer) {
12567 var container = layer._container = vmlCreate('shape');
12569 addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
12571 container.coordsize = '1 1';
12573 layer._path = vmlCreate('path');
12574 container.appendChild(layer._path);
12576 this._updateStyle(layer);
12577 this._layers[stamp(layer)] = layer;
12580 _addPath: function (layer) {
12581 var container = layer._container;
12582 this._container.appendChild(container);
12584 if (layer.options.interactive) {
12585 layer.addInteractiveTarget(container);
12589 _removePath: function (layer) {
12590 var container = layer._container;
12592 layer.removeInteractiveTarget(container);
12593 delete this._layers[stamp(layer)];
12596 _updateStyle: function (layer) {
12597 var stroke = layer._stroke,
12598 fill = layer._fill,
12599 options = layer.options,
12600 container = layer._container;
12602 container.stroked = !!options.stroke;
12603 container.filled = !!options.fill;
12605 if (options.stroke) {
12607 stroke = layer._stroke = vmlCreate('stroke');
12609 container.appendChild(stroke);
12610 stroke.weight = options.weight + 'px';
12611 stroke.color = options.color;
12612 stroke.opacity = options.opacity;
12614 if (options.dashArray) {
12615 stroke.dashStyle = isArray(options.dashArray) ?
12616 options.dashArray.join(' ') :
12617 options.dashArray.replace(/( *, *)/g, ' ');
12619 stroke.dashStyle = '';
12621 stroke.endcap = options.lineCap.replace('butt', 'flat');
12622 stroke.joinstyle = options.lineJoin;
12624 } else if (stroke) {
12625 container.removeChild(stroke);
12626 layer._stroke = null;
12629 if (options.fill) {
12631 fill = layer._fill = vmlCreate('fill');
12633 container.appendChild(fill);
12634 fill.color = options.fillColor || options.color;
12635 fill.opacity = options.fillOpacity;
12638 container.removeChild(fill);
12639 layer._fill = null;
12643 _updateCircle: function (layer) {
12644 var p = layer._point.round(),
12645 r = Math.round(layer._radius),
12646 r2 = Math.round(layer._radiusY || r);
12648 this._setPath(layer, layer._empty() ? 'M0 0' :
12649 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
12652 _setPath: function (layer, path) {
12653 layer._path.v = path;
12656 _bringToFront: function (layer) {
12657 toFront(layer._container);
12660 _bringToBack: function (layer) {
12661 toBack(layer._container);
12665 var create$2 = vml ? vmlCreate : svgCreate;
12669 * @inherits Renderer
12672 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
12673 * Inherits `Renderer`.
12675 * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
12676 * available in all web browsers, notably Android 2.x and 3.x.
12678 * Although SVG is not available on IE7 and IE8, these browsers support
12679 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
12680 * (a now deprecated technology), and the SVG renderer will fall back to VML in
12685 * Use SVG by default for all paths in the map:
12688 * var map = L.map('map', {
12689 * renderer: L.svg()
12693 * Use a SVG renderer with extra padding for specific vector geometries:
12696 * var map = L.map('map');
12697 * var myRenderer = L.svg({ padding: 0.5 });
12698 * var line = L.polyline( coordinates, { renderer: myRenderer } );
12699 * var circle = L.circle( center, { renderer: myRenderer } );
12703 var SVG = Renderer.extend({
12705 getEvents: function () {
12706 var events = Renderer.prototype.getEvents.call(this);
12707 events.zoomstart = this._onZoomStart;
12711 _initContainer: function () {
12712 this._container = create$2('svg');
12714 // makes it possible to click through svg root; we'll reset it back in individual paths
12715 this._container.setAttribute('pointer-events', 'none');
12717 this._rootGroup = create$2('g');
12718 this._container.appendChild(this._rootGroup);
12721 _destroyContainer: function () {
12722 remove(this._container);
12723 off(this._container);
12724 delete this._container;
12725 delete this._rootGroup;
12726 delete this._svgSize;
12729 _onZoomStart: function () {
12730 // Drag-then-pinch interactions might mess up the center and zoom.
12731 // In this case, the easiest way to prevent this is re-do the renderer
12732 // bounds and padding when the zooming starts.
12736 _update: function () {
12737 if (this._map._animatingZoom && this._bounds) { return; }
12739 Renderer.prototype._update.call(this);
12741 var b = this._bounds,
12742 size = b.getSize(),
12743 container = this._container;
12745 // set size of svg-container if changed
12746 if (!this._svgSize || !this._svgSize.equals(size)) {
12747 this._svgSize = size;
12748 container.setAttribute('width', size.x);
12749 container.setAttribute('height', size.y);
12752 // movement: update container viewBox so that we don't have to change coordinates of individual layers
12753 setPosition(container, b.min);
12754 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
12756 this.fire('update');
12759 // methods below are called by vector layers implementations
12761 _initPath: function (layer) {
12762 var path = layer._path = create$2('path');
12765 // @option className: String = null
12766 // Custom class name set on an element. Only for SVG renderer.
12767 if (layer.options.className) {
12768 addClass(path, layer.options.className);
12771 if (layer.options.interactive) {
12772 addClass(path, 'leaflet-interactive');
12775 this._updateStyle(layer);
12776 this._layers[stamp(layer)] = layer;
12779 _addPath: function (layer) {
12780 if (!this._rootGroup) { this._initContainer(); }
12781 this._rootGroup.appendChild(layer._path);
12782 layer.addInteractiveTarget(layer._path);
12785 _removePath: function (layer) {
12786 remove(layer._path);
12787 layer.removeInteractiveTarget(layer._path);
12788 delete this._layers[stamp(layer)];
12791 _updatePath: function (layer) {
12796 _updateStyle: function (layer) {
12797 var path = layer._path,
12798 options = layer.options;
12800 if (!path) { return; }
12802 if (options.stroke) {
12803 path.setAttribute('stroke', options.color);
12804 path.setAttribute('stroke-opacity', options.opacity);
12805 path.setAttribute('stroke-width', options.weight);
12806 path.setAttribute('stroke-linecap', options.lineCap);
12807 path.setAttribute('stroke-linejoin', options.lineJoin);
12809 if (options.dashArray) {
12810 path.setAttribute('stroke-dasharray', options.dashArray);
12812 path.removeAttribute('stroke-dasharray');
12815 if (options.dashOffset) {
12816 path.setAttribute('stroke-dashoffset', options.dashOffset);
12818 path.removeAttribute('stroke-dashoffset');
12821 path.setAttribute('stroke', 'none');
12824 if (options.fill) {
12825 path.setAttribute('fill', options.fillColor || options.color);
12826 path.setAttribute('fill-opacity', options.fillOpacity);
12827 path.setAttribute('fill-rule', options.fillRule || 'evenodd');
12829 path.setAttribute('fill', 'none');
12833 _updatePoly: function (layer, closed) {
12834 this._setPath(layer, pointsToPath(layer._parts, closed));
12837 _updateCircle: function (layer) {
12838 var p = layer._point,
12839 r = Math.max(Math.round(layer._radius), 1),
12840 r2 = Math.max(Math.round(layer._radiusY), 1) || r,
12841 arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
12843 // drawing a circle with two half-arcs
12844 var d = layer._empty() ? 'M0 0' :
12845 'M' + (p.x - r) + ',' + p.y +
12846 arc + (r * 2) + ',0 ' +
12847 arc + (-r * 2) + ',0 ';
12849 this._setPath(layer, d);
12852 _setPath: function (layer, path) {
12853 layer._path.setAttribute('d', path);
12856 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
12857 _bringToFront: function (layer) {
12858 toFront(layer._path);
12861 _bringToBack: function (layer) {
12862 toBack(layer._path);
12867 SVG.include(vmlMixin);
12871 // @factory L.svg(options?: Renderer options)
12872 // Creates a SVG renderer with the given options.
12873 function svg$1(options) {
12874 return svg || vml ? new SVG(options) : null;
12878 // @namespace Map; @method getRenderer(layer: Path): Renderer
12879 // Returns the instance of `Renderer` that should be used to render the given
12880 // `Path`. It will ensure that the `renderer` options of the map and paths
12881 // are respected, and that the renderers do exist on the map.
12882 getRenderer: function (layer) {
12883 // @namespace Path; @option renderer: Renderer
12884 // Use this specific instance of `Renderer` for this path. Takes
12885 // precedence over the map's [default renderer](#map-renderer).
12886 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
12889 renderer = this._renderer = this._createRenderer();
12892 if (!this.hasLayer(renderer)) {
12893 this.addLayer(renderer);
12898 _getPaneRenderer: function (name) {
12899 if (name === 'overlayPane' || name === undefined) {
12903 var renderer = this._paneRenderers[name];
12904 if (renderer === undefined) {
12905 renderer = this._createRenderer({pane: name});
12906 this._paneRenderers[name] = renderer;
12911 _createRenderer: function (options) {
12912 // @namespace Map; @option preferCanvas: Boolean = false
12913 // Whether `Path`s should be rendered on a `Canvas` renderer.
12914 // By default, all `Path`s are rendered in a `SVG` renderer.
12915 return (this.options.preferCanvas && canvas$1(options)) || svg$1(options);
12920 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
12926 * @inherits Polygon
12928 * A class for drawing rectangle overlays on a map. Extends `Polygon`.
12933 * // define rectangle geographical bounds
12934 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
12936 * // create an orange rectangle
12937 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
12939 * // zoom the map to the rectangle bounds
12940 * map.fitBounds(bounds);
12946 var Rectangle = Polygon.extend({
12947 initialize: function (latLngBounds, options) {
12948 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
12951 // @method setBounds(latLngBounds: LatLngBounds): this
12952 // Redraws the rectangle with the passed bounds.
12953 setBounds: function (latLngBounds) {
12954 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
12957 _boundsToLatLngs: function (latLngBounds) {
12958 latLngBounds = toLatLngBounds(latLngBounds);
12960 latLngBounds.getSouthWest(),
12961 latLngBounds.getNorthWest(),
12962 latLngBounds.getNorthEast(),
12963 latLngBounds.getSouthEast()
12969 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
12970 function rectangle(latLngBounds, options) {
12971 return new Rectangle(latLngBounds, options);
12974 SVG.create = create$2;
12975 SVG.pointsToPath = pointsToPath;
12977 GeoJSON.geometryToLayer = geometryToLayer;
12978 GeoJSON.coordsToLatLng = coordsToLatLng;
12979 GeoJSON.coordsToLatLngs = coordsToLatLngs;
12980 GeoJSON.latLngToCoords = latLngToCoords;
12981 GeoJSON.latLngsToCoords = latLngsToCoords;
12982 GeoJSON.getFeature = getFeature;
12983 GeoJSON.asFeature = asFeature;
12986 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
12987 * (zoom to a selected bounding box), enabled by default.
12991 // @section Interaction Options
12993 // @option boxZoom: Boolean = true
12994 // Whether the map can be zoomed to a rectangular area specified by
12995 // dragging the mouse while pressing the shift key.
12999 var BoxZoom = Handler.extend({
13000 initialize: function (map) {
13002 this._container = map._container;
13003 this._pane = map._panes.overlayPane;
13004 this._resetStateTimeout = 0;
13005 map.on('unload', this._destroy, this);
13008 addHooks: function () {
13009 on(this._container, 'mousedown', this._onMouseDown, this);
13012 removeHooks: function () {
13013 off(this._container, 'mousedown', this._onMouseDown, this);
13016 moved: function () {
13017 return this._moved;
13020 _destroy: function () {
13021 remove(this._pane);
13025 _resetState: function () {
13026 this._resetStateTimeout = 0;
13027 this._moved = false;
13030 _clearDeferredResetState: function () {
13031 if (this._resetStateTimeout !== 0) {
13032 clearTimeout(this._resetStateTimeout);
13033 this._resetStateTimeout = 0;
13037 _onMouseDown: function (e) {
13038 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
13040 // Clear the deferred resetState if it hasn't executed yet, otherwise it
13041 // will interrupt the interaction and orphan a box element in the container.
13042 this._clearDeferredResetState();
13043 this._resetState();
13045 disableTextSelection();
13046 disableImageDrag();
13048 this._startPoint = this._map.mouseEventToContainerPoint(e);
13052 mousemove: this._onMouseMove,
13053 mouseup: this._onMouseUp,
13054 keydown: this._onKeyDown
13058 _onMouseMove: function (e) {
13059 if (!this._moved) {
13060 this._moved = true;
13062 this._box = create$1('div', 'leaflet-zoom-box', this._container);
13063 addClass(this._container, 'leaflet-crosshair');
13065 this._map.fire('boxzoomstart');
13068 this._point = this._map.mouseEventToContainerPoint(e);
13070 var bounds = new Bounds(this._point, this._startPoint),
13071 size = bounds.getSize();
13073 setPosition(this._box, bounds.min);
13075 this._box.style.width = size.x + 'px';
13076 this._box.style.height = size.y + 'px';
13079 _finish: function () {
13082 removeClass(this._container, 'leaflet-crosshair');
13085 enableTextSelection();
13090 mousemove: this._onMouseMove,
13091 mouseup: this._onMouseUp,
13092 keydown: this._onKeyDown
13096 _onMouseUp: function (e) {
13097 if ((e.which !== 1) && (e.button !== 1)) { return; }
13101 if (!this._moved) { return; }
13102 // Postpone to next JS tick so internal click event handling
13103 // still see it as "moved".
13104 this._clearDeferredResetState();
13105 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
13107 var bounds = new LatLngBounds(
13108 this._map.containerPointToLatLng(this._startPoint),
13109 this._map.containerPointToLatLng(this._point));
13113 .fire('boxzoomend', {boxZoomBounds: bounds});
13116 _onKeyDown: function (e) {
13117 if (e.keyCode === 27) {
13123 // @section Handlers
13124 // @property boxZoom: Handler
13125 // Box (shift-drag with mouse) zoom handler.
13126 Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
13129 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
13133 // @section Interaction Options
13136 // @option doubleClickZoom: Boolean|String = true
13137 // Whether the map can be zoomed in by double clicking on it and
13138 // zoomed out by double clicking while holding shift. If passed
13139 // `'center'`, double-click zoom will zoom to the center of the
13140 // view regardless of where the mouse was.
13141 doubleClickZoom: true
13144 var DoubleClickZoom = Handler.extend({
13145 addHooks: function () {
13146 this._map.on('dblclick', this._onDoubleClick, this);
13149 removeHooks: function () {
13150 this._map.off('dblclick', this._onDoubleClick, this);
13153 _onDoubleClick: function (e) {
13154 var map = this._map,
13155 oldZoom = map.getZoom(),
13156 delta = map.options.zoomDelta,
13157 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
13159 if (map.options.doubleClickZoom === 'center') {
13162 map.setZoomAround(e.containerPoint, zoom);
13167 // @section Handlers
13169 // Map properties include interaction handlers that allow you to control
13170 // interaction behavior in runtime, enabling or disabling certain features such
13171 // as dragging or touch zoom (see `Handler` methods). For example:
13174 // map.doubleClickZoom.disable();
13177 // @property doubleClickZoom: Handler
13178 // Double click zoom handler.
13179 Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
13182 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
13186 // @section Interaction Options
13188 // @option dragging: Boolean = true
13189 // Whether the map be draggable with mouse/touch or not.
13192 // @section Panning Inertia Options
13193 // @option inertia: Boolean = *
13194 // If enabled, panning of the map will have an inertia effect where
13195 // the map builds momentum while dragging and continues moving in
13196 // the same direction for some time. Feels especially nice on touch
13197 // devices. Enabled by default unless running on old Android devices.
13198 inertia: !android23,
13200 // @option inertiaDeceleration: Number = 3000
13201 // The rate with which the inertial movement slows down, in pixels/second².
13202 inertiaDeceleration: 3400, // px/s^2
13204 // @option inertiaMaxSpeed: Number = Infinity
13205 // Max speed of the inertial movement, in pixels/second.
13206 inertiaMaxSpeed: Infinity, // px/s
13208 // @option easeLinearity: Number = 0.2
13209 easeLinearity: 0.2,
13211 // TODO refactor, move to CRS
13212 // @option worldCopyJump: Boolean = false
13213 // With this option enabled, the map tracks when you pan to another "copy"
13214 // of the world and seamlessly jumps to the original one so that all overlays
13215 // like markers and vector layers are still visible.
13216 worldCopyJump: false,
13218 // @option maxBoundsViscosity: Number = 0.0
13219 // If `maxBounds` is set, this option will control how solid the bounds
13220 // are when dragging the map around. The default value of `0.0` allows the
13221 // user to drag outside the bounds at normal speed, higher values will
13222 // slow down map dragging outside bounds, and `1.0` makes the bounds fully
13223 // solid, preventing the user from dragging outside the bounds.
13224 maxBoundsViscosity: 0.0
13227 var Drag = Handler.extend({
13228 addHooks: function () {
13229 if (!this._draggable) {
13230 var map = this._map;
13232 this._draggable = new Draggable(map._mapPane, map._container);
13234 this._draggable.on({
13235 dragstart: this._onDragStart,
13236 drag: this._onDrag,
13237 dragend: this._onDragEnd
13240 this._draggable.on('predrag', this._onPreDragLimit, this);
13241 if (map.options.worldCopyJump) {
13242 this._draggable.on('predrag', this._onPreDragWrap, this);
13243 map.on('zoomend', this._onZoomEnd, this);
13245 map.whenReady(this._onZoomEnd, this);
13248 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
13249 this._draggable.enable();
13250 this._positions = [];
13254 removeHooks: function () {
13255 removeClass(this._map._container, 'leaflet-grab');
13256 removeClass(this._map._container, 'leaflet-touch-drag');
13257 this._draggable.disable();
13260 moved: function () {
13261 return this._draggable && this._draggable._moved;
13264 moving: function () {
13265 return this._draggable && this._draggable._moving;
13268 _onDragStart: function () {
13269 var map = this._map;
13272 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
13273 var bounds = toLatLngBounds(this._map.options.maxBounds);
13275 this._offsetLimit = toBounds(
13276 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
13277 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
13278 .add(this._map.getSize()));
13280 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
13282 this._offsetLimit = null;
13287 .fire('dragstart');
13289 if (map.options.inertia) {
13290 this._positions = [];
13295 _onDrag: function (e) {
13296 if (this._map.options.inertia) {
13297 var time = this._lastTime = +new Date(),
13298 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
13300 this._positions.push(pos);
13301 this._times.push(time);
13303 this._prunePositions(time);
13311 _prunePositions: function (time) {
13312 while (this._positions.length > 1 && time - this._times[0] > 50) {
13313 this._positions.shift();
13314 this._times.shift();
13318 _onZoomEnd: function () {
13319 var pxCenter = this._map.getSize().divideBy(2),
13320 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
13322 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
13323 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
13326 _viscousLimit: function (value, threshold) {
13327 return value - (value - threshold) * this._viscosity;
13330 _onPreDragLimit: function () {
13331 if (!this._viscosity || !this._offsetLimit) { return; }
13333 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
13335 var limit = this._offsetLimit;
13336 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
13337 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
13338 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
13339 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
13341 this._draggable._newPos = this._draggable._startPos.add(offset);
13344 _onPreDragWrap: function () {
13345 // TODO refactor to be able to adjust map pane position after zoom
13346 var worldWidth = this._worldWidth,
13347 halfWidth = Math.round(worldWidth / 2),
13348 dx = this._initialWorldOffset,
13349 x = this._draggable._newPos.x,
13350 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
13351 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
13352 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
13354 this._draggable._absPos = this._draggable._newPos.clone();
13355 this._draggable._newPos.x = newX;
13358 _onDragEnd: function (e) {
13359 var map = this._map,
13360 options = map.options,
13362 noInertia = !options.inertia || this._times.length < 2;
13364 map.fire('dragend', e);
13367 map.fire('moveend');
13370 this._prunePositions(+new Date());
13372 var direction = this._lastPos.subtract(this._positions[0]),
13373 duration = (this._lastTime - this._times[0]) / 1000,
13374 ease = options.easeLinearity,
13376 speedVector = direction.multiplyBy(ease / duration),
13377 speed = speedVector.distanceTo([0, 0]),
13379 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
13380 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
13382 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
13383 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
13385 if (!offset.x && !offset.y) {
13386 map.fire('moveend');
13389 offset = map._limitOffset(offset, map.options.maxBounds);
13391 requestAnimFrame(function () {
13392 map.panBy(offset, {
13393 duration: decelerationDuration,
13394 easeLinearity: ease,
13404 // @section Handlers
13405 // @property dragging: Handler
13406 // Map dragging handler (by both mouse and touch).
13407 Map.addInitHook('addHandler', 'dragging', Drag);
13410 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
13414 // @section Keyboard Navigation Options
13416 // @option keyboard: Boolean = true
13417 // Makes the map focusable and allows users to navigate the map with keyboard
13418 // arrows and `+`/`-` keys.
13421 // @option keyboardPanDelta: Number = 80
13422 // Amount of pixels to pan when pressing an arrow key.
13423 keyboardPanDelta: 80
13426 var Keyboard = Handler.extend({
13433 zoomIn: [187, 107, 61, 171],
13434 zoomOut: [189, 109, 54, 173]
13437 initialize: function (map) {
13440 this._setPanDelta(map.options.keyboardPanDelta);
13441 this._setZoomDelta(map.options.zoomDelta);
13444 addHooks: function () {
13445 var container = this._map._container;
13447 // make the container focusable by tabbing
13448 if (container.tabIndex <= 0) {
13449 container.tabIndex = '0';
13453 focus: this._onFocus,
13454 blur: this._onBlur,
13455 mousedown: this._onMouseDown
13459 focus: this._addHooks,
13460 blur: this._removeHooks
13464 removeHooks: function () {
13465 this._removeHooks();
13467 off(this._map._container, {
13468 focus: this._onFocus,
13469 blur: this._onBlur,
13470 mousedown: this._onMouseDown
13474 focus: this._addHooks,
13475 blur: this._removeHooks
13479 _onMouseDown: function () {
13480 if (this._focused) { return; }
13482 var body = document.body,
13483 docEl = document.documentElement,
13484 top = body.scrollTop || docEl.scrollTop,
13485 left = body.scrollLeft || docEl.scrollLeft;
13487 this._map._container.focus();
13489 window.scrollTo(left, top);
13492 _onFocus: function () {
13493 this._focused = true;
13494 this._map.fire('focus');
13497 _onBlur: function () {
13498 this._focused = false;
13499 this._map.fire('blur');
13502 _setPanDelta: function (panDelta) {
13503 var keys = this._panKeys = {},
13504 codes = this.keyCodes,
13507 for (i = 0, len = codes.left.length; i < len; i++) {
13508 keys[codes.left[i]] = [-1 * panDelta, 0];
13510 for (i = 0, len = codes.right.length; i < len; i++) {
13511 keys[codes.right[i]] = [panDelta, 0];
13513 for (i = 0, len = codes.down.length; i < len; i++) {
13514 keys[codes.down[i]] = [0, panDelta];
13516 for (i = 0, len = codes.up.length; i < len; i++) {
13517 keys[codes.up[i]] = [0, -1 * panDelta];
13521 _setZoomDelta: function (zoomDelta) {
13522 var keys = this._zoomKeys = {},
13523 codes = this.keyCodes,
13526 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
13527 keys[codes.zoomIn[i]] = zoomDelta;
13529 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
13530 keys[codes.zoomOut[i]] = -zoomDelta;
13534 _addHooks: function () {
13535 on(document, 'keydown', this._onKeyDown, this);
13538 _removeHooks: function () {
13539 off(document, 'keydown', this._onKeyDown, this);
13542 _onKeyDown: function (e) {
13543 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
13545 var key = e.keyCode,
13549 if (key in this._panKeys) {
13550 if (!map._panAnim || !map._panAnim._inProgress) {
13551 offset = this._panKeys[key];
13553 offset = toPoint(offset).multiplyBy(3);
13558 if (map.options.maxBounds) {
13559 map.panInsideBounds(map.options.maxBounds);
13562 } else if (key in this._zoomKeys) {
13563 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
13565 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
13576 // @section Handlers
13577 // @section Handlers
13578 // @property keyboard: Handler
13579 // Keyboard navigation handler.
13580 Map.addInitHook('addHandler', 'keyboard', Keyboard);
13583 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
13587 // @section Interaction Options
13589 // @section Mousewheel options
13590 // @option scrollWheelZoom: Boolean|String = true
13591 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
13592 // it will zoom to the center of the view regardless of where the mouse was.
13593 scrollWheelZoom: true,
13595 // @option wheelDebounceTime: Number = 40
13596 // Limits the rate at which a wheel can fire (in milliseconds). By default
13597 // user can't zoom via wheel more often than once per 40 ms.
13598 wheelDebounceTime: 40,
13600 // @option wheelPxPerZoomLevel: Number = 60
13601 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
13602 // mean a change of one full zoom level. Smaller values will make wheel-zooming
13603 // faster (and vice versa).
13604 wheelPxPerZoomLevel: 60
13607 var ScrollWheelZoom = Handler.extend({
13608 addHooks: function () {
13609 on(this._map._container, 'mousewheel', this._onWheelScroll, this);
13614 removeHooks: function () {
13615 off(this._map._container, 'mousewheel', this._onWheelScroll, this);
13618 _onWheelScroll: function (e) {
13619 var delta = getWheelDelta(e);
13621 var debounce = this._map.options.wheelDebounceTime;
13623 this._delta += delta;
13624 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
13626 if (!this._startTime) {
13627 this._startTime = +new Date();
13630 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
13632 clearTimeout(this._timer);
13633 this._timer = setTimeout(bind(this._performZoom, this), left);
13638 _performZoom: function () {
13639 var map = this._map,
13640 zoom = map.getZoom(),
13641 snap = this._map.options.zoomSnap || 0;
13643 map._stop(); // stop panning and fly animations if any
13645 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
13646 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
13647 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
13648 d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
13649 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
13652 this._startTime = null;
13654 if (!delta) { return; }
13656 if (map.options.scrollWheelZoom === 'center') {
13657 map.setZoom(zoom + delta);
13659 map.setZoomAround(this._lastMousePos, zoom + delta);
13664 // @section Handlers
13665 // @property scrollWheelZoom: Handler
13666 // Scroll wheel zoom handler.
13667 Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
13670 * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
13674 // @section Interaction Options
13676 // @section Touch interaction options
13677 // @option tap: Boolean = true
13678 // Enables mobile hacks for supporting instant taps (fixing 200ms click
13679 // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
13682 // @option tapTolerance: Number = 15
13683 // The max number of pixels a user can shift his finger during touch
13684 // for it to be considered a valid tap.
13688 var Tap = Handler.extend({
13689 addHooks: function () {
13690 on(this._map._container, 'touchstart', this._onDown, this);
13693 removeHooks: function () {
13694 off(this._map._container, 'touchstart', this._onDown, this);
13697 _onDown: function (e) {
13698 if (!e.touches) { return; }
13702 this._fireClick = true;
13704 // don't simulate click or track longpress if more than 1 touch
13705 if (e.touches.length > 1) {
13706 this._fireClick = false;
13707 clearTimeout(this._holdTimeout);
13711 var first = e.touches[0],
13714 this._startPos = this._newPos = new Point(first.clientX, first.clientY);
13716 // if touching a link, highlight it
13717 if (el.tagName && el.tagName.toLowerCase() === 'a') {
13718 addClass(el, 'leaflet-active');
13721 // simulate long hold but setting a timeout
13722 this._holdTimeout = setTimeout(bind(function () {
13723 if (this._isTapValid()) {
13724 this._fireClick = false;
13726 this._simulateEvent('contextmenu', first);
13730 this._simulateEvent('mousedown', first);
13733 touchmove: this._onMove,
13734 touchend: this._onUp
13738 _onUp: function (e) {
13739 clearTimeout(this._holdTimeout);
13742 touchmove: this._onMove,
13743 touchend: this._onUp
13746 if (this._fireClick && e && e.changedTouches) {
13748 var first = e.changedTouches[0],
13751 if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
13752 removeClass(el, 'leaflet-active');
13755 this._simulateEvent('mouseup', first);
13757 // simulate click if the touch didn't move too much
13758 if (this._isTapValid()) {
13759 this._simulateEvent('click', first);
13764 _isTapValid: function () {
13765 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
13768 _onMove: function (e) {
13769 var first = e.touches[0];
13770 this._newPos = new Point(first.clientX, first.clientY);
13771 this._simulateEvent('mousemove', first);
13774 _simulateEvent: function (type, e) {
13775 var simulatedEvent = document.createEvent('MouseEvents');
13777 simulatedEvent._simulated = true;
13778 e.target._simulatedClick = true;
13780 simulatedEvent.initMouseEvent(
13781 type, true, true, window, 1,
13782 e.screenX, e.screenY,
13783 e.clientX, e.clientY,
13784 false, false, false, false, 0, null);
13786 e.target.dispatchEvent(simulatedEvent);
13790 // @section Handlers
13791 // @property tap: Handler
13792 // Mobile touch hacks (quick tap and touch hold) handler.
13793 if (touch && !pointer) {
13794 Map.addInitHook('addHandler', 'tap', Tap);
13798 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
13802 // @section Interaction Options
13804 // @section Touch interaction options
13805 // @option touchZoom: Boolean|String = *
13806 // Whether the map can be zoomed by touch-dragging with two fingers. If
13807 // passed `'center'`, it will zoom to the center of the view regardless of
13808 // where the touch events (fingers) were. Enabled for touch-capable web
13809 // browsers except for old Androids.
13810 touchZoom: touch && !android23,
13812 // @option bounceAtZoomLimits: Boolean = true
13813 // Set it to false if you don't want the map to zoom beyond min/max zoom
13814 // and then bounce back when pinch-zooming.
13815 bounceAtZoomLimits: true
13818 var TouchZoom = Handler.extend({
13819 addHooks: function () {
13820 addClass(this._map._container, 'leaflet-touch-zoom');
13821 on(this._map._container, 'touchstart', this._onTouchStart, this);
13824 removeHooks: function () {
13825 removeClass(this._map._container, 'leaflet-touch-zoom');
13826 off(this._map._container, 'touchstart', this._onTouchStart, this);
13829 _onTouchStart: function (e) {
13830 var map = this._map;
13831 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
13833 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
13834 p2 = map.mouseEventToContainerPoint(e.touches[1]);
13836 this._centerPoint = map.getSize()._divideBy(2);
13837 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
13838 if (map.options.touchZoom !== 'center') {
13839 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
13842 this._startDist = p1.distanceTo(p2);
13843 this._startZoom = map.getZoom();
13845 this._moved = false;
13846 this._zooming = true;
13850 on(document, 'touchmove', this._onTouchMove, this);
13851 on(document, 'touchend', this._onTouchEnd, this);
13856 _onTouchMove: function (e) {
13857 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
13859 var map = this._map,
13860 p1 = map.mouseEventToContainerPoint(e.touches[0]),
13861 p2 = map.mouseEventToContainerPoint(e.touches[1]),
13862 scale = p1.distanceTo(p2) / this._startDist;
13864 this._zoom = map.getScaleZoom(scale, this._startZoom);
13866 if (!map.options.bounceAtZoomLimits && (
13867 (this._zoom < map.getMinZoom() && scale < 1) ||
13868 (this._zoom > map.getMaxZoom() && scale > 1))) {
13869 this._zoom = map._limitZoom(this._zoom);
13872 if (map.options.touchZoom === 'center') {
13873 this._center = this._startLatLng;
13874 if (scale === 1) { return; }
13876 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
13877 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
13878 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
13879 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
13882 if (!this._moved) {
13883 map._moveStart(true, false);
13884 this._moved = true;
13887 cancelAnimFrame(this._animRequest);
13889 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
13890 this._animRequest = requestAnimFrame(moveFn, this, true);
13895 _onTouchEnd: function () {
13896 if (!this._moved || !this._zooming) {
13897 this._zooming = false;
13901 this._zooming = false;
13902 cancelAnimFrame(this._animRequest);
13904 off(document, 'touchmove', this._onTouchMove);
13905 off(document, 'touchend', this._onTouchEnd);
13907 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
13908 if (this._map.options.zoomAnimation) {
13909 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
13911 this._map._resetView(this._center, this._map._limitZoom(this._zoom));
13916 // @section Handlers
13917 // @property touchZoom: Handler
13918 // Touch zoom handler.
13919 Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
13921 Map.BoxZoom = BoxZoom;
13922 Map.DoubleClickZoom = DoubleClickZoom;
13924 Map.Keyboard = Keyboard;
13925 Map.ScrollWheelZoom = ScrollWheelZoom;
13927 Map.TouchZoom = TouchZoom;
13929 Object.freeze = freeze;
13931 exports.version = version;
13932 exports.Control = Control;
13933 exports.control = control;
13934 exports.Browser = Browser;
13935 exports.Evented = Evented;
13936 exports.Mixin = Mixin;
13937 exports.Util = Util;
13938 exports.Class = Class;
13939 exports.Handler = Handler;
13940 exports.extend = extend;
13941 exports.bind = bind;
13942 exports.stamp = stamp;
13943 exports.setOptions = setOptions;
13944 exports.DomEvent = DomEvent;
13945 exports.DomUtil = DomUtil;
13946 exports.PosAnimation = PosAnimation;
13947 exports.Draggable = Draggable;
13948 exports.LineUtil = LineUtil;
13949 exports.PolyUtil = PolyUtil;
13950 exports.Point = Point;
13951 exports.point = toPoint;
13952 exports.Bounds = Bounds;
13953 exports.bounds = toBounds;
13954 exports.Transformation = Transformation;
13955 exports.transformation = toTransformation;
13956 exports.Projection = index;
13957 exports.LatLng = LatLng;
13958 exports.latLng = toLatLng;
13959 exports.LatLngBounds = LatLngBounds;
13960 exports.latLngBounds = toLatLngBounds;
13962 exports.GeoJSON = GeoJSON;
13963 exports.geoJSON = geoJSON;
13964 exports.geoJson = geoJson;
13965 exports.Layer = Layer;
13966 exports.LayerGroup = LayerGroup;
13967 exports.layerGroup = layerGroup;
13968 exports.FeatureGroup = FeatureGroup;
13969 exports.featureGroup = featureGroup;
13970 exports.ImageOverlay = ImageOverlay;
13971 exports.imageOverlay = imageOverlay;
13972 exports.VideoOverlay = VideoOverlay;
13973 exports.videoOverlay = videoOverlay;
13974 exports.SVGOverlay = SVGOverlay;
13975 exports.svgOverlay = svgOverlay;
13976 exports.DivOverlay = DivOverlay;
13977 exports.Popup = Popup;
13978 exports.popup = popup;
13979 exports.Tooltip = Tooltip;
13980 exports.tooltip = tooltip;
13981 exports.Icon = Icon;
13982 exports.icon = icon;
13983 exports.DivIcon = DivIcon;
13984 exports.divIcon = divIcon;
13985 exports.Marker = Marker;
13986 exports.marker = marker;
13987 exports.TileLayer = TileLayer;
13988 exports.tileLayer = tileLayer;
13989 exports.GridLayer = GridLayer;
13990 exports.gridLayer = gridLayer;
13992 exports.svg = svg$1;
13993 exports.Renderer = Renderer;
13994 exports.Canvas = Canvas;
13995 exports.canvas = canvas$1;
13996 exports.Path = Path;
13997 exports.CircleMarker = CircleMarker;
13998 exports.circleMarker = circleMarker;
13999 exports.Circle = Circle;
14000 exports.circle = circle;
14001 exports.Polyline = Polyline;
14002 exports.polyline = polyline;
14003 exports.Polygon = Polygon;
14004 exports.polygon = polygon;
14005 exports.Rectangle = Rectangle;
14006 exports.rectangle = rectangle;
14008 exports.map = createMap;
14010 var oldL = window.L;
14011 exports.noConflict = function() {
14016 // Always export us to window global (see #2364)
14017 window.L = exports;
14020 //# sourceMappingURL=leaflet-src.js.map