2 * Leaflet 1.6.0, a JS library for interactive maps. http://leafletjs.com
3 * (c) 2010-2019 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.6.0";
17 * Various utility functions, used by Leaflet internally.
20 var freeze = Object.freeze;
21 Object.freeze = function (obj) { return obj; };
23 // @function extend(dest: Object, src?: Object): Object
24 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
25 function extend(dest) {
28 for (j = 1, len = arguments.length; j < len; j++) {
37 // @function create(proto: Object, properties?: Object): Object
38 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
39 var create = Object.create || (function () {
41 return function (proto) {
47 // @function bind(fn: Function, …): Function
48 // Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
49 // Has a `L.bind()` shortcut.
50 function bind(fn, obj) {
51 var slice = Array.prototype.slice;
54 return fn.bind.apply(fn, slice.call(arguments, 1));
57 var args = slice.call(arguments, 2);
60 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
64 // @property lastId: Number
65 // Last unique ID used by [`stamp()`](#util-stamp)
68 // @function stamp(obj: Object): Number
69 // Returns the unique ID of an object, assigning it one if it doesn't have it.
72 obj._leaflet_id = obj._leaflet_id || ++lastId;
73 return obj._leaflet_id;
77 // @function throttle(fn: Function, time: Number, context: Object): Function
78 // Returns a function which executes function `fn` with the given scope `context`
79 // (so that the `this` keyword refers to `context` inside `fn`'s code). The function
80 // `fn` will be called no more than one time per given amount of `time`. The arguments
81 // received by the bound function will be any arguments passed when binding the
82 // function, followed by any arguments passed when invoking the bound function.
83 // Has an `L.throttle` shortcut.
84 function throttle(fn, time, context) {
85 var lock, args, wrapperFn, later;
88 // reset lock and call if queued
91 wrapperFn.apply(context, args);
96 wrapperFn = function () {
98 // called too soon, queue to call later
102 // call and lock until later
103 fn.apply(context, arguments);
104 setTimeout(later, time);
112 // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
113 // Returns the number `num` modulo `range` in such a way so it lies within
114 // `range[0]` and `range[1]`. The returned value will be always smaller than
115 // `range[1]` unless `includeMax` is set to `true`.
116 function wrapNum(x, range, includeMax) {
120 return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
123 // @function falseFn(): Function
124 // Returns a function which always returns `false`.
125 function falseFn() { return false; }
127 // @function formatNum(num: Number, digits?: Number): Number
128 // Returns the number `num` rounded to `digits` decimals, or to 6 decimals by default.
129 function formatNum(num, digits) {
130 var pow = Math.pow(10, (digits === undefined ? 6 : digits));
131 return Math.round(num * pow) / pow;
134 // @function trim(str: String): String
135 // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
137 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
140 // @function splitWords(str: String): String[]
141 // Trims and splits the string on whitespace and returns the array of parts.
142 function splitWords(str) {
143 return trim(str).split(/\s+/);
146 // @function setOptions(obj: Object, options: Object): Object
147 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
148 function setOptions(obj, options) {
149 if (!obj.hasOwnProperty('options')) {
150 obj.options = obj.options ? create(obj.options) : {};
152 for (var i in options) {
153 obj.options[i] = options[i];
158 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
159 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
160 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
161 // be appended at the end. If `uppercase` is `true`, the parameter names will
162 // be uppercased (e.g. `'?A=foo&B=bar'`)
163 function getParamString(obj, existingUrl, uppercase) {
166 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
168 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
171 var templateRe = /\{ *([\w_-]+) *\}/g;
173 // @function template(str: String, data: Object): String
174 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
175 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
176 // `('Hello foo, bar')`. You can also specify functions instead of strings for
177 // data values — they will be evaluated passing `data` as an argument.
178 function template(str, data) {
179 return str.replace(templateRe, function (str, key) {
180 var value = data[key];
182 if (value === undefined) {
183 throw new Error('No value provided for variable ' + str);
185 } else if (typeof value === 'function') {
192 // @function isArray(obj): Boolean
193 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
194 var isArray = Array.isArray || function (obj) {
195 return (Object.prototype.toString.call(obj) === '[object Array]');
198 // @function indexOf(array: Array, el: Object): Number
199 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
200 function indexOf(array, el) {
201 for (var i = 0; i < array.length; i++) {
202 if (array[i] === el) { return i; }
207 // @property emptyImageUrl: String
208 // Data URI string containing a base64-encoded empty GIF image.
209 // Used as a hack to free memory from unused images on WebKit-powered
210 // mobile devices (by setting image `src` to this string).
211 var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
213 // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
215 function getPrefixed(name) {
216 return window['webkit' + name] || window['moz' + name] || window['ms' + name];
221 // fallback for IE 7-8
222 function timeoutDefer(fn) {
223 var time = +new Date(),
224 timeToCall = Math.max(0, 16 - (time - lastTime));
226 lastTime = time + timeToCall;
227 return window.setTimeout(fn, timeToCall);
230 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
231 var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
232 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
234 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
235 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
236 // `context` if given. When `immediate` is set, `fn` is called immediately if
237 // the browser doesn't have native support for
238 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
239 // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
240 function requestAnimFrame(fn, context, immediate) {
241 if (immediate && requestFn === timeoutDefer) {
244 return requestFn.call(window, bind(fn, context));
248 // @function cancelAnimFrame(id: Number): undefined
249 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
250 function cancelAnimFrame(id) {
252 cancelFn.call(window, id);
257 var Util = (Object.freeze || Object)({
267 formatNum: formatNum,
269 splitWords: splitWords,
270 setOptions: setOptions,
271 getParamString: getParamString,
275 emptyImageUrl: emptyImageUrl,
276 requestFn: requestFn,
278 requestAnimFrame: requestAnimFrame,
279 cancelAnimFrame: cancelAnimFrame
288 // Thanks to John Resig and Dean Edwards for inspiration!
292 Class.extend = function (props) {
294 // @function extend(props: Object): Function
295 // [Extends the current class](#class-inheritance) given the properties to be included.
296 // Returns a Javascript function that is a class constructor (to be called with `new`).
297 var NewClass = function () {
299 // call the constructor
300 if (this.initialize) {
301 this.initialize.apply(this, arguments);
304 // call all constructor hooks
305 this.callInitHooks();
308 var parentProto = NewClass.__super__ = this.prototype;
310 var proto = create(parentProto);
311 proto.constructor = NewClass;
313 NewClass.prototype = proto;
315 // inherit parent's statics
316 for (var i in this) {
317 if (this.hasOwnProperty(i) && i !== 'prototype' && i !== '__super__') {
318 NewClass[i] = this[i];
322 // mix static properties into the class
324 extend(NewClass, props.statics);
325 delete props.statics;
328 // mix includes into the prototype
329 if (props.includes) {
330 checkDeprecatedMixinEvents(props.includes);
331 extend.apply(null, [proto].concat(props.includes));
332 delete props.includes;
337 props.options = extend(create(proto.options), props.options);
340 // mix given properties into the prototype
341 extend(proto, props);
343 proto._initHooks = [];
345 // add method for calling all hooks
346 proto.callInitHooks = function () {
348 if (this._initHooksCalled) { return; }
350 if (parentProto.callInitHooks) {
351 parentProto.callInitHooks.call(this);
354 this._initHooksCalled = true;
356 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
357 proto._initHooks[i].call(this);
365 // @function include(properties: Object): this
366 // [Includes a mixin](#class-includes) into the current class.
367 Class.include = function (props) {
368 extend(this.prototype, props);
372 // @function mergeOptions(options: Object): this
373 // [Merges `options`](#class-options) into the defaults of the class.
374 Class.mergeOptions = function (options) {
375 extend(this.prototype.options, options);
379 // @function addInitHook(fn: Function): this
380 // Adds a [constructor hook](#class-constructor-hooks) to the class.
381 Class.addInitHook = function (fn) { // (Function) || (String, args...)
382 var args = Array.prototype.slice.call(arguments, 1);
384 var init = typeof fn === 'function' ? fn : function () {
385 this[fn].apply(this, args);
388 this.prototype._initHooks = this.prototype._initHooks || [];
389 this.prototype._initHooks.push(init);
393 function checkDeprecatedMixinEvents(includes) {
394 if (typeof L === 'undefined' || !L || !L.Mixin) { return; }
396 includes = isArray(includes) ? includes : [includes];
398 for (var i = 0; i < includes.length; i++) {
399 if (includes[i] === L.Mixin.Events) {
400 console.warn('Deprecated include of L.Mixin.Events: ' +
401 'this property will be removed in future releases, ' +
402 'please inherit from L.Evented instead.', new Error().stack);
412 * A set of methods shared between event-powered classes (like `Map` and `Marker`). Generally, events allow you to execute some function when something happens with an object (e.g. the user clicks on the map, causing the map to fire `'click'` event).
417 * map.on('click', function(e) {
422 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
425 * function onClick(e) { ... }
427 * map.on('click', onClick);
428 * map.off('click', onClick);
433 /* @method on(type: String, fn: Function, context?: Object): this
434 * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`).
437 * @method on(eventMap: Object): this
438 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
440 on: function (types, fn, context) {
442 // types can be a map of types/handlers
443 if (typeof types === 'object') {
444 for (var type in types) {
445 // we don't process space-separated events here for performance;
446 // it's a hot path since Layer uses the on(obj) syntax
447 this._on(type, types[type], fn);
451 // types can be a string of space-separated words
452 types = splitWords(types);
454 for (var i = 0, len = types.length; i < len; i++) {
455 this._on(types[i], fn, context);
462 /* @method off(type: String, fn?: Function, context?: Object): this
463 * Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener.
466 * @method off(eventMap: Object): this
467 * Removes a set of type/listener pairs.
471 * Removes all listeners to all events on the object. 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 = !webkit && !!(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;
1930 // @property passiveEvents: Boolean
1931 // `true` for browsers that support passive events.
1932 var passiveEvents = (function () {
1933 var supportsPassiveOption = false;
1935 var opts = Object.defineProperty({}, 'passive', {
1937 supportsPassiveOption = true;
1940 window.addEventListener('testPassiveEventSupport', falseFn, opts);
1941 window.removeEventListener('testPassiveEventSupport', falseFn, opts);
1943 // Errors can safely be ignored since this is only a browser support test.
1945 return supportsPassiveOption;
1948 // @property canvas: Boolean
1949 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
1950 var canvas = (function () {
1951 return !!document.createElement('canvas').getContext;
1954 // @property svg: Boolean
1955 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
1956 var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect);
1958 // @property vml: Boolean
1959 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
1960 var vml = !svg && (function () {
1962 var div = document.createElement('div');
1963 div.innerHTML = '<v:shape adj="1"/>';
1965 var shape = div.firstChild;
1966 shape.style.behavior = 'url(#default#VML)';
1968 return shape && (typeof shape.adj === 'object');
1976 function userAgentContains(str) {
1977 return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
1981 var Browser = (Object.freeze || Object)({
1987 android23: android23,
1988 androidStock: androidStock,
2001 mobileWebkit: mobileWebkit,
2002 mobileWebkit3d: mobileWebkit3d,
2003 msPointer: msPointer,
2006 mobileOpera: mobileOpera,
2007 mobileGecko: mobileGecko,
2009 passiveEvents: passiveEvents,
2016 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
2020 var POINTER_DOWN = msPointer ? 'MSPointerDown' : 'pointerdown';
2021 var POINTER_MOVE = msPointer ? 'MSPointerMove' : 'pointermove';
2022 var POINTER_UP = msPointer ? 'MSPointerUp' : 'pointerup';
2023 var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel';
2024 var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION'];
2027 var _pointerDocListener = false;
2029 // DomEvent.DoubleTap needs to know about this
2030 var _pointersCount = 0;
2032 // Provides a touch events wrapper for (ms)pointer events.
2033 // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
2035 function addPointerListener(obj, type, handler, id) {
2036 if (type === 'touchstart') {
2037 _addPointerStart(obj, handler, id);
2039 } else if (type === 'touchmove') {
2040 _addPointerMove(obj, handler, id);
2042 } else if (type === 'touchend') {
2043 _addPointerEnd(obj, handler, id);
2049 function removePointerListener(obj, type, id) {
2050 var handler = obj['_leaflet_' + type + id];
2052 if (type === 'touchstart') {
2053 obj.removeEventListener(POINTER_DOWN, handler, false);
2055 } else if (type === 'touchmove') {
2056 obj.removeEventListener(POINTER_MOVE, handler, false);
2058 } else if (type === 'touchend') {
2059 obj.removeEventListener(POINTER_UP, handler, false);
2060 obj.removeEventListener(POINTER_CANCEL, handler, false);
2066 function _addPointerStart(obj, handler, id) {
2067 var onDown = bind(function (e) {
2068 if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
2069 // In IE11, some touch events needs to fire for form controls, or
2070 // the controls will stop working. We keep a whitelist of tag names that
2071 // need these events. For other target tags, we prevent default on the event.
2072 if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
2079 _handlePointer(e, handler);
2082 obj['_leaflet_touchstart' + id] = onDown;
2083 obj.addEventListener(POINTER_DOWN, onDown, false);
2085 // need to keep track of what pointers and how many are active to provide e.touches emulation
2086 if (!_pointerDocListener) {
2087 // we listen documentElement as any drags that end by moving the touch off the screen get fired there
2088 document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true);
2089 document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true);
2090 document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true);
2091 document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
2093 _pointerDocListener = true;
2097 function _globalPointerDown(e) {
2098 _pointers[e.pointerId] = e;
2102 function _globalPointerMove(e) {
2103 if (_pointers[e.pointerId]) {
2104 _pointers[e.pointerId] = e;
2108 function _globalPointerUp(e) {
2109 delete _pointers[e.pointerId];
2113 function _handlePointer(e, handler) {
2115 for (var i in _pointers) {
2116 e.touches.push(_pointers[i]);
2118 e.changedTouches = [e];
2123 function _addPointerMove(obj, handler, id) {
2124 var onMove = function (e) {
2125 // don't fire touch moves when mouse isn't down
2126 if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
2128 _handlePointer(e, handler);
2131 obj['_leaflet_touchmove' + id] = onMove;
2132 obj.addEventListener(POINTER_MOVE, onMove, false);
2135 function _addPointerEnd(obj, handler, id) {
2136 var onUp = function (e) {
2137 _handlePointer(e, handler);
2140 obj['_leaflet_touchend' + id] = onUp;
2141 obj.addEventListener(POINTER_UP, onUp, false);
2142 obj.addEventListener(POINTER_CANCEL, onUp, false);
2146 * Extends the event handling code with double tap support for mobile browsers.
2149 var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart';
2150 var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend';
2151 var _pre = '_leaflet_';
2153 // inspired by Zepto touch code by Thomas Fuchs
2154 function addDoubleTapListener(obj, handler, id) {
2159 function onTouchStart(e) {
2163 if ((!edge) || e.pointerType === 'mouse') { return; }
2164 count = _pointersCount;
2166 count = e.touches.length;
2169 if (count > 1) { return; }
2171 var now = Date.now(),
2172 delta = now - (last || now);
2174 touch$$1 = e.touches ? e.touches[0] : e;
2175 doubleTap = (delta > 0 && delta <= delay);
2179 function onTouchEnd(e) {
2180 if (doubleTap && !touch$$1.cancelBubble) {
2182 if ((!edge) || e.pointerType === 'mouse') { return; }
2183 // work around .type being readonly with MSPointer* events
2187 for (i in touch$$1) {
2189 newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop;
2191 touch$$1 = newTouch;
2193 touch$$1.type = 'dblclick';
2194 touch$$1.button = 0;
2200 obj[_pre + _touchstart + id] = onTouchStart;
2201 obj[_pre + _touchend + id] = onTouchEnd;
2202 obj[_pre + 'dblclick' + id] = handler;
2204 obj.addEventListener(_touchstart, onTouchStart, passiveEvents ? {passive: false} : false);
2205 obj.addEventListener(_touchend, onTouchEnd, passiveEvents ? {passive: false} : false);
2207 // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
2208 // the browser doesn't fire touchend/pointerup events but does fire
2209 // native dblclicks. See #4127.
2210 // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
2211 obj.addEventListener('dblclick', handler, false);
2216 function removeDoubleTapListener(obj, id) {
2217 var touchstart = obj[_pre + _touchstart + id],
2218 touchend = obj[_pre + _touchend + id],
2219 dblclick = obj[_pre + 'dblclick' + id];
2221 obj.removeEventListener(_touchstart, touchstart, passiveEvents ? {passive: false} : false);
2222 obj.removeEventListener(_touchend, touchend, passiveEvents ? {passive: false} : false);
2224 obj.removeEventListener('dblclick', dblclick, false);
2231 * @namespace DomUtil
2233 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
2234 * tree, used by Leaflet internally.
2236 * Most functions expecting or returning a `HTMLElement` also work for
2237 * SVG elements. The only difference is that classes refer to CSS classes
2238 * in HTML and SVG classes in SVG.
2242 // @property TRANSFORM: String
2243 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
2244 var TRANSFORM = testProp(
2245 ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
2247 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
2248 // the same for the transitionend event, in particular the Android 4.1 stock browser
2250 // @property TRANSITION: String
2251 // Vendor-prefixed transition style name.
2252 var TRANSITION = testProp(
2253 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
2255 // @property TRANSITION_END: String
2256 // Vendor-prefixed transitionend event name.
2257 var TRANSITION_END =
2258 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
2261 // @function get(id: String|HTMLElement): HTMLElement
2262 // Returns an element given its DOM id, or returns the element itself
2263 // if it was passed directly.
2265 return typeof id === 'string' ? document.getElementById(id) : id;
2268 // @function getStyle(el: HTMLElement, styleAttrib: String): String
2269 // Returns the value for a certain style attribute on an element,
2270 // including computed values or values set through CSS.
2271 function getStyle(el, style) {
2272 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
2274 if ((!value || value === 'auto') && document.defaultView) {
2275 var css = document.defaultView.getComputedStyle(el, null);
2276 value = css ? css[style] : null;
2278 return value === 'auto' ? null : value;
2281 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
2282 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
2283 function create$1(tagName, className, container) {
2284 var el = document.createElement(tagName);
2285 el.className = className || '';
2288 container.appendChild(el);
2293 // @function remove(el: HTMLElement)
2294 // Removes `el` from its parent element
2295 function remove(el) {
2296 var parent = el.parentNode;
2298 parent.removeChild(el);
2302 // @function empty(el: HTMLElement)
2303 // Removes all of `el`'s children elements from `el`
2304 function empty(el) {
2305 while (el.firstChild) {
2306 el.removeChild(el.firstChild);
2310 // @function toFront(el: HTMLElement)
2311 // Makes `el` the last child of its parent, so it renders in front of the other children.
2312 function toFront(el) {
2313 var parent = el.parentNode;
2314 if (parent && parent.lastChild !== el) {
2315 parent.appendChild(el);
2319 // @function toBack(el: HTMLElement)
2320 // Makes `el` the first child of its parent, so it renders behind the other children.
2321 function toBack(el) {
2322 var parent = el.parentNode;
2323 if (parent && parent.firstChild !== el) {
2324 parent.insertBefore(el, parent.firstChild);
2328 // @function hasClass(el: HTMLElement, name: String): Boolean
2329 // Returns `true` if the element's class attribute contains `name`.
2330 function hasClass(el, name) {
2331 if (el.classList !== undefined) {
2332 return el.classList.contains(name);
2334 var className = getClass(el);
2335 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
2338 // @function addClass(el: HTMLElement, name: String)
2339 // Adds `name` to the element's class attribute.
2340 function addClass(el, name) {
2341 if (el.classList !== undefined) {
2342 var classes = splitWords(name);
2343 for (var i = 0, len = classes.length; i < len; i++) {
2344 el.classList.add(classes[i]);
2346 } else if (!hasClass(el, name)) {
2347 var className = getClass(el);
2348 setClass(el, (className ? className + ' ' : '') + name);
2352 // @function removeClass(el: HTMLElement, name: String)
2353 // Removes `name` from the element's class attribute.
2354 function removeClass(el, name) {
2355 if (el.classList !== undefined) {
2356 el.classList.remove(name);
2358 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
2362 // @function setClass(el: HTMLElement, name: String)
2363 // Sets the element's class.
2364 function setClass(el, name) {
2365 if (el.className.baseVal === undefined) {
2366 el.className = name;
2368 // in case of SVG element
2369 el.className.baseVal = name;
2373 // @function getClass(el: HTMLElement): String
2374 // Returns the element's class.
2375 function getClass(el) {
2376 // Check if the element is an SVGElementInstance and use the correspondingElement instead
2377 // (Required for linked SVG elements in IE11.)
2378 if (el.correspondingElement) {
2379 el = el.correspondingElement;
2381 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
2384 // @function setOpacity(el: HTMLElement, opacity: Number)
2385 // Set the opacity of an element (including old IE support).
2386 // `opacity` must be a number from `0` to `1`.
2387 function setOpacity(el, value) {
2388 if ('opacity' in el.style) {
2389 el.style.opacity = value;
2390 } else if ('filter' in el.style) {
2391 _setOpacityIE(el, value);
2395 function _setOpacityIE(el, value) {
2397 filterName = 'DXImageTransform.Microsoft.Alpha';
2399 // filters collection throws an error if we try to retrieve a filter that doesn't exist
2401 filter = el.filters.item(filterName);
2403 // don't set opacity to 1 if we haven't already set an opacity,
2404 // it isn't needed and breaks transparent pngs.
2405 if (value === 1) { return; }
2408 value = Math.round(value * 100);
2411 filter.Enabled = (value !== 100);
2412 filter.Opacity = value;
2414 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
2418 // @function testProp(props: String[]): String|false
2419 // Goes through the array of style names and returns the first name
2420 // that is a valid style name for an element. If no such name is found,
2421 // it returns false. Useful for vendor-prefixed styles like `transform`.
2422 function testProp(props) {
2423 var style = document.documentElement.style;
2425 for (var i = 0; i < props.length; i++) {
2426 if (props[i] in style) {
2433 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
2434 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
2435 // and optionally scaled by `scale`. Does not have an effect if the
2436 // browser doesn't support 3D CSS transforms.
2437 function setTransform(el, offset, scale) {
2438 var pos = offset || new Point(0, 0);
2440 el.style[TRANSFORM] =
2442 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
2443 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
2444 (scale ? ' scale(' + scale + ')' : '');
2447 // @function setPosition(el: HTMLElement, position: Point)
2448 // Sets the position of `el` to coordinates specified by `position`,
2449 // using CSS translate or top/left positioning depending on the browser
2450 // (used by Leaflet internally to position its layers).
2451 function setPosition(el, point) {
2454 el._leaflet_pos = point;
2458 setTransform(el, point);
2460 el.style.left = point.x + 'px';
2461 el.style.top = point.y + 'px';
2465 // @function getPosition(el: HTMLElement): Point
2466 // Returns the coordinates of an element previously positioned with setPosition.
2467 function getPosition(el) {
2468 // this method is only used for elements previously positioned using setPosition,
2469 // so it's safe to cache the position for performance
2471 return el._leaflet_pos || new Point(0, 0);
2474 // @function disableTextSelection()
2475 // Prevents the user from generating `selectstart` DOM events, usually generated
2476 // when the user drags the mouse through a page with text. Used internally
2477 // by Leaflet to override the behaviour of any click-and-drag interaction on
2478 // the map. Affects drag interactions on the whole document.
2480 // @function enableTextSelection()
2481 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
2482 var disableTextSelection;
2483 var enableTextSelection;
2485 if ('onselectstart' in document) {
2486 disableTextSelection = function () {
2487 on(window, 'selectstart', preventDefault);
2489 enableTextSelection = function () {
2490 off(window, 'selectstart', preventDefault);
2493 var userSelectProperty = testProp(
2494 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
2496 disableTextSelection = function () {
2497 if (userSelectProperty) {
2498 var style = document.documentElement.style;
2499 _userSelect = style[userSelectProperty];
2500 style[userSelectProperty] = 'none';
2503 enableTextSelection = function () {
2504 if (userSelectProperty) {
2505 document.documentElement.style[userSelectProperty] = _userSelect;
2506 _userSelect = undefined;
2511 // @function disableImageDrag()
2512 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
2513 // for `dragstart` DOM events, usually generated when the user drags an image.
2514 function disableImageDrag() {
2515 on(window, 'dragstart', preventDefault);
2518 // @function enableImageDrag()
2519 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
2520 function enableImageDrag() {
2521 off(window, 'dragstart', preventDefault);
2524 var _outlineElement;
2526 // @function preventOutline(el: HTMLElement)
2527 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
2528 // of the element `el` invisible. Used internally by Leaflet to prevent
2529 // focusable elements from displaying an outline when the user performs a
2530 // drag interaction on them.
2531 function preventOutline(element) {
2532 while (element.tabIndex === -1) {
2533 element = element.parentNode;
2535 if (!element.style) { return; }
2537 _outlineElement = element;
2538 _outlineStyle = element.style.outline;
2539 element.style.outline = 'none';
2540 on(window, 'keydown', restoreOutline);
2543 // @function restoreOutline()
2544 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
2545 function restoreOutline() {
2546 if (!_outlineElement) { return; }
2547 _outlineElement.style.outline = _outlineStyle;
2548 _outlineElement = undefined;
2549 _outlineStyle = undefined;
2550 off(window, 'keydown', restoreOutline);
2553 // @function getSizedParentNode(el: HTMLElement): HTMLElement
2554 // Finds the closest parent node which size (width and height) is not null.
2555 function getSizedParentNode(element) {
2557 element = element.parentNode;
2558 } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
2562 // @function getScale(el: HTMLElement): Object
2563 // Computes the CSS scale currently applied on the element.
2564 // Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
2565 // and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
2566 function getScale(element) {
2567 var rect = element.getBoundingClientRect(); // Read-only in old browsers.
2570 x: rect.width / element.offsetWidth || 1,
2571 y: rect.height / element.offsetHeight || 1,
2572 boundingClientRect: rect
2577 var DomUtil = (Object.freeze || Object)({
2578 TRANSFORM: TRANSFORM,
2579 TRANSITION: TRANSITION,
2580 TRANSITION_END: TRANSITION_END,
2590 removeClass: removeClass,
2593 setOpacity: setOpacity,
2595 setTransform: setTransform,
2596 setPosition: setPosition,
2597 getPosition: getPosition,
2598 disableTextSelection: disableTextSelection,
2599 enableTextSelection: enableTextSelection,
2600 disableImageDrag: disableImageDrag,
2601 enableImageDrag: enableImageDrag,
2602 preventOutline: preventOutline,
2603 restoreOutline: restoreOutline,
2604 getSizedParentNode: getSizedParentNode,
2609 * @namespace DomEvent
2610 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
2613 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
2615 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
2616 // Adds a listener function (`fn`) to a particular DOM event type of the
2617 // element `el`. You can optionally specify the context of the listener
2618 // (object the `this` keyword will point to). You can also pass several
2619 // space-separated types (e.g. `'click dblclick'`).
2622 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
2623 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2624 function on(obj, types, fn, context) {
2626 if (typeof types === 'object') {
2627 for (var type in types) {
2628 addOne(obj, type, types[type], fn);
2631 types = splitWords(types);
2633 for (var i = 0, len = types.length; i < len; i++) {
2634 addOne(obj, types[i], fn, context);
2641 var eventsKey = '_leaflet_events';
2643 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
2644 // Removes a previously added listener function.
2645 // Note that if you passed a custom context to on, you must pass the same
2646 // context to `off` in order to remove the listener.
2649 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
2650 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2651 function off(obj, types, fn, context) {
2653 if (typeof types === 'object') {
2654 for (var type in types) {
2655 removeOne(obj, type, types[type], fn);
2658 types = splitWords(types);
2660 for (var i = 0, len = types.length; i < len; i++) {
2661 removeOne(obj, types[i], fn, context);
2664 for (var j in obj[eventsKey]) {
2665 removeOne(obj, j, obj[eventsKey][j]);
2667 delete obj[eventsKey];
2673 function addOne(obj, type, fn, context) {
2674 var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
2676 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
2678 var handler = function (e) {
2679 return fn.call(context || obj, e || window.event);
2682 var originalHandler = handler;
2684 if (pointer && type.indexOf('touch') === 0) {
2685 // Needs DomEvent.Pointer.js
2686 addPointerListener(obj, type, handler, id);
2688 } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
2689 !(pointer && chrome)) {
2690 // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
2692 addDoubleTapListener(obj, handler, id);
2694 } else if ('addEventListener' in obj) {
2696 if (type === 'mousewheel') {
2697 obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, passiveEvents ? {passive: false} : false);
2699 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
2700 handler = function (e) {
2701 e = e || window.event;
2702 if (isExternalTarget(obj, e)) {
2706 obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
2709 if (type === 'click' && android) {
2710 handler = function (e) {
2711 filterClick(e, originalHandler);
2714 obj.addEventListener(type, handler, false);
2717 } else if ('attachEvent' in obj) {
2718 obj.attachEvent('on' + type, handler);
2721 obj[eventsKey] = obj[eventsKey] || {};
2722 obj[eventsKey][id] = handler;
2725 function removeOne(obj, type, fn, context) {
2727 var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
2728 handler = obj[eventsKey] && obj[eventsKey][id];
2730 if (!handler) { return this; }
2732 if (pointer && type.indexOf('touch') === 0) {
2733 removePointerListener(obj, type, id);
2735 } else if (touch && (type === 'dblclick') && removeDoubleTapListener &&
2736 !(pointer && chrome)) {
2737 removeDoubleTapListener(obj, id);
2739 } else if ('removeEventListener' in obj) {
2741 if (type === 'mousewheel') {
2742 obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, passiveEvents ? {passive: false} : false);
2745 obj.removeEventListener(
2746 type === 'mouseenter' ? 'mouseover' :
2747 type === 'mouseleave' ? 'mouseout' : type, handler, false);
2750 } else if ('detachEvent' in obj) {
2751 obj.detachEvent('on' + type, handler);
2754 obj[eventsKey][id] = null;
2757 // @function stopPropagation(ev: DOMEvent): this
2758 // Stop the given event from propagation to parent elements. Used inside the listener functions:
2760 // L.DomEvent.on(div, 'click', function (ev) {
2761 // L.DomEvent.stopPropagation(ev);
2764 function stopPropagation(e) {
2766 if (e.stopPropagation) {
2767 e.stopPropagation();
2768 } else if (e.originalEvent) { // In case of Leaflet event.
2769 e.originalEvent._stopped = true;
2771 e.cancelBubble = true;
2778 // @function disableScrollPropagation(el: HTMLElement): this
2779 // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
2780 function disableScrollPropagation(el) {
2781 addOne(el, 'mousewheel', stopPropagation);
2785 // @function disableClickPropagation(el: HTMLElement): this
2786 // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
2787 // `'mousedown'` and `'touchstart'` events (plus browser variants).
2788 function disableClickPropagation(el) {
2789 on(el, 'mousedown touchstart dblclick', stopPropagation);
2790 addOne(el, 'click', fakeStop);
2794 // @function preventDefault(ev: DOMEvent): this
2795 // Prevents the default action of the DOM Event `ev` from happening (such as
2796 // following a link in the href of the a element, or doing a POST request
2797 // with page reload when a `<form>` is submitted).
2798 // Use it inside listener functions.
2799 function preventDefault(e) {
2800 if (e.preventDefault) {
2803 e.returnValue = false;
2808 // @function stop(ev: DOMEvent): this
2809 // Does `stopPropagation` and `preventDefault` at the same time.
2816 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
2817 // Gets normalized mouse position from a DOM event relative to the
2818 // `container` (border excluded) or to the whole page if not specified.
2819 function getMousePosition(e, container) {
2821 return new Point(e.clientX, e.clientY);
2824 var scale = getScale(container),
2825 offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y)
2828 // offset.left/top values are in page scale (like clientX/Y),
2829 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
2830 (e.clientX - offset.left) / scale.x - container.clientLeft,
2831 (e.clientY - offset.top) / scale.y - container.clientTop
2835 // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
2836 // and Firefox scrolls device pixels, not CSS pixels
2838 (win && chrome) ? 2 * window.devicePixelRatio :
2839 gecko ? window.devicePixelRatio : 1;
2841 // @function getWheelDelta(ev: DOMEvent): Number
2842 // Gets normalized wheel delta from a mousewheel DOM event, in vertical
2843 // pixels scrolled (negative if scrolling down).
2844 // Events from pointing devices without precise scrolling are mapped to
2845 // a best guess of 60 pixels.
2846 function getWheelDelta(e) {
2847 return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
2848 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
2849 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
2850 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
2851 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
2852 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
2853 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
2854 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
2858 var skipEvents = {};
2860 function fakeStop(e) {
2861 // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
2862 skipEvents[e.type] = true;
2865 function skipped(e) {
2866 var events = skipEvents[e.type];
2867 // reset when checking, as it's only used in map container and propagates outside of the map
2868 skipEvents[e.type] = false;
2872 // check if element really left/entered the event target (for mouseenter/mouseleave)
2873 function isExternalTarget(el, e) {
2875 var related = e.relatedTarget;
2877 if (!related) { return true; }
2880 while (related && (related !== el)) {
2881 related = related.parentNode;
2886 return (related !== el);
2891 // this is a horrible workaround for a bug in Android where a single touch triggers two click events
2892 function filterClick(e, handler) {
2893 var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
2894 elapsed = lastClick && (timeStamp - lastClick);
2896 // are they closer together than 500ms yet more than 100ms?
2897 // Android typically triggers them ~300ms apart while multiple listeners
2898 // on the same event should be triggered far faster;
2899 // or check if click is simulated on the element, and if it is, reject any non-simulated events
2901 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
2905 lastClick = timeStamp;
2913 var DomEvent = (Object.freeze || Object)({
2916 stopPropagation: stopPropagation,
2917 disableScrollPropagation: disableScrollPropagation,
2918 disableClickPropagation: disableClickPropagation,
2919 preventDefault: preventDefault,
2921 getMousePosition: getMousePosition,
2922 getWheelDelta: getWheelDelta,
2925 isExternalTarget: isExternalTarget,
2931 * @class PosAnimation
2932 * @aka L.PosAnimation
2934 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
2938 * var fx = new L.PosAnimation();
2939 * fx.run(el, [300, 500], 0.5);
2942 * @constructor L.PosAnimation()
2943 * Creates a `PosAnimation` object.
2947 var PosAnimation = Evented.extend({
2949 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
2950 // Run an animation of a given element to a new position, optionally setting
2951 // duration in seconds (`0.25` by default) and easing linearity factor (3rd
2952 // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
2953 // `0.5` by default).
2954 run: function (el, newPos, duration, easeLinearity) {
2958 this._inProgress = true;
2959 this._duration = duration || 0.25;
2960 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
2962 this._startPos = getPosition(el);
2963 this._offset = newPos.subtract(this._startPos);
2964 this._startTime = +new Date();
2966 // @event start: Event
2967 // Fired when the animation starts
2974 // Stops the animation (if currently running).
2976 if (!this._inProgress) { return; }
2982 _animate: function () {
2984 this._animId = requestAnimFrame(this._animate, this);
2988 _step: function (round) {
2989 var elapsed = (+new Date()) - this._startTime,
2990 duration = this._duration * 1000;
2992 if (elapsed < duration) {
2993 this._runFrame(this._easeOut(elapsed / duration), round);
3000 _runFrame: function (progress, round) {
3001 var pos = this._startPos.add(this._offset.multiplyBy(progress));
3005 setPosition(this._el, pos);
3007 // @event step: Event
3008 // Fired continuously during the animation.
3012 _complete: function () {
3013 cancelAnimFrame(this._animId);
3015 this._inProgress = false;
3016 // @event end: Event
3017 // Fired when the animation ends.
3021 _easeOut: function (t) {
3022 return 1 - Math.pow(1 - t, this._easeOutPower);
3031 * The central class of the API — it is used to create a map on a page and manipulate it.
3036 * // initialize the map on the "map" div with a given center and zoom
3037 * var map = L.map('map', {
3038 * center: [51.505, -0.09],
3045 var Map = Evented.extend({
3048 // @section Map State Options
3049 // @option crs: CRS = L.CRS.EPSG3857
3050 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
3051 // sure what it means.
3054 // @option center: LatLng = undefined
3055 // Initial geographic center of the map
3058 // @option zoom: Number = undefined
3059 // Initial map zoom level
3062 // @option minZoom: Number = *
3063 // Minimum zoom level of the map.
3064 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3065 // the lowest of their `minZoom` options will be used instead.
3068 // @option maxZoom: Number = *
3069 // Maximum zoom level of the map.
3070 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3071 // the highest of their `maxZoom` options will be used instead.
3074 // @option layers: Layer[] = []
3075 // Array of layers that will be added to the map initially
3078 // @option maxBounds: LatLngBounds = null
3079 // When this option is set, the map restricts the view to the given
3080 // geographical bounds, bouncing the user back if the user tries to pan
3081 // outside the view. To set the restriction dynamically, use
3082 // [`setMaxBounds`](#map-setmaxbounds) method.
3083 maxBounds: undefined,
3085 // @option renderer: Renderer = *
3086 // The default method for drawing vector layers on the map. `L.SVG`
3087 // or `L.Canvas` by default depending on browser support.
3088 renderer: undefined,
3091 // @section Animation Options
3092 // @option zoomAnimation: Boolean = true
3093 // Whether the map zoom animation is enabled. By default it's enabled
3094 // in all browsers that support CSS3 Transitions except Android.
3095 zoomAnimation: true,
3097 // @option zoomAnimationThreshold: Number = 4
3098 // Won't animate zoom if the zoom difference exceeds this value.
3099 zoomAnimationThreshold: 4,
3101 // @option fadeAnimation: Boolean = true
3102 // Whether the tile fade animation is enabled. By default it's enabled
3103 // in all browsers that support CSS3 Transitions except Android.
3104 fadeAnimation: true,
3106 // @option markerZoomAnimation: Boolean = true
3107 // Whether markers animate their zoom with the zoom animation, if disabled
3108 // they will disappear for the length of the animation. By default it's
3109 // enabled in all browsers that support CSS3 Transitions except Android.
3110 markerZoomAnimation: true,
3112 // @option transform3DLimit: Number = 2^23
3113 // Defines the maximum size of a CSS translation transform. The default
3114 // value should not be changed unless a web browser positions layers in
3115 // the wrong place after doing a large `panBy`.
3116 transform3DLimit: 8388608, // Precision limit of a 32-bit float
3118 // @section Interaction Options
3119 // @option zoomSnap: Number = 1
3120 // Forces the map's zoom level to always be a multiple of this, particularly
3121 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
3122 // By default, the zoom level snaps to the nearest integer; lower values
3123 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
3124 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
3127 // @option zoomDelta: Number = 1
3128 // Controls how much the map's zoom level will change after a
3129 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
3130 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
3131 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
3134 // @option trackResize: Boolean = true
3135 // Whether the map automatically handles browser window resize to update itself.
3139 initialize: function (id, options) { // (HTMLElement or String, Object)
3140 options = setOptions(this, options);
3142 // Make sure to assign internal flags at the beginning,
3143 // to avoid inconsistent state in some edge cases.
3144 this._handlers = [];
3146 this._zoomBoundLayers = {};
3147 this._sizeChanged = true;
3149 this._initContainer(id);
3152 // hack for https://github.com/Leaflet/Leaflet/issues/1980
3153 this._onResize = bind(this._onResize, this);
3157 if (options.maxBounds) {
3158 this.setMaxBounds(options.maxBounds);
3161 if (options.zoom !== undefined) {
3162 this._zoom = this._limitZoom(options.zoom);
3165 if (options.center && options.zoom !== undefined) {
3166 this.setView(toLatLng(options.center), options.zoom, {reset: true});
3169 this.callInitHooks();
3171 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
3172 this._zoomAnimated = TRANSITION && any3d && !mobileOpera &&
3173 this.options.zoomAnimation;
3175 // zoom transitions run with the same duration for all layers, so if one of transitionend events
3176 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
3177 if (this._zoomAnimated) {
3178 this._createAnimProxy();
3179 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
3182 this._addLayers(this.options.layers);
3186 // @section Methods for modifying map state
3188 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
3189 // Sets the view of the map (geographical center and zoom) with the given
3190 // animation options.
3191 setView: function (center, zoom, options) {
3193 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
3194 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
3195 options = options || {};
3199 if (this._loaded && !options.reset && options !== true) {
3201 if (options.animate !== undefined) {
3202 options.zoom = extend({animate: options.animate}, options.zoom);
3203 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
3206 // try animating pan or zoom
3207 var moved = (this._zoom !== zoom) ?
3208 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
3209 this._tryAnimatedPan(center, options.pan);
3212 // prevent resize handler call, the view will refresh after animation anyway
3213 clearTimeout(this._sizeTimer);
3218 // animation didn't start, just reset the map view
3219 this._resetView(center, zoom);
3224 // @method setZoom(zoom: Number, options?: Zoom/pan options): this
3225 // Sets the zoom of the map.
3226 setZoom: function (zoom, options) {
3227 if (!this._loaded) {
3231 return this.setView(this.getCenter(), zoom, {zoom: options});
3234 // @method zoomIn(delta?: Number, options?: Zoom options): this
3235 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3236 zoomIn: function (delta, options) {
3237 delta = delta || (any3d ? this.options.zoomDelta : 1);
3238 return this.setZoom(this._zoom + delta, options);
3241 // @method zoomOut(delta?: Number, options?: Zoom options): this
3242 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3243 zoomOut: function (delta, options) {
3244 delta = delta || (any3d ? this.options.zoomDelta : 1);
3245 return this.setZoom(this._zoom - delta, options);
3248 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
3249 // Zooms the map while keeping a specified geographical point on the map
3250 // stationary (e.g. used internally for scroll zoom and double-click zoom).
3252 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
3253 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
3254 setZoomAround: function (latlng, zoom, options) {
3255 var scale = this.getZoomScale(zoom),
3256 viewHalf = this.getSize().divideBy(2),
3257 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
3259 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
3260 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
3262 return this.setView(newCenter, zoom, {zoom: options});
3265 _getBoundsCenterZoom: function (bounds, options) {
3267 options = options || {};
3268 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
3270 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3271 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3273 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
3275 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
3277 if (zoom === Infinity) {
3279 center: bounds.getCenter(),
3284 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
3286 swPoint = this.project(bounds.getSouthWest(), zoom),
3287 nePoint = this.project(bounds.getNorthEast(), zoom),
3288 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
3296 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
3297 // Sets a map view that contains the given geographical bounds with the
3298 // maximum zoom level possible.
3299 fitBounds: function (bounds, options) {
3301 bounds = toLatLngBounds(bounds);
3303 if (!bounds.isValid()) {
3304 throw new Error('Bounds are not valid.');
3307 var target = this._getBoundsCenterZoom(bounds, options);
3308 return this.setView(target.center, target.zoom, options);
3311 // @method fitWorld(options?: fitBounds options): this
3312 // Sets a map view that mostly contains the whole world with the maximum
3313 // zoom level possible.
3314 fitWorld: function (options) {
3315 return this.fitBounds([[-90, -180], [90, 180]], options);
3318 // @method panTo(latlng: LatLng, options?: Pan options): this
3319 // Pans the map to a given center.
3320 panTo: function (center, options) { // (LatLng)
3321 return this.setView(center, this._zoom, {pan: options});
3324 // @method panBy(offset: Point, options?: Pan options): this
3325 // Pans the map by a given number of pixels (animated).
3326 panBy: function (offset, options) {
3327 offset = toPoint(offset).round();
3328 options = options || {};
3330 if (!offset.x && !offset.y) {
3331 return this.fire('moveend');
3333 // If we pan too far, Chrome gets issues with tiles
3334 // and makes them disappear or appear in the wrong place (slightly offset) #2602
3335 if (options.animate !== true && !this.getSize().contains(offset)) {
3336 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
3340 if (!this._panAnim) {
3341 this._panAnim = new PosAnimation();
3344 'step': this._onPanTransitionStep,
3345 'end': this._onPanTransitionEnd
3349 // don't fire movestart if animating inertia
3350 if (!options.noMoveStart) {
3351 this.fire('movestart');
3354 // animate pan unless animate: false specified
3355 if (options.animate !== false) {
3356 addClass(this._mapPane, 'leaflet-pan-anim');
3358 var newPos = this._getMapPanePos().subtract(offset).round();
3359 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
3361 this._rawPanBy(offset);
3362 this.fire('move').fire('moveend');
3368 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
3369 // Sets the view of the map (geographical center and zoom) performing a smooth
3370 // pan-zoom animation.
3371 flyTo: function (targetCenter, targetZoom, options) {
3373 options = options || {};
3374 if (options.animate === false || !any3d) {
3375 return this.setView(targetCenter, targetZoom, options);
3380 var from = this.project(this.getCenter()),
3381 to = this.project(targetCenter),
3382 size = this.getSize(),
3383 startZoom = this._zoom;
3385 targetCenter = toLatLng(targetCenter);
3386 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
3388 var w0 = Math.max(size.x, size.y),
3389 w1 = w0 * this.getZoomScale(startZoom, targetZoom),
3390 u1 = (to.distanceTo(from)) || 1,
3395 var s1 = i ? -1 : 1,
3397 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
3398 b1 = 2 * s2 * rho2 * u1,
3400 sq = Math.sqrt(b * b + 1) - b;
3402 // workaround for floating point precision bug when sq = 0, log = -Infinite,
3403 // thus triggering an infinite loop in flyTo
3404 var log = sq < 0.000000001 ? -18 : Math.log(sq);
3409 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
3410 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
3411 function tanh(n) { return sinh(n) / cosh(n); }
3415 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
3416 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
3418 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
3420 var start = Date.now(),
3421 S = (r(1) - r0) / rho,
3422 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
3425 var t = (Date.now() - start) / duration,
3429 this._flyToFrame = requestAnimFrame(frame, this);
3432 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
3433 this.getScaleZoom(w0 / w(s), startZoom),
3438 ._move(targetCenter, targetZoom)
3443 this._moveStart(true, options.noMoveStart);
3449 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
3450 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
3451 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
3452 flyToBounds: function (bounds, options) {
3453 var target = this._getBoundsCenterZoom(bounds, options);
3454 return this.flyTo(target.center, target.zoom, options);
3457 // @method setMaxBounds(bounds: Bounds): this
3458 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
3459 setMaxBounds: function (bounds) {
3460 bounds = toLatLngBounds(bounds);
3462 if (!bounds.isValid()) {
3463 this.options.maxBounds = null;
3464 return this.off('moveend', this._panInsideMaxBounds);
3465 } else if (this.options.maxBounds) {
3466 this.off('moveend', this._panInsideMaxBounds);
3469 this.options.maxBounds = bounds;
3472 this._panInsideMaxBounds();
3475 return this.on('moveend', this._panInsideMaxBounds);
3478 // @method setMinZoom(zoom: Number): this
3479 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
3480 setMinZoom: function (zoom) {
3481 var oldZoom = this.options.minZoom;
3482 this.options.minZoom = zoom;
3484 if (this._loaded && oldZoom !== zoom) {
3485 this.fire('zoomlevelschange');
3487 if (this.getZoom() < this.options.minZoom) {
3488 return this.setZoom(zoom);
3495 // @method setMaxZoom(zoom: Number): this
3496 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
3497 setMaxZoom: function (zoom) {
3498 var oldZoom = this.options.maxZoom;
3499 this.options.maxZoom = zoom;
3501 if (this._loaded && oldZoom !== zoom) {
3502 this.fire('zoomlevelschange');
3504 if (this.getZoom() > this.options.maxZoom) {
3505 return this.setZoom(zoom);
3512 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
3513 // 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.
3514 panInsideBounds: function (bounds, options) {
3515 this._enforcingBounds = true;
3516 var center = this.getCenter(),
3517 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
3519 if (!center.equals(newCenter)) {
3520 this.panTo(newCenter, options);
3523 this._enforcingBounds = false;
3527 // @method panInside(latlng: LatLng, options?: options): this
3528 // Pans the map the minimum amount to make the `latlng` visible. Use
3529 // `padding`, `paddingTopLeft` and `paddingTopRight` options to fit
3530 // the display to more restricted bounds, like [`fitBounds`](#map-fitbounds).
3531 // If `latlng` is already within the (optionally padded) display bounds,
3532 // the map will not be panned.
3533 panInside: function (latlng, options) {
3534 options = options || {};
3536 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3537 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3538 center = this.getCenter(),
3539 pixelCenter = this.project(center),
3540 pixelPoint = this.project(latlng),
3541 pixelBounds = this.getPixelBounds(),
3542 halfPixelBounds = pixelBounds.getSize().divideBy(2),
3543 paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]);
3545 if (!paddedBounds.contains(pixelPoint)) {
3546 this._enforcingBounds = true;
3547 var diff = pixelCenter.subtract(pixelPoint),
3548 newCenter = toPoint(pixelPoint.x + diff.x, pixelPoint.y + diff.y);
3550 if (pixelPoint.x < paddedBounds.min.x || pixelPoint.x > paddedBounds.max.x) {
3551 newCenter.x = pixelCenter.x - diff.x;
3553 newCenter.x += halfPixelBounds.x - paddingTL.x;
3555 newCenter.x -= halfPixelBounds.x - paddingBR.x;
3558 if (pixelPoint.y < paddedBounds.min.y || pixelPoint.y > paddedBounds.max.y) {
3559 newCenter.y = pixelCenter.y - diff.y;
3561 newCenter.y += halfPixelBounds.y - paddingTL.y;
3563 newCenter.y -= halfPixelBounds.y - paddingBR.y;
3566 this.panTo(this.unproject(newCenter), options);
3567 this._enforcingBounds = false;
3572 // @method invalidateSize(options: Zoom/pan options): this
3573 // Checks if the map container size changed and updates the map if so —
3574 // call it after you've changed the map size dynamically, also animating
3575 // pan by default. If `options.pan` is `false`, panning will not occur.
3576 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
3577 // that it doesn't happen often even if the method is called many
3581 // @method invalidateSize(animate: Boolean): this
3582 // Checks if the map container size changed and updates the map if so —
3583 // call it after you've changed the map size dynamically, also animating
3585 invalidateSize: function (options) {
3586 if (!this._loaded) { return this; }
3591 }, options === true ? {animate: true} : options);
3593 var oldSize = this.getSize();
3594 this._sizeChanged = true;
3595 this._lastCenter = null;
3597 var newSize = this.getSize(),
3598 oldCenter = oldSize.divideBy(2).round(),
3599 newCenter = newSize.divideBy(2).round(),
3600 offset = oldCenter.subtract(newCenter);
3602 if (!offset.x && !offset.y) { return this; }
3604 if (options.animate && options.pan) {
3609 this._rawPanBy(offset);
3614 if (options.debounceMoveend) {
3615 clearTimeout(this._sizeTimer);
3616 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
3618 this.fire('moveend');
3622 // @section Map state change events
3623 // @event resize: ResizeEvent
3624 // Fired when the map is resized.
3625 return this.fire('resize', {
3631 // @section Methods for modifying map state
3632 // @method stop(): this
3633 // Stops the currently running `panTo` or `flyTo` animation, if any.
3635 this.setZoom(this._limitZoom(this._zoom));
3636 if (!this.options.zoomSnap) {
3637 this.fire('viewreset');
3639 return this._stop();
3642 // @section Geolocation methods
3643 // @method locate(options?: Locate options): this
3644 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
3645 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
3646 // and optionally sets the map view to the user's location with respect to
3647 // detection accuracy (or to the world view if geolocation failed).
3648 // Note that, if your page doesn't use HTTPS, this method will fail in
3649 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
3650 // See `Locate options` for more details.
3651 locate: function (options) {
3653 options = this._locateOptions = extend({
3657 // maxZoom: <Number>
3659 // enableHighAccuracy: false
3662 if (!('geolocation' in navigator)) {
3663 this._handleGeolocationError({
3665 message: 'Geolocation not supported.'
3670 var onResponse = bind(this._handleGeolocationResponse, this),
3671 onError = bind(this._handleGeolocationError, this);
3673 if (options.watch) {
3674 this._locationWatchId =
3675 navigator.geolocation.watchPosition(onResponse, onError, options);
3677 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
3682 // @method stopLocate(): this
3683 // Stops watching location previously initiated by `map.locate({watch: true})`
3684 // and aborts resetting the map view if map.locate was called with
3685 // `{setView: true}`.
3686 stopLocate: function () {
3687 if (navigator.geolocation && navigator.geolocation.clearWatch) {
3688 navigator.geolocation.clearWatch(this._locationWatchId);
3690 if (this._locateOptions) {
3691 this._locateOptions.setView = false;
3696 _handleGeolocationError: function (error) {
3698 message = error.message ||
3699 (c === 1 ? 'permission denied' :
3700 (c === 2 ? 'position unavailable' : 'timeout'));
3702 if (this._locateOptions.setView && !this._loaded) {
3706 // @section Location events
3707 // @event locationerror: ErrorEvent
3708 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
3709 this.fire('locationerror', {
3711 message: 'Geolocation error: ' + message + '.'
3715 _handleGeolocationResponse: function (pos) {
3716 var lat = pos.coords.latitude,
3717 lng = pos.coords.longitude,
3718 latlng = new LatLng(lat, lng),
3719 bounds = latlng.toBounds(pos.coords.accuracy * 2),
3720 options = this._locateOptions;
3722 if (options.setView) {
3723 var zoom = this.getBoundsZoom(bounds);
3724 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
3730 timestamp: pos.timestamp
3733 for (var i in pos.coords) {
3734 if (typeof pos.coords[i] === 'number') {
3735 data[i] = pos.coords[i];
3739 // @event locationfound: LocationEvent
3740 // Fired when geolocation (using the [`locate`](#map-locate) method)
3741 // went successfully.
3742 this.fire('locationfound', data);
3745 // TODO Appropriate docs section?
3746 // @section Other Methods
3747 // @method addHandler(name: String, HandlerClass: Function): this
3748 // Adds a new `Handler` to the map, given its name and constructor function.
3749 addHandler: function (name, HandlerClass) {
3750 if (!HandlerClass) { return this; }
3752 var handler = this[name] = new HandlerClass(this);
3754 this._handlers.push(handler);
3756 if (this.options[name]) {
3763 // @method remove(): this
3764 // Destroys the map and clears all related event listeners.
3765 remove: function () {
3767 this._initEvents(true);
3769 if (this._containerId !== this._container._leaflet_id) {
3770 throw new Error('Map container is being reused by another instance');
3774 // throws error in IE6-8
3775 delete this._container._leaflet_id;
3776 delete this._containerId;
3779 this._container._leaflet_id = undefined;
3781 this._containerId = undefined;
3784 if (this._locationWatchId !== undefined) {
3790 remove(this._mapPane);
3792 if (this._clearControlPos) {
3793 this._clearControlPos();
3795 if (this._resizeRequest) {
3796 cancelAnimFrame(this._resizeRequest);
3797 this._resizeRequest = null;
3800 this._clearHandlers();
3803 // @section Map state change events
3804 // @event unload: Event
3805 // Fired when the map is destroyed with [remove](#map-remove) method.
3806 this.fire('unload');
3810 for (i in this._layers) {
3811 this._layers[i].remove();
3813 for (i in this._panes) {
3814 remove(this._panes[i]);
3819 delete this._mapPane;
3820 delete this._renderer;
3825 // @section Other Methods
3826 // @method createPane(name: String, container?: HTMLElement): HTMLElement
3827 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
3828 // then returns it. The pane is created as a child of `container`, or
3829 // as a child of the main map pane if not set.
3830 createPane: function (name, container) {
3831 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3832 pane = create$1('div', className, container || this._mapPane);
3835 this._panes[name] = pane;
3840 // @section Methods for Getting Map State
3842 // @method getCenter(): LatLng
3843 // Returns the geographical center of the map view
3844 getCenter: function () {
3845 this._checkIfLoaded();
3847 if (this._lastCenter && !this._moved()) {
3848 return this._lastCenter;
3850 return this.layerPointToLatLng(this._getCenterLayerPoint());
3853 // @method getZoom(): Number
3854 // Returns the current zoom level of the map view
3855 getZoom: function () {
3859 // @method getBounds(): LatLngBounds
3860 // Returns the geographical bounds visible in the current map view
3861 getBounds: function () {
3862 var bounds = this.getPixelBounds(),
3863 sw = this.unproject(bounds.getBottomLeft()),
3864 ne = this.unproject(bounds.getTopRight());
3866 return new LatLngBounds(sw, ne);
3869 // @method getMinZoom(): Number
3870 // 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.
3871 getMinZoom: function () {
3872 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3875 // @method getMaxZoom(): Number
3876 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3877 getMaxZoom: function () {
3878 return this.options.maxZoom === undefined ?
3879 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3880 this.options.maxZoom;
3883 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
3884 // Returns the maximum zoom level on which the given bounds fit to the map
3885 // view in its entirety. If `inside` (optional) is set to `true`, the method
3886 // instead returns the minimum zoom level on which the map view fits into
3887 // the given bounds in its entirety.
3888 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3889 bounds = toLatLngBounds(bounds);
3890 padding = toPoint(padding || [0, 0]);
3892 var zoom = this.getZoom() || 0,
3893 min = this.getMinZoom(),
3894 max = this.getMaxZoom(),
3895 nw = bounds.getNorthWest(),
3896 se = bounds.getSouthEast(),
3897 size = this.getSize().subtract(padding),
3898 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3899 snap = any3d ? this.options.zoomSnap : 1,
3900 scalex = size.x / boundsSize.x,
3901 scaley = size.y / boundsSize.y,
3902 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
3904 zoom = this.getScaleZoom(scale, zoom);
3907 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3908 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3911 return Math.max(min, Math.min(max, zoom));
3914 // @method getSize(): Point
3915 // Returns the current size of the map container (in pixels).
3916 getSize: function () {
3917 if (!this._size || this._sizeChanged) {
3918 this._size = new Point(
3919 this._container.clientWidth || 0,
3920 this._container.clientHeight || 0);
3922 this._sizeChanged = false;
3924 return this._size.clone();
3927 // @method getPixelBounds(): Bounds
3928 // Returns the bounds of the current map view in projected pixel
3929 // coordinates (sometimes useful in layer and overlay implementations).
3930 getPixelBounds: function (center, zoom) {
3931 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3932 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3935 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3936 // the map pane? "left point of the map layer" can be confusing, specially
3937 // since there can be negative offsets.
3938 // @method getPixelOrigin(): Point
3939 // Returns the projected pixel coordinates of the top left point of
3940 // the map layer (useful in custom layer and overlay implementations).
3941 getPixelOrigin: function () {
3942 this._checkIfLoaded();
3943 return this._pixelOrigin;
3946 // @method getPixelWorldBounds(zoom?: Number): Bounds
3947 // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3948 // If `zoom` is omitted, the map's current zoom level is used.
3949 getPixelWorldBounds: function (zoom) {
3950 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3953 // @section Other Methods
3955 // @method getPane(pane: String|HTMLElement): HTMLElement
3956 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3957 getPane: function (pane) {
3958 return typeof pane === 'string' ? this._panes[pane] : pane;
3961 // @method getPanes(): Object
3962 // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3963 // the panes as values.
3964 getPanes: function () {
3968 // @method getContainer: HTMLElement
3969 // Returns the HTML element that contains the map.
3970 getContainer: function () {
3971 return this._container;
3975 // @section Conversion Methods
3977 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3978 // Returns the scale factor to be applied to a map transition from zoom level
3979 // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3980 getZoomScale: function (toZoom, fromZoom) {
3981 // TODO replace with universal implementation after refactoring projections
3982 var crs = this.options.crs;
3983 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3984 return crs.scale(toZoom) / crs.scale(fromZoom);
3987 // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3988 // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3989 // level and everything is scaled by a factor of `scale`. Inverse of
3990 // [`getZoomScale`](#map-getZoomScale).
3991 getScaleZoom: function (scale, fromZoom) {
3992 var crs = this.options.crs;
3993 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3994 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3995 return isNaN(zoom) ? Infinity : zoom;
3998 // @method project(latlng: LatLng, zoom: Number): Point
3999 // Projects a geographical coordinate `LatLng` according to the projection
4000 // of the map's CRS, then scales it according to `zoom` and the CRS's
4001 // `Transformation`. The result is pixel coordinate relative to
4003 project: function (latlng, zoom) {
4004 zoom = zoom === undefined ? this._zoom : zoom;
4005 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
4008 // @method unproject(point: Point, zoom: Number): LatLng
4009 // Inverse of [`project`](#map-project).
4010 unproject: function (point, zoom) {
4011 zoom = zoom === undefined ? this._zoom : zoom;
4012 return this.options.crs.pointToLatLng(toPoint(point), zoom);
4015 // @method layerPointToLatLng(point: Point): LatLng
4016 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
4017 // returns the corresponding geographical coordinate (for the current zoom level).
4018 layerPointToLatLng: function (point) {
4019 var projectedPoint = toPoint(point).add(this.getPixelOrigin());
4020 return this.unproject(projectedPoint);
4023 // @method latLngToLayerPoint(latlng: LatLng): Point
4024 // Given a geographical coordinate, returns the corresponding pixel coordinate
4025 // relative to the [origin pixel](#map-getpixelorigin).
4026 latLngToLayerPoint: function (latlng) {
4027 var projectedPoint = this.project(toLatLng(latlng))._round();
4028 return projectedPoint._subtract(this.getPixelOrigin());
4031 // @method wrapLatLng(latlng: LatLng): LatLng
4032 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
4033 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
4035 // By default this means longitude is wrapped around the dateline so its
4036 // value is between -180 and +180 degrees.
4037 wrapLatLng: function (latlng) {
4038 return this.options.crs.wrapLatLng(toLatLng(latlng));
4041 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
4042 // Returns a `LatLngBounds` with the same size as the given one, ensuring that
4043 // its center is within the CRS's bounds.
4044 // By default this means the center longitude is wrapped around the dateline so its
4045 // value is between -180 and +180 degrees, and the majority of the bounds
4046 // overlaps the CRS's bounds.
4047 wrapLatLngBounds: function (latlng) {
4048 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
4051 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
4052 // Returns the distance between two geographical coordinates according to
4053 // the map's CRS. By default this measures distance in meters.
4054 distance: function (latlng1, latlng2) {
4055 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
4058 // @method containerPointToLayerPoint(point: Point): Point
4059 // Given a pixel coordinate relative to the map container, returns the corresponding
4060 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
4061 containerPointToLayerPoint: function (point) { // (Point)
4062 return toPoint(point).subtract(this._getMapPanePos());
4065 // @method layerPointToContainerPoint(point: Point): Point
4066 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
4067 // returns the corresponding pixel coordinate relative to the map container.
4068 layerPointToContainerPoint: function (point) { // (Point)
4069 return toPoint(point).add(this._getMapPanePos());
4072 // @method containerPointToLatLng(point: Point): LatLng
4073 // Given a pixel coordinate relative to the map container, returns
4074 // the corresponding geographical coordinate (for the current zoom level).
4075 containerPointToLatLng: function (point) {
4076 var layerPoint = this.containerPointToLayerPoint(toPoint(point));
4077 return this.layerPointToLatLng(layerPoint);
4080 // @method latLngToContainerPoint(latlng: LatLng): Point
4081 // Given a geographical coordinate, returns the corresponding pixel coordinate
4082 // relative to the map container.
4083 latLngToContainerPoint: function (latlng) {
4084 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
4087 // @method mouseEventToContainerPoint(ev: MouseEvent): Point
4088 // Given a MouseEvent object, returns the pixel coordinate relative to the
4089 // map container where the event took place.
4090 mouseEventToContainerPoint: function (e) {
4091 return getMousePosition(e, this._container);
4094 // @method mouseEventToLayerPoint(ev: MouseEvent): Point
4095 // Given a MouseEvent object, returns the pixel coordinate relative to
4096 // the [origin pixel](#map-getpixelorigin) where the event took place.
4097 mouseEventToLayerPoint: function (e) {
4098 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
4101 // @method mouseEventToLatLng(ev: MouseEvent): LatLng
4102 // Given a MouseEvent object, returns geographical coordinate where the
4103 // event took place.
4104 mouseEventToLatLng: function (e) { // (MouseEvent)
4105 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
4109 // map initialization methods
4111 _initContainer: function (id) {
4112 var container = this._container = get(id);
4115 throw new Error('Map container not found.');
4116 } else if (container._leaflet_id) {
4117 throw new Error('Map container is already initialized.');
4120 on(container, 'scroll', this._onScroll, this);
4121 this._containerId = stamp(container);
4124 _initLayout: function () {
4125 var container = this._container;
4127 this._fadeAnimated = this.options.fadeAnimation && any3d;
4129 addClass(container, 'leaflet-container' +
4130 (touch ? ' leaflet-touch' : '') +
4131 (retina ? ' leaflet-retina' : '') +
4132 (ielt9 ? ' leaflet-oldie' : '') +
4133 (safari ? ' leaflet-safari' : '') +
4134 (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
4136 var position = getStyle(container, 'position');
4138 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
4139 container.style.position = 'relative';
4144 if (this._initControlPos) {
4145 this._initControlPos();
4149 _initPanes: function () {
4150 var panes = this._panes = {};
4151 this._paneRenderers = {};
4155 // Panes are DOM elements used to control the ordering of layers on the map. You
4156 // can access panes with [`map.getPane`](#map-getpane) or
4157 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
4158 // [`map.createPane`](#map-createpane) method.
4160 // Every map has the following default panes that differ only in zIndex.
4162 // @pane mapPane: HTMLElement = 'auto'
4163 // Pane that contains all other map panes
4165 this._mapPane = this.createPane('mapPane', this._container);
4166 setPosition(this._mapPane, new Point(0, 0));
4168 // @pane tilePane: HTMLElement = 200
4169 // Pane for `GridLayer`s and `TileLayer`s
4170 this.createPane('tilePane');
4171 // @pane overlayPane: HTMLElement = 400
4172 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
4173 this.createPane('shadowPane');
4174 // @pane shadowPane: HTMLElement = 500
4175 // Pane for overlay shadows (e.g. `Marker` shadows)
4176 this.createPane('overlayPane');
4177 // @pane markerPane: HTMLElement = 600
4178 // Pane for `Icon`s of `Marker`s
4179 this.createPane('markerPane');
4180 // @pane tooltipPane: HTMLElement = 650
4181 // Pane for `Tooltip`s.
4182 this.createPane('tooltipPane');
4183 // @pane popupPane: HTMLElement = 700
4184 // Pane for `Popup`s.
4185 this.createPane('popupPane');
4187 if (!this.options.markerZoomAnimation) {
4188 addClass(panes.markerPane, 'leaflet-zoom-hide');
4189 addClass(panes.shadowPane, 'leaflet-zoom-hide');
4194 // private methods that modify map state
4196 // @section Map state change events
4197 _resetView: function (center, zoom) {
4198 setPosition(this._mapPane, new Point(0, 0));
4200 var loading = !this._loaded;
4201 this._loaded = true;
4202 zoom = this._limitZoom(zoom);
4204 this.fire('viewprereset');
4206 var zoomChanged = this._zoom !== zoom;
4208 ._moveStart(zoomChanged, false)
4209 ._move(center, zoom)
4210 ._moveEnd(zoomChanged);
4212 // @event viewreset: Event
4213 // Fired when the map needs to redraw its content (this usually happens
4214 // on map zoom or load). Very useful for creating custom overlays.
4215 this.fire('viewreset');
4217 // @event load: Event
4218 // Fired when the map is initialized (when its center and zoom are set
4219 // for the first time).
4225 _moveStart: function (zoomChanged, noMoveStart) {
4226 // @event zoomstart: Event
4227 // Fired when the map zoom is about to change (e.g. before zoom animation).
4228 // @event movestart: Event
4229 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
4231 this.fire('zoomstart');
4234 this.fire('movestart');
4239 _move: function (center, zoom, data) {
4240 if (zoom === undefined) {
4243 var zoomChanged = this._zoom !== zoom;
4246 this._lastCenter = center;
4247 this._pixelOrigin = this._getNewPixelOrigin(center);
4249 // @event zoom: Event
4250 // Fired repeatedly during any change in zoom level, including zoom
4251 // and fly animations.
4252 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530
4253 this.fire('zoom', data);
4256 // @event move: Event
4257 // Fired repeatedly during any movement of the map, including pan and
4259 return this.fire('move', data);
4262 _moveEnd: function (zoomChanged) {
4263 // @event zoomend: Event
4264 // Fired when the map has changed, after any animations.
4266 this.fire('zoomend');
4269 // @event moveend: Event
4270 // Fired when the center of the map stops changing (e.g. user stopped
4271 // dragging the map).
4272 return this.fire('moveend');
4275 _stop: function () {
4276 cancelAnimFrame(this._flyToFrame);
4277 if (this._panAnim) {
4278 this._panAnim.stop();
4283 _rawPanBy: function (offset) {
4284 setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
4287 _getZoomSpan: function () {
4288 return this.getMaxZoom() - this.getMinZoom();
4291 _panInsideMaxBounds: function () {
4292 if (!this._enforcingBounds) {
4293 this.panInsideBounds(this.options.maxBounds);
4297 _checkIfLoaded: function () {
4298 if (!this._loaded) {
4299 throw new Error('Set map center and zoom first.');
4303 // DOM event handling
4305 // @section Interaction events
4306 _initEvents: function (remove$$1) {
4308 this._targets[stamp(this._container)] = this;
4310 var onOff = remove$$1 ? off : on;
4312 // @event click: MouseEvent
4313 // Fired when the user clicks (or taps) the map.
4314 // @event dblclick: MouseEvent
4315 // Fired when the user double-clicks (or double-taps) the map.
4316 // @event mousedown: MouseEvent
4317 // Fired when the user pushes the mouse button on the map.
4318 // @event mouseup: MouseEvent
4319 // Fired when the user releases the mouse button on the map.
4320 // @event mouseover: MouseEvent
4321 // Fired when the mouse enters the map.
4322 // @event mouseout: MouseEvent
4323 // Fired when the mouse leaves the map.
4324 // @event mousemove: MouseEvent
4325 // Fired while the mouse moves over the map.
4326 // @event contextmenu: MouseEvent
4327 // Fired when the user pushes the right mouse button on the map, prevents
4328 // default browser context menu from showing if there are listeners on
4329 // this event. Also fired on mobile when the user holds a single touch
4330 // for a second (also called long press).
4331 // @event keypress: KeyboardEvent
4332 // Fired when the user presses a key from the keyboard that produces a character value while the map is focused.
4333 // @event keydown: KeyboardEvent
4334 // Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event,
4335 // the `keydown` event is fired for keys that produce a character value and for keys
4336 // that do not produce a character value.
4337 // @event keyup: KeyboardEvent
4338 // Fired when the user releases a key from the keyboard while the map is focused.
4339 onOff(this._container, 'click dblclick mousedown mouseup ' +
4340 'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this);
4342 if (this.options.trackResize) {
4343 onOff(window, 'resize', this._onResize, this);
4346 if (any3d && this.options.transform3DLimit) {
4347 (remove$$1 ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
4351 _onResize: function () {
4352 cancelAnimFrame(this._resizeRequest);
4353 this._resizeRequest = requestAnimFrame(
4354 function () { this.invalidateSize({debounceMoveend: true}); }, this);
4357 _onScroll: function () {
4358 this._container.scrollTop = 0;
4359 this._container.scrollLeft = 0;
4362 _onMoveEnd: function () {
4363 var pos = this._getMapPanePos();
4364 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
4365 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
4366 // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
4367 this._resetView(this.getCenter(), this.getZoom());
4371 _findEventTargets: function (e, type) {
4374 isHover = type === 'mouseout' || type === 'mouseover',
4375 src = e.target || e.srcElement,
4379 target = this._targets[stamp(src)];
4380 if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
4381 // Prevent firing click after you just dragged an object.
4385 if (target && target.listens(type, true)) {
4386 if (isHover && !isExternalTarget(src, e)) { break; }
4387 targets.push(target);
4388 if (isHover) { break; }
4390 if (src === this._container) { break; }
4391 src = src.parentNode;
4393 if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) {
4399 _handleDOMEvent: function (e) {
4400 if (!this._loaded || skipped(e)) { return; }
4404 if (type === 'mousedown' || type === 'keypress' || type === 'keyup' || type === 'keydown') {
4405 // prevents outline when clicking on keyboard-focusable element
4406 preventOutline(e.target || e.srcElement);
4409 this._fireDOMEvent(e, type);
4412 _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
4414 _fireDOMEvent: function (e, type, targets) {
4416 if (e.type === 'click') {
4417 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
4418 // @event preclick: MouseEvent
4419 // Fired before mouse click on the map (sometimes useful when you
4420 // want something to happen on click before any existing click
4421 // handlers start running).
4422 var synth = extend({}, e);
4423 synth.type = 'preclick';
4424 this._fireDOMEvent(synth, synth.type, targets);
4427 if (e._stopped) { return; }
4429 // Find the layer the event is propagating from and its parents.
4430 targets = (targets || []).concat(this._findEventTargets(e, type));
4432 if (!targets.length) { return; }
4434 var target = targets[0];
4435 if (type === 'contextmenu' && target.listens(type, true)) {
4443 if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') {
4444 var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
4445 data.containerPoint = isMarker ?
4446 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
4447 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
4448 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
4451 for (var i = 0; i < targets.length; i++) {
4452 targets[i].fire(type, data, true);
4453 if (data.originalEvent._stopped ||
4454 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
4458 _draggableMoved: function (obj) {
4459 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
4460 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
4463 _clearHandlers: function () {
4464 for (var i = 0, len = this._handlers.length; i < len; i++) {
4465 this._handlers[i].disable();
4469 // @section Other Methods
4471 // @method whenReady(fn: Function, context?: Object): this
4472 // Runs the given function `fn` when the map gets initialized with
4473 // a view (center and zoom) and at least one layer, or immediately
4474 // if it's already initialized, optionally passing a function context.
4475 whenReady: function (callback, context) {
4477 callback.call(context || this, {target: this});
4479 this.on('load', callback, context);
4485 // private methods for getting map state
4487 _getMapPanePos: function () {
4488 return getPosition(this._mapPane) || new Point(0, 0);
4491 _moved: function () {
4492 var pos = this._getMapPanePos();
4493 return pos && !pos.equals([0, 0]);
4496 _getTopLeftPoint: function (center, zoom) {
4497 var pixelOrigin = center && zoom !== undefined ?
4498 this._getNewPixelOrigin(center, zoom) :
4499 this.getPixelOrigin();
4500 return pixelOrigin.subtract(this._getMapPanePos());
4503 _getNewPixelOrigin: function (center, zoom) {
4504 var viewHalf = this.getSize()._divideBy(2);
4505 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
4508 _latLngToNewLayerPoint: function (latlng, zoom, center) {
4509 var topLeft = this._getNewPixelOrigin(center, zoom);
4510 return this.project(latlng, zoom)._subtract(topLeft);
4513 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
4514 var topLeft = this._getNewPixelOrigin(center, zoom);
4516 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
4517 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
4518 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
4519 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
4523 // layer point of the current center
4524 _getCenterLayerPoint: function () {
4525 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
4528 // offset of the specified place to the current center in pixels
4529 _getCenterOffset: function (latlng) {
4530 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
4533 // adjust center for view to get inside bounds
4534 _limitCenter: function (center, zoom, bounds) {
4536 if (!bounds) { return center; }
4538 var centerPoint = this.project(center, zoom),
4539 viewHalf = this.getSize().divideBy(2),
4540 viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
4541 offset = this._getBoundsOffset(viewBounds, bounds, zoom);
4543 // If offset is less than a pixel, ignore.
4544 // This prevents unstable projections from getting into
4545 // an infinite loop of tiny offsets.
4546 if (offset.round().equals([0, 0])) {
4550 return this.unproject(centerPoint.add(offset), zoom);
4553 // adjust offset for view to get inside bounds
4554 _limitOffset: function (offset, bounds) {
4555 if (!bounds) { return offset; }
4557 var viewBounds = this.getPixelBounds(),
4558 newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
4560 return offset.add(this._getBoundsOffset(newBounds, bounds));
4563 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
4564 _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
4565 var projectedMaxBounds = toBounds(
4566 this.project(maxBounds.getNorthEast(), zoom),
4567 this.project(maxBounds.getSouthWest(), zoom)
4569 minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
4570 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
4572 dx = this._rebound(minOffset.x, -maxOffset.x),
4573 dy = this._rebound(minOffset.y, -maxOffset.y);
4575 return new Point(dx, dy);
4578 _rebound: function (left, right) {
4579 return left + right > 0 ?
4580 Math.round(left - right) / 2 :
4581 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
4584 _limitZoom: function (zoom) {
4585 var min = this.getMinZoom(),
4586 max = this.getMaxZoom(),
4587 snap = any3d ? this.options.zoomSnap : 1;
4589 zoom = Math.round(zoom / snap) * snap;
4591 return Math.max(min, Math.min(max, zoom));
4594 _onPanTransitionStep: function () {
4598 _onPanTransitionEnd: function () {
4599 removeClass(this._mapPane, 'leaflet-pan-anim');
4600 this.fire('moveend');
4603 _tryAnimatedPan: function (center, options) {
4604 // difference between the new and current centers in pixels
4605 var offset = this._getCenterOffset(center)._trunc();
4607 // don't animate too far unless animate: true specified in options
4608 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
4610 this.panBy(offset, options);
4615 _createAnimProxy: function () {
4617 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
4618 this._panes.mapPane.appendChild(proxy);
4620 this.on('zoomanim', function (e) {
4621 var prop = TRANSFORM,
4622 transform = this._proxy.style[prop];
4624 setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
4626 // workaround for case when transform is the same and so transitionend event is not fired
4627 if (transform === this._proxy.style[prop] && this._animatingZoom) {
4628 this._onZoomTransitionEnd();
4632 this.on('load moveend', this._animMoveEnd, this);
4634 this._on('unload', this._destroyAnimProxy, this);
4637 _destroyAnimProxy: function () {
4638 remove(this._proxy);
4639 this.off('load moveend', this._animMoveEnd, this);
4643 _animMoveEnd: function () {
4644 var c = this.getCenter(),
4646 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
4649 _catchTransitionEnd: function (e) {
4650 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
4651 this._onZoomTransitionEnd();
4655 _nothingToAnimate: function () {
4656 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
4659 _tryAnimatedZoom: function (center, zoom, options) {
4661 if (this._animatingZoom) { return true; }
4663 options = options || {};
4665 // don't animate if disabled, not supported or zoom difference is too large
4666 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
4667 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
4669 // offset is the pixel coords of the zoom origin relative to the current center
4670 var scale = this.getZoomScale(zoom),
4671 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
4673 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
4674 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
4676 requestAnimFrame(function () {
4678 ._moveStart(true, false)
4679 ._animateZoom(center, zoom, true);
4685 _animateZoom: function (center, zoom, startAnim, noUpdate) {
4686 if (!this._mapPane) { return; }
4689 this._animatingZoom = true;
4691 // remember what center/zoom to set after animation
4692 this._animateToCenter = center;
4693 this._animateToZoom = zoom;
4695 addClass(this._mapPane, 'leaflet-zoom-anim');
4698 // @section Other Events
4699 // @event zoomanim: ZoomAnimEvent
4700 // Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom.
4701 this.fire('zoomanim', {
4707 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
4708 setTimeout(bind(this._onZoomTransitionEnd, this), 250);
4711 _onZoomTransitionEnd: function () {
4712 if (!this._animatingZoom) { return; }
4714 if (this._mapPane) {
4715 removeClass(this._mapPane, 'leaflet-zoom-anim');
4718 this._animatingZoom = false;
4720 this._move(this._animateToCenter, this._animateToZoom);
4722 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
4723 requestAnimFrame(function () {
4724 this._moveEnd(true);
4731 // @factory L.map(id: String, options?: Map options)
4732 // Instantiates a map object given the DOM ID of a `<div>` element
4733 // and optionally an object literal with `Map options`.
4736 // @factory L.map(el: HTMLElement, options?: Map options)
4737 // Instantiates a map object given an instance of a `<div>` HTML element
4738 // and optionally an object literal with `Map options`.
4739 function createMap(id, options) {
4740 return new Map(id, options);
4748 * L.Control is a base class for implementing map controls. Handles positioning.
4749 * All other controls extend from this class.
4752 var Control = Class.extend({
4754 // @aka Control options
4756 // @option position: String = 'topright'
4757 // The position of the control (one of the map corners). Possible values are `'topleft'`,
4758 // `'topright'`, `'bottomleft'` or `'bottomright'`
4759 position: 'topright'
4762 initialize: function (options) {
4763 setOptions(this, options);
4767 * Classes extending L.Control will inherit the following methods:
4769 * @method getPosition: string
4770 * Returns the position of the control.
4772 getPosition: function () {
4773 return this.options.position;
4776 // @method setPosition(position: string): this
4777 // Sets the position of the control.
4778 setPosition: function (position) {
4779 var map = this._map;
4782 map.removeControl(this);
4785 this.options.position = position;
4788 map.addControl(this);
4794 // @method getContainer: HTMLElement
4795 // Returns the HTMLElement that contains the control.
4796 getContainer: function () {
4797 return this._container;
4800 // @method addTo(map: Map): this
4801 // Adds the control to the given map.
4802 addTo: function (map) {
4806 var container = this._container = this.onAdd(map),
4807 pos = this.getPosition(),
4808 corner = map._controlCorners[pos];
4810 addClass(container, 'leaflet-control');
4812 if (pos.indexOf('bottom') !== -1) {
4813 corner.insertBefore(container, corner.firstChild);
4815 corner.appendChild(container);
4818 this._map.on('unload', this.remove, this);
4823 // @method remove: this
4824 // Removes the control from the map it is currently active on.
4825 remove: function () {
4830 remove(this._container);
4832 if (this.onRemove) {
4833 this.onRemove(this._map);
4836 this._map.off('unload', this.remove, this);
4842 _refocusOnMap: function (e) {
4843 // if map exists and event is not a keyboard event
4844 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
4845 this._map.getContainer().focus();
4850 var control = function (options) {
4851 return new Control(options);
4854 /* @section Extension methods
4857 * Every control should extend from `L.Control` and (re-)implement the following methods.
4859 * @method onAdd(map: Map): HTMLElement
4860 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
4862 * @method onRemove(map: Map)
4863 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
4867 * @section Methods for Layers and Controls
4870 // @method addControl(control: Control): this
4871 // Adds the given control to the map
4872 addControl: function (control) {
4873 control.addTo(this);
4877 // @method removeControl(control: Control): this
4878 // Removes the given control from the map
4879 removeControl: function (control) {
4884 _initControlPos: function () {
4885 var corners = this._controlCorners = {},
4887 container = this._controlContainer =
4888 create$1('div', l + 'control-container', this._container);
4890 function createCorner(vSide, hSide) {
4891 var className = l + vSide + ' ' + l + hSide;
4893 corners[vSide + hSide] = create$1('div', className, container);
4896 createCorner('top', 'left');
4897 createCorner('top', 'right');
4898 createCorner('bottom', 'left');
4899 createCorner('bottom', 'right');
4902 _clearControlPos: function () {
4903 for (var i in this._controlCorners) {
4904 remove(this._controlCorners[i]);
4906 remove(this._controlContainer);
4907 delete this._controlCorners;
4908 delete this._controlContainer;
4913 * @class Control.Layers
4914 * @aka L.Control.Layers
4917 * 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`.
4922 * var baseLayers = {
4924 * "OpenStreetMap": osm
4929 * "Roads": roadsLayer
4932 * L.control.layers(baseLayers, overlays).addTo(map);
4935 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
4939 * "<someName1>": layer1,
4940 * "<someName2>": layer2
4944 * The layer names can contain HTML, which allows you to add additional styling to the items:
4947 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
4951 var Layers = Control.extend({
4953 // @aka Control.Layers options
4955 // @option collapsed: Boolean = true
4956 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
4958 position: 'topright',
4960 // @option autoZIndex: Boolean = true
4961 // 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.
4964 // @option hideSingleBase: Boolean = false
4965 // If `true`, the base layers in the control will be hidden when there is only one.
4966 hideSingleBase: false,
4968 // @option sortLayers: Boolean = false
4969 // Whether to sort the layers. When `false`, layers will keep the order
4970 // in which they were added to the control.
4973 // @option sortFunction: Function = *
4974 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
4975 // that will be used for sorting the layers, when `sortLayers` is `true`.
4976 // The function receives both the `L.Layer` instances and their names, as in
4977 // `sortFunction(layerA, layerB, nameA, nameB)`.
4978 // By default, it sorts layers alphabetically by their name.
4979 sortFunction: function (layerA, layerB, nameA, nameB) {
4980 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
4984 initialize: function (baseLayers, overlays, options) {
4985 setOptions(this, options);
4987 this._layerControlInputs = [];
4989 this._lastZIndex = 0;
4990 this._handlingClick = false;
4992 for (var i in baseLayers) {
4993 this._addLayer(baseLayers[i], i);
4996 for (i in overlays) {
4997 this._addLayer(overlays[i], i, true);
5001 onAdd: function (map) {
5006 map.on('zoomend', this._checkDisabledLayers, this);
5008 for (var i = 0; i < this._layers.length; i++) {
5009 this._layers[i].layer.on('add remove', this._onLayerChange, this);
5012 return this._container;
5015 addTo: function (map) {
5016 Control.prototype.addTo.call(this, map);
5017 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
5018 return this._expandIfNotCollapsed();
5021 onRemove: function () {
5022 this._map.off('zoomend', this._checkDisabledLayers, this);
5024 for (var i = 0; i < this._layers.length; i++) {
5025 this._layers[i].layer.off('add remove', this._onLayerChange, this);
5029 // @method addBaseLayer(layer: Layer, name: String): this
5030 // Adds a base layer (radio button entry) with the given name to the control.
5031 addBaseLayer: function (layer, name) {
5032 this._addLayer(layer, name);
5033 return (this._map) ? this._update() : this;
5036 // @method addOverlay(layer: Layer, name: String): this
5037 // Adds an overlay (checkbox entry) with the given name to the control.
5038 addOverlay: function (layer, name) {
5039 this._addLayer(layer, name, true);
5040 return (this._map) ? this._update() : this;
5043 // @method removeLayer(layer: Layer): this
5044 // Remove the given layer from the control.
5045 removeLayer: function (layer) {
5046 layer.off('add remove', this._onLayerChange, this);
5048 var obj = this._getLayer(stamp(layer));
5050 this._layers.splice(this._layers.indexOf(obj), 1);
5052 return (this._map) ? this._update() : this;
5055 // @method expand(): this
5056 // Expand the control container if collapsed.
5057 expand: function () {
5058 addClass(this._container, 'leaflet-control-layers-expanded');
5059 this._section.style.height = null;
5060 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
5061 if (acceptableHeight < this._section.clientHeight) {
5062 addClass(this._section, 'leaflet-control-layers-scrollbar');
5063 this._section.style.height = acceptableHeight + 'px';
5065 removeClass(this._section, 'leaflet-control-layers-scrollbar');
5067 this._checkDisabledLayers();
5071 // @method collapse(): this
5072 // Collapse the control container if expanded.
5073 collapse: function () {
5074 removeClass(this._container, 'leaflet-control-layers-expanded');
5078 _initLayout: function () {
5079 var className = 'leaflet-control-layers',
5080 container = this._container = create$1('div', className),
5081 collapsed = this.options.collapsed;
5083 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
5084 container.setAttribute('aria-haspopup', true);
5086 disableClickPropagation(container);
5087 disableScrollPropagation(container);
5089 var section = this._section = create$1('section', className + '-list');
5092 this._map.on('click', this.collapse, this);
5096 mouseenter: this.expand,
5097 mouseleave: this.collapse
5102 var link = this._layersLink = create$1('a', className + '-toggle', container);
5104 link.title = 'Layers';
5107 on(link, 'click', stop);
5108 on(link, 'click', this.expand, this);
5110 on(link, 'focus', this.expand, this);
5117 this._baseLayersList = create$1('div', className + '-base', section);
5118 this._separator = create$1('div', className + '-separator', section);
5119 this._overlaysList = create$1('div', className + '-overlays', section);
5121 container.appendChild(section);
5124 _getLayer: function (id) {
5125 for (var i = 0; i < this._layers.length; i++) {
5127 if (this._layers[i] && stamp(this._layers[i].layer) === id) {
5128 return this._layers[i];
5133 _addLayer: function (layer, name, overlay) {
5135 layer.on('add remove', this._onLayerChange, this);
5144 if (this.options.sortLayers) {
5145 this._layers.sort(bind(function (a, b) {
5146 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
5150 if (this.options.autoZIndex && layer.setZIndex) {
5152 layer.setZIndex(this._lastZIndex);
5155 this._expandIfNotCollapsed();
5158 _update: function () {
5159 if (!this._container) { return this; }
5161 empty(this._baseLayersList);
5162 empty(this._overlaysList);
5164 this._layerControlInputs = [];
5165 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
5167 for (i = 0; i < this._layers.length; i++) {
5168 obj = this._layers[i];
5170 overlaysPresent = overlaysPresent || obj.overlay;
5171 baseLayersPresent = baseLayersPresent || !obj.overlay;
5172 baseLayersCount += !obj.overlay ? 1 : 0;
5175 // Hide base layers section if there's only one layer.
5176 if (this.options.hideSingleBase) {
5177 baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
5178 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
5181 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
5186 _onLayerChange: function (e) {
5187 if (!this._handlingClick) {
5191 var obj = this._getLayer(stamp(e.target));
5194 // @section Layer events
5195 // @event baselayerchange: LayersControlEvent
5196 // Fired when the base layer is changed through the [layer control](#control-layers).
5197 // @event overlayadd: LayersControlEvent
5198 // Fired when an overlay is selected through the [layer control](#control-layers).
5199 // @event overlayremove: LayersControlEvent
5200 // Fired when an overlay is deselected through the [layer control](#control-layers).
5201 // @namespace Control.Layers
5202 var type = obj.overlay ?
5203 (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
5204 (e.type === 'add' ? 'baselayerchange' : null);
5207 this._map.fire(type, obj);
5211 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
5212 _createRadioElement: function (name, checked) {
5214 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
5215 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
5217 var radioFragment = document.createElement('div');
5218 radioFragment.innerHTML = radioHtml;
5220 return radioFragment.firstChild;
5223 _addItem: function (obj) {
5224 var label = document.createElement('label'),
5225 checked = this._map.hasLayer(obj.layer),
5229 input = document.createElement('input');
5230 input.type = 'checkbox';
5231 input.className = 'leaflet-control-layers-selector';
5232 input.defaultChecked = checked;
5234 input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked);
5237 this._layerControlInputs.push(input);
5238 input.layerId = stamp(obj.layer);
5240 on(input, 'click', this._onInputClick, this);
5242 var name = document.createElement('span');
5243 name.innerHTML = ' ' + obj.name;
5245 // Helps from preventing layer control flicker when checkboxes are disabled
5246 // https://github.com/Leaflet/Leaflet/issues/2771
5247 var holder = document.createElement('div');
5249 label.appendChild(holder);
5250 holder.appendChild(input);
5251 holder.appendChild(name);
5253 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
5254 container.appendChild(label);
5256 this._checkDisabledLayers();
5260 _onInputClick: function () {
5261 var inputs = this._layerControlInputs,
5263 var addedLayers = [],
5266 this._handlingClick = true;
5268 for (var i = inputs.length - 1; i >= 0; i--) {
5270 layer = this._getLayer(input.layerId).layer;
5272 if (input.checked) {
5273 addedLayers.push(layer);
5274 } else if (!input.checked) {
5275 removedLayers.push(layer);
5279 // Bugfix issue 2318: Should remove all old layers before readding new ones
5280 for (i = 0; i < removedLayers.length; i++) {
5281 if (this._map.hasLayer(removedLayers[i])) {
5282 this._map.removeLayer(removedLayers[i]);
5285 for (i = 0; i < addedLayers.length; i++) {
5286 if (!this._map.hasLayer(addedLayers[i])) {
5287 this._map.addLayer(addedLayers[i]);
5291 this._handlingClick = false;
5293 this._refocusOnMap();
5296 _checkDisabledLayers: function () {
5297 var inputs = this._layerControlInputs,
5300 zoom = this._map.getZoom();
5302 for (var i = inputs.length - 1; i >= 0; i--) {
5304 layer = this._getLayer(input.layerId).layer;
5305 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
5306 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
5311 _expandIfNotCollapsed: function () {
5312 if (this._map && !this.options.collapsed) {
5318 _expand: function () {
5319 // Backward compatibility, remove me in 1.1.
5320 return this.expand();
5323 _collapse: function () {
5324 // Backward compatibility, remove me in 1.1.
5325 return this.collapse();
5331 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
5332 // Creates a layers 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.
5333 var layers = function (baseLayers, overlays, options) {
5334 return new Layers(baseLayers, overlays, options);
5338 * @class Control.Zoom
5339 * @aka L.Control.Zoom
5342 * 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`.
5345 var Zoom = Control.extend({
5347 // @aka Control.Zoom options
5349 position: 'topleft',
5351 // @option zoomInText: String = '+'
5352 // The text set on the 'zoom in' button.
5355 // @option zoomInTitle: String = 'Zoom in'
5356 // The title set on the 'zoom in' button.
5357 zoomInTitle: 'Zoom in',
5359 // @option zoomOutText: String = '−'
5360 // The text set on the 'zoom out' button.
5361 zoomOutText: '−',
5363 // @option zoomOutTitle: String = 'Zoom out'
5364 // The title set on the 'zoom out' button.
5365 zoomOutTitle: 'Zoom out'
5368 onAdd: function (map) {
5369 var zoomName = 'leaflet-control-zoom',
5370 container = create$1('div', zoomName + ' leaflet-bar'),
5371 options = this.options;
5373 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle,
5374 zoomName + '-in', container, this._zoomIn);
5375 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
5376 zoomName + '-out', container, this._zoomOut);
5378 this._updateDisabled();
5379 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
5384 onRemove: function (map) {
5385 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
5388 disable: function () {
5389 this._disabled = true;
5390 this._updateDisabled();
5394 enable: function () {
5395 this._disabled = false;
5396 this._updateDisabled();
5400 _zoomIn: function (e) {
5401 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
5402 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5406 _zoomOut: function (e) {
5407 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
5408 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5412 _createButton: function (html, title, className, container, fn) {
5413 var link = create$1('a', className, container);
5414 link.innerHTML = html;
5419 * Will force screen readers like VoiceOver to read this as "Zoom in - button"
5421 link.setAttribute('role', 'button');
5422 link.setAttribute('aria-label', title);
5424 disableClickPropagation(link);
5425 on(link, 'click', stop);
5426 on(link, 'click', fn, this);
5427 on(link, 'click', this._refocusOnMap, this);
5432 _updateDisabled: function () {
5433 var map = this._map,
5434 className = 'leaflet-disabled';
5436 removeClass(this._zoomInButton, className);
5437 removeClass(this._zoomOutButton, className);
5439 if (this._disabled || map._zoom === map.getMinZoom()) {
5440 addClass(this._zoomOutButton, className);
5442 if (this._disabled || map._zoom === map.getMaxZoom()) {
5443 addClass(this._zoomInButton, className);
5449 // @section Control options
5450 // @option zoomControl: Boolean = true
5451 // Whether a [zoom control](#control-zoom) is added to the map by default.
5456 Map.addInitHook(function () {
5457 if (this.options.zoomControl) {
5458 // @section Controls
5459 // @property zoomControl: Control.Zoom
5460 // The default zoom control (only available if the
5461 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
5462 this.zoomControl = new Zoom();
5463 this.addControl(this.zoomControl);
5467 // @namespace Control.Zoom
5468 // @factory L.control.zoom(options: Control.Zoom options)
5469 // Creates a zoom control
5470 var zoom = function (options) {
5471 return new Zoom(options);
5475 * @class Control.Scale
5476 * @aka L.Control.Scale
5479 * 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`.
5484 * L.control.scale().addTo(map);
5488 var Scale = Control.extend({
5490 // @aka Control.Scale options
5492 position: 'bottomleft',
5494 // @option maxWidth: Number = 100
5495 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
5498 // @option metric: Boolean = True
5499 // Whether to show the metric scale line (m/km).
5502 // @option imperial: Boolean = True
5503 // Whether to show the imperial scale line (mi/ft).
5506 // @option updateWhenIdle: Boolean = false
5507 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
5510 onAdd: function (map) {
5511 var className = 'leaflet-control-scale',
5512 container = create$1('div', className),
5513 options = this.options;
5515 this._addScales(options, className + '-line', container);
5517 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5518 map.whenReady(this._update, this);
5523 onRemove: function (map) {
5524 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5527 _addScales: function (options, className, container) {
5528 if (options.metric) {
5529 this._mScale = create$1('div', className, container);
5531 if (options.imperial) {
5532 this._iScale = create$1('div', className, container);
5536 _update: function () {
5537 var map = this._map,
5538 y = map.getSize().y / 2;
5540 var maxMeters = map.distance(
5541 map.containerPointToLatLng([0, y]),
5542 map.containerPointToLatLng([this.options.maxWidth, y]));
5544 this._updateScales(maxMeters);
5547 _updateScales: function (maxMeters) {
5548 if (this.options.metric && maxMeters) {
5549 this._updateMetric(maxMeters);
5551 if (this.options.imperial && maxMeters) {
5552 this._updateImperial(maxMeters);
5556 _updateMetric: function (maxMeters) {
5557 var meters = this._getRoundNum(maxMeters),
5558 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
5560 this._updateScale(this._mScale, label, meters / maxMeters);
5563 _updateImperial: function (maxMeters) {
5564 var maxFeet = maxMeters * 3.2808399,
5565 maxMiles, miles, feet;
5567 if (maxFeet > 5280) {
5568 maxMiles = maxFeet / 5280;
5569 miles = this._getRoundNum(maxMiles);
5570 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
5573 feet = this._getRoundNum(maxFeet);
5574 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
5578 _updateScale: function (scale, text, ratio) {
5579 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
5580 scale.innerHTML = text;
5583 _getRoundNum: function (num) {
5584 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5597 // @factory L.control.scale(options?: Control.Scale options)
5598 // Creates an scale control with the given options.
5599 var scale = function (options) {
5600 return new Scale(options);
5604 * @class Control.Attribution
5605 * @aka L.Control.Attribution
5608 * 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.
5611 var Attribution = Control.extend({
5613 // @aka Control.Attribution options
5615 position: 'bottomright',
5617 // @option prefix: String = 'Leaflet'
5618 // The HTML text shown before the attributions. Pass `false` to disable.
5619 prefix: '<a href="https://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
5622 initialize: function (options) {
5623 setOptions(this, options);
5625 this._attributions = {};
5628 onAdd: function (map) {
5629 map.attributionControl = this;
5630 this._container = create$1('div', 'leaflet-control-attribution');
5631 disableClickPropagation(this._container);
5633 // TODO ugly, refactor
5634 for (var i in map._layers) {
5635 if (map._layers[i].getAttribution) {
5636 this.addAttribution(map._layers[i].getAttribution());
5642 return this._container;
5645 // @method setPrefix(prefix: String): this
5646 // Sets the text before the attributions.
5647 setPrefix: function (prefix) {
5648 this.options.prefix = prefix;
5653 // @method addAttribution(text: String): this
5654 // Adds an attribution text (e.g. `'Vector data © Mapbox'`).
5655 addAttribution: function (text) {
5656 if (!text) { return this; }
5658 if (!this._attributions[text]) {
5659 this._attributions[text] = 0;
5661 this._attributions[text]++;
5668 // @method removeAttribution(text: String): this
5669 // Removes an attribution text.
5670 removeAttribution: function (text) {
5671 if (!text) { return this; }
5673 if (this._attributions[text]) {
5674 this._attributions[text]--;
5681 _update: function () {
5682 if (!this._map) { return; }
5686 for (var i in this._attributions) {
5687 if (this._attributions[i]) {
5692 var prefixAndAttribs = [];
5694 if (this.options.prefix) {
5695 prefixAndAttribs.push(this.options.prefix);
5697 if (attribs.length) {
5698 prefixAndAttribs.push(attribs.join(', '));
5701 this._container.innerHTML = prefixAndAttribs.join(' | ');
5706 // @section Control options
5707 // @option attributionControl: Boolean = true
5708 // Whether a [attribution control](#control-attribution) is added to the map by default.
5710 attributionControl: true
5713 Map.addInitHook(function () {
5714 if (this.options.attributionControl) {
5715 new Attribution().addTo(this);
5719 // @namespace Control.Attribution
5720 // @factory L.control.attribution(options: Control.Attribution options)
5721 // Creates an attribution control.
5722 var attribution = function (options) {
5723 return new Attribution(options);
5726 Control.Layers = Layers;
5727 Control.Zoom = Zoom;
5728 Control.Scale = Scale;
5729 Control.Attribution = Attribution;
5731 control.layers = layers;
5732 control.zoom = zoom;
5733 control.scale = scale;
5734 control.attribution = attribution;
5737 L.Handler is a base class for handler classes that are used internally to inject
5738 interaction features like dragging to classes like Map and Marker.
5743 // Abstract class for map interaction handlers
5745 var Handler = Class.extend({
5746 initialize: function (map) {
5750 // @method enable(): this
5751 // Enables the handler
5752 enable: function () {
5753 if (this._enabled) { return this; }
5755 this._enabled = true;
5760 // @method disable(): this
5761 // Disables the handler
5762 disable: function () {
5763 if (!this._enabled) { return this; }
5765 this._enabled = false;
5770 // @method enabled(): Boolean
5771 // Returns `true` if the handler is enabled
5772 enabled: function () {
5773 return !!this._enabled;
5776 // @section Extension methods
5777 // Classes inheriting from `Handler` must implement the two following methods:
5778 // @method addHooks()
5779 // Called when the handler is enabled, should add event hooks.
5780 // @method removeHooks()
5781 // Called when the handler is disabled, should remove the event hooks added previously.
5784 // @section There is static function which can be called without instantiating L.Handler:
5785 // @function addTo(map: Map, name: String): this
5786 // Adds a new Handler to the given map with the given name.
5787 Handler.addTo = function (map, name) {
5788 map.addHandler(name, this);
5792 var Mixin = {Events: Events};
5799 * A class for making DOM elements draggable (including touch support).
5800 * Used internally for map and marker dragging. Only works for elements
5801 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
5805 * var draggable = new L.Draggable(elementToDrag);
5806 * draggable.enable();
5810 var START = touch ? 'touchstart mousedown' : 'mousedown';
5812 mousedown: 'mouseup',
5813 touchstart: 'touchend',
5814 pointerdown: 'touchend',
5815 MSPointerDown: 'touchend'
5818 mousedown: 'mousemove',
5819 touchstart: 'touchmove',
5820 pointerdown: 'touchmove',
5821 MSPointerDown: 'touchmove'
5825 var Draggable = Evented.extend({
5829 // @aka Draggable options
5830 // @option clickTolerance: Number = 3
5831 // The max number of pixels a user can shift the mouse pointer during a click
5832 // for it to be considered a valid click (as opposed to a mouse drag).
5836 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
5837 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
5838 initialize: function (element, dragStartTarget, preventOutline$$1, options) {
5839 setOptions(this, options);
5841 this._element = element;
5842 this._dragStartTarget = dragStartTarget || element;
5843 this._preventOutline = preventOutline$$1;
5847 // Enables the dragging ability
5848 enable: function () {
5849 if (this._enabled) { return; }
5851 on(this._dragStartTarget, START, this._onDown, this);
5853 this._enabled = true;
5856 // @method disable()
5857 // Disables the dragging ability
5858 disable: function () {
5859 if (!this._enabled) { return; }
5861 // If we're currently dragging this draggable,
5862 // disabling it counts as first ending the drag.
5863 if (Draggable._dragging === this) {
5867 off(this._dragStartTarget, START, this._onDown, this);
5869 this._enabled = false;
5870 this._moved = false;
5873 _onDown: function (e) {
5874 // Ignore simulated events, since we handle both touch and
5875 // mouse explicitly; otherwise we risk getting duplicates of
5876 // touch events, see #4315.
5877 // Also ignore the event if disabled; this happens in IE11
5878 // under some circumstances, see #3666.
5879 if (e._simulated || !this._enabled) { return; }
5881 this._moved = false;
5883 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
5885 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
5886 Draggable._dragging = this; // Prevent dragging multiple objects at once.
5888 if (this._preventOutline) {
5889 preventOutline(this._element);
5893 disableTextSelection();
5895 if (this._moving) { return; }
5897 // @event down: Event
5898 // Fired when a drag is about to start.
5901 var first = e.touches ? e.touches[0] : e,
5902 sizedParent = getSizedParentNode(this._element);
5904 this._startPoint = new Point(first.clientX, first.clientY);
5906 // Cache the scale, so that we can continuously compensate for it during drag (_onMove).
5907 this._parentScale = getScale(sizedParent);
5909 on(document, MOVE[e.type], this._onMove, this);
5910 on(document, END[e.type], this._onUp, this);
5913 _onMove: function (e) {
5914 // Ignore simulated events, since we handle both touch and
5915 // mouse explicitly; otherwise we risk getting duplicates of
5916 // touch events, see #4315.
5917 // Also ignore the event if disabled; this happens in IE11
5918 // under some circumstances, see #3666.
5919 if (e._simulated || !this._enabled) { return; }
5921 if (e.touches && e.touches.length > 1) {
5926 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
5927 offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
5929 if (!offset.x && !offset.y) { return; }
5930 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
5932 // We assume that the parent container's position, border and scale do not change for the duration of the drag.
5933 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
5934 // and we can use the cached value for the scale.
5935 offset.x /= this._parentScale.x;
5936 offset.y /= this._parentScale.y;
5941 // @event dragstart: Event
5942 // Fired when a drag starts
5943 this.fire('dragstart');
5946 this._startPos = getPosition(this._element).subtract(offset);
5948 addClass(document.body, 'leaflet-dragging');
5950 this._lastTarget = e.target || e.srcElement;
5951 // IE and Edge do not give the <use> element, so fetch it
5953 if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
5954 this._lastTarget = this._lastTarget.correspondingUseElement;
5956 addClass(this._lastTarget, 'leaflet-drag-target');
5959 this._newPos = this._startPos.add(offset);
5960 this._moving = true;
5962 cancelAnimFrame(this._animRequest);
5963 this._lastEvent = e;
5964 this._animRequest = requestAnimFrame(this._updatePosition, this, true);
5967 _updatePosition: function () {
5968 var e = {originalEvent: this._lastEvent};
5970 // @event predrag: Event
5971 // Fired continuously during dragging *before* each corresponding
5972 // update of the element's position.
5973 this.fire('predrag', e);
5974 setPosition(this._element, this._newPos);
5976 // @event drag: Event
5977 // Fired continuously during dragging.
5978 this.fire('drag', e);
5981 _onUp: function (e) {
5982 // Ignore simulated events, since we handle both touch and
5983 // mouse explicitly; otherwise we risk getting duplicates of
5984 // touch events, see #4315.
5985 // Also ignore the event if disabled; this happens in IE11
5986 // under some circumstances, see #3666.
5987 if (e._simulated || !this._enabled) { return; }
5991 finishDrag: function () {
5992 removeClass(document.body, 'leaflet-dragging');
5994 if (this._lastTarget) {
5995 removeClass(this._lastTarget, 'leaflet-drag-target');
5996 this._lastTarget = null;
5999 for (var i in MOVE) {
6000 off(document, MOVE[i], this._onMove, this);
6001 off(document, END[i], this._onUp, this);
6005 enableTextSelection();
6007 if (this._moved && this._moving) {
6008 // ensure drag is not fired after dragend
6009 cancelAnimFrame(this._animRequest);
6011 // @event dragend: DragEndEvent
6012 // Fired when the drag ends.
6013 this.fire('dragend', {
6014 distance: this._newPos.distanceTo(this._startPos)
6018 this._moving = false;
6019 Draggable._dragging = false;
6025 * @namespace LineUtil
6027 * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
6030 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
6031 // Improves rendering performance dramatically by lessening the number of points to draw.
6033 // @function simplify(points: Point[], tolerance: Number): Point[]
6034 // Dramatically reduces the number of points in a polyline while retaining
6035 // its shape and returns a new array of simplified points, using the
6036 // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
6037 // Used for a huge performance boost when processing/displaying Leaflet polylines for
6038 // each zoom level and also reducing visual noise. tolerance affects the amount of
6039 // simplification (lesser value means higher quality but slower and with more points).
6040 // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
6041 function simplify(points, tolerance) {
6042 if (!tolerance || !points.length) {
6043 return points.slice();
6046 var sqTolerance = tolerance * tolerance;
6048 // stage 1: vertex reduction
6049 points = _reducePoints(points, sqTolerance);
6051 // stage 2: Douglas-Peucker simplification
6052 points = _simplifyDP(points, sqTolerance);
6057 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
6058 // Returns the distance between point `p` and segment `p1` to `p2`.
6059 function pointToSegmentDistance(p, p1, p2) {
6060 return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
6063 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
6064 // Returns the closest point from a point `p` on a segment `p1` to `p2`.
6065 function closestPointOnSegment(p, p1, p2) {
6066 return _sqClosestPointOnSegment(p, p1, p2);
6069 // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
6070 function _simplifyDP(points, sqTolerance) {
6072 var len = points.length,
6073 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
6074 markers = new ArrayConstructor(len);
6076 markers[0] = markers[len - 1] = 1;
6078 _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
6083 for (i = 0; i < len; i++) {
6085 newPoints.push(points[i]);
6092 function _simplifyDPStep(points, markers, sqTolerance, first, last) {
6097 for (i = first + 1; i <= last - 1; i++) {
6098 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
6100 if (sqDist > maxSqDist) {
6106 if (maxSqDist > sqTolerance) {
6109 _simplifyDPStep(points, markers, sqTolerance, first, index);
6110 _simplifyDPStep(points, markers, sqTolerance, index, last);
6114 // reduce points that are too close to each other to a single point
6115 function _reducePoints(points, sqTolerance) {
6116 var reducedPoints = [points[0]];
6118 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
6119 if (_sqDist(points[i], points[prev]) > sqTolerance) {
6120 reducedPoints.push(points[i]);
6124 if (prev < len - 1) {
6125 reducedPoints.push(points[len - 1]);
6127 return reducedPoints;
6132 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
6133 // Clips the segment a to b by rectangular bounds with the
6134 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
6135 // (modifying the segment points directly!). Used by Leaflet to only show polyline
6136 // points that are on the screen or near, increasing performance.
6137 function clipSegment(a, b, bounds, useLastCode, round) {
6138 var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
6139 codeB = _getBitCode(b, bounds),
6141 codeOut, p, newCode;
6143 // save 2nd code to avoid calculating it on the next segment
6147 // if a,b is inside the clip window (trivial accept)
6148 if (!(codeA | codeB)) {
6152 // if a,b is outside the clip window (trivial reject)
6153 if (codeA & codeB) {
6158 codeOut = codeA || codeB;
6159 p = _getEdgeIntersection(a, b, codeOut, bounds, round);
6160 newCode = _getBitCode(p, bounds);
6162 if (codeOut === codeA) {
6172 function _getEdgeIntersection(a, b, code, bounds, round) {
6179 if (code & 8) { // top
6180 x = a.x + dx * (max.y - a.y) / dy;
6183 } else if (code & 4) { // bottom
6184 x = a.x + dx * (min.y - a.y) / dy;
6187 } else if (code & 2) { // right
6189 y = a.y + dy * (max.x - a.x) / dx;
6191 } else if (code & 1) { // left
6193 y = a.y + dy * (min.x - a.x) / dx;
6196 return new Point(x, y, round);
6199 function _getBitCode(p, bounds) {
6202 if (p.x < bounds.min.x) { // left
6204 } else if (p.x > bounds.max.x) { // right
6208 if (p.y < bounds.min.y) { // bottom
6210 } else if (p.y > bounds.max.y) { // top
6217 // square distance (to avoid unnecessary Math.sqrt calls)
6218 function _sqDist(p1, p2) {
6219 var dx = p2.x - p1.x,
6221 return dx * dx + dy * dy;
6224 // return closest point on segment or distance to that point
6225 function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
6230 dot = dx * dx + dy * dy,
6234 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
6248 return sqDist ? dx * dx + dy * dy : new Point(x, y);
6252 // @function isFlat(latlngs: LatLng[]): Boolean
6253 // Returns true if `latlngs` is a flat array, false is nested.
6254 function isFlat(latlngs) {
6255 return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
6258 function _flat(latlngs) {
6259 console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
6260 return isFlat(latlngs);
6264 var LineUtil = (Object.freeze || Object)({
6266 pointToSegmentDistance: pointToSegmentDistance,
6267 closestPointOnSegment: closestPointOnSegment,
6268 clipSegment: clipSegment,
6269 _getEdgeIntersection: _getEdgeIntersection,
6270 _getBitCode: _getBitCode,
6271 _sqClosestPointOnSegment: _sqClosestPointOnSegment,
6277 * @namespace PolyUtil
6278 * Various utility functions for polygon geometries.
6281 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
6282 * 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)).
6283 * Used by Leaflet to only show polygon points that are on the screen or near, increasing
6284 * performance. Note that polygon points needs different algorithm for clipping
6285 * than polyline, so there's a separate method for it.
6287 function clipPolygon(points, bounds, round) {
6289 edges = [1, 4, 2, 8],
6294 for (i = 0, len = points.length; i < len; i++) {
6295 points[i]._code = _getBitCode(points[i], bounds);
6298 // for each edge (left, bottom, right, top)
6299 for (k = 0; k < 4; k++) {
6303 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
6307 // if a is inside the clip window
6308 if (!(a._code & edge)) {
6309 // if b is outside the clip window (a->b goes out of screen)
6310 if (b._code & edge) {
6311 p = _getEdgeIntersection(b, a, edge, bounds, round);
6312 p._code = _getBitCode(p, bounds);
6313 clippedPoints.push(p);
6315 clippedPoints.push(a);
6317 // else if b is inside the clip window (a->b enters the screen)
6318 } else if (!(b._code & edge)) {
6319 p = _getEdgeIntersection(b, a, edge, bounds, round);
6320 p._code = _getBitCode(p, bounds);
6321 clippedPoints.push(p);
6324 points = clippedPoints;
6331 var PolyUtil = (Object.freeze || Object)({
6332 clipPolygon: clipPolygon
6336 * @namespace Projection
6338 * Leaflet comes with a set of already defined Projections out of the box:
6340 * @projection L.Projection.LonLat
6342 * Equirectangular, or Plate Carree projection — the most simple projection,
6343 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
6344 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
6345 * `EPSG:4326` and `Simple` CRS.
6349 project: function (latlng) {
6350 return new Point(latlng.lng, latlng.lat);
6353 unproject: function (point) {
6354 return new LatLng(point.y, point.x);
6357 bounds: new Bounds([-180, -90], [180, 90])
6361 * @namespace Projection
6362 * @projection L.Projection.Mercator
6364 * Elliptical Mercator projection — more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS.
6369 R_MINOR: 6356752.314245179,
6371 bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
6373 project: function (latlng) {
6374 var d = Math.PI / 180,
6377 tmp = this.R_MINOR / r,
6378 e = Math.sqrt(1 - tmp * tmp),
6379 con = e * Math.sin(y);
6381 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
6382 y = -r * Math.log(Math.max(ts, 1E-10));
6384 return new Point(latlng.lng * d * r, y);
6387 unproject: function (point) {
6388 var d = 180 / Math.PI,
6390 tmp = this.R_MINOR / r,
6391 e = Math.sqrt(1 - tmp * tmp),
6392 ts = Math.exp(-point.y / r),
6393 phi = Math.PI / 2 - 2 * Math.atan(ts);
6395 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
6396 con = e * Math.sin(phi);
6397 con = Math.pow((1 - con) / (1 + con), e / 2);
6398 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
6402 return new LatLng(phi * d, point.x * d / r);
6409 * An object with methods for projecting geographical coordinates of the world onto
6410 * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection).
6412 * @property bounds: Bounds
6413 * The bounds (specified in CRS units) where the projection is valid
6415 * @method project(latlng: LatLng): Point
6416 * Projects geographical coordinates into a 2D point.
6417 * Only accepts actual `L.LatLng` instances, not arrays.
6419 * @method unproject(point: Point): LatLng
6420 * The inverse of `project`. Projects a 2D point into a geographical location.
6421 * Only accepts actual `L.Point` instances, not arrays.
6423 * Note that the projection instances do not inherit from Leafet's `Class` object,
6424 * and can't be instantiated. Also, new classes can't inherit from them,
6425 * and methods can't be added to them with the `include` function.
6432 var index = (Object.freeze || Object)({
6435 SphericalMercator: SphericalMercator
6440 * @crs L.CRS.EPSG3395
6442 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
6444 var EPSG3395 = extend({}, Earth, {
6446 projection: Mercator,
6448 transformation: (function () {
6449 var scale = 0.5 / (Math.PI * Mercator.R);
6450 return toTransformation(scale, 0.5, -scale, 0.5);
6456 * @crs L.CRS.EPSG4326
6458 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
6460 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
6461 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer`
6462 * with this CRS, ensure that there are two 256x256 pixel tiles covering the
6463 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
6464 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
6467 var EPSG4326 = extend({}, Earth, {
6470 transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
6477 * A simple CRS that maps longitude and latitude into `x` and `y` directly.
6478 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
6479 * axis should still be inverted (going from bottom to top). `distance()` returns
6480 * simple euclidean distance.
6483 var Simple = extend({}, CRS, {
6485 transformation: toTransformation(1, 0, -1, 0),
6487 scale: function (zoom) {
6488 return Math.pow(2, zoom);
6491 zoom: function (scale) {
6492 return Math.log(scale) / Math.LN2;
6495 distance: function (latlng1, latlng2) {
6496 var dx = latlng2.lng - latlng1.lng,
6497 dy = latlng2.lat - latlng1.lat;
6499 return Math.sqrt(dx * dx + dy * dy);
6506 CRS.EPSG3395 = EPSG3395;
6507 CRS.EPSG3857 = EPSG3857;
6508 CRS.EPSG900913 = EPSG900913;
6509 CRS.EPSG4326 = EPSG4326;
6510 CRS.Simple = Simple;
6518 * A set of methods from the Layer base class that all Leaflet layers use.
6519 * Inherits all methods, options and events from `L.Evented`.
6524 * var layer = L.marker(latlng).addTo(map);
6530 * Fired after the layer is added to a map
6532 * @event remove: Event
6533 * Fired after the layer is removed from a map
6537 var Layer = Evented.extend({
6539 // Classes extending `L.Layer` will inherit the following options:
6541 // @option pane: String = 'overlayPane'
6542 // 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.
6543 pane: 'overlayPane',
6545 // @option attribution: String = null
6546 // 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.
6549 bubblingMouseEvents: true
6553 * Classes extending `L.Layer` will inherit the following methods:
6555 * @method addTo(map: Map|LayerGroup): this
6556 * Adds the layer to the given map or layer group.
6558 addTo: function (map) {
6563 // @method remove: this
6564 // Removes the layer from the map it is currently active on.
6565 remove: function () {
6566 return this.removeFrom(this._map || this._mapToAdd);
6569 // @method removeFrom(map: Map): this
6570 // Removes the layer from the given map
6571 removeFrom: function (obj) {
6573 obj.removeLayer(this);
6578 // @method getPane(name? : String): HTMLElement
6579 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
6580 getPane: function (name) {
6581 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
6584 addInteractiveTarget: function (targetEl) {
6585 this._map._targets[stamp(targetEl)] = this;
6589 removeInteractiveTarget: function (targetEl) {
6590 delete this._map._targets[stamp(targetEl)];
6594 // @method getAttribution: String
6595 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
6596 getAttribution: function () {
6597 return this.options.attribution;
6600 _layerAdd: function (e) {
6603 // check in case layer gets added and then removed before the map is ready
6604 if (!map.hasLayer(this)) { return; }
6607 this._zoomAnimated = map._zoomAnimated;
6609 if (this.getEvents) {
6610 var events = this.getEvents();
6611 map.on(events, this);
6612 this.once('remove', function () {
6613 map.off(events, this);
6619 if (this.getAttribution && map.attributionControl) {
6620 map.attributionControl.addAttribution(this.getAttribution());
6624 map.fire('layeradd', {layer: this});
6628 /* @section Extension methods
6631 * Every layer should extend from `L.Layer` and (re-)implement the following methods.
6633 * @method onAdd(map: Map): this
6634 * 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).
6636 * @method onRemove(map: Map): this
6637 * 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).
6639 * @method getEvents(): Object
6640 * 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.
6642 * @method getAttribution(): String
6643 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
6645 * @method beforeAdd(map: Map): this
6646 * 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.
6651 * @section Layer events
6653 * @event layeradd: LayerEvent
6654 * Fired when a new layer is added to the map.
6656 * @event layerremove: LayerEvent
6657 * Fired when some layer is removed from the map
6659 * @section Methods for Layers and Controls
6662 // @method addLayer(layer: Layer): this
6663 // Adds the given layer to the map
6664 addLayer: function (layer) {
6665 if (!layer._layerAdd) {
6666 throw new Error('The provided object is not a Layer.');
6669 var id = stamp(layer);
6670 if (this._layers[id]) { return this; }
6671 this._layers[id] = layer;
6673 layer._mapToAdd = this;
6675 if (layer.beforeAdd) {
6676 layer.beforeAdd(this);
6679 this.whenReady(layer._layerAdd, layer);
6684 // @method removeLayer(layer: Layer): this
6685 // Removes the given layer from the map.
6686 removeLayer: function (layer) {
6687 var id = stamp(layer);
6689 if (!this._layers[id]) { return this; }
6692 layer.onRemove(this);
6695 if (layer.getAttribution && this.attributionControl) {
6696 this.attributionControl.removeAttribution(layer.getAttribution());
6699 delete this._layers[id];
6702 this.fire('layerremove', {layer: layer});
6703 layer.fire('remove');
6706 layer._map = layer._mapToAdd = null;
6711 // @method hasLayer(layer: Layer): Boolean
6712 // Returns `true` if the given layer is currently added to the map
6713 hasLayer: function (layer) {
6714 return !!layer && (stamp(layer) in this._layers);
6717 /* @method eachLayer(fn: Function, context?: Object): this
6718 * Iterates over the layers of the map, optionally specifying context of the iterator function.
6720 * map.eachLayer(function(layer){
6721 * layer.bindPopup('Hello');
6725 eachLayer: function (method, context) {
6726 for (var i in this._layers) {
6727 method.call(context, this._layers[i]);
6732 _addLayers: function (layers) {
6733 layers = layers ? (isArray(layers) ? layers : [layers]) : [];
6735 for (var i = 0, len = layers.length; i < len; i++) {
6736 this.addLayer(layers[i]);
6740 _addZoomLimit: function (layer) {
6741 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
6742 this._zoomBoundLayers[stamp(layer)] = layer;
6743 this._updateZoomLevels();
6747 _removeZoomLimit: function (layer) {
6748 var id = stamp(layer);
6750 if (this._zoomBoundLayers[id]) {
6751 delete this._zoomBoundLayers[id];
6752 this._updateZoomLevels();
6756 _updateZoomLevels: function () {
6757 var minZoom = Infinity,
6758 maxZoom = -Infinity,
6759 oldZoomSpan = this._getZoomSpan();
6761 for (var i in this._zoomBoundLayers) {
6762 var options = this._zoomBoundLayers[i].options;
6764 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
6765 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
6768 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
6769 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
6771 // @section Map state change events
6772 // @event zoomlevelschange: Event
6773 // Fired when the number of zoomlevels on the map is changed due
6774 // to adding or removing a layer.
6775 if (oldZoomSpan !== this._getZoomSpan()) {
6776 this.fire('zoomlevelschange');
6779 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
6780 this.setZoom(this._layersMaxZoom);
6782 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
6783 this.setZoom(this._layersMinZoom);
6793 * Used to group several layers and handle them as one. If you add it to the map,
6794 * any layers added or removed from the group will be added/removed on the map as
6795 * well. Extends `Layer`.
6800 * L.layerGroup([marker1, marker2])
6801 * .addLayer(polyline)
6806 var LayerGroup = Layer.extend({
6808 initialize: function (layers, options) {
6809 setOptions(this, options);
6816 for (i = 0, len = layers.length; i < len; i++) {
6817 this.addLayer(layers[i]);
6822 // @method addLayer(layer: Layer): this
6823 // Adds the given layer to the group.
6824 addLayer: function (layer) {
6825 var id = this.getLayerId(layer);
6827 this._layers[id] = layer;
6830 this._map.addLayer(layer);
6836 // @method removeLayer(layer: Layer): this
6837 // Removes the given layer from the group.
6839 // @method removeLayer(id: Number): this
6840 // Removes the layer with the given internal ID from the group.
6841 removeLayer: function (layer) {
6842 var id = layer in this._layers ? layer : this.getLayerId(layer);
6844 if (this._map && this._layers[id]) {
6845 this._map.removeLayer(this._layers[id]);
6848 delete this._layers[id];
6853 // @method hasLayer(layer: Layer): Boolean
6854 // Returns `true` if the given layer is currently added to the group.
6856 // @method hasLayer(id: Number): Boolean
6857 // Returns `true` if the given internal ID is currently added to the group.
6858 hasLayer: function (layer) {
6859 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
6862 // @method clearLayers(): this
6863 // Removes all the layers from the group.
6864 clearLayers: function () {
6865 return this.eachLayer(this.removeLayer, this);
6868 // @method invoke(methodName: String, …): this
6869 // Calls `methodName` on every layer contained in this group, passing any
6870 // additional parameters. Has no effect if the layers contained do not
6871 // implement `methodName`.
6872 invoke: function (methodName) {
6873 var args = Array.prototype.slice.call(arguments, 1),
6876 for (i in this._layers) {
6877 layer = this._layers[i];
6879 if (layer[methodName]) {
6880 layer[methodName].apply(layer, args);
6887 onAdd: function (map) {
6888 this.eachLayer(map.addLayer, map);
6891 onRemove: function (map) {
6892 this.eachLayer(map.removeLayer, map);
6895 // @method eachLayer(fn: Function, context?: Object): this
6896 // Iterates over the layers of the group, optionally specifying context of the iterator function.
6898 // group.eachLayer(function (layer) {
6899 // layer.bindPopup('Hello');
6902 eachLayer: function (method, context) {
6903 for (var i in this._layers) {
6904 method.call(context, this._layers[i]);
6909 // @method getLayer(id: Number): Layer
6910 // Returns the layer with the given internal ID.
6911 getLayer: function (id) {
6912 return this._layers[id];
6915 // @method getLayers(): Layer[]
6916 // Returns an array of all the layers added to the group.
6917 getLayers: function () {
6919 this.eachLayer(layers.push, layers);
6923 // @method setZIndex(zIndex: Number): this
6924 // Calls `setZIndex` on every layer contained in this group, passing the z-index.
6925 setZIndex: function (zIndex) {
6926 return this.invoke('setZIndex', zIndex);
6929 // @method getLayerId(layer: Layer): Number
6930 // Returns the internal ID for a layer
6931 getLayerId: function (layer) {
6932 return stamp(layer);
6937 // @factory L.layerGroup(layers?: Layer[], options?: Object)
6938 // Create a layer group, optionally given an initial set of layers and an `options` object.
6939 var layerGroup = function (layers, options) {
6940 return new LayerGroup(layers, options);
6944 * @class FeatureGroup
6945 * @aka L.FeatureGroup
6946 * @inherits LayerGroup
6948 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
6949 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
6950 * * Events are propagated to the `FeatureGroup`, so if the group has an event
6951 * handler, it will handle events from any of the layers. This includes mouse events
6952 * and custom events.
6953 * * Has `layeradd` and `layerremove` events
6958 * L.featureGroup([marker1, marker2, polyline])
6959 * .bindPopup('Hello world!')
6960 * .on('click', function() { alert('Clicked on a member of the group!'); })
6965 var FeatureGroup = LayerGroup.extend({
6967 addLayer: function (layer) {
6968 if (this.hasLayer(layer)) {
6972 layer.addEventParent(this);
6974 LayerGroup.prototype.addLayer.call(this, layer);
6976 // @event layeradd: LayerEvent
6977 // Fired when a layer is added to this `FeatureGroup`
6978 return this.fire('layeradd', {layer: layer});
6981 removeLayer: function (layer) {
6982 if (!this.hasLayer(layer)) {
6985 if (layer in this._layers) {
6986 layer = this._layers[layer];
6989 layer.removeEventParent(this);
6991 LayerGroup.prototype.removeLayer.call(this, layer);
6993 // @event layerremove: LayerEvent
6994 // Fired when a layer is removed from this `FeatureGroup`
6995 return this.fire('layerremove', {layer: layer});
6998 // @method setStyle(style: Path options): this
6999 // Sets the given path options to each layer of the group that has a `setStyle` method.
7000 setStyle: function (style) {
7001 return this.invoke('setStyle', style);
7004 // @method bringToFront(): this
7005 // Brings the layer group to the top of all other layers
7006 bringToFront: function () {
7007 return this.invoke('bringToFront');
7010 // @method bringToBack(): this
7011 // Brings the layer group to the back of all other layers
7012 bringToBack: function () {
7013 return this.invoke('bringToBack');
7016 // @method getBounds(): LatLngBounds
7017 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
7018 getBounds: function () {
7019 var bounds = new LatLngBounds();
7021 for (var id in this._layers) {
7022 var layer = this._layers[id];
7023 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
7029 // @factory L.featureGroup(layers: Layer[])
7030 // Create a feature group, optionally given an initial set of layers.
7031 var featureGroup = function (layers) {
7032 return new FeatureGroup(layers);
7039 * Represents an icon to provide when creating a marker.
7044 * var myIcon = L.icon({
7045 * iconUrl: 'my-icon.png',
7046 * iconRetinaUrl: 'my-icon@2x.png',
7047 * iconSize: [38, 95],
7048 * iconAnchor: [22, 94],
7049 * popupAnchor: [-3, -76],
7050 * shadowUrl: 'my-icon-shadow.png',
7051 * shadowRetinaUrl: 'my-icon-shadow@2x.png',
7052 * shadowSize: [68, 95],
7053 * shadowAnchor: [22, 94]
7056 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
7059 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
7063 var Icon = Class.extend({
7068 * @option iconUrl: String = null
7069 * **(required)** The URL to the icon image (absolute or relative to your script path).
7071 * @option iconRetinaUrl: String = null
7072 * The URL to a retina sized version of the icon image (absolute or relative to your
7073 * script path). Used for Retina screen devices.
7075 * @option iconSize: Point = null
7076 * Size of the icon image in pixels.
7078 * @option iconAnchor: Point = null
7079 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
7080 * will be aligned so that this point is at the marker's geographical location. Centered
7081 * by default if size is specified, also can be set in CSS with negative margins.
7083 * @option popupAnchor: Point = [0, 0]
7084 * The coordinates of the point from which popups will "open", relative to the icon anchor.
7086 * @option tooltipAnchor: Point = [0, 0]
7087 * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
7089 * @option shadowUrl: String = null
7090 * The URL to the icon shadow image. If not specified, no shadow image will be created.
7092 * @option shadowRetinaUrl: String = null
7094 * @option shadowSize: Point = null
7095 * Size of the shadow image in pixels.
7097 * @option shadowAnchor: Point = null
7098 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
7099 * as iconAnchor if not specified).
7101 * @option className: String = ''
7102 * A custom class name to assign to both icon and shadow images. Empty by default.
7106 popupAnchor: [0, 0],
7107 tooltipAnchor: [0, 0]
7110 initialize: function (options) {
7111 setOptions(this, options);
7114 // @method createIcon(oldIcon?: HTMLElement): HTMLElement
7115 // Called internally when the icon has to be shown, returns a `<img>` HTML element
7116 // styled according to the options.
7117 createIcon: function (oldIcon) {
7118 return this._createIcon('icon', oldIcon);
7121 // @method createShadow(oldIcon?: HTMLElement): HTMLElement
7122 // As `createIcon`, but for the shadow beneath it.
7123 createShadow: function (oldIcon) {
7124 return this._createIcon('shadow', oldIcon);
7127 _createIcon: function (name, oldIcon) {
7128 var src = this._getIconUrl(name);
7131 if (name === 'icon') {
7132 throw new Error('iconUrl not set in Icon options (see the docs).');
7137 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
7138 this._setIconStyles(img, name);
7143 _setIconStyles: function (img, name) {
7144 var options = this.options;
7145 var sizeOption = options[name + 'Size'];
7147 if (typeof sizeOption === 'number') {
7148 sizeOption = [sizeOption, sizeOption];
7151 var size = toPoint(sizeOption),
7152 anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
7153 size && size.divideBy(2, true));
7155 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
7158 img.style.marginLeft = (-anchor.x) + 'px';
7159 img.style.marginTop = (-anchor.y) + 'px';
7163 img.style.width = size.x + 'px';
7164 img.style.height = size.y + 'px';
7168 _createImg: function (src, el) {
7169 el = el || document.createElement('img');
7174 _getIconUrl: function (name) {
7175 return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
7180 // @factory L.icon(options: Icon options)
7181 // Creates an icon instance with the given options.
7182 function icon(options) {
7183 return new Icon(options);
7187 * @miniclass Icon.Default (Icon)
7188 * @aka L.Icon.Default
7191 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
7192 * no icon is specified. Points to the blue marker image distributed with Leaflet
7195 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
7196 * (which is a set of `Icon options`).
7198 * If you want to _completely_ replace the default icon, override the
7199 * `L.Marker.prototype.options.icon` with your own icon instead.
7202 var IconDefault = Icon.extend({
7205 iconUrl: 'marker-icon.png',
7206 iconRetinaUrl: 'marker-icon-2x.png',
7207 shadowUrl: 'marker-shadow.png',
7209 iconAnchor: [12, 41],
7210 popupAnchor: [1, -34],
7211 tooltipAnchor: [16, -28],
7212 shadowSize: [41, 41]
7215 _getIconUrl: function (name) {
7216 if (!IconDefault.imagePath) { // Deprecated, backwards-compatibility only
7217 IconDefault.imagePath = this._detectIconPath();
7220 // @option imagePath: String
7221 // `Icon.Default` will try to auto-detect the location of the
7222 // blue icon images. If you are placing these images in a non-standard
7223 // way, set this option to point to the right path.
7224 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
7227 _detectIconPath: function () {
7228 var el = create$1('div', 'leaflet-default-icon-path', document.body);
7229 var path = getStyle(el, 'background-image') ||
7230 getStyle(el, 'backgroundImage'); // IE8
7232 document.body.removeChild(el);
7234 if (path === null || path.indexOf('url') !== 0) {
7237 path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, '');
7245 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7249 /* @namespace Marker
7250 * @section Interaction handlers
7252 * 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:
7255 * marker.dragging.disable();
7258 * @property dragging: Handler
7259 * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
7262 var MarkerDrag = Handler.extend({
7263 initialize: function (marker) {
7264 this._marker = marker;
7267 addHooks: function () {
7268 var icon = this._marker._icon;
7270 if (!this._draggable) {
7271 this._draggable = new Draggable(icon, icon, true);
7274 this._draggable.on({
7275 dragstart: this._onDragStart,
7276 predrag: this._onPreDrag,
7278 dragend: this._onDragEnd
7281 addClass(icon, 'leaflet-marker-draggable');
7284 removeHooks: function () {
7285 this._draggable.off({
7286 dragstart: this._onDragStart,
7287 predrag: this._onPreDrag,
7289 dragend: this._onDragEnd
7292 if (this._marker._icon) {
7293 removeClass(this._marker._icon, 'leaflet-marker-draggable');
7297 moved: function () {
7298 return this._draggable && this._draggable._moved;
7301 _adjustPan: function (e) {
7302 var marker = this._marker,
7304 speed = this._marker.options.autoPanSpeed,
7305 padding = this._marker.options.autoPanPadding,
7306 iconPos = getPosition(marker._icon),
7307 bounds = map.getPixelBounds(),
7308 origin = map.getPixelOrigin();
7310 var panBounds = toBounds(
7311 bounds.min._subtract(origin).add(padding),
7312 bounds.max._subtract(origin).subtract(padding)
7315 if (!panBounds.contains(iconPos)) {
7316 // Compute incremental movement
7317 var movement = toPoint(
7318 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
7319 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
7321 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
7322 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
7323 ).multiplyBy(speed);
7325 map.panBy(movement, {animate: false});
7327 this._draggable._newPos._add(movement);
7328 this._draggable._startPos._add(movement);
7330 setPosition(marker._icon, this._draggable._newPos);
7333 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7337 _onDragStart: function () {
7338 // @section Dragging events
7339 // @event dragstart: Event
7340 // Fired when the user starts dragging the marker.
7342 // @event movestart: Event
7343 // Fired when the marker starts moving (because of dragging).
7345 this._oldLatLng = this._marker.getLatLng();
7352 _onPreDrag: function (e) {
7353 if (this._marker.options.autoPan) {
7354 cancelAnimFrame(this._panRequest);
7355 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7359 _onDrag: function (e) {
7360 var marker = this._marker,
7361 shadow = marker._shadow,
7362 iconPos = getPosition(marker._icon),
7363 latlng = marker._map.layerPointToLatLng(iconPos);
7365 // update shadow position
7367 setPosition(shadow, iconPos);
7370 marker._latlng = latlng;
7372 e.oldLatLng = this._oldLatLng;
7374 // @event drag: Event
7375 // Fired repeatedly while the user drags the marker.
7381 _onDragEnd: function (e) {
7382 // @event dragend: DragEndEvent
7383 // Fired when the user stops dragging the marker.
7385 cancelAnimFrame(this._panRequest);
7387 // @event moveend: Event
7388 // Fired when the marker stops moving (because of dragging).
7389 delete this._oldLatLng;
7392 .fire('dragend', e);
7398 * @inherits Interactive layer
7400 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
7405 * L.marker([50.5, 30.5]).addTo(map);
7409 var Marker = Layer.extend({
7412 // @aka Marker options
7414 // @option icon: Icon = *
7415 // Icon instance to use for rendering the marker.
7416 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
7417 // If not specified, a common instance of `L.Icon.Default` is used.
7418 icon: new IconDefault(),
7420 // Option inherited from "Interactive layer" abstract class
7423 // @option keyboard: Boolean = true
7424 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
7427 // @option title: String = ''
7428 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
7431 // @option alt: String = ''
7432 // Text for the `alt` attribute of the icon image (useful for accessibility).
7435 // @option zIndexOffset: Number = 0
7436 // 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).
7439 // @option opacity: Number = 1.0
7440 // The opacity of the marker.
7443 // @option riseOnHover: Boolean = false
7444 // If `true`, the marker will get on top of others when you hover the mouse over it.
7447 // @option riseOffset: Number = 250
7448 // The z-index offset used for the `riseOnHover` feature.
7451 // @option pane: String = 'markerPane'
7452 // `Map pane` where the markers icon will be added.
7455 // @option pane: String = 'shadowPane'
7456 // `Map pane` where the markers shadow will be added.
7457 shadowPane: 'shadowPane',
7459 // @option bubblingMouseEvents: Boolean = false
7460 // When `true`, a mouse event on this marker will trigger the same event on the map
7461 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7462 bubblingMouseEvents: false,
7464 // @section Draggable marker options
7465 // @option draggable: Boolean = false
7466 // Whether the marker is draggable with mouse/touch or not.
7469 // @option autoPan: Boolean = false
7470 // Whether to pan the map when dragging this marker near its edge or not.
7473 // @option autoPanPadding: Point = Point(50, 50)
7474 // Distance (in pixels to the left/right and to the top/bottom) of the
7475 // map edge to start panning the map.
7476 autoPanPadding: [50, 50],
7478 // @option autoPanSpeed: Number = 10
7479 // Number of pixels the map should pan by.
7485 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
7488 initialize: function (latlng, options) {
7489 setOptions(this, options);
7490 this._latlng = toLatLng(latlng);
7493 onAdd: function (map) {
7494 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
7496 if (this._zoomAnimated) {
7497 map.on('zoomanim', this._animateZoom, this);
7504 onRemove: function (map) {
7505 if (this.dragging && this.dragging.enabled()) {
7506 this.options.draggable = true;
7507 this.dragging.removeHooks();
7509 delete this.dragging;
7511 if (this._zoomAnimated) {
7512 map.off('zoomanim', this._animateZoom, this);
7516 this._removeShadow();
7519 getEvents: function () {
7522 viewreset: this.update
7526 // @method getLatLng: LatLng
7527 // Returns the current geographical position of the marker.
7528 getLatLng: function () {
7529 return this._latlng;
7532 // @method setLatLng(latlng: LatLng): this
7533 // Changes the marker position to the given point.
7534 setLatLng: function (latlng) {
7535 var oldLatLng = this._latlng;
7536 this._latlng = toLatLng(latlng);
7539 // @event move: Event
7540 // 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`.
7541 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7544 // @method setZIndexOffset(offset: Number): this
7545 // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
7546 setZIndexOffset: function (offset) {
7547 this.options.zIndexOffset = offset;
7548 return this.update();
7551 // @method getIcon: Icon
7552 // Returns the current icon used by the marker
7553 getIcon: function () {
7554 return this.options.icon;
7557 // @method setIcon(icon: Icon): this
7558 // Changes the marker icon.
7559 setIcon: function (icon) {
7561 this.options.icon = icon;
7569 this.bindPopup(this._popup, this._popup.options);
7575 getElement: function () {
7579 update: function () {
7581 if (this._icon && this._map) {
7582 var pos = this._map.latLngToLayerPoint(this._latlng).round();
7589 _initIcon: function () {
7590 var options = this.options,
7591 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
7593 var icon = options.icon.createIcon(this._icon),
7596 // if we're not reusing the icon, remove the old one and init new one
7597 if (icon !== this._icon) {
7603 if (options.title) {
7604 icon.title = options.title;
7607 if (icon.tagName === 'IMG') {
7608 icon.alt = options.alt || '';
7612 addClass(icon, classToAdd);
7614 if (options.keyboard) {
7615 icon.tabIndex = '0';
7620 if (options.riseOnHover) {
7622 mouseover: this._bringToFront,
7623 mouseout: this._resetZIndex
7627 var newShadow = options.icon.createShadow(this._shadow),
7630 if (newShadow !== this._shadow) {
7631 this._removeShadow();
7636 addClass(newShadow, classToAdd);
7639 this._shadow = newShadow;
7642 if (options.opacity < 1) {
7643 this._updateOpacity();
7648 this.getPane().appendChild(this._icon);
7650 this._initInteraction();
7651 if (newShadow && addShadow) {
7652 this.getPane(options.shadowPane).appendChild(this._shadow);
7656 _removeIcon: function () {
7657 if (this.options.riseOnHover) {
7659 mouseover: this._bringToFront,
7660 mouseout: this._resetZIndex
7665 this.removeInteractiveTarget(this._icon);
7670 _removeShadow: function () {
7672 remove(this._shadow);
7674 this._shadow = null;
7677 _setPos: function (pos) {
7680 setPosition(this._icon, pos);
7684 setPosition(this._shadow, pos);
7687 this._zIndex = pos.y + this.options.zIndexOffset;
7689 this._resetZIndex();
7692 _updateZIndex: function (offset) {
7694 this._icon.style.zIndex = this._zIndex + offset;
7698 _animateZoom: function (opt) {
7699 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
7704 _initInteraction: function () {
7706 if (!this.options.interactive) { return; }
7708 addClass(this._icon, 'leaflet-interactive');
7710 this.addInteractiveTarget(this._icon);
7713 var draggable = this.options.draggable;
7714 if (this.dragging) {
7715 draggable = this.dragging.enabled();
7716 this.dragging.disable();
7719 this.dragging = new MarkerDrag(this);
7722 this.dragging.enable();
7727 // @method setOpacity(opacity: Number): this
7728 // Changes the opacity of the marker.
7729 setOpacity: function (opacity) {
7730 this.options.opacity = opacity;
7732 this._updateOpacity();
7738 _updateOpacity: function () {
7739 var opacity = this.options.opacity;
7742 setOpacity(this._icon, opacity);
7746 setOpacity(this._shadow, opacity);
7750 _bringToFront: function () {
7751 this._updateZIndex(this.options.riseOffset);
7754 _resetZIndex: function () {
7755 this._updateZIndex(0);
7758 _getPopupAnchor: function () {
7759 return this.options.icon.options.popupAnchor;
7762 _getTooltipAnchor: function () {
7763 return this.options.icon.options.tooltipAnchor;
7768 // factory L.marker(latlng: LatLng, options? : Marker options)
7770 // @factory L.marker(latlng: LatLng, options? : Marker options)
7771 // Instantiates a Marker object given a geographical point and optionally an options object.
7772 function marker(latlng, options) {
7773 return new Marker(latlng, options);
7779 * @inherits Interactive layer
7781 * An abstract class that contains options and constants shared between vector
7782 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
7785 var Path = Layer.extend({
7788 // @aka Path options
7790 // @option stroke: Boolean = true
7791 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
7794 // @option color: String = '#3388ff'
7798 // @option weight: Number = 3
7799 // Stroke width in pixels
7802 // @option opacity: Number = 1.0
7806 // @option lineCap: String= 'round'
7807 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
7810 // @option lineJoin: String = 'round'
7811 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
7814 // @option dashArray: String = null
7815 // 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).
7818 // @option dashOffset: String = null
7819 // 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).
7822 // @option fill: Boolean = depends
7823 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
7826 // @option fillColor: String = *
7827 // Fill color. Defaults to the value of the [`color`](#path-color) option
7830 // @option fillOpacity: Number = 0.2
7834 // @option fillRule: String = 'evenodd'
7835 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
7836 fillRule: 'evenodd',
7840 // Option inherited from "Interactive layer" abstract class
7843 // @option bubblingMouseEvents: Boolean = true
7844 // When `true`, a mouse event on this path will trigger the same event on the map
7845 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7846 bubblingMouseEvents: true
7849 beforeAdd: function (map) {
7850 // Renderer is set here because we need to call renderer.getEvents
7851 // before this.getEvents.
7852 this._renderer = map.getRenderer(this);
7855 onAdd: function () {
7856 this._renderer._initPath(this);
7858 this._renderer._addPath(this);
7861 onRemove: function () {
7862 this._renderer._removePath(this);
7865 // @method redraw(): this
7866 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
7867 redraw: function () {
7869 this._renderer._updatePath(this);
7874 // @method setStyle(style: Path options): this
7875 // Changes the appearance of a Path based on the options in the `Path options` object.
7876 setStyle: function (style) {
7877 setOptions(this, style);
7878 if (this._renderer) {
7879 this._renderer._updateStyle(this);
7880 if (this.options.stroke && style && style.hasOwnProperty('weight')) {
7881 this._updateBounds();
7887 // @method bringToFront(): this
7888 // Brings the layer to the top of all path layers.
7889 bringToFront: function () {
7890 if (this._renderer) {
7891 this._renderer._bringToFront(this);
7896 // @method bringToBack(): this
7897 // Brings the layer to the bottom of all path layers.
7898 bringToBack: function () {
7899 if (this._renderer) {
7900 this._renderer._bringToBack(this);
7905 getElement: function () {
7909 _reset: function () {
7910 // defined in child classes
7915 _clickTolerance: function () {
7916 // used when doing hit detection for Canvas layers
7917 return (this.options.stroke ? this.options.weight / 2 : 0) + this._renderer.options.tolerance;
7922 * @class CircleMarker
7923 * @aka L.CircleMarker
7926 * A circle of a fixed size with radius specified in pixels. Extends `Path`.
7929 var CircleMarker = Path.extend({
7932 // @aka CircleMarker options
7936 // @option radius: Number = 10
7937 // Radius of the circle marker, in pixels
7941 initialize: function (latlng, options) {
7942 setOptions(this, options);
7943 this._latlng = toLatLng(latlng);
7944 this._radius = this.options.radius;
7947 // @method setLatLng(latLng: LatLng): this
7948 // Sets the position of a circle marker to a new location.
7949 setLatLng: function (latlng) {
7950 var oldLatLng = this._latlng;
7951 this._latlng = toLatLng(latlng);
7954 // @event move: Event
7955 // Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
7956 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7959 // @method getLatLng(): LatLng
7960 // Returns the current geographical position of the circle marker
7961 getLatLng: function () {
7962 return this._latlng;
7965 // @method setRadius(radius: Number): this
7966 // Sets the radius of a circle marker. Units are in pixels.
7967 setRadius: function (radius) {
7968 this.options.radius = this._radius = radius;
7969 return this.redraw();
7972 // @method getRadius(): Number
7973 // Returns the current radius of the circle
7974 getRadius: function () {
7975 return this._radius;
7978 setStyle : function (options) {
7979 var radius = options && options.radius || this._radius;
7980 Path.prototype.setStyle.call(this, options);
7981 this.setRadius(radius);
7985 _project: function () {
7986 this._point = this._map.latLngToLayerPoint(this._latlng);
7987 this._updateBounds();
7990 _updateBounds: function () {
7991 var r = this._radius,
7992 r2 = this._radiusY || r,
7993 w = this._clickTolerance(),
7994 p = [r + w, r2 + w];
7995 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
7998 _update: function () {
8004 _updatePath: function () {
8005 this._renderer._updateCircle(this);
8008 _empty: function () {
8009 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
8012 // Needed by the `Canvas` renderer for interactivity
8013 _containsPoint: function (p) {
8014 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
8019 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
8020 // Instantiates a circle marker object given a geographical point, and an optional options object.
8021 function circleMarker(latlng, options) {
8022 return new CircleMarker(latlng, options);
8028 * @inherits CircleMarker
8030 * A class for drawing circle overlays on a map. Extends `CircleMarker`.
8032 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
8037 * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
8041 var Circle = CircleMarker.extend({
8043 initialize: function (latlng, options, legacyOptions) {
8044 if (typeof options === 'number') {
8045 // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
8046 options = extend({}, legacyOptions, {radius: options});
8048 setOptions(this, options);
8049 this._latlng = toLatLng(latlng);
8051 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
8054 // @aka Circle options
8055 // @option radius: Number; Radius of the circle, in meters.
8056 this._mRadius = this.options.radius;
8059 // @method setRadius(radius: Number): this
8060 // Sets the radius of a circle. Units are in meters.
8061 setRadius: function (radius) {
8062 this._mRadius = radius;
8063 return this.redraw();
8066 // @method getRadius(): Number
8067 // Returns the current radius of a circle. Units are in meters.
8068 getRadius: function () {
8069 return this._mRadius;
8072 // @method getBounds(): LatLngBounds
8073 // Returns the `LatLngBounds` of the path.
8074 getBounds: function () {
8075 var half = [this._radius, this._radiusY || this._radius];
8077 return new LatLngBounds(
8078 this._map.layerPointToLatLng(this._point.subtract(half)),
8079 this._map.layerPointToLatLng(this._point.add(half)));
8082 setStyle: Path.prototype.setStyle,
8084 _project: function () {
8086 var lng = this._latlng.lng,
8087 lat = this._latlng.lat,
8089 crs = map.options.crs;
8091 if (crs.distance === Earth.distance) {
8092 var d = Math.PI / 180,
8093 latR = (this._mRadius / Earth.R) / d,
8094 top = map.project([lat + latR, lng]),
8095 bottom = map.project([lat - latR, lng]),
8096 p = top.add(bottom).divideBy(2),
8097 lat2 = map.unproject(p).lat,
8098 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
8099 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
8101 if (isNaN(lngR) || lngR === 0) {
8102 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
8105 this._point = p.subtract(map.getPixelOrigin());
8106 this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
8107 this._radiusY = p.y - top.y;
8110 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
8112 this._point = map.latLngToLayerPoint(this._latlng);
8113 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
8116 this._updateBounds();
8120 // @factory L.circle(latlng: LatLng, options?: Circle options)
8121 // Instantiates a circle object given a geographical point, and an options object
8122 // which contains the circle radius.
8124 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
8125 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
8126 // Do not use in new applications or plugins.
8127 function circle(latlng, options, legacyOptions) {
8128 return new Circle(latlng, options, legacyOptions);
8136 * A class for drawing polyline overlays on a map. Extends `Path`.
8141 * // create a red polyline from an array of LatLng points
8148 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
8150 * // zoom the map to the polyline
8151 * map.fitBounds(polyline.getBounds());
8154 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
8157 * // create a red polyline from an array of arrays of LatLng points
8159 * [[45.51, -122.68],
8170 var Polyline = Path.extend({
8173 // @aka Polyline options
8175 // @option smoothFactor: Number = 1.0
8176 // How much to simplify the polyline on each zoom level. More means
8177 // better performance and smoother look, and less means more accurate representation.
8180 // @option noClip: Boolean = false
8181 // Disable polyline clipping.
8185 initialize: function (latlngs, options) {
8186 setOptions(this, options);
8187 this._setLatLngs(latlngs);
8190 // @method getLatLngs(): LatLng[]
8191 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8192 getLatLngs: function () {
8193 return this._latlngs;
8196 // @method setLatLngs(latlngs: LatLng[]): this
8197 // Replaces all the points in the polyline with the given array of geographical points.
8198 setLatLngs: function (latlngs) {
8199 this._setLatLngs(latlngs);
8200 return this.redraw();
8203 // @method isEmpty(): Boolean
8204 // Returns `true` if the Polyline has no LatLngs.
8205 isEmpty: function () {
8206 return !this._latlngs.length;
8209 // @method closestLayerPoint(p: Point): Point
8210 // Returns the point closest to `p` on the Polyline.
8211 closestLayerPoint: function (p) {
8212 var minDistance = Infinity,
8214 closest = _sqClosestPointOnSegment,
8217 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8218 var points = this._parts[j];
8220 for (var i = 1, len = points.length; i < len; i++) {
8224 var sqDist = closest(p, p1, p2, true);
8226 if (sqDist < minDistance) {
8227 minDistance = sqDist;
8228 minPoint = closest(p, p1, p2);
8233 minPoint.distance = Math.sqrt(minDistance);
8238 // @method getCenter(): LatLng
8239 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
8240 getCenter: function () {
8241 // throws error when not yet added to map as this center calculation requires projected coordinates
8243 throw new Error('Must add layer to map before using getCenter()');
8246 var i, halfDist, segDist, dist, p1, p2, ratio,
8247 points = this._rings[0],
8248 len = points.length;
8250 if (!len) { return null; }
8252 // polyline centroid algorithm; only uses the first ring if there are multiple
8254 for (i = 0, halfDist = 0; i < len - 1; i++) {
8255 halfDist += points[i].distanceTo(points[i + 1]) / 2;
8258 // The line is so small in the current view that all points are on the same pixel.
8259 if (halfDist === 0) {
8260 return this._map.layerPointToLatLng(points[0]);
8263 for (i = 0, dist = 0; i < len - 1; i++) {
8266 segDist = p1.distanceTo(p2);
8269 if (dist > halfDist) {
8270 ratio = (dist - halfDist) / segDist;
8271 return this._map.layerPointToLatLng([
8272 p2.x - ratio * (p2.x - p1.x),
8273 p2.y - ratio * (p2.y - p1.y)
8279 // @method getBounds(): LatLngBounds
8280 // Returns the `LatLngBounds` of the path.
8281 getBounds: function () {
8282 return this._bounds;
8285 // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
8286 // Adds a given point to the polyline. By default, adds to the first ring of
8287 // the polyline in case of a multi-polyline, but can be overridden by passing
8288 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8289 addLatLng: function (latlng, latlngs) {
8290 latlngs = latlngs || this._defaultShape();
8291 latlng = toLatLng(latlng);
8292 latlngs.push(latlng);
8293 this._bounds.extend(latlng);
8294 return this.redraw();
8297 _setLatLngs: function (latlngs) {
8298 this._bounds = new LatLngBounds();
8299 this._latlngs = this._convertLatLngs(latlngs);
8302 _defaultShape: function () {
8303 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
8306 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8307 _convertLatLngs: function (latlngs) {
8309 flat = isFlat(latlngs);
8311 for (var i = 0, len = latlngs.length; i < len; i++) {
8313 result[i] = toLatLng(latlngs[i]);
8314 this._bounds.extend(result[i]);
8316 result[i] = this._convertLatLngs(latlngs[i]);
8323 _project: function () {
8324 var pxBounds = new Bounds();
8326 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8328 if (this._bounds.isValid() && pxBounds.isValid()) {
8329 this._rawPxBounds = pxBounds;
8330 this._updateBounds();
8334 _updateBounds: function () {
8335 var w = this._clickTolerance(),
8336 p = new Point(w, w);
8337 this._pxBounds = new Bounds([
8338 this._rawPxBounds.min.subtract(p),
8339 this._rawPxBounds.max.add(p)
8343 // recursively turns latlngs into a set of rings with projected coordinates
8344 _projectLatlngs: function (latlngs, result, projectedBounds) {
8345 var flat = latlngs[0] instanceof LatLng,
8346 len = latlngs.length,
8351 for (i = 0; i < len; i++) {
8352 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8353 projectedBounds.extend(ring[i]);
8357 for (i = 0; i < len; i++) {
8358 this._projectLatlngs(latlngs[i], result, projectedBounds);
8363 // clip polyline by renderer bounds so that we have less to render for performance
8364 _clipPoints: function () {
8365 var bounds = this._renderer._bounds;
8368 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8372 if (this.options.noClip) {
8373 this._parts = this._rings;
8377 var parts = this._parts,
8378 i, j, k, len, len2, segment, points;
8380 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8381 points = this._rings[i];
8383 for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8384 segment = clipSegment(points[j], points[j + 1], bounds, j, true);
8386 if (!segment) { continue; }
8388 parts[k] = parts[k] || [];
8389 parts[k].push(segment[0]);
8391 // if segment goes out of screen, or it's the last one, it's the end of the line part
8392 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8393 parts[k].push(segment[1]);
8400 // simplify each clipped part of the polyline for performance
8401 _simplifyPoints: function () {
8402 var parts = this._parts,
8403 tolerance = this.options.smoothFactor;
8405 for (var i = 0, len = parts.length; i < len; i++) {
8406 parts[i] = simplify(parts[i], tolerance);
8410 _update: function () {
8411 if (!this._map) { return; }
8414 this._simplifyPoints();
8418 _updatePath: function () {
8419 this._renderer._updatePoly(this);
8422 // Needed by the `Canvas` renderer for interactivity
8423 _containsPoint: function (p, closed) {
8424 var i, j, k, len, len2, part,
8425 w = this._clickTolerance();
8427 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8429 // hit detection for polylines
8430 for (i = 0, len = this._parts.length; i < len; i++) {
8431 part = this._parts[i];
8433 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8434 if (!closed && (j === 0)) { continue; }
8436 if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
8445 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8446 // Instantiates a polyline object given an array of geographical points and
8447 // optionally an options object. You can create a `Polyline` object with
8448 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8449 // of geographic points.
8450 function polyline(latlngs, options) {
8451 return new Polyline(latlngs, options);
8454 // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
8455 Polyline._flat = _flat;
8460 * @inherits Polyline
8462 * A class for drawing polygon overlays on a map. Extends `Polyline`.
8464 * 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.
8470 * // create a red polygon from an array of LatLng points
8471 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
8473 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8475 * // zoom the map to the polygon
8476 * map.fitBounds(polygon.getBounds());
8479 * 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:
8483 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8484 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8488 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8492 * [ // first polygon
8493 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8494 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8496 * [ // second polygon
8497 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
8503 var Polygon = Polyline.extend({
8509 isEmpty: function () {
8510 return !this._latlngs.length || !this._latlngs[0].length;
8513 getCenter: function () {
8514 // throws error when not yet added to map as this center calculation requires projected coordinates
8516 throw new Error('Must add layer to map before using getCenter()');
8519 var i, j, p1, p2, f, area, x, y, center,
8520 points = this._rings[0],
8521 len = points.length;
8523 if (!len) { return null; }
8525 // polygon centroid algorithm; only uses the first ring if there are multiple
8529 for (i = 0, j = len - 1; i < len; j = i++) {
8533 f = p1.y * p2.x - p2.y * p1.x;
8534 x += (p1.x + p2.x) * f;
8535 y += (p1.y + p2.y) * f;
8540 // Polygon is so small that all points are on same pixel.
8543 center = [x / area, y / area];
8545 return this._map.layerPointToLatLng(center);
8548 _convertLatLngs: function (latlngs) {
8549 var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
8550 len = result.length;
8552 // remove last point if it equals first one
8553 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
8559 _setLatLngs: function (latlngs) {
8560 Polyline.prototype._setLatLngs.call(this, latlngs);
8561 if (isFlat(this._latlngs)) {
8562 this._latlngs = [this._latlngs];
8566 _defaultShape: function () {
8567 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8570 _clipPoints: function () {
8571 // polygons need a different clipping algorithm so we redefine that
8573 var bounds = this._renderer._bounds,
8574 w = this.options.weight,
8575 p = new Point(w, w);
8577 // increase clip padding by stroke width to avoid stroke on clip edges
8578 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
8581 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8585 if (this.options.noClip) {
8586 this._parts = this._rings;
8590 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8591 clipped = clipPolygon(this._rings[i], bounds, true);
8592 if (clipped.length) {
8593 this._parts.push(clipped);
8598 _updatePath: function () {
8599 this._renderer._updatePoly(this, true);
8602 // Needed by the `Canvas` renderer for interactivity
8603 _containsPoint: function (p) {
8605 part, p1, p2, i, j, k, len, len2;
8607 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8609 // ray casting algorithm for detecting if point is in polygon
8610 for (i = 0, len = this._parts.length; i < len; i++) {
8611 part = this._parts[i];
8613 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8617 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)) {
8623 // also check if it's on polygon stroke
8624 return inside || Polyline.prototype._containsPoint.call(this, p, true);
8630 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8631 function polygon(latlngs, options) {
8632 return new Polygon(latlngs, options);
8638 * @inherits FeatureGroup
8640 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
8641 * GeoJSON data and display it on the map. Extends `FeatureGroup`.
8647 * style: function (feature) {
8648 * return {color: feature.properties.color};
8650 * }).bindPopup(function (layer) {
8651 * return layer.feature.properties.description;
8656 var GeoJSON = FeatureGroup.extend({
8659 * @aka GeoJSON options
8661 * @option pointToLayer: Function = *
8662 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
8663 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
8664 * The default is to spawn a default `Marker`:
8666 * function(geoJsonPoint, latlng) {
8667 * return L.marker(latlng);
8671 * @option style: Function = *
8672 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
8673 * called internally when data is added.
8674 * The default value is to not override any defaults:
8676 * function (geoJsonFeature) {
8681 * @option onEachFeature: Function = *
8682 * A `Function` that will be called once for each created `Feature`, after it has
8683 * been created and styled. Useful for attaching events and popups to features.
8684 * The default is to do nothing with the newly created layers:
8686 * function (feature, layer) {}
8689 * @option filter: Function = *
8690 * A `Function` that will be used to decide whether to include a feature or not.
8691 * The default is to include all features:
8693 * function (geoJsonFeature) {
8697 * Note: dynamically changing the `filter` option will have effect only on newly
8698 * added data. It will _not_ re-evaluate already included features.
8700 * @option coordsToLatLng: Function = *
8701 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
8702 * The default is the `coordsToLatLng` static method.
8704 * @option markersInheritOptions: Boolean = false
8705 * Whether default Markers for "Point" type Features inherit from group options.
8708 initialize: function (geojson, options) {
8709 setOptions(this, options);
8714 this.addData(geojson);
8718 // @method addData( <GeoJSON> data ): this
8719 // Adds a GeoJSON object to the layer.
8720 addData: function (geojson) {
8721 var features = isArray(geojson) ? geojson : geojson.features,
8725 for (i = 0, len = features.length; i < len; i++) {
8726 // only add this if geometry or geometries are set and not null
8727 feature = features[i];
8728 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
8729 this.addData(feature);
8735 var options = this.options;
8737 if (options.filter && !options.filter(geojson)) { return this; }
8739 var layer = geometryToLayer(geojson, options);
8743 layer.feature = asFeature(geojson);
8745 layer.defaultOptions = layer.options;
8746 this.resetStyle(layer);
8748 if (options.onEachFeature) {
8749 options.onEachFeature(geojson, layer);
8752 return this.addLayer(layer);
8755 // @method resetStyle( <Path> layer? ): this
8756 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
8757 // If `layer` is omitted, the style of all features in the current layer is reset.
8758 resetStyle: function (layer) {
8759 if (layer === undefined) {
8760 return this.eachLayer(this.resetStyle, this);
8762 // reset any custom styles
8763 layer.options = extend({}, layer.defaultOptions);
8764 this._setLayerStyle(layer, this.options.style);
8768 // @method setStyle( <Function> style ): this
8769 // Changes styles of GeoJSON vector layers with the given style function.
8770 setStyle: function (style) {
8771 return this.eachLayer(function (layer) {
8772 this._setLayerStyle(layer, style);
8776 _setLayerStyle: function (layer, style) {
8777 if (layer.setStyle) {
8778 if (typeof style === 'function') {
8779 style = style(layer.feature);
8781 layer.setStyle(style);
8787 // There are several static functions which can be called without instantiating L.GeoJSON:
8789 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
8790 // Creates a `Layer` from a given GeoJSON feature. Can use a custom
8791 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
8792 // functions if provided as options.
8793 function geometryToLayer(geojson, options) {
8795 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
8796 coords = geometry ? geometry.coordinates : null,
8798 pointToLayer = options && options.pointToLayer,
8799 _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
8800 latlng, latlngs, i, len;
8802 if (!coords && !geometry) {
8806 switch (geometry.type) {
8808 latlng = _coordsToLatLng(coords);
8809 return _pointToLayer(pointToLayer, geojson, latlng, options);
8812 for (i = 0, len = coords.length; i < len; i++) {
8813 latlng = _coordsToLatLng(coords[i]);
8814 layers.push(_pointToLayer(pointToLayer, geojson, latlng, options));
8816 return new FeatureGroup(layers);
8819 case 'MultiLineString':
8820 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
8821 return new Polyline(latlngs, options);
8824 case 'MultiPolygon':
8825 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
8826 return new Polygon(latlngs, options);
8828 case 'GeometryCollection':
8829 for (i = 0, len = geometry.geometries.length; i < len; i++) {
8830 var layer = geometryToLayer({
8831 geometry: geometry.geometries[i],
8833 properties: geojson.properties
8840 return new FeatureGroup(layers);
8843 throw new Error('Invalid GeoJSON object.');
8847 function _pointToLayer(pointToLayerFn, geojson, latlng, options) {
8848 return pointToLayerFn ?
8849 pointToLayerFn(geojson, latlng) :
8850 new Marker(latlng, options && options.markersInheritOptions && options);
8853 // @function coordsToLatLng(coords: Array): LatLng
8854 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
8855 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
8856 function coordsToLatLng(coords) {
8857 return new LatLng(coords[1], coords[0], coords[2]);
8860 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
8861 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
8862 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
8863 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
8864 function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
8867 for (var i = 0, len = coords.length, latlng; i < len; i++) {
8868 latlng = levelsDeep ?
8869 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
8870 (_coordsToLatLng || coordsToLatLng)(coords[i]);
8872 latlngs.push(latlng);
8878 // @function latLngToCoords(latlng: LatLng, precision?: Number): Array
8879 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
8880 function latLngToCoords(latlng, precision) {
8881 precision = typeof precision === 'number' ? precision : 6;
8882 return latlng.alt !== undefined ?
8883 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
8884 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
8887 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
8888 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
8889 // `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.
8890 function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
8893 for (var i = 0, len = latlngs.length; i < len; i++) {
8894 coords.push(levelsDeep ?
8895 latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) :
8896 latLngToCoords(latlngs[i], precision));
8899 if (!levelsDeep && closed) {
8900 coords.push(coords[0]);
8906 function getFeature(layer, newGeometry) {
8907 return layer.feature ?
8908 extend({}, layer.feature, {geometry: newGeometry}) :
8909 asFeature(newGeometry);
8912 // @function asFeature(geojson: Object): Object
8913 // Normalize GeoJSON geometries/features into GeoJSON features.
8914 function asFeature(geojson) {
8915 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
8926 var PointToGeoJSON = {
8927 toGeoJSON: function (precision) {
8928 return getFeature(this, {
8930 coordinates: latLngToCoords(this.getLatLng(), precision)
8935 // @namespace Marker
8936 // @section Other methods
8937 // @method toGeoJSON(precision?: Number): Object
8938 // `precision` is the number of decimal places for coordinates.
8939 // The default value is 6 places.
8940 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
8941 Marker.include(PointToGeoJSON);
8943 // @namespace CircleMarker
8944 // @method toGeoJSON(precision?: Number): Object
8945 // `precision` is the number of decimal places for coordinates.
8946 // The default value is 6 places.
8947 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
8948 Circle.include(PointToGeoJSON);
8949 CircleMarker.include(PointToGeoJSON);
8952 // @namespace Polyline
8953 // @method toGeoJSON(precision?: Number): Object
8954 // `precision` is the number of decimal places for coordinates.
8955 // The default value is 6 places.
8956 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
8958 toGeoJSON: function (precision) {
8959 var multi = !isFlat(this._latlngs);
8961 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
8963 return getFeature(this, {
8964 type: (multi ? 'Multi' : '') + 'LineString',
8970 // @namespace Polygon
8971 // @method toGeoJSON(precision?: Number): Object
8972 // `precision` is the number of decimal places for coordinates.
8973 // The default value is 6 places.
8974 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
8976 toGeoJSON: function (precision) {
8977 var holes = !isFlat(this._latlngs),
8978 multi = holes && !isFlat(this._latlngs[0]);
8980 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
8986 return getFeature(this, {
8987 type: (multi ? 'Multi' : '') + 'Polygon',
8994 // @namespace LayerGroup
8995 LayerGroup.include({
8996 toMultiPoint: function (precision) {
8999 this.eachLayer(function (layer) {
9000 coords.push(layer.toGeoJSON(precision).geometry.coordinates);
9003 return getFeature(this, {
9009 // @method toGeoJSON(precision?: Number): Object
9010 // `precision` is the number of decimal places for coordinates.
9011 // The default value is 6 places.
9012 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
9013 toGeoJSON: function (precision) {
9015 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
9017 if (type === 'MultiPoint') {
9018 return this.toMultiPoint(precision);
9021 var isGeometryCollection = type === 'GeometryCollection',
9024 this.eachLayer(function (layer) {
9025 if (layer.toGeoJSON) {
9026 var json = layer.toGeoJSON(precision);
9027 if (isGeometryCollection) {
9028 jsons.push(json.geometry);
9030 var feature = asFeature(json);
9031 // Squash nested feature collections
9032 if (feature.type === 'FeatureCollection') {
9033 jsons.push.apply(jsons, feature.features);
9035 jsons.push(feature);
9041 if (isGeometryCollection) {
9042 return getFeature(this, {
9044 type: 'GeometryCollection'
9049 type: 'FeatureCollection',
9055 // @namespace GeoJSON
9056 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
9057 // Creates a GeoJSON layer. Optionally accepts an object in
9058 // [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
9059 // (you can alternatively add it later with `addData` method) and an `options` object.
9060 function geoJSON(geojson, options) {
9061 return new GeoJSON(geojson, options);
9064 // Backward compatibility.
9065 var geoJson = geoJSON;
9068 * @class ImageOverlay
9069 * @aka L.ImageOverlay
9070 * @inherits Interactive layer
9072 * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
9077 * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
9078 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
9079 * L.imageOverlay(imageUrl, imageBounds).addTo(map);
9083 var ImageOverlay = Layer.extend({
9086 // @aka ImageOverlay options
9088 // @option opacity: Number = 1.0
9089 // The opacity of the image overlay.
9092 // @option alt: String = ''
9093 // Text for the `alt` attribute of the image (useful for accessibility).
9096 // @option interactive: Boolean = false
9097 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
9100 // @option crossOrigin: Boolean|String = false
9101 // Whether the crossOrigin attribute will be added to the image.
9102 // 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.
9103 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
9106 // @option errorOverlayUrl: String = ''
9107 // URL to the overlay image to show in place of the overlay that failed to load.
9108 errorOverlayUrl: '',
9110 // @option zIndex: Number = 1
9111 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
9114 // @option className: String = ''
9115 // A custom class name to assign to the image. Empty by default.
9119 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
9121 this._bounds = toLatLngBounds(bounds);
9123 setOptions(this, options);
9126 onAdd: function () {
9130 if (this.options.opacity < 1) {
9131 this._updateOpacity();
9135 if (this.options.interactive) {
9136 addClass(this._image, 'leaflet-interactive');
9137 this.addInteractiveTarget(this._image);
9140 this.getPane().appendChild(this._image);
9144 onRemove: function () {
9145 remove(this._image);
9146 if (this.options.interactive) {
9147 this.removeInteractiveTarget(this._image);
9151 // @method setOpacity(opacity: Number): this
9152 // Sets the opacity of the overlay.
9153 setOpacity: function (opacity) {
9154 this.options.opacity = opacity;
9157 this._updateOpacity();
9162 setStyle: function (styleOpts) {
9163 if (styleOpts.opacity) {
9164 this.setOpacity(styleOpts.opacity);
9169 // @method bringToFront(): this
9170 // Brings the layer to the top of all overlays.
9171 bringToFront: function () {
9173 toFront(this._image);
9178 // @method bringToBack(): this
9179 // Brings the layer to the bottom of all overlays.
9180 bringToBack: function () {
9182 toBack(this._image);
9187 // @method setUrl(url: String): this
9188 // Changes the URL of the image.
9189 setUrl: function (url) {
9193 this._image.src = url;
9198 // @method setBounds(bounds: LatLngBounds): this
9199 // Update the bounds that this ImageOverlay covers
9200 setBounds: function (bounds) {
9201 this._bounds = toLatLngBounds(bounds);
9209 getEvents: function () {
9212 viewreset: this._reset
9215 if (this._zoomAnimated) {
9216 events.zoomanim = this._animateZoom;
9222 // @method setZIndex(value: Number): this
9223 // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
9224 setZIndex: function (value) {
9225 this.options.zIndex = value;
9226 this._updateZIndex();
9230 // @method getBounds(): LatLngBounds
9231 // Get the bounds that this ImageOverlay covers
9232 getBounds: function () {
9233 return this._bounds;
9236 // @method getElement(): HTMLElement
9237 // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
9238 // used by this overlay.
9239 getElement: function () {
9243 _initImage: function () {
9244 var wasElementSupplied = this._url.tagName === 'IMG';
9245 var img = this._image = wasElementSupplied ? this._url : create$1('img');
9247 addClass(img, 'leaflet-image-layer');
9248 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
9249 if (this.options.className) { addClass(img, this.options.className); }
9251 img.onselectstart = falseFn;
9252 img.onmousemove = falseFn;
9254 // @event load: Event
9255 // Fired when the ImageOverlay layer has loaded its image
9256 img.onload = bind(this.fire, this, 'load');
9257 img.onerror = bind(this._overlayOnError, this, 'error');
9259 if (this.options.crossOrigin || this.options.crossOrigin === '') {
9260 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
9263 if (this.options.zIndex) {
9264 this._updateZIndex();
9267 if (wasElementSupplied) {
9268 this._url = img.src;
9272 img.src = this._url;
9273 img.alt = this.options.alt;
9276 _animateZoom: function (e) {
9277 var scale = this._map.getZoomScale(e.zoom),
9278 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
9280 setTransform(this._image, offset, scale);
9283 _reset: function () {
9284 var image = this._image,
9285 bounds = new Bounds(
9286 this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
9287 this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
9288 size = bounds.getSize();
9290 setPosition(image, bounds.min);
9292 image.style.width = size.x + 'px';
9293 image.style.height = size.y + 'px';
9296 _updateOpacity: function () {
9297 setOpacity(this._image, this.options.opacity);
9300 _updateZIndex: function () {
9301 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
9302 this._image.style.zIndex = this.options.zIndex;
9306 _overlayOnError: function () {
9307 // @event error: Event
9308 // Fired when the ImageOverlay layer fails to load its image
9311 var errorUrl = this.options.errorOverlayUrl;
9312 if (errorUrl && this._url !== errorUrl) {
9313 this._url = errorUrl;
9314 this._image.src = errorUrl;
9319 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
9320 // Instantiates an image overlay object given the URL of the image and the
9321 // geographical bounds it is tied to.
9322 var imageOverlay = function (url, bounds, options) {
9323 return new ImageOverlay(url, bounds, options);
9327 * @class VideoOverlay
9328 * @aka L.VideoOverlay
9329 * @inherits ImageOverlay
9331 * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
9333 * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
9339 * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
9340 * videoBounds = [[ 32, -130], [ 13, -100]];
9341 * L.videoOverlay(videoUrl, videoBounds ).addTo(map);
9345 var VideoOverlay = ImageOverlay.extend({
9348 // @aka VideoOverlay options
9350 // @option autoplay: Boolean = true
9351 // Whether the video starts playing automatically when loaded.
9354 // @option loop: Boolean = true
9355 // Whether the video will loop back to the beginning when played.
9358 // @option keepAspectRatio: Boolean = true
9359 // Whether the video will save aspect ratio after the projection.
9360 // Relevant for supported browsers. Browser compatibility- https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit
9361 keepAspectRatio: true
9364 _initImage: function () {
9365 var wasElementSupplied = this._url.tagName === 'VIDEO';
9366 var vid = this._image = wasElementSupplied ? this._url : create$1('video');
9368 addClass(vid, 'leaflet-image-layer');
9369 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
9370 if (this.options.className) { addClass(vid, this.options.className); }
9372 vid.onselectstart = falseFn;
9373 vid.onmousemove = falseFn;
9375 // @event load: Event
9376 // Fired when the video has finished loading the first frame
9377 vid.onloadeddata = bind(this.fire, this, 'load');
9379 if (wasElementSupplied) {
9380 var sourceElements = vid.getElementsByTagName('source');
9382 for (var j = 0; j < sourceElements.length; j++) {
9383 sources.push(sourceElements[j].src);
9386 this._url = (sourceElements.length > 0) ? sources : [vid.src];
9390 if (!isArray(this._url)) { this._url = [this._url]; }
9392 if (!this.options.keepAspectRatio && vid.style.hasOwnProperty('objectFit')) { vid.style['objectFit'] = 'fill'; }
9393 vid.autoplay = !!this.options.autoplay;
9394 vid.loop = !!this.options.loop;
9395 for (var i = 0; i < this._url.length; i++) {
9396 var source = create$1('source');
9397 source.src = this._url[i];
9398 vid.appendChild(source);
9402 // @method getElement(): HTMLVideoElement
9403 // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
9404 // used by this overlay.
9408 // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
9409 // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
9410 // geographical bounds it is tied to.
9412 function videoOverlay(video, bounds, options) {
9413 return new VideoOverlay(video, bounds, options);
9419 * @inherits ImageOverlay
9421 * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`.
9423 * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element.
9428 * var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
9429 * svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg");
9430 * svgElement.setAttribute('viewBox', "0 0 200 200");
9431 * svgElement.innerHTML = '<rect width="200" height="200"/><rect x="75" y="23" width="50" height="50" style="fill:red"/><rect x="75" y="123" width="50" height="50" style="fill:#0013ff"/>';
9432 * var svgElementBounds = [ [ 32, -130 ], [ 13, -100 ] ];
9433 * L.svgOverlay(svgElement, svgElementBounds).addTo(map);
9437 var SVGOverlay = ImageOverlay.extend({
9438 _initImage: function () {
9439 var el = this._image = this._url;
9441 addClass(el, 'leaflet-image-layer');
9442 if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); }
9443 if (this.options.className) { addClass(el, this.options.className); }
9445 el.onselectstart = falseFn;
9446 el.onmousemove = falseFn;
9449 // @method getElement(): SVGElement
9450 // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement)
9451 // used by this overlay.
9455 // @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options)
9456 // Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to.
9457 // A viewBox attribute is required on the SVG element to zoom in and out properly.
9459 function svgOverlay(el, bounds, options) {
9460 return new SVGOverlay(el, bounds, options);
9467 * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
9470 // @namespace DivOverlay
9471 var DivOverlay = Layer.extend({
9474 // @aka DivOverlay options
9476 // @option offset: Point = Point(0, 7)
9477 // The offset of the popup position. Useful to control the anchor
9478 // of the popup when opening it on some overlays.
9481 // @option className: String = ''
9482 // A custom CSS class name to assign to the popup.
9485 // @option pane: String = 'popupPane'
9486 // `Map pane` where the popup will be added.
9490 initialize: function (options, source) {
9491 setOptions(this, options);
9493 this._source = source;
9496 onAdd: function (map) {
9497 this._zoomAnimated = map._zoomAnimated;
9499 if (!this._container) {
9503 if (map._fadeAnimated) {
9504 setOpacity(this._container, 0);
9507 clearTimeout(this._removeTimeout);
9508 this.getPane().appendChild(this._container);
9511 if (map._fadeAnimated) {
9512 setOpacity(this._container, 1);
9515 this.bringToFront();
9518 onRemove: function (map) {
9519 if (map._fadeAnimated) {
9520 setOpacity(this._container, 0);
9521 this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
9523 remove(this._container);
9528 // @method getLatLng: LatLng
9529 // Returns the geographical point of popup.
9530 getLatLng: function () {
9531 return this._latlng;
9534 // @method setLatLng(latlng: LatLng): this
9535 // Sets the geographical point where the popup will open.
9536 setLatLng: function (latlng) {
9537 this._latlng = toLatLng(latlng);
9539 this._updatePosition();
9545 // @method getContent: String|HTMLElement
9546 // Returns the content of the popup.
9547 getContent: function () {
9548 return this._content;
9551 // @method setContent(htmlContent: String|HTMLElement|Function): this
9552 // 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.
9553 setContent: function (content) {
9554 this._content = content;
9559 // @method getElement: String|HTMLElement
9560 // Alias for [getContent()](#popup-getcontent)
9561 getElement: function () {
9562 return this._container;
9565 // @method update: null
9566 // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
9567 update: function () {
9568 if (!this._map) { return; }
9570 this._container.style.visibility = 'hidden';
9572 this._updateContent();
9573 this._updateLayout();
9574 this._updatePosition();
9576 this._container.style.visibility = '';
9581 getEvents: function () {
9583 zoom: this._updatePosition,
9584 viewreset: this._updatePosition
9587 if (this._zoomAnimated) {
9588 events.zoomanim = this._animateZoom;
9593 // @method isOpen: Boolean
9594 // Returns `true` when the popup is visible on the map.
9595 isOpen: function () {
9596 return !!this._map && this._map.hasLayer(this);
9599 // @method bringToFront: this
9600 // Brings this popup in front of other popups (in the same map pane).
9601 bringToFront: function () {
9603 toFront(this._container);
9608 // @method bringToBack: this
9609 // Brings this popup to the back of other popups (in the same map pane).
9610 bringToBack: function () {
9612 toBack(this._container);
9617 _prepareOpen: function (parent, layer, latlng) {
9618 if (!(layer instanceof Layer)) {
9623 if (layer instanceof FeatureGroup) {
9624 for (var id in parent._layers) {
9625 layer = parent._layers[id];
9631 if (layer.getCenter) {
9632 latlng = layer.getCenter();
9633 } else if (layer.getLatLng) {
9634 latlng = layer.getLatLng();
9636 throw new Error('Unable to get source layer LatLng.');
9640 // set overlay source to this layer
9641 this._source = layer;
9643 // update the overlay (content, layout, ect...)
9649 _updateContent: function () {
9650 if (!this._content) { return; }
9652 var node = this._contentNode;
9653 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
9655 if (typeof content === 'string') {
9656 node.innerHTML = content;
9658 while (node.hasChildNodes()) {
9659 node.removeChild(node.firstChild);
9661 node.appendChild(content);
9663 this.fire('contentupdate');
9666 _updatePosition: function () {
9667 if (!this._map) { return; }
9669 var pos = this._map.latLngToLayerPoint(this._latlng),
9670 offset = toPoint(this.options.offset),
9671 anchor = this._getAnchor();
9673 if (this._zoomAnimated) {
9674 setPosition(this._container, pos.add(anchor));
9676 offset = offset.add(pos).add(anchor);
9679 var bottom = this._containerBottom = -offset.y,
9680 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
9682 // bottom position the popup in case the height of the popup changes (images loading etc)
9683 this._container.style.bottom = bottom + 'px';
9684 this._container.style.left = left + 'px';
9687 _getAnchor: function () {
9695 * @inherits DivOverlay
9697 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
9698 * open popups while making sure that only one popup is open at one time
9699 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
9703 * If you want to just bind a popup to marker click and then open it, it's really easy:
9706 * marker.bindPopup(popupContent).openPopup();
9708 * Path overlays like polylines also have a `bindPopup` method.
9709 * Here's a more complicated way to open a popup on a map:
9712 * var popup = L.popup()
9713 * .setLatLng(latlng)
9714 * .setContent('<p>Hello world!<br />This is a nice popup.</p>')
9721 var Popup = DivOverlay.extend({
9724 // @aka Popup options
9726 // @option maxWidth: Number = 300
9727 // Max width of the popup, in pixels.
9730 // @option minWidth: Number = 50
9731 // Min width of the popup, in pixels.
9734 // @option maxHeight: Number = null
9735 // If set, creates a scrollable container of the given height
9736 // inside a popup if its content exceeds it.
9739 // @option autoPan: Boolean = true
9740 // Set it to `false` if you don't want the map to do panning animation
9741 // to fit the opened popup.
9744 // @option autoPanPaddingTopLeft: Point = null
9745 // The margin between the popup and the top left corner of the map
9746 // view after autopanning was performed.
9747 autoPanPaddingTopLeft: null,
9749 // @option autoPanPaddingBottomRight: Point = null
9750 // The margin between the popup and the bottom right corner of the map
9751 // view after autopanning was performed.
9752 autoPanPaddingBottomRight: null,
9754 // @option autoPanPadding: Point = Point(5, 5)
9755 // Equivalent of setting both top left and bottom right autopan padding to the same value.
9756 autoPanPadding: [5, 5],
9758 // @option keepInView: Boolean = false
9759 // Set it to `true` if you want to prevent users from panning the popup
9760 // off of the screen while it is open.
9763 // @option closeButton: Boolean = true
9764 // Controls the presence of a close button in the popup.
9767 // @option autoClose: Boolean = true
9768 // Set it to `false` if you want to override the default behavior of
9769 // the popup closing when another popup is opened.
9772 // @option closeOnEscapeKey: Boolean = true
9773 // Set it to `false` if you want to override the default behavior of
9774 // the ESC key for closing of the popup.
9775 closeOnEscapeKey: true,
9777 // @option closeOnClick: Boolean = *
9778 // Set it if you want to override the default behavior of the popup closing when user clicks
9779 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
9781 // @option className: String = ''
9782 // A custom CSS class name to assign to the popup.
9787 // @method openOn(map: Map): this
9788 // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
9789 openOn: function (map) {
9790 map.openPopup(this);
9794 onAdd: function (map) {
9795 DivOverlay.prototype.onAdd.call(this, map);
9798 // @section Popup events
9799 // @event popupopen: PopupEvent
9800 // Fired when a popup is opened in the map
9801 map.fire('popupopen', {popup: this});
9805 // @section Popup events
9806 // @event popupopen: PopupEvent
9807 // Fired when a popup bound to this layer is opened
9808 this._source.fire('popupopen', {popup: this}, true);
9809 // For non-path layers, we toggle the popup when clicking
9810 // again the layer, so prevent the map to reopen it.
9811 if (!(this._source instanceof Path)) {
9812 this._source.on('preclick', stopPropagation);
9817 onRemove: function (map) {
9818 DivOverlay.prototype.onRemove.call(this, map);
9821 // @section Popup events
9822 // @event popupclose: PopupEvent
9823 // Fired when a popup in the map is closed
9824 map.fire('popupclose', {popup: this});
9828 // @section Popup events
9829 // @event popupclose: PopupEvent
9830 // Fired when a popup bound to this layer is closed
9831 this._source.fire('popupclose', {popup: this}, true);
9832 if (!(this._source instanceof Path)) {
9833 this._source.off('preclick', stopPropagation);
9838 getEvents: function () {
9839 var events = DivOverlay.prototype.getEvents.call(this);
9841 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
9842 events.preclick = this._close;
9845 if (this.options.keepInView) {
9846 events.moveend = this._adjustPan;
9852 _close: function () {
9854 this._map.closePopup(this);
9858 _initLayout: function () {
9859 var prefix = 'leaflet-popup',
9860 container = this._container = create$1('div',
9861 prefix + ' ' + (this.options.className || '') +
9862 ' leaflet-zoom-animated');
9864 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
9865 this._contentNode = create$1('div', prefix + '-content', wrapper);
9867 disableClickPropagation(wrapper);
9868 disableScrollPropagation(this._contentNode);
9869 on(wrapper, 'contextmenu', stopPropagation);
9871 this._tipContainer = create$1('div', prefix + '-tip-container', container);
9872 this._tip = create$1('div', prefix + '-tip', this._tipContainer);
9874 if (this.options.closeButton) {
9875 var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
9876 closeButton.href = '#close';
9877 closeButton.innerHTML = '×';
9879 on(closeButton, 'click', this._onCloseButtonClick, this);
9883 _updateLayout: function () {
9884 var container = this._contentNode,
9885 style = container.style;
9888 style.whiteSpace = 'nowrap';
9890 var width = container.offsetWidth;
9891 width = Math.min(width, this.options.maxWidth);
9892 width = Math.max(width, this.options.minWidth);
9894 style.width = (width + 1) + 'px';
9895 style.whiteSpace = '';
9899 var height = container.offsetHeight,
9900 maxHeight = this.options.maxHeight,
9901 scrolledClass = 'leaflet-popup-scrolled';
9903 if (maxHeight && height > maxHeight) {
9904 style.height = maxHeight + 'px';
9905 addClass(container, scrolledClass);
9907 removeClass(container, scrolledClass);
9910 this._containerWidth = this._container.offsetWidth;
9913 _animateZoom: function (e) {
9914 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
9915 anchor = this._getAnchor();
9916 setPosition(this._container, pos.add(anchor));
9919 _adjustPan: function () {
9920 if (!this.options.autoPan) { return; }
9921 if (this._map._panAnim) { this._map._panAnim.stop(); }
9923 var map = this._map,
9924 marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
9925 containerHeight = this._container.offsetHeight + marginBottom,
9926 containerWidth = this._containerWidth,
9927 layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
9929 layerPos._add(getPosition(this._container));
9931 var containerPos = map.layerPointToContainerPoint(layerPos),
9932 padding = toPoint(this.options.autoPanPadding),
9933 paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
9934 paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
9935 size = map.getSize(),
9939 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
9940 dx = containerPos.x + containerWidth - size.x + paddingBR.x;
9942 if (containerPos.x - dx - paddingTL.x < 0) { // left
9943 dx = containerPos.x - paddingTL.x;
9945 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
9946 dy = containerPos.y + containerHeight - size.y + paddingBR.y;
9948 if (containerPos.y - dy - paddingTL.y < 0) { // top
9949 dy = containerPos.y - paddingTL.y;
9953 // @section Popup events
9954 // @event autopanstart: Event
9955 // Fired when the map starts autopanning when opening a popup.
9958 .fire('autopanstart')
9963 _onCloseButtonClick: function (e) {
9968 _getAnchor: function () {
9969 // Where should we anchor the popup on the source layer?
9970 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
9976 // @factory L.popup(options?: Popup options, source?: Layer)
9977 // 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.
9978 var popup = function (options, source) {
9979 return new Popup(options, source);
9984 * @section Interaction Options
9985 * @option closePopupOnClick: Boolean = true
9986 * Set it to `false` if you don't want popups to close when user clicks the map.
9989 closePopupOnClick: true
9994 // @section Methods for Layers and Controls
9996 // @method openPopup(popup: Popup): this
9997 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
9999 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
10000 // Creates a popup with the specified content and options and opens it in the given point on a map.
10001 openPopup: function (popup, latlng, options) {
10002 if (!(popup instanceof Popup)) {
10003 popup = new Popup(options).setContent(popup);
10007 popup.setLatLng(latlng);
10010 if (this.hasLayer(popup)) {
10014 if (this._popup && this._popup.options.autoClose) {
10018 this._popup = popup;
10019 return this.addLayer(popup);
10022 // @method closePopup(popup?: Popup): this
10023 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
10024 closePopup: function (popup) {
10025 if (!popup || popup === this._popup) {
10026 popup = this._popup;
10027 this._popup = null;
10030 this.removeLayer(popup);
10038 * @section Popup methods example
10040 * All layers share a set of methods convenient for binding popups to it.
10043 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
10044 * layer.openPopup();
10045 * layer.closePopup();
10048 * 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.
10051 // @section Popup methods
10054 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
10055 // Binds a popup to the layer with the passed `content` and sets up the
10056 // necessary event listeners. If a `Function` is passed it will receive
10057 // the layer as the first argument and should return a `String` or `HTMLElement`.
10058 bindPopup: function (content, options) {
10060 if (content instanceof Popup) {
10061 setOptions(content, options);
10062 this._popup = content;
10063 content._source = this;
10065 if (!this._popup || options) {
10066 this._popup = new Popup(options, this);
10068 this._popup.setContent(content);
10071 if (!this._popupHandlersAdded) {
10073 click: this._openPopup,
10074 keypress: this._onKeyPress,
10075 remove: this.closePopup,
10076 move: this._movePopup
10078 this._popupHandlersAdded = true;
10084 // @method unbindPopup(): this
10085 // Removes the popup previously bound with `bindPopup`.
10086 unbindPopup: function () {
10089 click: this._openPopup,
10090 keypress: this._onKeyPress,
10091 remove: this.closePopup,
10092 move: this._movePopup
10094 this._popupHandlersAdded = false;
10095 this._popup = null;
10100 // @method openPopup(latlng?: LatLng): this
10101 // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
10102 openPopup: function (layer, latlng) {
10103 if (this._popup && this._map) {
10104 latlng = this._popup._prepareOpen(this, layer, latlng);
10106 // open the popup on the map
10107 this._map.openPopup(this._popup, latlng);
10113 // @method closePopup(): this
10114 // Closes the popup bound to this layer if it is open.
10115 closePopup: function () {
10117 this._popup._close();
10122 // @method togglePopup(): this
10123 // Opens or closes the popup bound to this layer depending on its current state.
10124 togglePopup: function (target) {
10126 if (this._popup._map) {
10129 this.openPopup(target);
10135 // @method isPopupOpen(): boolean
10136 // Returns `true` if the popup bound to this layer is currently open.
10137 isPopupOpen: function () {
10138 return (this._popup ? this._popup.isOpen() : false);
10141 // @method setPopupContent(content: String|HTMLElement|Popup): this
10142 // Sets the content of the popup bound to this layer.
10143 setPopupContent: function (content) {
10145 this._popup.setContent(content);
10150 // @method getPopup(): Popup
10151 // Returns the popup bound to this layer.
10152 getPopup: function () {
10153 return this._popup;
10156 _openPopup: function (e) {
10157 var layer = e.layer || e.target;
10159 if (!this._popup) {
10167 // prevent map click
10170 // if this inherits from Path its a vector and we can just
10171 // open the popup at the new location
10172 if (layer instanceof Path) {
10173 this.openPopup(e.layer || e.target, e.latlng);
10177 // otherwise treat it like a marker and figure out
10178 // if we should toggle it open/closed
10179 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
10182 this.openPopup(layer, e.latlng);
10186 _movePopup: function (e) {
10187 this._popup.setLatLng(e.latlng);
10190 _onKeyPress: function (e) {
10191 if (e.originalEvent.keyCode === 13) {
10192 this._openPopup(e);
10199 * @inherits DivOverlay
10201 * Used to display small texts on top of map layers.
10206 * marker.bindTooltip("my tooltip text").openTooltip();
10208 * Note about tooltip offset. Leaflet takes two options in consideration
10209 * for computing tooltip offsetting:
10210 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
10211 * Add a positive x offset to move the tooltip to the right, and a positive y offset to
10212 * move it to the bottom. Negatives will move to the left and top.
10213 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
10214 * should adapt this value if you use a custom icon.
10218 // @namespace Tooltip
10219 var Tooltip = DivOverlay.extend({
10222 // @aka Tooltip options
10224 // @option pane: String = 'tooltipPane'
10225 // `Map pane` where the tooltip will be added.
10226 pane: 'tooltipPane',
10228 // @option offset: Point = Point(0, 0)
10229 // Optional offset of the tooltip position.
10232 // @option direction: String = 'auto'
10233 // Direction where to open the tooltip. Possible values are: `right`, `left`,
10234 // `top`, `bottom`, `center`, `auto`.
10235 // `auto` will dynamically switch between `right` and `left` according to the tooltip
10236 // position on the map.
10239 // @option permanent: Boolean = false
10240 // Whether to open the tooltip permanently or only on mouseover.
10243 // @option sticky: Boolean = false
10244 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
10247 // @option interactive: Boolean = false
10248 // If true, the tooltip will listen to the feature events.
10249 interactive: false,
10251 // @option opacity: Number = 0.9
10252 // Tooltip container opacity.
10256 onAdd: function (map) {
10257 DivOverlay.prototype.onAdd.call(this, map);
10258 this.setOpacity(this.options.opacity);
10261 // @section Tooltip events
10262 // @event tooltipopen: TooltipEvent
10263 // Fired when a tooltip is opened in the map.
10264 map.fire('tooltipopen', {tooltip: this});
10266 if (this._source) {
10267 // @namespace Layer
10268 // @section Tooltip events
10269 // @event tooltipopen: TooltipEvent
10270 // Fired when a tooltip bound to this layer is opened.
10271 this._source.fire('tooltipopen', {tooltip: this}, true);
10275 onRemove: function (map) {
10276 DivOverlay.prototype.onRemove.call(this, map);
10279 // @section Tooltip events
10280 // @event tooltipclose: TooltipEvent
10281 // Fired when a tooltip in the map is closed.
10282 map.fire('tooltipclose', {tooltip: this});
10284 if (this._source) {
10285 // @namespace Layer
10286 // @section Tooltip events
10287 // @event tooltipclose: TooltipEvent
10288 // Fired when a tooltip bound to this layer is closed.
10289 this._source.fire('tooltipclose', {tooltip: this}, true);
10293 getEvents: function () {
10294 var events = DivOverlay.prototype.getEvents.call(this);
10296 if (touch && !this.options.permanent) {
10297 events.preclick = this._close;
10303 _close: function () {
10305 this._map.closeTooltip(this);
10309 _initLayout: function () {
10310 var prefix = 'leaflet-tooltip',
10311 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
10313 this._contentNode = this._container = create$1('div', className);
10316 _updateLayout: function () {},
10318 _adjustPan: function () {},
10320 _setPosition: function (pos) {
10321 var map = this._map,
10322 container = this._container,
10323 centerPoint = map.latLngToContainerPoint(map.getCenter()),
10324 tooltipPoint = map.layerPointToContainerPoint(pos),
10325 direction = this.options.direction,
10326 tooltipWidth = container.offsetWidth,
10327 tooltipHeight = container.offsetHeight,
10328 offset = toPoint(this.options.offset),
10329 anchor = this._getAnchor();
10331 if (direction === 'top') {
10332 pos = pos.add(toPoint(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true));
10333 } else if (direction === 'bottom') {
10334 pos = pos.subtract(toPoint(tooltipWidth / 2 - offset.x, -offset.y, true));
10335 } else if (direction === 'center') {
10336 pos = pos.subtract(toPoint(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true));
10337 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
10338 direction = 'right';
10339 pos = pos.add(toPoint(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true));
10341 direction = 'left';
10342 pos = pos.subtract(toPoint(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true));
10345 removeClass(container, 'leaflet-tooltip-right');
10346 removeClass(container, 'leaflet-tooltip-left');
10347 removeClass(container, 'leaflet-tooltip-top');
10348 removeClass(container, 'leaflet-tooltip-bottom');
10349 addClass(container, 'leaflet-tooltip-' + direction);
10350 setPosition(container, pos);
10353 _updatePosition: function () {
10354 var pos = this._map.latLngToLayerPoint(this._latlng);
10355 this._setPosition(pos);
10358 setOpacity: function (opacity) {
10359 this.options.opacity = opacity;
10361 if (this._container) {
10362 setOpacity(this._container, opacity);
10366 _animateZoom: function (e) {
10367 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
10368 this._setPosition(pos);
10371 _getAnchor: function () {
10372 // Where should we anchor the tooltip on the source layer?
10373 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
10378 // @namespace Tooltip
10379 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
10380 // 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.
10381 var tooltip = function (options, source) {
10382 return new Tooltip(options, source);
10386 // @section Methods for Layers and Controls
10389 // @method openTooltip(tooltip: Tooltip): this
10390 // Opens the specified tooltip.
10392 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
10393 // Creates a tooltip with the specified content and options and open it.
10394 openTooltip: function (tooltip, latlng, options) {
10395 if (!(tooltip instanceof Tooltip)) {
10396 tooltip = new Tooltip(options).setContent(tooltip);
10400 tooltip.setLatLng(latlng);
10403 if (this.hasLayer(tooltip)) {
10407 return this.addLayer(tooltip);
10410 // @method closeTooltip(tooltip?: Tooltip): this
10411 // Closes the tooltip given as parameter.
10412 closeTooltip: function (tooltip) {
10414 this.removeLayer(tooltip);
10423 * @section Tooltip methods example
10425 * All layers share a set of methods convenient for binding tooltips to it.
10428 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
10429 * layer.openTooltip();
10430 * layer.closeTooltip();
10434 // @section Tooltip methods
10437 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
10438 // Binds a tooltip to the layer with the passed `content` and sets up the
10439 // necessary event listeners. If a `Function` is passed it will receive
10440 // the layer as the first argument and should return a `String` or `HTMLElement`.
10441 bindTooltip: function (content, options) {
10443 if (content instanceof Tooltip) {
10444 setOptions(content, options);
10445 this._tooltip = content;
10446 content._source = this;
10448 if (!this._tooltip || options) {
10449 this._tooltip = new Tooltip(options, this);
10451 this._tooltip.setContent(content);
10455 this._initTooltipInteractions();
10457 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
10458 this.openTooltip();
10464 // @method unbindTooltip(): this
10465 // Removes the tooltip previously bound with `bindTooltip`.
10466 unbindTooltip: function () {
10467 if (this._tooltip) {
10468 this._initTooltipInteractions(true);
10469 this.closeTooltip();
10470 this._tooltip = null;
10475 _initTooltipInteractions: function (remove$$1) {
10476 if (!remove$$1 && this._tooltipHandlersAdded) { return; }
10477 var onOff = remove$$1 ? 'off' : 'on',
10479 remove: this.closeTooltip,
10480 move: this._moveTooltip
10482 if (!this._tooltip.options.permanent) {
10483 events.mouseover = this._openTooltip;
10484 events.mouseout = this.closeTooltip;
10485 if (this._tooltip.options.sticky) {
10486 events.mousemove = this._moveTooltip;
10489 events.click = this._openTooltip;
10492 events.add = this._openTooltip;
10494 this[onOff](events);
10495 this._tooltipHandlersAdded = !remove$$1;
10498 // @method openTooltip(latlng?: LatLng): this
10499 // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
10500 openTooltip: function (layer, latlng) {
10501 if (this._tooltip && this._map) {
10502 latlng = this._tooltip._prepareOpen(this, layer, latlng);
10504 // open the tooltip on the map
10505 this._map.openTooltip(this._tooltip, latlng);
10507 // Tooltip container may not be defined if not permanent and never
10509 if (this._tooltip.options.interactive && this._tooltip._container) {
10510 addClass(this._tooltip._container, 'leaflet-clickable');
10511 this.addInteractiveTarget(this._tooltip._container);
10518 // @method closeTooltip(): this
10519 // Closes the tooltip bound to this layer if it is open.
10520 closeTooltip: function () {
10521 if (this._tooltip) {
10522 this._tooltip._close();
10523 if (this._tooltip.options.interactive && this._tooltip._container) {
10524 removeClass(this._tooltip._container, 'leaflet-clickable');
10525 this.removeInteractiveTarget(this._tooltip._container);
10531 // @method toggleTooltip(): this
10532 // Opens or closes the tooltip bound to this layer depending on its current state.
10533 toggleTooltip: function (target) {
10534 if (this._tooltip) {
10535 if (this._tooltip._map) {
10536 this.closeTooltip();
10538 this.openTooltip(target);
10544 // @method isTooltipOpen(): boolean
10545 // Returns `true` if the tooltip bound to this layer is currently open.
10546 isTooltipOpen: function () {
10547 return this._tooltip.isOpen();
10550 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10551 // Sets the content of the tooltip bound to this layer.
10552 setTooltipContent: function (content) {
10553 if (this._tooltip) {
10554 this._tooltip.setContent(content);
10559 // @method getTooltip(): Tooltip
10560 // Returns the tooltip bound to this layer.
10561 getTooltip: function () {
10562 return this._tooltip;
10565 _openTooltip: function (e) {
10566 var layer = e.layer || e.target;
10568 if (!this._tooltip || !this._map) {
10571 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
10574 _moveTooltip: function (e) {
10575 var latlng = e.latlng, containerPoint, layerPoint;
10576 if (this._tooltip.options.sticky && e.originalEvent) {
10577 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
10578 layerPoint = this._map.containerPointToLayerPoint(containerPoint);
10579 latlng = this._map.layerPointToLatLng(layerPoint);
10581 this._tooltip.setLatLng(latlng);
10590 * Represents a lightweight icon for markers that uses a simple `<div>`
10591 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
10595 * var myIcon = L.divIcon({className: 'my-div-icon'});
10596 * // you can set .my-div-icon styles in CSS
10598 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
10601 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
10604 var DivIcon = Icon.extend({
10607 // @aka DivIcon options
10608 iconSize: [12, 12], // also can be set through CSS
10610 // iconAnchor: (Point),
10611 // popupAnchor: (Point),
10613 // @option html: String|HTMLElement = ''
10614 // Custom HTML code to put inside the div element, empty by default. Alternatively,
10615 // an instance of `HTMLElement`.
10618 // @option bgPos: Point = [0, 0]
10619 // Optional relative position of the background, in pixels
10622 className: 'leaflet-div-icon'
10625 createIcon: function (oldIcon) {
10626 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
10627 options = this.options;
10629 if (options.html instanceof Element) {
10631 div.appendChild(options.html);
10633 div.innerHTML = options.html !== false ? options.html : '';
10636 if (options.bgPos) {
10637 var bgPos = toPoint(options.bgPos);
10638 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
10640 this._setIconStyles(div, 'icon');
10645 createShadow: function () {
10650 // @factory L.divIcon(options: DivIcon options)
10651 // Creates a `DivIcon` instance with the given options.
10652 function divIcon(options) {
10653 return new DivIcon(options);
10656 Icon.Default = IconDefault;
10663 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
10664 * 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.
10667 * @section Synchronous usage
10670 * 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.
10673 * var CanvasLayer = L.GridLayer.extend({
10674 * createTile: function(coords){
10675 * // create a <canvas> element for drawing
10676 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10678 * // setup tile width and height according to the options
10679 * var size = this.getTileSize();
10680 * tile.width = size.x;
10681 * tile.height = size.y;
10683 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z
10684 * var ctx = tile.getContext('2d');
10686 * // return the tile so it can be rendered on screen
10692 * @section Asynchronous usage
10695 * 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.
10698 * var CanvasLayer = L.GridLayer.extend({
10699 * createTile: function(coords, done){
10702 * // create a <canvas> element for drawing
10703 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10705 * // setup tile width and height according to the options
10706 * var size = this.getTileSize();
10707 * tile.width = size.x;
10708 * tile.height = size.y;
10710 * // draw something asynchronously and pass the tile to the done() callback
10711 * setTimeout(function() {
10712 * done(error, tile);
10724 var GridLayer = Layer.extend({
10727 // @aka GridLayer options
10729 // @option tileSize: Number|Point = 256
10730 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
10733 // @option opacity: Number = 1.0
10734 // Opacity of the tiles. Can be used in the `createTile()` function.
10737 // @option updateWhenIdle: Boolean = (depends)
10738 // Load new tiles only when panning ends.
10739 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
10740 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
10741 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
10742 updateWhenIdle: mobile,
10744 // @option updateWhenZooming: Boolean = true
10745 // 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.
10746 updateWhenZooming: true,
10748 // @option updateInterval: Number = 200
10749 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
10750 updateInterval: 200,
10752 // @option zIndex: Number = 1
10753 // The explicit zIndex of the tile layer.
10756 // @option bounds: LatLngBounds = undefined
10757 // If set, tiles will only be loaded inside the set `LatLngBounds`.
10760 // @option minZoom: Number = 0
10761 // The minimum zoom level down to which this layer will be displayed (inclusive).
10764 // @option maxZoom: Number = undefined
10765 // The maximum zoom level up to which this layer will be displayed (inclusive).
10766 maxZoom: undefined,
10768 // @option maxNativeZoom: Number = undefined
10769 // Maximum zoom number the tile source has available. If it is specified,
10770 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
10771 // from `maxNativeZoom` level and auto-scaled.
10772 maxNativeZoom: undefined,
10774 // @option minNativeZoom: Number = undefined
10775 // Minimum zoom number the tile source has available. If it is specified,
10776 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
10777 // from `minNativeZoom` level and auto-scaled.
10778 minNativeZoom: undefined,
10780 // @option noWrap: Boolean = false
10781 // Whether the layer is wrapped around the antimeridian. If `true`, the
10782 // GridLayer will only be displayed once at low zoom levels. Has no
10783 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
10784 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
10785 // tiles outside the CRS limits.
10788 // @option pane: String = 'tilePane'
10789 // `Map pane` where the grid layer will be added.
10792 // @option className: String = ''
10793 // A custom class name to assign to the tile layer. Empty by default.
10796 // @option keepBuffer: Number = 2
10797 // When panning the map, keep this many rows and columns of tiles before unloading them.
10801 initialize: function (options) {
10802 setOptions(this, options);
10805 onAdd: function () {
10806 this._initContainer();
10815 beforeAdd: function (map) {
10816 map._addZoomLimit(this);
10819 onRemove: function (map) {
10820 this._removeAllTiles();
10821 remove(this._container);
10822 map._removeZoomLimit(this);
10823 this._container = null;
10824 this._tileZoom = undefined;
10827 // @method bringToFront: this
10828 // Brings the tile layer to the top of all tile layers.
10829 bringToFront: function () {
10831 toFront(this._container);
10832 this._setAutoZIndex(Math.max);
10837 // @method bringToBack: this
10838 // Brings the tile layer to the bottom of all tile layers.
10839 bringToBack: function () {
10841 toBack(this._container);
10842 this._setAutoZIndex(Math.min);
10847 // @method getContainer: HTMLElement
10848 // Returns the HTML element that contains the tiles for this layer.
10849 getContainer: function () {
10850 return this._container;
10853 // @method setOpacity(opacity: Number): this
10854 // Changes the [opacity](#gridlayer-opacity) of the grid layer.
10855 setOpacity: function (opacity) {
10856 this.options.opacity = opacity;
10857 this._updateOpacity();
10861 // @method setZIndex(zIndex: Number): this
10862 // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
10863 setZIndex: function (zIndex) {
10864 this.options.zIndex = zIndex;
10865 this._updateZIndex();
10870 // @method isLoading: Boolean
10871 // Returns `true` if any tile in the grid layer has not finished loading.
10872 isLoading: function () {
10873 return this._loading;
10876 // @method redraw: this
10877 // Causes the layer to clear all the tiles and request them again.
10878 redraw: function () {
10880 this._removeAllTiles();
10886 getEvents: function () {
10888 viewprereset: this._invalidateAll,
10889 viewreset: this._resetView,
10890 zoom: this._resetView,
10891 moveend: this._onMoveEnd
10894 if (!this.options.updateWhenIdle) {
10895 // update tiles on move, but not more often than once per given interval
10896 if (!this._onMove) {
10897 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
10900 events.move = this._onMove;
10903 if (this._zoomAnimated) {
10904 events.zoomanim = this._animateZoom;
10910 // @section Extension methods
10911 // Layers extending `GridLayer` shall reimplement the following method.
10912 // @method createTile(coords: Object, done?: Function): HTMLElement
10913 // Called only internally, must be overridden by classes extending `GridLayer`.
10914 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
10915 // is specified, it must be called when the tile has finished loading and drawing.
10916 createTile: function () {
10917 return document.createElement('div');
10921 // @method getTileSize: Point
10922 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
10923 getTileSize: function () {
10924 var s = this.options.tileSize;
10925 return s instanceof Point ? s : new Point(s, s);
10928 _updateZIndex: function () {
10929 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
10930 this._container.style.zIndex = this.options.zIndex;
10934 _setAutoZIndex: function (compare) {
10935 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
10937 var layers = this.getPane().children,
10938 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
10940 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
10942 zIndex = layers[i].style.zIndex;
10944 if (layers[i] !== this._container && zIndex) {
10945 edgeZIndex = compare(edgeZIndex, +zIndex);
10949 if (isFinite(edgeZIndex)) {
10950 this.options.zIndex = edgeZIndex + compare(-1, 1);
10951 this._updateZIndex();
10955 _updateOpacity: function () {
10956 if (!this._map) { return; }
10958 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
10959 if (ielt9) { return; }
10961 setOpacity(this._container, this.options.opacity);
10963 var now = +new Date(),
10967 for (var key in this._tiles) {
10968 var tile = this._tiles[key];
10969 if (!tile.current || !tile.loaded) { continue; }
10971 var fade = Math.min(1, (now - tile.loaded) / 200);
10973 setOpacity(tile.el, fade);
10980 this._onOpaqueTile(tile);
10982 tile.active = true;
10986 if (willPrune && !this._noPrune) { this._pruneTiles(); }
10989 cancelAnimFrame(this._fadeFrame);
10990 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
10994 _onOpaqueTile: falseFn,
10996 _initContainer: function () {
10997 if (this._container) { return; }
10999 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
11000 this._updateZIndex();
11002 if (this.options.opacity < 1) {
11003 this._updateOpacity();
11006 this.getPane().appendChild(this._container);
11009 _updateLevels: function () {
11011 var zoom = this._tileZoom,
11012 maxZoom = this.options.maxZoom;
11014 if (zoom === undefined) { return undefined; }
11016 for (var z in this._levels) {
11017 if (this._levels[z].el.children.length || z === zoom) {
11018 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
11019 this._onUpdateLevel(z);
11021 remove(this._levels[z].el);
11022 this._removeTilesAtZoom(z);
11023 this._onRemoveLevel(z);
11024 delete this._levels[z];
11028 var level = this._levels[zoom],
11032 level = this._levels[zoom] = {};
11034 level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
11035 level.el.style.zIndex = maxZoom;
11037 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
11040 this._setZoomTransform(level, map.getCenter(), map.getZoom());
11042 // force the browser to consider the newly added element for transition
11043 falseFn(level.el.offsetWidth);
11045 this._onCreateLevel(level);
11048 this._level = level;
11053 _onUpdateLevel: falseFn,
11055 _onRemoveLevel: falseFn,
11057 _onCreateLevel: falseFn,
11059 _pruneTiles: function () {
11066 var zoom = this._map.getZoom();
11067 if (zoom > this.options.maxZoom ||
11068 zoom < this.options.minZoom) {
11069 this._removeAllTiles();
11073 for (key in this._tiles) {
11074 tile = this._tiles[key];
11075 tile.retain = tile.current;
11078 for (key in this._tiles) {
11079 tile = this._tiles[key];
11080 if (tile.current && !tile.active) {
11081 var coords = tile.coords;
11082 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
11083 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
11088 for (key in this._tiles) {
11089 if (!this._tiles[key].retain) {
11090 this._removeTile(key);
11095 _removeTilesAtZoom: function (zoom) {
11096 for (var key in this._tiles) {
11097 if (this._tiles[key].coords.z !== zoom) {
11100 this._removeTile(key);
11104 _removeAllTiles: function () {
11105 for (var key in this._tiles) {
11106 this._removeTile(key);
11110 _invalidateAll: function () {
11111 for (var z in this._levels) {
11112 remove(this._levels[z].el);
11113 this._onRemoveLevel(z);
11114 delete this._levels[z];
11116 this._removeAllTiles();
11118 this._tileZoom = undefined;
11121 _retainParent: function (x, y, z, minZoom) {
11122 var x2 = Math.floor(x / 2),
11123 y2 = Math.floor(y / 2),
11125 coords2 = new Point(+x2, +y2);
11128 var key = this._tileCoordsToKey(coords2),
11129 tile = this._tiles[key];
11131 if (tile && tile.active) {
11132 tile.retain = true;
11135 } else if (tile && tile.loaded) {
11136 tile.retain = true;
11139 if (z2 > minZoom) {
11140 return this._retainParent(x2, y2, z2, minZoom);
11146 _retainChildren: function (x, y, z, maxZoom) {
11148 for (var i = 2 * x; i < 2 * x + 2; i++) {
11149 for (var j = 2 * y; j < 2 * y + 2; j++) {
11151 var coords = new Point(i, j);
11154 var key = this._tileCoordsToKey(coords),
11155 tile = this._tiles[key];
11157 if (tile && tile.active) {
11158 tile.retain = true;
11161 } else if (tile && tile.loaded) {
11162 tile.retain = true;
11165 if (z + 1 < maxZoom) {
11166 this._retainChildren(i, j, z + 1, maxZoom);
11172 _resetView: function (e) {
11173 var animating = e && (e.pinch || e.flyTo);
11174 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
11177 _animateZoom: function (e) {
11178 this._setView(e.center, e.zoom, true, e.noUpdate);
11181 _clampZoom: function (zoom) {
11182 var options = this.options;
11184 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
11185 return options.minNativeZoom;
11188 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
11189 return options.maxNativeZoom;
11195 _setView: function (center, zoom, noPrune, noUpdate) {
11196 var tileZoom = this._clampZoom(Math.round(zoom));
11197 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
11198 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
11199 tileZoom = undefined;
11202 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
11204 if (!noUpdate || tileZoomChanged) {
11206 this._tileZoom = tileZoom;
11208 if (this._abortLoading) {
11209 this._abortLoading();
11212 this._updateLevels();
11215 if (tileZoom !== undefined) {
11216 this._update(center);
11220 this._pruneTiles();
11223 // Flag to prevent _updateOpacity from pruning tiles during
11224 // a zoom anim or a pinch gesture
11225 this._noPrune = !!noPrune;
11228 this._setZoomTransforms(center, zoom);
11231 _setZoomTransforms: function (center, zoom) {
11232 for (var i in this._levels) {
11233 this._setZoomTransform(this._levels[i], center, zoom);
11237 _setZoomTransform: function (level, center, zoom) {
11238 var scale = this._map.getZoomScale(zoom, level.zoom),
11239 translate = level.origin.multiplyBy(scale)
11240 .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
11243 setTransform(level.el, translate, scale);
11245 setPosition(level.el, translate);
11249 _resetGrid: function () {
11250 var map = this._map,
11251 crs = map.options.crs,
11252 tileSize = this._tileSize = this.getTileSize(),
11253 tileZoom = this._tileZoom;
11255 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
11257 this._globalTileRange = this._pxBoundsToTileRange(bounds);
11260 this._wrapX = crs.wrapLng && !this.options.noWrap && [
11261 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
11262 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
11264 this._wrapY = crs.wrapLat && !this.options.noWrap && [
11265 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
11266 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
11270 _onMoveEnd: function () {
11271 if (!this._map || this._map._animatingZoom) { return; }
11276 _getTiledPixelBounds: function (center) {
11277 var map = this._map,
11278 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
11279 scale = map.getZoomScale(mapZoom, this._tileZoom),
11280 pixelCenter = map.project(center, this._tileZoom).floor(),
11281 halfSize = map.getSize().divideBy(scale * 2);
11283 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
11286 // Private method to load tiles in the grid's active zoom level according to map bounds
11287 _update: function (center) {
11288 var map = this._map;
11289 if (!map) { return; }
11290 var zoom = this._clampZoom(map.getZoom());
11292 if (center === undefined) { center = map.getCenter(); }
11293 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom
11295 var pixelBounds = this._getTiledPixelBounds(center),
11296 tileRange = this._pxBoundsToTileRange(pixelBounds),
11297 tileCenter = tileRange.getCenter(),
11299 margin = this.options.keepBuffer,
11300 noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
11301 tileRange.getTopRight().add([margin, -margin]));
11303 // Sanity check: panic if the tile range contains Infinity somewhere.
11304 if (!(isFinite(tileRange.min.x) &&
11305 isFinite(tileRange.min.y) &&
11306 isFinite(tileRange.max.x) &&
11307 isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
11309 for (var key in this._tiles) {
11310 var c = this._tiles[key].coords;
11311 if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
11312 this._tiles[key].current = false;
11316 // _update just loads more tiles. If the tile zoom level differs too much
11317 // from the map's, let _setView reset levels and prune old tiles.
11318 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
11320 // create a queue of coordinates to load tiles from
11321 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
11322 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
11323 var coords = new Point(i, j);
11324 coords.z = this._tileZoom;
11326 if (!this._isValidTile(coords)) { continue; }
11328 var tile = this._tiles[this._tileCoordsToKey(coords)];
11330 tile.current = true;
11332 queue.push(coords);
11337 // sort tile queue to load tiles in order of their distance to center
11338 queue.sort(function (a, b) {
11339 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
11342 if (queue.length !== 0) {
11343 // if it's the first batch of tiles to load
11344 if (!this._loading) {
11345 this._loading = true;
11346 // @event loading: Event
11347 // Fired when the grid layer starts loading tiles.
11348 this.fire('loading');
11351 // create DOM fragment to append tiles in one batch
11352 var fragment = document.createDocumentFragment();
11354 for (i = 0; i < queue.length; i++) {
11355 this._addTile(queue[i], fragment);
11358 this._level.el.appendChild(fragment);
11362 _isValidTile: function (coords) {
11363 var crs = this._map.options.crs;
11365 if (!crs.infinite) {
11366 // don't load tile if it's out of bounds and not wrapped
11367 var bounds = this._globalTileRange;
11368 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
11369 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
11372 if (!this.options.bounds) { return true; }
11374 // don't load tile if it doesn't intersect the bounds in options
11375 var tileBounds = this._tileCoordsToBounds(coords);
11376 return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
11379 _keyToBounds: function (key) {
11380 return this._tileCoordsToBounds(this._keyToTileCoords(key));
11383 _tileCoordsToNwSe: function (coords) {
11384 var map = this._map,
11385 tileSize = this.getTileSize(),
11386 nwPoint = coords.scaleBy(tileSize),
11387 sePoint = nwPoint.add(tileSize),
11388 nw = map.unproject(nwPoint, coords.z),
11389 se = map.unproject(sePoint, coords.z);
11393 // converts tile coordinates to its geographical bounds
11394 _tileCoordsToBounds: function (coords) {
11395 var bp = this._tileCoordsToNwSe(coords),
11396 bounds = new LatLngBounds(bp[0], bp[1]);
11398 if (!this.options.noWrap) {
11399 bounds = this._map.wrapLatLngBounds(bounds);
11403 // converts tile coordinates to key for the tile cache
11404 _tileCoordsToKey: function (coords) {
11405 return coords.x + ':' + coords.y + ':' + coords.z;
11408 // converts tile cache key to coordinates
11409 _keyToTileCoords: function (key) {
11410 var k = key.split(':'),
11411 coords = new Point(+k[0], +k[1]);
11416 _removeTile: function (key) {
11417 var tile = this._tiles[key];
11418 if (!tile) { return; }
11422 delete this._tiles[key];
11424 // @event tileunload: TileEvent
11425 // Fired when a tile is removed (e.g. when a tile goes off the screen).
11426 this.fire('tileunload', {
11428 coords: this._keyToTileCoords(key)
11432 _initTile: function (tile) {
11433 addClass(tile, 'leaflet-tile');
11435 var tileSize = this.getTileSize();
11436 tile.style.width = tileSize.x + 'px';
11437 tile.style.height = tileSize.y + 'px';
11439 tile.onselectstart = falseFn;
11440 tile.onmousemove = falseFn;
11442 // update opacity on tiles in IE7-8 because of filter inheritance problems
11443 if (ielt9 && this.options.opacity < 1) {
11444 setOpacity(tile, this.options.opacity);
11447 // without this hack, tiles disappear after zoom on Chrome for Android
11448 // https://github.com/Leaflet/Leaflet/issues/2078
11449 if (android && !android23) {
11450 tile.style.WebkitBackfaceVisibility = 'hidden';
11454 _addTile: function (coords, container) {
11455 var tilePos = this._getTilePos(coords),
11456 key = this._tileCoordsToKey(coords);
11458 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
11460 this._initTile(tile);
11462 // if createTile is defined with a second argument ("done" callback),
11463 // we know that tile is async and will be ready later; otherwise
11464 if (this.createTile.length < 2) {
11465 // mark tile as ready, but delay one frame for opacity animation to happen
11466 requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
11469 setPosition(tile, tilePos);
11471 // save tile in cache
11472 this._tiles[key] = {
11478 container.appendChild(tile);
11479 // @event tileloadstart: TileEvent
11480 // Fired when a tile is requested and starts loading.
11481 this.fire('tileloadstart', {
11487 _tileReady: function (coords, err, tile) {
11489 // @event tileerror: TileErrorEvent
11490 // Fired when there is an error loading a tile.
11491 this.fire('tileerror', {
11498 var key = this._tileCoordsToKey(coords);
11500 tile = this._tiles[key];
11501 if (!tile) { return; }
11503 tile.loaded = +new Date();
11504 if (this._map._fadeAnimated) {
11505 setOpacity(tile.el, 0);
11506 cancelAnimFrame(this._fadeFrame);
11507 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11509 tile.active = true;
11510 this._pruneTiles();
11514 addClass(tile.el, 'leaflet-tile-loaded');
11516 // @event tileload: TileEvent
11517 // Fired when a tile loads.
11518 this.fire('tileload', {
11524 if (this._noTilesToLoad()) {
11525 this._loading = false;
11526 // @event load: Event
11527 // Fired when the grid layer loaded all visible tiles.
11530 if (ielt9 || !this._map._fadeAnimated) {
11531 requestAnimFrame(this._pruneTiles, this);
11533 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
11534 // to trigger a pruning.
11535 setTimeout(bind(this._pruneTiles, this), 250);
11540 _getTilePos: function (coords) {
11541 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
11544 _wrapCoords: function (coords) {
11545 var newCoords = new Point(
11546 this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
11547 this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
11548 newCoords.z = coords.z;
11552 _pxBoundsToTileRange: function (bounds) {
11553 var tileSize = this.getTileSize();
11555 bounds.min.unscaleBy(tileSize).floor(),
11556 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
11559 _noTilesToLoad: function () {
11560 for (var key in this._tiles) {
11561 if (!this._tiles[key].loaded) { return false; }
11567 // @factory L.gridLayer(options?: GridLayer options)
11568 // Creates a new instance of GridLayer with the supplied options.
11569 function gridLayer(options) {
11570 return new GridLayer(options);
11575 * @inherits GridLayer
11577 * 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`.
11582 * 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);
11585 * @section URL template
11588 * A string of the following form:
11591 * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
11594 * `{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.
11596 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
11599 * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
11604 var TileLayer = GridLayer.extend({
11607 // @aka TileLayer options
11609 // @option minZoom: Number = 0
11610 // The minimum zoom level down to which this layer will be displayed (inclusive).
11613 // @option maxZoom: Number = 18
11614 // The maximum zoom level up to which this layer will be displayed (inclusive).
11617 // @option subdomains: String|String[] = 'abc'
11618 // 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.
11621 // @option errorTileUrl: String = ''
11622 // URL to the tile image to show in place of the tile that failed to load.
11625 // @option zoomOffset: Number = 0
11626 // The zoom number used in tile URLs will be offset with this value.
11629 // @option tms: Boolean = false
11630 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
11633 // @option zoomReverse: Boolean = false
11634 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
11635 zoomReverse: false,
11637 // @option detectRetina: Boolean = false
11638 // 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.
11639 detectRetina: false,
11641 // @option crossOrigin: Boolean|String = false
11642 // Whether the crossOrigin attribute will be added to the tiles.
11643 // 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.
11644 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
11648 initialize: function (url, options) {
11652 options = setOptions(this, options);
11654 // detecting retina displays, adjusting tileSize and zoom levels
11655 if (options.detectRetina && retina && options.maxZoom > 0) {
11657 options.tileSize = Math.floor(options.tileSize / 2);
11659 if (!options.zoomReverse) {
11660 options.zoomOffset++;
11663 options.zoomOffset--;
11667 options.minZoom = Math.max(0, options.minZoom);
11670 if (typeof options.subdomains === 'string') {
11671 options.subdomains = options.subdomains.split('');
11674 // for https://github.com/Leaflet/Leaflet/issues/137
11676 this.on('tileunload', this._onTileRemove);
11680 // @method setUrl(url: String, noRedraw?: Boolean): this
11681 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
11682 // If the URL does not change, the layer will not be redrawn unless
11683 // the noRedraw parameter is set to false.
11684 setUrl: function (url, noRedraw) {
11685 if (this._url === url && noRedraw === undefined) {
11697 // @method createTile(coords: Object, done?: Function): HTMLElement
11698 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
11699 // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
11700 // callback is called when the tile has been loaded.
11701 createTile: function (coords, done) {
11702 var tile = document.createElement('img');
11704 on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
11705 on(tile, 'error', bind(this._tileOnError, this, done, tile));
11707 if (this.options.crossOrigin || this.options.crossOrigin === '') {
11708 tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
11712 Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
11713 http://www.w3.org/TR/WCAG20-TECHS/H67
11718 Set role="presentation" to force screen readers to ignore this
11719 https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
11721 tile.setAttribute('role', 'presentation');
11723 tile.src = this.getTileUrl(coords);
11728 // @section Extension methods
11730 // Layers extending `TileLayer` might reimplement the following method.
11731 // @method getTileUrl(coords: Object): String
11732 // Called only internally, returns the URL for a tile given its coordinates.
11733 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
11734 getTileUrl: function (coords) {
11736 r: retina ? '@2x' : '',
11737 s: this._getSubdomain(coords),
11740 z: this._getZoomForUrl()
11742 if (this._map && !this._map.options.crs.infinite) {
11743 var invertedY = this._globalTileRange.max.y - coords.y;
11744 if (this.options.tms) {
11745 data['y'] = invertedY;
11747 data['-y'] = invertedY;
11750 return template(this._url, extend(data, this.options));
11753 _tileOnLoad: function (done, tile) {
11754 // For https://github.com/Leaflet/Leaflet/issues/3332
11756 setTimeout(bind(done, this, null, tile), 0);
11762 _tileOnError: function (done, tile, e) {
11763 var errorUrl = this.options.errorTileUrl;
11764 if (errorUrl && tile.getAttribute('src') !== errorUrl) {
11765 tile.src = errorUrl;
11770 _onTileRemove: function (e) {
11771 e.tile.onload = null;
11774 _getZoomForUrl: function () {
11775 var zoom = this._tileZoom,
11776 maxZoom = this.options.maxZoom,
11777 zoomReverse = this.options.zoomReverse,
11778 zoomOffset = this.options.zoomOffset;
11781 zoom = maxZoom - zoom;
11784 return zoom + zoomOffset;
11787 _getSubdomain: function (tilePoint) {
11788 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
11789 return this.options.subdomains[index];
11792 // stops loading all tiles in the background layer
11793 _abortLoading: function () {
11795 for (i in this._tiles) {
11796 if (this._tiles[i].coords.z !== this._tileZoom) {
11797 tile = this._tiles[i].el;
11799 tile.onload = falseFn;
11800 tile.onerror = falseFn;
11802 if (!tile.complete) {
11803 tile.src = emptyImageUrl;
11805 delete this._tiles[i];
11811 _removeTile: function (key) {
11812 var tile = this._tiles[key];
11813 if (!tile) { return; }
11815 // Cancels any pending http requests associated with the tile
11816 // unless we're on Android's stock browser,
11817 // see https://github.com/Leaflet/Leaflet/issues/137
11818 if (!androidStock) {
11819 tile.el.setAttribute('src', emptyImageUrl);
11822 return GridLayer.prototype._removeTile.call(this, key);
11825 _tileReady: function (coords, err, tile) {
11826 if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) {
11830 return GridLayer.prototype._tileReady.call(this, coords, err, tile);
11835 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
11836 // Instantiates a tile layer object given a `URL template` and optionally an options object.
11838 function tileLayer(url, options) {
11839 return new TileLayer(url, options);
11843 * @class TileLayer.WMS
11844 * @inherits TileLayer
11845 * @aka L.TileLayer.WMS
11846 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
11851 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
11852 * layers: 'nexrad-n0r-900913',
11853 * format: 'image/png',
11854 * transparent: true,
11855 * attribution: "Weather data © 2012 IEM Nexrad"
11860 var TileLayerWMS = TileLayer.extend({
11863 // @aka TileLayer.WMS options
11864 // If any custom options not documented here are used, they will be sent to the
11865 // WMS server as extra parameters in each request URL. This can be useful for
11866 // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
11867 defaultWmsParams: {
11871 // @option layers: String = ''
11872 // **(required)** Comma-separated list of WMS layers to show.
11875 // @option styles: String = ''
11876 // Comma-separated list of WMS styles.
11879 // @option format: String = 'image/jpeg'
11880 // WMS image format (use `'image/png'` for layers with transparency).
11881 format: 'image/jpeg',
11883 // @option transparent: Boolean = false
11884 // If `true`, the WMS service will return images with transparency.
11885 transparent: false,
11887 // @option version: String = '1.1.1'
11888 // Version of the WMS service to use
11893 // @option crs: CRS = null
11894 // Coordinate Reference System to use for the WMS requests, defaults to
11895 // map CRS. Don't change this if you're not sure what it means.
11898 // @option uppercase: Boolean = false
11899 // If `true`, WMS request parameter keys will be uppercase.
11903 initialize: function (url, options) {
11907 var wmsParams = extend({}, this.defaultWmsParams);
11909 // all keys that are not TileLayer options go to WMS params
11910 for (var i in options) {
11911 if (!(i in this.options)) {
11912 wmsParams[i] = options[i];
11916 options = setOptions(this, options);
11918 var realRetina = options.detectRetina && retina ? 2 : 1;
11919 var tileSize = this.getTileSize();
11920 wmsParams.width = tileSize.x * realRetina;
11921 wmsParams.height = tileSize.y * realRetina;
11923 this.wmsParams = wmsParams;
11926 onAdd: function (map) {
11928 this._crs = this.options.crs || map.options.crs;
11929 this._wmsVersion = parseFloat(this.wmsParams.version);
11931 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
11932 this.wmsParams[projectionKey] = this._crs.code;
11934 TileLayer.prototype.onAdd.call(this, map);
11937 getTileUrl: function (coords) {
11939 var tileBounds = this._tileCoordsToNwSe(coords),
11941 bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
11944 bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
11945 [min.y, min.x, max.y, max.x] :
11946 [min.x, min.y, max.x, max.y]).join(','),
11947 url = TileLayer.prototype.getTileUrl.call(this, coords);
11949 getParamString(this.wmsParams, url, this.options.uppercase) +
11950 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
11953 // @method setParams(params: Object, noRedraw?: Boolean): this
11954 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
11955 setParams: function (params, noRedraw) {
11957 extend(this.wmsParams, params);
11968 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
11969 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
11970 function tileLayerWMS(url, options) {
11971 return new TileLayerWMS(url, options);
11974 TileLayer.WMS = TileLayerWMS;
11975 tileLayer.wms = tileLayerWMS;
11982 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
11983 * DOM container of the renderer, its bounds, and its zoom animation.
11985 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
11986 * itself can be added or removed to the map. All paths use a renderer, which can
11987 * be implicit (the map will decide the type of renderer and use it automatically)
11988 * or explicit (using the [`renderer`](#path-renderer) option of the path).
11990 * Do not use this class directly, use `SVG` and `Canvas` instead.
11992 * @event update: Event
11993 * Fired when the renderer updates its bounds, center and zoom, for example when
11994 * its map has moved
11997 var Renderer = Layer.extend({
12000 // @aka Renderer options
12002 // @option padding: Number = 0.1
12003 // How much to extend the clip area around the map view (relative to its size)
12004 // e.g. 0.1 would be 10% of map view in each direction
12007 // @option tolerance: Number = 0
12008 // How much to extend click tolerance round a path/object on the map
12012 initialize: function (options) {
12013 setOptions(this, options);
12015 this._layers = this._layers || {};
12018 onAdd: function () {
12019 if (!this._container) {
12020 this._initContainer(); // defined by renderer implementations
12022 if (this._zoomAnimated) {
12023 addClass(this._container, 'leaflet-zoom-animated');
12027 this.getPane().appendChild(this._container);
12029 this.on('update', this._updatePaths, this);
12032 onRemove: function () {
12033 this.off('update', this._updatePaths, this);
12034 this._destroyContainer();
12037 getEvents: function () {
12039 viewreset: this._reset,
12040 zoom: this._onZoom,
12041 moveend: this._update,
12042 zoomend: this._onZoomEnd
12044 if (this._zoomAnimated) {
12045 events.zoomanim = this._onAnimZoom;
12050 _onAnimZoom: function (ev) {
12051 this._updateTransform(ev.center, ev.zoom);
12054 _onZoom: function () {
12055 this._updateTransform(this._map.getCenter(), this._map.getZoom());
12058 _updateTransform: function (center, zoom) {
12059 var scale = this._map.getZoomScale(zoom, this._zoom),
12060 position = getPosition(this._container),
12061 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
12062 currentCenterPoint = this._map.project(this._center, zoom),
12063 destCenterPoint = this._map.project(center, zoom),
12064 centerOffset = destCenterPoint.subtract(currentCenterPoint),
12066 topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
12069 setTransform(this._container, topLeftOffset, scale);
12071 setPosition(this._container, topLeftOffset);
12075 _reset: function () {
12077 this._updateTransform(this._center, this._zoom);
12079 for (var id in this._layers) {
12080 this._layers[id]._reset();
12084 _onZoomEnd: function () {
12085 for (var id in this._layers) {
12086 this._layers[id]._project();
12090 _updatePaths: function () {
12091 for (var id in this._layers) {
12092 this._layers[id]._update();
12096 _update: function () {
12097 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
12098 // Subclasses are responsible of firing the 'update' event.
12099 var p = this.options.padding,
12100 size = this._map.getSize(),
12101 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
12103 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
12105 this._center = this._map.getCenter();
12106 this._zoom = this._map.getZoom();
12112 * @inherits Renderer
12115 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
12116 * Inherits `Renderer`.
12118 * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
12119 * available in all web browsers, notably IE8, and overlapping geometries might
12120 * not display properly in some edge cases.
12124 * Use Canvas by default for all paths in the map:
12127 * var map = L.map('map', {
12128 * renderer: L.canvas()
12132 * Use a Canvas renderer with extra padding for specific vector geometries:
12135 * var map = L.map('map');
12136 * var myRenderer = L.canvas({ padding: 0.5 });
12137 * var line = L.polyline( coordinates, { renderer: myRenderer } );
12138 * var circle = L.circle( center, { renderer: myRenderer } );
12142 var Canvas = Renderer.extend({
12143 getEvents: function () {
12144 var events = Renderer.prototype.getEvents.call(this);
12145 events.viewprereset = this._onViewPreReset;
12149 _onViewPreReset: function () {
12150 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
12151 this._postponeUpdatePaths = true;
12154 onAdd: function () {
12155 Renderer.prototype.onAdd.call(this);
12157 // Redraw vectors since canvas is cleared upon removal,
12158 // in case of removing the renderer itself from the map.
12162 _initContainer: function () {
12163 var container = this._container = document.createElement('canvas');
12165 on(container, 'mousemove', this._onMouseMove, this);
12166 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
12167 on(container, 'mouseout', this._handleMouseOut, this);
12169 this._ctx = container.getContext('2d');
12172 _destroyContainer: function () {
12173 cancelAnimFrame(this._redrawRequest);
12175 remove(this._container);
12176 off(this._container);
12177 delete this._container;
12180 _updatePaths: function () {
12181 if (this._postponeUpdatePaths) { return; }
12184 this._redrawBounds = null;
12185 for (var id in this._layers) {
12186 layer = this._layers[id];
12192 _update: function () {
12193 if (this._map._animatingZoom && this._bounds) { return; }
12195 Renderer.prototype._update.call(this);
12197 var b = this._bounds,
12198 container = this._container,
12199 size = b.getSize(),
12200 m = retina ? 2 : 1;
12202 setPosition(container, b.min);
12204 // set canvas size (also clearing it); use double size on retina
12205 container.width = m * size.x;
12206 container.height = m * size.y;
12207 container.style.width = size.x + 'px';
12208 container.style.height = size.y + 'px';
12211 this._ctx.scale(2, 2);
12214 // translate so we use the same path coordinates after canvas element moves
12215 this._ctx.translate(-b.min.x, -b.min.y);
12217 // Tell paths to redraw themselves
12218 this.fire('update');
12221 _reset: function () {
12222 Renderer.prototype._reset.call(this);
12224 if (this._postponeUpdatePaths) {
12225 this._postponeUpdatePaths = false;
12226 this._updatePaths();
12230 _initPath: function (layer) {
12231 this._updateDashArray(layer);
12232 this._layers[stamp(layer)] = layer;
12234 var order = layer._order = {
12236 prev: this._drawLast,
12239 if (this._drawLast) { this._drawLast.next = order; }
12240 this._drawLast = order;
12241 this._drawFirst = this._drawFirst || this._drawLast;
12244 _addPath: function (layer) {
12245 this._requestRedraw(layer);
12248 _removePath: function (layer) {
12249 var order = layer._order;
12250 var next = order.next;
12251 var prev = order.prev;
12256 this._drawLast = prev;
12261 this._drawFirst = next;
12264 delete layer._order;
12266 delete this._layers[stamp(layer)];
12268 this._requestRedraw(layer);
12271 _updatePath: function (layer) {
12272 // Redraw the union of the layer's old pixel
12273 // bounds and the new pixel bounds.
12274 this._extendRedrawBounds(layer);
12277 // The redraw will extend the redraw bounds
12278 // with the new pixel bounds.
12279 this._requestRedraw(layer);
12282 _updateStyle: function (layer) {
12283 this._updateDashArray(layer);
12284 this._requestRedraw(layer);
12287 _updateDashArray: function (layer) {
12288 if (typeof layer.options.dashArray === 'string') {
12289 var parts = layer.options.dashArray.split(/[, ]+/),
12293 for (i = 0; i < parts.length; i++) {
12294 dashValue = Number(parts[i]);
12295 // Ignore dash array containing invalid lengths
12296 if (isNaN(dashValue)) { return; }
12297 dashArray.push(dashValue);
12299 layer.options._dashArray = dashArray;
12301 layer.options._dashArray = layer.options.dashArray;
12305 _requestRedraw: function (layer) {
12306 if (!this._map) { return; }
12308 this._extendRedrawBounds(layer);
12309 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
12312 _extendRedrawBounds: function (layer) {
12313 if (layer._pxBounds) {
12314 var padding = (layer.options.weight || 0) + 1;
12315 this._redrawBounds = this._redrawBounds || new Bounds();
12316 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
12317 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
12321 _redraw: function () {
12322 this._redrawRequest = null;
12324 if (this._redrawBounds) {
12325 this._redrawBounds.min._floor();
12326 this._redrawBounds.max._ceil();
12329 this._clear(); // clear layers in redraw bounds
12330 this._draw(); // draw layers
12332 this._redrawBounds = null;
12335 _clear: function () {
12336 var bounds = this._redrawBounds;
12338 var size = bounds.getSize();
12339 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
12341 this._ctx.clearRect(0, 0, this._container.width, this._container.height);
12345 _draw: function () {
12346 var layer, bounds = this._redrawBounds;
12349 var size = bounds.getSize();
12350 this._ctx.beginPath();
12351 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
12355 this._drawing = true;
12357 for (var order = this._drawFirst; order; order = order.next) {
12358 layer = order.layer;
12359 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
12360 layer._updatePath();
12364 this._drawing = false;
12366 this._ctx.restore(); // Restore state before clipping.
12369 _updatePoly: function (layer, closed) {
12370 if (!this._drawing) { return; }
12373 parts = layer._parts,
12374 len = parts.length,
12377 if (!len) { return; }
12381 for (i = 0; i < len; i++) {
12382 for (j = 0, len2 = parts[i].length; j < len2; j++) {
12384 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
12391 this._fillStroke(ctx, layer);
12393 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
12396 _updateCircle: function (layer) {
12398 if (!this._drawing || layer._empty()) { return; }
12400 var p = layer._point,
12402 r = Math.max(Math.round(layer._radius), 1),
12403 s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
12411 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
12417 this._fillStroke(ctx, layer);
12420 _fillStroke: function (ctx, layer) {
12421 var options = layer.options;
12423 if (options.fill) {
12424 ctx.globalAlpha = options.fillOpacity;
12425 ctx.fillStyle = options.fillColor || options.color;
12426 ctx.fill(options.fillRule || 'evenodd');
12429 if (options.stroke && options.weight !== 0) {
12430 if (ctx.setLineDash) {
12431 ctx.setLineDash(layer.options && layer.options._dashArray || []);
12433 ctx.globalAlpha = options.opacity;
12434 ctx.lineWidth = options.weight;
12435 ctx.strokeStyle = options.color;
12436 ctx.lineCap = options.lineCap;
12437 ctx.lineJoin = options.lineJoin;
12442 // Canvas obviously doesn't have mouse events for individual drawn objects,
12443 // so we emulate that by calculating what's under the mouse on mousemove/click manually
12445 _onClick: function (e) {
12446 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
12448 for (var order = this._drawFirst; order; order = order.next) {
12449 layer = order.layer;
12450 if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
12451 clickedLayer = layer;
12454 if (clickedLayer) {
12456 this._fireEvent([clickedLayer], e);
12460 _onMouseMove: function (e) {
12461 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
12463 var point = this._map.mouseEventToLayerPoint(e);
12464 this._handleMouseHover(e, point);
12468 _handleMouseOut: function (e) {
12469 var layer = this._hoveredLayer;
12471 // if we're leaving the layer, fire mouseout
12472 removeClass(this._container, 'leaflet-interactive');
12473 this._fireEvent([layer], e, 'mouseout');
12474 this._hoveredLayer = null;
12475 this._mouseHoverThrottled = false;
12479 _handleMouseHover: function (e, point) {
12480 if (this._mouseHoverThrottled) {
12484 var layer, candidateHoveredLayer;
12486 for (var order = this._drawFirst; order; order = order.next) {
12487 layer = order.layer;
12488 if (layer.options.interactive && layer._containsPoint(point)) {
12489 candidateHoveredLayer = layer;
12493 if (candidateHoveredLayer !== this._hoveredLayer) {
12494 this._handleMouseOut(e);
12496 if (candidateHoveredLayer) {
12497 addClass(this._container, 'leaflet-interactive'); // change cursor
12498 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
12499 this._hoveredLayer = candidateHoveredLayer;
12503 if (this._hoveredLayer) {
12504 this._fireEvent([this._hoveredLayer], e);
12507 this._mouseHoverThrottled = true;
12508 setTimeout(L.bind(function () {
12509 this._mouseHoverThrottled = false;
12513 _fireEvent: function (layers, e, type) {
12514 this._map._fireDOMEvent(e, type || e.type, layers);
12517 _bringToFront: function (layer) {
12518 var order = layer._order;
12520 if (!order) { return; }
12522 var next = order.next;
12523 var prev = order.prev;
12534 // Update first entry unless this is the
12536 this._drawFirst = next;
12539 order.prev = this._drawLast;
12540 this._drawLast.next = order;
12543 this._drawLast = order;
12545 this._requestRedraw(layer);
12548 _bringToBack: function (layer) {
12549 var order = layer._order;
12551 if (!order) { return; }
12553 var next = order.next;
12554 var prev = order.prev;
12565 // Update last entry unless this is the
12567 this._drawLast = prev;
12572 order.next = this._drawFirst;
12573 this._drawFirst.prev = order;
12574 this._drawFirst = order;
12576 this._requestRedraw(layer);
12580 // @factory L.canvas(options?: Renderer options)
12581 // Creates a Canvas renderer with the given options.
12582 function canvas$1(options) {
12583 return canvas ? new Canvas(options) : null;
12587 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
12591 var vmlCreate = (function () {
12593 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
12594 return function (name) {
12595 return document.createElement('<lvml:' + name + ' class="lvml">');
12598 return function (name) {
12599 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
12609 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
12610 * with old versions of Internet Explorer.
12613 // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
12616 _initContainer: function () {
12617 this._container = create$1('div', 'leaflet-vml-container');
12620 _update: function () {
12621 if (this._map._animatingZoom) { return; }
12622 Renderer.prototype._update.call(this);
12623 this.fire('update');
12626 _initPath: function (layer) {
12627 var container = layer._container = vmlCreate('shape');
12629 addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
12631 container.coordsize = '1 1';
12633 layer._path = vmlCreate('path');
12634 container.appendChild(layer._path);
12636 this._updateStyle(layer);
12637 this._layers[stamp(layer)] = layer;
12640 _addPath: function (layer) {
12641 var container = layer._container;
12642 this._container.appendChild(container);
12644 if (layer.options.interactive) {
12645 layer.addInteractiveTarget(container);
12649 _removePath: function (layer) {
12650 var container = layer._container;
12652 layer.removeInteractiveTarget(container);
12653 delete this._layers[stamp(layer)];
12656 _updateStyle: function (layer) {
12657 var stroke = layer._stroke,
12658 fill = layer._fill,
12659 options = layer.options,
12660 container = layer._container;
12662 container.stroked = !!options.stroke;
12663 container.filled = !!options.fill;
12665 if (options.stroke) {
12667 stroke = layer._stroke = vmlCreate('stroke');
12669 container.appendChild(stroke);
12670 stroke.weight = options.weight + 'px';
12671 stroke.color = options.color;
12672 stroke.opacity = options.opacity;
12674 if (options.dashArray) {
12675 stroke.dashStyle = isArray(options.dashArray) ?
12676 options.dashArray.join(' ') :
12677 options.dashArray.replace(/( *, *)/g, ' ');
12679 stroke.dashStyle = '';
12681 stroke.endcap = options.lineCap.replace('butt', 'flat');
12682 stroke.joinstyle = options.lineJoin;
12684 } else if (stroke) {
12685 container.removeChild(stroke);
12686 layer._stroke = null;
12689 if (options.fill) {
12691 fill = layer._fill = vmlCreate('fill');
12693 container.appendChild(fill);
12694 fill.color = options.fillColor || options.color;
12695 fill.opacity = options.fillOpacity;
12698 container.removeChild(fill);
12699 layer._fill = null;
12703 _updateCircle: function (layer) {
12704 var p = layer._point.round(),
12705 r = Math.round(layer._radius),
12706 r2 = Math.round(layer._radiusY || r);
12708 this._setPath(layer, layer._empty() ? 'M0 0' :
12709 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
12712 _setPath: function (layer, path) {
12713 layer._path.v = path;
12716 _bringToFront: function (layer) {
12717 toFront(layer._container);
12720 _bringToBack: function (layer) {
12721 toBack(layer._container);
12725 var create$2 = vml ? vmlCreate : svgCreate;
12729 * @inherits Renderer
12732 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
12733 * Inherits `Renderer`.
12735 * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
12736 * available in all web browsers, notably Android 2.x and 3.x.
12738 * Although SVG is not available on IE7 and IE8, these browsers support
12739 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
12740 * (a now deprecated technology), and the SVG renderer will fall back to VML in
12745 * Use SVG by default for all paths in the map:
12748 * var map = L.map('map', {
12749 * renderer: L.svg()
12753 * Use a SVG renderer with extra padding for specific vector geometries:
12756 * var map = L.map('map');
12757 * var myRenderer = L.svg({ padding: 0.5 });
12758 * var line = L.polyline( coordinates, { renderer: myRenderer } );
12759 * var circle = L.circle( center, { renderer: myRenderer } );
12763 var SVG = Renderer.extend({
12765 getEvents: function () {
12766 var events = Renderer.prototype.getEvents.call(this);
12767 events.zoomstart = this._onZoomStart;
12771 _initContainer: function () {
12772 this._container = create$2('svg');
12774 // makes it possible to click through svg root; we'll reset it back in individual paths
12775 this._container.setAttribute('pointer-events', 'none');
12777 this._rootGroup = create$2('g');
12778 this._container.appendChild(this._rootGroup);
12781 _destroyContainer: function () {
12782 remove(this._container);
12783 off(this._container);
12784 delete this._container;
12785 delete this._rootGroup;
12786 delete this._svgSize;
12789 _onZoomStart: function () {
12790 // Drag-then-pinch interactions might mess up the center and zoom.
12791 // In this case, the easiest way to prevent this is re-do the renderer
12792 // bounds and padding when the zooming starts.
12796 _update: function () {
12797 if (this._map._animatingZoom && this._bounds) { return; }
12799 Renderer.prototype._update.call(this);
12801 var b = this._bounds,
12802 size = b.getSize(),
12803 container = this._container;
12805 // set size of svg-container if changed
12806 if (!this._svgSize || !this._svgSize.equals(size)) {
12807 this._svgSize = size;
12808 container.setAttribute('width', size.x);
12809 container.setAttribute('height', size.y);
12812 // movement: update container viewBox so that we don't have to change coordinates of individual layers
12813 setPosition(container, b.min);
12814 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
12816 this.fire('update');
12819 // methods below are called by vector layers implementations
12821 _initPath: function (layer) {
12822 var path = layer._path = create$2('path');
12825 // @option className: String = null
12826 // Custom class name set on an element. Only for SVG renderer.
12827 if (layer.options.className) {
12828 addClass(path, layer.options.className);
12831 if (layer.options.interactive) {
12832 addClass(path, 'leaflet-interactive');
12835 this._updateStyle(layer);
12836 this._layers[stamp(layer)] = layer;
12839 _addPath: function (layer) {
12840 if (!this._rootGroup) { this._initContainer(); }
12841 this._rootGroup.appendChild(layer._path);
12842 layer.addInteractiveTarget(layer._path);
12845 _removePath: function (layer) {
12846 remove(layer._path);
12847 layer.removeInteractiveTarget(layer._path);
12848 delete this._layers[stamp(layer)];
12851 _updatePath: function (layer) {
12856 _updateStyle: function (layer) {
12857 var path = layer._path,
12858 options = layer.options;
12860 if (!path) { return; }
12862 if (options.stroke) {
12863 path.setAttribute('stroke', options.color);
12864 path.setAttribute('stroke-opacity', options.opacity);
12865 path.setAttribute('stroke-width', options.weight);
12866 path.setAttribute('stroke-linecap', options.lineCap);
12867 path.setAttribute('stroke-linejoin', options.lineJoin);
12869 if (options.dashArray) {
12870 path.setAttribute('stroke-dasharray', options.dashArray);
12872 path.removeAttribute('stroke-dasharray');
12875 if (options.dashOffset) {
12876 path.setAttribute('stroke-dashoffset', options.dashOffset);
12878 path.removeAttribute('stroke-dashoffset');
12881 path.setAttribute('stroke', 'none');
12884 if (options.fill) {
12885 path.setAttribute('fill', options.fillColor || options.color);
12886 path.setAttribute('fill-opacity', options.fillOpacity);
12887 path.setAttribute('fill-rule', options.fillRule || 'evenodd');
12889 path.setAttribute('fill', 'none');
12893 _updatePoly: function (layer, closed) {
12894 this._setPath(layer, pointsToPath(layer._parts, closed));
12897 _updateCircle: function (layer) {
12898 var p = layer._point,
12899 r = Math.max(Math.round(layer._radius), 1),
12900 r2 = Math.max(Math.round(layer._radiusY), 1) || r,
12901 arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
12903 // drawing a circle with two half-arcs
12904 var d = layer._empty() ? 'M0 0' :
12905 'M' + (p.x - r) + ',' + p.y +
12906 arc + (r * 2) + ',0 ' +
12907 arc + (-r * 2) + ',0 ';
12909 this._setPath(layer, d);
12912 _setPath: function (layer, path) {
12913 layer._path.setAttribute('d', path);
12916 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
12917 _bringToFront: function (layer) {
12918 toFront(layer._path);
12921 _bringToBack: function (layer) {
12922 toBack(layer._path);
12927 SVG.include(vmlMixin);
12931 // @factory L.svg(options?: Renderer options)
12932 // Creates a SVG renderer with the given options.
12933 function svg$1(options) {
12934 return svg || vml ? new SVG(options) : null;
12938 // @namespace Map; @method getRenderer(layer: Path): Renderer
12939 // Returns the instance of `Renderer` that should be used to render the given
12940 // `Path`. It will ensure that the `renderer` options of the map and paths
12941 // are respected, and that the renderers do exist on the map.
12942 getRenderer: function (layer) {
12943 // @namespace Path; @option renderer: Renderer
12944 // Use this specific instance of `Renderer` for this path. Takes
12945 // precedence over the map's [default renderer](#map-renderer).
12946 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
12949 renderer = this._renderer = this._createRenderer();
12952 if (!this.hasLayer(renderer)) {
12953 this.addLayer(renderer);
12958 _getPaneRenderer: function (name) {
12959 if (name === 'overlayPane' || name === undefined) {
12963 var renderer = this._paneRenderers[name];
12964 if (renderer === undefined) {
12965 renderer = this._createRenderer({pane: name});
12966 this._paneRenderers[name] = renderer;
12971 _createRenderer: function (options) {
12972 // @namespace Map; @option preferCanvas: Boolean = false
12973 // Whether `Path`s should be rendered on a `Canvas` renderer.
12974 // By default, all `Path`s are rendered in a `SVG` renderer.
12975 return (this.options.preferCanvas && canvas$1(options)) || svg$1(options);
12980 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
12986 * @inherits Polygon
12988 * A class for drawing rectangle overlays on a map. Extends `Polygon`.
12993 * // define rectangle geographical bounds
12994 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
12996 * // create an orange rectangle
12997 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
12999 * // zoom the map to the rectangle bounds
13000 * map.fitBounds(bounds);
13006 var Rectangle = Polygon.extend({
13007 initialize: function (latLngBounds, options) {
13008 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
13011 // @method setBounds(latLngBounds: LatLngBounds): this
13012 // Redraws the rectangle with the passed bounds.
13013 setBounds: function (latLngBounds) {
13014 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
13017 _boundsToLatLngs: function (latLngBounds) {
13018 latLngBounds = toLatLngBounds(latLngBounds);
13020 latLngBounds.getSouthWest(),
13021 latLngBounds.getNorthWest(),
13022 latLngBounds.getNorthEast(),
13023 latLngBounds.getSouthEast()
13029 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
13030 function rectangle(latLngBounds, options) {
13031 return new Rectangle(latLngBounds, options);
13034 SVG.create = create$2;
13035 SVG.pointsToPath = pointsToPath;
13037 GeoJSON.geometryToLayer = geometryToLayer;
13038 GeoJSON.coordsToLatLng = coordsToLatLng;
13039 GeoJSON.coordsToLatLngs = coordsToLatLngs;
13040 GeoJSON.latLngToCoords = latLngToCoords;
13041 GeoJSON.latLngsToCoords = latLngsToCoords;
13042 GeoJSON.getFeature = getFeature;
13043 GeoJSON.asFeature = asFeature;
13046 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
13047 * (zoom to a selected bounding box), enabled by default.
13051 // @section Interaction Options
13053 // @option boxZoom: Boolean = true
13054 // Whether the map can be zoomed to a rectangular area specified by
13055 // dragging the mouse while pressing the shift key.
13059 var BoxZoom = Handler.extend({
13060 initialize: function (map) {
13062 this._container = map._container;
13063 this._pane = map._panes.overlayPane;
13064 this._resetStateTimeout = 0;
13065 map.on('unload', this._destroy, this);
13068 addHooks: function () {
13069 on(this._container, 'mousedown', this._onMouseDown, this);
13072 removeHooks: function () {
13073 off(this._container, 'mousedown', this._onMouseDown, this);
13076 moved: function () {
13077 return this._moved;
13080 _destroy: function () {
13081 remove(this._pane);
13085 _resetState: function () {
13086 this._resetStateTimeout = 0;
13087 this._moved = false;
13090 _clearDeferredResetState: function () {
13091 if (this._resetStateTimeout !== 0) {
13092 clearTimeout(this._resetStateTimeout);
13093 this._resetStateTimeout = 0;
13097 _onMouseDown: function (e) {
13098 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
13100 // Clear the deferred resetState if it hasn't executed yet, otherwise it
13101 // will interrupt the interaction and orphan a box element in the container.
13102 this._clearDeferredResetState();
13103 this._resetState();
13105 disableTextSelection();
13106 disableImageDrag();
13108 this._startPoint = this._map.mouseEventToContainerPoint(e);
13112 mousemove: this._onMouseMove,
13113 mouseup: this._onMouseUp,
13114 keydown: this._onKeyDown
13118 _onMouseMove: function (e) {
13119 if (!this._moved) {
13120 this._moved = true;
13122 this._box = create$1('div', 'leaflet-zoom-box', this._container);
13123 addClass(this._container, 'leaflet-crosshair');
13125 this._map.fire('boxzoomstart');
13128 this._point = this._map.mouseEventToContainerPoint(e);
13130 var bounds = new Bounds(this._point, this._startPoint),
13131 size = bounds.getSize();
13133 setPosition(this._box, bounds.min);
13135 this._box.style.width = size.x + 'px';
13136 this._box.style.height = size.y + 'px';
13139 _finish: function () {
13142 removeClass(this._container, 'leaflet-crosshair');
13145 enableTextSelection();
13150 mousemove: this._onMouseMove,
13151 mouseup: this._onMouseUp,
13152 keydown: this._onKeyDown
13156 _onMouseUp: function (e) {
13157 if ((e.which !== 1) && (e.button !== 1)) { return; }
13161 if (!this._moved) { return; }
13162 // Postpone to next JS tick so internal click event handling
13163 // still see it as "moved".
13164 this._clearDeferredResetState();
13165 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
13167 var bounds = new LatLngBounds(
13168 this._map.containerPointToLatLng(this._startPoint),
13169 this._map.containerPointToLatLng(this._point));
13173 .fire('boxzoomend', {boxZoomBounds: bounds});
13176 _onKeyDown: function (e) {
13177 if (e.keyCode === 27) {
13183 // @section Handlers
13184 // @property boxZoom: Handler
13185 // Box (shift-drag with mouse) zoom handler.
13186 Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
13189 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
13193 // @section Interaction Options
13196 // @option doubleClickZoom: Boolean|String = true
13197 // Whether the map can be zoomed in by double clicking on it and
13198 // zoomed out by double clicking while holding shift. If passed
13199 // `'center'`, double-click zoom will zoom to the center of the
13200 // view regardless of where the mouse was.
13201 doubleClickZoom: true
13204 var DoubleClickZoom = Handler.extend({
13205 addHooks: function () {
13206 this._map.on('dblclick', this._onDoubleClick, this);
13209 removeHooks: function () {
13210 this._map.off('dblclick', this._onDoubleClick, this);
13213 _onDoubleClick: function (e) {
13214 var map = this._map,
13215 oldZoom = map.getZoom(),
13216 delta = map.options.zoomDelta,
13217 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
13219 if (map.options.doubleClickZoom === 'center') {
13222 map.setZoomAround(e.containerPoint, zoom);
13227 // @section Handlers
13229 // Map properties include interaction handlers that allow you to control
13230 // interaction behavior in runtime, enabling or disabling certain features such
13231 // as dragging or touch zoom (see `Handler` methods). For example:
13234 // map.doubleClickZoom.disable();
13237 // @property doubleClickZoom: Handler
13238 // Double click zoom handler.
13239 Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
13242 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
13246 // @section Interaction Options
13248 // @option dragging: Boolean = true
13249 // Whether the map be draggable with mouse/touch or not.
13252 // @section Panning Inertia Options
13253 // @option inertia: Boolean = *
13254 // If enabled, panning of the map will have an inertia effect where
13255 // the map builds momentum while dragging and continues moving in
13256 // the same direction for some time. Feels especially nice on touch
13257 // devices. Enabled by default unless running on old Android devices.
13258 inertia: !android23,
13260 // @option inertiaDeceleration: Number = 3000
13261 // The rate with which the inertial movement slows down, in pixels/second².
13262 inertiaDeceleration: 3400, // px/s^2
13264 // @option inertiaMaxSpeed: Number = Infinity
13265 // Max speed of the inertial movement, in pixels/second.
13266 inertiaMaxSpeed: Infinity, // px/s
13268 // @option easeLinearity: Number = 0.2
13269 easeLinearity: 0.2,
13271 // TODO refactor, move to CRS
13272 // @option worldCopyJump: Boolean = false
13273 // With this option enabled, the map tracks when you pan to another "copy"
13274 // of the world and seamlessly jumps to the original one so that all overlays
13275 // like markers and vector layers are still visible.
13276 worldCopyJump: false,
13278 // @option maxBoundsViscosity: Number = 0.0
13279 // If `maxBounds` is set, this option will control how solid the bounds
13280 // are when dragging the map around. The default value of `0.0` allows the
13281 // user to drag outside the bounds at normal speed, higher values will
13282 // slow down map dragging outside bounds, and `1.0` makes the bounds fully
13283 // solid, preventing the user from dragging outside the bounds.
13284 maxBoundsViscosity: 0.0
13287 var Drag = Handler.extend({
13288 addHooks: function () {
13289 if (!this._draggable) {
13290 var map = this._map;
13292 this._draggable = new Draggable(map._mapPane, map._container);
13294 this._draggable.on({
13295 dragstart: this._onDragStart,
13296 drag: this._onDrag,
13297 dragend: this._onDragEnd
13300 this._draggable.on('predrag', this._onPreDragLimit, this);
13301 if (map.options.worldCopyJump) {
13302 this._draggable.on('predrag', this._onPreDragWrap, this);
13303 map.on('zoomend', this._onZoomEnd, this);
13305 map.whenReady(this._onZoomEnd, this);
13308 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
13309 this._draggable.enable();
13310 this._positions = [];
13314 removeHooks: function () {
13315 removeClass(this._map._container, 'leaflet-grab');
13316 removeClass(this._map._container, 'leaflet-touch-drag');
13317 this._draggable.disable();
13320 moved: function () {
13321 return this._draggable && this._draggable._moved;
13324 moving: function () {
13325 return this._draggable && this._draggable._moving;
13328 _onDragStart: function () {
13329 var map = this._map;
13332 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
13333 var bounds = toLatLngBounds(this._map.options.maxBounds);
13335 this._offsetLimit = toBounds(
13336 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
13337 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
13338 .add(this._map.getSize()));
13340 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
13342 this._offsetLimit = null;
13347 .fire('dragstart');
13349 if (map.options.inertia) {
13350 this._positions = [];
13355 _onDrag: function (e) {
13356 if (this._map.options.inertia) {
13357 var time = this._lastTime = +new Date(),
13358 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
13360 this._positions.push(pos);
13361 this._times.push(time);
13363 this._prunePositions(time);
13371 _prunePositions: function (time) {
13372 while (this._positions.length > 1 && time - this._times[0] > 50) {
13373 this._positions.shift();
13374 this._times.shift();
13378 _onZoomEnd: function () {
13379 var pxCenter = this._map.getSize().divideBy(2),
13380 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
13382 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
13383 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
13386 _viscousLimit: function (value, threshold) {
13387 return value - (value - threshold) * this._viscosity;
13390 _onPreDragLimit: function () {
13391 if (!this._viscosity || !this._offsetLimit) { return; }
13393 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
13395 var limit = this._offsetLimit;
13396 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
13397 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
13398 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
13399 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
13401 this._draggable._newPos = this._draggable._startPos.add(offset);
13404 _onPreDragWrap: function () {
13405 // TODO refactor to be able to adjust map pane position after zoom
13406 var worldWidth = this._worldWidth,
13407 halfWidth = Math.round(worldWidth / 2),
13408 dx = this._initialWorldOffset,
13409 x = this._draggable._newPos.x,
13410 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
13411 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
13412 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
13414 this._draggable._absPos = this._draggable._newPos.clone();
13415 this._draggable._newPos.x = newX;
13418 _onDragEnd: function (e) {
13419 var map = this._map,
13420 options = map.options,
13422 noInertia = !options.inertia || this._times.length < 2;
13424 map.fire('dragend', e);
13427 map.fire('moveend');
13430 this._prunePositions(+new Date());
13432 var direction = this._lastPos.subtract(this._positions[0]),
13433 duration = (this._lastTime - this._times[0]) / 1000,
13434 ease = options.easeLinearity,
13436 speedVector = direction.multiplyBy(ease / duration),
13437 speed = speedVector.distanceTo([0, 0]),
13439 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
13440 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
13442 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
13443 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
13445 if (!offset.x && !offset.y) {
13446 map.fire('moveend');
13449 offset = map._limitOffset(offset, map.options.maxBounds);
13451 requestAnimFrame(function () {
13452 map.panBy(offset, {
13453 duration: decelerationDuration,
13454 easeLinearity: ease,
13464 // @section Handlers
13465 // @property dragging: Handler
13466 // Map dragging handler (by both mouse and touch).
13467 Map.addInitHook('addHandler', 'dragging', Drag);
13470 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
13474 // @section Keyboard Navigation Options
13476 // @option keyboard: Boolean = true
13477 // Makes the map focusable and allows users to navigate the map with keyboard
13478 // arrows and `+`/`-` keys.
13481 // @option keyboardPanDelta: Number = 80
13482 // Amount of pixels to pan when pressing an arrow key.
13483 keyboardPanDelta: 80
13486 var Keyboard = Handler.extend({
13493 zoomIn: [187, 107, 61, 171],
13494 zoomOut: [189, 109, 54, 173]
13497 initialize: function (map) {
13500 this._setPanDelta(map.options.keyboardPanDelta);
13501 this._setZoomDelta(map.options.zoomDelta);
13504 addHooks: function () {
13505 var container = this._map._container;
13507 // make the container focusable by tabbing
13508 if (container.tabIndex <= 0) {
13509 container.tabIndex = '0';
13513 focus: this._onFocus,
13514 blur: this._onBlur,
13515 mousedown: this._onMouseDown
13519 focus: this._addHooks,
13520 blur: this._removeHooks
13524 removeHooks: function () {
13525 this._removeHooks();
13527 off(this._map._container, {
13528 focus: this._onFocus,
13529 blur: this._onBlur,
13530 mousedown: this._onMouseDown
13534 focus: this._addHooks,
13535 blur: this._removeHooks
13539 _onMouseDown: function () {
13540 if (this._focused) { return; }
13542 var body = document.body,
13543 docEl = document.documentElement,
13544 top = body.scrollTop || docEl.scrollTop,
13545 left = body.scrollLeft || docEl.scrollLeft;
13547 this._map._container.focus();
13549 window.scrollTo(left, top);
13552 _onFocus: function () {
13553 this._focused = true;
13554 this._map.fire('focus');
13557 _onBlur: function () {
13558 this._focused = false;
13559 this._map.fire('blur');
13562 _setPanDelta: function (panDelta) {
13563 var keys = this._panKeys = {},
13564 codes = this.keyCodes,
13567 for (i = 0, len = codes.left.length; i < len; i++) {
13568 keys[codes.left[i]] = [-1 * panDelta, 0];
13570 for (i = 0, len = codes.right.length; i < len; i++) {
13571 keys[codes.right[i]] = [panDelta, 0];
13573 for (i = 0, len = codes.down.length; i < len; i++) {
13574 keys[codes.down[i]] = [0, panDelta];
13576 for (i = 0, len = codes.up.length; i < len; i++) {
13577 keys[codes.up[i]] = [0, -1 * panDelta];
13581 _setZoomDelta: function (zoomDelta) {
13582 var keys = this._zoomKeys = {},
13583 codes = this.keyCodes,
13586 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
13587 keys[codes.zoomIn[i]] = zoomDelta;
13589 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
13590 keys[codes.zoomOut[i]] = -zoomDelta;
13594 _addHooks: function () {
13595 on(document, 'keydown', this._onKeyDown, this);
13598 _removeHooks: function () {
13599 off(document, 'keydown', this._onKeyDown, this);
13602 _onKeyDown: function (e) {
13603 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
13605 var key = e.keyCode,
13609 if (key in this._panKeys) {
13610 if (!map._panAnim || !map._panAnim._inProgress) {
13611 offset = this._panKeys[key];
13613 offset = toPoint(offset).multiplyBy(3);
13618 if (map.options.maxBounds) {
13619 map.panInsideBounds(map.options.maxBounds);
13622 } else if (key in this._zoomKeys) {
13623 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
13625 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
13636 // @section Handlers
13637 // @section Handlers
13638 // @property keyboard: Handler
13639 // Keyboard navigation handler.
13640 Map.addInitHook('addHandler', 'keyboard', Keyboard);
13643 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
13647 // @section Interaction Options
13649 // @section Mousewheel options
13650 // @option scrollWheelZoom: Boolean|String = true
13651 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
13652 // it will zoom to the center of the view regardless of where the mouse was.
13653 scrollWheelZoom: true,
13655 // @option wheelDebounceTime: Number = 40
13656 // Limits the rate at which a wheel can fire (in milliseconds). By default
13657 // user can't zoom via wheel more often than once per 40 ms.
13658 wheelDebounceTime: 40,
13660 // @option wheelPxPerZoomLevel: Number = 60
13661 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
13662 // mean a change of one full zoom level. Smaller values will make wheel-zooming
13663 // faster (and vice versa).
13664 wheelPxPerZoomLevel: 60
13667 var ScrollWheelZoom = Handler.extend({
13668 addHooks: function () {
13669 on(this._map._container, 'mousewheel', this._onWheelScroll, this);
13674 removeHooks: function () {
13675 off(this._map._container, 'mousewheel', this._onWheelScroll, this);
13678 _onWheelScroll: function (e) {
13679 var delta = getWheelDelta(e);
13681 var debounce = this._map.options.wheelDebounceTime;
13683 this._delta += delta;
13684 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
13686 if (!this._startTime) {
13687 this._startTime = +new Date();
13690 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
13692 clearTimeout(this._timer);
13693 this._timer = setTimeout(bind(this._performZoom, this), left);
13698 _performZoom: function () {
13699 var map = this._map,
13700 zoom = map.getZoom(),
13701 snap = this._map.options.zoomSnap || 0;
13703 map._stop(); // stop panning and fly animations if any
13705 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
13706 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
13707 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
13708 d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
13709 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
13712 this._startTime = null;
13714 if (!delta) { return; }
13716 if (map.options.scrollWheelZoom === 'center') {
13717 map.setZoom(zoom + delta);
13719 map.setZoomAround(this._lastMousePos, zoom + delta);
13724 // @section Handlers
13725 // @property scrollWheelZoom: Handler
13726 // Scroll wheel zoom handler.
13727 Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
13730 * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
13734 // @section Interaction Options
13736 // @section Touch interaction options
13737 // @option tap: Boolean = true
13738 // Enables mobile hacks for supporting instant taps (fixing 200ms click
13739 // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
13742 // @option tapTolerance: Number = 15
13743 // The max number of pixels a user can shift his finger during touch
13744 // for it to be considered a valid tap.
13748 var Tap = Handler.extend({
13749 addHooks: function () {
13750 on(this._map._container, 'touchstart', this._onDown, this);
13753 removeHooks: function () {
13754 off(this._map._container, 'touchstart', this._onDown, this);
13757 _onDown: function (e) {
13758 if (!e.touches) { return; }
13762 this._fireClick = true;
13764 // don't simulate click or track longpress if more than 1 touch
13765 if (e.touches.length > 1) {
13766 this._fireClick = false;
13767 clearTimeout(this._holdTimeout);
13771 var first = e.touches[0],
13774 this._startPos = this._newPos = new Point(first.clientX, first.clientY);
13776 // if touching a link, highlight it
13777 if (el.tagName && el.tagName.toLowerCase() === 'a') {
13778 addClass(el, 'leaflet-active');
13781 // simulate long hold but setting a timeout
13782 this._holdTimeout = setTimeout(bind(function () {
13783 if (this._isTapValid()) {
13784 this._fireClick = false;
13786 this._simulateEvent('contextmenu', first);
13790 this._simulateEvent('mousedown', first);
13793 touchmove: this._onMove,
13794 touchend: this._onUp
13798 _onUp: function (e) {
13799 clearTimeout(this._holdTimeout);
13802 touchmove: this._onMove,
13803 touchend: this._onUp
13806 if (this._fireClick && e && e.changedTouches) {
13808 var first = e.changedTouches[0],
13811 if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
13812 removeClass(el, 'leaflet-active');
13815 this._simulateEvent('mouseup', first);
13817 // simulate click if the touch didn't move too much
13818 if (this._isTapValid()) {
13819 this._simulateEvent('click', first);
13824 _isTapValid: function () {
13825 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
13828 _onMove: function (e) {
13829 var first = e.touches[0];
13830 this._newPos = new Point(first.clientX, first.clientY);
13831 this._simulateEvent('mousemove', first);
13834 _simulateEvent: function (type, e) {
13835 var simulatedEvent = document.createEvent('MouseEvents');
13837 simulatedEvent._simulated = true;
13838 e.target._simulatedClick = true;
13840 simulatedEvent.initMouseEvent(
13841 type, true, true, window, 1,
13842 e.screenX, e.screenY,
13843 e.clientX, e.clientY,
13844 false, false, false, false, 0, null);
13846 e.target.dispatchEvent(simulatedEvent);
13850 // @section Handlers
13851 // @property tap: Handler
13852 // Mobile touch hacks (quick tap and touch hold) handler.
13853 if (touch && !pointer) {
13854 Map.addInitHook('addHandler', 'tap', Tap);
13858 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
13862 // @section Interaction Options
13864 // @section Touch interaction options
13865 // @option touchZoom: Boolean|String = *
13866 // Whether the map can be zoomed by touch-dragging with two fingers. If
13867 // passed `'center'`, it will zoom to the center of the view regardless of
13868 // where the touch events (fingers) were. Enabled for touch-capable web
13869 // browsers except for old Androids.
13870 touchZoom: touch && !android23,
13872 // @option bounceAtZoomLimits: Boolean = true
13873 // Set it to false if you don't want the map to zoom beyond min/max zoom
13874 // and then bounce back when pinch-zooming.
13875 bounceAtZoomLimits: true
13878 var TouchZoom = Handler.extend({
13879 addHooks: function () {
13880 addClass(this._map._container, 'leaflet-touch-zoom');
13881 on(this._map._container, 'touchstart', this._onTouchStart, this);
13884 removeHooks: function () {
13885 removeClass(this._map._container, 'leaflet-touch-zoom');
13886 off(this._map._container, 'touchstart', this._onTouchStart, this);
13889 _onTouchStart: function (e) {
13890 var map = this._map;
13891 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
13893 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
13894 p2 = map.mouseEventToContainerPoint(e.touches[1]);
13896 this._centerPoint = map.getSize()._divideBy(2);
13897 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
13898 if (map.options.touchZoom !== 'center') {
13899 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
13902 this._startDist = p1.distanceTo(p2);
13903 this._startZoom = map.getZoom();
13905 this._moved = false;
13906 this._zooming = true;
13910 on(document, 'touchmove', this._onTouchMove, this);
13911 on(document, 'touchend', this._onTouchEnd, this);
13916 _onTouchMove: function (e) {
13917 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
13919 var map = this._map,
13920 p1 = map.mouseEventToContainerPoint(e.touches[0]),
13921 p2 = map.mouseEventToContainerPoint(e.touches[1]),
13922 scale = p1.distanceTo(p2) / this._startDist;
13924 this._zoom = map.getScaleZoom(scale, this._startZoom);
13926 if (!map.options.bounceAtZoomLimits && (
13927 (this._zoom < map.getMinZoom() && scale < 1) ||
13928 (this._zoom > map.getMaxZoom() && scale > 1))) {
13929 this._zoom = map._limitZoom(this._zoom);
13932 if (map.options.touchZoom === 'center') {
13933 this._center = this._startLatLng;
13934 if (scale === 1) { return; }
13936 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
13937 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
13938 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
13939 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
13942 if (!this._moved) {
13943 map._moveStart(true, false);
13944 this._moved = true;
13947 cancelAnimFrame(this._animRequest);
13949 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
13950 this._animRequest = requestAnimFrame(moveFn, this, true);
13955 _onTouchEnd: function () {
13956 if (!this._moved || !this._zooming) {
13957 this._zooming = false;
13961 this._zooming = false;
13962 cancelAnimFrame(this._animRequest);
13964 off(document, 'touchmove', this._onTouchMove);
13965 off(document, 'touchend', this._onTouchEnd);
13967 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
13968 if (this._map.options.zoomAnimation) {
13969 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
13971 this._map._resetView(this._center, this._map._limitZoom(this._zoom));
13976 // @section Handlers
13977 // @property touchZoom: Handler
13978 // Touch zoom handler.
13979 Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
13981 Map.BoxZoom = BoxZoom;
13982 Map.DoubleClickZoom = DoubleClickZoom;
13984 Map.Keyboard = Keyboard;
13985 Map.ScrollWheelZoom = ScrollWheelZoom;
13987 Map.TouchZoom = TouchZoom;
13989 Object.freeze = freeze;
13991 exports.version = version;
13992 exports.Control = Control;
13993 exports.control = control;
13994 exports.Browser = Browser;
13995 exports.Evented = Evented;
13996 exports.Mixin = Mixin;
13997 exports.Util = Util;
13998 exports.Class = Class;
13999 exports.Handler = Handler;
14000 exports.extend = extend;
14001 exports.bind = bind;
14002 exports.stamp = stamp;
14003 exports.setOptions = setOptions;
14004 exports.DomEvent = DomEvent;
14005 exports.DomUtil = DomUtil;
14006 exports.PosAnimation = PosAnimation;
14007 exports.Draggable = Draggable;
14008 exports.LineUtil = LineUtil;
14009 exports.PolyUtil = PolyUtil;
14010 exports.Point = Point;
14011 exports.point = toPoint;
14012 exports.Bounds = Bounds;
14013 exports.bounds = toBounds;
14014 exports.Transformation = Transformation;
14015 exports.transformation = toTransformation;
14016 exports.Projection = index;
14017 exports.LatLng = LatLng;
14018 exports.latLng = toLatLng;
14019 exports.LatLngBounds = LatLngBounds;
14020 exports.latLngBounds = toLatLngBounds;
14022 exports.GeoJSON = GeoJSON;
14023 exports.geoJSON = geoJSON;
14024 exports.geoJson = geoJson;
14025 exports.Layer = Layer;
14026 exports.LayerGroup = LayerGroup;
14027 exports.layerGroup = layerGroup;
14028 exports.FeatureGroup = FeatureGroup;
14029 exports.featureGroup = featureGroup;
14030 exports.ImageOverlay = ImageOverlay;
14031 exports.imageOverlay = imageOverlay;
14032 exports.VideoOverlay = VideoOverlay;
14033 exports.videoOverlay = videoOverlay;
14034 exports.SVGOverlay = SVGOverlay;
14035 exports.svgOverlay = svgOverlay;
14036 exports.DivOverlay = DivOverlay;
14037 exports.Popup = Popup;
14038 exports.popup = popup;
14039 exports.Tooltip = Tooltip;
14040 exports.tooltip = tooltip;
14041 exports.Icon = Icon;
14042 exports.icon = icon;
14043 exports.DivIcon = DivIcon;
14044 exports.divIcon = divIcon;
14045 exports.Marker = Marker;
14046 exports.marker = marker;
14047 exports.TileLayer = TileLayer;
14048 exports.tileLayer = tileLayer;
14049 exports.GridLayer = GridLayer;
14050 exports.gridLayer = gridLayer;
14052 exports.svg = svg$1;
14053 exports.Renderer = Renderer;
14054 exports.Canvas = Canvas;
14055 exports.canvas = canvas$1;
14056 exports.Path = Path;
14057 exports.CircleMarker = CircleMarker;
14058 exports.circleMarker = circleMarker;
14059 exports.Circle = Circle;
14060 exports.circle = circle;
14061 exports.Polyline = Polyline;
14062 exports.polyline = polyline;
14063 exports.Polygon = Polygon;
14064 exports.polygon = polygon;
14065 exports.Rectangle = Rectangle;
14066 exports.rectangle = rectangle;
14068 exports.map = createMap;
14070 var oldL = window.L;
14071 exports.noConflict = function() {
14076 // Always export us to window global (see #2364)
14077 window.L = exports;
14080 //# sourceMappingURL=leaflet-src.js.map