2 * Leaflet 1.3.4, a JS library for interactive maps. http://leafletjs.com
3 * (c) 2010-2018 Vladimir Agafonkin, (c) 2010-2011 CloudMade
6 (function (global, factory) {
7 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
8 typeof define === 'function' && define.amd ? define(['exports'], factory) :
9 (factory((global.L = {})));
10 }(this, (function (exports) { 'use strict';
12 var version = "1.3.4";
17 * Various utility functions, used by Leaflet internally.
20 var freeze = Object.freeze;
21 Object.freeze = function (obj) { return obj; };
23 // @function extend(dest: Object, src?: Object): Object
24 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
25 function extend(dest) {
28 for (j = 1, len = arguments.length; j < len; j++) {
37 // @function create(proto: Object, properties?: Object): Object
38 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
39 var create = Object.create || (function () {
41 return function (proto) {
47 // @function bind(fn: Function, …): Function
48 // Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
49 // Has a `L.bind()` shortcut.
50 function bind(fn, obj) {
51 var slice = Array.prototype.slice;
54 return fn.bind.apply(fn, slice.call(arguments, 1));
57 var args = slice.call(arguments, 2);
60 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
64 // @property lastId: Number
65 // Last unique ID used by [`stamp()`](#util-stamp)
68 // @function stamp(obj: Object): Number
69 // Returns the unique ID of an object, assigning it one if it doesn't have it.
72 obj._leaflet_id = obj._leaflet_id || ++lastId;
73 return obj._leaflet_id;
77 // @function throttle(fn: Function, time: Number, context: Object): Function
78 // Returns a function which executes function `fn` with the given scope `context`
79 // (so that the `this` keyword refers to `context` inside `fn`'s code). The function
80 // `fn` will be called no more than one time per given amount of `time`. The arguments
81 // received by the bound function will be any arguments passed when binding the
82 // function, followed by any arguments passed when invoking the bound function.
83 // Has an `L.throttle` shortcut.
84 function throttle(fn, time, context) {
85 var lock, args, wrapperFn, later;
88 // reset lock and call if queued
91 wrapperFn.apply(context, args);
96 wrapperFn = function () {
98 // called too soon, queue to call later
102 // call and lock until later
103 fn.apply(context, arguments);
104 setTimeout(later, time);
112 // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
113 // Returns the number `num` modulo `range` in such a way so it lies within
114 // `range[0]` and `range[1]`. The returned value will be always smaller than
115 // `range[1]` unless `includeMax` is set to `true`.
116 function wrapNum(x, range, includeMax) {
120 return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
123 // @function falseFn(): Function
124 // Returns a function which always returns `false`.
125 function falseFn() { return false; }
127 // @function formatNum(num: Number, digits?: Number): Number
128 // Returns the number `num` rounded to `digits` decimals, or to 6 decimals by default.
129 function formatNum(num, digits) {
130 var pow = Math.pow(10, (digits === undefined ? 6 : digits));
131 return Math.round(num * pow) / pow;
134 // @function trim(str: String): String
135 // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
137 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
140 // @function splitWords(str: String): String[]
141 // Trims and splits the string on whitespace and returns the array of parts.
142 function splitWords(str) {
143 return trim(str).split(/\s+/);
146 // @function setOptions(obj: Object, options: Object): Object
147 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
148 function setOptions(obj, options) {
149 if (!obj.hasOwnProperty('options')) {
150 obj.options = obj.options ? create(obj.options) : {};
152 for (var i in options) {
153 obj.options[i] = options[i];
158 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
159 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
160 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
161 // be appended at the end. If `uppercase` is `true`, the parameter names will
162 // be uppercased (e.g. `'?A=foo&B=bar'`)
163 function getParamString(obj, existingUrl, uppercase) {
166 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
168 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
171 var templateRe = /\{ *([\w_-]+) *\}/g;
173 // @function template(str: String, data: Object): String
174 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
175 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
176 // `('Hello foo, bar')`. You can also specify functions instead of strings for
177 // data values — they will be evaluated passing `data` as an argument.
178 function template(str, data) {
179 return str.replace(templateRe, function (str, key) {
180 var value = data[key];
182 if (value === undefined) {
183 throw new Error('No value provided for variable ' + str);
185 } else if (typeof value === 'function') {
192 // @function isArray(obj): Boolean
193 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
194 var isArray = Array.isArray || function (obj) {
195 return (Object.prototype.toString.call(obj) === '[object Array]');
198 // @function indexOf(array: Array, el: Object): Number
199 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
200 function indexOf(array, el) {
201 for (var i = 0; i < array.length; i++) {
202 if (array[i] === el) { return i; }
207 // @property emptyImageUrl: String
208 // Data URI string containing a base64-encoded empty GIF image.
209 // Used as a hack to free memory from unused images on WebKit-powered
210 // mobile devices (by setting image `src` to this string).
211 var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
213 // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
215 function getPrefixed(name) {
216 return window['webkit' + name] || window['moz' + name] || window['ms' + name];
221 // fallback for IE 7-8
222 function timeoutDefer(fn) {
223 var time = +new Date(),
224 timeToCall = Math.max(0, 16 - (time - lastTime));
226 lastTime = time + timeToCall;
227 return window.setTimeout(fn, timeToCall);
230 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
231 var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
232 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
234 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
235 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
236 // `context` if given. When `immediate` is set, `fn` is called immediately if
237 // the browser doesn't have native support for
238 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
239 // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
240 function requestAnimFrame(fn, context, immediate) {
241 if (immediate && requestFn === timeoutDefer) {
244 return requestFn.call(window, bind(fn, context));
248 // @function cancelAnimFrame(id: Number): undefined
249 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
250 function cancelAnimFrame(id) {
252 cancelFn.call(window, id);
257 var Util = (Object.freeze || Object)({
267 formatNum: formatNum,
269 splitWords: splitWords,
270 setOptions: setOptions,
271 getParamString: getParamString,
275 emptyImageUrl: emptyImageUrl,
276 requestFn: requestFn,
278 requestAnimFrame: requestAnimFrame,
279 cancelAnimFrame: cancelAnimFrame
288 // Thanks to John Resig and Dean Edwards for inspiration!
292 Class.extend = function (props) {
294 // @function extend(props: Object): Function
295 // [Extends the current class](#class-inheritance) given the properties to be included.
296 // Returns a Javascript function that is a class constructor (to be called with `new`).
297 var NewClass = function () {
299 // call the constructor
300 if (this.initialize) {
301 this.initialize.apply(this, arguments);
304 // call all constructor hooks
305 this.callInitHooks();
308 var parentProto = NewClass.__super__ = this.prototype;
310 var proto = create(parentProto);
311 proto.constructor = NewClass;
313 NewClass.prototype = proto;
315 // inherit parent's statics
316 for (var i in this) {
317 if (this.hasOwnProperty(i) && i !== 'prototype' && i !== '__super__') {
318 NewClass[i] = this[i];
322 // mix static properties into the class
324 extend(NewClass, props.statics);
325 delete props.statics;
328 // mix includes into the prototype
329 if (props.includes) {
330 checkDeprecatedMixinEvents(props.includes);
331 extend.apply(null, [proto].concat(props.includes));
332 delete props.includes;
337 props.options = extend(create(proto.options), props.options);
340 // mix given properties into the prototype
341 extend(proto, props);
343 proto._initHooks = [];
345 // add method for calling all hooks
346 proto.callInitHooks = function () {
348 if (this._initHooksCalled) { return; }
350 if (parentProto.callInitHooks) {
351 parentProto.callInitHooks.call(this);
354 this._initHooksCalled = true;
356 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
357 proto._initHooks[i].call(this);
365 // @function include(properties: Object): this
366 // [Includes a mixin](#class-includes) into the current class.
367 Class.include = function (props) {
368 extend(this.prototype, props);
372 // @function mergeOptions(options: Object): this
373 // [Merges `options`](#class-options) into the defaults of the class.
374 Class.mergeOptions = function (options) {
375 extend(this.prototype.options, options);
379 // @function addInitHook(fn: Function): this
380 // Adds a [constructor hook](#class-constructor-hooks) to the class.
381 Class.addInitHook = function (fn) { // (Function) || (String, args...)
382 var args = Array.prototype.slice.call(arguments, 1);
384 var init = typeof fn === 'function' ? fn : function () {
385 this[fn].apply(this, args);
388 this.prototype._initHooks = this.prototype._initHooks || [];
389 this.prototype._initHooks.push(init);
393 function checkDeprecatedMixinEvents(includes) {
394 if (typeof L === 'undefined' || !L || !L.Mixin) { return; }
396 includes = isArray(includes) ? includes : [includes];
398 for (var i = 0; i < includes.length; i++) {
399 if (includes[i] === L.Mixin.Events) {
400 console.warn('Deprecated include of L.Mixin.Events: ' +
401 'this property will be removed in future releases, ' +
402 'please inherit from L.Evented instead.', new Error().stack);
412 * A set of methods shared between event-powered classes (like `Map` and `Marker`). Generally, events allow you to execute some function when something happens with an object (e.g. the user clicks on the map, causing the map to fire `'click'` event).
417 * map.on('click', function(e) {
422 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
425 * function onClick(e) { ... }
427 * map.on('click', onClick);
428 * map.off('click', onClick);
433 /* @method on(type: String, fn: Function, context?: Object): this
434 * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`).
437 * @method on(eventMap: Object): this
438 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
440 on: function (types, fn, context) {
442 // types can be a map of types/handlers
443 if (typeof types === 'object') {
444 for (var type in types) {
445 // we don't process space-separated events here for performance;
446 // it's a hot path since Layer uses the on(obj) syntax
447 this._on(type, types[type], fn);
451 // types can be a string of space-separated words
452 types = splitWords(types);
454 for (var i = 0, len = types.length; i < len; i++) {
455 this._on(types[i], fn, context);
462 /* @method off(type: String, fn?: Function, context?: Object): this
463 * Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener.
466 * @method off(eventMap: Object): this
467 * Removes a set of type/listener pairs.
471 * Removes all listeners to all events on the object.
473 off: function (types, fn, context) {
476 // clear all listeners if called without arguments
479 } else if (typeof types === 'object') {
480 for (var type in types) {
481 this._off(type, types[type], fn);
485 types = splitWords(types);
487 for (var i = 0, len = types.length; i < len; i++) {
488 this._off(types[i], fn, context);
495 // attach listener (without syntactic sugar now)
496 _on: function (type, fn, context) {
497 this._events = this._events || {};
499 /* get/init listeners for type */
500 var typeListeners = this._events[type];
501 if (!typeListeners) {
503 this._events[type] = typeListeners;
506 if (context === this) {
507 // Less memory footprint.
510 var newListener = {fn: fn, ctx: context},
511 listeners = typeListeners;
513 // check if fn already there
514 for (var i = 0, len = listeners.length; i < len; i++) {
515 if (listeners[i].fn === fn && listeners[i].ctx === context) {
520 listeners.push(newListener);
523 _off: function (type, fn, context) {
528 if (!this._events) { return; }
530 listeners = this._events[type];
537 // Set all removed listeners to noop so they are not called if remove happens in fire
538 for (i = 0, len = listeners.length; i < len; i++) {
539 listeners[i].fn = falseFn;
541 // clear all listeners for a type if function isn't specified
542 delete this._events[type];
546 if (context === this) {
552 // find fn and remove it
553 for (i = 0, len = listeners.length; i < len; i++) {
554 var l = listeners[i];
555 if (l.ctx !== context) { continue; }
558 // set the removed listener to noop so that's not called if remove happens in fire
561 if (this._firingCount) {
562 /* copy array in case events are being fired */
563 this._events[type] = listeners = listeners.slice();
565 listeners.splice(i, 1);
573 // @method fire(type: String, data?: Object, propagate?: Boolean): this
574 // Fires an event of the specified type. You can optionally provide an data
575 // object — the first argument of the listener function will contain its
576 // properties. The event can optionally be propagated to event parents.
577 fire: function (type, data, propagate) {
578 if (!this.listens(type, propagate)) { return this; }
580 var event = extend({}, data, {
583 sourceTarget: data && data.sourceTarget || this
587 var listeners = this._events[type];
590 this._firingCount = (this._firingCount + 1) || 1;
591 for (var i = 0, len = listeners.length; i < len; i++) {
592 var l = listeners[i];
593 l.fn.call(l.ctx || this, event);
601 // propagate the event to parents (set with addEventParent)
602 this._propagateEvent(event);
608 // @method listens(type: String): Boolean
609 // Returns `true` if a particular event type has any listeners attached to it.
610 listens: function (type, propagate) {
611 var listeners = this._events && this._events[type];
612 if (listeners && listeners.length) { return true; }
615 // also check parents for listeners if event propagates
616 for (var id in this._eventParents) {
617 if (this._eventParents[id].listens(type, propagate)) { return true; }
623 // @method once(…): this
624 // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
625 once: function (types, fn, context) {
627 if (typeof types === 'object') {
628 for (var type in types) {
629 this.once(type, types[type], fn);
634 var handler = bind(function () {
636 .off(types, fn, context)
637 .off(types, handler, context);
640 // add a listener that's executed once and removed after that
642 .on(types, fn, context)
643 .on(types, handler, context);
646 // @method addEventParent(obj: Evented): this
647 // Adds an event parent - an `Evented` that will receive propagated events
648 addEventParent: function (obj) {
649 this._eventParents = this._eventParents || {};
650 this._eventParents[stamp(obj)] = obj;
654 // @method removeEventParent(obj: Evented): this
655 // Removes an event parent, so it will stop receiving propagated events
656 removeEventParent: function (obj) {
657 if (this._eventParents) {
658 delete this._eventParents[stamp(obj)];
663 _propagateEvent: function (e) {
664 for (var id in this._eventParents) {
665 this._eventParents[id].fire(e.type, extend({
667 propagatedFrom: e.target
673 // aliases; we should ditch those eventually
675 // @method addEventListener(…): this
676 // Alias to [`on(…)`](#evented-on)
677 Events.addEventListener = Events.on;
679 // @method removeEventListener(…): this
680 // Alias to [`off(…)`](#evented-off)
682 // @method clearAllEventListeners(…): this
683 // Alias to [`off()`](#evented-off)
684 Events.removeEventListener = Events.clearAllEventListeners = Events.off;
686 // @method addOneTimeEventListener(…): this
687 // Alias to [`once(…)`](#evented-once)
688 Events.addOneTimeEventListener = Events.once;
690 // @method fireEvent(…): this
691 // Alias to [`fire(…)`](#evented-fire)
692 Events.fireEvent = Events.fire;
694 // @method hasEventListeners(…): Boolean
695 // Alias to [`listens(…)`](#evented-listens)
696 Events.hasEventListeners = Events.listens;
698 var Evented = Class.extend(Events);
704 * Represents a point with `x` and `y` coordinates in pixels.
709 * var point = L.point(200, 300);
712 * All Leaflet methods and options that accept `Point` objects also accept them in a simple Array form (unless noted otherwise), so these lines are equivalent:
715 * map.panBy([200, 300]);
716 * map.panBy(L.point(200, 300));
719 * Note that `Point` does not inherit from Leafet's `Class` object,
720 * which means new classes can't inherit from it, and new methods
721 * can't be added to it with the `include` function.
724 function Point(x, y, round) {
725 // @property x: Number; The `x` coordinate of the point
726 this.x = (round ? Math.round(x) : x);
727 // @property y: Number; The `y` coordinate of the point
728 this.y = (round ? Math.round(y) : y);
731 var trunc = Math.trunc || function (v) {
732 return v > 0 ? Math.floor(v) : Math.ceil(v);
737 // @method clone(): Point
738 // Returns a copy of the current point.
740 return new Point(this.x, this.y);
743 // @method add(otherPoint: Point): Point
744 // Returns the result of addition of the current and the given points.
745 add: function (point) {
746 // non-destructive, returns a new point
747 return this.clone()._add(toPoint(point));
750 _add: function (point) {
751 // destructive, used directly for performance in situations where it's safe to modify existing point
757 // @method subtract(otherPoint: Point): Point
758 // Returns the result of subtraction of the given point from the current.
759 subtract: function (point) {
760 return this.clone()._subtract(toPoint(point));
763 _subtract: function (point) {
769 // @method divideBy(num: Number): Point
770 // Returns the result of division of the current point by the given number.
771 divideBy: function (num) {
772 return this.clone()._divideBy(num);
775 _divideBy: function (num) {
781 // @method multiplyBy(num: Number): Point
782 // Returns the result of multiplication of the current point by the given number.
783 multiplyBy: function (num) {
784 return this.clone()._multiplyBy(num);
787 _multiplyBy: function (num) {
793 // @method scaleBy(scale: Point): Point
794 // Multiply each coordinate of the current point by each coordinate of
795 // `scale`. In linear algebra terms, multiply the point by the
796 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
797 // defined by `scale`.
798 scaleBy: function (point) {
799 return new Point(this.x * point.x, this.y * point.y);
802 // @method unscaleBy(scale: Point): Point
803 // Inverse of `scaleBy`. Divide each coordinate of the current point by
804 // each coordinate of `scale`.
805 unscaleBy: function (point) {
806 return new Point(this.x / point.x, this.y / point.y);
809 // @method round(): Point
810 // Returns a copy of the current point with rounded coordinates.
812 return this.clone()._round();
815 _round: function () {
816 this.x = Math.round(this.x);
817 this.y = Math.round(this.y);
821 // @method floor(): Point
822 // Returns a copy of the current point with floored coordinates (rounded down).
824 return this.clone()._floor();
827 _floor: function () {
828 this.x = Math.floor(this.x);
829 this.y = Math.floor(this.y);
833 // @method ceil(): Point
834 // Returns a copy of the current point with ceiled coordinates (rounded up).
836 return this.clone()._ceil();
840 this.x = Math.ceil(this.x);
841 this.y = Math.ceil(this.y);
845 // @method trunc(): Point
846 // Returns a copy of the current point with truncated coordinates (rounded towards zero).
848 return this.clone()._trunc();
851 _trunc: function () {
852 this.x = trunc(this.x);
853 this.y = trunc(this.y);
857 // @method distanceTo(otherPoint: Point): Number
858 // Returns the cartesian distance between the current and the given points.
859 distanceTo: function (point) {
860 point = toPoint(point);
862 var x = point.x - this.x,
863 y = point.y - this.y;
865 return Math.sqrt(x * x + y * y);
868 // @method equals(otherPoint: Point): Boolean
869 // Returns `true` if the given point has the same coordinates.
870 equals: function (point) {
871 point = toPoint(point);
873 return point.x === this.x &&
877 // @method contains(otherPoint: Point): Boolean
878 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
879 contains: function (point) {
880 point = toPoint(point);
882 return Math.abs(point.x) <= Math.abs(this.x) &&
883 Math.abs(point.y) <= Math.abs(this.y);
886 // @method toString(): String
887 // Returns a string representation of the point for debugging purposes.
888 toString: function () {
890 formatNum(this.x) + ', ' +
891 formatNum(this.y) + ')';
895 // @factory L.point(x: Number, y: Number, round?: Boolean)
896 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
899 // @factory L.point(coords: Number[])
900 // Expects an array of the form `[x, y]` instead.
903 // @factory L.point(coords: Object)
904 // Expects a plain object of the form `{x: Number, y: Number}` instead.
905 function toPoint(x, y, round) {
906 if (x instanceof Point) {
910 return new Point(x[0], x[1]);
912 if (x === undefined || x === null) {
915 if (typeof x === 'object' && 'x' in x && 'y' in x) {
916 return new Point(x.x, x.y);
918 return new Point(x, y, round);
925 * Represents a rectangular area in pixel coordinates.
930 * var p1 = L.point(10, 10),
931 * p2 = L.point(40, 60),
932 * bounds = L.bounds(p1, p2);
935 * All Leaflet methods that accept `Bounds` objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
938 * otherBounds.intersects([[10, 10], [40, 60]]);
941 * Note that `Bounds` does not inherit from Leafet's `Class` object,
942 * which means new classes can't inherit from it, and new methods
943 * can't be added to it with the `include` function.
946 function Bounds(a, b) {
949 var points = b ? [a, b] : a;
951 for (var i = 0, len = points.length; i < len; i++) {
952 this.extend(points[i]);
957 // @method extend(point: Point): this
958 // Extends the bounds to contain the given point.
959 extend: function (point) { // (Point)
960 point = toPoint(point);
962 // @property min: Point
963 // The top left corner of the rectangle.
964 // @property max: Point
965 // The bottom right corner of the rectangle.
966 if (!this.min && !this.max) {
967 this.min = point.clone();
968 this.max = point.clone();
970 this.min.x = Math.min(point.x, this.min.x);
971 this.max.x = Math.max(point.x, this.max.x);
972 this.min.y = Math.min(point.y, this.min.y);
973 this.max.y = Math.max(point.y, this.max.y);
978 // @method getCenter(round?: Boolean): Point
979 // Returns the center point of the bounds.
980 getCenter: function (round) {
982 (this.min.x + this.max.x) / 2,
983 (this.min.y + this.max.y) / 2, round);
986 // @method getBottomLeft(): Point
987 // Returns the bottom-left point of the bounds.
988 getBottomLeft: function () {
989 return new Point(this.min.x, this.max.y);
992 // @method getTopRight(): Point
993 // Returns the top-right point of the bounds.
994 getTopRight: function () { // -> Point
995 return new Point(this.max.x, this.min.y);
998 // @method getTopLeft(): Point
999 // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).
1000 getTopLeft: function () {
1001 return this.min; // left, top
1004 // @method getBottomRight(): Point
1005 // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).
1006 getBottomRight: function () {
1007 return this.max; // right, bottom
1010 // @method getSize(): Point
1011 // Returns the size of the given bounds
1012 getSize: function () {
1013 return this.max.subtract(this.min);
1016 // @method contains(otherBounds: Bounds): Boolean
1017 // Returns `true` if the rectangle contains the given one.
1019 // @method contains(point: Point): Boolean
1020 // Returns `true` if the rectangle contains the given point.
1021 contains: function (obj) {
1024 if (typeof obj[0] === 'number' || obj instanceof Point) {
1027 obj = toBounds(obj);
1030 if (obj instanceof Bounds) {
1037 return (min.x >= this.min.x) &&
1038 (max.x <= this.max.x) &&
1039 (min.y >= this.min.y) &&
1040 (max.y <= this.max.y);
1043 // @method intersects(otherBounds: Bounds): Boolean
1044 // Returns `true` if the rectangle intersects the given bounds. Two bounds
1045 // intersect if they have at least one point in common.
1046 intersects: function (bounds) { // (Bounds) -> Boolean
1047 bounds = toBounds(bounds);
1053 xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1054 yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1056 return xIntersects && yIntersects;
1059 // @method overlaps(otherBounds: Bounds): Boolean
1060 // Returns `true` if the rectangle overlaps the given bounds. Two bounds
1061 // overlap if their intersection is an area.
1062 overlaps: function (bounds) { // (Bounds) -> Boolean
1063 bounds = toBounds(bounds);
1069 xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1070 yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1072 return xOverlaps && yOverlaps;
1075 isValid: function () {
1076 return !!(this.min && this.max);
1081 // @factory L.bounds(corner1: Point, corner2: Point)
1082 // Creates a Bounds object from two corners coordinate pairs.
1084 // @factory L.bounds(points: Point[])
1085 // Creates a Bounds object from the given array of points.
1086 function toBounds(a, b) {
1087 if (!a || a instanceof Bounds) {
1090 return new Bounds(a, b);
1094 * @class LatLngBounds
1095 * @aka L.LatLngBounds
1097 * Represents a rectangular geographical area on a map.
1102 * var corner1 = L.latLng(40.712, -74.227),
1103 * corner2 = L.latLng(40.774, -74.125),
1104 * bounds = L.latLngBounds(corner1, corner2);
1107 * All Leaflet methods that accept LatLngBounds objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
1111 * [40.712, -74.227],
1116 * Caution: if the area crosses the antimeridian (often confused with the International Date Line), you must specify corners _outside_ the [-180, 180] degrees longitude range.
1118 * Note that `LatLngBounds` does not inherit from Leafet's `Class` object,
1119 * which means new classes can't inherit from it, and new methods
1120 * can't be added to it with the `include` function.
1123 function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
1124 if (!corner1) { return; }
1126 var latlngs = corner2 ? [corner1, corner2] : corner1;
1128 for (var i = 0, len = latlngs.length; i < len; i++) {
1129 this.extend(latlngs[i]);
1133 LatLngBounds.prototype = {
1135 // @method extend(latlng: LatLng): this
1136 // Extend the bounds to contain the given point
1139 // @method extend(otherBounds: LatLngBounds): this
1140 // Extend the bounds to contain the given bounds
1141 extend: function (obj) {
1142 var sw = this._southWest,
1143 ne = this._northEast,
1146 if (obj instanceof LatLng) {
1150 } else if (obj instanceof LatLngBounds) {
1151 sw2 = obj._southWest;
1152 ne2 = obj._northEast;
1154 if (!sw2 || !ne2) { return this; }
1157 return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
1161 this._southWest = new LatLng(sw2.lat, sw2.lng);
1162 this._northEast = new LatLng(ne2.lat, ne2.lng);
1164 sw.lat = Math.min(sw2.lat, sw.lat);
1165 sw.lng = Math.min(sw2.lng, sw.lng);
1166 ne.lat = Math.max(ne2.lat, ne.lat);
1167 ne.lng = Math.max(ne2.lng, ne.lng);
1173 // @method pad(bufferRatio: Number): LatLngBounds
1174 // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
1175 // For example, a ratio of 0.5 extends the bounds by 50% in each direction.
1176 // Negative values will retract the bounds.
1177 pad: function (bufferRatio) {
1178 var sw = this._southWest,
1179 ne = this._northEast,
1180 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1181 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1183 return new LatLngBounds(
1184 new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1185 new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1188 // @method getCenter(): LatLng
1189 // Returns the center point of the bounds.
1190 getCenter: function () {
1192 (this._southWest.lat + this._northEast.lat) / 2,
1193 (this._southWest.lng + this._northEast.lng) / 2);
1196 // @method getSouthWest(): LatLng
1197 // Returns the south-west point of the bounds.
1198 getSouthWest: function () {
1199 return this._southWest;
1202 // @method getNorthEast(): LatLng
1203 // Returns the north-east point of the bounds.
1204 getNorthEast: function () {
1205 return this._northEast;
1208 // @method getNorthWest(): LatLng
1209 // Returns the north-west point of the bounds.
1210 getNorthWest: function () {
1211 return new LatLng(this.getNorth(), this.getWest());
1214 // @method getSouthEast(): LatLng
1215 // Returns the south-east point of the bounds.
1216 getSouthEast: function () {
1217 return new LatLng(this.getSouth(), this.getEast());
1220 // @method getWest(): Number
1221 // Returns the west longitude of the bounds
1222 getWest: function () {
1223 return this._southWest.lng;
1226 // @method getSouth(): Number
1227 // Returns the south latitude of the bounds
1228 getSouth: function () {
1229 return this._southWest.lat;
1232 // @method getEast(): Number
1233 // Returns the east longitude of the bounds
1234 getEast: function () {
1235 return this._northEast.lng;
1238 // @method getNorth(): Number
1239 // Returns the north latitude of the bounds
1240 getNorth: function () {
1241 return this._northEast.lat;
1244 // @method contains(otherBounds: LatLngBounds): Boolean
1245 // Returns `true` if the rectangle contains the given one.
1248 // @method contains (latlng: LatLng): Boolean
1249 // Returns `true` if the rectangle contains the given point.
1250 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1251 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
1252 obj = toLatLng(obj);
1254 obj = toLatLngBounds(obj);
1257 var sw = this._southWest,
1258 ne = this._northEast,
1261 if (obj instanceof LatLngBounds) {
1262 sw2 = obj.getSouthWest();
1263 ne2 = obj.getNorthEast();
1268 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1269 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1272 // @method intersects(otherBounds: LatLngBounds): Boolean
1273 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1274 intersects: function (bounds) {
1275 bounds = toLatLngBounds(bounds);
1277 var sw = this._southWest,
1278 ne = this._northEast,
1279 sw2 = bounds.getSouthWest(),
1280 ne2 = bounds.getNorthEast(),
1282 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1283 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1285 return latIntersects && lngIntersects;
1288 // @method overlaps(otherBounds: Bounds): Boolean
1289 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1290 overlaps: function (bounds) {
1291 bounds = toLatLngBounds(bounds);
1293 var sw = this._southWest,
1294 ne = this._northEast,
1295 sw2 = bounds.getSouthWest(),
1296 ne2 = bounds.getNorthEast(),
1298 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1299 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1301 return latOverlaps && lngOverlaps;
1304 // @method toBBoxString(): String
1305 // Returns a string with bounding box coordinates in a 'southwest_lng,southwest_lat,northeast_lng,northeast_lat' format. Useful for sending requests to web services that return geo data.
1306 toBBoxString: function () {
1307 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1310 // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
1311 // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overridden by setting `maxMargin` to a small number.
1312 equals: function (bounds, maxMargin) {
1313 if (!bounds) { return false; }
1315 bounds = toLatLngBounds(bounds);
1317 return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
1318 this._northEast.equals(bounds.getNorthEast(), maxMargin);
1321 // @method isValid(): Boolean
1322 // Returns `true` if the bounds are properly initialized.
1323 isValid: function () {
1324 return !!(this._southWest && this._northEast);
1328 // TODO International date line?
1330 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
1331 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
1334 // @factory L.latLngBounds(latlngs: LatLng[])
1335 // Creates a `LatLngBounds` object defined by the geographical points it contains. Very useful for zooming the map to fit a particular set of locations with [`fitBounds`](#map-fitbounds).
1336 function toLatLngBounds(a, b) {
1337 if (a instanceof LatLngBounds) {
1340 return new LatLngBounds(a, b);
1346 * Represents a geographical point with a certain latitude and longitude.
1351 * var latlng = L.latLng(50.5, 30.5);
1354 * All Leaflet methods that accept LatLng objects also accept them in a simple Array form and simple object form (unless noted otherwise), so these lines are equivalent:
1357 * map.panTo([50, 30]);
1358 * map.panTo({lon: 30, lat: 50});
1359 * map.panTo({lat: 50, lng: 30});
1360 * map.panTo(L.latLng(50, 30));
1363 * Note that `LatLng` does not inherit from Leaflet's `Class` object,
1364 * which means new classes can't inherit from it, and new methods
1365 * can't be added to it with the `include` function.
1368 function LatLng(lat, lng, alt) {
1369 if (isNaN(lat) || isNaN(lng)) {
1370 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1373 // @property lat: Number
1374 // Latitude in degrees
1377 // @property lng: Number
1378 // Longitude in degrees
1381 // @property alt: Number
1382 // Altitude in meters (optional)
1383 if (alt !== undefined) {
1388 LatLng.prototype = {
1389 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1390 // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overridden by setting `maxMargin` to a small number.
1391 equals: function (obj, maxMargin) {
1392 if (!obj) { return false; }
1394 obj = toLatLng(obj);
1396 var margin = Math.max(
1397 Math.abs(this.lat - obj.lat),
1398 Math.abs(this.lng - obj.lng));
1400 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1403 // @method toString(): String
1404 // Returns a string representation of the point (for debugging purposes).
1405 toString: function (precision) {
1407 formatNum(this.lat, precision) + ', ' +
1408 formatNum(this.lng, precision) + ')';
1411 // @method distanceTo(otherLatLng: LatLng): Number
1412 // Returns the distance (in meters) to the given `LatLng` calculated using the [Spherical Law of Cosines](https://en.wikipedia.org/wiki/Spherical_law_of_cosines).
1413 distanceTo: function (other) {
1414 return Earth.distance(this, toLatLng(other));
1417 // @method wrap(): LatLng
1418 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1420 return Earth.wrapLatLng(this);
1423 // @method toBounds(sizeInMeters: Number): LatLngBounds
1424 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
1425 toBounds: function (sizeInMeters) {
1426 var latAccuracy = 180 * sizeInMeters / 40075017,
1427 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1429 return toLatLngBounds(
1430 [this.lat - latAccuracy, this.lng - lngAccuracy],
1431 [this.lat + latAccuracy, this.lng + lngAccuracy]);
1434 clone: function () {
1435 return new LatLng(this.lat, this.lng, this.alt);
1441 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1442 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1445 // @factory L.latLng(coords: Array): LatLng
1446 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1449 // @factory L.latLng(coords: Object): LatLng
1450 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1452 function toLatLng(a, b, c) {
1453 if (a instanceof LatLng) {
1456 if (isArray(a) && typeof a[0] !== 'object') {
1457 if (a.length === 3) {
1458 return new LatLng(a[0], a[1], a[2]);
1460 if (a.length === 2) {
1461 return new LatLng(a[0], a[1]);
1465 if (a === undefined || a === null) {
1468 if (typeof a === 'object' && 'lat' in a) {
1469 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1471 if (b === undefined) {
1474 return new LatLng(a, b, c);
1480 * Object that defines coordinate reference systems for projecting
1481 * geographical points into pixel (screen) coordinates and back (and to
1482 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
1483 * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
1485 * Leaflet defines the most usual CRSs by default. If you want to use a
1486 * CRS not defined by default, take a look at the
1487 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
1489 * Note that the CRS instances do not inherit from Leafet's `Class` object,
1490 * and can't be instantiated. Also, new classes can't inherit from them,
1491 * and methods can't be added to them with the `include` function.
1495 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
1496 // Projects geographical coordinates into pixel coordinates for a given zoom.
1497 latLngToPoint: function (latlng, zoom) {
1498 var projectedPoint = this.projection.project(latlng),
1499 scale = this.scale(zoom);
1501 return this.transformation._transform(projectedPoint, scale);
1504 // @method pointToLatLng(point: Point, zoom: Number): LatLng
1505 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
1506 // zoom into geographical coordinates.
1507 pointToLatLng: function (point, zoom) {
1508 var scale = this.scale(zoom),
1509 untransformedPoint = this.transformation.untransform(point, scale);
1511 return this.projection.unproject(untransformedPoint);
1514 // @method project(latlng: LatLng): Point
1515 // Projects geographical coordinates into coordinates in units accepted for
1516 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
1517 project: function (latlng) {
1518 return this.projection.project(latlng);
1521 // @method unproject(point: Point): LatLng
1522 // Given a projected coordinate returns the corresponding LatLng.
1523 // The inverse of `project`.
1524 unproject: function (point) {
1525 return this.projection.unproject(point);
1528 // @method scale(zoom: Number): Number
1529 // Returns the scale used when transforming projected coordinates into
1530 // pixel coordinates for a particular zoom. For example, it returns
1531 // `256 * 2^zoom` for Mercator-based CRS.
1532 scale: function (zoom) {
1533 return 256 * Math.pow(2, zoom);
1536 // @method zoom(scale: Number): Number
1537 // Inverse of `scale()`, returns the zoom level corresponding to a scale
1538 // factor of `scale`.
1539 zoom: function (scale) {
1540 return Math.log(scale / 256) / Math.LN2;
1543 // @method getProjectedBounds(zoom: Number): Bounds
1544 // Returns the projection's bounds scaled and transformed for the provided `zoom`.
1545 getProjectedBounds: function (zoom) {
1546 if (this.infinite) { return null; }
1548 var b = this.projection.bounds,
1549 s = this.scale(zoom),
1550 min = this.transformation.transform(b.min, s),
1551 max = this.transformation.transform(b.max, s);
1553 return new Bounds(min, max);
1556 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
1557 // Returns the distance between two geographical coordinates.
1559 // @property code: String
1560 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
1562 // @property wrapLng: Number[]
1563 // An array of two numbers defining whether the longitude (horizontal) coordinate
1564 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
1565 // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
1567 // @property wrapLat: Number[]
1568 // Like `wrapLng`, but for the latitude (vertical) axis.
1570 // wrapLng: [min, max],
1571 // wrapLat: [min, max],
1573 // @property infinite: Boolean
1574 // If true, the coordinate space will be unbounded (infinite in both axes)
1577 // @method wrapLatLng(latlng: LatLng): LatLng
1578 // Returns a `LatLng` where lat and lng has been wrapped according to the
1579 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
1580 wrapLatLng: function (latlng) {
1581 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
1582 lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
1585 return new LatLng(lat, lng, alt);
1588 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
1589 // Returns a `LatLngBounds` with the same size as the given one, ensuring
1590 // that its center is within the CRS's bounds.
1591 // Only accepts actual `L.LatLngBounds` instances, not arrays.
1592 wrapLatLngBounds: function (bounds) {
1593 var center = bounds.getCenter(),
1594 newCenter = this.wrapLatLng(center),
1595 latShift = center.lat - newCenter.lat,
1596 lngShift = center.lng - newCenter.lng;
1598 if (latShift === 0 && lngShift === 0) {
1602 var sw = bounds.getSouthWest(),
1603 ne = bounds.getNorthEast(),
1604 newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),
1605 newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);
1607 return new LatLngBounds(newSw, newNe);
1615 * Serves as the base for CRS that are global such that they cover the earth.
1616 * Can only be used as the base for other CRS and cannot be used directly,
1617 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
1621 var Earth = extend({}, CRS, {
1622 wrapLng: [-180, 180],
1624 // Mean Earth Radius, as recommended for use by
1625 // the International Union of Geodesy and Geophysics,
1626 // see http://rosettacode.org/wiki/Haversine_formula
1629 // distance between two geographical points using spherical law of cosines approximation
1630 distance: function (latlng1, latlng2) {
1631 var rad = Math.PI / 180,
1632 lat1 = latlng1.lat * rad,
1633 lat2 = latlng2.lat * rad,
1634 sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2),
1635 sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2),
1636 a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon,
1637 c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1643 * @namespace Projection
1644 * @projection L.Projection.SphericalMercator
1646 * Spherical Mercator projection — the most common projection for online maps,
1647 * used by almost all free and commercial tile providers. Assumes that Earth is
1648 * a sphere. Used by the `EPSG:3857` CRS.
1651 var SphericalMercator = {
1654 MAX_LATITUDE: 85.0511287798,
1656 project: function (latlng) {
1657 var d = Math.PI / 180,
1658 max = this.MAX_LATITUDE,
1659 lat = Math.max(Math.min(max, latlng.lat), -max),
1660 sin = Math.sin(lat * d);
1663 this.R * latlng.lng * d,
1664 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
1667 unproject: function (point) {
1668 var d = 180 / Math.PI;
1671 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
1672 point.x * d / this.R);
1675 bounds: (function () {
1676 var d = 6378137 * Math.PI;
1677 return new Bounds([-d, -d], [d, d]);
1682 * @class Transformation
1683 * @aka L.Transformation
1685 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1686 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1687 * the reverse. Used by Leaflet in its projections code.
1692 * var transformation = L.transformation(2, 5, -1, 10),
1693 * p = L.point(1, 2),
1694 * p2 = transformation.transform(p), // L.point(7, 8)
1695 * p3 = transformation.untransform(p2); // L.point(1, 2)
1700 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1701 // Creates a `Transformation` object with the given coefficients.
1702 function Transformation(a, b, c, d) {
1704 // use array properties
1717 Transformation.prototype = {
1718 // @method transform(point: Point, scale?: Number): Point
1719 // Returns a transformed point, optionally multiplied by the given scale.
1720 // Only accepts actual `L.Point` instances, not arrays.
1721 transform: function (point, scale) { // (Point, Number) -> Point
1722 return this._transform(point.clone(), scale);
1725 // destructive transform (faster)
1726 _transform: function (point, scale) {
1728 point.x = scale * (this._a * point.x + this._b);
1729 point.y = scale * (this._c * point.y + this._d);
1733 // @method untransform(point: Point, scale?: Number): Point
1734 // Returns the reverse transformation of the given point, optionally divided
1735 // by the given scale. Only accepts actual `L.Point` instances, not arrays.
1736 untransform: function (point, scale) {
1739 (point.x / scale - this._b) / this._a,
1740 (point.y / scale - this._d) / this._c);
1744 // factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1746 // @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1747 // Instantiates a Transformation object with the given coefficients.
1750 // @factory L.transformation(coefficients: Array): Transformation
1751 // Expects an coefficients array of the form
1752 // `[a: Number, b: Number, c: Number, d: Number]`.
1754 function toTransformation(a, b, c, d) {
1755 return new Transformation(a, b, c, d);
1760 * @crs L.CRS.EPSG3857
1762 * The most common CRS for online maps, used by almost all free and commercial
1763 * tile providers. Uses Spherical Mercator projection. Set in by default in
1764 * Map's `crs` option.
1767 var EPSG3857 = extend({}, Earth, {
1769 projection: SphericalMercator,
1771 transformation: (function () {
1772 var scale = 0.5 / (Math.PI * SphericalMercator.R);
1773 return toTransformation(scale, 0.5, -scale, 0.5);
1777 var EPSG900913 = extend({}, EPSG3857, {
1781 // @namespace SVG; @section
1782 // There are several static functions which can be called without instantiating L.SVG:
1784 // @function create(name: String): SVGElement
1785 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
1786 // corresponding to the class name passed. For example, using 'line' will return
1787 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
1788 function svgCreate(name) {
1789 return document.createElementNS('http://www.w3.org/2000/svg', name);
1792 // @function pointsToPath(rings: Point[], closed: Boolean): String
1793 // Generates a SVG path string for multiple rings, with each ring turning
1794 // into "M..L..L.." instructions
1795 function pointsToPath(rings, closed) {
1797 i, j, len, len2, points, p;
1799 for (i = 0, len = rings.length; i < len; i++) {
1802 for (j = 0, len2 = points.length; j < len2; j++) {
1804 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
1807 // closes the ring for polygons; "x" is VML syntax
1808 str += closed ? (svg ? 'z' : 'x') : '';
1811 // SVG complains about empty path strings
1812 return str || 'M0 0';
1816 * @namespace Browser
1819 * A namespace with static properties for browser/feature detection used by Leaflet internally.
1824 * if (L.Browser.ielt9) {
1825 * alert('Upgrade your browser, dude!');
1830 var style$1 = document.documentElement.style;
1832 // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
1833 var ie = 'ActiveXObject' in window;
1835 // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
1836 var ielt9 = ie && !document.addEventListener;
1838 // @property edge: Boolean; `true` for the Edge web browser.
1839 var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
1841 // @property webkit: Boolean;
1842 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
1843 var webkit = userAgentContains('webkit');
1845 // @property android: Boolean
1846 // `true` for any browser running on an Android platform.
1847 var android = userAgentContains('android');
1849 // @property android23: Boolean; `true` for browsers running on Android 2 or Android 3.
1850 var android23 = userAgentContains('android 2') || userAgentContains('android 3');
1852 /* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */
1853 var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit
1854 // @property androidStock: Boolean; `true` for the Android stock browser (i.e. not Chrome)
1855 var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);
1857 // @property opera: Boolean; `true` for the Opera browser
1858 var opera = !!window.opera;
1860 // @property chrome: Boolean; `true` for the Chrome browser.
1861 var chrome = userAgentContains('chrome');
1863 // @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
1864 var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
1866 // @property safari: Boolean; `true` for the Safari browser.
1867 var safari = !chrome && userAgentContains('safari');
1869 var phantom = userAgentContains('phantom');
1871 // @property opera12: Boolean
1872 // `true` for the Opera browser supporting CSS transforms (version 12 or later).
1873 var opera12 = 'OTransition' in style$1;
1875 // @property win: Boolean; `true` when the browser is running in a Windows platform
1876 var win = navigator.platform.indexOf('Win') === 0;
1878 // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
1879 var ie3d = ie && ('transition' in style$1);
1881 // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
1882 var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;
1884 // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
1885 var gecko3d = 'MozPerspective' in style$1;
1887 // @property any3d: Boolean
1888 // `true` for all browsers supporting CSS transforms.
1889 var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
1891 // @property mobile: Boolean; `true` for all browsers running in a mobile device.
1892 var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
1894 // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
1895 var mobileWebkit = mobile && webkit;
1897 // @property mobileWebkit3d: Boolean
1898 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
1899 var mobileWebkit3d = mobile && webkit3d;
1901 // @property msPointer: Boolean
1902 // `true` for browsers implementing the Microsoft touch events model (notably IE10).
1903 var msPointer = !window.PointerEvent && window.MSPointerEvent;
1905 // @property pointer: Boolean
1906 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
1907 var pointer = !!(window.PointerEvent || msPointer);
1909 // @property touch: Boolean
1910 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
1911 // This does not necessarily mean that the browser is running in a computer with
1912 // a touchscreen, it only means that the browser is capable of understanding
1914 var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
1915 (window.DocumentTouch && document instanceof window.DocumentTouch));
1917 // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
1918 var mobileOpera = mobile && opera;
1920 // @property mobileGecko: Boolean
1921 // `true` for gecko-based browsers running in a mobile device.
1922 var mobileGecko = mobile && gecko;
1924 // @property retina: Boolean
1925 // `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%.
1926 var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
1929 // @property canvas: Boolean
1930 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
1931 var canvas = (function () {
1932 return !!document.createElement('canvas').getContext;
1935 // @property svg: Boolean
1936 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
1937 var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect);
1939 // @property vml: Boolean
1940 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
1941 var vml = !svg && (function () {
1943 var div = document.createElement('div');
1944 div.innerHTML = '<v:shape adj="1"/>';
1946 var shape = div.firstChild;
1947 shape.style.behavior = 'url(#default#VML)';
1949 return shape && (typeof shape.adj === 'object');
1957 function userAgentContains(str) {
1958 return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
1962 var Browser = (Object.freeze || Object)({
1968 android23: android23,
1969 androidStock: androidStock,
1982 mobileWebkit: mobileWebkit,
1983 mobileWebkit3d: mobileWebkit3d,
1984 msPointer: msPointer,
1987 mobileOpera: mobileOpera,
1988 mobileGecko: mobileGecko,
1996 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
2000 var POINTER_DOWN = msPointer ? 'MSPointerDown' : 'pointerdown';
2001 var POINTER_MOVE = msPointer ? 'MSPointerMove' : 'pointermove';
2002 var POINTER_UP = msPointer ? 'MSPointerUp' : 'pointerup';
2003 var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel';
2004 var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION'];
2007 var _pointerDocListener = false;
2009 // DomEvent.DoubleTap needs to know about this
2010 var _pointersCount = 0;
2012 // Provides a touch events wrapper for (ms)pointer events.
2013 // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
2015 function addPointerListener(obj, type, handler, id) {
2016 if (type === 'touchstart') {
2017 _addPointerStart(obj, handler, id);
2019 } else if (type === 'touchmove') {
2020 _addPointerMove(obj, handler, id);
2022 } else if (type === 'touchend') {
2023 _addPointerEnd(obj, handler, id);
2029 function removePointerListener(obj, type, id) {
2030 var handler = obj['_leaflet_' + type + id];
2032 if (type === 'touchstart') {
2033 obj.removeEventListener(POINTER_DOWN, handler, false);
2035 } else if (type === 'touchmove') {
2036 obj.removeEventListener(POINTER_MOVE, handler, false);
2038 } else if (type === 'touchend') {
2039 obj.removeEventListener(POINTER_UP, handler, false);
2040 obj.removeEventListener(POINTER_CANCEL, handler, false);
2046 function _addPointerStart(obj, handler, id) {
2047 var onDown = bind(function (e) {
2048 if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
2049 // In IE11, some touch events needs to fire for form controls, or
2050 // the controls will stop working. We keep a whitelist of tag names that
2051 // need these events. For other target tags, we prevent default on the event.
2052 if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
2059 _handlePointer(e, handler);
2062 obj['_leaflet_touchstart' + id] = onDown;
2063 obj.addEventListener(POINTER_DOWN, onDown, false);
2065 // need to keep track of what pointers and how many are active to provide e.touches emulation
2066 if (!_pointerDocListener) {
2067 // we listen documentElement as any drags that end by moving the touch off the screen get fired there
2068 document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true);
2069 document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true);
2070 document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true);
2071 document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
2073 _pointerDocListener = true;
2077 function _globalPointerDown(e) {
2078 _pointers[e.pointerId] = e;
2082 function _globalPointerMove(e) {
2083 if (_pointers[e.pointerId]) {
2084 _pointers[e.pointerId] = e;
2088 function _globalPointerUp(e) {
2089 delete _pointers[e.pointerId];
2093 function _handlePointer(e, handler) {
2095 for (var i in _pointers) {
2096 e.touches.push(_pointers[i]);
2098 e.changedTouches = [e];
2103 function _addPointerMove(obj, handler, id) {
2104 var onMove = function (e) {
2105 // don't fire touch moves when mouse isn't down
2106 if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
2108 _handlePointer(e, handler);
2111 obj['_leaflet_touchmove' + id] = onMove;
2112 obj.addEventListener(POINTER_MOVE, onMove, false);
2115 function _addPointerEnd(obj, handler, id) {
2116 var onUp = function (e) {
2117 _handlePointer(e, handler);
2120 obj['_leaflet_touchend' + id] = onUp;
2121 obj.addEventListener(POINTER_UP, onUp, false);
2122 obj.addEventListener(POINTER_CANCEL, onUp, false);
2126 * Extends the event handling code with double tap support for mobile browsers.
2129 var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart';
2130 var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend';
2131 var _pre = '_leaflet_';
2133 // inspired by Zepto touch code by Thomas Fuchs
2134 function addDoubleTapListener(obj, handler, id) {
2139 function onTouchStart(e) {
2143 if ((!edge) || e.pointerType === 'mouse') { return; }
2144 count = _pointersCount;
2146 count = e.touches.length;
2149 if (count > 1) { return; }
2151 var now = Date.now(),
2152 delta = now - (last || now);
2154 touch$$1 = e.touches ? e.touches[0] : e;
2155 doubleTap = (delta > 0 && delta <= delay);
2159 function onTouchEnd(e) {
2160 if (doubleTap && !touch$$1.cancelBubble) {
2162 if ((!edge) || e.pointerType === 'mouse') { return; }
2163 // work around .type being readonly with MSPointer* events
2167 for (i in touch$$1) {
2169 newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop;
2171 touch$$1 = newTouch;
2173 touch$$1.type = 'dblclick';
2179 obj[_pre + _touchstart + id] = onTouchStart;
2180 obj[_pre + _touchend + id] = onTouchEnd;
2181 obj[_pre + 'dblclick' + id] = handler;
2183 obj.addEventListener(_touchstart, onTouchStart, false);
2184 obj.addEventListener(_touchend, onTouchEnd, false);
2186 // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
2187 // the browser doesn't fire touchend/pointerup events but does fire
2188 // native dblclicks. See #4127.
2189 // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
2190 obj.addEventListener('dblclick', handler, false);
2195 function removeDoubleTapListener(obj, id) {
2196 var touchstart = obj[_pre + _touchstart + id],
2197 touchend = obj[_pre + _touchend + id],
2198 dblclick = obj[_pre + 'dblclick' + id];
2200 obj.removeEventListener(_touchstart, touchstart, false);
2201 obj.removeEventListener(_touchend, touchend, false);
2203 obj.removeEventListener('dblclick', dblclick, false);
2210 * @namespace DomUtil
2212 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
2213 * tree, used by Leaflet internally.
2215 * Most functions expecting or returning a `HTMLElement` also work for
2216 * SVG elements. The only difference is that classes refer to CSS classes
2217 * in HTML and SVG classes in SVG.
2221 // @property TRANSFORM: String
2222 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
2223 var TRANSFORM = testProp(
2224 ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
2226 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
2227 // the same for the transitionend event, in particular the Android 4.1 stock browser
2229 // @property TRANSITION: String
2230 // Vendor-prefixed transition style name.
2231 var TRANSITION = testProp(
2232 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
2234 // @property TRANSITION_END: String
2235 // Vendor-prefixed transitionend event name.
2236 var TRANSITION_END =
2237 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
2240 // @function get(id: String|HTMLElement): HTMLElement
2241 // Returns an element given its DOM id, or returns the element itself
2242 // if it was passed directly.
2244 return typeof id === 'string' ? document.getElementById(id) : id;
2247 // @function getStyle(el: HTMLElement, styleAttrib: String): String
2248 // Returns the value for a certain style attribute on an element,
2249 // including computed values or values set through CSS.
2250 function getStyle(el, style) {
2251 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
2253 if ((!value || value === 'auto') && document.defaultView) {
2254 var css = document.defaultView.getComputedStyle(el, null);
2255 value = css ? css[style] : null;
2257 return value === 'auto' ? null : value;
2260 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
2261 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
2262 function create$1(tagName, className, container) {
2263 var el = document.createElement(tagName);
2264 el.className = className || '';
2267 container.appendChild(el);
2272 // @function remove(el: HTMLElement)
2273 // Removes `el` from its parent element
2274 function remove(el) {
2275 var parent = el.parentNode;
2277 parent.removeChild(el);
2281 // @function empty(el: HTMLElement)
2282 // Removes all of `el`'s children elements from `el`
2283 function empty(el) {
2284 while (el.firstChild) {
2285 el.removeChild(el.firstChild);
2289 // @function toFront(el: HTMLElement)
2290 // Makes `el` the last child of its parent, so it renders in front of the other children.
2291 function toFront(el) {
2292 var parent = el.parentNode;
2293 if (parent.lastChild !== el) {
2294 parent.appendChild(el);
2298 // @function toBack(el: HTMLElement)
2299 // Makes `el` the first child of its parent, so it renders behind the other children.
2300 function toBack(el) {
2301 var parent = el.parentNode;
2302 if (parent.firstChild !== el) {
2303 parent.insertBefore(el, parent.firstChild);
2307 // @function hasClass(el: HTMLElement, name: String): Boolean
2308 // Returns `true` if the element's class attribute contains `name`.
2309 function hasClass(el, name) {
2310 if (el.classList !== undefined) {
2311 return el.classList.contains(name);
2313 var className = getClass(el);
2314 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
2317 // @function addClass(el: HTMLElement, name: String)
2318 // Adds `name` to the element's class attribute.
2319 function addClass(el, name) {
2320 if (el.classList !== undefined) {
2321 var classes = splitWords(name);
2322 for (var i = 0, len = classes.length; i < len; i++) {
2323 el.classList.add(classes[i]);
2325 } else if (!hasClass(el, name)) {
2326 var className = getClass(el);
2327 setClass(el, (className ? className + ' ' : '') + name);
2331 // @function removeClass(el: HTMLElement, name: String)
2332 // Removes `name` from the element's class attribute.
2333 function removeClass(el, name) {
2334 if (el.classList !== undefined) {
2335 el.classList.remove(name);
2337 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
2341 // @function setClass(el: HTMLElement, name: String)
2342 // Sets the element's class.
2343 function setClass(el, name) {
2344 if (el.className.baseVal === undefined) {
2345 el.className = name;
2347 // in case of SVG element
2348 el.className.baseVal = name;
2352 // @function getClass(el: HTMLElement): String
2353 // Returns the element's class.
2354 function getClass(el) {
2355 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
2358 // @function setOpacity(el: HTMLElement, opacity: Number)
2359 // Set the opacity of an element (including old IE support).
2360 // `opacity` must be a number from `0` to `1`.
2361 function setOpacity(el, value) {
2362 if ('opacity' in el.style) {
2363 el.style.opacity = value;
2364 } else if ('filter' in el.style) {
2365 _setOpacityIE(el, value);
2369 function _setOpacityIE(el, value) {
2371 filterName = 'DXImageTransform.Microsoft.Alpha';
2373 // filters collection throws an error if we try to retrieve a filter that doesn't exist
2375 filter = el.filters.item(filterName);
2377 // don't set opacity to 1 if we haven't already set an opacity,
2378 // it isn't needed and breaks transparent pngs.
2379 if (value === 1) { return; }
2382 value = Math.round(value * 100);
2385 filter.Enabled = (value !== 100);
2386 filter.Opacity = value;
2388 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
2392 // @function testProp(props: String[]): String|false
2393 // Goes through the array of style names and returns the first name
2394 // that is a valid style name for an element. If no such name is found,
2395 // it returns false. Useful for vendor-prefixed styles like `transform`.
2396 function testProp(props) {
2397 var style = document.documentElement.style;
2399 for (var i = 0; i < props.length; i++) {
2400 if (props[i] in style) {
2407 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
2408 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
2409 // and optionally scaled by `scale`. Does not have an effect if the
2410 // browser doesn't support 3D CSS transforms.
2411 function setTransform(el, offset, scale) {
2412 var pos = offset || new Point(0, 0);
2414 el.style[TRANSFORM] =
2416 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
2417 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
2418 (scale ? ' scale(' + scale + ')' : '');
2421 // @function setPosition(el: HTMLElement, position: Point)
2422 // Sets the position of `el` to coordinates specified by `position`,
2423 // using CSS translate or top/left positioning depending on the browser
2424 // (used by Leaflet internally to position its layers).
2425 function setPosition(el, point) {
2428 el._leaflet_pos = point;
2432 setTransform(el, point);
2434 el.style.left = point.x + 'px';
2435 el.style.top = point.y + 'px';
2439 // @function getPosition(el: HTMLElement): Point
2440 // Returns the coordinates of an element previously positioned with setPosition.
2441 function getPosition(el) {
2442 // this method is only used for elements previously positioned using setPosition,
2443 // so it's safe to cache the position for performance
2445 return el._leaflet_pos || new Point(0, 0);
2448 // @function disableTextSelection()
2449 // Prevents the user from generating `selectstart` DOM events, usually generated
2450 // when the user drags the mouse through a page with text. Used internally
2451 // by Leaflet to override the behaviour of any click-and-drag interaction on
2452 // the map. Affects drag interactions on the whole document.
2454 // @function enableTextSelection()
2455 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
2456 var disableTextSelection;
2457 var enableTextSelection;
2459 if ('onselectstart' in document) {
2460 disableTextSelection = function () {
2461 on(window, 'selectstart', preventDefault);
2463 enableTextSelection = function () {
2464 off(window, 'selectstart', preventDefault);
2467 var userSelectProperty = testProp(
2468 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
2470 disableTextSelection = function () {
2471 if (userSelectProperty) {
2472 var style = document.documentElement.style;
2473 _userSelect = style[userSelectProperty];
2474 style[userSelectProperty] = 'none';
2477 enableTextSelection = function () {
2478 if (userSelectProperty) {
2479 document.documentElement.style[userSelectProperty] = _userSelect;
2480 _userSelect = undefined;
2485 // @function disableImageDrag()
2486 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
2487 // for `dragstart` DOM events, usually generated when the user drags an image.
2488 function disableImageDrag() {
2489 on(window, 'dragstart', preventDefault);
2492 // @function enableImageDrag()
2493 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
2494 function enableImageDrag() {
2495 off(window, 'dragstart', preventDefault);
2498 var _outlineElement;
2500 // @function preventOutline(el: HTMLElement)
2501 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
2502 // of the element `el` invisible. Used internally by Leaflet to prevent
2503 // focusable elements from displaying an outline when the user performs a
2504 // drag interaction on them.
2505 function preventOutline(element) {
2506 while (element.tabIndex === -1) {
2507 element = element.parentNode;
2509 if (!element.style) { return; }
2511 _outlineElement = element;
2512 _outlineStyle = element.style.outline;
2513 element.style.outline = 'none';
2514 on(window, 'keydown', restoreOutline);
2517 // @function restoreOutline()
2518 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
2519 function restoreOutline() {
2520 if (!_outlineElement) { return; }
2521 _outlineElement.style.outline = _outlineStyle;
2522 _outlineElement = undefined;
2523 _outlineStyle = undefined;
2524 off(window, 'keydown', restoreOutline);
2527 // @function getSizedParentNode(el: HTMLElement): HTMLElement
2528 // Finds the closest parent node which size (width and height) is not null.
2529 function getSizedParentNode(element) {
2531 element = element.parentNode;
2532 } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
2536 // @function getScale(el: HTMLElement): Object
2537 // Computes the CSS scale currently applied on the element.
2538 // Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
2539 // and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
2540 function getScale(element) {
2541 var rect = element.getBoundingClientRect(); // Read-only in old browsers.
2544 x: rect.width / element.offsetWidth || 1,
2545 y: rect.height / element.offsetHeight || 1,
2546 boundingClientRect: rect
2551 var DomUtil = (Object.freeze || Object)({
2552 TRANSFORM: TRANSFORM,
2553 TRANSITION: TRANSITION,
2554 TRANSITION_END: TRANSITION_END,
2564 removeClass: removeClass,
2567 setOpacity: setOpacity,
2569 setTransform: setTransform,
2570 setPosition: setPosition,
2571 getPosition: getPosition,
2572 disableTextSelection: disableTextSelection,
2573 enableTextSelection: enableTextSelection,
2574 disableImageDrag: disableImageDrag,
2575 enableImageDrag: enableImageDrag,
2576 preventOutline: preventOutline,
2577 restoreOutline: restoreOutline,
2578 getSizedParentNode: getSizedParentNode,
2583 * @namespace DomEvent
2584 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
2587 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
2589 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
2590 // Adds a listener function (`fn`) to a particular DOM event type of the
2591 // element `el`. You can optionally specify the context of the listener
2592 // (object the `this` keyword will point to). You can also pass several
2593 // space-separated types (e.g. `'click dblclick'`).
2596 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
2597 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2598 function on(obj, types, fn, context) {
2600 if (typeof types === 'object') {
2601 for (var type in types) {
2602 addOne(obj, type, types[type], fn);
2605 types = splitWords(types);
2607 for (var i = 0, len = types.length; i < len; i++) {
2608 addOne(obj, types[i], fn, context);
2615 var eventsKey = '_leaflet_events';
2617 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
2618 // Removes a previously added listener function.
2619 // Note that if you passed a custom context to on, you must pass the same
2620 // context to `off` in order to remove the listener.
2623 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
2624 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2625 function off(obj, types, fn, context) {
2627 if (typeof types === 'object') {
2628 for (var type in types) {
2629 removeOne(obj, type, types[type], fn);
2632 types = splitWords(types);
2634 for (var i = 0, len = types.length; i < len; i++) {
2635 removeOne(obj, types[i], fn, context);
2638 for (var j in obj[eventsKey]) {
2639 removeOne(obj, j, obj[eventsKey][j]);
2641 delete obj[eventsKey];
2647 function addOne(obj, type, fn, context) {
2648 var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
2650 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
2652 var handler = function (e) {
2653 return fn.call(context || obj, e || window.event);
2656 var originalHandler = handler;
2658 if (pointer && type.indexOf('touch') === 0) {
2659 // Needs DomEvent.Pointer.js
2660 addPointerListener(obj, type, handler, id);
2662 } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
2663 !(pointer && chrome)) {
2664 // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
2666 addDoubleTapListener(obj, handler, id);
2668 } else if ('addEventListener' in obj) {
2670 if (type === 'mousewheel') {
2671 obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2673 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
2674 handler = function (e) {
2675 e = e || window.event;
2676 if (isExternalTarget(obj, e)) {
2680 obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
2683 if (type === 'click' && android) {
2684 handler = function (e) {
2685 filterClick(e, originalHandler);
2688 obj.addEventListener(type, handler, false);
2691 } else if ('attachEvent' in obj) {
2692 obj.attachEvent('on' + type, handler);
2695 obj[eventsKey] = obj[eventsKey] || {};
2696 obj[eventsKey][id] = handler;
2699 function removeOne(obj, type, fn, context) {
2701 var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
2702 handler = obj[eventsKey] && obj[eventsKey][id];
2704 if (!handler) { return this; }
2706 if (pointer && type.indexOf('touch') === 0) {
2707 removePointerListener(obj, type, id);
2709 } else if (touch && (type === 'dblclick') && removeDoubleTapListener &&
2710 !(pointer && chrome)) {
2711 removeDoubleTapListener(obj, id);
2713 } else if ('removeEventListener' in obj) {
2715 if (type === 'mousewheel') {
2716 obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2719 obj.removeEventListener(
2720 type === 'mouseenter' ? 'mouseover' :
2721 type === 'mouseleave' ? 'mouseout' : type, handler, false);
2724 } else if ('detachEvent' in obj) {
2725 obj.detachEvent('on' + type, handler);
2728 obj[eventsKey][id] = null;
2731 // @function stopPropagation(ev: DOMEvent): this
2732 // Stop the given event from propagation to parent elements. Used inside the listener functions:
2734 // L.DomEvent.on(div, 'click', function (ev) {
2735 // L.DomEvent.stopPropagation(ev);
2738 function stopPropagation(e) {
2740 if (e.stopPropagation) {
2741 e.stopPropagation();
2742 } else if (e.originalEvent) { // In case of Leaflet event.
2743 e.originalEvent._stopped = true;
2745 e.cancelBubble = true;
2752 // @function disableScrollPropagation(el: HTMLElement): this
2753 // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
2754 function disableScrollPropagation(el) {
2755 addOne(el, 'mousewheel', stopPropagation);
2759 // @function disableClickPropagation(el: HTMLElement): this
2760 // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
2761 // `'mousedown'` and `'touchstart'` events (plus browser variants).
2762 function disableClickPropagation(el) {
2763 on(el, 'mousedown touchstart dblclick', stopPropagation);
2764 addOne(el, 'click', fakeStop);
2768 // @function preventDefault(ev: DOMEvent): this
2769 // Prevents the default action of the DOM Event `ev` from happening (such as
2770 // following a link in the href of the a element, or doing a POST request
2771 // with page reload when a `<form>` is submitted).
2772 // Use it inside listener functions.
2773 function preventDefault(e) {
2774 if (e.preventDefault) {
2777 e.returnValue = false;
2782 // @function stop(ev: DOMEvent): this
2783 // Does `stopPropagation` and `preventDefault` at the same time.
2790 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
2791 // Gets normalized mouse position from a DOM event relative to the
2792 // `container` (border excluded) or to the whole page if not specified.
2793 function getMousePosition(e, container) {
2795 return new Point(e.clientX, e.clientY);
2798 var scale = getScale(container),
2799 offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y)
2802 // offset.left/top values are in page scale (like clientX/Y),
2803 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
2804 (e.clientX - offset.left) / scale.x - container.clientLeft,
2805 (e.clientY - offset.top) / scale.y - container.clientTop
2809 // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
2810 // and Firefox scrolls device pixels, not CSS pixels
2812 (win && chrome) ? 2 * window.devicePixelRatio :
2813 gecko ? window.devicePixelRatio : 1;
2815 // @function getWheelDelta(ev: DOMEvent): Number
2816 // Gets normalized wheel delta from a mousewheel DOM event, in vertical
2817 // pixels scrolled (negative if scrolling down).
2818 // Events from pointing devices without precise scrolling are mapped to
2819 // a best guess of 60 pixels.
2820 function getWheelDelta(e) {
2821 return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
2822 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
2823 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
2824 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
2825 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
2826 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
2827 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
2828 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
2832 var skipEvents = {};
2834 function fakeStop(e) {
2835 // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
2836 skipEvents[e.type] = true;
2839 function skipped(e) {
2840 var events = skipEvents[e.type];
2841 // reset when checking, as it's only used in map container and propagates outside of the map
2842 skipEvents[e.type] = false;
2846 // check if element really left/entered the event target (for mouseenter/mouseleave)
2847 function isExternalTarget(el, e) {
2849 var related = e.relatedTarget;
2851 if (!related) { return true; }
2854 while (related && (related !== el)) {
2855 related = related.parentNode;
2860 return (related !== el);
2865 // this is a horrible workaround for a bug in Android where a single touch triggers two click events
2866 function filterClick(e, handler) {
2867 var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
2868 elapsed = lastClick && (timeStamp - lastClick);
2870 // are they closer together than 500ms yet more than 100ms?
2871 // Android typically triggers them ~300ms apart while multiple listeners
2872 // on the same event should be triggered far faster;
2873 // or check if click is simulated on the element, and if it is, reject any non-simulated events
2875 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
2879 lastClick = timeStamp;
2887 var DomEvent = (Object.freeze || Object)({
2890 stopPropagation: stopPropagation,
2891 disableScrollPropagation: disableScrollPropagation,
2892 disableClickPropagation: disableClickPropagation,
2893 preventDefault: preventDefault,
2895 getMousePosition: getMousePosition,
2896 getWheelDelta: getWheelDelta,
2899 isExternalTarget: isExternalTarget,
2905 * @class PosAnimation
2906 * @aka L.PosAnimation
2908 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
2912 * var fx = new L.PosAnimation();
2913 * fx.run(el, [300, 500], 0.5);
2916 * @constructor L.PosAnimation()
2917 * Creates a `PosAnimation` object.
2921 var PosAnimation = Evented.extend({
2923 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
2924 // Run an animation of a given element to a new position, optionally setting
2925 // duration in seconds (`0.25` by default) and easing linearity factor (3rd
2926 // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
2927 // `0.5` by default).
2928 run: function (el, newPos, duration, easeLinearity) {
2932 this._inProgress = true;
2933 this._duration = duration || 0.25;
2934 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
2936 this._startPos = getPosition(el);
2937 this._offset = newPos.subtract(this._startPos);
2938 this._startTime = +new Date();
2940 // @event start: Event
2941 // Fired when the animation starts
2948 // Stops the animation (if currently running).
2950 if (!this._inProgress) { return; }
2956 _animate: function () {
2958 this._animId = requestAnimFrame(this._animate, this);
2962 _step: function (round) {
2963 var elapsed = (+new Date()) - this._startTime,
2964 duration = this._duration * 1000;
2966 if (elapsed < duration) {
2967 this._runFrame(this._easeOut(elapsed / duration), round);
2974 _runFrame: function (progress, round) {
2975 var pos = this._startPos.add(this._offset.multiplyBy(progress));
2979 setPosition(this._el, pos);
2981 // @event step: Event
2982 // Fired continuously during the animation.
2986 _complete: function () {
2987 cancelAnimFrame(this._animId);
2989 this._inProgress = false;
2990 // @event end: Event
2991 // Fired when the animation ends.
2995 _easeOut: function (t) {
2996 return 1 - Math.pow(1 - t, this._easeOutPower);
3005 * The central class of the API — it is used to create a map on a page and manipulate it.
3010 * // initialize the map on the "map" div with a given center and zoom
3011 * var map = L.map('map', {
3012 * center: [51.505, -0.09],
3019 var Map = Evented.extend({
3022 // @section Map State Options
3023 // @option crs: CRS = L.CRS.EPSG3857
3024 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
3025 // sure what it means.
3028 // @option center: LatLng = undefined
3029 // Initial geographic center of the map
3032 // @option zoom: Number = undefined
3033 // Initial map zoom level
3036 // @option minZoom: Number = *
3037 // Minimum zoom level of the map.
3038 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3039 // the lowest of their `minZoom` options will be used instead.
3042 // @option maxZoom: Number = *
3043 // Maximum zoom level of the map.
3044 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3045 // the highest of their `maxZoom` options will be used instead.
3048 // @option layers: Layer[] = []
3049 // Array of layers that will be added to the map initially
3052 // @option maxBounds: LatLngBounds = null
3053 // When this option is set, the map restricts the view to the given
3054 // geographical bounds, bouncing the user back if the user tries to pan
3055 // outside the view. To set the restriction dynamically, use
3056 // [`setMaxBounds`](#map-setmaxbounds) method.
3057 maxBounds: undefined,
3059 // @option renderer: Renderer = *
3060 // The default method for drawing vector layers on the map. `L.SVG`
3061 // or `L.Canvas` by default depending on browser support.
3062 renderer: undefined,
3065 // @section Animation Options
3066 // @option zoomAnimation: Boolean = true
3067 // Whether the map zoom animation is enabled. By default it's enabled
3068 // in all browsers that support CSS3 Transitions except Android.
3069 zoomAnimation: true,
3071 // @option zoomAnimationThreshold: Number = 4
3072 // Won't animate zoom if the zoom difference exceeds this value.
3073 zoomAnimationThreshold: 4,
3075 // @option fadeAnimation: Boolean = true
3076 // Whether the tile fade animation is enabled. By default it's enabled
3077 // in all browsers that support CSS3 Transitions except Android.
3078 fadeAnimation: true,
3080 // @option markerZoomAnimation: Boolean = true
3081 // Whether markers animate their zoom with the zoom animation, if disabled
3082 // they will disappear for the length of the animation. By default it's
3083 // enabled in all browsers that support CSS3 Transitions except Android.
3084 markerZoomAnimation: true,
3086 // @option transform3DLimit: Number = 2^23
3087 // Defines the maximum size of a CSS translation transform. The default
3088 // value should not be changed unless a web browser positions layers in
3089 // the wrong place after doing a large `panBy`.
3090 transform3DLimit: 8388608, // Precision limit of a 32-bit float
3092 // @section Interaction Options
3093 // @option zoomSnap: Number = 1
3094 // Forces the map's zoom level to always be a multiple of this, particularly
3095 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
3096 // By default, the zoom level snaps to the nearest integer; lower values
3097 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
3098 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
3101 // @option zoomDelta: Number = 1
3102 // Controls how much the map's zoom level will change after a
3103 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
3104 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
3105 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
3108 // @option trackResize: Boolean = true
3109 // Whether the map automatically handles browser window resize to update itself.
3113 initialize: function (id, options) { // (HTMLElement or String, Object)
3114 options = setOptions(this, options);
3116 this._initContainer(id);
3119 // hack for https://github.com/Leaflet/Leaflet/issues/1980
3120 this._onResize = bind(this._onResize, this);
3124 if (options.maxBounds) {
3125 this.setMaxBounds(options.maxBounds);
3128 if (options.zoom !== undefined) {
3129 this._zoom = this._limitZoom(options.zoom);
3132 if (options.center && options.zoom !== undefined) {
3133 this.setView(toLatLng(options.center), options.zoom, {reset: true});
3136 this._handlers = [];
3138 this._zoomBoundLayers = {};
3139 this._sizeChanged = true;
3141 this.callInitHooks();
3143 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
3144 this._zoomAnimated = TRANSITION && any3d && !mobileOpera &&
3145 this.options.zoomAnimation;
3147 // zoom transitions run with the same duration for all layers, so if one of transitionend events
3148 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
3149 if (this._zoomAnimated) {
3150 this._createAnimProxy();
3151 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
3154 this._addLayers(this.options.layers);
3158 // @section Methods for modifying map state
3160 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
3161 // Sets the view of the map (geographical center and zoom) with the given
3162 // animation options.
3163 setView: function (center, zoom, options) {
3165 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
3166 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
3167 options = options || {};
3171 if (this._loaded && !options.reset && options !== true) {
3173 if (options.animate !== undefined) {
3174 options.zoom = extend({animate: options.animate}, options.zoom);
3175 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
3178 // try animating pan or zoom
3179 var moved = (this._zoom !== zoom) ?
3180 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
3181 this._tryAnimatedPan(center, options.pan);
3184 // prevent resize handler call, the view will refresh after animation anyway
3185 clearTimeout(this._sizeTimer);
3190 // animation didn't start, just reset the map view
3191 this._resetView(center, zoom);
3196 // @method setZoom(zoom: Number, options?: Zoom/pan options): this
3197 // Sets the zoom of the map.
3198 setZoom: function (zoom, options) {
3199 if (!this._loaded) {
3203 return this.setView(this.getCenter(), zoom, {zoom: options});
3206 // @method zoomIn(delta?: Number, options?: Zoom options): this
3207 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3208 zoomIn: function (delta, options) {
3209 delta = delta || (any3d ? this.options.zoomDelta : 1);
3210 return this.setZoom(this._zoom + delta, options);
3213 // @method zoomOut(delta?: Number, options?: Zoom options): this
3214 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3215 zoomOut: function (delta, options) {
3216 delta = delta || (any3d ? this.options.zoomDelta : 1);
3217 return this.setZoom(this._zoom - delta, options);
3220 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
3221 // Zooms the map while keeping a specified geographical point on the map
3222 // stationary (e.g. used internally for scroll zoom and double-click zoom).
3224 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
3225 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
3226 setZoomAround: function (latlng, zoom, options) {
3227 var scale = this.getZoomScale(zoom),
3228 viewHalf = this.getSize().divideBy(2),
3229 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
3231 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
3232 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
3234 return this.setView(newCenter, zoom, {zoom: options});
3237 _getBoundsCenterZoom: function (bounds, options) {
3239 options = options || {};
3240 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
3242 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3243 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3245 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
3247 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
3249 if (zoom === Infinity) {
3251 center: bounds.getCenter(),
3256 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
3258 swPoint = this.project(bounds.getSouthWest(), zoom),
3259 nePoint = this.project(bounds.getNorthEast(), zoom),
3260 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
3268 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
3269 // Sets a map view that contains the given geographical bounds with the
3270 // maximum zoom level possible.
3271 fitBounds: function (bounds, options) {
3273 bounds = toLatLngBounds(bounds);
3275 if (!bounds.isValid()) {
3276 throw new Error('Bounds are not valid.');
3279 var target = this._getBoundsCenterZoom(bounds, options);
3280 return this.setView(target.center, target.zoom, options);
3283 // @method fitWorld(options?: fitBounds options): this
3284 // Sets a map view that mostly contains the whole world with the maximum
3285 // zoom level possible.
3286 fitWorld: function (options) {
3287 return this.fitBounds([[-90, -180], [90, 180]], options);
3290 // @method panTo(latlng: LatLng, options?: Pan options): this
3291 // Pans the map to a given center.
3292 panTo: function (center, options) { // (LatLng)
3293 return this.setView(center, this._zoom, {pan: options});
3296 // @method panBy(offset: Point, options?: Pan options): this
3297 // Pans the map by a given number of pixels (animated).
3298 panBy: function (offset, options) {
3299 offset = toPoint(offset).round();
3300 options = options || {};
3302 if (!offset.x && !offset.y) {
3303 return this.fire('moveend');
3305 // If we pan too far, Chrome gets issues with tiles
3306 // and makes them disappear or appear in the wrong place (slightly offset) #2602
3307 if (options.animate !== true && !this.getSize().contains(offset)) {
3308 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
3312 if (!this._panAnim) {
3313 this._panAnim = new PosAnimation();
3316 'step': this._onPanTransitionStep,
3317 'end': this._onPanTransitionEnd
3321 // don't fire movestart if animating inertia
3322 if (!options.noMoveStart) {
3323 this.fire('movestart');
3326 // animate pan unless animate: false specified
3327 if (options.animate !== false) {
3328 addClass(this._mapPane, 'leaflet-pan-anim');
3330 var newPos = this._getMapPanePos().subtract(offset).round();
3331 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
3333 this._rawPanBy(offset);
3334 this.fire('move').fire('moveend');
3340 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
3341 // Sets the view of the map (geographical center and zoom) performing a smooth
3342 // pan-zoom animation.
3343 flyTo: function (targetCenter, targetZoom, options) {
3345 options = options || {};
3346 if (options.animate === false || !any3d) {
3347 return this.setView(targetCenter, targetZoom, options);
3352 var from = this.project(this.getCenter()),
3353 to = this.project(targetCenter),
3354 size = this.getSize(),
3355 startZoom = this._zoom;
3357 targetCenter = toLatLng(targetCenter);
3358 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
3360 var w0 = Math.max(size.x, size.y),
3361 w1 = w0 * this.getZoomScale(startZoom, targetZoom),
3362 u1 = (to.distanceTo(from)) || 1,
3367 var s1 = i ? -1 : 1,
3369 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
3370 b1 = 2 * s2 * rho2 * u1,
3372 sq = Math.sqrt(b * b + 1) - b;
3374 // workaround for floating point precision bug when sq = 0, log = -Infinite,
3375 // thus triggering an infinite loop in flyTo
3376 var log = sq < 0.000000001 ? -18 : Math.log(sq);
3381 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
3382 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
3383 function tanh(n) { return sinh(n) / cosh(n); }
3387 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
3388 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
3390 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
3392 var start = Date.now(),
3393 S = (r(1) - r0) / rho,
3394 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
3397 var t = (Date.now() - start) / duration,
3401 this._flyToFrame = requestAnimFrame(frame, this);
3404 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
3405 this.getScaleZoom(w0 / w(s), startZoom),
3410 ._move(targetCenter, targetZoom)
3415 this._moveStart(true, options.noMoveStart);
3421 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
3422 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
3423 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
3424 flyToBounds: function (bounds, options) {
3425 var target = this._getBoundsCenterZoom(bounds, options);
3426 return this.flyTo(target.center, target.zoom, options);
3429 // @method setMaxBounds(bounds: Bounds): this
3430 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
3431 setMaxBounds: function (bounds) {
3432 bounds = toLatLngBounds(bounds);
3434 if (!bounds.isValid()) {
3435 this.options.maxBounds = null;
3436 return this.off('moveend', this._panInsideMaxBounds);
3437 } else if (this.options.maxBounds) {
3438 this.off('moveend', this._panInsideMaxBounds);
3441 this.options.maxBounds = bounds;
3444 this._panInsideMaxBounds();
3447 return this.on('moveend', this._panInsideMaxBounds);
3450 // @method setMinZoom(zoom: Number): this
3451 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
3452 setMinZoom: function (zoom) {
3453 var oldZoom = this.options.minZoom;
3454 this.options.minZoom = zoom;
3456 if (this._loaded && oldZoom !== zoom) {
3457 this.fire('zoomlevelschange');
3459 if (this.getZoom() < this.options.minZoom) {
3460 return this.setZoom(zoom);
3467 // @method setMaxZoom(zoom: Number): this
3468 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
3469 setMaxZoom: function (zoom) {
3470 var oldZoom = this.options.maxZoom;
3471 this.options.maxZoom = zoom;
3473 if (this._loaded && oldZoom !== zoom) {
3474 this.fire('zoomlevelschange');
3476 if (this.getZoom() > this.options.maxZoom) {
3477 return this.setZoom(zoom);
3484 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
3485 // Pans the map to the closest view that would lie inside the given bounds (if it's not already), controlling the animation using the options specific, if any.
3486 panInsideBounds: function (bounds, options) {
3487 this._enforcingBounds = true;
3488 var center = this.getCenter(),
3489 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
3491 if (!center.equals(newCenter)) {
3492 this.panTo(newCenter, options);
3495 this._enforcingBounds = false;
3499 // @method invalidateSize(options: Zoom/pan options): this
3500 // Checks if the map container size changed and updates the map if so —
3501 // call it after you've changed the map size dynamically, also animating
3502 // pan by default. If `options.pan` is `false`, panning will not occur.
3503 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
3504 // that it doesn't happen often even if the method is called many
3508 // @method invalidateSize(animate: Boolean): this
3509 // Checks if the map container size changed and updates the map if so —
3510 // call it after you've changed the map size dynamically, also animating
3512 invalidateSize: function (options) {
3513 if (!this._loaded) { return this; }
3518 }, options === true ? {animate: true} : options);
3520 var oldSize = this.getSize();
3521 this._sizeChanged = true;
3522 this._lastCenter = null;
3524 var newSize = this.getSize(),
3525 oldCenter = oldSize.divideBy(2).round(),
3526 newCenter = newSize.divideBy(2).round(),
3527 offset = oldCenter.subtract(newCenter);
3529 if (!offset.x && !offset.y) { return this; }
3531 if (options.animate && options.pan) {
3536 this._rawPanBy(offset);
3541 if (options.debounceMoveend) {
3542 clearTimeout(this._sizeTimer);
3543 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
3545 this.fire('moveend');
3549 // @section Map state change events
3550 // @event resize: ResizeEvent
3551 // Fired when the map is resized.
3552 return this.fire('resize', {
3558 // @section Methods for modifying map state
3559 // @method stop(): this
3560 // Stops the currently running `panTo` or `flyTo` animation, if any.
3562 this.setZoom(this._limitZoom(this._zoom));
3563 if (!this.options.zoomSnap) {
3564 this.fire('viewreset');
3566 return this._stop();
3569 // @section Geolocation methods
3570 // @method locate(options?: Locate options): this
3571 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
3572 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
3573 // and optionally sets the map view to the user's location with respect to
3574 // detection accuracy (or to the world view if geolocation failed).
3575 // Note that, if your page doesn't use HTTPS, this method will fail in
3576 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
3577 // See `Locate options` for more details.
3578 locate: function (options) {
3580 options = this._locateOptions = extend({
3584 // maxZoom: <Number>
3586 // enableHighAccuracy: false
3589 if (!('geolocation' in navigator)) {
3590 this._handleGeolocationError({
3592 message: 'Geolocation not supported.'
3597 var onResponse = bind(this._handleGeolocationResponse, this),
3598 onError = bind(this._handleGeolocationError, this);
3600 if (options.watch) {
3601 this._locationWatchId =
3602 navigator.geolocation.watchPosition(onResponse, onError, options);
3604 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
3609 // @method stopLocate(): this
3610 // Stops watching location previously initiated by `map.locate({watch: true})`
3611 // and aborts resetting the map view if map.locate was called with
3612 // `{setView: true}`.
3613 stopLocate: function () {
3614 if (navigator.geolocation && navigator.geolocation.clearWatch) {
3615 navigator.geolocation.clearWatch(this._locationWatchId);
3617 if (this._locateOptions) {
3618 this._locateOptions.setView = false;
3623 _handleGeolocationError: function (error) {
3625 message = error.message ||
3626 (c === 1 ? 'permission denied' :
3627 (c === 2 ? 'position unavailable' : 'timeout'));
3629 if (this._locateOptions.setView && !this._loaded) {
3633 // @section Location events
3634 // @event locationerror: ErrorEvent
3635 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
3636 this.fire('locationerror', {
3638 message: 'Geolocation error: ' + message + '.'
3642 _handleGeolocationResponse: function (pos) {
3643 var lat = pos.coords.latitude,
3644 lng = pos.coords.longitude,
3645 latlng = new LatLng(lat, lng),
3646 bounds = latlng.toBounds(pos.coords.accuracy * 2),
3647 options = this._locateOptions;
3649 if (options.setView) {
3650 var zoom = this.getBoundsZoom(bounds);
3651 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
3657 timestamp: pos.timestamp
3660 for (var i in pos.coords) {
3661 if (typeof pos.coords[i] === 'number') {
3662 data[i] = pos.coords[i];
3666 // @event locationfound: LocationEvent
3667 // Fired when geolocation (using the [`locate`](#map-locate) method)
3668 // went successfully.
3669 this.fire('locationfound', data);
3672 // TODO Appropriate docs section?
3673 // @section Other Methods
3674 // @method addHandler(name: String, HandlerClass: Function): this
3675 // Adds a new `Handler` to the map, given its name and constructor function.
3676 addHandler: function (name, HandlerClass) {
3677 if (!HandlerClass) { return this; }
3679 var handler = this[name] = new HandlerClass(this);
3681 this._handlers.push(handler);
3683 if (this.options[name]) {
3690 // @method remove(): this
3691 // Destroys the map and clears all related event listeners.
3692 remove: function () {
3694 this._initEvents(true);
3696 if (this._containerId !== this._container._leaflet_id) {
3697 throw new Error('Map container is being reused by another instance');
3701 // throws error in IE6-8
3702 delete this._container._leaflet_id;
3703 delete this._containerId;
3706 this._container._leaflet_id = undefined;
3708 this._containerId = undefined;
3711 if (this._locationWatchId !== undefined) {
3717 remove(this._mapPane);
3719 if (this._clearControlPos) {
3720 this._clearControlPos();
3722 if (this._resizeRequest) {
3723 cancelAnimFrame(this._resizeRequest);
3724 this._resizeRequest = null;
3727 this._clearHandlers();
3730 // @section Map state change events
3731 // @event unload: Event
3732 // Fired when the map is destroyed with [remove](#map-remove) method.
3733 this.fire('unload');
3737 for (i in this._layers) {
3738 this._layers[i].remove();
3740 for (i in this._panes) {
3741 remove(this._panes[i]);
3746 delete this._mapPane;
3747 delete this._renderer;
3752 // @section Other Methods
3753 // @method createPane(name: String, container?: HTMLElement): HTMLElement
3754 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
3755 // then returns it. The pane is created as a child of `container`, or
3756 // as a child of the main map pane if not set.
3757 createPane: function (name, container) {
3758 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3759 pane = create$1('div', className, container || this._mapPane);
3762 this._panes[name] = pane;
3767 // @section Methods for Getting Map State
3769 // @method getCenter(): LatLng
3770 // Returns the geographical center of the map view
3771 getCenter: function () {
3772 this._checkIfLoaded();
3774 if (this._lastCenter && !this._moved()) {
3775 return this._lastCenter;
3777 return this.layerPointToLatLng(this._getCenterLayerPoint());
3780 // @method getZoom(): Number
3781 // Returns the current zoom level of the map view
3782 getZoom: function () {
3786 // @method getBounds(): LatLngBounds
3787 // Returns the geographical bounds visible in the current map view
3788 getBounds: function () {
3789 var bounds = this.getPixelBounds(),
3790 sw = this.unproject(bounds.getBottomLeft()),
3791 ne = this.unproject(bounds.getTopRight());
3793 return new LatLngBounds(sw, ne);
3796 // @method getMinZoom(): Number
3797 // Returns the minimum zoom level of the map (if set in the `minZoom` option of the map or of any layers), or `0` by default.
3798 getMinZoom: function () {
3799 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3802 // @method getMaxZoom(): Number
3803 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3804 getMaxZoom: function () {
3805 return this.options.maxZoom === undefined ?
3806 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3807 this.options.maxZoom;
3810 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
3811 // Returns the maximum zoom level on which the given bounds fit to the map
3812 // view in its entirety. If `inside` (optional) is set to `true`, the method
3813 // instead returns the minimum zoom level on which the map view fits into
3814 // the given bounds in its entirety.
3815 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3816 bounds = toLatLngBounds(bounds);
3817 padding = toPoint(padding || [0, 0]);
3819 var zoom = this.getZoom() || 0,
3820 min = this.getMinZoom(),
3821 max = this.getMaxZoom(),
3822 nw = bounds.getNorthWest(),
3823 se = bounds.getSouthEast(),
3824 size = this.getSize().subtract(padding),
3825 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3826 snap = any3d ? this.options.zoomSnap : 1,
3827 scalex = size.x / boundsSize.x,
3828 scaley = size.y / boundsSize.y,
3829 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
3831 zoom = this.getScaleZoom(scale, zoom);
3834 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3835 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3838 return Math.max(min, Math.min(max, zoom));
3841 // @method getSize(): Point
3842 // Returns the current size of the map container (in pixels).
3843 getSize: function () {
3844 if (!this._size || this._sizeChanged) {
3845 this._size = new Point(
3846 this._container.clientWidth || 0,
3847 this._container.clientHeight || 0);
3849 this._sizeChanged = false;
3851 return this._size.clone();
3854 // @method getPixelBounds(): Bounds
3855 // Returns the bounds of the current map view in projected pixel
3856 // coordinates (sometimes useful in layer and overlay implementations).
3857 getPixelBounds: function (center, zoom) {
3858 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3859 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3862 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3863 // the map pane? "left point of the map layer" can be confusing, specially
3864 // since there can be negative offsets.
3865 // @method getPixelOrigin(): Point
3866 // Returns the projected pixel coordinates of the top left point of
3867 // the map layer (useful in custom layer and overlay implementations).
3868 getPixelOrigin: function () {
3869 this._checkIfLoaded();
3870 return this._pixelOrigin;
3873 // @method getPixelWorldBounds(zoom?: Number): Bounds
3874 // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3875 // If `zoom` is omitted, the map's current zoom level is used.
3876 getPixelWorldBounds: function (zoom) {
3877 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3880 // @section Other Methods
3882 // @method getPane(pane: String|HTMLElement): HTMLElement
3883 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3884 getPane: function (pane) {
3885 return typeof pane === 'string' ? this._panes[pane] : pane;
3888 // @method getPanes(): Object
3889 // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3890 // the panes as values.
3891 getPanes: function () {
3895 // @method getContainer: HTMLElement
3896 // Returns the HTML element that contains the map.
3897 getContainer: function () {
3898 return this._container;
3902 // @section Conversion Methods
3904 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3905 // Returns the scale factor to be applied to a map transition from zoom level
3906 // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3907 getZoomScale: function (toZoom, fromZoom) {
3908 // TODO replace with universal implementation after refactoring projections
3909 var crs = this.options.crs;
3910 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3911 return crs.scale(toZoom) / crs.scale(fromZoom);
3914 // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3915 // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3916 // level and everything is scaled by a factor of `scale`. Inverse of
3917 // [`getZoomScale`](#map-getZoomScale).
3918 getScaleZoom: function (scale, fromZoom) {
3919 var crs = this.options.crs;
3920 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3921 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3922 return isNaN(zoom) ? Infinity : zoom;
3925 // @method project(latlng: LatLng, zoom: Number): Point
3926 // Projects a geographical coordinate `LatLng` according to the projection
3927 // of the map's CRS, then scales it according to `zoom` and the CRS's
3928 // `Transformation`. The result is pixel coordinate relative to
3930 project: function (latlng, zoom) {
3931 zoom = zoom === undefined ? this._zoom : zoom;
3932 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
3935 // @method unproject(point: Point, zoom: Number): LatLng
3936 // Inverse of [`project`](#map-project).
3937 unproject: function (point, zoom) {
3938 zoom = zoom === undefined ? this._zoom : zoom;
3939 return this.options.crs.pointToLatLng(toPoint(point), zoom);
3942 // @method layerPointToLatLng(point: Point): LatLng
3943 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3944 // returns the corresponding geographical coordinate (for the current zoom level).
3945 layerPointToLatLng: function (point) {
3946 var projectedPoint = toPoint(point).add(this.getPixelOrigin());
3947 return this.unproject(projectedPoint);
3950 // @method latLngToLayerPoint(latlng: LatLng): Point
3951 // Given a geographical coordinate, returns the corresponding pixel coordinate
3952 // relative to the [origin pixel](#map-getpixelorigin).
3953 latLngToLayerPoint: function (latlng) {
3954 var projectedPoint = this.project(toLatLng(latlng))._round();
3955 return projectedPoint._subtract(this.getPixelOrigin());
3958 // @method wrapLatLng(latlng: LatLng): LatLng
3959 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
3960 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
3962 // By default this means longitude is wrapped around the dateline so its
3963 // value is between -180 and +180 degrees.
3964 wrapLatLng: function (latlng) {
3965 return this.options.crs.wrapLatLng(toLatLng(latlng));
3968 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
3969 // Returns a `LatLngBounds` with the same size as the given one, ensuring that
3970 // its center is within the CRS's bounds.
3971 // By default this means the center longitude is wrapped around the dateline so its
3972 // value is between -180 and +180 degrees, and the majority of the bounds
3973 // overlaps the CRS's bounds.
3974 wrapLatLngBounds: function (latlng) {
3975 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
3978 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
3979 // Returns the distance between two geographical coordinates according to
3980 // the map's CRS. By default this measures distance in meters.
3981 distance: function (latlng1, latlng2) {
3982 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
3985 // @method containerPointToLayerPoint(point: Point): Point
3986 // Given a pixel coordinate relative to the map container, returns the corresponding
3987 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
3988 containerPointToLayerPoint: function (point) { // (Point)
3989 return toPoint(point).subtract(this._getMapPanePos());
3992 // @method layerPointToContainerPoint(point: Point): Point
3993 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3994 // returns the corresponding pixel coordinate relative to the map container.
3995 layerPointToContainerPoint: function (point) { // (Point)
3996 return toPoint(point).add(this._getMapPanePos());
3999 // @method containerPointToLatLng(point: Point): LatLng
4000 // Given a pixel coordinate relative to the map container, returns
4001 // the corresponding geographical coordinate (for the current zoom level).
4002 containerPointToLatLng: function (point) {
4003 var layerPoint = this.containerPointToLayerPoint(toPoint(point));
4004 return this.layerPointToLatLng(layerPoint);
4007 // @method latLngToContainerPoint(latlng: LatLng): Point
4008 // Given a geographical coordinate, returns the corresponding pixel coordinate
4009 // relative to the map container.
4010 latLngToContainerPoint: function (latlng) {
4011 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
4014 // @method mouseEventToContainerPoint(ev: MouseEvent): Point
4015 // Given a MouseEvent object, returns the pixel coordinate relative to the
4016 // map container where the event took place.
4017 mouseEventToContainerPoint: function (e) {
4018 return getMousePosition(e, this._container);
4021 // @method mouseEventToLayerPoint(ev: MouseEvent): Point
4022 // Given a MouseEvent object, returns the pixel coordinate relative to
4023 // the [origin pixel](#map-getpixelorigin) where the event took place.
4024 mouseEventToLayerPoint: function (e) {
4025 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
4028 // @method mouseEventToLatLng(ev: MouseEvent): LatLng
4029 // Given a MouseEvent object, returns geographical coordinate where the
4030 // event took place.
4031 mouseEventToLatLng: function (e) { // (MouseEvent)
4032 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
4036 // map initialization methods
4038 _initContainer: function (id) {
4039 var container = this._container = get(id);
4042 throw new Error('Map container not found.');
4043 } else if (container._leaflet_id) {
4044 throw new Error('Map container is already initialized.');
4047 on(container, 'scroll', this._onScroll, this);
4048 this._containerId = stamp(container);
4051 _initLayout: function () {
4052 var container = this._container;
4054 this._fadeAnimated = this.options.fadeAnimation && any3d;
4056 addClass(container, 'leaflet-container' +
4057 (touch ? ' leaflet-touch' : '') +
4058 (retina ? ' leaflet-retina' : '') +
4059 (ielt9 ? ' leaflet-oldie' : '') +
4060 (safari ? ' leaflet-safari' : '') +
4061 (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
4063 var position = getStyle(container, 'position');
4065 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
4066 container.style.position = 'relative';
4071 if (this._initControlPos) {
4072 this._initControlPos();
4076 _initPanes: function () {
4077 var panes = this._panes = {};
4078 this._paneRenderers = {};
4082 // Panes are DOM elements used to control the ordering of layers on the map. You
4083 // can access panes with [`map.getPane`](#map-getpane) or
4084 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
4085 // [`map.createPane`](#map-createpane) method.
4087 // Every map has the following default panes that differ only in zIndex.
4089 // @pane mapPane: HTMLElement = 'auto'
4090 // Pane that contains all other map panes
4092 this._mapPane = this.createPane('mapPane', this._container);
4093 setPosition(this._mapPane, new Point(0, 0));
4095 // @pane tilePane: HTMLElement = 200
4096 // Pane for `GridLayer`s and `TileLayer`s
4097 this.createPane('tilePane');
4098 // @pane overlayPane: HTMLElement = 400
4099 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
4100 this.createPane('shadowPane');
4101 // @pane shadowPane: HTMLElement = 500
4102 // Pane for overlay shadows (e.g. `Marker` shadows)
4103 this.createPane('overlayPane');
4104 // @pane markerPane: HTMLElement = 600
4105 // Pane for `Icon`s of `Marker`s
4106 this.createPane('markerPane');
4107 // @pane tooltipPane: HTMLElement = 650
4108 // Pane for `Tooltip`s.
4109 this.createPane('tooltipPane');
4110 // @pane popupPane: HTMLElement = 700
4111 // Pane for `Popup`s.
4112 this.createPane('popupPane');
4114 if (!this.options.markerZoomAnimation) {
4115 addClass(panes.markerPane, 'leaflet-zoom-hide');
4116 addClass(panes.shadowPane, 'leaflet-zoom-hide');
4121 // private methods that modify map state
4123 // @section Map state change events
4124 _resetView: function (center, zoom) {
4125 setPosition(this._mapPane, new Point(0, 0));
4127 var loading = !this._loaded;
4128 this._loaded = true;
4129 zoom = this._limitZoom(zoom);
4131 this.fire('viewprereset');
4133 var zoomChanged = this._zoom !== zoom;
4135 ._moveStart(zoomChanged, false)
4136 ._move(center, zoom)
4137 ._moveEnd(zoomChanged);
4139 // @event viewreset: Event
4140 // Fired when the map needs to redraw its content (this usually happens
4141 // on map zoom or load). Very useful for creating custom overlays.
4142 this.fire('viewreset');
4144 // @event load: Event
4145 // Fired when the map is initialized (when its center and zoom are set
4146 // for the first time).
4152 _moveStart: function (zoomChanged, noMoveStart) {
4153 // @event zoomstart: Event
4154 // Fired when the map zoom is about to change (e.g. before zoom animation).
4155 // @event movestart: Event
4156 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
4158 this.fire('zoomstart');
4161 this.fire('movestart');
4166 _move: function (center, zoom, data) {
4167 if (zoom === undefined) {
4170 var zoomChanged = this._zoom !== zoom;
4173 this._lastCenter = center;
4174 this._pixelOrigin = this._getNewPixelOrigin(center);
4176 // @event zoom: Event
4177 // Fired repeatedly during any change in zoom level, including zoom
4178 // and fly animations.
4179 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530
4180 this.fire('zoom', data);
4183 // @event move: Event
4184 // Fired repeatedly during any movement of the map, including pan and
4186 return this.fire('move', data);
4189 _moveEnd: function (zoomChanged) {
4190 // @event zoomend: Event
4191 // Fired when the map has changed, after any animations.
4193 this.fire('zoomend');
4196 // @event moveend: Event
4197 // Fired when the center of the map stops changing (e.g. user stopped
4198 // dragging the map).
4199 return this.fire('moveend');
4202 _stop: function () {
4203 cancelAnimFrame(this._flyToFrame);
4204 if (this._panAnim) {
4205 this._panAnim.stop();
4210 _rawPanBy: function (offset) {
4211 setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
4214 _getZoomSpan: function () {
4215 return this.getMaxZoom() - this.getMinZoom();
4218 _panInsideMaxBounds: function () {
4219 if (!this._enforcingBounds) {
4220 this.panInsideBounds(this.options.maxBounds);
4224 _checkIfLoaded: function () {
4225 if (!this._loaded) {
4226 throw new Error('Set map center and zoom first.');
4230 // DOM event handling
4232 // @section Interaction events
4233 _initEvents: function (remove$$1) {
4235 this._targets[stamp(this._container)] = this;
4237 var onOff = remove$$1 ? off : on;
4239 // @event click: MouseEvent
4240 // Fired when the user clicks (or taps) the map.
4241 // @event dblclick: MouseEvent
4242 // Fired when the user double-clicks (or double-taps) the map.
4243 // @event mousedown: MouseEvent
4244 // Fired when the user pushes the mouse button on the map.
4245 // @event mouseup: MouseEvent
4246 // Fired when the user releases the mouse button on the map.
4247 // @event mouseover: MouseEvent
4248 // Fired when the mouse enters the map.
4249 // @event mouseout: MouseEvent
4250 // Fired when the mouse leaves the map.
4251 // @event mousemove: MouseEvent
4252 // Fired while the mouse moves over the map.
4253 // @event contextmenu: MouseEvent
4254 // Fired when the user pushes the right mouse button on the map, prevents
4255 // default browser context menu from showing if there are listeners on
4256 // this event. Also fired on mobile when the user holds a single touch
4257 // for a second (also called long press).
4258 // @event keypress: KeyboardEvent
4259 // Fired when the user presses a key from the keyboard while the map is focused.
4260 onOff(this._container, 'click dblclick mousedown mouseup ' +
4261 'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);
4263 if (this.options.trackResize) {
4264 onOff(window, 'resize', this._onResize, this);
4267 if (any3d && this.options.transform3DLimit) {
4268 (remove$$1 ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
4272 _onResize: function () {
4273 cancelAnimFrame(this._resizeRequest);
4274 this._resizeRequest = requestAnimFrame(
4275 function () { this.invalidateSize({debounceMoveend: true}); }, this);
4278 _onScroll: function () {
4279 this._container.scrollTop = 0;
4280 this._container.scrollLeft = 0;
4283 _onMoveEnd: function () {
4284 var pos = this._getMapPanePos();
4285 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
4286 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
4287 // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
4288 this._resetView(this.getCenter(), this.getZoom());
4292 _findEventTargets: function (e, type) {
4295 isHover = type === 'mouseout' || type === 'mouseover',
4296 src = e.target || e.srcElement,
4300 target = this._targets[stamp(src)];
4301 if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
4302 // Prevent firing click after you just dragged an object.
4306 if (target && target.listens(type, true)) {
4307 if (isHover && !isExternalTarget(src, e)) { break; }
4308 targets.push(target);
4309 if (isHover) { break; }
4311 if (src === this._container) { break; }
4312 src = src.parentNode;
4314 if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) {
4320 _handleDOMEvent: function (e) {
4321 if (!this._loaded || skipped(e)) { return; }
4325 if (type === 'mousedown' || type === 'keypress') {
4326 // prevents outline when clicking on keyboard-focusable element
4327 preventOutline(e.target || e.srcElement);
4330 this._fireDOMEvent(e, type);
4333 _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
4335 _fireDOMEvent: function (e, type, targets) {
4337 if (e.type === 'click') {
4338 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
4339 // @event preclick: MouseEvent
4340 // Fired before mouse click on the map (sometimes useful when you
4341 // want something to happen on click before any existing click
4342 // handlers start running).
4343 var synth = extend({}, e);
4344 synth.type = 'preclick';
4345 this._fireDOMEvent(synth, synth.type, targets);
4348 if (e._stopped) { return; }
4350 // Find the layer the event is propagating from and its parents.
4351 targets = (targets || []).concat(this._findEventTargets(e, type));
4353 if (!targets.length) { return; }
4355 var target = targets[0];
4356 if (type === 'contextmenu' && target.listens(type, true)) {
4364 if (e.type !== 'keypress') {
4365 var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
4366 data.containerPoint = isMarker ?
4367 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
4368 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
4369 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
4372 for (var i = 0; i < targets.length; i++) {
4373 targets[i].fire(type, data, true);
4374 if (data.originalEvent._stopped ||
4375 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
4379 _draggableMoved: function (obj) {
4380 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
4381 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
4384 _clearHandlers: function () {
4385 for (var i = 0, len = this._handlers.length; i < len; i++) {
4386 this._handlers[i].disable();
4390 // @section Other Methods
4392 // @method whenReady(fn: Function, context?: Object): this
4393 // Runs the given function `fn` when the map gets initialized with
4394 // a view (center and zoom) and at least one layer, or immediately
4395 // if it's already initialized, optionally passing a function context.
4396 whenReady: function (callback, context) {
4398 callback.call(context || this, {target: this});
4400 this.on('load', callback, context);
4406 // private methods for getting map state
4408 _getMapPanePos: function () {
4409 return getPosition(this._mapPane) || new Point(0, 0);
4412 _moved: function () {
4413 var pos = this._getMapPanePos();
4414 return pos && !pos.equals([0, 0]);
4417 _getTopLeftPoint: function (center, zoom) {
4418 var pixelOrigin = center && zoom !== undefined ?
4419 this._getNewPixelOrigin(center, zoom) :
4420 this.getPixelOrigin();
4421 return pixelOrigin.subtract(this._getMapPanePos());
4424 _getNewPixelOrigin: function (center, zoom) {
4425 var viewHalf = this.getSize()._divideBy(2);
4426 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
4429 _latLngToNewLayerPoint: function (latlng, zoom, center) {
4430 var topLeft = this._getNewPixelOrigin(center, zoom);
4431 return this.project(latlng, zoom)._subtract(topLeft);
4434 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
4435 var topLeft = this._getNewPixelOrigin(center, zoom);
4437 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
4438 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
4439 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
4440 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
4444 // layer point of the current center
4445 _getCenterLayerPoint: function () {
4446 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
4449 // offset of the specified place to the current center in pixels
4450 _getCenterOffset: function (latlng) {
4451 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
4454 // adjust center for view to get inside bounds
4455 _limitCenter: function (center, zoom, bounds) {
4457 if (!bounds) { return center; }
4459 var centerPoint = this.project(center, zoom),
4460 viewHalf = this.getSize().divideBy(2),
4461 viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
4462 offset = this._getBoundsOffset(viewBounds, bounds, zoom);
4464 // If offset is less than a pixel, ignore.
4465 // This prevents unstable projections from getting into
4466 // an infinite loop of tiny offsets.
4467 if (offset.round().equals([0, 0])) {
4471 return this.unproject(centerPoint.add(offset), zoom);
4474 // adjust offset for view to get inside bounds
4475 _limitOffset: function (offset, bounds) {
4476 if (!bounds) { return offset; }
4478 var viewBounds = this.getPixelBounds(),
4479 newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
4481 return offset.add(this._getBoundsOffset(newBounds, bounds));
4484 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
4485 _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
4486 var projectedMaxBounds = toBounds(
4487 this.project(maxBounds.getNorthEast(), zoom),
4488 this.project(maxBounds.getSouthWest(), zoom)
4490 minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
4491 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
4493 dx = this._rebound(minOffset.x, -maxOffset.x),
4494 dy = this._rebound(minOffset.y, -maxOffset.y);
4496 return new Point(dx, dy);
4499 _rebound: function (left, right) {
4500 return left + right > 0 ?
4501 Math.round(left - right) / 2 :
4502 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
4505 _limitZoom: function (zoom) {
4506 var min = this.getMinZoom(),
4507 max = this.getMaxZoom(),
4508 snap = any3d ? this.options.zoomSnap : 1;
4510 zoom = Math.round(zoom / snap) * snap;
4512 return Math.max(min, Math.min(max, zoom));
4515 _onPanTransitionStep: function () {
4519 _onPanTransitionEnd: function () {
4520 removeClass(this._mapPane, 'leaflet-pan-anim');
4521 this.fire('moveend');
4524 _tryAnimatedPan: function (center, options) {
4525 // difference between the new and current centers in pixels
4526 var offset = this._getCenterOffset(center)._trunc();
4528 // don't animate too far unless animate: true specified in options
4529 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
4531 this.panBy(offset, options);
4536 _createAnimProxy: function () {
4538 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
4539 this._panes.mapPane.appendChild(proxy);
4541 this.on('zoomanim', function (e) {
4542 var prop = TRANSFORM,
4543 transform = this._proxy.style[prop];
4545 setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
4547 // workaround for case when transform is the same and so transitionend event is not fired
4548 if (transform === this._proxy.style[prop] && this._animatingZoom) {
4549 this._onZoomTransitionEnd();
4553 this.on('load moveend', function () {
4554 var c = this.getCenter(),
4556 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
4559 this._on('unload', this._destroyAnimProxy, this);
4562 _destroyAnimProxy: function () {
4563 remove(this._proxy);
4567 _catchTransitionEnd: function (e) {
4568 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
4569 this._onZoomTransitionEnd();
4573 _nothingToAnimate: function () {
4574 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
4577 _tryAnimatedZoom: function (center, zoom, options) {
4579 if (this._animatingZoom) { return true; }
4581 options = options || {};
4583 // don't animate if disabled, not supported or zoom difference is too large
4584 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
4585 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
4587 // offset is the pixel coords of the zoom origin relative to the current center
4588 var scale = this.getZoomScale(zoom),
4589 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
4591 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
4592 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
4594 requestAnimFrame(function () {
4596 ._moveStart(true, false)
4597 ._animateZoom(center, zoom, true);
4603 _animateZoom: function (center, zoom, startAnim, noUpdate) {
4604 if (!this._mapPane) { return; }
4607 this._animatingZoom = true;
4609 // remember what center/zoom to set after animation
4610 this._animateToCenter = center;
4611 this._animateToZoom = zoom;
4613 addClass(this._mapPane, 'leaflet-zoom-anim');
4616 // @event zoomanim: ZoomAnimEvent
4617 // Fired on every frame of a zoom animation
4618 this.fire('zoomanim', {
4624 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
4625 setTimeout(bind(this._onZoomTransitionEnd, this), 250);
4628 _onZoomTransitionEnd: function () {
4629 if (!this._animatingZoom) { return; }
4631 if (this._mapPane) {
4632 removeClass(this._mapPane, 'leaflet-zoom-anim');
4635 this._animatingZoom = false;
4637 this._move(this._animateToCenter, this._animateToZoom);
4639 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
4640 requestAnimFrame(function () {
4641 this._moveEnd(true);
4648 // @factory L.map(id: String, options?: Map options)
4649 // Instantiates a map object given the DOM ID of a `<div>` element
4650 // and optionally an object literal with `Map options`.
4653 // @factory L.map(el: HTMLElement, options?: Map options)
4654 // Instantiates a map object given an instance of a `<div>` HTML element
4655 // and optionally an object literal with `Map options`.
4656 function createMap(id, options) {
4657 return new Map(id, options);
4665 * L.Control is a base class for implementing map controls. Handles positioning.
4666 * All other controls extend from this class.
4669 var Control = Class.extend({
4671 // @aka Control options
4673 // @option position: String = 'topright'
4674 // The position of the control (one of the map corners). Possible values are `'topleft'`,
4675 // `'topright'`, `'bottomleft'` or `'bottomright'`
4676 position: 'topright'
4679 initialize: function (options) {
4680 setOptions(this, options);
4684 * Classes extending L.Control will inherit the following methods:
4686 * @method getPosition: string
4687 * Returns the position of the control.
4689 getPosition: function () {
4690 return this.options.position;
4693 // @method setPosition(position: string): this
4694 // Sets the position of the control.
4695 setPosition: function (position) {
4696 var map = this._map;
4699 map.removeControl(this);
4702 this.options.position = position;
4705 map.addControl(this);
4711 // @method getContainer: HTMLElement
4712 // Returns the HTMLElement that contains the control.
4713 getContainer: function () {
4714 return this._container;
4717 // @method addTo(map: Map): this
4718 // Adds the control to the given map.
4719 addTo: function (map) {
4723 var container = this._container = this.onAdd(map),
4724 pos = this.getPosition(),
4725 corner = map._controlCorners[pos];
4727 addClass(container, 'leaflet-control');
4729 if (pos.indexOf('bottom') !== -1) {
4730 corner.insertBefore(container, corner.firstChild);
4732 corner.appendChild(container);
4738 // @method remove: this
4739 // Removes the control from the map it is currently active on.
4740 remove: function () {
4745 remove(this._container);
4747 if (this.onRemove) {
4748 this.onRemove(this._map);
4756 _refocusOnMap: function (e) {
4757 // if map exists and event is not a keyboard event
4758 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
4759 this._map.getContainer().focus();
4764 var control = function (options) {
4765 return new Control(options);
4768 /* @section Extension methods
4771 * Every control should extend from `L.Control` and (re-)implement the following methods.
4773 * @method onAdd(map: Map): HTMLElement
4774 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
4776 * @method onRemove(map: Map)
4777 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
4781 * @section Methods for Layers and Controls
4784 // @method addControl(control: Control): this
4785 // Adds the given control to the map
4786 addControl: function (control) {
4787 control.addTo(this);
4791 // @method removeControl(control: Control): this
4792 // Removes the given control from the map
4793 removeControl: function (control) {
4798 _initControlPos: function () {
4799 var corners = this._controlCorners = {},
4801 container = this._controlContainer =
4802 create$1('div', l + 'control-container', this._container);
4804 function createCorner(vSide, hSide) {
4805 var className = l + vSide + ' ' + l + hSide;
4807 corners[vSide + hSide] = create$1('div', className, container);
4810 createCorner('top', 'left');
4811 createCorner('top', 'right');
4812 createCorner('bottom', 'left');
4813 createCorner('bottom', 'right');
4816 _clearControlPos: function () {
4817 for (var i in this._controlCorners) {
4818 remove(this._controlCorners[i]);
4820 remove(this._controlContainer);
4821 delete this._controlCorners;
4822 delete this._controlContainer;
4827 * @class Control.Layers
4828 * @aka L.Control.Layers
4831 * The layers control gives users the ability to switch between different base layers and switch overlays on/off (check out the [detailed example](http://leafletjs.com/examples/layers-control/)). Extends `Control`.
4836 * var baseLayers = {
4838 * "OpenStreetMap": osm
4843 * "Roads": roadsLayer
4846 * L.control.layers(baseLayers, overlays).addTo(map);
4849 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
4853 * "<someName1>": layer1,
4854 * "<someName2>": layer2
4858 * The layer names can contain HTML, which allows you to add additional styling to the items:
4861 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
4865 var Layers = Control.extend({
4867 // @aka Control.Layers options
4869 // @option collapsed: Boolean = true
4870 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
4872 position: 'topright',
4874 // @option autoZIndex: Boolean = true
4875 // If `true`, the control will assign zIndexes in increasing order to all of its layers so that the order is preserved when switching them on/off.
4878 // @option hideSingleBase: Boolean = false
4879 // If `true`, the base layers in the control will be hidden when there is only one.
4880 hideSingleBase: false,
4882 // @option sortLayers: Boolean = false
4883 // Whether to sort the layers. When `false`, layers will keep the order
4884 // in which they were added to the control.
4887 // @option sortFunction: Function = *
4888 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
4889 // that will be used for sorting the layers, when `sortLayers` is `true`.
4890 // The function receives both the `L.Layer` instances and their names, as in
4891 // `sortFunction(layerA, layerB, nameA, nameB)`.
4892 // By default, it sorts layers alphabetically by their name.
4893 sortFunction: function (layerA, layerB, nameA, nameB) {
4894 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
4898 initialize: function (baseLayers, overlays, options) {
4899 setOptions(this, options);
4901 this._layerControlInputs = [];
4903 this._lastZIndex = 0;
4904 this._handlingClick = false;
4906 for (var i in baseLayers) {
4907 this._addLayer(baseLayers[i], i);
4910 for (i in overlays) {
4911 this._addLayer(overlays[i], i, true);
4915 onAdd: function (map) {
4920 map.on('zoomend', this._checkDisabledLayers, this);
4922 for (var i = 0; i < this._layers.length; i++) {
4923 this._layers[i].layer.on('add remove', this._onLayerChange, this);
4926 return this._container;
4929 addTo: function (map) {
4930 Control.prototype.addTo.call(this, map);
4931 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
4932 return this._expandIfNotCollapsed();
4935 onRemove: function () {
4936 this._map.off('zoomend', this._checkDisabledLayers, this);
4938 for (var i = 0; i < this._layers.length; i++) {
4939 this._layers[i].layer.off('add remove', this._onLayerChange, this);
4943 // @method addBaseLayer(layer: Layer, name: String): this
4944 // Adds a base layer (radio button entry) with the given name to the control.
4945 addBaseLayer: function (layer, name) {
4946 this._addLayer(layer, name);
4947 return (this._map) ? this._update() : this;
4950 // @method addOverlay(layer: Layer, name: String): this
4951 // Adds an overlay (checkbox entry) with the given name to the control.
4952 addOverlay: function (layer, name) {
4953 this._addLayer(layer, name, true);
4954 return (this._map) ? this._update() : this;
4957 // @method removeLayer(layer: Layer): this
4958 // Remove the given layer from the control.
4959 removeLayer: function (layer) {
4960 layer.off('add remove', this._onLayerChange, this);
4962 var obj = this._getLayer(stamp(layer));
4964 this._layers.splice(this._layers.indexOf(obj), 1);
4966 return (this._map) ? this._update() : this;
4969 // @method expand(): this
4970 // Expand the control container if collapsed.
4971 expand: function () {
4972 addClass(this._container, 'leaflet-control-layers-expanded');
4973 this._form.style.height = null;
4974 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
4975 if (acceptableHeight < this._form.clientHeight) {
4976 addClass(this._form, 'leaflet-control-layers-scrollbar');
4977 this._form.style.height = acceptableHeight + 'px';
4979 removeClass(this._form, 'leaflet-control-layers-scrollbar');
4981 this._checkDisabledLayers();
4985 // @method collapse(): this
4986 // Collapse the control container if expanded.
4987 collapse: function () {
4988 removeClass(this._container, 'leaflet-control-layers-expanded');
4992 _initLayout: function () {
4993 var className = 'leaflet-control-layers',
4994 container = this._container = create$1('div', className),
4995 collapsed = this.options.collapsed;
4997 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
4998 container.setAttribute('aria-haspopup', true);
5000 disableClickPropagation(container);
5001 disableScrollPropagation(container);
5003 var form = this._form = create$1('form', className + '-list');
5006 this._map.on('click', this.collapse, this);
5010 mouseenter: this.expand,
5011 mouseleave: this.collapse
5016 var link = this._layersLink = create$1('a', className + '-toggle', container);
5018 link.title = 'Layers';
5021 on(link, 'click', stop);
5022 on(link, 'click', this.expand, this);
5024 on(link, 'focus', this.expand, this);
5031 this._baseLayersList = create$1('div', className + '-base', form);
5032 this._separator = create$1('div', className + '-separator', form);
5033 this._overlaysList = create$1('div', className + '-overlays', form);
5035 container.appendChild(form);
5038 _getLayer: function (id) {
5039 for (var i = 0; i < this._layers.length; i++) {
5041 if (this._layers[i] && stamp(this._layers[i].layer) === id) {
5042 return this._layers[i];
5047 _addLayer: function (layer, name, overlay) {
5049 layer.on('add remove', this._onLayerChange, this);
5058 if (this.options.sortLayers) {
5059 this._layers.sort(bind(function (a, b) {
5060 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
5064 if (this.options.autoZIndex && layer.setZIndex) {
5066 layer.setZIndex(this._lastZIndex);
5069 this._expandIfNotCollapsed();
5072 _update: function () {
5073 if (!this._container) { return this; }
5075 empty(this._baseLayersList);
5076 empty(this._overlaysList);
5078 this._layerControlInputs = [];
5079 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
5081 for (i = 0; i < this._layers.length; i++) {
5082 obj = this._layers[i];
5084 overlaysPresent = overlaysPresent || obj.overlay;
5085 baseLayersPresent = baseLayersPresent || !obj.overlay;
5086 baseLayersCount += !obj.overlay ? 1 : 0;
5089 // Hide base layers section if there's only one layer.
5090 if (this.options.hideSingleBase) {
5091 baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
5092 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
5095 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
5100 _onLayerChange: function (e) {
5101 if (!this._handlingClick) {
5105 var obj = this._getLayer(stamp(e.target));
5108 // @section Layer events
5109 // @event baselayerchange: LayersControlEvent
5110 // Fired when the base layer is changed through the [layer control](#control-layers).
5111 // @event overlayadd: LayersControlEvent
5112 // Fired when an overlay is selected through the [layer control](#control-layers).
5113 // @event overlayremove: LayersControlEvent
5114 // Fired when an overlay is deselected through the [layer control](#control-layers).
5115 // @namespace Control.Layers
5116 var type = obj.overlay ?
5117 (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
5118 (e.type === 'add' ? 'baselayerchange' : null);
5121 this._map.fire(type, obj);
5125 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
5126 _createRadioElement: function (name, checked) {
5128 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
5129 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
5131 var radioFragment = document.createElement('div');
5132 radioFragment.innerHTML = radioHtml;
5134 return radioFragment.firstChild;
5137 _addItem: function (obj) {
5138 var label = document.createElement('label'),
5139 checked = this._map.hasLayer(obj.layer),
5143 input = document.createElement('input');
5144 input.type = 'checkbox';
5145 input.className = 'leaflet-control-layers-selector';
5146 input.defaultChecked = checked;
5148 input = this._createRadioElement('leaflet-base-layers', checked);
5151 this._layerControlInputs.push(input);
5152 input.layerId = stamp(obj.layer);
5154 on(input, 'click', this._onInputClick, this);
5156 var name = document.createElement('span');
5157 name.innerHTML = ' ' + obj.name;
5159 // Helps from preventing layer control flicker when checkboxes are disabled
5160 // https://github.com/Leaflet/Leaflet/issues/2771
5161 var holder = document.createElement('div');
5163 label.appendChild(holder);
5164 holder.appendChild(input);
5165 holder.appendChild(name);
5167 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
5168 container.appendChild(label);
5170 this._checkDisabledLayers();
5174 _onInputClick: function () {
5175 var inputs = this._layerControlInputs,
5177 var addedLayers = [],
5180 this._handlingClick = true;
5182 for (var i = inputs.length - 1; i >= 0; i--) {
5184 layer = this._getLayer(input.layerId).layer;
5186 if (input.checked) {
5187 addedLayers.push(layer);
5188 } else if (!input.checked) {
5189 removedLayers.push(layer);
5193 // Bugfix issue 2318: Should remove all old layers before readding new ones
5194 for (i = 0; i < removedLayers.length; i++) {
5195 if (this._map.hasLayer(removedLayers[i])) {
5196 this._map.removeLayer(removedLayers[i]);
5199 for (i = 0; i < addedLayers.length; i++) {
5200 if (!this._map.hasLayer(addedLayers[i])) {
5201 this._map.addLayer(addedLayers[i]);
5205 this._handlingClick = false;
5207 this._refocusOnMap();
5210 _checkDisabledLayers: function () {
5211 var inputs = this._layerControlInputs,
5214 zoom = this._map.getZoom();
5216 for (var i = inputs.length - 1; i >= 0; i--) {
5218 layer = this._getLayer(input.layerId).layer;
5219 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
5220 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
5225 _expandIfNotCollapsed: function () {
5226 if (this._map && !this.options.collapsed) {
5232 _expand: function () {
5233 // Backward compatibility, remove me in 1.1.
5234 return this.expand();
5237 _collapse: function () {
5238 // Backward compatibility, remove me in 1.1.
5239 return this.collapse();
5245 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
5246 // Creates an attribution control with the given layers. Base layers will be switched with radio buttons, while overlays will be switched with checkboxes. Note that all base layers should be passed in the base layers object, but only one should be added to the map during map instantiation.
5247 var layers = function (baseLayers, overlays, options) {
5248 return new Layers(baseLayers, overlays, options);
5252 * @class Control.Zoom
5253 * @aka L.Control.Zoom
5256 * A basic zoom control with two buttons (zoom in and zoom out). It is put on the map by default unless you set its [`zoomControl` option](#map-zoomcontrol) to `false`. Extends `Control`.
5259 var Zoom = Control.extend({
5261 // @aka Control.Zoom options
5263 position: 'topleft',
5265 // @option zoomInText: String = '+'
5266 // The text set on the 'zoom in' button.
5269 // @option zoomInTitle: String = 'Zoom in'
5270 // The title set on the 'zoom in' button.
5271 zoomInTitle: 'Zoom in',
5273 // @option zoomOutText: String = '−'
5274 // The text set on the 'zoom out' button.
5275 zoomOutText: '−',
5277 // @option zoomOutTitle: String = 'Zoom out'
5278 // The title set on the 'zoom out' button.
5279 zoomOutTitle: 'Zoom out'
5282 onAdd: function (map) {
5283 var zoomName = 'leaflet-control-zoom',
5284 container = create$1('div', zoomName + ' leaflet-bar'),
5285 options = this.options;
5287 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle,
5288 zoomName + '-in', container, this._zoomIn);
5289 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
5290 zoomName + '-out', container, this._zoomOut);
5292 this._updateDisabled();
5293 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
5298 onRemove: function (map) {
5299 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
5302 disable: function () {
5303 this._disabled = true;
5304 this._updateDisabled();
5308 enable: function () {
5309 this._disabled = false;
5310 this._updateDisabled();
5314 _zoomIn: function (e) {
5315 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
5316 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5320 _zoomOut: function (e) {
5321 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
5322 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5326 _createButton: function (html, title, className, container, fn) {
5327 var link = create$1('a', className, container);
5328 link.innerHTML = html;
5333 * Will force screen readers like VoiceOver to read this as "Zoom in - button"
5335 link.setAttribute('role', 'button');
5336 link.setAttribute('aria-label', title);
5338 disableClickPropagation(link);
5339 on(link, 'click', stop);
5340 on(link, 'click', fn, this);
5341 on(link, 'click', this._refocusOnMap, this);
5346 _updateDisabled: function () {
5347 var map = this._map,
5348 className = 'leaflet-disabled';
5350 removeClass(this._zoomInButton, className);
5351 removeClass(this._zoomOutButton, className);
5353 if (this._disabled || map._zoom === map.getMinZoom()) {
5354 addClass(this._zoomOutButton, className);
5356 if (this._disabled || map._zoom === map.getMaxZoom()) {
5357 addClass(this._zoomInButton, className);
5363 // @section Control options
5364 // @option zoomControl: Boolean = true
5365 // Whether a [zoom control](#control-zoom) is added to the map by default.
5370 Map.addInitHook(function () {
5371 if (this.options.zoomControl) {
5372 // @section Controls
5373 // @property zoomControl: Control.Zoom
5374 // The default zoom control (only available if the
5375 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
5376 this.zoomControl = new Zoom();
5377 this.addControl(this.zoomControl);
5381 // @namespace Control.Zoom
5382 // @factory L.control.zoom(options: Control.Zoom options)
5383 // Creates a zoom control
5384 var zoom = function (options) {
5385 return new Zoom(options);
5389 * @class Control.Scale
5390 * @aka L.Control.Scale
5393 * 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`.
5398 * L.control.scale().addTo(map);
5402 var Scale = Control.extend({
5404 // @aka Control.Scale options
5406 position: 'bottomleft',
5408 // @option maxWidth: Number = 100
5409 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
5412 // @option metric: Boolean = True
5413 // Whether to show the metric scale line (m/km).
5416 // @option imperial: Boolean = True
5417 // Whether to show the imperial scale line (mi/ft).
5420 // @option updateWhenIdle: Boolean = false
5421 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
5424 onAdd: function (map) {
5425 var className = 'leaflet-control-scale',
5426 container = create$1('div', className),
5427 options = this.options;
5429 this._addScales(options, className + '-line', container);
5431 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5432 map.whenReady(this._update, this);
5437 onRemove: function (map) {
5438 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5441 _addScales: function (options, className, container) {
5442 if (options.metric) {
5443 this._mScale = create$1('div', className, container);
5445 if (options.imperial) {
5446 this._iScale = create$1('div', className, container);
5450 _update: function () {
5451 var map = this._map,
5452 y = map.getSize().y / 2;
5454 var maxMeters = map.distance(
5455 map.containerPointToLatLng([0, y]),
5456 map.containerPointToLatLng([this.options.maxWidth, y]));
5458 this._updateScales(maxMeters);
5461 _updateScales: function (maxMeters) {
5462 if (this.options.metric && maxMeters) {
5463 this._updateMetric(maxMeters);
5465 if (this.options.imperial && maxMeters) {
5466 this._updateImperial(maxMeters);
5470 _updateMetric: function (maxMeters) {
5471 var meters = this._getRoundNum(maxMeters),
5472 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
5474 this._updateScale(this._mScale, label, meters / maxMeters);
5477 _updateImperial: function (maxMeters) {
5478 var maxFeet = maxMeters * 3.2808399,
5479 maxMiles, miles, feet;
5481 if (maxFeet > 5280) {
5482 maxMiles = maxFeet / 5280;
5483 miles = this._getRoundNum(maxMiles);
5484 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
5487 feet = this._getRoundNum(maxFeet);
5488 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
5492 _updateScale: function (scale, text, ratio) {
5493 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
5494 scale.innerHTML = text;
5497 _getRoundNum: function (num) {
5498 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5511 // @factory L.control.scale(options?: Control.Scale options)
5512 // Creates an scale control with the given options.
5513 var scale = function (options) {
5514 return new Scale(options);
5518 * @class Control.Attribution
5519 * @aka L.Control.Attribution
5522 * 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.
5525 var Attribution = Control.extend({
5527 // @aka Control.Attribution options
5529 position: 'bottomright',
5531 // @option prefix: String = 'Leaflet'
5532 // The HTML text shown before the attributions. Pass `false` to disable.
5533 prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
5536 initialize: function (options) {
5537 setOptions(this, options);
5539 this._attributions = {};
5542 onAdd: function (map) {
5543 map.attributionControl = this;
5544 this._container = create$1('div', 'leaflet-control-attribution');
5545 disableClickPropagation(this._container);
5547 // TODO ugly, refactor
5548 for (var i in map._layers) {
5549 if (map._layers[i].getAttribution) {
5550 this.addAttribution(map._layers[i].getAttribution());
5556 return this._container;
5559 // @method setPrefix(prefix: String): this
5560 // Sets the text before the attributions.
5561 setPrefix: function (prefix) {
5562 this.options.prefix = prefix;
5567 // @method addAttribution(text: String): this
5568 // Adds an attribution text (e.g. `'Vector data © Mapbox'`).
5569 addAttribution: function (text) {
5570 if (!text) { return this; }
5572 if (!this._attributions[text]) {
5573 this._attributions[text] = 0;
5575 this._attributions[text]++;
5582 // @method removeAttribution(text: String): this
5583 // Removes an attribution text.
5584 removeAttribution: function (text) {
5585 if (!text) { return this; }
5587 if (this._attributions[text]) {
5588 this._attributions[text]--;
5595 _update: function () {
5596 if (!this._map) { return; }
5600 for (var i in this._attributions) {
5601 if (this._attributions[i]) {
5606 var prefixAndAttribs = [];
5608 if (this.options.prefix) {
5609 prefixAndAttribs.push(this.options.prefix);
5611 if (attribs.length) {
5612 prefixAndAttribs.push(attribs.join(', '));
5615 this._container.innerHTML = prefixAndAttribs.join(' | ');
5620 // @section Control options
5621 // @option attributionControl: Boolean = true
5622 // Whether a [attribution control](#control-attribution) is added to the map by default.
5624 attributionControl: true
5627 Map.addInitHook(function () {
5628 if (this.options.attributionControl) {
5629 new Attribution().addTo(this);
5633 // @namespace Control.Attribution
5634 // @factory L.control.attribution(options: Control.Attribution options)
5635 // Creates an attribution control.
5636 var attribution = function (options) {
5637 return new Attribution(options);
5640 Control.Layers = Layers;
5641 Control.Zoom = Zoom;
5642 Control.Scale = Scale;
5643 Control.Attribution = Attribution;
5645 control.layers = layers;
5646 control.zoom = zoom;
5647 control.scale = scale;
5648 control.attribution = attribution;
5651 L.Handler is a base class for handler classes that are used internally to inject
5652 interaction features like dragging to classes like Map and Marker.
5657 // Abstract class for map interaction handlers
5659 var Handler = Class.extend({
5660 initialize: function (map) {
5664 // @method enable(): this
5665 // Enables the handler
5666 enable: function () {
5667 if (this._enabled) { return this; }
5669 this._enabled = true;
5674 // @method disable(): this
5675 // Disables the handler
5676 disable: function () {
5677 if (!this._enabled) { return this; }
5679 this._enabled = false;
5684 // @method enabled(): Boolean
5685 // Returns `true` if the handler is enabled
5686 enabled: function () {
5687 return !!this._enabled;
5690 // @section Extension methods
5691 // Classes inheriting from `Handler` must implement the two following methods:
5692 // @method addHooks()
5693 // Called when the handler is enabled, should add event hooks.
5694 // @method removeHooks()
5695 // Called when the handler is disabled, should remove the event hooks added previously.
5698 // @section There is static function which can be called without instantiating L.Handler:
5699 // @function addTo(map: Map, name: String): this
5700 // Adds a new Handler to the given map with the given name.
5701 Handler.addTo = function (map, name) {
5702 map.addHandler(name, this);
5706 var Mixin = {Events: Events};
5713 * A class for making DOM elements draggable (including touch support).
5714 * Used internally for map and marker dragging. Only works for elements
5715 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
5719 * var draggable = new L.Draggable(elementToDrag);
5720 * draggable.enable();
5724 var START = touch ? 'touchstart mousedown' : 'mousedown';
5726 mousedown: 'mouseup',
5727 touchstart: 'touchend',
5728 pointerdown: 'touchend',
5729 MSPointerDown: 'touchend'
5732 mousedown: 'mousemove',
5733 touchstart: 'touchmove',
5734 pointerdown: 'touchmove',
5735 MSPointerDown: 'touchmove'
5739 var Draggable = Evented.extend({
5743 // @aka Draggable options
5744 // @option clickTolerance: Number = 3
5745 // The max number of pixels a user can shift the mouse pointer during a click
5746 // for it to be considered a valid click (as opposed to a mouse drag).
5750 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
5751 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
5752 initialize: function (element, dragStartTarget, preventOutline$$1, options) {
5753 setOptions(this, options);
5755 this._element = element;
5756 this._dragStartTarget = dragStartTarget || element;
5757 this._preventOutline = preventOutline$$1;
5761 // Enables the dragging ability
5762 enable: function () {
5763 if (this._enabled) { return; }
5765 on(this._dragStartTarget, START, this._onDown, this);
5767 this._enabled = true;
5770 // @method disable()
5771 // Disables the dragging ability
5772 disable: function () {
5773 if (!this._enabled) { return; }
5775 // If we're currently dragging this draggable,
5776 // disabling it counts as first ending the drag.
5777 if (Draggable._dragging === this) {
5781 off(this._dragStartTarget, START, this._onDown, this);
5783 this._enabled = false;
5784 this._moved = false;
5787 _onDown: function (e) {
5788 // Ignore simulated events, since we handle both touch and
5789 // mouse explicitly; otherwise we risk getting duplicates of
5790 // touch events, see #4315.
5791 // Also ignore the event if disabled; this happens in IE11
5792 // under some circumstances, see #3666.
5793 if (e._simulated || !this._enabled) { return; }
5795 this._moved = false;
5797 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
5799 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
5800 Draggable._dragging = this; // Prevent dragging multiple objects at once.
5802 if (this._preventOutline) {
5803 preventOutline(this._element);
5807 disableTextSelection();
5809 if (this._moving) { return; }
5811 // @event down: Event
5812 // Fired when a drag is about to start.
5815 var first = e.touches ? e.touches[0] : e,
5816 sizedParent = getSizedParentNode(this._element);
5818 this._startPoint = new Point(first.clientX, first.clientY);
5820 // Cache the scale, so that we can continuously compensate for it during drag (_onMove).
5821 this._parentScale = getScale(sizedParent);
5823 on(document, MOVE[e.type], this._onMove, this);
5824 on(document, END[e.type], this._onUp, this);
5827 _onMove: function (e) {
5828 // Ignore simulated events, since we handle both touch and
5829 // mouse explicitly; otherwise we risk getting duplicates of
5830 // touch events, see #4315.
5831 // Also ignore the event if disabled; this happens in IE11
5832 // under some circumstances, see #3666.
5833 if (e._simulated || !this._enabled) { return; }
5835 if (e.touches && e.touches.length > 1) {
5840 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
5841 offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
5843 if (!offset.x && !offset.y) { return; }
5844 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
5846 // We assume that the parent container's position, border and scale do not change for the duration of the drag.
5847 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
5848 // and we can use the cached value for the scale.
5849 offset.x /= this._parentScale.x;
5850 offset.y /= this._parentScale.y;
5855 // @event dragstart: Event
5856 // Fired when a drag starts
5857 this.fire('dragstart');
5860 this._startPos = getPosition(this._element).subtract(offset);
5862 addClass(document.body, 'leaflet-dragging');
5864 this._lastTarget = e.target || e.srcElement;
5865 // IE and Edge do not give the <use> element, so fetch it
5867 if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
5868 this._lastTarget = this._lastTarget.correspondingUseElement;
5870 addClass(this._lastTarget, 'leaflet-drag-target');
5873 this._newPos = this._startPos.add(offset);
5874 this._moving = true;
5876 cancelAnimFrame(this._animRequest);
5877 this._lastEvent = e;
5878 this._animRequest = requestAnimFrame(this._updatePosition, this, true);
5881 _updatePosition: function () {
5882 var e = {originalEvent: this._lastEvent};
5884 // @event predrag: Event
5885 // Fired continuously during dragging *before* each corresponding
5886 // update of the element's position.
5887 this.fire('predrag', e);
5888 setPosition(this._element, this._newPos);
5890 // @event drag: Event
5891 // Fired continuously during dragging.
5892 this.fire('drag', e);
5895 _onUp: function (e) {
5896 // Ignore simulated events, since we handle both touch and
5897 // mouse explicitly; otherwise we risk getting duplicates of
5898 // touch events, see #4315.
5899 // Also ignore the event if disabled; this happens in IE11
5900 // under some circumstances, see #3666.
5901 if (e._simulated || !this._enabled) { return; }
5905 finishDrag: function () {
5906 removeClass(document.body, 'leaflet-dragging');
5908 if (this._lastTarget) {
5909 removeClass(this._lastTarget, 'leaflet-drag-target');
5910 this._lastTarget = null;
5913 for (var i in MOVE) {
5914 off(document, MOVE[i], this._onMove, this);
5915 off(document, END[i], this._onUp, this);
5919 enableTextSelection();
5921 if (this._moved && this._moving) {
5922 // ensure drag is not fired after dragend
5923 cancelAnimFrame(this._animRequest);
5925 // @event dragend: DragEndEvent
5926 // Fired when the drag ends.
5927 this.fire('dragend', {
5928 distance: this._newPos.distanceTo(this._startPos)
5932 this._moving = false;
5933 Draggable._dragging = false;
5939 * @namespace LineUtil
5941 * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
5944 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
5945 // Improves rendering performance dramatically by lessening the number of points to draw.
5947 // @function simplify(points: Point[], tolerance: Number): Point[]
5948 // Dramatically reduces the number of points in a polyline while retaining
5949 // its shape and returns a new array of simplified points, using the
5950 // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
5951 // Used for a huge performance boost when processing/displaying Leaflet polylines for
5952 // each zoom level and also reducing visual noise. tolerance affects the amount of
5953 // simplification (lesser value means higher quality but slower and with more points).
5954 // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
5955 function simplify(points, tolerance) {
5956 if (!tolerance || !points.length) {
5957 return points.slice();
5960 var sqTolerance = tolerance * tolerance;
5962 // stage 1: vertex reduction
5963 points = _reducePoints(points, sqTolerance);
5965 // stage 2: Douglas-Peucker simplification
5966 points = _simplifyDP(points, sqTolerance);
5971 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
5972 // Returns the distance between point `p` and segment `p1` to `p2`.
5973 function pointToSegmentDistance(p, p1, p2) {
5974 return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
5977 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
5978 // Returns the closest point from a point `p` on a segment `p1` to `p2`.
5979 function closestPointOnSegment(p, p1, p2) {
5980 return _sqClosestPointOnSegment(p, p1, p2);
5983 // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
5984 function _simplifyDP(points, sqTolerance) {
5986 var len = points.length,
5987 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
5988 markers = new ArrayConstructor(len);
5990 markers[0] = markers[len - 1] = 1;
5992 _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
5997 for (i = 0; i < len; i++) {
5999 newPoints.push(points[i]);
6006 function _simplifyDPStep(points, markers, sqTolerance, first, last) {
6011 for (i = first + 1; i <= last - 1; i++) {
6012 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
6014 if (sqDist > maxSqDist) {
6020 if (maxSqDist > sqTolerance) {
6023 _simplifyDPStep(points, markers, sqTolerance, first, index);
6024 _simplifyDPStep(points, markers, sqTolerance, index, last);
6028 // reduce points that are too close to each other to a single point
6029 function _reducePoints(points, sqTolerance) {
6030 var reducedPoints = [points[0]];
6032 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
6033 if (_sqDist(points[i], points[prev]) > sqTolerance) {
6034 reducedPoints.push(points[i]);
6038 if (prev < len - 1) {
6039 reducedPoints.push(points[len - 1]);
6041 return reducedPoints;
6046 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
6047 // Clips the segment a to b by rectangular bounds with the
6048 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
6049 // (modifying the segment points directly!). Used by Leaflet to only show polyline
6050 // points that are on the screen or near, increasing performance.
6051 function clipSegment(a, b, bounds, useLastCode, round) {
6052 var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
6053 codeB = _getBitCode(b, bounds),
6055 codeOut, p, newCode;
6057 // save 2nd code to avoid calculating it on the next segment
6061 // if a,b is inside the clip window (trivial accept)
6062 if (!(codeA | codeB)) {
6066 // if a,b is outside the clip window (trivial reject)
6067 if (codeA & codeB) {
6072 codeOut = codeA || codeB;
6073 p = _getEdgeIntersection(a, b, codeOut, bounds, round);
6074 newCode = _getBitCode(p, bounds);
6076 if (codeOut === codeA) {
6086 function _getEdgeIntersection(a, b, code, bounds, round) {
6093 if (code & 8) { // top
6094 x = a.x + dx * (max.y - a.y) / dy;
6097 } else if (code & 4) { // bottom
6098 x = a.x + dx * (min.y - a.y) / dy;
6101 } else if (code & 2) { // right
6103 y = a.y + dy * (max.x - a.x) / dx;
6105 } else if (code & 1) { // left
6107 y = a.y + dy * (min.x - a.x) / dx;
6110 return new Point(x, y, round);
6113 function _getBitCode(p, bounds) {
6116 if (p.x < bounds.min.x) { // left
6118 } else if (p.x > bounds.max.x) { // right
6122 if (p.y < bounds.min.y) { // bottom
6124 } else if (p.y > bounds.max.y) { // top
6131 // square distance (to avoid unnecessary Math.sqrt calls)
6132 function _sqDist(p1, p2) {
6133 var dx = p2.x - p1.x,
6135 return dx * dx + dy * dy;
6138 // return closest point on segment or distance to that point
6139 function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
6144 dot = dx * dx + dy * dy,
6148 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
6162 return sqDist ? dx * dx + dy * dy : new Point(x, y);
6166 // @function isFlat(latlngs: LatLng[]): Boolean
6167 // Returns true if `latlngs` is a flat array, false is nested.
6168 function isFlat(latlngs) {
6169 return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
6172 function _flat(latlngs) {
6173 console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
6174 return isFlat(latlngs);
6178 var LineUtil = (Object.freeze || Object)({
6180 pointToSegmentDistance: pointToSegmentDistance,
6181 closestPointOnSegment: closestPointOnSegment,
6182 clipSegment: clipSegment,
6183 _getEdgeIntersection: _getEdgeIntersection,
6184 _getBitCode: _getBitCode,
6185 _sqClosestPointOnSegment: _sqClosestPointOnSegment,
6191 * @namespace PolyUtil
6192 * Various utility functions for polygon geometries.
6195 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
6196 * 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)).
6197 * Used by Leaflet to only show polygon points that are on the screen or near, increasing
6198 * performance. Note that polygon points needs different algorithm for clipping
6199 * than polyline, so there's a separate method for it.
6201 function clipPolygon(points, bounds, round) {
6203 edges = [1, 4, 2, 8],
6208 for (i = 0, len = points.length; i < len; i++) {
6209 points[i]._code = _getBitCode(points[i], bounds);
6212 // for each edge (left, bottom, right, top)
6213 for (k = 0; k < 4; k++) {
6217 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
6221 // if a is inside the clip window
6222 if (!(a._code & edge)) {
6223 // if b is outside the clip window (a->b goes out of screen)
6224 if (b._code & edge) {
6225 p = _getEdgeIntersection(b, a, edge, bounds, round);
6226 p._code = _getBitCode(p, bounds);
6227 clippedPoints.push(p);
6229 clippedPoints.push(a);
6231 // else if b is inside the clip window (a->b enters the screen)
6232 } else if (!(b._code & edge)) {
6233 p = _getEdgeIntersection(b, a, edge, bounds, round);
6234 p._code = _getBitCode(p, bounds);
6235 clippedPoints.push(p);
6238 points = clippedPoints;
6245 var PolyUtil = (Object.freeze || Object)({
6246 clipPolygon: clipPolygon
6250 * @namespace Projection
6252 * Leaflet comes with a set of already defined Projections out of the box:
6254 * @projection L.Projection.LonLat
6256 * Equirectangular, or Plate Carree projection — the most simple projection,
6257 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
6258 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
6259 * `EPSG:4326` and `Simple` CRS.
6263 project: function (latlng) {
6264 return new Point(latlng.lng, latlng.lat);
6267 unproject: function (point) {
6268 return new LatLng(point.y, point.x);
6271 bounds: new Bounds([-180, -90], [180, 90])
6275 * @namespace Projection
6276 * @projection L.Projection.Mercator
6278 * Elliptical Mercator projection — more complex than Spherical Mercator. Takes into account that Earth is a geoid, not a perfect sphere. Used by the EPSG:3395 CRS.
6283 R_MINOR: 6356752.314245179,
6285 bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
6287 project: function (latlng) {
6288 var d = Math.PI / 180,
6291 tmp = this.R_MINOR / r,
6292 e = Math.sqrt(1 - tmp * tmp),
6293 con = e * Math.sin(y);
6295 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
6296 y = -r * Math.log(Math.max(ts, 1E-10));
6298 return new Point(latlng.lng * d * r, y);
6301 unproject: function (point) {
6302 var d = 180 / Math.PI,
6304 tmp = this.R_MINOR / r,
6305 e = Math.sqrt(1 - tmp * tmp),
6306 ts = Math.exp(-point.y / r),
6307 phi = Math.PI / 2 - 2 * Math.atan(ts);
6309 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
6310 con = e * Math.sin(phi);
6311 con = Math.pow((1 - con) / (1 + con), e / 2);
6312 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
6316 return new LatLng(phi * d, point.x * d / r);
6323 * An object with methods for projecting geographical coordinates of the world onto
6324 * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection).
6326 * @property bounds: Bounds
6327 * The bounds (specified in CRS units) where the projection is valid
6329 * @method project(latlng: LatLng): Point
6330 * Projects geographical coordinates into a 2D point.
6331 * Only accepts actual `L.LatLng` instances, not arrays.
6333 * @method unproject(point: Point): LatLng
6334 * The inverse of `project`. Projects a 2D point into a geographical location.
6335 * Only accepts actual `L.Point` instances, not arrays.
6337 * Note that the projection instances do not inherit from Leafet's `Class` object,
6338 * and can't be instantiated. Also, new classes can't inherit from them,
6339 * and methods can't be added to them with the `include` function.
6346 var index = (Object.freeze || Object)({
6349 SphericalMercator: SphericalMercator
6354 * @crs L.CRS.EPSG3395
6356 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
6358 var EPSG3395 = extend({}, Earth, {
6360 projection: Mercator,
6362 transformation: (function () {
6363 var scale = 0.5 / (Math.PI * Mercator.R);
6364 return toTransformation(scale, 0.5, -scale, 0.5);
6370 * @crs L.CRS.EPSG4326
6372 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
6374 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
6375 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer`
6376 * with this CRS, ensure that there are two 256x256 pixel tiles covering the
6377 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
6378 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
6381 var EPSG4326 = extend({}, Earth, {
6384 transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
6391 * A simple CRS that maps longitude and latitude into `x` and `y` directly.
6392 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
6393 * axis should still be inverted (going from bottom to top). `distance()` returns
6394 * simple euclidean distance.
6397 var Simple = extend({}, CRS, {
6399 transformation: toTransformation(1, 0, -1, 0),
6401 scale: function (zoom) {
6402 return Math.pow(2, zoom);
6405 zoom: function (scale) {
6406 return Math.log(scale) / Math.LN2;
6409 distance: function (latlng1, latlng2) {
6410 var dx = latlng2.lng - latlng1.lng,
6411 dy = latlng2.lat - latlng1.lat;
6413 return Math.sqrt(dx * dx + dy * dy);
6420 CRS.EPSG3395 = EPSG3395;
6421 CRS.EPSG3857 = EPSG3857;
6422 CRS.EPSG900913 = EPSG900913;
6423 CRS.EPSG4326 = EPSG4326;
6424 CRS.Simple = Simple;
6432 * A set of methods from the Layer base class that all Leaflet layers use.
6433 * Inherits all methods, options and events from `L.Evented`.
6438 * var layer = L.Marker(latlng).addTo(map);
6444 * Fired after the layer is added to a map
6446 * @event remove: Event
6447 * Fired after the layer is removed from a map
6451 var Layer = Evented.extend({
6453 // Classes extending `L.Layer` will inherit the following options:
6455 // @option pane: String = 'overlayPane'
6456 // 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.
6457 pane: 'overlayPane',
6459 // @option attribution: String = null
6460 // String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox".
6463 bubblingMouseEvents: true
6467 * Classes extending `L.Layer` will inherit the following methods:
6469 * @method addTo(map: Map|LayerGroup): this
6470 * Adds the layer to the given map or layer group.
6472 addTo: function (map) {
6477 // @method remove: this
6478 // Removes the layer from the map it is currently active on.
6479 remove: function () {
6480 return this.removeFrom(this._map || this._mapToAdd);
6483 // @method removeFrom(map: Map): this
6484 // Removes the layer from the given map
6485 removeFrom: function (obj) {
6487 obj.removeLayer(this);
6492 // @method getPane(name? : String): HTMLElement
6493 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
6494 getPane: function (name) {
6495 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
6498 addInteractiveTarget: function (targetEl) {
6499 this._map._targets[stamp(targetEl)] = this;
6503 removeInteractiveTarget: function (targetEl) {
6504 delete this._map._targets[stamp(targetEl)];
6508 // @method getAttribution: String
6509 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
6510 getAttribution: function () {
6511 return this.options.attribution;
6514 _layerAdd: function (e) {
6517 // check in case layer gets added and then removed before the map is ready
6518 if (!map.hasLayer(this)) { return; }
6521 this._zoomAnimated = map._zoomAnimated;
6523 if (this.getEvents) {
6524 var events = this.getEvents();
6525 map.on(events, this);
6526 this.once('remove', function () {
6527 map.off(events, this);
6533 if (this.getAttribution && map.attributionControl) {
6534 map.attributionControl.addAttribution(this.getAttribution());
6538 map.fire('layeradd', {layer: this});
6542 /* @section Extension methods
6545 * Every layer should extend from `L.Layer` and (re-)implement the following methods.
6547 * @method onAdd(map: Map): this
6548 * 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).
6550 * @method onRemove(map: Map): this
6551 * 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).
6553 * @method getEvents(): Object
6554 * 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.
6556 * @method getAttribution(): String
6557 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
6559 * @method beforeAdd(map: Map): this
6560 * 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.
6565 * @section Layer events
6567 * @event layeradd: LayerEvent
6568 * Fired when a new layer is added to the map.
6570 * @event layerremove: LayerEvent
6571 * Fired when some layer is removed from the map
6573 * @section Methods for Layers and Controls
6576 // @method addLayer(layer: Layer): this
6577 // Adds the given layer to the map
6578 addLayer: function (layer) {
6579 if (!layer._layerAdd) {
6580 throw new Error('The provided object is not a Layer.');
6583 var id = stamp(layer);
6584 if (this._layers[id]) { return this; }
6585 this._layers[id] = layer;
6587 layer._mapToAdd = this;
6589 if (layer.beforeAdd) {
6590 layer.beforeAdd(this);
6593 this.whenReady(layer._layerAdd, layer);
6598 // @method removeLayer(layer: Layer): this
6599 // Removes the given layer from the map.
6600 removeLayer: function (layer) {
6601 var id = stamp(layer);
6603 if (!this._layers[id]) { return this; }
6606 layer.onRemove(this);
6609 if (layer.getAttribution && this.attributionControl) {
6610 this.attributionControl.removeAttribution(layer.getAttribution());
6613 delete this._layers[id];
6616 this.fire('layerremove', {layer: layer});
6617 layer.fire('remove');
6620 layer._map = layer._mapToAdd = null;
6625 // @method hasLayer(layer: Layer): Boolean
6626 // Returns `true` if the given layer is currently added to the map
6627 hasLayer: function (layer) {
6628 return !!layer && (stamp(layer) in this._layers);
6631 /* @method eachLayer(fn: Function, context?: Object): this
6632 * Iterates over the layers of the map, optionally specifying context of the iterator function.
6634 * map.eachLayer(function(layer){
6635 * layer.bindPopup('Hello');
6639 eachLayer: function (method, context) {
6640 for (var i in this._layers) {
6641 method.call(context, this._layers[i]);
6646 _addLayers: function (layers) {
6647 layers = layers ? (isArray(layers) ? layers : [layers]) : [];
6649 for (var i = 0, len = layers.length; i < len; i++) {
6650 this.addLayer(layers[i]);
6654 _addZoomLimit: function (layer) {
6655 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
6656 this._zoomBoundLayers[stamp(layer)] = layer;
6657 this._updateZoomLevels();
6661 _removeZoomLimit: function (layer) {
6662 var id = stamp(layer);
6664 if (this._zoomBoundLayers[id]) {
6665 delete this._zoomBoundLayers[id];
6666 this._updateZoomLevels();
6670 _updateZoomLevels: function () {
6671 var minZoom = Infinity,
6672 maxZoom = -Infinity,
6673 oldZoomSpan = this._getZoomSpan();
6675 for (var i in this._zoomBoundLayers) {
6676 var options = this._zoomBoundLayers[i].options;
6678 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
6679 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
6682 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
6683 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
6685 // @section Map state change events
6686 // @event zoomlevelschange: Event
6687 // Fired when the number of zoomlevels on the map is changed due
6688 // to adding or removing a layer.
6689 if (oldZoomSpan !== this._getZoomSpan()) {
6690 this.fire('zoomlevelschange');
6693 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
6694 this.setZoom(this._layersMaxZoom);
6696 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
6697 this.setZoom(this._layersMinZoom);
6707 * Used to group several layers and handle them as one. If you add it to the map,
6708 * any layers added or removed from the group will be added/removed on the map as
6709 * well. Extends `Layer`.
6714 * L.layerGroup([marker1, marker2])
6715 * .addLayer(polyline)
6720 var LayerGroup = Layer.extend({
6722 initialize: function (layers, options) {
6723 setOptions(this, options);
6730 for (i = 0, len = layers.length; i < len; i++) {
6731 this.addLayer(layers[i]);
6736 // @method addLayer(layer: Layer): this
6737 // Adds the given layer to the group.
6738 addLayer: function (layer) {
6739 var id = this.getLayerId(layer);
6741 this._layers[id] = layer;
6744 this._map.addLayer(layer);
6750 // @method removeLayer(layer: Layer): this
6751 // Removes the given layer from the group.
6753 // @method removeLayer(id: Number): this
6754 // Removes the layer with the given internal ID from the group.
6755 removeLayer: function (layer) {
6756 var id = layer in this._layers ? layer : this.getLayerId(layer);
6758 if (this._map && this._layers[id]) {
6759 this._map.removeLayer(this._layers[id]);
6762 delete this._layers[id];
6767 // @method hasLayer(layer: Layer): Boolean
6768 // Returns `true` if the given layer is currently added to the group.
6770 // @method hasLayer(id: Number): Boolean
6771 // Returns `true` if the given internal ID is currently added to the group.
6772 hasLayer: function (layer) {
6773 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
6776 // @method clearLayers(): this
6777 // Removes all the layers from the group.
6778 clearLayers: function () {
6779 return this.eachLayer(this.removeLayer, this);
6782 // @method invoke(methodName: String, …): this
6783 // Calls `methodName` on every layer contained in this group, passing any
6784 // additional parameters. Has no effect if the layers contained do not
6785 // implement `methodName`.
6786 invoke: function (methodName) {
6787 var args = Array.prototype.slice.call(arguments, 1),
6790 for (i in this._layers) {
6791 layer = this._layers[i];
6793 if (layer[methodName]) {
6794 layer[methodName].apply(layer, args);
6801 onAdd: function (map) {
6802 this.eachLayer(map.addLayer, map);
6805 onRemove: function (map) {
6806 this.eachLayer(map.removeLayer, map);
6809 // @method eachLayer(fn: Function, context?: Object): this
6810 // Iterates over the layers of the group, optionally specifying context of the iterator function.
6812 // group.eachLayer(function (layer) {
6813 // layer.bindPopup('Hello');
6816 eachLayer: function (method, context) {
6817 for (var i in this._layers) {
6818 method.call(context, this._layers[i]);
6823 // @method getLayer(id: Number): Layer
6824 // Returns the layer with the given internal ID.
6825 getLayer: function (id) {
6826 return this._layers[id];
6829 // @method getLayers(): Layer[]
6830 // Returns an array of all the layers added to the group.
6831 getLayers: function () {
6833 this.eachLayer(layers.push, layers);
6837 // @method setZIndex(zIndex: Number): this
6838 // Calls `setZIndex` on every layer contained in this group, passing the z-index.
6839 setZIndex: function (zIndex) {
6840 return this.invoke('setZIndex', zIndex);
6843 // @method getLayerId(layer: Layer): Number
6844 // Returns the internal ID for a layer
6845 getLayerId: function (layer) {
6846 return stamp(layer);
6851 // @factory L.layerGroup(layers?: Layer[], options?: Object)
6852 // Create a layer group, optionally given an initial set of layers and an `options` object.
6853 var layerGroup = function (layers, options) {
6854 return new LayerGroup(layers, options);
6858 * @class FeatureGroup
6859 * @aka L.FeatureGroup
6860 * @inherits LayerGroup
6862 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
6863 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
6864 * * Events are propagated to the `FeatureGroup`, so if the group has an event
6865 * handler, it will handle events from any of the layers. This includes mouse events
6866 * and custom events.
6867 * * Has `layeradd` and `layerremove` events
6872 * L.featureGroup([marker1, marker2, polyline])
6873 * .bindPopup('Hello world!')
6874 * .on('click', function() { alert('Clicked on a member of the group!'); })
6879 var FeatureGroup = LayerGroup.extend({
6881 addLayer: function (layer) {
6882 if (this.hasLayer(layer)) {
6886 layer.addEventParent(this);
6888 LayerGroup.prototype.addLayer.call(this, layer);
6890 // @event layeradd: LayerEvent
6891 // Fired when a layer is added to this `FeatureGroup`
6892 return this.fire('layeradd', {layer: layer});
6895 removeLayer: function (layer) {
6896 if (!this.hasLayer(layer)) {
6899 if (layer in this._layers) {
6900 layer = this._layers[layer];
6903 layer.removeEventParent(this);
6905 LayerGroup.prototype.removeLayer.call(this, layer);
6907 // @event layerremove: LayerEvent
6908 // Fired when a layer is removed from this `FeatureGroup`
6909 return this.fire('layerremove', {layer: layer});
6912 // @method setStyle(style: Path options): this
6913 // Sets the given path options to each layer of the group that has a `setStyle` method.
6914 setStyle: function (style) {
6915 return this.invoke('setStyle', style);
6918 // @method bringToFront(): this
6919 // Brings the layer group to the top of all other layers
6920 bringToFront: function () {
6921 return this.invoke('bringToFront');
6924 // @method bringToBack(): this
6925 // Brings the layer group to the back of all other layers
6926 bringToBack: function () {
6927 return this.invoke('bringToBack');
6930 // @method getBounds(): LatLngBounds
6931 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
6932 getBounds: function () {
6933 var bounds = new LatLngBounds();
6935 for (var id in this._layers) {
6936 var layer = this._layers[id];
6937 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
6943 // @factory L.featureGroup(layers: Layer[])
6944 // Create a feature group, optionally given an initial set of layers.
6945 var featureGroup = function (layers) {
6946 return new FeatureGroup(layers);
6953 * Represents an icon to provide when creating a marker.
6958 * var myIcon = L.icon({
6959 * iconUrl: 'my-icon.png',
6960 * iconRetinaUrl: 'my-icon@2x.png',
6961 * iconSize: [38, 95],
6962 * iconAnchor: [22, 94],
6963 * popupAnchor: [-3, -76],
6964 * shadowUrl: 'my-icon-shadow.png',
6965 * shadowRetinaUrl: 'my-icon-shadow@2x.png',
6966 * shadowSize: [68, 95],
6967 * shadowAnchor: [22, 94]
6970 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
6973 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
6977 var Icon = Class.extend({
6982 * @option iconUrl: String = null
6983 * **(required)** The URL to the icon image (absolute or relative to your script path).
6985 * @option iconRetinaUrl: String = null
6986 * The URL to a retina sized version of the icon image (absolute or relative to your
6987 * script path). Used for Retina screen devices.
6989 * @option iconSize: Point = null
6990 * Size of the icon image in pixels.
6992 * @option iconAnchor: Point = null
6993 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
6994 * will be aligned so that this point is at the marker's geographical location. Centered
6995 * by default if size is specified, also can be set in CSS with negative margins.
6997 * @option popupAnchor: Point = [0, 0]
6998 * The coordinates of the point from which popups will "open", relative to the icon anchor.
7000 * @option tooltipAnchor: Point = [0, 0]
7001 * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
7003 * @option shadowUrl: String = null
7004 * The URL to the icon shadow image. If not specified, no shadow image will be created.
7006 * @option shadowRetinaUrl: String = null
7008 * @option shadowSize: Point = null
7009 * Size of the shadow image in pixels.
7011 * @option shadowAnchor: Point = null
7012 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
7013 * as iconAnchor if not specified).
7015 * @option className: String = ''
7016 * A custom class name to assign to both icon and shadow images. Empty by default.
7020 popupAnchor: [0, 0],
7021 tooltipAnchor: [0, 0]
7024 initialize: function (options) {
7025 setOptions(this, options);
7028 // @method createIcon(oldIcon?: HTMLElement): HTMLElement
7029 // Called internally when the icon has to be shown, returns a `<img>` HTML element
7030 // styled according to the options.
7031 createIcon: function (oldIcon) {
7032 return this._createIcon('icon', oldIcon);
7035 // @method createShadow(oldIcon?: HTMLElement): HTMLElement
7036 // As `createIcon`, but for the shadow beneath it.
7037 createShadow: function (oldIcon) {
7038 return this._createIcon('shadow', oldIcon);
7041 _createIcon: function (name, oldIcon) {
7042 var src = this._getIconUrl(name);
7045 if (name === 'icon') {
7046 throw new Error('iconUrl not set in Icon options (see the docs).');
7051 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
7052 this._setIconStyles(img, name);
7057 _setIconStyles: function (img, name) {
7058 var options = this.options;
7059 var sizeOption = options[name + 'Size'];
7061 if (typeof sizeOption === 'number') {
7062 sizeOption = [sizeOption, sizeOption];
7065 var size = toPoint(sizeOption),
7066 anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
7067 size && size.divideBy(2, true));
7069 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
7072 img.style.marginLeft = (-anchor.x) + 'px';
7073 img.style.marginTop = (-anchor.y) + 'px';
7077 img.style.width = size.x + 'px';
7078 img.style.height = size.y + 'px';
7082 _createImg: function (src, el) {
7083 el = el || document.createElement('img');
7088 _getIconUrl: function (name) {
7089 return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
7094 // @factory L.icon(options: Icon options)
7095 // Creates an icon instance with the given options.
7096 function icon(options) {
7097 return new Icon(options);
7101 * @miniclass Icon.Default (Icon)
7102 * @aka L.Icon.Default
7105 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
7106 * no icon is specified. Points to the blue marker image distributed with Leaflet
7109 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
7110 * (which is a set of `Icon options`).
7112 * If you want to _completely_ replace the default icon, override the
7113 * `L.Marker.prototype.options.icon` with your own icon instead.
7116 var IconDefault = Icon.extend({
7119 iconUrl: 'marker-icon.png',
7120 iconRetinaUrl: 'marker-icon-2x.png',
7121 shadowUrl: 'marker-shadow.png',
7123 iconAnchor: [12, 41],
7124 popupAnchor: [1, -34],
7125 tooltipAnchor: [16, -28],
7126 shadowSize: [41, 41]
7129 _getIconUrl: function (name) {
7130 if (!IconDefault.imagePath) { // Deprecated, backwards-compatibility only
7131 IconDefault.imagePath = this._detectIconPath();
7134 // @option imagePath: String
7135 // `Icon.Default` will try to auto-detect the location of the
7136 // blue icon images. If you are placing these images in a non-standard
7137 // way, set this option to point to the right path.
7138 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
7141 _detectIconPath: function () {
7142 var el = create$1('div', 'leaflet-default-icon-path', document.body);
7143 var path = getStyle(el, 'background-image') ||
7144 getStyle(el, 'backgroundImage'); // IE8
7146 document.body.removeChild(el);
7148 if (path === null || path.indexOf('url') !== 0) {
7151 path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, '');
7159 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7163 /* @namespace Marker
7164 * @section Interaction handlers
7166 * 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:
7169 * marker.dragging.disable();
7172 * @property dragging: Handler
7173 * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
7176 var MarkerDrag = Handler.extend({
7177 initialize: function (marker) {
7178 this._marker = marker;
7181 addHooks: function () {
7182 var icon = this._marker._icon;
7184 if (!this._draggable) {
7185 this._draggable = new Draggable(icon, icon, true);
7188 this._draggable.on({
7189 dragstart: this._onDragStart,
7190 predrag: this._onPreDrag,
7192 dragend: this._onDragEnd
7195 addClass(icon, 'leaflet-marker-draggable');
7198 removeHooks: function () {
7199 this._draggable.off({
7200 dragstart: this._onDragStart,
7201 predrag: this._onPreDrag,
7203 dragend: this._onDragEnd
7206 if (this._marker._icon) {
7207 removeClass(this._marker._icon, 'leaflet-marker-draggable');
7211 moved: function () {
7212 return this._draggable && this._draggable._moved;
7215 _adjustPan: function (e) {
7216 var marker = this._marker,
7218 speed = this._marker.options.autoPanSpeed,
7219 padding = this._marker.options.autoPanPadding,
7220 iconPos = getPosition(marker._icon),
7221 bounds = map.getPixelBounds(),
7222 origin = map.getPixelOrigin();
7224 var panBounds = toBounds(
7225 bounds.min._subtract(origin).add(padding),
7226 bounds.max._subtract(origin).subtract(padding)
7229 if (!panBounds.contains(iconPos)) {
7230 // Compute incremental movement
7231 var movement = toPoint(
7232 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
7233 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
7235 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
7236 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
7237 ).multiplyBy(speed);
7239 map.panBy(movement, {animate: false});
7241 this._draggable._newPos._add(movement);
7242 this._draggable._startPos._add(movement);
7244 setPosition(marker._icon, this._draggable._newPos);
7247 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7251 _onDragStart: function () {
7252 // @section Dragging events
7253 // @event dragstart: Event
7254 // Fired when the user starts dragging the marker.
7256 // @event movestart: Event
7257 // Fired when the marker starts moving (because of dragging).
7259 this._oldLatLng = this._marker.getLatLng();
7266 _onPreDrag: function (e) {
7267 if (this._marker.options.autoPan) {
7268 cancelAnimFrame(this._panRequest);
7269 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7273 _onDrag: function (e) {
7274 var marker = this._marker,
7275 shadow = marker._shadow,
7276 iconPos = getPosition(marker._icon),
7277 latlng = marker._map.layerPointToLatLng(iconPos);
7279 // update shadow position
7281 setPosition(shadow, iconPos);
7284 marker._latlng = latlng;
7286 e.oldLatLng = this._oldLatLng;
7288 // @event drag: Event
7289 // Fired repeatedly while the user drags the marker.
7295 _onDragEnd: function (e) {
7296 // @event dragend: DragEndEvent
7297 // Fired when the user stops dragging the marker.
7299 cancelAnimFrame(this._panRequest);
7301 // @event moveend: Event
7302 // Fired when the marker stops moving (because of dragging).
7303 delete this._oldLatLng;
7306 .fire('dragend', e);
7312 * @inherits Interactive layer
7314 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
7319 * L.marker([50.5, 30.5]).addTo(map);
7323 var Marker = Layer.extend({
7326 // @aka Marker options
7328 // @option icon: Icon = *
7329 // Icon instance to use for rendering the marker.
7330 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
7331 // If not specified, a common instance of `L.Icon.Default` is used.
7332 icon: new IconDefault(),
7334 // Option inherited from "Interactive layer" abstract class
7337 // @option keyboard: Boolean = true
7338 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
7341 // @option title: String = ''
7342 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
7345 // @option alt: String = ''
7346 // Text for the `alt` attribute of the icon image (useful for accessibility).
7349 // @option zIndexOffset: Number = 0
7350 // 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).
7353 // @option opacity: Number = 1.0
7354 // The opacity of the marker.
7357 // @option riseOnHover: Boolean = false
7358 // If `true`, the marker will get on top of others when you hover the mouse over it.
7361 // @option riseOffset: Number = 250
7362 // The z-index offset used for the `riseOnHover` feature.
7365 // @option pane: String = 'markerPane'
7366 // `Map pane` where the markers icon will be added.
7369 // @option bubblingMouseEvents: Boolean = false
7370 // When `true`, a mouse event on this marker will trigger the same event on the map
7371 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7372 bubblingMouseEvents: false,
7374 // @section Draggable marker options
7375 // @option draggable: Boolean = false
7376 // Whether the marker is draggable with mouse/touch or not.
7379 // @option autoPan: Boolean = false
7380 // Whether to pan the map when dragging this marker near its edge or not.
7383 // @option autoPanPadding: Point = Point(50, 50)
7384 // Distance (in pixels to the left/right and to the top/bottom) of the
7385 // map edge to start panning the map.
7386 autoPanPadding: [50, 50],
7388 // @option autoPanSpeed: Number = 10
7389 // Number of pixels the map should pan by.
7395 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
7398 initialize: function (latlng, options) {
7399 setOptions(this, options);
7400 this._latlng = toLatLng(latlng);
7403 onAdd: function (map) {
7404 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
7406 if (this._zoomAnimated) {
7407 map.on('zoomanim', this._animateZoom, this);
7414 onRemove: function (map) {
7415 if (this.dragging && this.dragging.enabled()) {
7416 this.options.draggable = true;
7417 this.dragging.removeHooks();
7419 delete this.dragging;
7421 if (this._zoomAnimated) {
7422 map.off('zoomanim', this._animateZoom, this);
7426 this._removeShadow();
7429 getEvents: function () {
7432 viewreset: this.update
7436 // @method getLatLng: LatLng
7437 // Returns the current geographical position of the marker.
7438 getLatLng: function () {
7439 return this._latlng;
7442 // @method setLatLng(latlng: LatLng): this
7443 // Changes the marker position to the given point.
7444 setLatLng: function (latlng) {
7445 var oldLatLng = this._latlng;
7446 this._latlng = toLatLng(latlng);
7449 // @event move: Event
7450 // 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`.
7451 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7454 // @method setZIndexOffset(offset: Number): this
7455 // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
7456 setZIndexOffset: function (offset) {
7457 this.options.zIndexOffset = offset;
7458 return this.update();
7461 // @method setIcon(icon: Icon): this
7462 // Changes the marker icon.
7463 setIcon: function (icon) {
7465 this.options.icon = icon;
7473 this.bindPopup(this._popup, this._popup.options);
7479 getElement: function () {
7483 update: function () {
7485 if (this._icon && this._map) {
7486 var pos = this._map.latLngToLayerPoint(this._latlng).round();
7493 _initIcon: function () {
7494 var options = this.options,
7495 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
7497 var icon = options.icon.createIcon(this._icon),
7500 // if we're not reusing the icon, remove the old one and init new one
7501 if (icon !== this._icon) {
7507 if (options.title) {
7508 icon.title = options.title;
7511 if (icon.tagName === 'IMG') {
7512 icon.alt = options.alt || '';
7516 addClass(icon, classToAdd);
7518 if (options.keyboard) {
7519 icon.tabIndex = '0';
7524 if (options.riseOnHover) {
7526 mouseover: this._bringToFront,
7527 mouseout: this._resetZIndex
7531 var newShadow = options.icon.createShadow(this._shadow),
7534 if (newShadow !== this._shadow) {
7535 this._removeShadow();
7540 addClass(newShadow, classToAdd);
7543 this._shadow = newShadow;
7546 if (options.opacity < 1) {
7547 this._updateOpacity();
7552 this.getPane().appendChild(this._icon);
7554 this._initInteraction();
7555 if (newShadow && addShadow) {
7556 this.getPane('shadowPane').appendChild(this._shadow);
7560 _removeIcon: function () {
7561 if (this.options.riseOnHover) {
7563 mouseover: this._bringToFront,
7564 mouseout: this._resetZIndex
7569 this.removeInteractiveTarget(this._icon);
7574 _removeShadow: function () {
7576 remove(this._shadow);
7578 this._shadow = null;
7581 _setPos: function (pos) {
7582 setPosition(this._icon, pos);
7585 setPosition(this._shadow, pos);
7588 this._zIndex = pos.y + this.options.zIndexOffset;
7590 this._resetZIndex();
7593 _updateZIndex: function (offset) {
7594 this._icon.style.zIndex = this._zIndex + offset;
7597 _animateZoom: function (opt) {
7598 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
7603 _initInteraction: function () {
7605 if (!this.options.interactive) { return; }
7607 addClass(this._icon, 'leaflet-interactive');
7609 this.addInteractiveTarget(this._icon);
7612 var draggable = this.options.draggable;
7613 if (this.dragging) {
7614 draggable = this.dragging.enabled();
7615 this.dragging.disable();
7618 this.dragging = new MarkerDrag(this);
7621 this.dragging.enable();
7626 // @method setOpacity(opacity: Number): this
7627 // Changes the opacity of the marker.
7628 setOpacity: function (opacity) {
7629 this.options.opacity = opacity;
7631 this._updateOpacity();
7637 _updateOpacity: function () {
7638 var opacity = this.options.opacity;
7640 setOpacity(this._icon, opacity);
7643 setOpacity(this._shadow, opacity);
7647 _bringToFront: function () {
7648 this._updateZIndex(this.options.riseOffset);
7651 _resetZIndex: function () {
7652 this._updateZIndex(0);
7655 _getPopupAnchor: function () {
7656 return this.options.icon.options.popupAnchor;
7659 _getTooltipAnchor: function () {
7660 return this.options.icon.options.tooltipAnchor;
7665 // factory L.marker(latlng: LatLng, options? : Marker options)
7667 // @factory L.marker(latlng: LatLng, options? : Marker options)
7668 // Instantiates a Marker object given a geographical point and optionally an options object.
7669 function marker(latlng, options) {
7670 return new Marker(latlng, options);
7676 * @inherits Interactive layer
7678 * An abstract class that contains options and constants shared between vector
7679 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
7682 var Path = Layer.extend({
7685 // @aka Path options
7687 // @option stroke: Boolean = true
7688 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
7691 // @option color: String = '#3388ff'
7695 // @option weight: Number = 3
7696 // Stroke width in pixels
7699 // @option opacity: Number = 1.0
7703 // @option lineCap: String= 'round'
7704 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
7707 // @option lineJoin: String = 'round'
7708 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
7711 // @option dashArray: String = null
7712 // 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).
7715 // @option dashOffset: String = null
7716 // 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).
7719 // @option fill: Boolean = depends
7720 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
7723 // @option fillColor: String = *
7724 // Fill color. Defaults to the value of the [`color`](#path-color) option
7727 // @option fillOpacity: Number = 0.2
7731 // @option fillRule: String = 'evenodd'
7732 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
7733 fillRule: 'evenodd',
7737 // Option inherited from "Interactive layer" abstract class
7740 // @option bubblingMouseEvents: Boolean = true
7741 // When `true`, a mouse event on this path will trigger the same event on the map
7742 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7743 bubblingMouseEvents: true
7746 beforeAdd: function (map) {
7747 // Renderer is set here because we need to call renderer.getEvents
7748 // before this.getEvents.
7749 this._renderer = map.getRenderer(this);
7752 onAdd: function () {
7753 this._renderer._initPath(this);
7755 this._renderer._addPath(this);
7758 onRemove: function () {
7759 this._renderer._removePath(this);
7762 // @method redraw(): this
7763 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
7764 redraw: function () {
7766 this._renderer._updatePath(this);
7771 // @method setStyle(style: Path options): this
7772 // Changes the appearance of a Path based on the options in the `Path options` object.
7773 setStyle: function (style) {
7774 setOptions(this, style);
7775 if (this._renderer) {
7776 this._renderer._updateStyle(this);
7781 // @method bringToFront(): this
7782 // Brings the layer to the top of all path layers.
7783 bringToFront: function () {
7784 if (this._renderer) {
7785 this._renderer._bringToFront(this);
7790 // @method bringToBack(): this
7791 // Brings the layer to the bottom of all path layers.
7792 bringToBack: function () {
7793 if (this._renderer) {
7794 this._renderer._bringToBack(this);
7799 getElement: function () {
7803 _reset: function () {
7804 // defined in child classes
7809 _clickTolerance: function () {
7810 // used when doing hit detection for Canvas layers
7811 return (this.options.stroke ? this.options.weight / 2 : 0) + this._renderer.options.tolerance;
7816 * @class CircleMarker
7817 * @aka L.CircleMarker
7820 * A circle of a fixed size with radius specified in pixels. Extends `Path`.
7823 var CircleMarker = Path.extend({
7826 // @aka CircleMarker options
7830 // @option radius: Number = 10
7831 // Radius of the circle marker, in pixels
7835 initialize: function (latlng, options) {
7836 setOptions(this, options);
7837 this._latlng = toLatLng(latlng);
7838 this._radius = this.options.radius;
7841 // @method setLatLng(latLng: LatLng): this
7842 // Sets the position of a circle marker to a new location.
7843 setLatLng: function (latlng) {
7844 this._latlng = toLatLng(latlng);
7846 return this.fire('move', {latlng: this._latlng});
7849 // @method getLatLng(): LatLng
7850 // Returns the current geographical position of the circle marker
7851 getLatLng: function () {
7852 return this._latlng;
7855 // @method setRadius(radius: Number): this
7856 // Sets the radius of a circle marker. Units are in pixels.
7857 setRadius: function (radius) {
7858 this.options.radius = this._radius = radius;
7859 return this.redraw();
7862 // @method getRadius(): Number
7863 // Returns the current radius of the circle
7864 getRadius: function () {
7865 return this._radius;
7868 setStyle : function (options) {
7869 var radius = options && options.radius || this._radius;
7870 Path.prototype.setStyle.call(this, options);
7871 this.setRadius(radius);
7875 _project: function () {
7876 this._point = this._map.latLngToLayerPoint(this._latlng);
7877 this._updateBounds();
7880 _updateBounds: function () {
7881 var r = this._radius,
7882 r2 = this._radiusY || r,
7883 w = this._clickTolerance(),
7884 p = [r + w, r2 + w];
7885 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
7888 _update: function () {
7894 _updatePath: function () {
7895 this._renderer._updateCircle(this);
7898 _empty: function () {
7899 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
7902 // Needed by the `Canvas` renderer for interactivity
7903 _containsPoint: function (p) {
7904 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
7909 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
7910 // Instantiates a circle marker object given a geographical point, and an optional options object.
7911 function circleMarker(latlng, options) {
7912 return new CircleMarker(latlng, options);
7918 * @inherits CircleMarker
7920 * A class for drawing circle overlays on a map. Extends `CircleMarker`.
7922 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
7927 * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
7931 var Circle = CircleMarker.extend({
7933 initialize: function (latlng, options, legacyOptions) {
7934 if (typeof options === 'number') {
7935 // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
7936 options = extend({}, legacyOptions, {radius: options});
7938 setOptions(this, options);
7939 this._latlng = toLatLng(latlng);
7941 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
7944 // @aka Circle options
7945 // @option radius: Number; Radius of the circle, in meters.
7946 this._mRadius = this.options.radius;
7949 // @method setRadius(radius: Number): this
7950 // Sets the radius of a circle. Units are in meters.
7951 setRadius: function (radius) {
7952 this._mRadius = radius;
7953 return this.redraw();
7956 // @method getRadius(): Number
7957 // Returns the current radius of a circle. Units are in meters.
7958 getRadius: function () {
7959 return this._mRadius;
7962 // @method getBounds(): LatLngBounds
7963 // Returns the `LatLngBounds` of the path.
7964 getBounds: function () {
7965 var half = [this._radius, this._radiusY || this._radius];
7967 return new LatLngBounds(
7968 this._map.layerPointToLatLng(this._point.subtract(half)),
7969 this._map.layerPointToLatLng(this._point.add(half)));
7972 setStyle: Path.prototype.setStyle,
7974 _project: function () {
7976 var lng = this._latlng.lng,
7977 lat = this._latlng.lat,
7979 crs = map.options.crs;
7981 if (crs.distance === Earth.distance) {
7982 var d = Math.PI / 180,
7983 latR = (this._mRadius / Earth.R) / d,
7984 top = map.project([lat + latR, lng]),
7985 bottom = map.project([lat - latR, lng]),
7986 p = top.add(bottom).divideBy(2),
7987 lat2 = map.unproject(p).lat,
7988 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
7989 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
7991 if (isNaN(lngR) || lngR === 0) {
7992 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
7995 this._point = p.subtract(map.getPixelOrigin());
7996 this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
7997 this._radiusY = p.y - top.y;
8000 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
8002 this._point = map.latLngToLayerPoint(this._latlng);
8003 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
8006 this._updateBounds();
8010 // @factory L.circle(latlng: LatLng, options?: Circle options)
8011 // Instantiates a circle object given a geographical point, and an options object
8012 // which contains the circle radius.
8014 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
8015 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
8016 // Do not use in new applications or plugins.
8017 function circle(latlng, options, legacyOptions) {
8018 return new Circle(latlng, options, legacyOptions);
8026 * A class for drawing polyline overlays on a map. Extends `Path`.
8031 * // create a red polyline from an array of LatLng points
8038 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
8040 * // zoom the map to the polyline
8041 * map.fitBounds(polyline.getBounds());
8044 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
8047 * // create a red polyline from an array of arrays of LatLng points
8049 * [[45.51, -122.68],
8060 var Polyline = Path.extend({
8063 // @aka Polyline options
8065 // @option smoothFactor: Number = 1.0
8066 // How much to simplify the polyline on each zoom level. More means
8067 // better performance and smoother look, and less means more accurate representation.
8070 // @option noClip: Boolean = false
8071 // Disable polyline clipping.
8075 initialize: function (latlngs, options) {
8076 setOptions(this, options);
8077 this._setLatLngs(latlngs);
8080 // @method getLatLngs(): LatLng[]
8081 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8082 getLatLngs: function () {
8083 return this._latlngs;
8086 // @method setLatLngs(latlngs: LatLng[]): this
8087 // Replaces all the points in the polyline with the given array of geographical points.
8088 setLatLngs: function (latlngs) {
8089 this._setLatLngs(latlngs);
8090 return this.redraw();
8093 // @method isEmpty(): Boolean
8094 // Returns `true` if the Polyline has no LatLngs.
8095 isEmpty: function () {
8096 return !this._latlngs.length;
8099 // @method closestLayerPoint(p: Point): Point
8100 // Returns the point closest to `p` on the Polyline.
8101 closestLayerPoint: function (p) {
8102 var minDistance = Infinity,
8104 closest = _sqClosestPointOnSegment,
8107 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8108 var points = this._parts[j];
8110 for (var i = 1, len = points.length; i < len; i++) {
8114 var sqDist = closest(p, p1, p2, true);
8116 if (sqDist < minDistance) {
8117 minDistance = sqDist;
8118 minPoint = closest(p, p1, p2);
8123 minPoint.distance = Math.sqrt(minDistance);
8128 // @method getCenter(): LatLng
8129 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
8130 getCenter: function () {
8131 // throws error when not yet added to map as this center calculation requires projected coordinates
8133 throw new Error('Must add layer to map before using getCenter()');
8136 var i, halfDist, segDist, dist, p1, p2, ratio,
8137 points = this._rings[0],
8138 len = points.length;
8140 if (!len) { return null; }
8142 // polyline centroid algorithm; only uses the first ring if there are multiple
8144 for (i = 0, halfDist = 0; i < len - 1; i++) {
8145 halfDist += points[i].distanceTo(points[i + 1]) / 2;
8148 // The line is so small in the current view that all points are on the same pixel.
8149 if (halfDist === 0) {
8150 return this._map.layerPointToLatLng(points[0]);
8153 for (i = 0, dist = 0; i < len - 1; i++) {
8156 segDist = p1.distanceTo(p2);
8159 if (dist > halfDist) {
8160 ratio = (dist - halfDist) / segDist;
8161 return this._map.layerPointToLatLng([
8162 p2.x - ratio * (p2.x - p1.x),
8163 p2.y - ratio * (p2.y - p1.y)
8169 // @method getBounds(): LatLngBounds
8170 // Returns the `LatLngBounds` of the path.
8171 getBounds: function () {
8172 return this._bounds;
8175 // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
8176 // Adds a given point to the polyline. By default, adds to the first ring of
8177 // the polyline in case of a multi-polyline, but can be overridden by passing
8178 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8179 addLatLng: function (latlng, latlngs) {
8180 latlngs = latlngs || this._defaultShape();
8181 latlng = toLatLng(latlng);
8182 latlngs.push(latlng);
8183 this._bounds.extend(latlng);
8184 return this.redraw();
8187 _setLatLngs: function (latlngs) {
8188 this._bounds = new LatLngBounds();
8189 this._latlngs = this._convertLatLngs(latlngs);
8192 _defaultShape: function () {
8193 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
8196 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8197 _convertLatLngs: function (latlngs) {
8199 flat = isFlat(latlngs);
8201 for (var i = 0, len = latlngs.length; i < len; i++) {
8203 result[i] = toLatLng(latlngs[i]);
8204 this._bounds.extend(result[i]);
8206 result[i] = this._convertLatLngs(latlngs[i]);
8213 _project: function () {
8214 var pxBounds = new Bounds();
8216 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8218 var w = this._clickTolerance(),
8219 p = new Point(w, w);
8221 if (this._bounds.isValid() && pxBounds.isValid()) {
8222 pxBounds.min._subtract(p);
8223 pxBounds.max._add(p);
8224 this._pxBounds = pxBounds;
8228 // recursively turns latlngs into a set of rings with projected coordinates
8229 _projectLatlngs: function (latlngs, result, projectedBounds) {
8230 var flat = latlngs[0] instanceof LatLng,
8231 len = latlngs.length,
8236 for (i = 0; i < len; i++) {
8237 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8238 projectedBounds.extend(ring[i]);
8242 for (i = 0; i < len; i++) {
8243 this._projectLatlngs(latlngs[i], result, projectedBounds);
8248 // clip polyline by renderer bounds so that we have less to render for performance
8249 _clipPoints: function () {
8250 var bounds = this._renderer._bounds;
8253 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8257 if (this.options.noClip) {
8258 this._parts = this._rings;
8262 var parts = this._parts,
8263 i, j, k, len, len2, segment, points;
8265 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8266 points = this._rings[i];
8268 for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8269 segment = clipSegment(points[j], points[j + 1], bounds, j, true);
8271 if (!segment) { continue; }
8273 parts[k] = parts[k] || [];
8274 parts[k].push(segment[0]);
8276 // if segment goes out of screen, or it's the last one, it's the end of the line part
8277 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8278 parts[k].push(segment[1]);
8285 // simplify each clipped part of the polyline for performance
8286 _simplifyPoints: function () {
8287 var parts = this._parts,
8288 tolerance = this.options.smoothFactor;
8290 for (var i = 0, len = parts.length; i < len; i++) {
8291 parts[i] = simplify(parts[i], tolerance);
8295 _update: function () {
8296 if (!this._map) { return; }
8299 this._simplifyPoints();
8303 _updatePath: function () {
8304 this._renderer._updatePoly(this);
8307 // Needed by the `Canvas` renderer for interactivity
8308 _containsPoint: function (p, closed) {
8309 var i, j, k, len, len2, part,
8310 w = this._clickTolerance();
8312 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8314 // hit detection for polylines
8315 for (i = 0, len = this._parts.length; i < len; i++) {
8316 part = this._parts[i];
8318 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8319 if (!closed && (j === 0)) { continue; }
8321 if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
8330 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8331 // Instantiates a polyline object given an array of geographical points and
8332 // optionally an options object. You can create a `Polyline` object with
8333 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8334 // of geographic points.
8335 function polyline(latlngs, options) {
8336 return new Polyline(latlngs, options);
8339 // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
8340 Polyline._flat = _flat;
8345 * @inherits Polyline
8347 * A class for drawing polygon overlays on a map. Extends `Polyline`.
8349 * 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.
8355 * // create a red polygon from an array of LatLng points
8356 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
8358 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8360 * // zoom the map to the polygon
8361 * map.fitBounds(polygon.getBounds());
8364 * 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:
8368 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8369 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8373 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8377 * [ // first polygon
8378 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8379 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8381 * [ // second polygon
8382 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
8388 var Polygon = Polyline.extend({
8394 isEmpty: function () {
8395 return !this._latlngs.length || !this._latlngs[0].length;
8398 getCenter: function () {
8399 // throws error when not yet added to map as this center calculation requires projected coordinates
8401 throw new Error('Must add layer to map before using getCenter()');
8404 var i, j, p1, p2, f, area, x, y, center,
8405 points = this._rings[0],
8406 len = points.length;
8408 if (!len) { return null; }
8410 // polygon centroid algorithm; only uses the first ring if there are multiple
8414 for (i = 0, j = len - 1; i < len; j = i++) {
8418 f = p1.y * p2.x - p2.y * p1.x;
8419 x += (p1.x + p2.x) * f;
8420 y += (p1.y + p2.y) * f;
8425 // Polygon is so small that all points are on same pixel.
8428 center = [x / area, y / area];
8430 return this._map.layerPointToLatLng(center);
8433 _convertLatLngs: function (latlngs) {
8434 var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
8435 len = result.length;
8437 // remove last point if it equals first one
8438 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
8444 _setLatLngs: function (latlngs) {
8445 Polyline.prototype._setLatLngs.call(this, latlngs);
8446 if (isFlat(this._latlngs)) {
8447 this._latlngs = [this._latlngs];
8451 _defaultShape: function () {
8452 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8455 _clipPoints: function () {
8456 // polygons need a different clipping algorithm so we redefine that
8458 var bounds = this._renderer._bounds,
8459 w = this.options.weight,
8460 p = new Point(w, w);
8462 // increase clip padding by stroke width to avoid stroke on clip edges
8463 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
8466 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8470 if (this.options.noClip) {
8471 this._parts = this._rings;
8475 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8476 clipped = clipPolygon(this._rings[i], bounds, true);
8477 if (clipped.length) {
8478 this._parts.push(clipped);
8483 _updatePath: function () {
8484 this._renderer._updatePoly(this, true);
8487 // Needed by the `Canvas` renderer for interactivity
8488 _containsPoint: function (p) {
8490 part, p1, p2, i, j, k, len, len2;
8492 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8494 // ray casting algorithm for detecting if point is in polygon
8495 for (i = 0, len = this._parts.length; i < len; i++) {
8496 part = this._parts[i];
8498 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8502 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)) {
8508 // also check if it's on polygon stroke
8509 return inside || Polyline.prototype._containsPoint.call(this, p, true);
8515 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8516 function polygon(latlngs, options) {
8517 return new Polygon(latlngs, options);
8523 * @inherits FeatureGroup
8525 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
8526 * GeoJSON data and display it on the map. Extends `FeatureGroup`.
8532 * style: function (feature) {
8533 * return {color: feature.properties.color};
8535 * }).bindPopup(function (layer) {
8536 * return layer.feature.properties.description;
8541 var GeoJSON = FeatureGroup.extend({
8544 * @aka GeoJSON options
8546 * @option pointToLayer: Function = *
8547 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
8548 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
8549 * The default is to spawn a default `Marker`:
8551 * function(geoJsonPoint, latlng) {
8552 * return L.marker(latlng);
8556 * @option style: Function = *
8557 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
8558 * called internally when data is added.
8559 * The default value is to not override any defaults:
8561 * function (geoJsonFeature) {
8566 * @option onEachFeature: Function = *
8567 * A `Function` that will be called once for each created `Feature`, after it has
8568 * been created and styled. Useful for attaching events and popups to features.
8569 * The default is to do nothing with the newly created layers:
8571 * function (feature, layer) {}
8574 * @option filter: Function = *
8575 * A `Function` that will be used to decide whether to include a feature or not.
8576 * The default is to include all features:
8578 * function (geoJsonFeature) {
8582 * Note: dynamically changing the `filter` option will have effect only on newly
8583 * added data. It will _not_ re-evaluate already included features.
8585 * @option coordsToLatLng: Function = *
8586 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
8587 * The default is the `coordsToLatLng` static method.
8590 initialize: function (geojson, options) {
8591 setOptions(this, options);
8596 this.addData(geojson);
8600 // @method addData( <GeoJSON> data ): this
8601 // Adds a GeoJSON object to the layer.
8602 addData: function (geojson) {
8603 var features = isArray(geojson) ? geojson : geojson.features,
8607 for (i = 0, len = features.length; i < len; i++) {
8608 // only add this if geometry or geometries are set and not null
8609 feature = features[i];
8610 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
8611 this.addData(feature);
8617 var options = this.options;
8619 if (options.filter && !options.filter(geojson)) { return this; }
8621 var layer = geometryToLayer(geojson, options);
8625 layer.feature = asFeature(geojson);
8627 layer.defaultOptions = layer.options;
8628 this.resetStyle(layer);
8630 if (options.onEachFeature) {
8631 options.onEachFeature(geojson, layer);
8634 return this.addLayer(layer);
8637 // @method resetStyle( <Path> layer ): this
8638 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
8639 resetStyle: function (layer) {
8640 // reset any custom styles
8641 layer.options = extend({}, layer.defaultOptions);
8642 this._setLayerStyle(layer, this.options.style);
8646 // @method setStyle( <Function> style ): this
8647 // Changes styles of GeoJSON vector layers with the given style function.
8648 setStyle: function (style) {
8649 return this.eachLayer(function (layer) {
8650 this._setLayerStyle(layer, style);
8654 _setLayerStyle: function (layer, style) {
8655 if (typeof style === 'function') {
8656 style = style(layer.feature);
8658 if (layer.setStyle) {
8659 layer.setStyle(style);
8665 // There are several static functions which can be called without instantiating L.GeoJSON:
8667 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
8668 // Creates a `Layer` from a given GeoJSON feature. Can use a custom
8669 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
8670 // functions if provided as options.
8671 function geometryToLayer(geojson, options) {
8673 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
8674 coords = geometry ? geometry.coordinates : null,
8676 pointToLayer = options && options.pointToLayer,
8677 _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
8678 latlng, latlngs, i, len;
8680 if (!coords && !geometry) {
8684 switch (geometry.type) {
8686 latlng = _coordsToLatLng(coords);
8687 return pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng);
8690 for (i = 0, len = coords.length; i < len; i++) {
8691 latlng = _coordsToLatLng(coords[i]);
8692 layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng));
8694 return new FeatureGroup(layers);
8697 case 'MultiLineString':
8698 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
8699 return new Polyline(latlngs, options);
8702 case 'MultiPolygon':
8703 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
8704 return new Polygon(latlngs, options);
8706 case 'GeometryCollection':
8707 for (i = 0, len = geometry.geometries.length; i < len; i++) {
8708 var layer = geometryToLayer({
8709 geometry: geometry.geometries[i],
8711 properties: geojson.properties
8718 return new FeatureGroup(layers);
8721 throw new Error('Invalid GeoJSON object.');
8725 // @function coordsToLatLng(coords: Array): LatLng
8726 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
8727 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
8728 function coordsToLatLng(coords) {
8729 return new LatLng(coords[1], coords[0], coords[2]);
8732 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
8733 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
8734 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
8735 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
8736 function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
8739 for (var i = 0, len = coords.length, latlng; i < len; i++) {
8740 latlng = levelsDeep ?
8741 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
8742 (_coordsToLatLng || coordsToLatLng)(coords[i]);
8744 latlngs.push(latlng);
8750 // @function latLngToCoords(latlng: LatLng, precision?: Number): Array
8751 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
8752 function latLngToCoords(latlng, precision) {
8753 precision = typeof precision === 'number' ? precision : 6;
8754 return latlng.alt !== undefined ?
8755 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
8756 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
8759 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
8760 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
8761 // `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.
8762 function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
8765 for (var i = 0, len = latlngs.length; i < len; i++) {
8766 coords.push(levelsDeep ?
8767 latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) :
8768 latLngToCoords(latlngs[i], precision));
8771 if (!levelsDeep && closed) {
8772 coords.push(coords[0]);
8778 function getFeature(layer, newGeometry) {
8779 return layer.feature ?
8780 extend({}, layer.feature, {geometry: newGeometry}) :
8781 asFeature(newGeometry);
8784 // @function asFeature(geojson: Object): Object
8785 // Normalize GeoJSON geometries/features into GeoJSON features.
8786 function asFeature(geojson) {
8787 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
8798 var PointToGeoJSON = {
8799 toGeoJSON: function (precision) {
8800 return getFeature(this, {
8802 coordinates: latLngToCoords(this.getLatLng(), precision)
8807 // @namespace Marker
8808 // @method toGeoJSON(): Object
8809 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
8810 Marker.include(PointToGeoJSON);
8812 // @namespace CircleMarker
8813 // @method toGeoJSON(): Object
8814 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
8815 Circle.include(PointToGeoJSON);
8816 CircleMarker.include(PointToGeoJSON);
8819 // @namespace Polyline
8820 // @method toGeoJSON(): Object
8821 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
8823 toGeoJSON: function (precision) {
8824 var multi = !isFlat(this._latlngs);
8826 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
8828 return getFeature(this, {
8829 type: (multi ? 'Multi' : '') + 'LineString',
8835 // @namespace Polygon
8836 // @method toGeoJSON(): Object
8837 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
8839 toGeoJSON: function (precision) {
8840 var holes = !isFlat(this._latlngs),
8841 multi = holes && !isFlat(this._latlngs[0]);
8843 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
8849 return getFeature(this, {
8850 type: (multi ? 'Multi' : '') + 'Polygon',
8857 // @namespace LayerGroup
8858 LayerGroup.include({
8859 toMultiPoint: function (precision) {
8862 this.eachLayer(function (layer) {
8863 coords.push(layer.toGeoJSON(precision).geometry.coordinates);
8866 return getFeature(this, {
8872 // @method toGeoJSON(): Object
8873 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
8874 toGeoJSON: function (precision) {
8876 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
8878 if (type === 'MultiPoint') {
8879 return this.toMultiPoint(precision);
8882 var isGeometryCollection = type === 'GeometryCollection',
8885 this.eachLayer(function (layer) {
8886 if (layer.toGeoJSON) {
8887 var json = layer.toGeoJSON(precision);
8888 if (isGeometryCollection) {
8889 jsons.push(json.geometry);
8891 var feature = asFeature(json);
8892 // Squash nested feature collections
8893 if (feature.type === 'FeatureCollection') {
8894 jsons.push.apply(jsons, feature.features);
8896 jsons.push(feature);
8902 if (isGeometryCollection) {
8903 return getFeature(this, {
8905 type: 'GeometryCollection'
8910 type: 'FeatureCollection',
8916 // @namespace GeoJSON
8917 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
8918 // Creates a GeoJSON layer. Optionally accepts an object in
8919 // [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
8920 // (you can alternatively add it later with `addData` method) and an `options` object.
8921 function geoJSON(geojson, options) {
8922 return new GeoJSON(geojson, options);
8925 // Backward compatibility.
8926 var geoJson = geoJSON;
8929 * @class ImageOverlay
8930 * @aka L.ImageOverlay
8931 * @inherits Interactive layer
8933 * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
8938 * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
8939 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
8940 * L.imageOverlay(imageUrl, imageBounds).addTo(map);
8944 var ImageOverlay = Layer.extend({
8947 // @aka ImageOverlay options
8949 // @option opacity: Number = 1.0
8950 // The opacity of the image overlay.
8953 // @option alt: String = ''
8954 // Text for the `alt` attribute of the image (useful for accessibility).
8957 // @option interactive: Boolean = false
8958 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
8961 // @option crossOrigin: Boolean|String = false
8962 // Whether the crossOrigin attribute will be added to the image.
8963 // 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.
8964 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
8967 // @option errorOverlayUrl: String = ''
8968 // URL to the overlay image to show in place of the overlay that failed to load.
8969 errorOverlayUrl: '',
8971 // @option zIndex: Number = 1
8972 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
8975 // @option className: String = ''
8976 // A custom class name to assign to the image. Empty by default.
8980 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
8982 this._bounds = toLatLngBounds(bounds);
8984 setOptions(this, options);
8987 onAdd: function () {
8991 if (this.options.opacity < 1) {
8992 this._updateOpacity();
8996 if (this.options.interactive) {
8997 addClass(this._image, 'leaflet-interactive');
8998 this.addInteractiveTarget(this._image);
9001 this.getPane().appendChild(this._image);
9005 onRemove: function () {
9006 remove(this._image);
9007 if (this.options.interactive) {
9008 this.removeInteractiveTarget(this._image);
9012 // @method setOpacity(opacity: Number): this
9013 // Sets the opacity of the overlay.
9014 setOpacity: function (opacity) {
9015 this.options.opacity = opacity;
9018 this._updateOpacity();
9023 setStyle: function (styleOpts) {
9024 if (styleOpts.opacity) {
9025 this.setOpacity(styleOpts.opacity);
9030 // @method bringToFront(): this
9031 // Brings the layer to the top of all overlays.
9032 bringToFront: function () {
9034 toFront(this._image);
9039 // @method bringToBack(): this
9040 // Brings the layer to the bottom of all overlays.
9041 bringToBack: function () {
9043 toBack(this._image);
9048 // @method setUrl(url: String): this
9049 // Changes the URL of the image.
9050 setUrl: function (url) {
9054 this._image.src = url;
9059 // @method setBounds(bounds: LatLngBounds): this
9060 // Update the bounds that this ImageOverlay covers
9061 setBounds: function (bounds) {
9062 this._bounds = toLatLngBounds(bounds);
9070 getEvents: function () {
9073 viewreset: this._reset
9076 if (this._zoomAnimated) {
9077 events.zoomanim = this._animateZoom;
9083 // @method setZIndex(value: Number): this
9084 // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
9085 setZIndex: function (value) {
9086 this.options.zIndex = value;
9087 this._updateZIndex();
9091 // @method getBounds(): LatLngBounds
9092 // Get the bounds that this ImageOverlay covers
9093 getBounds: function () {
9094 return this._bounds;
9097 // @method getElement(): HTMLElement
9098 // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
9099 // used by this overlay.
9100 getElement: function () {
9104 _initImage: function () {
9105 var wasElementSupplied = this._url.tagName === 'IMG';
9106 var img = this._image = wasElementSupplied ? this._url : create$1('img');
9108 addClass(img, 'leaflet-image-layer');
9109 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
9110 if (this.options.className) { addClass(img, this.options.className); }
9112 img.onselectstart = falseFn;
9113 img.onmousemove = falseFn;
9115 // @event load: Event
9116 // Fired when the ImageOverlay layer has loaded its image
9117 img.onload = bind(this.fire, this, 'load');
9118 img.onerror = bind(this._overlayOnError, this, 'error');
9120 if (this.options.crossOrigin || this.options.crossOrigin === '') {
9121 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
9124 if (this.options.zIndex) {
9125 this._updateZIndex();
9128 if (wasElementSupplied) {
9129 this._url = img.src;
9133 img.src = this._url;
9134 img.alt = this.options.alt;
9137 _animateZoom: function (e) {
9138 var scale = this._map.getZoomScale(e.zoom),
9139 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
9141 setTransform(this._image, offset, scale);
9144 _reset: function () {
9145 var image = this._image,
9146 bounds = new Bounds(
9147 this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
9148 this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
9149 size = bounds.getSize();
9151 setPosition(image, bounds.min);
9153 image.style.width = size.x + 'px';
9154 image.style.height = size.y + 'px';
9157 _updateOpacity: function () {
9158 setOpacity(this._image, this.options.opacity);
9161 _updateZIndex: function () {
9162 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
9163 this._image.style.zIndex = this.options.zIndex;
9167 _overlayOnError: function () {
9168 // @event error: Event
9169 // Fired when the ImageOverlay layer fails to load its image
9172 var errorUrl = this.options.errorOverlayUrl;
9173 if (errorUrl && this._url !== errorUrl) {
9174 this._url = errorUrl;
9175 this._image.src = errorUrl;
9180 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
9181 // Instantiates an image overlay object given the URL of the image and the
9182 // geographical bounds it is tied to.
9183 var imageOverlay = function (url, bounds, options) {
9184 return new ImageOverlay(url, bounds, options);
9188 * @class VideoOverlay
9189 * @aka L.VideoOverlay
9190 * @inherits ImageOverlay
9192 * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
9194 * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
9200 * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
9201 * videoBounds = [[ 32, -130], [ 13, -100]];
9202 * L.videoOverlay(videoUrl, videoBounds ).addTo(map);
9206 var VideoOverlay = ImageOverlay.extend({
9209 // @aka VideoOverlay options
9211 // @option autoplay: Boolean = true
9212 // Whether the video starts playing automatically when loaded.
9215 // @option loop: Boolean = true
9216 // Whether the video will loop back to the beginning when played.
9220 _initImage: function () {
9221 var wasElementSupplied = this._url.tagName === 'VIDEO';
9222 var vid = this._image = wasElementSupplied ? this._url : create$1('video');
9224 addClass(vid, 'leaflet-image-layer');
9225 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
9227 vid.onselectstart = falseFn;
9228 vid.onmousemove = falseFn;
9230 // @event load: Event
9231 // Fired when the video has finished loading the first frame
9232 vid.onloadeddata = bind(this.fire, this, 'load');
9234 if (wasElementSupplied) {
9235 var sourceElements = vid.getElementsByTagName('source');
9237 for (var j = 0; j < sourceElements.length; j++) {
9238 sources.push(sourceElements[j].src);
9241 this._url = (sourceElements.length > 0) ? sources : [vid.src];
9245 if (!isArray(this._url)) { this._url = [this._url]; }
9247 vid.autoplay = !!this.options.autoplay;
9248 vid.loop = !!this.options.loop;
9249 for (var i = 0; i < this._url.length; i++) {
9250 var source = create$1('source');
9251 source.src = this._url[i];
9252 vid.appendChild(source);
9256 // @method getElement(): HTMLVideoElement
9257 // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
9258 // used by this overlay.
9262 // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
9263 // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
9264 // geographical bounds it is tied to.
9266 function videoOverlay(video, bounds, options) {
9267 return new VideoOverlay(video, bounds, options);
9274 * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
9277 // @namespace DivOverlay
9278 var DivOverlay = Layer.extend({
9281 // @aka DivOverlay options
9283 // @option offset: Point = Point(0, 7)
9284 // The offset of the popup position. Useful to control the anchor
9285 // of the popup when opening it on some overlays.
9288 // @option className: String = ''
9289 // A custom CSS class name to assign to the popup.
9292 // @option pane: String = 'popupPane'
9293 // `Map pane` where the popup will be added.
9297 initialize: function (options, source) {
9298 setOptions(this, options);
9300 this._source = source;
9303 onAdd: function (map) {
9304 this._zoomAnimated = map._zoomAnimated;
9306 if (!this._container) {
9310 if (map._fadeAnimated) {
9311 setOpacity(this._container, 0);
9314 clearTimeout(this._removeTimeout);
9315 this.getPane().appendChild(this._container);
9318 if (map._fadeAnimated) {
9319 setOpacity(this._container, 1);
9322 this.bringToFront();
9325 onRemove: function (map) {
9326 if (map._fadeAnimated) {
9327 setOpacity(this._container, 0);
9328 this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
9330 remove(this._container);
9335 // @method getLatLng: LatLng
9336 // Returns the geographical point of popup.
9337 getLatLng: function () {
9338 return this._latlng;
9341 // @method setLatLng(latlng: LatLng): this
9342 // Sets the geographical point where the popup will open.
9343 setLatLng: function (latlng) {
9344 this._latlng = toLatLng(latlng);
9346 this._updatePosition();
9352 // @method getContent: String|HTMLElement
9353 // Returns the content of the popup.
9354 getContent: function () {
9355 return this._content;
9358 // @method setContent(htmlContent: String|HTMLElement|Function): this
9359 // 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.
9360 setContent: function (content) {
9361 this._content = content;
9366 // @method getElement: String|HTMLElement
9367 // Alias for [getContent()](#popup-getcontent)
9368 getElement: function () {
9369 return this._container;
9372 // @method update: null
9373 // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
9374 update: function () {
9375 if (!this._map) { return; }
9377 this._container.style.visibility = 'hidden';
9379 this._updateContent();
9380 this._updateLayout();
9381 this._updatePosition();
9383 this._container.style.visibility = '';
9388 getEvents: function () {
9390 zoom: this._updatePosition,
9391 viewreset: this._updatePosition
9394 if (this._zoomAnimated) {
9395 events.zoomanim = this._animateZoom;
9400 // @method isOpen: Boolean
9401 // Returns `true` when the popup is visible on the map.
9402 isOpen: function () {
9403 return !!this._map && this._map.hasLayer(this);
9406 // @method bringToFront: this
9407 // Brings this popup in front of other popups (in the same map pane).
9408 bringToFront: function () {
9410 toFront(this._container);
9415 // @method bringToBack: this
9416 // Brings this popup to the back of other popups (in the same map pane).
9417 bringToBack: function () {
9419 toBack(this._container);
9424 _updateContent: function () {
9425 if (!this._content) { return; }
9427 var node = this._contentNode;
9428 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
9430 if (typeof content === 'string') {
9431 node.innerHTML = content;
9433 while (node.hasChildNodes()) {
9434 node.removeChild(node.firstChild);
9436 node.appendChild(content);
9438 this.fire('contentupdate');
9441 _updatePosition: function () {
9442 if (!this._map) { return; }
9444 var pos = this._map.latLngToLayerPoint(this._latlng),
9445 offset = toPoint(this.options.offset),
9446 anchor = this._getAnchor();
9448 if (this._zoomAnimated) {
9449 setPosition(this._container, pos.add(anchor));
9451 offset = offset.add(pos).add(anchor);
9454 var bottom = this._containerBottom = -offset.y,
9455 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
9457 // bottom position the popup in case the height of the popup changes (images loading etc)
9458 this._container.style.bottom = bottom + 'px';
9459 this._container.style.left = left + 'px';
9462 _getAnchor: function () {
9470 * @inherits DivOverlay
9472 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
9473 * open popups while making sure that only one popup is open at one time
9474 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
9478 * If you want to just bind a popup to marker click and then open it, it's really easy:
9481 * marker.bindPopup(popupContent).openPopup();
9483 * Path overlays like polylines also have a `bindPopup` method.
9484 * Here's a more complicated way to open a popup on a map:
9487 * var popup = L.popup()
9488 * .setLatLng(latlng)
9489 * .setContent('<p>Hello world!<br />This is a nice popup.</p>')
9496 var Popup = DivOverlay.extend({
9499 // @aka Popup options
9501 // @option maxWidth: Number = 300
9502 // Max width of the popup, in pixels.
9505 // @option minWidth: Number = 50
9506 // Min width of the popup, in pixels.
9509 // @option maxHeight: Number = null
9510 // If set, creates a scrollable container of the given height
9511 // inside a popup if its content exceeds it.
9514 // @option autoPan: Boolean = true
9515 // Set it to `false` if you don't want the map to do panning animation
9516 // to fit the opened popup.
9519 // @option autoPanPaddingTopLeft: Point = null
9520 // The margin between the popup and the top left corner of the map
9521 // view after autopanning was performed.
9522 autoPanPaddingTopLeft: null,
9524 // @option autoPanPaddingBottomRight: Point = null
9525 // The margin between the popup and the bottom right corner of the map
9526 // view after autopanning was performed.
9527 autoPanPaddingBottomRight: null,
9529 // @option autoPanPadding: Point = Point(5, 5)
9530 // Equivalent of setting both top left and bottom right autopan padding to the same value.
9531 autoPanPadding: [5, 5],
9533 // @option keepInView: Boolean = false
9534 // Set it to `true` if you want to prevent users from panning the popup
9535 // off of the screen while it is open.
9538 // @option closeButton: Boolean = true
9539 // Controls the presence of a close button in the popup.
9542 // @option autoClose: Boolean = true
9543 // Set it to `false` if you want to override the default behavior of
9544 // the popup closing when another popup is opened.
9547 // @option closeOnEscapeKey: Boolean = true
9548 // Set it to `false` if you want to override the default behavior of
9549 // the ESC key for closing of the popup.
9550 closeOnEscapeKey: true,
9552 // @option closeOnClick: Boolean = *
9553 // Set it if you want to override the default behavior of the popup closing when user clicks
9554 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
9556 // @option className: String = ''
9557 // A custom CSS class name to assign to the popup.
9562 // @method openOn(map: Map): this
9563 // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
9564 openOn: function (map) {
9565 map.openPopup(this);
9569 onAdd: function (map) {
9570 DivOverlay.prototype.onAdd.call(this, map);
9573 // @section Popup events
9574 // @event popupopen: PopupEvent
9575 // Fired when a popup is opened in the map
9576 map.fire('popupopen', {popup: this});
9580 // @section Popup events
9581 // @event popupopen: PopupEvent
9582 // Fired when a popup bound to this layer is opened
9583 this._source.fire('popupopen', {popup: this}, true);
9584 // For non-path layers, we toggle the popup when clicking
9585 // again the layer, so prevent the map to reopen it.
9586 if (!(this._source instanceof Path)) {
9587 this._source.on('preclick', stopPropagation);
9592 onRemove: function (map) {
9593 DivOverlay.prototype.onRemove.call(this, map);
9596 // @section Popup events
9597 // @event popupclose: PopupEvent
9598 // Fired when a popup in the map is closed
9599 map.fire('popupclose', {popup: this});
9603 // @section Popup events
9604 // @event popupclose: PopupEvent
9605 // Fired when a popup bound to this layer is closed
9606 this._source.fire('popupclose', {popup: this}, true);
9607 if (!(this._source instanceof Path)) {
9608 this._source.off('preclick', stopPropagation);
9613 getEvents: function () {
9614 var events = DivOverlay.prototype.getEvents.call(this);
9616 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
9617 events.preclick = this._close;
9620 if (this.options.keepInView) {
9621 events.moveend = this._adjustPan;
9627 _close: function () {
9629 this._map.closePopup(this);
9633 _initLayout: function () {
9634 var prefix = 'leaflet-popup',
9635 container = this._container = create$1('div',
9636 prefix + ' ' + (this.options.className || '') +
9637 ' leaflet-zoom-animated');
9639 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
9640 this._contentNode = create$1('div', prefix + '-content', wrapper);
9642 disableClickPropagation(wrapper);
9643 disableScrollPropagation(this._contentNode);
9644 on(wrapper, 'contextmenu', stopPropagation);
9646 this._tipContainer = create$1('div', prefix + '-tip-container', container);
9647 this._tip = create$1('div', prefix + '-tip', this._tipContainer);
9649 if (this.options.closeButton) {
9650 var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
9651 closeButton.href = '#close';
9652 closeButton.innerHTML = '×';
9654 on(closeButton, 'click', this._onCloseButtonClick, this);
9658 _updateLayout: function () {
9659 var container = this._contentNode,
9660 style = container.style;
9663 style.whiteSpace = 'nowrap';
9665 var width = container.offsetWidth;
9666 width = Math.min(width, this.options.maxWidth);
9667 width = Math.max(width, this.options.minWidth);
9669 style.width = (width + 1) + 'px';
9670 style.whiteSpace = '';
9674 var height = container.offsetHeight,
9675 maxHeight = this.options.maxHeight,
9676 scrolledClass = 'leaflet-popup-scrolled';
9678 if (maxHeight && height > maxHeight) {
9679 style.height = maxHeight + 'px';
9680 addClass(container, scrolledClass);
9682 removeClass(container, scrolledClass);
9685 this._containerWidth = this._container.offsetWidth;
9688 _animateZoom: function (e) {
9689 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
9690 anchor = this._getAnchor();
9691 setPosition(this._container, pos.add(anchor));
9694 _adjustPan: function () {
9695 if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; }
9697 var map = this._map,
9698 marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
9699 containerHeight = this._container.offsetHeight + marginBottom,
9700 containerWidth = this._containerWidth,
9701 layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
9703 layerPos._add(getPosition(this._container));
9705 var containerPos = map.layerPointToContainerPoint(layerPos),
9706 padding = toPoint(this.options.autoPanPadding),
9707 paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
9708 paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
9709 size = map.getSize(),
9713 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
9714 dx = containerPos.x + containerWidth - size.x + paddingBR.x;
9716 if (containerPos.x - dx - paddingTL.x < 0) { // left
9717 dx = containerPos.x - paddingTL.x;
9719 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
9720 dy = containerPos.y + containerHeight - size.y + paddingBR.y;
9722 if (containerPos.y - dy - paddingTL.y < 0) { // top
9723 dy = containerPos.y - paddingTL.y;
9727 // @section Popup events
9728 // @event autopanstart: Event
9729 // Fired when the map starts autopanning when opening a popup.
9732 .fire('autopanstart')
9737 _onCloseButtonClick: function (e) {
9742 _getAnchor: function () {
9743 // Where should we anchor the popup on the source layer?
9744 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
9750 // @factory L.popup(options?: Popup options, source?: Layer)
9751 // 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.
9752 var popup = function (options, source) {
9753 return new Popup(options, source);
9758 * @section Interaction Options
9759 * @option closePopupOnClick: Boolean = true
9760 * Set it to `false` if you don't want popups to close when user clicks the map.
9763 closePopupOnClick: true
9768 // @section Methods for Layers and Controls
9770 // @method openPopup(popup: Popup): this
9771 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
9773 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
9774 // Creates a popup with the specified content and options and opens it in the given point on a map.
9775 openPopup: function (popup, latlng, options) {
9776 if (!(popup instanceof Popup)) {
9777 popup = new Popup(options).setContent(popup);
9781 popup.setLatLng(latlng);
9784 if (this.hasLayer(popup)) {
9788 if (this._popup && this._popup.options.autoClose) {
9792 this._popup = popup;
9793 return this.addLayer(popup);
9796 // @method closePopup(popup?: Popup): this
9797 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
9798 closePopup: function (popup) {
9799 if (!popup || popup === this._popup) {
9800 popup = this._popup;
9804 this.removeLayer(popup);
9812 * @section Popup methods example
9814 * All layers share a set of methods convenient for binding popups to it.
9817 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
9818 * layer.openPopup();
9819 * layer.closePopup();
9822 * 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.
9825 // @section Popup methods
9828 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
9829 // Binds a popup to the layer with the passed `content` and sets up the
9830 // necessary event listeners. If a `Function` is passed it will receive
9831 // the layer as the first argument and should return a `String` or `HTMLElement`.
9832 bindPopup: function (content, options) {
9834 if (content instanceof Popup) {
9835 setOptions(content, options);
9836 this._popup = content;
9837 content._source = this;
9839 if (!this._popup || options) {
9840 this._popup = new Popup(options, this);
9842 this._popup.setContent(content);
9845 if (!this._popupHandlersAdded) {
9847 click: this._openPopup,
9848 keypress: this._onKeyPress,
9849 remove: this.closePopup,
9850 move: this._movePopup
9852 this._popupHandlersAdded = true;
9858 // @method unbindPopup(): this
9859 // Removes the popup previously bound with `bindPopup`.
9860 unbindPopup: function () {
9863 click: this._openPopup,
9864 keypress: this._onKeyPress,
9865 remove: this.closePopup,
9866 move: this._movePopup
9868 this._popupHandlersAdded = false;
9874 // @method openPopup(latlng?: LatLng): this
9875 // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
9876 openPopup: function (layer, latlng) {
9877 if (!(layer instanceof Layer)) {
9882 if (layer instanceof FeatureGroup) {
9883 for (var id in this._layers) {
9884 layer = this._layers[id];
9890 latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
9893 if (this._popup && this._map) {
9894 // set popup source to this layer
9895 this._popup._source = layer;
9897 // update the popup (content, layout, ect...)
9898 this._popup.update();
9900 // open the popup on the map
9901 this._map.openPopup(this._popup, latlng);
9907 // @method closePopup(): this
9908 // Closes the popup bound to this layer if it is open.
9909 closePopup: function () {
9911 this._popup._close();
9916 // @method togglePopup(): this
9917 // Opens or closes the popup bound to this layer depending on its current state.
9918 togglePopup: function (target) {
9920 if (this._popup._map) {
9923 this.openPopup(target);
9929 // @method isPopupOpen(): boolean
9930 // Returns `true` if the popup bound to this layer is currently open.
9931 isPopupOpen: function () {
9932 return (this._popup ? this._popup.isOpen() : false);
9935 // @method setPopupContent(content: String|HTMLElement|Popup): this
9936 // Sets the content of the popup bound to this layer.
9937 setPopupContent: function (content) {
9939 this._popup.setContent(content);
9944 // @method getPopup(): Popup
9945 // Returns the popup bound to this layer.
9946 getPopup: function () {
9950 _openPopup: function (e) {
9951 var layer = e.layer || e.target;
9961 // prevent map click
9964 // if this inherits from Path its a vector and we can just
9965 // open the popup at the new location
9966 if (layer instanceof Path) {
9967 this.openPopup(e.layer || e.target, e.latlng);
9971 // otherwise treat it like a marker and figure out
9972 // if we should toggle it open/closed
9973 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
9976 this.openPopup(layer, e.latlng);
9980 _movePopup: function (e) {
9981 this._popup.setLatLng(e.latlng);
9984 _onKeyPress: function (e) {
9985 if (e.originalEvent.keyCode === 13) {
9993 * @inherits DivOverlay
9995 * Used to display small texts on top of map layers.
10000 * marker.bindTooltip("my tooltip text").openTooltip();
10002 * Note about tooltip offset. Leaflet takes two options in consideration
10003 * for computing tooltip offsetting:
10004 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
10005 * Add a positive x offset to move the tooltip to the right, and a positive y offset to
10006 * move it to the bottom. Negatives will move to the left and top.
10007 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
10008 * should adapt this value if you use a custom icon.
10012 // @namespace Tooltip
10013 var Tooltip = DivOverlay.extend({
10016 // @aka Tooltip options
10018 // @option pane: String = 'tooltipPane'
10019 // `Map pane` where the tooltip will be added.
10020 pane: 'tooltipPane',
10022 // @option offset: Point = Point(0, 0)
10023 // Optional offset of the tooltip position.
10026 // @option direction: String = 'auto'
10027 // Direction where to open the tooltip. Possible values are: `right`, `left`,
10028 // `top`, `bottom`, `center`, `auto`.
10029 // `auto` will dynamically switch between `right` and `left` according to the tooltip
10030 // position on the map.
10033 // @option permanent: Boolean = false
10034 // Whether to open the tooltip permanently or only on mouseover.
10037 // @option sticky: Boolean = false
10038 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
10041 // @option interactive: Boolean = false
10042 // If true, the tooltip will listen to the feature events.
10043 interactive: false,
10045 // @option opacity: Number = 0.9
10046 // Tooltip container opacity.
10050 onAdd: function (map) {
10051 DivOverlay.prototype.onAdd.call(this, map);
10052 this.setOpacity(this.options.opacity);
10055 // @section Tooltip events
10056 // @event tooltipopen: TooltipEvent
10057 // Fired when a tooltip is opened in the map.
10058 map.fire('tooltipopen', {tooltip: this});
10060 if (this._source) {
10061 // @namespace Layer
10062 // @section Tooltip events
10063 // @event tooltipopen: TooltipEvent
10064 // Fired when a tooltip bound to this layer is opened.
10065 this._source.fire('tooltipopen', {tooltip: this}, true);
10069 onRemove: function (map) {
10070 DivOverlay.prototype.onRemove.call(this, map);
10073 // @section Tooltip events
10074 // @event tooltipclose: TooltipEvent
10075 // Fired when a tooltip in the map is closed.
10076 map.fire('tooltipclose', {tooltip: this});
10078 if (this._source) {
10079 // @namespace Layer
10080 // @section Tooltip events
10081 // @event tooltipclose: TooltipEvent
10082 // Fired when a tooltip bound to this layer is closed.
10083 this._source.fire('tooltipclose', {tooltip: this}, true);
10087 getEvents: function () {
10088 var events = DivOverlay.prototype.getEvents.call(this);
10090 if (touch && !this.options.permanent) {
10091 events.preclick = this._close;
10097 _close: function () {
10099 this._map.closeTooltip(this);
10103 _initLayout: function () {
10104 var prefix = 'leaflet-tooltip',
10105 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
10107 this._contentNode = this._container = create$1('div', className);
10110 _updateLayout: function () {},
10112 _adjustPan: function () {},
10114 _setPosition: function (pos) {
10115 var map = this._map,
10116 container = this._container,
10117 centerPoint = map.latLngToContainerPoint(map.getCenter()),
10118 tooltipPoint = map.layerPointToContainerPoint(pos),
10119 direction = this.options.direction,
10120 tooltipWidth = container.offsetWidth,
10121 tooltipHeight = container.offsetHeight,
10122 offset = toPoint(this.options.offset),
10123 anchor = this._getAnchor();
10125 if (direction === 'top') {
10126 pos = pos.add(toPoint(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true));
10127 } else if (direction === 'bottom') {
10128 pos = pos.subtract(toPoint(tooltipWidth / 2 - offset.x, -offset.y, true));
10129 } else if (direction === 'center') {
10130 pos = pos.subtract(toPoint(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true));
10131 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
10132 direction = 'right';
10133 pos = pos.add(toPoint(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true));
10135 direction = 'left';
10136 pos = pos.subtract(toPoint(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true));
10139 removeClass(container, 'leaflet-tooltip-right');
10140 removeClass(container, 'leaflet-tooltip-left');
10141 removeClass(container, 'leaflet-tooltip-top');
10142 removeClass(container, 'leaflet-tooltip-bottom');
10143 addClass(container, 'leaflet-tooltip-' + direction);
10144 setPosition(container, pos);
10147 _updatePosition: function () {
10148 var pos = this._map.latLngToLayerPoint(this._latlng);
10149 this._setPosition(pos);
10152 setOpacity: function (opacity) {
10153 this.options.opacity = opacity;
10155 if (this._container) {
10156 setOpacity(this._container, opacity);
10160 _animateZoom: function (e) {
10161 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
10162 this._setPosition(pos);
10165 _getAnchor: function () {
10166 // Where should we anchor the tooltip on the source layer?
10167 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
10172 // @namespace Tooltip
10173 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
10174 // 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.
10175 var tooltip = function (options, source) {
10176 return new Tooltip(options, source);
10180 // @section Methods for Layers and Controls
10183 // @method openTooltip(tooltip: Tooltip): this
10184 // Opens the specified tooltip.
10186 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
10187 // Creates a tooltip with the specified content and options and open it.
10188 openTooltip: function (tooltip, latlng, options) {
10189 if (!(tooltip instanceof Tooltip)) {
10190 tooltip = new Tooltip(options).setContent(tooltip);
10194 tooltip.setLatLng(latlng);
10197 if (this.hasLayer(tooltip)) {
10201 return this.addLayer(tooltip);
10204 // @method closeTooltip(tooltip?: Tooltip): this
10205 // Closes the tooltip given as parameter.
10206 closeTooltip: function (tooltip) {
10208 this.removeLayer(tooltip);
10217 * @section Tooltip methods example
10219 * All layers share a set of methods convenient for binding tooltips to it.
10222 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
10223 * layer.openTooltip();
10224 * layer.closeTooltip();
10228 // @section Tooltip methods
10231 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
10232 // Binds a tooltip to the layer with the passed `content` and sets up the
10233 // necessary event listeners. If a `Function` is passed it will receive
10234 // the layer as the first argument and should return a `String` or `HTMLElement`.
10235 bindTooltip: function (content, options) {
10237 if (content instanceof Tooltip) {
10238 setOptions(content, options);
10239 this._tooltip = content;
10240 content._source = this;
10242 if (!this._tooltip || options) {
10243 this._tooltip = new Tooltip(options, this);
10245 this._tooltip.setContent(content);
10249 this._initTooltipInteractions();
10251 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
10252 this.openTooltip();
10258 // @method unbindTooltip(): this
10259 // Removes the tooltip previously bound with `bindTooltip`.
10260 unbindTooltip: function () {
10261 if (this._tooltip) {
10262 this._initTooltipInteractions(true);
10263 this.closeTooltip();
10264 this._tooltip = null;
10269 _initTooltipInteractions: function (remove$$1) {
10270 if (!remove$$1 && this._tooltipHandlersAdded) { return; }
10271 var onOff = remove$$1 ? 'off' : 'on',
10273 remove: this.closeTooltip,
10274 move: this._moveTooltip
10276 if (!this._tooltip.options.permanent) {
10277 events.mouseover = this._openTooltip;
10278 events.mouseout = this.closeTooltip;
10279 if (this._tooltip.options.sticky) {
10280 events.mousemove = this._moveTooltip;
10283 events.click = this._openTooltip;
10286 events.add = this._openTooltip;
10288 this[onOff](events);
10289 this._tooltipHandlersAdded = !remove$$1;
10292 // @method openTooltip(latlng?: LatLng): this
10293 // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
10294 openTooltip: function (layer, latlng) {
10295 if (!(layer instanceof Layer)) {
10300 if (layer instanceof FeatureGroup) {
10301 for (var id in this._layers) {
10302 layer = this._layers[id];
10308 latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
10311 if (this._tooltip && this._map) {
10313 // set tooltip source to this layer
10314 this._tooltip._source = layer;
10316 // update the tooltip (content, layout, ect...)
10317 this._tooltip.update();
10319 // open the tooltip on the map
10320 this._map.openTooltip(this._tooltip, latlng);
10322 // Tooltip container may not be defined if not permanent and never
10324 if (this._tooltip.options.interactive && this._tooltip._container) {
10325 addClass(this._tooltip._container, 'leaflet-clickable');
10326 this.addInteractiveTarget(this._tooltip._container);
10333 // @method closeTooltip(): this
10334 // Closes the tooltip bound to this layer if it is open.
10335 closeTooltip: function () {
10336 if (this._tooltip) {
10337 this._tooltip._close();
10338 if (this._tooltip.options.interactive && this._tooltip._container) {
10339 removeClass(this._tooltip._container, 'leaflet-clickable');
10340 this.removeInteractiveTarget(this._tooltip._container);
10346 // @method toggleTooltip(): this
10347 // Opens or closes the tooltip bound to this layer depending on its current state.
10348 toggleTooltip: function (target) {
10349 if (this._tooltip) {
10350 if (this._tooltip._map) {
10351 this.closeTooltip();
10353 this.openTooltip(target);
10359 // @method isTooltipOpen(): boolean
10360 // Returns `true` if the tooltip bound to this layer is currently open.
10361 isTooltipOpen: function () {
10362 return this._tooltip.isOpen();
10365 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10366 // Sets the content of the tooltip bound to this layer.
10367 setTooltipContent: function (content) {
10368 if (this._tooltip) {
10369 this._tooltip.setContent(content);
10374 // @method getTooltip(): Tooltip
10375 // Returns the tooltip bound to this layer.
10376 getTooltip: function () {
10377 return this._tooltip;
10380 _openTooltip: function (e) {
10381 var layer = e.layer || e.target;
10383 if (!this._tooltip || !this._map) {
10386 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
10389 _moveTooltip: function (e) {
10390 var latlng = e.latlng, containerPoint, layerPoint;
10391 if (this._tooltip.options.sticky && e.originalEvent) {
10392 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
10393 layerPoint = this._map.containerPointToLayerPoint(containerPoint);
10394 latlng = this._map.layerPointToLatLng(layerPoint);
10396 this._tooltip.setLatLng(latlng);
10405 * Represents a lightweight icon for markers that uses a simple `<div>`
10406 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
10410 * var myIcon = L.divIcon({className: 'my-div-icon'});
10411 * // you can set .my-div-icon styles in CSS
10413 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
10416 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
10419 var DivIcon = Icon.extend({
10422 // @aka DivIcon options
10423 iconSize: [12, 12], // also can be set through CSS
10425 // iconAnchor: (Point),
10426 // popupAnchor: (Point),
10428 // @option html: String = ''
10429 // Custom HTML code to put inside the div element, empty by default.
10432 // @option bgPos: Point = [0, 0]
10433 // Optional relative position of the background, in pixels
10436 className: 'leaflet-div-icon'
10439 createIcon: function (oldIcon) {
10440 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
10441 options = this.options;
10443 div.innerHTML = options.html !== false ? options.html : '';
10445 if (options.bgPos) {
10446 var bgPos = toPoint(options.bgPos);
10447 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
10449 this._setIconStyles(div, 'icon');
10454 createShadow: function () {
10459 // @factory L.divIcon(options: DivIcon options)
10460 // Creates a `DivIcon` instance with the given options.
10461 function divIcon(options) {
10462 return new DivIcon(options);
10465 Icon.Default = IconDefault;
10472 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
10473 * 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.
10476 * @section Synchronous usage
10479 * 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.
10482 * var CanvasLayer = L.GridLayer.extend({
10483 * createTile: function(coords){
10484 * // create a <canvas> element for drawing
10485 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10487 * // setup tile width and height according to the options
10488 * var size = this.getTileSize();
10489 * tile.width = size.x;
10490 * tile.height = size.y;
10492 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z
10493 * var ctx = tile.getContext('2d');
10495 * // return the tile so it can be rendered on screen
10501 * @section Asynchronous usage
10504 * 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.
10507 * var CanvasLayer = L.GridLayer.extend({
10508 * createTile: function(coords, done){
10511 * // create a <canvas> element for drawing
10512 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10514 * // setup tile width and height according to the options
10515 * var size = this.getTileSize();
10516 * tile.width = size.x;
10517 * tile.height = size.y;
10519 * // draw something asynchronously and pass the tile to the done() callback
10520 * setTimeout(function() {
10521 * done(error, tile);
10533 var GridLayer = Layer.extend({
10536 // @aka GridLayer options
10538 // @option tileSize: Number|Point = 256
10539 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
10542 // @option opacity: Number = 1.0
10543 // Opacity of the tiles. Can be used in the `createTile()` function.
10546 // @option updateWhenIdle: Boolean = (depends)
10547 // Load new tiles only when panning ends.
10548 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
10549 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
10550 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
10551 updateWhenIdle: mobile,
10553 // @option updateWhenZooming: Boolean = true
10554 // 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.
10555 updateWhenZooming: true,
10557 // @option updateInterval: Number = 200
10558 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
10559 updateInterval: 200,
10561 // @option zIndex: Number = 1
10562 // The explicit zIndex of the tile layer.
10565 // @option bounds: LatLngBounds = undefined
10566 // If set, tiles will only be loaded inside the set `LatLngBounds`.
10569 // @option minZoom: Number = 0
10570 // The minimum zoom level down to which this layer will be displayed (inclusive).
10573 // @option maxZoom: Number = undefined
10574 // The maximum zoom level up to which this layer will be displayed (inclusive).
10575 maxZoom: undefined,
10577 // @option maxNativeZoom: Number = undefined
10578 // Maximum zoom number the tile source has available. If it is specified,
10579 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
10580 // from `maxNativeZoom` level and auto-scaled.
10581 maxNativeZoom: undefined,
10583 // @option minNativeZoom: Number = undefined
10584 // Minimum zoom number the tile source has available. If it is specified,
10585 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
10586 // from `minNativeZoom` level and auto-scaled.
10587 minNativeZoom: undefined,
10589 // @option noWrap: Boolean = false
10590 // Whether the layer is wrapped around the antimeridian. If `true`, the
10591 // GridLayer will only be displayed once at low zoom levels. Has no
10592 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
10593 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
10594 // tiles outside the CRS limits.
10597 // @option pane: String = 'tilePane'
10598 // `Map pane` where the grid layer will be added.
10601 // @option className: String = ''
10602 // A custom class name to assign to the tile layer. Empty by default.
10605 // @option keepBuffer: Number = 2
10606 // When panning the map, keep this many rows and columns of tiles before unloading them.
10610 initialize: function (options) {
10611 setOptions(this, options);
10614 onAdd: function () {
10615 this._initContainer();
10624 beforeAdd: function (map) {
10625 map._addZoomLimit(this);
10628 onRemove: function (map) {
10629 this._removeAllTiles();
10630 remove(this._container);
10631 map._removeZoomLimit(this);
10632 this._container = null;
10633 this._tileZoom = undefined;
10636 // @method bringToFront: this
10637 // Brings the tile layer to the top of all tile layers.
10638 bringToFront: function () {
10640 toFront(this._container);
10641 this._setAutoZIndex(Math.max);
10646 // @method bringToBack: this
10647 // Brings the tile layer to the bottom of all tile layers.
10648 bringToBack: function () {
10650 toBack(this._container);
10651 this._setAutoZIndex(Math.min);
10656 // @method getContainer: HTMLElement
10657 // Returns the HTML element that contains the tiles for this layer.
10658 getContainer: function () {
10659 return this._container;
10662 // @method setOpacity(opacity: Number): this
10663 // Changes the [opacity](#gridlayer-opacity) of the grid layer.
10664 setOpacity: function (opacity) {
10665 this.options.opacity = opacity;
10666 this._updateOpacity();
10670 // @method setZIndex(zIndex: Number): this
10671 // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
10672 setZIndex: function (zIndex) {
10673 this.options.zIndex = zIndex;
10674 this._updateZIndex();
10679 // @method isLoading: Boolean
10680 // Returns `true` if any tile in the grid layer has not finished loading.
10681 isLoading: function () {
10682 return this._loading;
10685 // @method redraw: this
10686 // Causes the layer to clear all the tiles and request them again.
10687 redraw: function () {
10689 this._removeAllTiles();
10695 getEvents: function () {
10697 viewprereset: this._invalidateAll,
10698 viewreset: this._resetView,
10699 zoom: this._resetView,
10700 moveend: this._onMoveEnd
10703 if (!this.options.updateWhenIdle) {
10704 // update tiles on move, but not more often than once per given interval
10705 if (!this._onMove) {
10706 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
10709 events.move = this._onMove;
10712 if (this._zoomAnimated) {
10713 events.zoomanim = this._animateZoom;
10719 // @section Extension methods
10720 // Layers extending `GridLayer` shall reimplement the following method.
10721 // @method createTile(coords: Object, done?: Function): HTMLElement
10722 // Called only internally, must be overridden by classes extending `GridLayer`.
10723 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
10724 // is specified, it must be called when the tile has finished loading and drawing.
10725 createTile: function () {
10726 return document.createElement('div');
10730 // @method getTileSize: Point
10731 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
10732 getTileSize: function () {
10733 var s = this.options.tileSize;
10734 return s instanceof Point ? s : new Point(s, s);
10737 _updateZIndex: function () {
10738 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
10739 this._container.style.zIndex = this.options.zIndex;
10743 _setAutoZIndex: function (compare) {
10744 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
10746 var layers = this.getPane().children,
10747 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
10749 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
10751 zIndex = layers[i].style.zIndex;
10753 if (layers[i] !== this._container && zIndex) {
10754 edgeZIndex = compare(edgeZIndex, +zIndex);
10758 if (isFinite(edgeZIndex)) {
10759 this.options.zIndex = edgeZIndex + compare(-1, 1);
10760 this._updateZIndex();
10764 _updateOpacity: function () {
10765 if (!this._map) { return; }
10767 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
10768 if (ielt9) { return; }
10770 setOpacity(this._container, this.options.opacity);
10772 var now = +new Date(),
10776 for (var key in this._tiles) {
10777 var tile = this._tiles[key];
10778 if (!tile.current || !tile.loaded) { continue; }
10780 var fade = Math.min(1, (now - tile.loaded) / 200);
10782 setOpacity(tile.el, fade);
10789 this._onOpaqueTile(tile);
10791 tile.active = true;
10795 if (willPrune && !this._noPrune) { this._pruneTiles(); }
10798 cancelAnimFrame(this._fadeFrame);
10799 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
10803 _onOpaqueTile: falseFn,
10805 _initContainer: function () {
10806 if (this._container) { return; }
10808 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
10809 this._updateZIndex();
10811 if (this.options.opacity < 1) {
10812 this._updateOpacity();
10815 this.getPane().appendChild(this._container);
10818 _updateLevels: function () {
10820 var zoom = this._tileZoom,
10821 maxZoom = this.options.maxZoom;
10823 if (zoom === undefined) { return undefined; }
10825 for (var z in this._levels) {
10826 if (this._levels[z].el.children.length || z === zoom) {
10827 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
10828 this._onUpdateLevel(z);
10830 remove(this._levels[z].el);
10831 this._removeTilesAtZoom(z);
10832 this._onRemoveLevel(z);
10833 delete this._levels[z];
10837 var level = this._levels[zoom],
10841 level = this._levels[zoom] = {};
10843 level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
10844 level.el.style.zIndex = maxZoom;
10846 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
10849 this._setZoomTransform(level, map.getCenter(), map.getZoom());
10851 // force the browser to consider the newly added element for transition
10852 falseFn(level.el.offsetWidth);
10854 this._onCreateLevel(level);
10857 this._level = level;
10862 _onUpdateLevel: falseFn,
10864 _onRemoveLevel: falseFn,
10866 _onCreateLevel: falseFn,
10868 _pruneTiles: function () {
10875 var zoom = this._map.getZoom();
10876 if (zoom > this.options.maxZoom ||
10877 zoom < this.options.minZoom) {
10878 this._removeAllTiles();
10882 for (key in this._tiles) {
10883 tile = this._tiles[key];
10884 tile.retain = tile.current;
10887 for (key in this._tiles) {
10888 tile = this._tiles[key];
10889 if (tile.current && !tile.active) {
10890 var coords = tile.coords;
10891 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
10892 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
10897 for (key in this._tiles) {
10898 if (!this._tiles[key].retain) {
10899 this._removeTile(key);
10904 _removeTilesAtZoom: function (zoom) {
10905 for (var key in this._tiles) {
10906 if (this._tiles[key].coords.z !== zoom) {
10909 this._removeTile(key);
10913 _removeAllTiles: function () {
10914 for (var key in this._tiles) {
10915 this._removeTile(key);
10919 _invalidateAll: function () {
10920 for (var z in this._levels) {
10921 remove(this._levels[z].el);
10922 this._onRemoveLevel(z);
10923 delete this._levels[z];
10925 this._removeAllTiles();
10927 this._tileZoom = undefined;
10930 _retainParent: function (x, y, z, minZoom) {
10931 var x2 = Math.floor(x / 2),
10932 y2 = Math.floor(y / 2),
10934 coords2 = new Point(+x2, +y2);
10937 var key = this._tileCoordsToKey(coords2),
10938 tile = this._tiles[key];
10940 if (tile && tile.active) {
10941 tile.retain = true;
10944 } else if (tile && tile.loaded) {
10945 tile.retain = true;
10948 if (z2 > minZoom) {
10949 return this._retainParent(x2, y2, z2, minZoom);
10955 _retainChildren: function (x, y, z, maxZoom) {
10957 for (var i = 2 * x; i < 2 * x + 2; i++) {
10958 for (var j = 2 * y; j < 2 * y + 2; j++) {
10960 var coords = new Point(i, j);
10963 var key = this._tileCoordsToKey(coords),
10964 tile = this._tiles[key];
10966 if (tile && tile.active) {
10967 tile.retain = true;
10970 } else if (tile && tile.loaded) {
10971 tile.retain = true;
10974 if (z + 1 < maxZoom) {
10975 this._retainChildren(i, j, z + 1, maxZoom);
10981 _resetView: function (e) {
10982 var animating = e && (e.pinch || e.flyTo);
10983 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
10986 _animateZoom: function (e) {
10987 this._setView(e.center, e.zoom, true, e.noUpdate);
10990 _clampZoom: function (zoom) {
10991 var options = this.options;
10993 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
10994 return options.minNativeZoom;
10997 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
10998 return options.maxNativeZoom;
11004 _setView: function (center, zoom, noPrune, noUpdate) {
11005 var tileZoom = this._clampZoom(Math.round(zoom));
11006 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
11007 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
11008 tileZoom = undefined;
11011 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
11013 if (!noUpdate || tileZoomChanged) {
11015 this._tileZoom = tileZoom;
11017 if (this._abortLoading) {
11018 this._abortLoading();
11021 this._updateLevels();
11024 if (tileZoom !== undefined) {
11025 this._update(center);
11029 this._pruneTiles();
11032 // Flag to prevent _updateOpacity from pruning tiles during
11033 // a zoom anim or a pinch gesture
11034 this._noPrune = !!noPrune;
11037 this._setZoomTransforms(center, zoom);
11040 _setZoomTransforms: function (center, zoom) {
11041 for (var i in this._levels) {
11042 this._setZoomTransform(this._levels[i], center, zoom);
11046 _setZoomTransform: function (level, center, zoom) {
11047 var scale = this._map.getZoomScale(zoom, level.zoom),
11048 translate = level.origin.multiplyBy(scale)
11049 .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
11052 setTransform(level.el, translate, scale);
11054 setPosition(level.el, translate);
11058 _resetGrid: function () {
11059 var map = this._map,
11060 crs = map.options.crs,
11061 tileSize = this._tileSize = this.getTileSize(),
11062 tileZoom = this._tileZoom;
11064 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
11066 this._globalTileRange = this._pxBoundsToTileRange(bounds);
11069 this._wrapX = crs.wrapLng && !this.options.noWrap && [
11070 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
11071 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
11073 this._wrapY = crs.wrapLat && !this.options.noWrap && [
11074 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
11075 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
11079 _onMoveEnd: function () {
11080 if (!this._map || this._map._animatingZoom) { return; }
11085 _getTiledPixelBounds: function (center) {
11086 var map = this._map,
11087 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
11088 scale = map.getZoomScale(mapZoom, this._tileZoom),
11089 pixelCenter = map.project(center, this._tileZoom).floor(),
11090 halfSize = map.getSize().divideBy(scale * 2);
11092 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
11095 // Private method to load tiles in the grid's active zoom level according to map bounds
11096 _update: function (center) {
11097 var map = this._map;
11098 if (!map) { return; }
11099 var zoom = this._clampZoom(map.getZoom());
11101 if (center === undefined) { center = map.getCenter(); }
11102 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom
11104 var pixelBounds = this._getTiledPixelBounds(center),
11105 tileRange = this._pxBoundsToTileRange(pixelBounds),
11106 tileCenter = tileRange.getCenter(),
11108 margin = this.options.keepBuffer,
11109 noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
11110 tileRange.getTopRight().add([margin, -margin]));
11112 // Sanity check: panic if the tile range contains Infinity somewhere.
11113 if (!(isFinite(tileRange.min.x) &&
11114 isFinite(tileRange.min.y) &&
11115 isFinite(tileRange.max.x) &&
11116 isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
11118 for (var key in this._tiles) {
11119 var c = this._tiles[key].coords;
11120 if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
11121 this._tiles[key].current = false;
11125 // _update just loads more tiles. If the tile zoom level differs too much
11126 // from the map's, let _setView reset levels and prune old tiles.
11127 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
11129 // create a queue of coordinates to load tiles from
11130 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
11131 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
11132 var coords = new Point(i, j);
11133 coords.z = this._tileZoom;
11135 if (!this._isValidTile(coords)) { continue; }
11137 var tile = this._tiles[this._tileCoordsToKey(coords)];
11139 tile.current = true;
11141 queue.push(coords);
11146 // sort tile queue to load tiles in order of their distance to center
11147 queue.sort(function (a, b) {
11148 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
11151 if (queue.length !== 0) {
11152 // if it's the first batch of tiles to load
11153 if (!this._loading) {
11154 this._loading = true;
11155 // @event loading: Event
11156 // Fired when the grid layer starts loading tiles.
11157 this.fire('loading');
11160 // create DOM fragment to append tiles in one batch
11161 var fragment = document.createDocumentFragment();
11163 for (i = 0; i < queue.length; i++) {
11164 this._addTile(queue[i], fragment);
11167 this._level.el.appendChild(fragment);
11171 _isValidTile: function (coords) {
11172 var crs = this._map.options.crs;
11174 if (!crs.infinite) {
11175 // don't load tile if it's out of bounds and not wrapped
11176 var bounds = this._globalTileRange;
11177 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
11178 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
11181 if (!this.options.bounds) { return true; }
11183 // don't load tile if it doesn't intersect the bounds in options
11184 var tileBounds = this._tileCoordsToBounds(coords);
11185 return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
11188 _keyToBounds: function (key) {
11189 return this._tileCoordsToBounds(this._keyToTileCoords(key));
11192 _tileCoordsToNwSe: function (coords) {
11193 var map = this._map,
11194 tileSize = this.getTileSize(),
11195 nwPoint = coords.scaleBy(tileSize),
11196 sePoint = nwPoint.add(tileSize),
11197 nw = map.unproject(nwPoint, coords.z),
11198 se = map.unproject(sePoint, coords.z);
11202 // converts tile coordinates to its geographical bounds
11203 _tileCoordsToBounds: function (coords) {
11204 var bp = this._tileCoordsToNwSe(coords),
11205 bounds = new LatLngBounds(bp[0], bp[1]);
11207 if (!this.options.noWrap) {
11208 bounds = this._map.wrapLatLngBounds(bounds);
11212 // converts tile coordinates to key for the tile cache
11213 _tileCoordsToKey: function (coords) {
11214 return coords.x + ':' + coords.y + ':' + coords.z;
11217 // converts tile cache key to coordinates
11218 _keyToTileCoords: function (key) {
11219 var k = key.split(':'),
11220 coords = new Point(+k[0], +k[1]);
11225 _removeTile: function (key) {
11226 var tile = this._tiles[key];
11227 if (!tile) { return; }
11231 delete this._tiles[key];
11233 // @event tileunload: TileEvent
11234 // Fired when a tile is removed (e.g. when a tile goes off the screen).
11235 this.fire('tileunload', {
11237 coords: this._keyToTileCoords(key)
11241 _initTile: function (tile) {
11242 addClass(tile, 'leaflet-tile');
11244 var tileSize = this.getTileSize();
11245 tile.style.width = tileSize.x + 'px';
11246 tile.style.height = tileSize.y + 'px';
11248 tile.onselectstart = falseFn;
11249 tile.onmousemove = falseFn;
11251 // update opacity on tiles in IE7-8 because of filter inheritance problems
11252 if (ielt9 && this.options.opacity < 1) {
11253 setOpacity(tile, this.options.opacity);
11256 // without this hack, tiles disappear after zoom on Chrome for Android
11257 // https://github.com/Leaflet/Leaflet/issues/2078
11258 if (android && !android23) {
11259 tile.style.WebkitBackfaceVisibility = 'hidden';
11263 _addTile: function (coords, container) {
11264 var tilePos = this._getTilePos(coords),
11265 key = this._tileCoordsToKey(coords);
11267 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
11269 this._initTile(tile);
11271 // if createTile is defined with a second argument ("done" callback),
11272 // we know that tile is async and will be ready later; otherwise
11273 if (this.createTile.length < 2) {
11274 // mark tile as ready, but delay one frame for opacity animation to happen
11275 requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
11278 setPosition(tile, tilePos);
11280 // save tile in cache
11281 this._tiles[key] = {
11287 container.appendChild(tile);
11288 // @event tileloadstart: TileEvent
11289 // Fired when a tile is requested and starts loading.
11290 this.fire('tileloadstart', {
11296 _tileReady: function (coords, err, tile) {
11298 // @event tileerror: TileErrorEvent
11299 // Fired when there is an error loading a tile.
11300 this.fire('tileerror', {
11307 var key = this._tileCoordsToKey(coords);
11309 tile = this._tiles[key];
11310 if (!tile) { return; }
11312 tile.loaded = +new Date();
11313 if (this._map._fadeAnimated) {
11314 setOpacity(tile.el, 0);
11315 cancelAnimFrame(this._fadeFrame);
11316 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11318 tile.active = true;
11319 this._pruneTiles();
11323 addClass(tile.el, 'leaflet-tile-loaded');
11325 // @event tileload: TileEvent
11326 // Fired when a tile loads.
11327 this.fire('tileload', {
11333 if (this._noTilesToLoad()) {
11334 this._loading = false;
11335 // @event load: Event
11336 // Fired when the grid layer loaded all visible tiles.
11339 if (ielt9 || !this._map._fadeAnimated) {
11340 requestAnimFrame(this._pruneTiles, this);
11342 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
11343 // to trigger a pruning.
11344 setTimeout(bind(this._pruneTiles, this), 250);
11349 _getTilePos: function (coords) {
11350 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
11353 _wrapCoords: function (coords) {
11354 var newCoords = new Point(
11355 this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
11356 this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
11357 newCoords.z = coords.z;
11361 _pxBoundsToTileRange: function (bounds) {
11362 var tileSize = this.getTileSize();
11364 bounds.min.unscaleBy(tileSize).floor(),
11365 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
11368 _noTilesToLoad: function () {
11369 for (var key in this._tiles) {
11370 if (!this._tiles[key].loaded) { return false; }
11376 // @factory L.gridLayer(options?: GridLayer options)
11377 // Creates a new instance of GridLayer with the supplied options.
11378 function gridLayer(options) {
11379 return new GridLayer(options);
11384 * @inherits GridLayer
11386 * Used to load and display tile layers on the map. Extends `GridLayer`.
11391 * L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map);
11394 * @section URL template
11397 * A string of the following form:
11400 * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
11403 * `{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.
11405 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
11408 * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
11413 var TileLayer = GridLayer.extend({
11416 // @aka TileLayer options
11418 // @option minZoom: Number = 0
11419 // The minimum zoom level down to which this layer will be displayed (inclusive).
11422 // @option maxZoom: Number = 18
11423 // The maximum zoom level up to which this layer will be displayed (inclusive).
11426 // @option subdomains: String|String[] = 'abc'
11427 // 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.
11430 // @option errorTileUrl: String = ''
11431 // URL to the tile image to show in place of the tile that failed to load.
11434 // @option zoomOffset: Number = 0
11435 // The zoom number used in tile URLs will be offset with this value.
11438 // @option tms: Boolean = false
11439 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
11442 // @option zoomReverse: Boolean = false
11443 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
11444 zoomReverse: false,
11446 // @option detectRetina: Boolean = false
11447 // 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.
11448 detectRetina: false,
11450 // @option crossOrigin: Boolean|String = false
11451 // Whether the crossOrigin attribute will be added to the tiles.
11452 // 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.
11453 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
11457 initialize: function (url, options) {
11461 options = setOptions(this, options);
11463 // detecting retina displays, adjusting tileSize and zoom levels
11464 if (options.detectRetina && retina && options.maxZoom > 0) {
11466 options.tileSize = Math.floor(options.tileSize / 2);
11468 if (!options.zoomReverse) {
11469 options.zoomOffset++;
11472 options.zoomOffset--;
11476 options.minZoom = Math.max(0, options.minZoom);
11479 if (typeof options.subdomains === 'string') {
11480 options.subdomains = options.subdomains.split('');
11483 // for https://github.com/Leaflet/Leaflet/issues/137
11485 this.on('tileunload', this._onTileRemove);
11489 // @method setUrl(url: String, noRedraw?: Boolean): this
11490 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
11491 setUrl: function (url, noRedraw) {
11500 // @method createTile(coords: Object, done?: Function): HTMLElement
11501 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
11502 // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
11503 // callback is called when the tile has been loaded.
11504 createTile: function (coords, done) {
11505 var tile = document.createElement('img');
11507 on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
11508 on(tile, 'error', bind(this._tileOnError, this, done, tile));
11510 if (this.options.crossOrigin || this.options.crossOrigin === '') {
11511 tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
11515 Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
11516 http://www.w3.org/TR/WCAG20-TECHS/H67
11521 Set role="presentation" to force screen readers to ignore this
11522 https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
11524 tile.setAttribute('role', 'presentation');
11526 tile.src = this.getTileUrl(coords);
11531 // @section Extension methods
11533 // Layers extending `TileLayer` might reimplement the following method.
11534 // @method getTileUrl(coords: Object): String
11535 // Called only internally, returns the URL for a tile given its coordinates.
11536 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
11537 getTileUrl: function (coords) {
11539 r: retina ? '@2x' : '',
11540 s: this._getSubdomain(coords),
11543 z: this._getZoomForUrl()
11545 if (this._map && !this._map.options.crs.infinite) {
11546 var invertedY = this._globalTileRange.max.y - coords.y;
11547 if (this.options.tms) {
11548 data['y'] = invertedY;
11550 data['-y'] = invertedY;
11553 return template(this._url, extend(data, this.options));
11556 _tileOnLoad: function (done, tile) {
11557 // For https://github.com/Leaflet/Leaflet/issues/3332
11559 setTimeout(bind(done, this, null, tile), 0);
11565 _tileOnError: function (done, tile, e) {
11566 var errorUrl = this.options.errorTileUrl;
11567 if (errorUrl && tile.getAttribute('src') !== errorUrl) {
11568 tile.src = errorUrl;
11573 _onTileRemove: function (e) {
11574 e.tile.onload = null;
11577 _getZoomForUrl: function () {
11578 var zoom = this._tileZoom,
11579 maxZoom = this.options.maxZoom,
11580 zoomReverse = this.options.zoomReverse,
11581 zoomOffset = this.options.zoomOffset;
11584 zoom = maxZoom - zoom;
11587 return zoom + zoomOffset;
11590 _getSubdomain: function (tilePoint) {
11591 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
11592 return this.options.subdomains[index];
11595 // stops loading all tiles in the background layer
11596 _abortLoading: function () {
11598 for (i in this._tiles) {
11599 if (this._tiles[i].coords.z !== this._tileZoom) {
11600 tile = this._tiles[i].el;
11602 tile.onload = falseFn;
11603 tile.onerror = falseFn;
11605 if (!tile.complete) {
11606 tile.src = emptyImageUrl;
11608 delete this._tiles[i];
11614 _removeTile: function (key) {
11615 var tile = this._tiles[key];
11616 if (!tile) { return; }
11618 // Cancels any pending http requests associated with the tile
11619 // unless we're on Android's stock browser,
11620 // see https://github.com/Leaflet/Leaflet/issues/137
11621 if (!androidStock) {
11622 tile.el.setAttribute('src', emptyImageUrl);
11625 return GridLayer.prototype._removeTile.call(this, key);
11628 _tileReady: function (coords, err, tile) {
11629 if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) {
11633 return GridLayer.prototype._tileReady.call(this, coords, err, tile);
11638 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
11639 // Instantiates a tile layer object given a `URL template` and optionally an options object.
11641 function tileLayer(url, options) {
11642 return new TileLayer(url, options);
11646 * @class TileLayer.WMS
11647 * @inherits TileLayer
11648 * @aka L.TileLayer.WMS
11649 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
11654 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
11655 * layers: 'nexrad-n0r-900913',
11656 * format: 'image/png',
11657 * transparent: true,
11658 * attribution: "Weather data © 2012 IEM Nexrad"
11663 var TileLayerWMS = TileLayer.extend({
11666 // @aka TileLayer.WMS options
11667 // If any custom options not documented here are used, they will be sent to the
11668 // WMS server as extra parameters in each request URL. This can be useful for
11669 // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
11670 defaultWmsParams: {
11674 // @option layers: String = ''
11675 // **(required)** Comma-separated list of WMS layers to show.
11678 // @option styles: String = ''
11679 // Comma-separated list of WMS styles.
11682 // @option format: String = 'image/jpeg'
11683 // WMS image format (use `'image/png'` for layers with transparency).
11684 format: 'image/jpeg',
11686 // @option transparent: Boolean = false
11687 // If `true`, the WMS service will return images with transparency.
11688 transparent: false,
11690 // @option version: String = '1.1.1'
11691 // Version of the WMS service to use
11696 // @option crs: CRS = null
11697 // Coordinate Reference System to use for the WMS requests, defaults to
11698 // map CRS. Don't change this if you're not sure what it means.
11701 // @option uppercase: Boolean = false
11702 // If `true`, WMS request parameter keys will be uppercase.
11706 initialize: function (url, options) {
11710 var wmsParams = extend({}, this.defaultWmsParams);
11712 // all keys that are not TileLayer options go to WMS params
11713 for (var i in options) {
11714 if (!(i in this.options)) {
11715 wmsParams[i] = options[i];
11719 options = setOptions(this, options);
11721 var realRetina = options.detectRetina && retina ? 2 : 1;
11722 var tileSize = this.getTileSize();
11723 wmsParams.width = tileSize.x * realRetina;
11724 wmsParams.height = tileSize.y * realRetina;
11726 this.wmsParams = wmsParams;
11729 onAdd: function (map) {
11731 this._crs = this.options.crs || map.options.crs;
11732 this._wmsVersion = parseFloat(this.wmsParams.version);
11734 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
11735 this.wmsParams[projectionKey] = this._crs.code;
11737 TileLayer.prototype.onAdd.call(this, map);
11740 getTileUrl: function (coords) {
11742 var tileBounds = this._tileCoordsToNwSe(coords),
11744 bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
11747 bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
11748 [min.y, min.x, max.y, max.x] :
11749 [min.x, min.y, max.x, max.y]).join(','),
11750 url = TileLayer.prototype.getTileUrl.call(this, coords);
11752 getParamString(this.wmsParams, url, this.options.uppercase) +
11753 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
11756 // @method setParams(params: Object, noRedraw?: Boolean): this
11757 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
11758 setParams: function (params, noRedraw) {
11760 extend(this.wmsParams, params);
11771 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
11772 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
11773 function tileLayerWMS(url, options) {
11774 return new TileLayerWMS(url, options);
11777 TileLayer.WMS = TileLayerWMS;
11778 tileLayer.wms = tileLayerWMS;
11785 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
11786 * DOM container of the renderer, its bounds, and its zoom animation.
11788 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
11789 * itself can be added or removed to the map. All paths use a renderer, which can
11790 * be implicit (the map will decide the type of renderer and use it automatically)
11791 * or explicit (using the [`renderer`](#path-renderer) option of the path).
11793 * Do not use this class directly, use `SVG` and `Canvas` instead.
11795 * @event update: Event
11796 * Fired when the renderer updates its bounds, center and zoom, for example when
11797 * its map has moved
11800 var Renderer = Layer.extend({
11803 // @aka Renderer options
11805 // @option padding: Number = 0.1
11806 // How much to extend the clip area around the map view (relative to its size)
11807 // e.g. 0.1 would be 10% of map view in each direction
11810 // @option tolerance: Number = 0
11811 // How much to extend click tolerance round a path/object on the map
11815 initialize: function (options) {
11816 setOptions(this, options);
11818 this._layers = this._layers || {};
11821 onAdd: function () {
11822 if (!this._container) {
11823 this._initContainer(); // defined by renderer implementations
11825 if (this._zoomAnimated) {
11826 addClass(this._container, 'leaflet-zoom-animated');
11830 this.getPane().appendChild(this._container);
11832 this.on('update', this._updatePaths, this);
11835 onRemove: function () {
11836 this.off('update', this._updatePaths, this);
11837 this._destroyContainer();
11840 getEvents: function () {
11842 viewreset: this._reset,
11843 zoom: this._onZoom,
11844 moveend: this._update,
11845 zoomend: this._onZoomEnd
11847 if (this._zoomAnimated) {
11848 events.zoomanim = this._onAnimZoom;
11853 _onAnimZoom: function (ev) {
11854 this._updateTransform(ev.center, ev.zoom);
11857 _onZoom: function () {
11858 this._updateTransform(this._map.getCenter(), this._map.getZoom());
11861 _updateTransform: function (center, zoom) {
11862 var scale = this._map.getZoomScale(zoom, this._zoom),
11863 position = getPosition(this._container),
11864 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
11865 currentCenterPoint = this._map.project(this._center, zoom),
11866 destCenterPoint = this._map.project(center, zoom),
11867 centerOffset = destCenterPoint.subtract(currentCenterPoint),
11869 topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
11872 setTransform(this._container, topLeftOffset, scale);
11874 setPosition(this._container, topLeftOffset);
11878 _reset: function () {
11880 this._updateTransform(this._center, this._zoom);
11882 for (var id in this._layers) {
11883 this._layers[id]._reset();
11887 _onZoomEnd: function () {
11888 for (var id in this._layers) {
11889 this._layers[id]._project();
11893 _updatePaths: function () {
11894 for (var id in this._layers) {
11895 this._layers[id]._update();
11899 _update: function () {
11900 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
11901 // Subclasses are responsible of firing the 'update' event.
11902 var p = this.options.padding,
11903 size = this._map.getSize(),
11904 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
11906 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
11908 this._center = this._map.getCenter();
11909 this._zoom = this._map.getZoom();
11915 * @inherits Renderer
11918 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
11919 * Inherits `Renderer`.
11921 * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
11922 * available in all web browsers, notably IE8, and overlapping geometries might
11923 * not display properly in some edge cases.
11927 * Use Canvas by default for all paths in the map:
11930 * var map = L.map('map', {
11931 * renderer: L.canvas()
11935 * Use a Canvas renderer with extra padding for specific vector geometries:
11938 * var map = L.map('map');
11939 * var myRenderer = L.canvas({ padding: 0.5 });
11940 * var line = L.polyline( coordinates, { renderer: myRenderer } );
11941 * var circle = L.circle( center, { renderer: myRenderer } );
11945 var Canvas = Renderer.extend({
11946 getEvents: function () {
11947 var events = Renderer.prototype.getEvents.call(this);
11948 events.viewprereset = this._onViewPreReset;
11952 _onViewPreReset: function () {
11953 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
11954 this._postponeUpdatePaths = true;
11957 onAdd: function () {
11958 Renderer.prototype.onAdd.call(this);
11960 // Redraw vectors since canvas is cleared upon removal,
11961 // in case of removing the renderer itself from the map.
11965 _initContainer: function () {
11966 var container = this._container = document.createElement('canvas');
11968 on(container, 'mousemove', throttle(this._onMouseMove, 32, this), this);
11969 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
11970 on(container, 'mouseout', this._handleMouseOut, this);
11972 this._ctx = container.getContext('2d');
11975 _destroyContainer: function () {
11976 cancelAnimFrame(this._redrawRequest);
11978 remove(this._container);
11979 off(this._container);
11980 delete this._container;
11983 _updatePaths: function () {
11984 if (this._postponeUpdatePaths) { return; }
11987 this._redrawBounds = null;
11988 for (var id in this._layers) {
11989 layer = this._layers[id];
11995 _update: function () {
11996 if (this._map._animatingZoom && this._bounds) { return; }
11998 this._drawnLayers = {};
12000 Renderer.prototype._update.call(this);
12002 var b = this._bounds,
12003 container = this._container,
12004 size = b.getSize(),
12005 m = retina ? 2 : 1;
12007 setPosition(container, b.min);
12009 // set canvas size (also clearing it); use double size on retina
12010 container.width = m * size.x;
12011 container.height = m * size.y;
12012 container.style.width = size.x + 'px';
12013 container.style.height = size.y + 'px';
12016 this._ctx.scale(2, 2);
12019 // translate so we use the same path coordinates after canvas element moves
12020 this._ctx.translate(-b.min.x, -b.min.y);
12022 // Tell paths to redraw themselves
12023 this.fire('update');
12026 _reset: function () {
12027 Renderer.prototype._reset.call(this);
12029 if (this._postponeUpdatePaths) {
12030 this._postponeUpdatePaths = false;
12031 this._updatePaths();
12035 _initPath: function (layer) {
12036 this._updateDashArray(layer);
12037 this._layers[stamp(layer)] = layer;
12039 var order = layer._order = {
12041 prev: this._drawLast,
12044 if (this._drawLast) { this._drawLast.next = order; }
12045 this._drawLast = order;
12046 this._drawFirst = this._drawFirst || this._drawLast;
12049 _addPath: function (layer) {
12050 this._requestRedraw(layer);
12053 _removePath: function (layer) {
12054 var order = layer._order;
12055 var next = order.next;
12056 var prev = order.prev;
12061 this._drawLast = prev;
12066 this._drawFirst = next;
12069 delete this._drawnLayers[layer._leaflet_id];
12071 delete layer._order;
12073 delete this._layers[stamp(layer)];
12075 this._requestRedraw(layer);
12078 _updatePath: function (layer) {
12079 // Redraw the union of the layer's old pixel
12080 // bounds and the new pixel bounds.
12081 this._extendRedrawBounds(layer);
12084 // The redraw will extend the redraw bounds
12085 // with the new pixel bounds.
12086 this._requestRedraw(layer);
12089 _updateStyle: function (layer) {
12090 this._updateDashArray(layer);
12091 this._requestRedraw(layer);
12094 _updateDashArray: function (layer) {
12095 if (typeof layer.options.dashArray === 'string') {
12096 var parts = layer.options.dashArray.split(/[, ]+/),
12099 for (i = 0; i < parts.length; i++) {
12100 dashArray.push(Number(parts[i]));
12102 layer.options._dashArray = dashArray;
12104 layer.options._dashArray = layer.options.dashArray;
12108 _requestRedraw: function (layer) {
12109 if (!this._map) { return; }
12111 this._extendRedrawBounds(layer);
12112 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
12115 _extendRedrawBounds: function (layer) {
12116 if (layer._pxBounds) {
12117 var padding = (layer.options.weight || 0) + 1;
12118 this._redrawBounds = this._redrawBounds || new Bounds();
12119 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
12120 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
12124 _redraw: function () {
12125 this._redrawRequest = null;
12127 if (this._redrawBounds) {
12128 this._redrawBounds.min._floor();
12129 this._redrawBounds.max._ceil();
12132 this._clear(); // clear layers in redraw bounds
12133 this._draw(); // draw layers
12135 this._redrawBounds = null;
12138 _clear: function () {
12139 var bounds = this._redrawBounds;
12141 var size = bounds.getSize();
12142 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
12144 this._ctx.clearRect(0, 0, this._container.width, this._container.height);
12148 _draw: function () {
12149 var layer, bounds = this._redrawBounds;
12152 var size = bounds.getSize();
12153 this._ctx.beginPath();
12154 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
12158 this._drawing = true;
12160 for (var order = this._drawFirst; order; order = order.next) {
12161 layer = order.layer;
12162 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
12163 layer._updatePath();
12167 this._drawing = false;
12169 this._ctx.restore(); // Restore state before clipping.
12172 _updatePoly: function (layer, closed) {
12173 if (!this._drawing) { return; }
12176 parts = layer._parts,
12177 len = parts.length,
12180 if (!len) { return; }
12182 this._drawnLayers[layer._leaflet_id] = layer;
12186 for (i = 0; i < len; i++) {
12187 for (j = 0, len2 = parts[i].length; j < len2; j++) {
12189 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
12196 this._fillStroke(ctx, layer);
12198 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
12201 _updateCircle: function (layer) {
12203 if (!this._drawing || layer._empty()) { return; }
12205 var p = layer._point,
12207 r = Math.max(Math.round(layer._radius), 1),
12208 s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
12210 this._drawnLayers[layer._leaflet_id] = layer;
12218 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
12224 this._fillStroke(ctx, layer);
12227 _fillStroke: function (ctx, layer) {
12228 var options = layer.options;
12230 if (options.fill) {
12231 ctx.globalAlpha = options.fillOpacity;
12232 ctx.fillStyle = options.fillColor || options.color;
12233 ctx.fill(options.fillRule || 'evenodd');
12236 if (options.stroke && options.weight !== 0) {
12237 if (ctx.setLineDash) {
12238 ctx.setLineDash(layer.options && layer.options._dashArray || []);
12240 ctx.globalAlpha = options.opacity;
12241 ctx.lineWidth = options.weight;
12242 ctx.strokeStyle = options.color;
12243 ctx.lineCap = options.lineCap;
12244 ctx.lineJoin = options.lineJoin;
12249 // Canvas obviously doesn't have mouse events for individual drawn objects,
12250 // so we emulate that by calculating what's under the mouse on mousemove/click manually
12252 _onClick: function (e) {
12253 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
12255 for (var order = this._drawFirst; order; order = order.next) {
12256 layer = order.layer;
12257 if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
12258 clickedLayer = layer;
12261 if (clickedLayer) {
12263 this._fireEvent([clickedLayer], e);
12267 _onMouseMove: function (e) {
12268 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
12270 var point = this._map.mouseEventToLayerPoint(e);
12271 this._handleMouseHover(e, point);
12275 _handleMouseOut: function (e) {
12276 var layer = this._hoveredLayer;
12278 // if we're leaving the layer, fire mouseout
12279 removeClass(this._container, 'leaflet-interactive');
12280 this._fireEvent([layer], e, 'mouseout');
12281 this._hoveredLayer = null;
12285 _handleMouseHover: function (e, point) {
12286 var layer, candidateHoveredLayer;
12288 for (var order = this._drawFirst; order; order = order.next) {
12289 layer = order.layer;
12290 if (layer.options.interactive && layer._containsPoint(point)) {
12291 candidateHoveredLayer = layer;
12295 if (candidateHoveredLayer !== this._hoveredLayer) {
12296 this._handleMouseOut(e);
12298 if (candidateHoveredLayer) {
12299 addClass(this._container, 'leaflet-interactive'); // change cursor
12300 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
12301 this._hoveredLayer = candidateHoveredLayer;
12305 if (this._hoveredLayer) {
12306 this._fireEvent([this._hoveredLayer], e);
12310 _fireEvent: function (layers, e, type) {
12311 this._map._fireDOMEvent(e, type || e.type, layers);
12314 _bringToFront: function (layer) {
12315 var order = layer._order;
12316 var next = order.next;
12317 var prev = order.prev;
12328 // Update first entry unless this is the
12330 this._drawFirst = next;
12333 order.prev = this._drawLast;
12334 this._drawLast.next = order;
12337 this._drawLast = order;
12339 this._requestRedraw(layer);
12342 _bringToBack: function (layer) {
12343 var order = layer._order;
12344 var next = order.next;
12345 var prev = order.prev;
12356 // Update last entry unless this is the
12358 this._drawLast = prev;
12363 order.next = this._drawFirst;
12364 this._drawFirst.prev = order;
12365 this._drawFirst = order;
12367 this._requestRedraw(layer);
12371 // @factory L.canvas(options?: Renderer options)
12372 // Creates a Canvas renderer with the given options.
12373 function canvas$1(options) {
12374 return canvas ? new Canvas(options) : null;
12378 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
12382 var vmlCreate = (function () {
12384 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
12385 return function (name) {
12386 return document.createElement('<lvml:' + name + ' class="lvml">');
12389 return function (name) {
12390 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
12399 * Although SVG is not available on IE7 and IE8, these browsers support [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language), and the SVG renderer will fall back to VML in this case.
12401 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
12402 * with old versions of Internet Explorer.
12405 // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
12408 _initContainer: function () {
12409 this._container = create$1('div', 'leaflet-vml-container');
12412 _update: function () {
12413 if (this._map._animatingZoom) { return; }
12414 Renderer.prototype._update.call(this);
12415 this.fire('update');
12418 _initPath: function (layer) {
12419 var container = layer._container = vmlCreate('shape');
12421 addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
12423 container.coordsize = '1 1';
12425 layer._path = vmlCreate('path');
12426 container.appendChild(layer._path);
12428 this._updateStyle(layer);
12429 this._layers[stamp(layer)] = layer;
12432 _addPath: function (layer) {
12433 var container = layer._container;
12434 this._container.appendChild(container);
12436 if (layer.options.interactive) {
12437 layer.addInteractiveTarget(container);
12441 _removePath: function (layer) {
12442 var container = layer._container;
12444 layer.removeInteractiveTarget(container);
12445 delete this._layers[stamp(layer)];
12448 _updateStyle: function (layer) {
12449 var stroke = layer._stroke,
12450 fill = layer._fill,
12451 options = layer.options,
12452 container = layer._container;
12454 container.stroked = !!options.stroke;
12455 container.filled = !!options.fill;
12457 if (options.stroke) {
12459 stroke = layer._stroke = vmlCreate('stroke');
12461 container.appendChild(stroke);
12462 stroke.weight = options.weight + 'px';
12463 stroke.color = options.color;
12464 stroke.opacity = options.opacity;
12466 if (options.dashArray) {
12467 stroke.dashStyle = isArray(options.dashArray) ?
12468 options.dashArray.join(' ') :
12469 options.dashArray.replace(/( *, *)/g, ' ');
12471 stroke.dashStyle = '';
12473 stroke.endcap = options.lineCap.replace('butt', 'flat');
12474 stroke.joinstyle = options.lineJoin;
12476 } else if (stroke) {
12477 container.removeChild(stroke);
12478 layer._stroke = null;
12481 if (options.fill) {
12483 fill = layer._fill = vmlCreate('fill');
12485 container.appendChild(fill);
12486 fill.color = options.fillColor || options.color;
12487 fill.opacity = options.fillOpacity;
12490 container.removeChild(fill);
12491 layer._fill = null;
12495 _updateCircle: function (layer) {
12496 var p = layer._point.round(),
12497 r = Math.round(layer._radius),
12498 r2 = Math.round(layer._radiusY || r);
12500 this._setPath(layer, layer._empty() ? 'M0 0' :
12501 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
12504 _setPath: function (layer, path) {
12505 layer._path.v = path;
12508 _bringToFront: function (layer) {
12509 toFront(layer._container);
12512 _bringToBack: function (layer) {
12513 toBack(layer._container);
12517 var create$2 = vml ? vmlCreate : svgCreate;
12521 * @inherits Renderer
12524 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
12525 * Inherits `Renderer`.
12527 * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
12528 * available in all web browsers, notably Android 2.x and 3.x.
12530 * Although SVG is not available on IE7 and IE8, these browsers support
12531 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
12532 * (a now deprecated technology), and the SVG renderer will fall back to VML in
12537 * Use SVG by default for all paths in the map:
12540 * var map = L.map('map', {
12541 * renderer: L.svg()
12545 * Use a SVG renderer with extra padding for specific vector geometries:
12548 * var map = L.map('map');
12549 * var myRenderer = L.svg({ padding: 0.5 });
12550 * var line = L.polyline( coordinates, { renderer: myRenderer } );
12551 * var circle = L.circle( center, { renderer: myRenderer } );
12555 var SVG = Renderer.extend({
12557 getEvents: function () {
12558 var events = Renderer.prototype.getEvents.call(this);
12559 events.zoomstart = this._onZoomStart;
12563 _initContainer: function () {
12564 this._container = create$2('svg');
12566 // makes it possible to click through svg root; we'll reset it back in individual paths
12567 this._container.setAttribute('pointer-events', 'none');
12569 this._rootGroup = create$2('g');
12570 this._container.appendChild(this._rootGroup);
12573 _destroyContainer: function () {
12574 remove(this._container);
12575 off(this._container);
12576 delete this._container;
12577 delete this._rootGroup;
12578 delete this._svgSize;
12581 _onZoomStart: function () {
12582 // Drag-then-pinch interactions might mess up the center and zoom.
12583 // In this case, the easiest way to prevent this is re-do the renderer
12584 // bounds and padding when the zooming starts.
12588 _update: function () {
12589 if (this._map._animatingZoom && this._bounds) { return; }
12591 Renderer.prototype._update.call(this);
12593 var b = this._bounds,
12594 size = b.getSize(),
12595 container = this._container;
12597 // set size of svg-container if changed
12598 if (!this._svgSize || !this._svgSize.equals(size)) {
12599 this._svgSize = size;
12600 container.setAttribute('width', size.x);
12601 container.setAttribute('height', size.y);
12604 // movement: update container viewBox so that we don't have to change coordinates of individual layers
12605 setPosition(container, b.min);
12606 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
12608 this.fire('update');
12611 // methods below are called by vector layers implementations
12613 _initPath: function (layer) {
12614 var path = layer._path = create$2('path');
12617 // @option className: String = null
12618 // Custom class name set on an element. Only for SVG renderer.
12619 if (layer.options.className) {
12620 addClass(path, layer.options.className);
12623 if (layer.options.interactive) {
12624 addClass(path, 'leaflet-interactive');
12627 this._updateStyle(layer);
12628 this._layers[stamp(layer)] = layer;
12631 _addPath: function (layer) {
12632 if (!this._rootGroup) { this._initContainer(); }
12633 this._rootGroup.appendChild(layer._path);
12634 layer.addInteractiveTarget(layer._path);
12637 _removePath: function (layer) {
12638 remove(layer._path);
12639 layer.removeInteractiveTarget(layer._path);
12640 delete this._layers[stamp(layer)];
12643 _updatePath: function (layer) {
12648 _updateStyle: function (layer) {
12649 var path = layer._path,
12650 options = layer.options;
12652 if (!path) { return; }
12654 if (options.stroke) {
12655 path.setAttribute('stroke', options.color);
12656 path.setAttribute('stroke-opacity', options.opacity);
12657 path.setAttribute('stroke-width', options.weight);
12658 path.setAttribute('stroke-linecap', options.lineCap);
12659 path.setAttribute('stroke-linejoin', options.lineJoin);
12661 if (options.dashArray) {
12662 path.setAttribute('stroke-dasharray', options.dashArray);
12664 path.removeAttribute('stroke-dasharray');
12667 if (options.dashOffset) {
12668 path.setAttribute('stroke-dashoffset', options.dashOffset);
12670 path.removeAttribute('stroke-dashoffset');
12673 path.setAttribute('stroke', 'none');
12676 if (options.fill) {
12677 path.setAttribute('fill', options.fillColor || options.color);
12678 path.setAttribute('fill-opacity', options.fillOpacity);
12679 path.setAttribute('fill-rule', options.fillRule || 'evenodd');
12681 path.setAttribute('fill', 'none');
12685 _updatePoly: function (layer, closed) {
12686 this._setPath(layer, pointsToPath(layer._parts, closed));
12689 _updateCircle: function (layer) {
12690 var p = layer._point,
12691 r = Math.max(Math.round(layer._radius), 1),
12692 r2 = Math.max(Math.round(layer._radiusY), 1) || r,
12693 arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
12695 // drawing a circle with two half-arcs
12696 var d = layer._empty() ? 'M0 0' :
12697 'M' + (p.x - r) + ',' + p.y +
12698 arc + (r * 2) + ',0 ' +
12699 arc + (-r * 2) + ',0 ';
12701 this._setPath(layer, d);
12704 _setPath: function (layer, path) {
12705 layer._path.setAttribute('d', path);
12708 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
12709 _bringToFront: function (layer) {
12710 toFront(layer._path);
12713 _bringToBack: function (layer) {
12714 toBack(layer._path);
12719 SVG.include(vmlMixin);
12723 // @factory L.svg(options?: Renderer options)
12724 // Creates a SVG renderer with the given options.
12725 function svg$1(options) {
12726 return svg || vml ? new SVG(options) : null;
12730 // @namespace Map; @method getRenderer(layer: Path): Renderer
12731 // Returns the instance of `Renderer` that should be used to render the given
12732 // `Path`. It will ensure that the `renderer` options of the map and paths
12733 // are respected, and that the renderers do exist on the map.
12734 getRenderer: function (layer) {
12735 // @namespace Path; @option renderer: Renderer
12736 // Use this specific instance of `Renderer` for this path. Takes
12737 // precedence over the map's [default renderer](#map-renderer).
12738 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
12741 renderer = this._renderer = this._createRenderer();
12744 if (!this.hasLayer(renderer)) {
12745 this.addLayer(renderer);
12750 _getPaneRenderer: function (name) {
12751 if (name === 'overlayPane' || name === undefined) {
12755 var renderer = this._paneRenderers[name];
12756 if (renderer === undefined) {
12757 renderer = this._createRenderer({pane: name});
12758 this._paneRenderers[name] = renderer;
12763 _createRenderer: function (options) {
12764 // @namespace Map; @option preferCanvas: Boolean = false
12765 // Whether `Path`s should be rendered on a `Canvas` renderer.
12766 // By default, all `Path`s are rendered in a `SVG` renderer.
12767 return (this.options.preferCanvas && canvas$1(options)) || svg$1(options);
12772 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
12778 * @inherits Polygon
12780 * A class for drawing rectangle overlays on a map. Extends `Polygon`.
12785 * // define rectangle geographical bounds
12786 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
12788 * // create an orange rectangle
12789 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
12791 * // zoom the map to the rectangle bounds
12792 * map.fitBounds(bounds);
12798 var Rectangle = Polygon.extend({
12799 initialize: function (latLngBounds, options) {
12800 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
12803 // @method setBounds(latLngBounds: LatLngBounds): this
12804 // Redraws the rectangle with the passed bounds.
12805 setBounds: function (latLngBounds) {
12806 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
12809 _boundsToLatLngs: function (latLngBounds) {
12810 latLngBounds = toLatLngBounds(latLngBounds);
12812 latLngBounds.getSouthWest(),
12813 latLngBounds.getNorthWest(),
12814 latLngBounds.getNorthEast(),
12815 latLngBounds.getSouthEast()
12821 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
12822 function rectangle(latLngBounds, options) {
12823 return new Rectangle(latLngBounds, options);
12826 SVG.create = create$2;
12827 SVG.pointsToPath = pointsToPath;
12829 GeoJSON.geometryToLayer = geometryToLayer;
12830 GeoJSON.coordsToLatLng = coordsToLatLng;
12831 GeoJSON.coordsToLatLngs = coordsToLatLngs;
12832 GeoJSON.latLngToCoords = latLngToCoords;
12833 GeoJSON.latLngsToCoords = latLngsToCoords;
12834 GeoJSON.getFeature = getFeature;
12835 GeoJSON.asFeature = asFeature;
12838 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
12839 * (zoom to a selected bounding box), enabled by default.
12843 // @section Interaction Options
12845 // @option boxZoom: Boolean = true
12846 // Whether the map can be zoomed to a rectangular area specified by
12847 // dragging the mouse while pressing the shift key.
12851 var BoxZoom = Handler.extend({
12852 initialize: function (map) {
12854 this._container = map._container;
12855 this._pane = map._panes.overlayPane;
12856 this._resetStateTimeout = 0;
12857 map.on('unload', this._destroy, this);
12860 addHooks: function () {
12861 on(this._container, 'mousedown', this._onMouseDown, this);
12864 removeHooks: function () {
12865 off(this._container, 'mousedown', this._onMouseDown, this);
12868 moved: function () {
12869 return this._moved;
12872 _destroy: function () {
12873 remove(this._pane);
12877 _resetState: function () {
12878 this._resetStateTimeout = 0;
12879 this._moved = false;
12882 _clearDeferredResetState: function () {
12883 if (this._resetStateTimeout !== 0) {
12884 clearTimeout(this._resetStateTimeout);
12885 this._resetStateTimeout = 0;
12889 _onMouseDown: function (e) {
12890 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
12892 // Clear the deferred resetState if it hasn't executed yet, otherwise it
12893 // will interrupt the interaction and orphan a box element in the container.
12894 this._clearDeferredResetState();
12895 this._resetState();
12897 disableTextSelection();
12898 disableImageDrag();
12900 this._startPoint = this._map.mouseEventToContainerPoint(e);
12904 mousemove: this._onMouseMove,
12905 mouseup: this._onMouseUp,
12906 keydown: this._onKeyDown
12910 _onMouseMove: function (e) {
12911 if (!this._moved) {
12912 this._moved = true;
12914 this._box = create$1('div', 'leaflet-zoom-box', this._container);
12915 addClass(this._container, 'leaflet-crosshair');
12917 this._map.fire('boxzoomstart');
12920 this._point = this._map.mouseEventToContainerPoint(e);
12922 var bounds = new Bounds(this._point, this._startPoint),
12923 size = bounds.getSize();
12925 setPosition(this._box, bounds.min);
12927 this._box.style.width = size.x + 'px';
12928 this._box.style.height = size.y + 'px';
12931 _finish: function () {
12934 removeClass(this._container, 'leaflet-crosshair');
12937 enableTextSelection();
12942 mousemove: this._onMouseMove,
12943 mouseup: this._onMouseUp,
12944 keydown: this._onKeyDown
12948 _onMouseUp: function (e) {
12949 if ((e.which !== 1) && (e.button !== 1)) { return; }
12953 if (!this._moved) { return; }
12954 // Postpone to next JS tick so internal click event handling
12955 // still see it as "moved".
12956 this._clearDeferredResetState();
12957 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
12959 var bounds = new LatLngBounds(
12960 this._map.containerPointToLatLng(this._startPoint),
12961 this._map.containerPointToLatLng(this._point));
12965 .fire('boxzoomend', {boxZoomBounds: bounds});
12968 _onKeyDown: function (e) {
12969 if (e.keyCode === 27) {
12975 // @section Handlers
12976 // @property boxZoom: Handler
12977 // Box (shift-drag with mouse) zoom handler.
12978 Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
12981 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
12985 // @section Interaction Options
12988 // @option doubleClickZoom: Boolean|String = true
12989 // Whether the map can be zoomed in by double clicking on it and
12990 // zoomed out by double clicking while holding shift. If passed
12991 // `'center'`, double-click zoom will zoom to the center of the
12992 // view regardless of where the mouse was.
12993 doubleClickZoom: true
12996 var DoubleClickZoom = Handler.extend({
12997 addHooks: function () {
12998 this._map.on('dblclick', this._onDoubleClick, this);
13001 removeHooks: function () {
13002 this._map.off('dblclick', this._onDoubleClick, this);
13005 _onDoubleClick: function (e) {
13006 var map = this._map,
13007 oldZoom = map.getZoom(),
13008 delta = map.options.zoomDelta,
13009 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
13011 if (map.options.doubleClickZoom === 'center') {
13014 map.setZoomAround(e.containerPoint, zoom);
13019 // @section Handlers
13021 // Map properties include interaction handlers that allow you to control
13022 // interaction behavior in runtime, enabling or disabling certain features such
13023 // as dragging or touch zoom (see `Handler` methods). For example:
13026 // map.doubleClickZoom.disable();
13029 // @property doubleClickZoom: Handler
13030 // Double click zoom handler.
13031 Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
13034 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
13038 // @section Interaction Options
13040 // @option dragging: Boolean = true
13041 // Whether the map be draggable with mouse/touch or not.
13044 // @section Panning Inertia Options
13045 // @option inertia: Boolean = *
13046 // If enabled, panning of the map will have an inertia effect where
13047 // the map builds momentum while dragging and continues moving in
13048 // the same direction for some time. Feels especially nice on touch
13049 // devices. Enabled by default unless running on old Android devices.
13050 inertia: !android23,
13052 // @option inertiaDeceleration: Number = 3000
13053 // The rate with which the inertial movement slows down, in pixels/second².
13054 inertiaDeceleration: 3400, // px/s^2
13056 // @option inertiaMaxSpeed: Number = Infinity
13057 // Max speed of the inertial movement, in pixels/second.
13058 inertiaMaxSpeed: Infinity, // px/s
13060 // @option easeLinearity: Number = 0.2
13061 easeLinearity: 0.2,
13063 // TODO refactor, move to CRS
13064 // @option worldCopyJump: Boolean = false
13065 // With this option enabled, the map tracks when you pan to another "copy"
13066 // of the world and seamlessly jumps to the original one so that all overlays
13067 // like markers and vector layers are still visible.
13068 worldCopyJump: false,
13070 // @option maxBoundsViscosity: Number = 0.0
13071 // If `maxBounds` is set, this option will control how solid the bounds
13072 // are when dragging the map around. The default value of `0.0` allows the
13073 // user to drag outside the bounds at normal speed, higher values will
13074 // slow down map dragging outside bounds, and `1.0` makes the bounds fully
13075 // solid, preventing the user from dragging outside the bounds.
13076 maxBoundsViscosity: 0.0
13079 var Drag = Handler.extend({
13080 addHooks: function () {
13081 if (!this._draggable) {
13082 var map = this._map;
13084 this._draggable = new Draggable(map._mapPane, map._container);
13086 this._draggable.on({
13087 dragstart: this._onDragStart,
13088 drag: this._onDrag,
13089 dragend: this._onDragEnd
13092 this._draggable.on('predrag', this._onPreDragLimit, this);
13093 if (map.options.worldCopyJump) {
13094 this._draggable.on('predrag', this._onPreDragWrap, this);
13095 map.on('zoomend', this._onZoomEnd, this);
13097 map.whenReady(this._onZoomEnd, this);
13100 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
13101 this._draggable.enable();
13102 this._positions = [];
13106 removeHooks: function () {
13107 removeClass(this._map._container, 'leaflet-grab');
13108 removeClass(this._map._container, 'leaflet-touch-drag');
13109 this._draggable.disable();
13112 moved: function () {
13113 return this._draggable && this._draggable._moved;
13116 moving: function () {
13117 return this._draggable && this._draggable._moving;
13120 _onDragStart: function () {
13121 var map = this._map;
13124 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
13125 var bounds = toLatLngBounds(this._map.options.maxBounds);
13127 this._offsetLimit = toBounds(
13128 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
13129 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
13130 .add(this._map.getSize()));
13132 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
13134 this._offsetLimit = null;
13139 .fire('dragstart');
13141 if (map.options.inertia) {
13142 this._positions = [];
13147 _onDrag: function (e) {
13148 if (this._map.options.inertia) {
13149 var time = this._lastTime = +new Date(),
13150 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
13152 this._positions.push(pos);
13153 this._times.push(time);
13155 this._prunePositions(time);
13163 _prunePositions: function (time) {
13164 while (this._positions.length > 1 && time - this._times[0] > 50) {
13165 this._positions.shift();
13166 this._times.shift();
13170 _onZoomEnd: function () {
13171 var pxCenter = this._map.getSize().divideBy(2),
13172 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
13174 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
13175 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
13178 _viscousLimit: function (value, threshold) {
13179 return value - (value - threshold) * this._viscosity;
13182 _onPreDragLimit: function () {
13183 if (!this._viscosity || !this._offsetLimit) { return; }
13185 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
13187 var limit = this._offsetLimit;
13188 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
13189 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
13190 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
13191 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
13193 this._draggable._newPos = this._draggable._startPos.add(offset);
13196 _onPreDragWrap: function () {
13197 // TODO refactor to be able to adjust map pane position after zoom
13198 var worldWidth = this._worldWidth,
13199 halfWidth = Math.round(worldWidth / 2),
13200 dx = this._initialWorldOffset,
13201 x = this._draggable._newPos.x,
13202 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
13203 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
13204 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
13206 this._draggable._absPos = this._draggable._newPos.clone();
13207 this._draggable._newPos.x = newX;
13210 _onDragEnd: function (e) {
13211 var map = this._map,
13212 options = map.options,
13214 noInertia = !options.inertia || this._times.length < 2;
13216 map.fire('dragend', e);
13219 map.fire('moveend');
13222 this._prunePositions(+new Date());
13224 var direction = this._lastPos.subtract(this._positions[0]),
13225 duration = (this._lastTime - this._times[0]) / 1000,
13226 ease = options.easeLinearity,
13228 speedVector = direction.multiplyBy(ease / duration),
13229 speed = speedVector.distanceTo([0, 0]),
13231 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
13232 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
13234 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
13235 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
13237 if (!offset.x && !offset.y) {
13238 map.fire('moveend');
13241 offset = map._limitOffset(offset, map.options.maxBounds);
13243 requestAnimFrame(function () {
13244 map.panBy(offset, {
13245 duration: decelerationDuration,
13246 easeLinearity: ease,
13256 // @section Handlers
13257 // @property dragging: Handler
13258 // Map dragging handler (by both mouse and touch).
13259 Map.addInitHook('addHandler', 'dragging', Drag);
13262 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
13266 // @section Keyboard Navigation Options
13268 // @option keyboard: Boolean = true
13269 // Makes the map focusable and allows users to navigate the map with keyboard
13270 // arrows and `+`/`-` keys.
13273 // @option keyboardPanDelta: Number = 80
13274 // Amount of pixels to pan when pressing an arrow key.
13275 keyboardPanDelta: 80
13278 var Keyboard = Handler.extend({
13285 zoomIn: [187, 107, 61, 171],
13286 zoomOut: [189, 109, 54, 173]
13289 initialize: function (map) {
13292 this._setPanDelta(map.options.keyboardPanDelta);
13293 this._setZoomDelta(map.options.zoomDelta);
13296 addHooks: function () {
13297 var container = this._map._container;
13299 // make the container focusable by tabbing
13300 if (container.tabIndex <= 0) {
13301 container.tabIndex = '0';
13305 focus: this._onFocus,
13306 blur: this._onBlur,
13307 mousedown: this._onMouseDown
13311 focus: this._addHooks,
13312 blur: this._removeHooks
13316 removeHooks: function () {
13317 this._removeHooks();
13319 off(this._map._container, {
13320 focus: this._onFocus,
13321 blur: this._onBlur,
13322 mousedown: this._onMouseDown
13326 focus: this._addHooks,
13327 blur: this._removeHooks
13331 _onMouseDown: function () {
13332 if (this._focused) { return; }
13334 var body = document.body,
13335 docEl = document.documentElement,
13336 top = body.scrollTop || docEl.scrollTop,
13337 left = body.scrollLeft || docEl.scrollLeft;
13339 this._map._container.focus();
13341 window.scrollTo(left, top);
13344 _onFocus: function () {
13345 this._focused = true;
13346 this._map.fire('focus');
13349 _onBlur: function () {
13350 this._focused = false;
13351 this._map.fire('blur');
13354 _setPanDelta: function (panDelta) {
13355 var keys = this._panKeys = {},
13356 codes = this.keyCodes,
13359 for (i = 0, len = codes.left.length; i < len; i++) {
13360 keys[codes.left[i]] = [-1 * panDelta, 0];
13362 for (i = 0, len = codes.right.length; i < len; i++) {
13363 keys[codes.right[i]] = [panDelta, 0];
13365 for (i = 0, len = codes.down.length; i < len; i++) {
13366 keys[codes.down[i]] = [0, panDelta];
13368 for (i = 0, len = codes.up.length; i < len; i++) {
13369 keys[codes.up[i]] = [0, -1 * panDelta];
13373 _setZoomDelta: function (zoomDelta) {
13374 var keys = this._zoomKeys = {},
13375 codes = this.keyCodes,
13378 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
13379 keys[codes.zoomIn[i]] = zoomDelta;
13381 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
13382 keys[codes.zoomOut[i]] = -zoomDelta;
13386 _addHooks: function () {
13387 on(document, 'keydown', this._onKeyDown, this);
13390 _removeHooks: function () {
13391 off(document, 'keydown', this._onKeyDown, this);
13394 _onKeyDown: function (e) {
13395 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
13397 var key = e.keyCode,
13401 if (key in this._panKeys) {
13402 if (!map._panAnim || !map._panAnim._inProgress) {
13403 offset = this._panKeys[key];
13405 offset = toPoint(offset).multiplyBy(3);
13410 if (map.options.maxBounds) {
13411 map.panInsideBounds(map.options.maxBounds);
13414 } else if (key in this._zoomKeys) {
13415 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
13417 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
13428 // @section Handlers
13429 // @section Handlers
13430 // @property keyboard: Handler
13431 // Keyboard navigation handler.
13432 Map.addInitHook('addHandler', 'keyboard', Keyboard);
13435 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
13439 // @section Interaction Options
13441 // @section Mousewheel options
13442 // @option scrollWheelZoom: Boolean|String = true
13443 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
13444 // it will zoom to the center of the view regardless of where the mouse was.
13445 scrollWheelZoom: true,
13447 // @option wheelDebounceTime: Number = 40
13448 // Limits the rate at which a wheel can fire (in milliseconds). By default
13449 // user can't zoom via wheel more often than once per 40 ms.
13450 wheelDebounceTime: 40,
13452 // @option wheelPxPerZoomLevel: Number = 60
13453 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
13454 // mean a change of one full zoom level. Smaller values will make wheel-zooming
13455 // faster (and vice versa).
13456 wheelPxPerZoomLevel: 60
13459 var ScrollWheelZoom = Handler.extend({
13460 addHooks: function () {
13461 on(this._map._container, 'mousewheel', this._onWheelScroll, this);
13466 removeHooks: function () {
13467 off(this._map._container, 'mousewheel', this._onWheelScroll, this);
13470 _onWheelScroll: function (e) {
13471 var delta = getWheelDelta(e);
13473 var debounce = this._map.options.wheelDebounceTime;
13475 this._delta += delta;
13476 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
13478 if (!this._startTime) {
13479 this._startTime = +new Date();
13482 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
13484 clearTimeout(this._timer);
13485 this._timer = setTimeout(bind(this._performZoom, this), left);
13490 _performZoom: function () {
13491 var map = this._map,
13492 zoom = map.getZoom(),
13493 snap = this._map.options.zoomSnap || 0;
13495 map._stop(); // stop panning and fly animations if any
13497 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
13498 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
13499 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
13500 d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
13501 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
13504 this._startTime = null;
13506 if (!delta) { return; }
13508 if (map.options.scrollWheelZoom === 'center') {
13509 map.setZoom(zoom + delta);
13511 map.setZoomAround(this._lastMousePos, zoom + delta);
13516 // @section Handlers
13517 // @property scrollWheelZoom: Handler
13518 // Scroll wheel zoom handler.
13519 Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
13522 * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
13526 // @section Interaction Options
13528 // @section Touch interaction options
13529 // @option tap: Boolean = true
13530 // Enables mobile hacks for supporting instant taps (fixing 200ms click
13531 // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
13534 // @option tapTolerance: Number = 15
13535 // The max number of pixels a user can shift his finger during touch
13536 // for it to be considered a valid tap.
13540 var Tap = Handler.extend({
13541 addHooks: function () {
13542 on(this._map._container, 'touchstart', this._onDown, this);
13545 removeHooks: function () {
13546 off(this._map._container, 'touchstart', this._onDown, this);
13549 _onDown: function (e) {
13550 if (!e.touches) { return; }
13554 this._fireClick = true;
13556 // don't simulate click or track longpress if more than 1 touch
13557 if (e.touches.length > 1) {
13558 this._fireClick = false;
13559 clearTimeout(this._holdTimeout);
13563 var first = e.touches[0],
13566 this._startPos = this._newPos = new Point(first.clientX, first.clientY);
13568 // if touching a link, highlight it
13569 if (el.tagName && el.tagName.toLowerCase() === 'a') {
13570 addClass(el, 'leaflet-active');
13573 // simulate long hold but setting a timeout
13574 this._holdTimeout = setTimeout(bind(function () {
13575 if (this._isTapValid()) {
13576 this._fireClick = false;
13578 this._simulateEvent('contextmenu', first);
13582 this._simulateEvent('mousedown', first);
13585 touchmove: this._onMove,
13586 touchend: this._onUp
13590 _onUp: function (e) {
13591 clearTimeout(this._holdTimeout);
13594 touchmove: this._onMove,
13595 touchend: this._onUp
13598 if (this._fireClick && e && e.changedTouches) {
13600 var first = e.changedTouches[0],
13603 if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
13604 removeClass(el, 'leaflet-active');
13607 this._simulateEvent('mouseup', first);
13609 // simulate click if the touch didn't move too much
13610 if (this._isTapValid()) {
13611 this._simulateEvent('click', first);
13616 _isTapValid: function () {
13617 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
13620 _onMove: function (e) {
13621 var first = e.touches[0];
13622 this._newPos = new Point(first.clientX, first.clientY);
13623 this._simulateEvent('mousemove', first);
13626 _simulateEvent: function (type, e) {
13627 var simulatedEvent = document.createEvent('MouseEvents');
13629 simulatedEvent._simulated = true;
13630 e.target._simulatedClick = true;
13632 simulatedEvent.initMouseEvent(
13633 type, true, true, window, 1,
13634 e.screenX, e.screenY,
13635 e.clientX, e.clientY,
13636 false, false, false, false, 0, null);
13638 e.target.dispatchEvent(simulatedEvent);
13642 // @section Handlers
13643 // @property tap: Handler
13644 // Mobile touch hacks (quick tap and touch hold) handler.
13645 if (touch && !pointer) {
13646 Map.addInitHook('addHandler', 'tap', Tap);
13650 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
13654 // @section Interaction Options
13656 // @section Touch interaction options
13657 // @option touchZoom: Boolean|String = *
13658 // Whether the map can be zoomed by touch-dragging with two fingers. If
13659 // passed `'center'`, it will zoom to the center of the view regardless of
13660 // where the touch events (fingers) were. Enabled for touch-capable web
13661 // browsers except for old Androids.
13662 touchZoom: touch && !android23,
13664 // @option bounceAtZoomLimits: Boolean = true
13665 // Set it to false if you don't want the map to zoom beyond min/max zoom
13666 // and then bounce back when pinch-zooming.
13667 bounceAtZoomLimits: true
13670 var TouchZoom = Handler.extend({
13671 addHooks: function () {
13672 addClass(this._map._container, 'leaflet-touch-zoom');
13673 on(this._map._container, 'touchstart', this._onTouchStart, this);
13676 removeHooks: function () {
13677 removeClass(this._map._container, 'leaflet-touch-zoom');
13678 off(this._map._container, 'touchstart', this._onTouchStart, this);
13681 _onTouchStart: function (e) {
13682 var map = this._map;
13683 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
13685 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
13686 p2 = map.mouseEventToContainerPoint(e.touches[1]);
13688 this._centerPoint = map.getSize()._divideBy(2);
13689 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
13690 if (map.options.touchZoom !== 'center') {
13691 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
13694 this._startDist = p1.distanceTo(p2);
13695 this._startZoom = map.getZoom();
13697 this._moved = false;
13698 this._zooming = true;
13702 on(document, 'touchmove', this._onTouchMove, this);
13703 on(document, 'touchend', this._onTouchEnd, this);
13708 _onTouchMove: function (e) {
13709 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
13711 var map = this._map,
13712 p1 = map.mouseEventToContainerPoint(e.touches[0]),
13713 p2 = map.mouseEventToContainerPoint(e.touches[1]),
13714 scale = p1.distanceTo(p2) / this._startDist;
13716 this._zoom = map.getScaleZoom(scale, this._startZoom);
13718 if (!map.options.bounceAtZoomLimits && (
13719 (this._zoom < map.getMinZoom() && scale < 1) ||
13720 (this._zoom > map.getMaxZoom() && scale > 1))) {
13721 this._zoom = map._limitZoom(this._zoom);
13724 if (map.options.touchZoom === 'center') {
13725 this._center = this._startLatLng;
13726 if (scale === 1) { return; }
13728 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
13729 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
13730 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
13731 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
13734 if (!this._moved) {
13735 map._moveStart(true, false);
13736 this._moved = true;
13739 cancelAnimFrame(this._animRequest);
13741 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
13742 this._animRequest = requestAnimFrame(moveFn, this, true);
13747 _onTouchEnd: function () {
13748 if (!this._moved || !this._zooming) {
13749 this._zooming = false;
13753 this._zooming = false;
13754 cancelAnimFrame(this._animRequest);
13756 off(document, 'touchmove', this._onTouchMove);
13757 off(document, 'touchend', this._onTouchEnd);
13759 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
13760 if (this._map.options.zoomAnimation) {
13761 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
13763 this._map._resetView(this._center, this._map._limitZoom(this._zoom));
13768 // @section Handlers
13769 // @property touchZoom: Handler
13770 // Touch zoom handler.
13771 Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
13773 Map.BoxZoom = BoxZoom;
13774 Map.DoubleClickZoom = DoubleClickZoom;
13776 Map.Keyboard = Keyboard;
13777 Map.ScrollWheelZoom = ScrollWheelZoom;
13779 Map.TouchZoom = TouchZoom;
13781 Object.freeze = freeze;
13783 exports.version = version;
13784 exports.Control = Control;
13785 exports.control = control;
13786 exports.Browser = Browser;
13787 exports.Evented = Evented;
13788 exports.Mixin = Mixin;
13789 exports.Util = Util;
13790 exports.Class = Class;
13791 exports.Handler = Handler;
13792 exports.extend = extend;
13793 exports.bind = bind;
13794 exports.stamp = stamp;
13795 exports.setOptions = setOptions;
13796 exports.DomEvent = DomEvent;
13797 exports.DomUtil = DomUtil;
13798 exports.PosAnimation = PosAnimation;
13799 exports.Draggable = Draggable;
13800 exports.LineUtil = LineUtil;
13801 exports.PolyUtil = PolyUtil;
13802 exports.Point = Point;
13803 exports.point = toPoint;
13804 exports.Bounds = Bounds;
13805 exports.bounds = toBounds;
13806 exports.Transformation = Transformation;
13807 exports.transformation = toTransformation;
13808 exports.Projection = index;
13809 exports.LatLng = LatLng;
13810 exports.latLng = toLatLng;
13811 exports.LatLngBounds = LatLngBounds;
13812 exports.latLngBounds = toLatLngBounds;
13814 exports.GeoJSON = GeoJSON;
13815 exports.geoJSON = geoJSON;
13816 exports.geoJson = geoJson;
13817 exports.Layer = Layer;
13818 exports.LayerGroup = LayerGroup;
13819 exports.layerGroup = layerGroup;
13820 exports.FeatureGroup = FeatureGroup;
13821 exports.featureGroup = featureGroup;
13822 exports.ImageOverlay = ImageOverlay;
13823 exports.imageOverlay = imageOverlay;
13824 exports.VideoOverlay = VideoOverlay;
13825 exports.videoOverlay = videoOverlay;
13826 exports.DivOverlay = DivOverlay;
13827 exports.Popup = Popup;
13828 exports.popup = popup;
13829 exports.Tooltip = Tooltip;
13830 exports.tooltip = tooltip;
13831 exports.Icon = Icon;
13832 exports.icon = icon;
13833 exports.DivIcon = DivIcon;
13834 exports.divIcon = divIcon;
13835 exports.Marker = Marker;
13836 exports.marker = marker;
13837 exports.TileLayer = TileLayer;
13838 exports.tileLayer = tileLayer;
13839 exports.GridLayer = GridLayer;
13840 exports.gridLayer = gridLayer;
13842 exports.svg = svg$1;
13843 exports.Renderer = Renderer;
13844 exports.Canvas = Canvas;
13845 exports.canvas = canvas$1;
13846 exports.Path = Path;
13847 exports.CircleMarker = CircleMarker;
13848 exports.circleMarker = circleMarker;
13849 exports.Circle = Circle;
13850 exports.circle = circle;
13851 exports.Polyline = Polyline;
13852 exports.polyline = polyline;
13853 exports.Polygon = Polygon;
13854 exports.polygon = polygon;
13855 exports.Rectangle = Rectangle;
13856 exports.rectangle = rectangle;
13858 exports.map = createMap;
13860 var oldL = window.L;
13861 exports.noConflict = function() {
13866 // Always export us to window global (see #2364)
13867 window.L = exports;
13870 //# sourceMappingURL=leaflet-src.js.map