2 * Leaflet 1.4.0, 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.4.0";
17 * Various utility functions, used by Leaflet internally.
20 var freeze = Object.freeze;
21 Object.freeze = function (obj) { return obj; };
23 // @function extend(dest: Object, src?: Object): Object
24 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
25 function extend(dest) {
28 for (j = 1, len = arguments.length; j < len; j++) {
37 // @function create(proto: Object, properties?: Object): Object
38 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
39 var create = Object.create || (function () {
41 return function (proto) {
47 // @function bind(fn: Function, …): Function
48 // Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
49 // Has a `L.bind()` shortcut.
50 function bind(fn, obj) {
51 var slice = Array.prototype.slice;
54 return fn.bind.apply(fn, slice.call(arguments, 1));
57 var args = slice.call(arguments, 2);
60 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
64 // @property lastId: Number
65 // Last unique ID used by [`stamp()`](#util-stamp)
68 // @function stamp(obj: Object): Number
69 // Returns the unique ID of an object, assigning it one if it doesn't have it.
72 obj._leaflet_id = obj._leaflet_id || ++lastId;
73 return obj._leaflet_id;
77 // @function throttle(fn: Function, time: Number, context: Object): Function
78 // Returns a function which executes function `fn` with the given scope `context`
79 // (so that the `this` keyword refers to `context` inside `fn`'s code). The function
80 // `fn` will be called no more than one time per given amount of `time`. The arguments
81 // received by the bound function will be any arguments passed when binding the
82 // function, followed by any arguments passed when invoking the bound function.
83 // Has an `L.throttle` shortcut.
84 function throttle(fn, time, context) {
85 var lock, args, wrapperFn, later;
88 // reset lock and call if queued
91 wrapperFn.apply(context, args);
96 wrapperFn = function () {
98 // called too soon, queue to call later
102 // call and lock until later
103 fn.apply(context, arguments);
104 setTimeout(later, time);
112 // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
113 // Returns the number `num` modulo `range` in such a way so it lies within
114 // `range[0]` and `range[1]`. The returned value will be always smaller than
115 // `range[1]` unless `includeMax` is set to `true`.
116 function wrapNum(x, range, includeMax) {
120 return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
123 // @function falseFn(): Function
124 // Returns a function which always returns `false`.
125 function falseFn() { return false; }
127 // @function formatNum(num: Number, digits?: Number): Number
128 // Returns the number `num` rounded to `digits` decimals, or to 6 decimals by default.
129 function formatNum(num, digits) {
130 var pow = Math.pow(10, (digits === undefined ? 6 : digits));
131 return Math.round(num * pow) / pow;
134 // @function trim(str: String): String
135 // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
137 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
140 // @function splitWords(str: String): String[]
141 // Trims and splits the string on whitespace and returns the array of parts.
142 function splitWords(str) {
143 return trim(str).split(/\s+/);
146 // @function setOptions(obj: Object, options: Object): Object
147 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
148 function setOptions(obj, options) {
149 if (!obj.hasOwnProperty('options')) {
150 obj.options = obj.options ? create(obj.options) : {};
152 for (var i in options) {
153 obj.options[i] = options[i];
158 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
159 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
160 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
161 // be appended at the end. If `uppercase` is `true`, the parameter names will
162 // be uppercased (e.g. `'?A=foo&B=bar'`)
163 function getParamString(obj, existingUrl, uppercase) {
166 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
168 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
171 var templateRe = /\{ *([\w_-]+) *\}/g;
173 // @function template(str: String, data: Object): String
174 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
175 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
176 // `('Hello foo, bar')`. You can also specify functions instead of strings for
177 // data values — they will be evaluated passing `data` as an argument.
178 function template(str, data) {
179 return str.replace(templateRe, function (str, key) {
180 var value = data[key];
182 if (value === undefined) {
183 throw new Error('No value provided for variable ' + str);
185 } else if (typeof value === 'function') {
192 // @function isArray(obj): Boolean
193 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
194 var isArray = Array.isArray || function (obj) {
195 return (Object.prototype.toString.call(obj) === '[object Array]');
198 // @function indexOf(array: Array, el: Object): Number
199 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
200 function indexOf(array, el) {
201 for (var i = 0; i < array.length; i++) {
202 if (array[i] === el) { return i; }
207 // @property emptyImageUrl: String
208 // Data URI string containing a base64-encoded empty GIF image.
209 // Used as a hack to free memory from unused images on WebKit-powered
210 // mobile devices (by setting image `src` to this string).
211 var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
213 // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
215 function getPrefixed(name) {
216 return window['webkit' + name] || window['moz' + name] || window['ms' + name];
221 // fallback for IE 7-8
222 function timeoutDefer(fn) {
223 var time = +new Date(),
224 timeToCall = Math.max(0, 16 - (time - lastTime));
226 lastTime = time + timeToCall;
227 return window.setTimeout(fn, timeToCall);
230 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
231 var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
232 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
234 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
235 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
236 // `context` if given. When `immediate` is set, `fn` is called immediately if
237 // the browser doesn't have native support for
238 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
239 // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
240 function requestAnimFrame(fn, context, immediate) {
241 if (immediate && requestFn === timeoutDefer) {
244 return requestFn.call(window, bind(fn, context));
248 // @function cancelAnimFrame(id: Number): undefined
249 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
250 function cancelAnimFrame(id) {
252 cancelFn.call(window, id);
257 var Util = (Object.freeze || Object)({
267 formatNum: formatNum,
269 splitWords: splitWords,
270 setOptions: setOptions,
271 getParamString: getParamString,
275 emptyImageUrl: emptyImageUrl,
276 requestFn: requestFn,
278 requestAnimFrame: requestAnimFrame,
279 cancelAnimFrame: cancelAnimFrame
288 // Thanks to John Resig and Dean Edwards for inspiration!
292 Class.extend = function (props) {
294 // @function extend(props: Object): Function
295 // [Extends the current class](#class-inheritance) given the properties to be included.
296 // Returns a Javascript function that is a class constructor (to be called with `new`).
297 var NewClass = function () {
299 // call the constructor
300 if (this.initialize) {
301 this.initialize.apply(this, arguments);
304 // call all constructor hooks
305 this.callInitHooks();
308 var parentProto = NewClass.__super__ = this.prototype;
310 var proto = create(parentProto);
311 proto.constructor = NewClass;
313 NewClass.prototype = proto;
315 // inherit parent's statics
316 for (var i in this) {
317 if (this.hasOwnProperty(i) && i !== 'prototype' && i !== '__super__') {
318 NewClass[i] = this[i];
322 // mix static properties into the class
324 extend(NewClass, props.statics);
325 delete props.statics;
328 // mix includes into the prototype
329 if (props.includes) {
330 checkDeprecatedMixinEvents(props.includes);
331 extend.apply(null, [proto].concat(props.includes));
332 delete props.includes;
337 props.options = extend(create(proto.options), props.options);
340 // mix given properties into the prototype
341 extend(proto, props);
343 proto._initHooks = [];
345 // add method for calling all hooks
346 proto.callInitHooks = function () {
348 if (this._initHooksCalled) { return; }
350 if (parentProto.callInitHooks) {
351 parentProto.callInitHooks.call(this);
354 this._initHooksCalled = true;
356 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
357 proto._initHooks[i].call(this);
365 // @function include(properties: Object): this
366 // [Includes a mixin](#class-includes) into the current class.
367 Class.include = function (props) {
368 extend(this.prototype, props);
372 // @function mergeOptions(options: Object): this
373 // [Merges `options`](#class-options) into the defaults of the class.
374 Class.mergeOptions = function (options) {
375 extend(this.prototype.options, options);
379 // @function addInitHook(fn: Function): this
380 // Adds a [constructor hook](#class-constructor-hooks) to the class.
381 Class.addInitHook = function (fn) { // (Function) || (String, args...)
382 var args = Array.prototype.slice.call(arguments, 1);
384 var init = typeof fn === 'function' ? fn : function () {
385 this[fn].apply(this, args);
388 this.prototype._initHooks = this.prototype._initHooks || [];
389 this.prototype._initHooks.push(init);
393 function checkDeprecatedMixinEvents(includes) {
394 if (typeof L === 'undefined' || !L || !L.Mixin) { return; }
396 includes = isArray(includes) ? includes : [includes];
398 for (var i = 0; i < includes.length; i++) {
399 if (includes[i] === L.Mixin.Events) {
400 console.warn('Deprecated include of L.Mixin.Events: ' +
401 'this property will be removed in future releases, ' +
402 'please inherit from L.Evented instead.', new Error().stack);
412 * A set of methods shared between event-powered classes (like `Map` and `Marker`). Generally, events allow you to execute some function when something happens with an object (e.g. the user clicks on the map, causing the map to fire `'click'` event).
417 * map.on('click', function(e) {
422 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
425 * function onClick(e) { ... }
427 * map.on('click', onClick);
428 * map.off('click', onClick);
433 /* @method on(type: String, fn: Function, context?: Object): this
434 * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`).
437 * @method on(eventMap: Object): this
438 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
440 on: function (types, fn, context) {
442 // types can be a map of types/handlers
443 if (typeof types === 'object') {
444 for (var type in types) {
445 // we don't process space-separated events here for performance;
446 // it's a hot path since Layer uses the on(obj) syntax
447 this._on(type, types[type], fn);
451 // types can be a string of space-separated words
452 types = splitWords(types);
454 for (var i = 0, len = types.length; i < len; i++) {
455 this._on(types[i], fn, context);
462 /* @method off(type: String, fn?: Function, context?: Object): this
463 * Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener.
466 * @method off(eventMap: Object): this
467 * Removes a set of type/listener pairs.
471 * Removes all listeners to all events on the object.
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 && 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 && 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 // Check if the element is an SVGElementInstance and use the correspondingElement instead
2356 // (Required for linked SVG elements in IE11.)
2357 if (el.correspondingElement) {
2358 el = el.correspondingElement;
2360 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
2363 // @function setOpacity(el: HTMLElement, opacity: Number)
2364 // Set the opacity of an element (including old IE support).
2365 // `opacity` must be a number from `0` to `1`.
2366 function setOpacity(el, value) {
2367 if ('opacity' in el.style) {
2368 el.style.opacity = value;
2369 } else if ('filter' in el.style) {
2370 _setOpacityIE(el, value);
2374 function _setOpacityIE(el, value) {
2376 filterName = 'DXImageTransform.Microsoft.Alpha';
2378 // filters collection throws an error if we try to retrieve a filter that doesn't exist
2380 filter = el.filters.item(filterName);
2382 // don't set opacity to 1 if we haven't already set an opacity,
2383 // it isn't needed and breaks transparent pngs.
2384 if (value === 1) { return; }
2387 value = Math.round(value * 100);
2390 filter.Enabled = (value !== 100);
2391 filter.Opacity = value;
2393 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
2397 // @function testProp(props: String[]): String|false
2398 // Goes through the array of style names and returns the first name
2399 // that is a valid style name for an element. If no such name is found,
2400 // it returns false. Useful for vendor-prefixed styles like `transform`.
2401 function testProp(props) {
2402 var style = document.documentElement.style;
2404 for (var i = 0; i < props.length; i++) {
2405 if (props[i] in style) {
2412 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
2413 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
2414 // and optionally scaled by `scale`. Does not have an effect if the
2415 // browser doesn't support 3D CSS transforms.
2416 function setTransform(el, offset, scale) {
2417 var pos = offset || new Point(0, 0);
2419 el.style[TRANSFORM] =
2421 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
2422 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
2423 (scale ? ' scale(' + scale + ')' : '');
2426 // @function setPosition(el: HTMLElement, position: Point)
2427 // Sets the position of `el` to coordinates specified by `position`,
2428 // using CSS translate or top/left positioning depending on the browser
2429 // (used by Leaflet internally to position its layers).
2430 function setPosition(el, point) {
2433 el._leaflet_pos = point;
2437 setTransform(el, point);
2439 el.style.left = point.x + 'px';
2440 el.style.top = point.y + 'px';
2444 // @function getPosition(el: HTMLElement): Point
2445 // Returns the coordinates of an element previously positioned with setPosition.
2446 function getPosition(el) {
2447 // this method is only used for elements previously positioned using setPosition,
2448 // so it's safe to cache the position for performance
2450 return el._leaflet_pos || new Point(0, 0);
2453 // @function disableTextSelection()
2454 // Prevents the user from generating `selectstart` DOM events, usually generated
2455 // when the user drags the mouse through a page with text. Used internally
2456 // by Leaflet to override the behaviour of any click-and-drag interaction on
2457 // the map. Affects drag interactions on the whole document.
2459 // @function enableTextSelection()
2460 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
2461 var disableTextSelection;
2462 var enableTextSelection;
2464 if ('onselectstart' in document) {
2465 disableTextSelection = function () {
2466 on(window, 'selectstart', preventDefault);
2468 enableTextSelection = function () {
2469 off(window, 'selectstart', preventDefault);
2472 var userSelectProperty = testProp(
2473 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
2475 disableTextSelection = function () {
2476 if (userSelectProperty) {
2477 var style = document.documentElement.style;
2478 _userSelect = style[userSelectProperty];
2479 style[userSelectProperty] = 'none';
2482 enableTextSelection = function () {
2483 if (userSelectProperty) {
2484 document.documentElement.style[userSelectProperty] = _userSelect;
2485 _userSelect = undefined;
2490 // @function disableImageDrag()
2491 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
2492 // for `dragstart` DOM events, usually generated when the user drags an image.
2493 function disableImageDrag() {
2494 on(window, 'dragstart', preventDefault);
2497 // @function enableImageDrag()
2498 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
2499 function enableImageDrag() {
2500 off(window, 'dragstart', preventDefault);
2503 var _outlineElement;
2505 // @function preventOutline(el: HTMLElement)
2506 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
2507 // of the element `el` invisible. Used internally by Leaflet to prevent
2508 // focusable elements from displaying an outline when the user performs a
2509 // drag interaction on them.
2510 function preventOutline(element) {
2511 while (element.tabIndex === -1) {
2512 element = element.parentNode;
2514 if (!element.style) { return; }
2516 _outlineElement = element;
2517 _outlineStyle = element.style.outline;
2518 element.style.outline = 'none';
2519 on(window, 'keydown', restoreOutline);
2522 // @function restoreOutline()
2523 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
2524 function restoreOutline() {
2525 if (!_outlineElement) { return; }
2526 _outlineElement.style.outline = _outlineStyle;
2527 _outlineElement = undefined;
2528 _outlineStyle = undefined;
2529 off(window, 'keydown', restoreOutline);
2532 // @function getSizedParentNode(el: HTMLElement): HTMLElement
2533 // Finds the closest parent node which size (width and height) is not null.
2534 function getSizedParentNode(element) {
2536 element = element.parentNode;
2537 } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
2541 // @function getScale(el: HTMLElement): Object
2542 // Computes the CSS scale currently applied on the element.
2543 // Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
2544 // and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
2545 function getScale(element) {
2546 var rect = element.getBoundingClientRect(); // Read-only in old browsers.
2549 x: rect.width / element.offsetWidth || 1,
2550 y: rect.height / element.offsetHeight || 1,
2551 boundingClientRect: rect
2556 var DomUtil = (Object.freeze || Object)({
2557 TRANSFORM: TRANSFORM,
2558 TRANSITION: TRANSITION,
2559 TRANSITION_END: TRANSITION_END,
2569 removeClass: removeClass,
2572 setOpacity: setOpacity,
2574 setTransform: setTransform,
2575 setPosition: setPosition,
2576 getPosition: getPosition,
2577 disableTextSelection: disableTextSelection,
2578 enableTextSelection: enableTextSelection,
2579 disableImageDrag: disableImageDrag,
2580 enableImageDrag: enableImageDrag,
2581 preventOutline: preventOutline,
2582 restoreOutline: restoreOutline,
2583 getSizedParentNode: getSizedParentNode,
2588 * @namespace DomEvent
2589 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
2592 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
2594 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
2595 // Adds a listener function (`fn`) to a particular DOM event type of the
2596 // element `el`. You can optionally specify the context of the listener
2597 // (object the `this` keyword will point to). You can also pass several
2598 // space-separated types (e.g. `'click dblclick'`).
2601 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
2602 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2603 function on(obj, types, fn, context) {
2605 if (typeof types === 'object') {
2606 for (var type in types) {
2607 addOne(obj, type, types[type], fn);
2610 types = splitWords(types);
2612 for (var i = 0, len = types.length; i < len; i++) {
2613 addOne(obj, types[i], fn, context);
2620 var eventsKey = '_leaflet_events';
2622 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
2623 // Removes a previously added listener function.
2624 // Note that if you passed a custom context to on, you must pass the same
2625 // context to `off` in order to remove the listener.
2628 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
2629 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2630 function off(obj, types, fn, context) {
2632 if (typeof types === 'object') {
2633 for (var type in types) {
2634 removeOne(obj, type, types[type], fn);
2637 types = splitWords(types);
2639 for (var i = 0, len = types.length; i < len; i++) {
2640 removeOne(obj, types[i], fn, context);
2643 for (var j in obj[eventsKey]) {
2644 removeOne(obj, j, obj[eventsKey][j]);
2646 delete obj[eventsKey];
2652 function addOne(obj, type, fn, context) {
2653 var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
2655 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
2657 var handler = function (e) {
2658 return fn.call(context || obj, e || window.event);
2661 var originalHandler = handler;
2663 if (pointer && type.indexOf('touch') === 0) {
2664 // Needs DomEvent.Pointer.js
2665 addPointerListener(obj, type, handler, id);
2667 } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
2668 !(pointer && chrome)) {
2669 // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
2671 addDoubleTapListener(obj, handler, id);
2673 } else if ('addEventListener' in obj) {
2675 if (type === 'mousewheel') {
2676 obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2678 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
2679 handler = function (e) {
2680 e = e || window.event;
2681 if (isExternalTarget(obj, e)) {
2685 obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
2688 if (type === 'click' && android) {
2689 handler = function (e) {
2690 filterClick(e, originalHandler);
2693 obj.addEventListener(type, handler, false);
2696 } else if ('attachEvent' in obj) {
2697 obj.attachEvent('on' + type, handler);
2700 obj[eventsKey] = obj[eventsKey] || {};
2701 obj[eventsKey][id] = handler;
2704 function removeOne(obj, type, fn, context) {
2706 var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
2707 handler = obj[eventsKey] && obj[eventsKey][id];
2709 if (!handler) { return this; }
2711 if (pointer && type.indexOf('touch') === 0) {
2712 removePointerListener(obj, type, id);
2714 } else if (touch && (type === 'dblclick') && removeDoubleTapListener &&
2715 !(pointer && chrome)) {
2716 removeDoubleTapListener(obj, id);
2718 } else if ('removeEventListener' in obj) {
2720 if (type === 'mousewheel') {
2721 obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2724 obj.removeEventListener(
2725 type === 'mouseenter' ? 'mouseover' :
2726 type === 'mouseleave' ? 'mouseout' : type, handler, false);
2729 } else if ('detachEvent' in obj) {
2730 obj.detachEvent('on' + type, handler);
2733 obj[eventsKey][id] = null;
2736 // @function stopPropagation(ev: DOMEvent): this
2737 // Stop the given event from propagation to parent elements. Used inside the listener functions:
2739 // L.DomEvent.on(div, 'click', function (ev) {
2740 // L.DomEvent.stopPropagation(ev);
2743 function stopPropagation(e) {
2745 if (e.stopPropagation) {
2746 e.stopPropagation();
2747 } else if (e.originalEvent) { // In case of Leaflet event.
2748 e.originalEvent._stopped = true;
2750 e.cancelBubble = true;
2757 // @function disableScrollPropagation(el: HTMLElement): this
2758 // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
2759 function disableScrollPropagation(el) {
2760 addOne(el, 'mousewheel', stopPropagation);
2764 // @function disableClickPropagation(el: HTMLElement): this
2765 // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
2766 // `'mousedown'` and `'touchstart'` events (plus browser variants).
2767 function disableClickPropagation(el) {
2768 on(el, 'mousedown touchstart dblclick', stopPropagation);
2769 addOne(el, 'click', fakeStop);
2773 // @function preventDefault(ev: DOMEvent): this
2774 // Prevents the default action of the DOM Event `ev` from happening (such as
2775 // following a link in the href of the a element, or doing a POST request
2776 // with page reload when a `<form>` is submitted).
2777 // Use it inside listener functions.
2778 function preventDefault(e) {
2779 if (e.preventDefault) {
2782 e.returnValue = false;
2787 // @function stop(ev: DOMEvent): this
2788 // Does `stopPropagation` and `preventDefault` at the same time.
2795 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
2796 // Gets normalized mouse position from a DOM event relative to the
2797 // `container` (border excluded) or to the whole page if not specified.
2798 function getMousePosition(e, container) {
2800 return new Point(e.clientX, e.clientY);
2803 var scale = getScale(container),
2804 offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y)
2807 // offset.left/top values are in page scale (like clientX/Y),
2808 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
2809 (e.clientX - offset.left) / scale.x - container.clientLeft,
2810 (e.clientY - offset.top) / scale.y - container.clientTop
2814 // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
2815 // and Firefox scrolls device pixels, not CSS pixels
2817 (win && chrome) ? 2 * window.devicePixelRatio :
2818 gecko ? window.devicePixelRatio : 1;
2820 // @function getWheelDelta(ev: DOMEvent): Number
2821 // Gets normalized wheel delta from a mousewheel DOM event, in vertical
2822 // pixels scrolled (negative if scrolling down).
2823 // Events from pointing devices without precise scrolling are mapped to
2824 // a best guess of 60 pixels.
2825 function getWheelDelta(e) {
2826 return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
2827 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
2828 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
2829 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
2830 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
2831 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
2832 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
2833 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
2837 var skipEvents = {};
2839 function fakeStop(e) {
2840 // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
2841 skipEvents[e.type] = true;
2844 function skipped(e) {
2845 var events = skipEvents[e.type];
2846 // reset when checking, as it's only used in map container and propagates outside of the map
2847 skipEvents[e.type] = false;
2851 // check if element really left/entered the event target (for mouseenter/mouseleave)
2852 function isExternalTarget(el, e) {
2854 var related = e.relatedTarget;
2856 if (!related) { return true; }
2859 while (related && (related !== el)) {
2860 related = related.parentNode;
2865 return (related !== el);
2870 // this is a horrible workaround for a bug in Android where a single touch triggers two click events
2871 function filterClick(e, handler) {
2872 var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
2873 elapsed = lastClick && (timeStamp - lastClick);
2875 // are they closer together than 500ms yet more than 100ms?
2876 // Android typically triggers them ~300ms apart while multiple listeners
2877 // on the same event should be triggered far faster;
2878 // or check if click is simulated on the element, and if it is, reject any non-simulated events
2880 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
2884 lastClick = timeStamp;
2892 var DomEvent = (Object.freeze || Object)({
2895 stopPropagation: stopPropagation,
2896 disableScrollPropagation: disableScrollPropagation,
2897 disableClickPropagation: disableClickPropagation,
2898 preventDefault: preventDefault,
2900 getMousePosition: getMousePosition,
2901 getWheelDelta: getWheelDelta,
2904 isExternalTarget: isExternalTarget,
2910 * @class PosAnimation
2911 * @aka L.PosAnimation
2913 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
2917 * var fx = new L.PosAnimation();
2918 * fx.run(el, [300, 500], 0.5);
2921 * @constructor L.PosAnimation()
2922 * Creates a `PosAnimation` object.
2926 var PosAnimation = Evented.extend({
2928 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
2929 // Run an animation of a given element to a new position, optionally setting
2930 // duration in seconds (`0.25` by default) and easing linearity factor (3rd
2931 // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
2932 // `0.5` by default).
2933 run: function (el, newPos, duration, easeLinearity) {
2937 this._inProgress = true;
2938 this._duration = duration || 0.25;
2939 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
2941 this._startPos = getPosition(el);
2942 this._offset = newPos.subtract(this._startPos);
2943 this._startTime = +new Date();
2945 // @event start: Event
2946 // Fired when the animation starts
2953 // Stops the animation (if currently running).
2955 if (!this._inProgress) { return; }
2961 _animate: function () {
2963 this._animId = requestAnimFrame(this._animate, this);
2967 _step: function (round) {
2968 var elapsed = (+new Date()) - this._startTime,
2969 duration = this._duration * 1000;
2971 if (elapsed < duration) {
2972 this._runFrame(this._easeOut(elapsed / duration), round);
2979 _runFrame: function (progress, round) {
2980 var pos = this._startPos.add(this._offset.multiplyBy(progress));
2984 setPosition(this._el, pos);
2986 // @event step: Event
2987 // Fired continuously during the animation.
2991 _complete: function () {
2992 cancelAnimFrame(this._animId);
2994 this._inProgress = false;
2995 // @event end: Event
2996 // Fired when the animation ends.
3000 _easeOut: function (t) {
3001 return 1 - Math.pow(1 - t, this._easeOutPower);
3010 * The central class of the API — it is used to create a map on a page and manipulate it.
3015 * // initialize the map on the "map" div with a given center and zoom
3016 * var map = L.map('map', {
3017 * center: [51.505, -0.09],
3024 var Map = Evented.extend({
3027 // @section Map State Options
3028 // @option crs: CRS = L.CRS.EPSG3857
3029 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
3030 // sure what it means.
3033 // @option center: LatLng = undefined
3034 // Initial geographic center of the map
3037 // @option zoom: Number = undefined
3038 // Initial map zoom level
3041 // @option minZoom: Number = *
3042 // Minimum zoom level of the map.
3043 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3044 // the lowest of their `minZoom` options will be used instead.
3047 // @option maxZoom: Number = *
3048 // Maximum zoom level of the map.
3049 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3050 // the highest of their `maxZoom` options will be used instead.
3053 // @option layers: Layer[] = []
3054 // Array of layers that will be added to the map initially
3057 // @option maxBounds: LatLngBounds = null
3058 // When this option is set, the map restricts the view to the given
3059 // geographical bounds, bouncing the user back if the user tries to pan
3060 // outside the view. To set the restriction dynamically, use
3061 // [`setMaxBounds`](#map-setmaxbounds) method.
3062 maxBounds: undefined,
3064 // @option renderer: Renderer = *
3065 // The default method for drawing vector layers on the map. `L.SVG`
3066 // or `L.Canvas` by default depending on browser support.
3067 renderer: undefined,
3070 // @section Animation Options
3071 // @option zoomAnimation: Boolean = true
3072 // Whether the map zoom animation is enabled. By default it's enabled
3073 // in all browsers that support CSS3 Transitions except Android.
3074 zoomAnimation: true,
3076 // @option zoomAnimationThreshold: Number = 4
3077 // Won't animate zoom if the zoom difference exceeds this value.
3078 zoomAnimationThreshold: 4,
3080 // @option fadeAnimation: Boolean = true
3081 // Whether the tile fade animation is enabled. By default it's enabled
3082 // in all browsers that support CSS3 Transitions except Android.
3083 fadeAnimation: true,
3085 // @option markerZoomAnimation: Boolean = true
3086 // Whether markers animate their zoom with the zoom animation, if disabled
3087 // they will disappear for the length of the animation. By default it's
3088 // enabled in all browsers that support CSS3 Transitions except Android.
3089 markerZoomAnimation: true,
3091 // @option transform3DLimit: Number = 2^23
3092 // Defines the maximum size of a CSS translation transform. The default
3093 // value should not be changed unless a web browser positions layers in
3094 // the wrong place after doing a large `panBy`.
3095 transform3DLimit: 8388608, // Precision limit of a 32-bit float
3097 // @section Interaction Options
3098 // @option zoomSnap: Number = 1
3099 // Forces the map's zoom level to always be a multiple of this, particularly
3100 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
3101 // By default, the zoom level snaps to the nearest integer; lower values
3102 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
3103 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
3106 // @option zoomDelta: Number = 1
3107 // Controls how much the map's zoom level will change after a
3108 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
3109 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
3110 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
3113 // @option trackResize: Boolean = true
3114 // Whether the map automatically handles browser window resize to update itself.
3118 initialize: function (id, options) { // (HTMLElement or String, Object)
3119 options = setOptions(this, options);
3121 // Make sure to assign internal flags at the beginning,
3122 // to avoid inconsistent state in some edge cases.
3123 this._handlers = [];
3125 this._zoomBoundLayers = {};
3126 this._sizeChanged = true;
3128 this._initContainer(id);
3131 // hack for https://github.com/Leaflet/Leaflet/issues/1980
3132 this._onResize = bind(this._onResize, this);
3136 if (options.maxBounds) {
3137 this.setMaxBounds(options.maxBounds);
3140 if (options.zoom !== undefined) {
3141 this._zoom = this._limitZoom(options.zoom);
3144 if (options.center && options.zoom !== undefined) {
3145 this.setView(toLatLng(options.center), options.zoom, {reset: true});
3148 this.callInitHooks();
3150 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
3151 this._zoomAnimated = TRANSITION && any3d && !mobileOpera &&
3152 this.options.zoomAnimation;
3154 // zoom transitions run with the same duration for all layers, so if one of transitionend events
3155 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
3156 if (this._zoomAnimated) {
3157 this._createAnimProxy();
3158 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
3161 this._addLayers(this.options.layers);
3165 // @section Methods for modifying map state
3167 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
3168 // Sets the view of the map (geographical center and zoom) with the given
3169 // animation options.
3170 setView: function (center, zoom, options) {
3172 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
3173 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
3174 options = options || {};
3178 if (this._loaded && !options.reset && options !== true) {
3180 if (options.animate !== undefined) {
3181 options.zoom = extend({animate: options.animate}, options.zoom);
3182 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
3185 // try animating pan or zoom
3186 var moved = (this._zoom !== zoom) ?
3187 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
3188 this._tryAnimatedPan(center, options.pan);
3191 // prevent resize handler call, the view will refresh after animation anyway
3192 clearTimeout(this._sizeTimer);
3197 // animation didn't start, just reset the map view
3198 this._resetView(center, zoom);
3203 // @method setZoom(zoom: Number, options?: Zoom/pan options): this
3204 // Sets the zoom of the map.
3205 setZoom: function (zoom, options) {
3206 if (!this._loaded) {
3210 return this.setView(this.getCenter(), zoom, {zoom: options});
3213 // @method zoomIn(delta?: Number, options?: Zoom options): this
3214 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3215 zoomIn: function (delta, options) {
3216 delta = delta || (any3d ? this.options.zoomDelta : 1);
3217 return this.setZoom(this._zoom + delta, options);
3220 // @method zoomOut(delta?: Number, options?: Zoom options): this
3221 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3222 zoomOut: function (delta, options) {
3223 delta = delta || (any3d ? this.options.zoomDelta : 1);
3224 return this.setZoom(this._zoom - delta, options);
3227 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
3228 // Zooms the map while keeping a specified geographical point on the map
3229 // stationary (e.g. used internally for scroll zoom and double-click zoom).
3231 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
3232 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
3233 setZoomAround: function (latlng, zoom, options) {
3234 var scale = this.getZoomScale(zoom),
3235 viewHalf = this.getSize().divideBy(2),
3236 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
3238 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
3239 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
3241 return this.setView(newCenter, zoom, {zoom: options});
3244 _getBoundsCenterZoom: function (bounds, options) {
3246 options = options || {};
3247 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
3249 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3250 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3252 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
3254 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
3256 if (zoom === Infinity) {
3258 center: bounds.getCenter(),
3263 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
3265 swPoint = this.project(bounds.getSouthWest(), zoom),
3266 nePoint = this.project(bounds.getNorthEast(), zoom),
3267 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
3275 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
3276 // Sets a map view that contains the given geographical bounds with the
3277 // maximum zoom level possible.
3278 fitBounds: function (bounds, options) {
3280 bounds = toLatLngBounds(bounds);
3282 if (!bounds.isValid()) {
3283 throw new Error('Bounds are not valid.');
3286 var target = this._getBoundsCenterZoom(bounds, options);
3287 return this.setView(target.center, target.zoom, options);
3290 // @method fitWorld(options?: fitBounds options): this
3291 // Sets a map view that mostly contains the whole world with the maximum
3292 // zoom level possible.
3293 fitWorld: function (options) {
3294 return this.fitBounds([[-90, -180], [90, 180]], options);
3297 // @method panTo(latlng: LatLng, options?: Pan options): this
3298 // Pans the map to a given center.
3299 panTo: function (center, options) { // (LatLng)
3300 return this.setView(center, this._zoom, {pan: options});
3303 // @method panBy(offset: Point, options?: Pan options): this
3304 // Pans the map by a given number of pixels (animated).
3305 panBy: function (offset, options) {
3306 offset = toPoint(offset).round();
3307 options = options || {};
3309 if (!offset.x && !offset.y) {
3310 return this.fire('moveend');
3312 // If we pan too far, Chrome gets issues with tiles
3313 // and makes them disappear or appear in the wrong place (slightly offset) #2602
3314 if (options.animate !== true && !this.getSize().contains(offset)) {
3315 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
3319 if (!this._panAnim) {
3320 this._panAnim = new PosAnimation();
3323 'step': this._onPanTransitionStep,
3324 'end': this._onPanTransitionEnd
3328 // don't fire movestart if animating inertia
3329 if (!options.noMoveStart) {
3330 this.fire('movestart');
3333 // animate pan unless animate: false specified
3334 if (options.animate !== false) {
3335 addClass(this._mapPane, 'leaflet-pan-anim');
3337 var newPos = this._getMapPanePos().subtract(offset).round();
3338 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
3340 this._rawPanBy(offset);
3341 this.fire('move').fire('moveend');
3347 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
3348 // Sets the view of the map (geographical center and zoom) performing a smooth
3349 // pan-zoom animation.
3350 flyTo: function (targetCenter, targetZoom, options) {
3352 options = options || {};
3353 if (options.animate === false || !any3d) {
3354 return this.setView(targetCenter, targetZoom, options);
3359 var from = this.project(this.getCenter()),
3360 to = this.project(targetCenter),
3361 size = this.getSize(),
3362 startZoom = this._zoom;
3364 targetCenter = toLatLng(targetCenter);
3365 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
3367 var w0 = Math.max(size.x, size.y),
3368 w1 = w0 * this.getZoomScale(startZoom, targetZoom),
3369 u1 = (to.distanceTo(from)) || 1,
3374 var s1 = i ? -1 : 1,
3376 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
3377 b1 = 2 * s2 * rho2 * u1,
3379 sq = Math.sqrt(b * b + 1) - b;
3381 // workaround for floating point precision bug when sq = 0, log = -Infinite,
3382 // thus triggering an infinite loop in flyTo
3383 var log = sq < 0.000000001 ? -18 : Math.log(sq);
3388 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
3389 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
3390 function tanh(n) { return sinh(n) / cosh(n); }
3394 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
3395 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
3397 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
3399 var start = Date.now(),
3400 S = (r(1) - r0) / rho,
3401 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
3404 var t = (Date.now() - start) / duration,
3408 this._flyToFrame = requestAnimFrame(frame, this);
3411 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
3412 this.getScaleZoom(w0 / w(s), startZoom),
3417 ._move(targetCenter, targetZoom)
3422 this._moveStart(true, options.noMoveStart);
3428 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
3429 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
3430 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
3431 flyToBounds: function (bounds, options) {
3432 var target = this._getBoundsCenterZoom(bounds, options);
3433 return this.flyTo(target.center, target.zoom, options);
3436 // @method setMaxBounds(bounds: Bounds): this
3437 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
3438 setMaxBounds: function (bounds) {
3439 bounds = toLatLngBounds(bounds);
3441 if (!bounds.isValid()) {
3442 this.options.maxBounds = null;
3443 return this.off('moveend', this._panInsideMaxBounds);
3444 } else if (this.options.maxBounds) {
3445 this.off('moveend', this._panInsideMaxBounds);
3448 this.options.maxBounds = bounds;
3451 this._panInsideMaxBounds();
3454 return this.on('moveend', this._panInsideMaxBounds);
3457 // @method setMinZoom(zoom: Number): this
3458 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
3459 setMinZoom: function (zoom) {
3460 var oldZoom = this.options.minZoom;
3461 this.options.minZoom = zoom;
3463 if (this._loaded && oldZoom !== zoom) {
3464 this.fire('zoomlevelschange');
3466 if (this.getZoom() < this.options.minZoom) {
3467 return this.setZoom(zoom);
3474 // @method setMaxZoom(zoom: Number): this
3475 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
3476 setMaxZoom: function (zoom) {
3477 var oldZoom = this.options.maxZoom;
3478 this.options.maxZoom = zoom;
3480 if (this._loaded && oldZoom !== zoom) {
3481 this.fire('zoomlevelschange');
3483 if (this.getZoom() > this.options.maxZoom) {
3484 return this.setZoom(zoom);
3491 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
3492 // 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.
3493 panInsideBounds: function (bounds, options) {
3494 this._enforcingBounds = true;
3495 var center = this.getCenter(),
3496 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
3498 if (!center.equals(newCenter)) {
3499 this.panTo(newCenter, options);
3502 this._enforcingBounds = false;
3506 // @method panInside(latlng: LatLng, options?: options): this
3507 // Pans the map the minimum amount to make the `latlng` visible. Use
3508 // `padding`, `paddingTopLeft` and `paddingTopRight` options to fit
3509 // the display to more restricted bounds, like [`fitBounds`](#map-fitbounds).
3510 // If `latlng` is already within the (optionally padded) display bounds,
3511 // the map will not be panned.
3512 panInside: function (latlng, options) {
3513 options = options || {};
3515 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3516 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3517 center = this.getCenter(),
3518 pixelCenter = this.project(center),
3519 pixelPoint = this.project(latlng),
3520 pixelBounds = this.getPixelBounds(),
3521 halfPixelBounds = pixelBounds.getSize().divideBy(2),
3522 paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]);
3524 if (!paddedBounds.contains(pixelPoint)) {
3525 this._enforcingBounds = true;
3526 var diff = pixelCenter.subtract(pixelPoint),
3527 newCenter = toPoint(pixelPoint.x + diff.x, pixelPoint.y + diff.y);
3529 if (pixelPoint.x < paddedBounds.min.x || pixelPoint.x > paddedBounds.max.x) {
3530 newCenter.x = pixelCenter.x - diff.x;
3532 newCenter.x += halfPixelBounds.x - paddingTL.x;
3534 newCenter.x -= halfPixelBounds.x - paddingBR.x;
3537 if (pixelPoint.y < paddedBounds.min.y || pixelPoint.y > paddedBounds.max.y) {
3538 newCenter.y = pixelCenter.y - diff.y;
3540 newCenter.y += halfPixelBounds.y - paddingTL.y;
3542 newCenter.y -= halfPixelBounds.y - paddingBR.y;
3545 this.panTo(this.unproject(newCenter), options);
3546 this._enforcingBounds = false;
3551 // @method invalidateSize(options: Zoom/pan options): this
3552 // Checks if the map container size changed and updates the map if so —
3553 // call it after you've changed the map size dynamically, also animating
3554 // pan by default. If `options.pan` is `false`, panning will not occur.
3555 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
3556 // that it doesn't happen often even if the method is called many
3560 // @method invalidateSize(animate: Boolean): this
3561 // Checks if the map container size changed and updates the map if so —
3562 // call it after you've changed the map size dynamically, also animating
3564 invalidateSize: function (options) {
3565 if (!this._loaded) { return this; }
3570 }, options === true ? {animate: true} : options);
3572 var oldSize = this.getSize();
3573 this._sizeChanged = true;
3574 this._lastCenter = null;
3576 var newSize = this.getSize(),
3577 oldCenter = oldSize.divideBy(2).round(),
3578 newCenter = newSize.divideBy(2).round(),
3579 offset = oldCenter.subtract(newCenter);
3581 if (!offset.x && !offset.y) { return this; }
3583 if (options.animate && options.pan) {
3588 this._rawPanBy(offset);
3593 if (options.debounceMoveend) {
3594 clearTimeout(this._sizeTimer);
3595 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
3597 this.fire('moveend');
3601 // @section Map state change events
3602 // @event resize: ResizeEvent
3603 // Fired when the map is resized.
3604 return this.fire('resize', {
3610 // @section Methods for modifying map state
3611 // @method stop(): this
3612 // Stops the currently running `panTo` or `flyTo` animation, if any.
3614 this.setZoom(this._limitZoom(this._zoom));
3615 if (!this.options.zoomSnap) {
3616 this.fire('viewreset');
3618 return this._stop();
3621 // @section Geolocation methods
3622 // @method locate(options?: Locate options): this
3623 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
3624 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
3625 // and optionally sets the map view to the user's location with respect to
3626 // detection accuracy (or to the world view if geolocation failed).
3627 // Note that, if your page doesn't use HTTPS, this method will fail in
3628 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
3629 // See `Locate options` for more details.
3630 locate: function (options) {
3632 options = this._locateOptions = extend({
3636 // maxZoom: <Number>
3638 // enableHighAccuracy: false
3641 if (!('geolocation' in navigator)) {
3642 this._handleGeolocationError({
3644 message: 'Geolocation not supported.'
3649 var onResponse = bind(this._handleGeolocationResponse, this),
3650 onError = bind(this._handleGeolocationError, this);
3652 if (options.watch) {
3653 this._locationWatchId =
3654 navigator.geolocation.watchPosition(onResponse, onError, options);
3656 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
3661 // @method stopLocate(): this
3662 // Stops watching location previously initiated by `map.locate({watch: true})`
3663 // and aborts resetting the map view if map.locate was called with
3664 // `{setView: true}`.
3665 stopLocate: function () {
3666 if (navigator.geolocation && navigator.geolocation.clearWatch) {
3667 navigator.geolocation.clearWatch(this._locationWatchId);
3669 if (this._locateOptions) {
3670 this._locateOptions.setView = false;
3675 _handleGeolocationError: function (error) {
3677 message = error.message ||
3678 (c === 1 ? 'permission denied' :
3679 (c === 2 ? 'position unavailable' : 'timeout'));
3681 if (this._locateOptions.setView && !this._loaded) {
3685 // @section Location events
3686 // @event locationerror: ErrorEvent
3687 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
3688 this.fire('locationerror', {
3690 message: 'Geolocation error: ' + message + '.'
3694 _handleGeolocationResponse: function (pos) {
3695 var lat = pos.coords.latitude,
3696 lng = pos.coords.longitude,
3697 latlng = new LatLng(lat, lng),
3698 bounds = latlng.toBounds(pos.coords.accuracy * 2),
3699 options = this._locateOptions;
3701 if (options.setView) {
3702 var zoom = this.getBoundsZoom(bounds);
3703 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
3709 timestamp: pos.timestamp
3712 for (var i in pos.coords) {
3713 if (typeof pos.coords[i] === 'number') {
3714 data[i] = pos.coords[i];
3718 // @event locationfound: LocationEvent
3719 // Fired when geolocation (using the [`locate`](#map-locate) method)
3720 // went successfully.
3721 this.fire('locationfound', data);
3724 // TODO Appropriate docs section?
3725 // @section Other Methods
3726 // @method addHandler(name: String, HandlerClass: Function): this
3727 // Adds a new `Handler` to the map, given its name and constructor function.
3728 addHandler: function (name, HandlerClass) {
3729 if (!HandlerClass) { return this; }
3731 var handler = this[name] = new HandlerClass(this);
3733 this._handlers.push(handler);
3735 if (this.options[name]) {
3742 // @method remove(): this
3743 // Destroys the map and clears all related event listeners.
3744 remove: function () {
3746 this._initEvents(true);
3748 if (this._containerId !== this._container._leaflet_id) {
3749 throw new Error('Map container is being reused by another instance');
3753 // throws error in IE6-8
3754 delete this._container._leaflet_id;
3755 delete this._containerId;
3758 this._container._leaflet_id = undefined;
3760 this._containerId = undefined;
3763 if (this._locationWatchId !== undefined) {
3769 remove(this._mapPane);
3771 if (this._clearControlPos) {
3772 this._clearControlPos();
3774 if (this._resizeRequest) {
3775 cancelAnimFrame(this._resizeRequest);
3776 this._resizeRequest = null;
3779 this._clearHandlers();
3782 // @section Map state change events
3783 // @event unload: Event
3784 // Fired when the map is destroyed with [remove](#map-remove) method.
3785 this.fire('unload');
3789 for (i in this._layers) {
3790 this._layers[i].remove();
3792 for (i in this._panes) {
3793 remove(this._panes[i]);
3798 delete this._mapPane;
3799 delete this._renderer;
3804 // @section Other Methods
3805 // @method createPane(name: String, container?: HTMLElement): HTMLElement
3806 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
3807 // then returns it. The pane is created as a child of `container`, or
3808 // as a child of the main map pane if not set.
3809 createPane: function (name, container) {
3810 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3811 pane = create$1('div', className, container || this._mapPane);
3814 this._panes[name] = pane;
3819 // @section Methods for Getting Map State
3821 // @method getCenter(): LatLng
3822 // Returns the geographical center of the map view
3823 getCenter: function () {
3824 this._checkIfLoaded();
3826 if (this._lastCenter && !this._moved()) {
3827 return this._lastCenter;
3829 return this.layerPointToLatLng(this._getCenterLayerPoint());
3832 // @method getZoom(): Number
3833 // Returns the current zoom level of the map view
3834 getZoom: function () {
3838 // @method getBounds(): LatLngBounds
3839 // Returns the geographical bounds visible in the current map view
3840 getBounds: function () {
3841 var bounds = this.getPixelBounds(),
3842 sw = this.unproject(bounds.getBottomLeft()),
3843 ne = this.unproject(bounds.getTopRight());
3845 return new LatLngBounds(sw, ne);
3848 // @method getMinZoom(): Number
3849 // 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.
3850 getMinZoom: function () {
3851 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3854 // @method getMaxZoom(): Number
3855 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3856 getMaxZoom: function () {
3857 return this.options.maxZoom === undefined ?
3858 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3859 this.options.maxZoom;
3862 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
3863 // Returns the maximum zoom level on which the given bounds fit to the map
3864 // view in its entirety. If `inside` (optional) is set to `true`, the method
3865 // instead returns the minimum zoom level on which the map view fits into
3866 // the given bounds in its entirety.
3867 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3868 bounds = toLatLngBounds(bounds);
3869 padding = toPoint(padding || [0, 0]);
3871 var zoom = this.getZoom() || 0,
3872 min = this.getMinZoom(),
3873 max = this.getMaxZoom(),
3874 nw = bounds.getNorthWest(),
3875 se = bounds.getSouthEast(),
3876 size = this.getSize().subtract(padding),
3877 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3878 snap = any3d ? this.options.zoomSnap : 1,
3879 scalex = size.x / boundsSize.x,
3880 scaley = size.y / boundsSize.y,
3881 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
3883 zoom = this.getScaleZoom(scale, zoom);
3886 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3887 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3890 return Math.max(min, Math.min(max, zoom));
3893 // @method getSize(): Point
3894 // Returns the current size of the map container (in pixels).
3895 getSize: function () {
3896 if (!this._size || this._sizeChanged) {
3897 this._size = new Point(
3898 this._container.clientWidth || 0,
3899 this._container.clientHeight || 0);
3901 this._sizeChanged = false;
3903 return this._size.clone();
3906 // @method getPixelBounds(): Bounds
3907 // Returns the bounds of the current map view in projected pixel
3908 // coordinates (sometimes useful in layer and overlay implementations).
3909 getPixelBounds: function (center, zoom) {
3910 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3911 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3914 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3915 // the map pane? "left point of the map layer" can be confusing, specially
3916 // since there can be negative offsets.
3917 // @method getPixelOrigin(): Point
3918 // Returns the projected pixel coordinates of the top left point of
3919 // the map layer (useful in custom layer and overlay implementations).
3920 getPixelOrigin: function () {
3921 this._checkIfLoaded();
3922 return this._pixelOrigin;
3925 // @method getPixelWorldBounds(zoom?: Number): Bounds
3926 // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3927 // If `zoom` is omitted, the map's current zoom level is used.
3928 getPixelWorldBounds: function (zoom) {
3929 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3932 // @section Other Methods
3934 // @method getPane(pane: String|HTMLElement): HTMLElement
3935 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3936 getPane: function (pane) {
3937 return typeof pane === 'string' ? this._panes[pane] : pane;
3940 // @method getPanes(): Object
3941 // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3942 // the panes as values.
3943 getPanes: function () {
3947 // @method getContainer: HTMLElement
3948 // Returns the HTML element that contains the map.
3949 getContainer: function () {
3950 return this._container;
3954 // @section Conversion Methods
3956 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3957 // Returns the scale factor to be applied to a map transition from zoom level
3958 // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3959 getZoomScale: function (toZoom, fromZoom) {
3960 // TODO replace with universal implementation after refactoring projections
3961 var crs = this.options.crs;
3962 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3963 return crs.scale(toZoom) / crs.scale(fromZoom);
3966 // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3967 // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3968 // level and everything is scaled by a factor of `scale`. Inverse of
3969 // [`getZoomScale`](#map-getZoomScale).
3970 getScaleZoom: function (scale, fromZoom) {
3971 var crs = this.options.crs;
3972 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3973 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3974 return isNaN(zoom) ? Infinity : zoom;
3977 // @method project(latlng: LatLng, zoom: Number): Point
3978 // Projects a geographical coordinate `LatLng` according to the projection
3979 // of the map's CRS, then scales it according to `zoom` and the CRS's
3980 // `Transformation`. The result is pixel coordinate relative to
3982 project: function (latlng, zoom) {
3983 zoom = zoom === undefined ? this._zoom : zoom;
3984 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
3987 // @method unproject(point: Point, zoom: Number): LatLng
3988 // Inverse of [`project`](#map-project).
3989 unproject: function (point, zoom) {
3990 zoom = zoom === undefined ? this._zoom : zoom;
3991 return this.options.crs.pointToLatLng(toPoint(point), zoom);
3994 // @method layerPointToLatLng(point: Point): LatLng
3995 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3996 // returns the corresponding geographical coordinate (for the current zoom level).
3997 layerPointToLatLng: function (point) {
3998 var projectedPoint = toPoint(point).add(this.getPixelOrigin());
3999 return this.unproject(projectedPoint);
4002 // @method latLngToLayerPoint(latlng: LatLng): Point
4003 // Given a geographical coordinate, returns the corresponding pixel coordinate
4004 // relative to the [origin pixel](#map-getpixelorigin).
4005 latLngToLayerPoint: function (latlng) {
4006 var projectedPoint = this.project(toLatLng(latlng))._round();
4007 return projectedPoint._subtract(this.getPixelOrigin());
4010 // @method wrapLatLng(latlng: LatLng): LatLng
4011 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
4012 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
4014 // By default this means longitude is wrapped around the dateline so its
4015 // value is between -180 and +180 degrees.
4016 wrapLatLng: function (latlng) {
4017 return this.options.crs.wrapLatLng(toLatLng(latlng));
4020 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
4021 // Returns a `LatLngBounds` with the same size as the given one, ensuring that
4022 // its center is within the CRS's bounds.
4023 // By default this means the center longitude is wrapped around the dateline so its
4024 // value is between -180 and +180 degrees, and the majority of the bounds
4025 // overlaps the CRS's bounds.
4026 wrapLatLngBounds: function (latlng) {
4027 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
4030 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
4031 // Returns the distance between two geographical coordinates according to
4032 // the map's CRS. By default this measures distance in meters.
4033 distance: function (latlng1, latlng2) {
4034 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
4037 // @method containerPointToLayerPoint(point: Point): Point
4038 // Given a pixel coordinate relative to the map container, returns the corresponding
4039 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
4040 containerPointToLayerPoint: function (point) { // (Point)
4041 return toPoint(point).subtract(this._getMapPanePos());
4044 // @method layerPointToContainerPoint(point: Point): Point
4045 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
4046 // returns the corresponding pixel coordinate relative to the map container.
4047 layerPointToContainerPoint: function (point) { // (Point)
4048 return toPoint(point).add(this._getMapPanePos());
4051 // @method containerPointToLatLng(point: Point): LatLng
4052 // Given a pixel coordinate relative to the map container, returns
4053 // the corresponding geographical coordinate (for the current zoom level).
4054 containerPointToLatLng: function (point) {
4055 var layerPoint = this.containerPointToLayerPoint(toPoint(point));
4056 return this.layerPointToLatLng(layerPoint);
4059 // @method latLngToContainerPoint(latlng: LatLng): Point
4060 // Given a geographical coordinate, returns the corresponding pixel coordinate
4061 // relative to the map container.
4062 latLngToContainerPoint: function (latlng) {
4063 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
4066 // @method mouseEventToContainerPoint(ev: MouseEvent): Point
4067 // Given a MouseEvent object, returns the pixel coordinate relative to the
4068 // map container where the event took place.
4069 mouseEventToContainerPoint: function (e) {
4070 return getMousePosition(e, this._container);
4073 // @method mouseEventToLayerPoint(ev: MouseEvent): Point
4074 // Given a MouseEvent object, returns the pixel coordinate relative to
4075 // the [origin pixel](#map-getpixelorigin) where the event took place.
4076 mouseEventToLayerPoint: function (e) {
4077 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
4080 // @method mouseEventToLatLng(ev: MouseEvent): LatLng
4081 // Given a MouseEvent object, returns geographical coordinate where the
4082 // event took place.
4083 mouseEventToLatLng: function (e) { // (MouseEvent)
4084 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
4088 // map initialization methods
4090 _initContainer: function (id) {
4091 var container = this._container = get(id);
4094 throw new Error('Map container not found.');
4095 } else if (container._leaflet_id) {
4096 throw new Error('Map container is already initialized.');
4099 on(container, 'scroll', this._onScroll, this);
4100 this._containerId = stamp(container);
4103 _initLayout: function () {
4104 var container = this._container;
4106 this._fadeAnimated = this.options.fadeAnimation && any3d;
4108 addClass(container, 'leaflet-container' +
4109 (touch ? ' leaflet-touch' : '') +
4110 (retina ? ' leaflet-retina' : '') +
4111 (ielt9 ? ' leaflet-oldie' : '') +
4112 (safari ? ' leaflet-safari' : '') +
4113 (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
4115 var position = getStyle(container, 'position');
4117 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
4118 container.style.position = 'relative';
4123 if (this._initControlPos) {
4124 this._initControlPos();
4128 _initPanes: function () {
4129 var panes = this._panes = {};
4130 this._paneRenderers = {};
4134 // Panes are DOM elements used to control the ordering of layers on the map. You
4135 // can access panes with [`map.getPane`](#map-getpane) or
4136 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
4137 // [`map.createPane`](#map-createpane) method.
4139 // Every map has the following default panes that differ only in zIndex.
4141 // @pane mapPane: HTMLElement = 'auto'
4142 // Pane that contains all other map panes
4144 this._mapPane = this.createPane('mapPane', this._container);
4145 setPosition(this._mapPane, new Point(0, 0));
4147 // @pane tilePane: HTMLElement = 200
4148 // Pane for `GridLayer`s and `TileLayer`s
4149 this.createPane('tilePane');
4150 // @pane overlayPane: HTMLElement = 400
4151 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
4152 this.createPane('shadowPane');
4153 // @pane shadowPane: HTMLElement = 500
4154 // Pane for overlay shadows (e.g. `Marker` shadows)
4155 this.createPane('overlayPane');
4156 // @pane markerPane: HTMLElement = 600
4157 // Pane for `Icon`s of `Marker`s
4158 this.createPane('markerPane');
4159 // @pane tooltipPane: HTMLElement = 650
4160 // Pane for `Tooltip`s.
4161 this.createPane('tooltipPane');
4162 // @pane popupPane: HTMLElement = 700
4163 // Pane for `Popup`s.
4164 this.createPane('popupPane');
4166 if (!this.options.markerZoomAnimation) {
4167 addClass(panes.markerPane, 'leaflet-zoom-hide');
4168 addClass(panes.shadowPane, 'leaflet-zoom-hide');
4173 // private methods that modify map state
4175 // @section Map state change events
4176 _resetView: function (center, zoom) {
4177 setPosition(this._mapPane, new Point(0, 0));
4179 var loading = !this._loaded;
4180 this._loaded = true;
4181 zoom = this._limitZoom(zoom);
4183 this.fire('viewprereset');
4185 var zoomChanged = this._zoom !== zoom;
4187 ._moveStart(zoomChanged, false)
4188 ._move(center, zoom)
4189 ._moveEnd(zoomChanged);
4191 // @event viewreset: Event
4192 // Fired when the map needs to redraw its content (this usually happens
4193 // on map zoom or load). Very useful for creating custom overlays.
4194 this.fire('viewreset');
4196 // @event load: Event
4197 // Fired when the map is initialized (when its center and zoom are set
4198 // for the first time).
4204 _moveStart: function (zoomChanged, noMoveStart) {
4205 // @event zoomstart: Event
4206 // Fired when the map zoom is about to change (e.g. before zoom animation).
4207 // @event movestart: Event
4208 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
4210 this.fire('zoomstart');
4213 this.fire('movestart');
4218 _move: function (center, zoom, data) {
4219 if (zoom === undefined) {
4222 var zoomChanged = this._zoom !== zoom;
4225 this._lastCenter = center;
4226 this._pixelOrigin = this._getNewPixelOrigin(center);
4228 // @event zoom: Event
4229 // Fired repeatedly during any change in zoom level, including zoom
4230 // and fly animations.
4231 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530
4232 this.fire('zoom', data);
4235 // @event move: Event
4236 // Fired repeatedly during any movement of the map, including pan and
4238 return this.fire('move', data);
4241 _moveEnd: function (zoomChanged) {
4242 // @event zoomend: Event
4243 // Fired when the map has changed, after any animations.
4245 this.fire('zoomend');
4248 // @event moveend: Event
4249 // Fired when the center of the map stops changing (e.g. user stopped
4250 // dragging the map).
4251 return this.fire('moveend');
4254 _stop: function () {
4255 cancelAnimFrame(this._flyToFrame);
4256 if (this._panAnim) {
4257 this._panAnim.stop();
4262 _rawPanBy: function (offset) {
4263 setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
4266 _getZoomSpan: function () {
4267 return this.getMaxZoom() - this.getMinZoom();
4270 _panInsideMaxBounds: function () {
4271 if (!this._enforcingBounds) {
4272 this.panInsideBounds(this.options.maxBounds);
4276 _checkIfLoaded: function () {
4277 if (!this._loaded) {
4278 throw new Error('Set map center and zoom first.');
4282 // DOM event handling
4284 // @section Interaction events
4285 _initEvents: function (remove$$1) {
4287 this._targets[stamp(this._container)] = this;
4289 var onOff = remove$$1 ? off : on;
4291 // @event click: MouseEvent
4292 // Fired when the user clicks (or taps) the map.
4293 // @event dblclick: MouseEvent
4294 // Fired when the user double-clicks (or double-taps) the map.
4295 // @event mousedown: MouseEvent
4296 // Fired when the user pushes the mouse button on the map.
4297 // @event mouseup: MouseEvent
4298 // Fired when the user releases the mouse button on the map.
4299 // @event mouseover: MouseEvent
4300 // Fired when the mouse enters the map.
4301 // @event mouseout: MouseEvent
4302 // Fired when the mouse leaves the map.
4303 // @event mousemove: MouseEvent
4304 // Fired while the mouse moves over the map.
4305 // @event contextmenu: MouseEvent
4306 // Fired when the user pushes the right mouse button on the map, prevents
4307 // default browser context menu from showing if there are listeners on
4308 // this event. Also fired on mobile when the user holds a single touch
4309 // for a second (also called long press).
4310 // @event keypress: KeyboardEvent
4311 // Fired when the user presses a key from the keyboard while the map is focused.
4312 onOff(this._container, 'click dblclick mousedown mouseup ' +
4313 'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);
4315 if (this.options.trackResize) {
4316 onOff(window, 'resize', this._onResize, this);
4319 if (any3d && this.options.transform3DLimit) {
4320 (remove$$1 ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
4324 _onResize: function () {
4325 cancelAnimFrame(this._resizeRequest);
4326 this._resizeRequest = requestAnimFrame(
4327 function () { this.invalidateSize({debounceMoveend: true}); }, this);
4330 _onScroll: function () {
4331 this._container.scrollTop = 0;
4332 this._container.scrollLeft = 0;
4335 _onMoveEnd: function () {
4336 var pos = this._getMapPanePos();
4337 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
4338 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
4339 // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
4340 this._resetView(this.getCenter(), this.getZoom());
4344 _findEventTargets: function (e, type) {
4347 isHover = type === 'mouseout' || type === 'mouseover',
4348 src = e.target || e.srcElement,
4352 target = this._targets[stamp(src)];
4353 if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
4354 // Prevent firing click after you just dragged an object.
4358 if (target && target.listens(type, true)) {
4359 if (isHover && !isExternalTarget(src, e)) { break; }
4360 targets.push(target);
4361 if (isHover) { break; }
4363 if (src === this._container) { break; }
4364 src = src.parentNode;
4366 if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) {
4372 _handleDOMEvent: function (e) {
4373 if (!this._loaded || skipped(e)) { return; }
4377 if (type === 'mousedown' || type === 'keypress') {
4378 // prevents outline when clicking on keyboard-focusable element
4379 preventOutline(e.target || e.srcElement);
4382 this._fireDOMEvent(e, type);
4385 _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
4387 _fireDOMEvent: function (e, type, targets) {
4389 if (e.type === 'click') {
4390 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
4391 // @event preclick: MouseEvent
4392 // Fired before mouse click on the map (sometimes useful when you
4393 // want something to happen on click before any existing click
4394 // handlers start running).
4395 var synth = extend({}, e);
4396 synth.type = 'preclick';
4397 this._fireDOMEvent(synth, synth.type, targets);
4400 if (e._stopped) { return; }
4402 // Find the layer the event is propagating from and its parents.
4403 targets = (targets || []).concat(this._findEventTargets(e, type));
4405 if (!targets.length) { return; }
4407 var target = targets[0];
4408 if (type === 'contextmenu' && target.listens(type, true)) {
4416 if (e.type !== 'keypress') {
4417 var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
4418 data.containerPoint = isMarker ?
4419 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
4420 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
4421 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
4424 for (var i = 0; i < targets.length; i++) {
4425 targets[i].fire(type, data, true);
4426 if (data.originalEvent._stopped ||
4427 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
4431 _draggableMoved: function (obj) {
4432 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
4433 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
4436 _clearHandlers: function () {
4437 for (var i = 0, len = this._handlers.length; i < len; i++) {
4438 this._handlers[i].disable();
4442 // @section Other Methods
4444 // @method whenReady(fn: Function, context?: Object): this
4445 // Runs the given function `fn` when the map gets initialized with
4446 // a view (center and zoom) and at least one layer, or immediately
4447 // if it's already initialized, optionally passing a function context.
4448 whenReady: function (callback, context) {
4450 callback.call(context || this, {target: this});
4452 this.on('load', callback, context);
4458 // private methods for getting map state
4460 _getMapPanePos: function () {
4461 return getPosition(this._mapPane) || new Point(0, 0);
4464 _moved: function () {
4465 var pos = this._getMapPanePos();
4466 return pos && !pos.equals([0, 0]);
4469 _getTopLeftPoint: function (center, zoom) {
4470 var pixelOrigin = center && zoom !== undefined ?
4471 this._getNewPixelOrigin(center, zoom) :
4472 this.getPixelOrigin();
4473 return pixelOrigin.subtract(this._getMapPanePos());
4476 _getNewPixelOrigin: function (center, zoom) {
4477 var viewHalf = this.getSize()._divideBy(2);
4478 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
4481 _latLngToNewLayerPoint: function (latlng, zoom, center) {
4482 var topLeft = this._getNewPixelOrigin(center, zoom);
4483 return this.project(latlng, zoom)._subtract(topLeft);
4486 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
4487 var topLeft = this._getNewPixelOrigin(center, zoom);
4489 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
4490 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
4491 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
4492 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
4496 // layer point of the current center
4497 _getCenterLayerPoint: function () {
4498 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
4501 // offset of the specified place to the current center in pixels
4502 _getCenterOffset: function (latlng) {
4503 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
4506 // adjust center for view to get inside bounds
4507 _limitCenter: function (center, zoom, bounds) {
4509 if (!bounds) { return center; }
4511 var centerPoint = this.project(center, zoom),
4512 viewHalf = this.getSize().divideBy(2),
4513 viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
4514 offset = this._getBoundsOffset(viewBounds, bounds, zoom);
4516 // If offset is less than a pixel, ignore.
4517 // This prevents unstable projections from getting into
4518 // an infinite loop of tiny offsets.
4519 if (offset.round().equals([0, 0])) {
4523 return this.unproject(centerPoint.add(offset), zoom);
4526 // adjust offset for view to get inside bounds
4527 _limitOffset: function (offset, bounds) {
4528 if (!bounds) { return offset; }
4530 var viewBounds = this.getPixelBounds(),
4531 newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
4533 return offset.add(this._getBoundsOffset(newBounds, bounds));
4536 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
4537 _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
4538 var projectedMaxBounds = toBounds(
4539 this.project(maxBounds.getNorthEast(), zoom),
4540 this.project(maxBounds.getSouthWest(), zoom)
4542 minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
4543 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
4545 dx = this._rebound(minOffset.x, -maxOffset.x),
4546 dy = this._rebound(minOffset.y, -maxOffset.y);
4548 return new Point(dx, dy);
4551 _rebound: function (left, right) {
4552 return left + right > 0 ?
4553 Math.round(left - right) / 2 :
4554 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
4557 _limitZoom: function (zoom) {
4558 var min = this.getMinZoom(),
4559 max = this.getMaxZoom(),
4560 snap = any3d ? this.options.zoomSnap : 1;
4562 zoom = Math.round(zoom / snap) * snap;
4564 return Math.max(min, Math.min(max, zoom));
4567 _onPanTransitionStep: function () {
4571 _onPanTransitionEnd: function () {
4572 removeClass(this._mapPane, 'leaflet-pan-anim');
4573 this.fire('moveend');
4576 _tryAnimatedPan: function (center, options) {
4577 // difference between the new and current centers in pixels
4578 var offset = this._getCenterOffset(center)._trunc();
4580 // don't animate too far unless animate: true specified in options
4581 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
4583 this.panBy(offset, options);
4588 _createAnimProxy: function () {
4590 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
4591 this._panes.mapPane.appendChild(proxy);
4593 this.on('zoomanim', function (e) {
4594 var prop = TRANSFORM,
4595 transform = this._proxy.style[prop];
4597 setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
4599 // workaround for case when transform is the same and so transitionend event is not fired
4600 if (transform === this._proxy.style[prop] && this._animatingZoom) {
4601 this._onZoomTransitionEnd();
4605 this.on('load moveend', function () {
4606 var c = this.getCenter(),
4608 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
4611 this._on('unload', this._destroyAnimProxy, this);
4614 _destroyAnimProxy: function () {
4615 remove(this._proxy);
4619 _catchTransitionEnd: function (e) {
4620 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
4621 this._onZoomTransitionEnd();
4625 _nothingToAnimate: function () {
4626 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
4629 _tryAnimatedZoom: function (center, zoom, options) {
4631 if (this._animatingZoom) { return true; }
4633 options = options || {};
4635 // don't animate if disabled, not supported or zoom difference is too large
4636 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
4637 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
4639 // offset is the pixel coords of the zoom origin relative to the current center
4640 var scale = this.getZoomScale(zoom),
4641 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
4643 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
4644 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
4646 requestAnimFrame(function () {
4648 ._moveStart(true, false)
4649 ._animateZoom(center, zoom, true);
4655 _animateZoom: function (center, zoom, startAnim, noUpdate) {
4656 if (!this._mapPane) { return; }
4659 this._animatingZoom = true;
4661 // remember what center/zoom to set after animation
4662 this._animateToCenter = center;
4663 this._animateToZoom = zoom;
4665 addClass(this._mapPane, 'leaflet-zoom-anim');
4668 // @event zoomanim: ZoomAnimEvent
4669 // Fired at least once per zoom animation. For continous zoom, like pinch zooming, fired once per frame during zoom.
4670 this.fire('zoomanim', {
4676 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
4677 setTimeout(bind(this._onZoomTransitionEnd, this), 250);
4680 _onZoomTransitionEnd: function () {
4681 if (!this._animatingZoom) { return; }
4683 if (this._mapPane) {
4684 removeClass(this._mapPane, 'leaflet-zoom-anim');
4687 this._animatingZoom = false;
4689 this._move(this._animateToCenter, this._animateToZoom);
4691 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
4692 requestAnimFrame(function () {
4693 this._moveEnd(true);
4700 // @factory L.map(id: String, options?: Map options)
4701 // Instantiates a map object given the DOM ID of a `<div>` element
4702 // and optionally an object literal with `Map options`.
4705 // @factory L.map(el: HTMLElement, options?: Map options)
4706 // Instantiates a map object given an instance of a `<div>` HTML element
4707 // and optionally an object literal with `Map options`.
4708 function createMap(id, options) {
4709 return new Map(id, options);
4717 * L.Control is a base class for implementing map controls. Handles positioning.
4718 * All other controls extend from this class.
4721 var Control = Class.extend({
4723 // @aka Control options
4725 // @option position: String = 'topright'
4726 // The position of the control (one of the map corners). Possible values are `'topleft'`,
4727 // `'topright'`, `'bottomleft'` or `'bottomright'`
4728 position: 'topright'
4731 initialize: function (options) {
4732 setOptions(this, options);
4736 * Classes extending L.Control will inherit the following methods:
4738 * @method getPosition: string
4739 * Returns the position of the control.
4741 getPosition: function () {
4742 return this.options.position;
4745 // @method setPosition(position: string): this
4746 // Sets the position of the control.
4747 setPosition: function (position) {
4748 var map = this._map;
4751 map.removeControl(this);
4754 this.options.position = position;
4757 map.addControl(this);
4763 // @method getContainer: HTMLElement
4764 // Returns the HTMLElement that contains the control.
4765 getContainer: function () {
4766 return this._container;
4769 // @method addTo(map: Map): this
4770 // Adds the control to the given map.
4771 addTo: function (map) {
4775 var container = this._container = this.onAdd(map),
4776 pos = this.getPosition(),
4777 corner = map._controlCorners[pos];
4779 addClass(container, 'leaflet-control');
4781 if (pos.indexOf('bottom') !== -1) {
4782 corner.insertBefore(container, corner.firstChild);
4784 corner.appendChild(container);
4790 // @method remove: this
4791 // Removes the control from the map it is currently active on.
4792 remove: function () {
4797 remove(this._container);
4799 if (this.onRemove) {
4800 this.onRemove(this._map);
4808 _refocusOnMap: function (e) {
4809 // if map exists and event is not a keyboard event
4810 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
4811 this._map.getContainer().focus();
4816 var control = function (options) {
4817 return new Control(options);
4820 /* @section Extension methods
4823 * Every control should extend from `L.Control` and (re-)implement the following methods.
4825 * @method onAdd(map: Map): HTMLElement
4826 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
4828 * @method onRemove(map: Map)
4829 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
4833 * @section Methods for Layers and Controls
4836 // @method addControl(control: Control): this
4837 // Adds the given control to the map
4838 addControl: function (control) {
4839 control.addTo(this);
4843 // @method removeControl(control: Control): this
4844 // Removes the given control from the map
4845 removeControl: function (control) {
4850 _initControlPos: function () {
4851 var corners = this._controlCorners = {},
4853 container = this._controlContainer =
4854 create$1('div', l + 'control-container', this._container);
4856 function createCorner(vSide, hSide) {
4857 var className = l + vSide + ' ' + l + hSide;
4859 corners[vSide + hSide] = create$1('div', className, container);
4862 createCorner('top', 'left');
4863 createCorner('top', 'right');
4864 createCorner('bottom', 'left');
4865 createCorner('bottom', 'right');
4868 _clearControlPos: function () {
4869 for (var i in this._controlCorners) {
4870 remove(this._controlCorners[i]);
4872 remove(this._controlContainer);
4873 delete this._controlCorners;
4874 delete this._controlContainer;
4879 * @class Control.Layers
4880 * @aka L.Control.Layers
4883 * 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`.
4888 * var baseLayers = {
4890 * "OpenStreetMap": osm
4895 * "Roads": roadsLayer
4898 * L.control.layers(baseLayers, overlays).addTo(map);
4901 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
4905 * "<someName1>": layer1,
4906 * "<someName2>": layer2
4910 * The layer names can contain HTML, which allows you to add additional styling to the items:
4913 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
4917 var Layers = Control.extend({
4919 // @aka Control.Layers options
4921 // @option collapsed: Boolean = true
4922 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
4924 position: 'topright',
4926 // @option autoZIndex: Boolean = true
4927 // 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.
4930 // @option hideSingleBase: Boolean = false
4931 // If `true`, the base layers in the control will be hidden when there is only one.
4932 hideSingleBase: false,
4934 // @option sortLayers: Boolean = false
4935 // Whether to sort the layers. When `false`, layers will keep the order
4936 // in which they were added to the control.
4939 // @option sortFunction: Function = *
4940 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
4941 // that will be used for sorting the layers, when `sortLayers` is `true`.
4942 // The function receives both the `L.Layer` instances and their names, as in
4943 // `sortFunction(layerA, layerB, nameA, nameB)`.
4944 // By default, it sorts layers alphabetically by their name.
4945 sortFunction: function (layerA, layerB, nameA, nameB) {
4946 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
4950 initialize: function (baseLayers, overlays, options) {
4951 setOptions(this, options);
4953 this._layerControlInputs = [];
4955 this._lastZIndex = 0;
4956 this._handlingClick = false;
4958 for (var i in baseLayers) {
4959 this._addLayer(baseLayers[i], i);
4962 for (i in overlays) {
4963 this._addLayer(overlays[i], i, true);
4967 onAdd: function (map) {
4972 map.on('zoomend', this._checkDisabledLayers, this);
4974 for (var i = 0; i < this._layers.length; i++) {
4975 this._layers[i].layer.on('add remove', this._onLayerChange, this);
4978 return this._container;
4981 addTo: function (map) {
4982 Control.prototype.addTo.call(this, map);
4983 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
4984 return this._expandIfNotCollapsed();
4987 onRemove: function () {
4988 this._map.off('zoomend', this._checkDisabledLayers, this);
4990 for (var i = 0; i < this._layers.length; i++) {
4991 this._layers[i].layer.off('add remove', this._onLayerChange, this);
4995 // @method addBaseLayer(layer: Layer, name: String): this
4996 // Adds a base layer (radio button entry) with the given name to the control.
4997 addBaseLayer: function (layer, name) {
4998 this._addLayer(layer, name);
4999 return (this._map) ? this._update() : this;
5002 // @method addOverlay(layer: Layer, name: String): this
5003 // Adds an overlay (checkbox entry) with the given name to the control.
5004 addOverlay: function (layer, name) {
5005 this._addLayer(layer, name, true);
5006 return (this._map) ? this._update() : this;
5009 // @method removeLayer(layer: Layer): this
5010 // Remove the given layer from the control.
5011 removeLayer: function (layer) {
5012 layer.off('add remove', this._onLayerChange, this);
5014 var obj = this._getLayer(stamp(layer));
5016 this._layers.splice(this._layers.indexOf(obj), 1);
5018 return (this._map) ? this._update() : this;
5021 // @method expand(): this
5022 // Expand the control container if collapsed.
5023 expand: function () {
5024 addClass(this._container, 'leaflet-control-layers-expanded');
5025 this._section.style.height = null;
5026 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
5027 if (acceptableHeight < this._section.clientHeight) {
5028 addClass(this._section, 'leaflet-control-layers-scrollbar');
5029 this._section.style.height = acceptableHeight + 'px';
5031 removeClass(this._section, 'leaflet-control-layers-scrollbar');
5033 this._checkDisabledLayers();
5037 // @method collapse(): this
5038 // Collapse the control container if expanded.
5039 collapse: function () {
5040 removeClass(this._container, 'leaflet-control-layers-expanded');
5044 _initLayout: function () {
5045 var className = 'leaflet-control-layers',
5046 container = this._container = create$1('div', className),
5047 collapsed = this.options.collapsed;
5049 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
5050 container.setAttribute('aria-haspopup', true);
5052 disableClickPropagation(container);
5053 disableScrollPropagation(container);
5055 var section = this._section = create$1('section', className + '-list');
5058 this._map.on('click', this.collapse, this);
5062 mouseenter: this.expand,
5063 mouseleave: this.collapse
5068 var link = this._layersLink = create$1('a', className + '-toggle', container);
5070 link.title = 'Layers';
5073 on(link, 'click', stop);
5074 on(link, 'click', this.expand, this);
5076 on(link, 'focus', this.expand, this);
5083 this._baseLayersList = create$1('div', className + '-base', section);
5084 this._separator = create$1('div', className + '-separator', section);
5085 this._overlaysList = create$1('div', className + '-overlays', section);
5087 container.appendChild(section);
5090 _getLayer: function (id) {
5091 for (var i = 0; i < this._layers.length; i++) {
5093 if (this._layers[i] && stamp(this._layers[i].layer) === id) {
5094 return this._layers[i];
5099 _addLayer: function (layer, name, overlay) {
5101 layer.on('add remove', this._onLayerChange, this);
5110 if (this.options.sortLayers) {
5111 this._layers.sort(bind(function (a, b) {
5112 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
5116 if (this.options.autoZIndex && layer.setZIndex) {
5118 layer.setZIndex(this._lastZIndex);
5121 this._expandIfNotCollapsed();
5124 _update: function () {
5125 if (!this._container) { return this; }
5127 empty(this._baseLayersList);
5128 empty(this._overlaysList);
5130 this._layerControlInputs = [];
5131 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
5133 for (i = 0; i < this._layers.length; i++) {
5134 obj = this._layers[i];
5136 overlaysPresent = overlaysPresent || obj.overlay;
5137 baseLayersPresent = baseLayersPresent || !obj.overlay;
5138 baseLayersCount += !obj.overlay ? 1 : 0;
5141 // Hide base layers section if there's only one layer.
5142 if (this.options.hideSingleBase) {
5143 baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
5144 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
5147 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
5152 _onLayerChange: function (e) {
5153 if (!this._handlingClick) {
5157 var obj = this._getLayer(stamp(e.target));
5160 // @section Layer events
5161 // @event baselayerchange: LayersControlEvent
5162 // Fired when the base layer is changed through the [layer control](#control-layers).
5163 // @event overlayadd: LayersControlEvent
5164 // Fired when an overlay is selected through the [layer control](#control-layers).
5165 // @event overlayremove: LayersControlEvent
5166 // Fired when an overlay is deselected through the [layer control](#control-layers).
5167 // @namespace Control.Layers
5168 var type = obj.overlay ?
5169 (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
5170 (e.type === 'add' ? 'baselayerchange' : null);
5173 this._map.fire(type, obj);
5177 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
5178 _createRadioElement: function (name, checked) {
5180 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
5181 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
5183 var radioFragment = document.createElement('div');
5184 radioFragment.innerHTML = radioHtml;
5186 return radioFragment.firstChild;
5189 _addItem: function (obj) {
5190 var label = document.createElement('label'),
5191 checked = this._map.hasLayer(obj.layer),
5195 input = document.createElement('input');
5196 input.type = 'checkbox';
5197 input.className = 'leaflet-control-layers-selector';
5198 input.defaultChecked = checked;
5200 input = this._createRadioElement('leaflet-base-layers', checked);
5203 this._layerControlInputs.push(input);
5204 input.layerId = stamp(obj.layer);
5206 on(input, 'click', this._onInputClick, this);
5208 var name = document.createElement('span');
5209 name.innerHTML = ' ' + obj.name;
5211 // Helps from preventing layer control flicker when checkboxes are disabled
5212 // https://github.com/Leaflet/Leaflet/issues/2771
5213 var holder = document.createElement('div');
5215 label.appendChild(holder);
5216 holder.appendChild(input);
5217 holder.appendChild(name);
5219 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
5220 container.appendChild(label);
5222 this._checkDisabledLayers();
5226 _onInputClick: function () {
5227 var inputs = this._layerControlInputs,
5229 var addedLayers = [],
5232 this._handlingClick = true;
5234 for (var i = inputs.length - 1; i >= 0; i--) {
5236 layer = this._getLayer(input.layerId).layer;
5238 if (input.checked) {
5239 addedLayers.push(layer);
5240 } else if (!input.checked) {
5241 removedLayers.push(layer);
5245 // Bugfix issue 2318: Should remove all old layers before readding new ones
5246 for (i = 0; i < removedLayers.length; i++) {
5247 if (this._map.hasLayer(removedLayers[i])) {
5248 this._map.removeLayer(removedLayers[i]);
5251 for (i = 0; i < addedLayers.length; i++) {
5252 if (!this._map.hasLayer(addedLayers[i])) {
5253 this._map.addLayer(addedLayers[i]);
5257 this._handlingClick = false;
5259 this._refocusOnMap();
5262 _checkDisabledLayers: function () {
5263 var inputs = this._layerControlInputs,
5266 zoom = this._map.getZoom();
5268 for (var i = inputs.length - 1; i >= 0; i--) {
5270 layer = this._getLayer(input.layerId).layer;
5271 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
5272 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
5277 _expandIfNotCollapsed: function () {
5278 if (this._map && !this.options.collapsed) {
5284 _expand: function () {
5285 // Backward compatibility, remove me in 1.1.
5286 return this.expand();
5289 _collapse: function () {
5290 // Backward compatibility, remove me in 1.1.
5291 return this.collapse();
5297 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
5298 // 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.
5299 var layers = function (baseLayers, overlays, options) {
5300 return new Layers(baseLayers, overlays, options);
5304 * @class Control.Zoom
5305 * @aka L.Control.Zoom
5308 * 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`.
5311 var Zoom = Control.extend({
5313 // @aka Control.Zoom options
5315 position: 'topleft',
5317 // @option zoomInText: String = '+'
5318 // The text set on the 'zoom in' button.
5321 // @option zoomInTitle: String = 'Zoom in'
5322 // The title set on the 'zoom in' button.
5323 zoomInTitle: 'Zoom in',
5325 // @option zoomOutText: String = '−'
5326 // The text set on the 'zoom out' button.
5327 zoomOutText: '−',
5329 // @option zoomOutTitle: String = 'Zoom out'
5330 // The title set on the 'zoom out' button.
5331 zoomOutTitle: 'Zoom out'
5334 onAdd: function (map) {
5335 var zoomName = 'leaflet-control-zoom',
5336 container = create$1('div', zoomName + ' leaflet-bar'),
5337 options = this.options;
5339 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle,
5340 zoomName + '-in', container, this._zoomIn);
5341 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
5342 zoomName + '-out', container, this._zoomOut);
5344 this._updateDisabled();
5345 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
5350 onRemove: function (map) {
5351 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
5354 disable: function () {
5355 this._disabled = true;
5356 this._updateDisabled();
5360 enable: function () {
5361 this._disabled = false;
5362 this._updateDisabled();
5366 _zoomIn: function (e) {
5367 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
5368 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5372 _zoomOut: function (e) {
5373 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
5374 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5378 _createButton: function (html, title, className, container, fn) {
5379 var link = create$1('a', className, container);
5380 link.innerHTML = html;
5385 * Will force screen readers like VoiceOver to read this as "Zoom in - button"
5387 link.setAttribute('role', 'button');
5388 link.setAttribute('aria-label', title);
5390 disableClickPropagation(link);
5391 on(link, 'click', stop);
5392 on(link, 'click', fn, this);
5393 on(link, 'click', this._refocusOnMap, this);
5398 _updateDisabled: function () {
5399 var map = this._map,
5400 className = 'leaflet-disabled';
5402 removeClass(this._zoomInButton, className);
5403 removeClass(this._zoomOutButton, className);
5405 if (this._disabled || map._zoom === map.getMinZoom()) {
5406 addClass(this._zoomOutButton, className);
5408 if (this._disabled || map._zoom === map.getMaxZoom()) {
5409 addClass(this._zoomInButton, className);
5415 // @section Control options
5416 // @option zoomControl: Boolean = true
5417 // Whether a [zoom control](#control-zoom) is added to the map by default.
5422 Map.addInitHook(function () {
5423 if (this.options.zoomControl) {
5424 // @section Controls
5425 // @property zoomControl: Control.Zoom
5426 // The default zoom control (only available if the
5427 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
5428 this.zoomControl = new Zoom();
5429 this.addControl(this.zoomControl);
5433 // @namespace Control.Zoom
5434 // @factory L.control.zoom(options: Control.Zoom options)
5435 // Creates a zoom control
5436 var zoom = function (options) {
5437 return new Zoom(options);
5441 * @class Control.Scale
5442 * @aka L.Control.Scale
5445 * 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`.
5450 * L.control.scale().addTo(map);
5454 var Scale = Control.extend({
5456 // @aka Control.Scale options
5458 position: 'bottomleft',
5460 // @option maxWidth: Number = 100
5461 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
5464 // @option metric: Boolean = True
5465 // Whether to show the metric scale line (m/km).
5468 // @option imperial: Boolean = True
5469 // Whether to show the imperial scale line (mi/ft).
5472 // @option updateWhenIdle: Boolean = false
5473 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
5476 onAdd: function (map) {
5477 var className = 'leaflet-control-scale',
5478 container = create$1('div', className),
5479 options = this.options;
5481 this._addScales(options, className + '-line', container);
5483 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5484 map.whenReady(this._update, this);
5489 onRemove: function (map) {
5490 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5493 _addScales: function (options, className, container) {
5494 if (options.metric) {
5495 this._mScale = create$1('div', className, container);
5497 if (options.imperial) {
5498 this._iScale = create$1('div', className, container);
5502 _update: function () {
5503 var map = this._map,
5504 y = map.getSize().y / 2;
5506 var maxMeters = map.distance(
5507 map.containerPointToLatLng([0, y]),
5508 map.containerPointToLatLng([this.options.maxWidth, y]));
5510 this._updateScales(maxMeters);
5513 _updateScales: function (maxMeters) {
5514 if (this.options.metric && maxMeters) {
5515 this._updateMetric(maxMeters);
5517 if (this.options.imperial && maxMeters) {
5518 this._updateImperial(maxMeters);
5522 _updateMetric: function (maxMeters) {
5523 var meters = this._getRoundNum(maxMeters),
5524 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
5526 this._updateScale(this._mScale, label, meters / maxMeters);
5529 _updateImperial: function (maxMeters) {
5530 var maxFeet = maxMeters * 3.2808399,
5531 maxMiles, miles, feet;
5533 if (maxFeet > 5280) {
5534 maxMiles = maxFeet / 5280;
5535 miles = this._getRoundNum(maxMiles);
5536 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
5539 feet = this._getRoundNum(maxFeet);
5540 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
5544 _updateScale: function (scale, text, ratio) {
5545 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
5546 scale.innerHTML = text;
5549 _getRoundNum: function (num) {
5550 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5563 // @factory L.control.scale(options?: Control.Scale options)
5564 // Creates an scale control with the given options.
5565 var scale = function (options) {
5566 return new Scale(options);
5570 * @class Control.Attribution
5571 * @aka L.Control.Attribution
5574 * 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.
5577 var Attribution = Control.extend({
5579 // @aka Control.Attribution options
5581 position: 'bottomright',
5583 // @option prefix: String = 'Leaflet'
5584 // The HTML text shown before the attributions. Pass `false` to disable.
5585 prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
5588 initialize: function (options) {
5589 setOptions(this, options);
5591 this._attributions = {};
5594 onAdd: function (map) {
5595 map.attributionControl = this;
5596 this._container = create$1('div', 'leaflet-control-attribution');
5597 disableClickPropagation(this._container);
5599 // TODO ugly, refactor
5600 for (var i in map._layers) {
5601 if (map._layers[i].getAttribution) {
5602 this.addAttribution(map._layers[i].getAttribution());
5608 return this._container;
5611 // @method setPrefix(prefix: String): this
5612 // Sets the text before the attributions.
5613 setPrefix: function (prefix) {
5614 this.options.prefix = prefix;
5619 // @method addAttribution(text: String): this
5620 // Adds an attribution text (e.g. `'Vector data © Mapbox'`).
5621 addAttribution: function (text) {
5622 if (!text) { return this; }
5624 if (!this._attributions[text]) {
5625 this._attributions[text] = 0;
5627 this._attributions[text]++;
5634 // @method removeAttribution(text: String): this
5635 // Removes an attribution text.
5636 removeAttribution: function (text) {
5637 if (!text) { return this; }
5639 if (this._attributions[text]) {
5640 this._attributions[text]--;
5647 _update: function () {
5648 if (!this._map) { return; }
5652 for (var i in this._attributions) {
5653 if (this._attributions[i]) {
5658 var prefixAndAttribs = [];
5660 if (this.options.prefix) {
5661 prefixAndAttribs.push(this.options.prefix);
5663 if (attribs.length) {
5664 prefixAndAttribs.push(attribs.join(', '));
5667 this._container.innerHTML = prefixAndAttribs.join(' | ');
5672 // @section Control options
5673 // @option attributionControl: Boolean = true
5674 // Whether a [attribution control](#control-attribution) is added to the map by default.
5676 attributionControl: true
5679 Map.addInitHook(function () {
5680 if (this.options.attributionControl) {
5681 new Attribution().addTo(this);
5685 // @namespace Control.Attribution
5686 // @factory L.control.attribution(options: Control.Attribution options)
5687 // Creates an attribution control.
5688 var attribution = function (options) {
5689 return new Attribution(options);
5692 Control.Layers = Layers;
5693 Control.Zoom = Zoom;
5694 Control.Scale = Scale;
5695 Control.Attribution = Attribution;
5697 control.layers = layers;
5698 control.zoom = zoom;
5699 control.scale = scale;
5700 control.attribution = attribution;
5703 L.Handler is a base class for handler classes that are used internally to inject
5704 interaction features like dragging to classes like Map and Marker.
5709 // Abstract class for map interaction handlers
5711 var Handler = Class.extend({
5712 initialize: function (map) {
5716 // @method enable(): this
5717 // Enables the handler
5718 enable: function () {
5719 if (this._enabled) { return this; }
5721 this._enabled = true;
5726 // @method disable(): this
5727 // Disables the handler
5728 disable: function () {
5729 if (!this._enabled) { return this; }
5731 this._enabled = false;
5736 // @method enabled(): Boolean
5737 // Returns `true` if the handler is enabled
5738 enabled: function () {
5739 return !!this._enabled;
5742 // @section Extension methods
5743 // Classes inheriting from `Handler` must implement the two following methods:
5744 // @method addHooks()
5745 // Called when the handler is enabled, should add event hooks.
5746 // @method removeHooks()
5747 // Called when the handler is disabled, should remove the event hooks added previously.
5750 // @section There is static function which can be called without instantiating L.Handler:
5751 // @function addTo(map: Map, name: String): this
5752 // Adds a new Handler to the given map with the given name.
5753 Handler.addTo = function (map, name) {
5754 map.addHandler(name, this);
5758 var Mixin = {Events: Events};
5765 * A class for making DOM elements draggable (including touch support).
5766 * Used internally for map and marker dragging. Only works for elements
5767 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
5771 * var draggable = new L.Draggable(elementToDrag);
5772 * draggable.enable();
5776 var START = touch ? 'touchstart mousedown' : 'mousedown';
5778 mousedown: 'mouseup',
5779 touchstart: 'touchend',
5780 pointerdown: 'touchend',
5781 MSPointerDown: 'touchend'
5784 mousedown: 'mousemove',
5785 touchstart: 'touchmove',
5786 pointerdown: 'touchmove',
5787 MSPointerDown: 'touchmove'
5791 var Draggable = Evented.extend({
5795 // @aka Draggable options
5796 // @option clickTolerance: Number = 3
5797 // The max number of pixels a user can shift the mouse pointer during a click
5798 // for it to be considered a valid click (as opposed to a mouse drag).
5802 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
5803 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
5804 initialize: function (element, dragStartTarget, preventOutline$$1, options) {
5805 setOptions(this, options);
5807 this._element = element;
5808 this._dragStartTarget = dragStartTarget || element;
5809 this._preventOutline = preventOutline$$1;
5813 // Enables the dragging ability
5814 enable: function () {
5815 if (this._enabled) { return; }
5817 on(this._dragStartTarget, START, this._onDown, this);
5819 this._enabled = true;
5822 // @method disable()
5823 // Disables the dragging ability
5824 disable: function () {
5825 if (!this._enabled) { return; }
5827 // If we're currently dragging this draggable,
5828 // disabling it counts as first ending the drag.
5829 if (Draggable._dragging === this) {
5833 off(this._dragStartTarget, START, this._onDown, this);
5835 this._enabled = false;
5836 this._moved = false;
5839 _onDown: function (e) {
5840 // Ignore simulated events, since we handle both touch and
5841 // mouse explicitly; otherwise we risk getting duplicates of
5842 // touch events, see #4315.
5843 // Also ignore the event if disabled; this happens in IE11
5844 // under some circumstances, see #3666.
5845 if (e._simulated || !this._enabled) { return; }
5847 this._moved = false;
5849 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
5851 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
5852 Draggable._dragging = this; // Prevent dragging multiple objects at once.
5854 if (this._preventOutline) {
5855 preventOutline(this._element);
5859 disableTextSelection();
5861 if (this._moving) { return; }
5863 // @event down: Event
5864 // Fired when a drag is about to start.
5867 var first = e.touches ? e.touches[0] : e,
5868 sizedParent = getSizedParentNode(this._element);
5870 this._startPoint = new Point(first.clientX, first.clientY);
5872 // Cache the scale, so that we can continuously compensate for it during drag (_onMove).
5873 this._parentScale = getScale(sizedParent);
5875 on(document, MOVE[e.type], this._onMove, this);
5876 on(document, END[e.type], this._onUp, this);
5879 _onMove: function (e) {
5880 // Ignore simulated events, since we handle both touch and
5881 // mouse explicitly; otherwise we risk getting duplicates of
5882 // touch events, see #4315.
5883 // Also ignore the event if disabled; this happens in IE11
5884 // under some circumstances, see #3666.
5885 if (e._simulated || !this._enabled) { return; }
5887 if (e.touches && e.touches.length > 1) {
5892 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
5893 offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
5895 if (!offset.x && !offset.y) { return; }
5896 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
5898 // We assume that the parent container's position, border and scale do not change for the duration of the drag.
5899 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
5900 // and we can use the cached value for the scale.
5901 offset.x /= this._parentScale.x;
5902 offset.y /= this._parentScale.y;
5907 // @event dragstart: Event
5908 // Fired when a drag starts
5909 this.fire('dragstart');
5912 this._startPos = getPosition(this._element).subtract(offset);
5914 addClass(document.body, 'leaflet-dragging');
5916 this._lastTarget = e.target || e.srcElement;
5917 // IE and Edge do not give the <use> element, so fetch it
5919 if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
5920 this._lastTarget = this._lastTarget.correspondingUseElement;
5922 addClass(this._lastTarget, 'leaflet-drag-target');
5925 this._newPos = this._startPos.add(offset);
5926 this._moving = true;
5928 cancelAnimFrame(this._animRequest);
5929 this._lastEvent = e;
5930 this._animRequest = requestAnimFrame(this._updatePosition, this, true);
5933 _updatePosition: function () {
5934 var e = {originalEvent: this._lastEvent};
5936 // @event predrag: Event
5937 // Fired continuously during dragging *before* each corresponding
5938 // update of the element's position.
5939 this.fire('predrag', e);
5940 setPosition(this._element, this._newPos);
5942 // @event drag: Event
5943 // Fired continuously during dragging.
5944 this.fire('drag', e);
5947 _onUp: function (e) {
5948 // Ignore simulated events, since we handle both touch and
5949 // mouse explicitly; otherwise we risk getting duplicates of
5950 // touch events, see #4315.
5951 // Also ignore the event if disabled; this happens in IE11
5952 // under some circumstances, see #3666.
5953 if (e._simulated || !this._enabled) { return; }
5957 finishDrag: function () {
5958 removeClass(document.body, 'leaflet-dragging');
5960 if (this._lastTarget) {
5961 removeClass(this._lastTarget, 'leaflet-drag-target');
5962 this._lastTarget = null;
5965 for (var i in MOVE) {
5966 off(document, MOVE[i], this._onMove, this);
5967 off(document, END[i], this._onUp, this);
5971 enableTextSelection();
5973 if (this._moved && this._moving) {
5974 // ensure drag is not fired after dragend
5975 cancelAnimFrame(this._animRequest);
5977 // @event dragend: DragEndEvent
5978 // Fired when the drag ends.
5979 this.fire('dragend', {
5980 distance: this._newPos.distanceTo(this._startPos)
5984 this._moving = false;
5985 Draggable._dragging = false;
5991 * @namespace LineUtil
5993 * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
5996 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
5997 // Improves rendering performance dramatically by lessening the number of points to draw.
5999 // @function simplify(points: Point[], tolerance: Number): Point[]
6000 // Dramatically reduces the number of points in a polyline while retaining
6001 // its shape and returns a new array of simplified points, using the
6002 // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
6003 // Used for a huge performance boost when processing/displaying Leaflet polylines for
6004 // each zoom level and also reducing visual noise. tolerance affects the amount of
6005 // simplification (lesser value means higher quality but slower and with more points).
6006 // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
6007 function simplify(points, tolerance) {
6008 if (!tolerance || !points.length) {
6009 return points.slice();
6012 var sqTolerance = tolerance * tolerance;
6014 // stage 1: vertex reduction
6015 points = _reducePoints(points, sqTolerance);
6017 // stage 2: Douglas-Peucker simplification
6018 points = _simplifyDP(points, sqTolerance);
6023 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
6024 // Returns the distance between point `p` and segment `p1` to `p2`.
6025 function pointToSegmentDistance(p, p1, p2) {
6026 return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
6029 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
6030 // Returns the closest point from a point `p` on a segment `p1` to `p2`.
6031 function closestPointOnSegment(p, p1, p2) {
6032 return _sqClosestPointOnSegment(p, p1, p2);
6035 // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
6036 function _simplifyDP(points, sqTolerance) {
6038 var len = points.length,
6039 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
6040 markers = new ArrayConstructor(len);
6042 markers[0] = markers[len - 1] = 1;
6044 _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
6049 for (i = 0; i < len; i++) {
6051 newPoints.push(points[i]);
6058 function _simplifyDPStep(points, markers, sqTolerance, first, last) {
6063 for (i = first + 1; i <= last - 1; i++) {
6064 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
6066 if (sqDist > maxSqDist) {
6072 if (maxSqDist > sqTolerance) {
6075 _simplifyDPStep(points, markers, sqTolerance, first, index);
6076 _simplifyDPStep(points, markers, sqTolerance, index, last);
6080 // reduce points that are too close to each other to a single point
6081 function _reducePoints(points, sqTolerance) {
6082 var reducedPoints = [points[0]];
6084 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
6085 if (_sqDist(points[i], points[prev]) > sqTolerance) {
6086 reducedPoints.push(points[i]);
6090 if (prev < len - 1) {
6091 reducedPoints.push(points[len - 1]);
6093 return reducedPoints;
6098 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
6099 // Clips the segment a to b by rectangular bounds with the
6100 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
6101 // (modifying the segment points directly!). Used by Leaflet to only show polyline
6102 // points that are on the screen or near, increasing performance.
6103 function clipSegment(a, b, bounds, useLastCode, round) {
6104 var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
6105 codeB = _getBitCode(b, bounds),
6107 codeOut, p, newCode;
6109 // save 2nd code to avoid calculating it on the next segment
6113 // if a,b is inside the clip window (trivial accept)
6114 if (!(codeA | codeB)) {
6118 // if a,b is outside the clip window (trivial reject)
6119 if (codeA & codeB) {
6124 codeOut = codeA || codeB;
6125 p = _getEdgeIntersection(a, b, codeOut, bounds, round);
6126 newCode = _getBitCode(p, bounds);
6128 if (codeOut === codeA) {
6138 function _getEdgeIntersection(a, b, code, bounds, round) {
6145 if (code & 8) { // top
6146 x = a.x + dx * (max.y - a.y) / dy;
6149 } else if (code & 4) { // bottom
6150 x = a.x + dx * (min.y - a.y) / dy;
6153 } else if (code & 2) { // right
6155 y = a.y + dy * (max.x - a.x) / dx;
6157 } else if (code & 1) { // left
6159 y = a.y + dy * (min.x - a.x) / dx;
6162 return new Point(x, y, round);
6165 function _getBitCode(p, bounds) {
6168 if (p.x < bounds.min.x) { // left
6170 } else if (p.x > bounds.max.x) { // right
6174 if (p.y < bounds.min.y) { // bottom
6176 } else if (p.y > bounds.max.y) { // top
6183 // square distance (to avoid unnecessary Math.sqrt calls)
6184 function _sqDist(p1, p2) {
6185 var dx = p2.x - p1.x,
6187 return dx * dx + dy * dy;
6190 // return closest point on segment or distance to that point
6191 function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
6196 dot = dx * dx + dy * dy,
6200 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
6214 return sqDist ? dx * dx + dy * dy : new Point(x, y);
6218 // @function isFlat(latlngs: LatLng[]): Boolean
6219 // Returns true if `latlngs` is a flat array, false is nested.
6220 function isFlat(latlngs) {
6221 return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
6224 function _flat(latlngs) {
6225 console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
6226 return isFlat(latlngs);
6230 var LineUtil = (Object.freeze || Object)({
6232 pointToSegmentDistance: pointToSegmentDistance,
6233 closestPointOnSegment: closestPointOnSegment,
6234 clipSegment: clipSegment,
6235 _getEdgeIntersection: _getEdgeIntersection,
6236 _getBitCode: _getBitCode,
6237 _sqClosestPointOnSegment: _sqClosestPointOnSegment,
6243 * @namespace PolyUtil
6244 * Various utility functions for polygon geometries.
6247 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
6248 * 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)).
6249 * Used by Leaflet to only show polygon points that are on the screen or near, increasing
6250 * performance. Note that polygon points needs different algorithm for clipping
6251 * than polyline, so there's a separate method for it.
6253 function clipPolygon(points, bounds, round) {
6255 edges = [1, 4, 2, 8],
6260 for (i = 0, len = points.length; i < len; i++) {
6261 points[i]._code = _getBitCode(points[i], bounds);
6264 // for each edge (left, bottom, right, top)
6265 for (k = 0; k < 4; k++) {
6269 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
6273 // if a is inside the clip window
6274 if (!(a._code & edge)) {
6275 // if b is outside the clip window (a->b goes out of screen)
6276 if (b._code & edge) {
6277 p = _getEdgeIntersection(b, a, edge, bounds, round);
6278 p._code = _getBitCode(p, bounds);
6279 clippedPoints.push(p);
6281 clippedPoints.push(a);
6283 // else if b is inside the clip window (a->b enters the screen)
6284 } else if (!(b._code & edge)) {
6285 p = _getEdgeIntersection(b, a, edge, bounds, round);
6286 p._code = _getBitCode(p, bounds);
6287 clippedPoints.push(p);
6290 points = clippedPoints;
6297 var PolyUtil = (Object.freeze || Object)({
6298 clipPolygon: clipPolygon
6302 * @namespace Projection
6304 * Leaflet comes with a set of already defined Projections out of the box:
6306 * @projection L.Projection.LonLat
6308 * Equirectangular, or Plate Carree projection — the most simple projection,
6309 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
6310 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
6311 * `EPSG:4326` and `Simple` CRS.
6315 project: function (latlng) {
6316 return new Point(latlng.lng, latlng.lat);
6319 unproject: function (point) {
6320 return new LatLng(point.y, point.x);
6323 bounds: new Bounds([-180, -90], [180, 90])
6327 * @namespace Projection
6328 * @projection L.Projection.Mercator
6330 * 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.
6335 R_MINOR: 6356752.314245179,
6337 bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
6339 project: function (latlng) {
6340 var d = Math.PI / 180,
6343 tmp = this.R_MINOR / r,
6344 e = Math.sqrt(1 - tmp * tmp),
6345 con = e * Math.sin(y);
6347 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
6348 y = -r * Math.log(Math.max(ts, 1E-10));
6350 return new Point(latlng.lng * d * r, y);
6353 unproject: function (point) {
6354 var d = 180 / Math.PI,
6356 tmp = this.R_MINOR / r,
6357 e = Math.sqrt(1 - tmp * tmp),
6358 ts = Math.exp(-point.y / r),
6359 phi = Math.PI / 2 - 2 * Math.atan(ts);
6361 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
6362 con = e * Math.sin(phi);
6363 con = Math.pow((1 - con) / (1 + con), e / 2);
6364 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
6368 return new LatLng(phi * d, point.x * d / r);
6375 * An object with methods for projecting geographical coordinates of the world onto
6376 * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection).
6378 * @property bounds: Bounds
6379 * The bounds (specified in CRS units) where the projection is valid
6381 * @method project(latlng: LatLng): Point
6382 * Projects geographical coordinates into a 2D point.
6383 * Only accepts actual `L.LatLng` instances, not arrays.
6385 * @method unproject(point: Point): LatLng
6386 * The inverse of `project`. Projects a 2D point into a geographical location.
6387 * Only accepts actual `L.Point` instances, not arrays.
6389 * Note that the projection instances do not inherit from Leafet's `Class` object,
6390 * and can't be instantiated. Also, new classes can't inherit from them,
6391 * and methods can't be added to them with the `include` function.
6398 var index = (Object.freeze || Object)({
6401 SphericalMercator: SphericalMercator
6406 * @crs L.CRS.EPSG3395
6408 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
6410 var EPSG3395 = extend({}, Earth, {
6412 projection: Mercator,
6414 transformation: (function () {
6415 var scale = 0.5 / (Math.PI * Mercator.R);
6416 return toTransformation(scale, 0.5, -scale, 0.5);
6422 * @crs L.CRS.EPSG4326
6424 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
6426 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
6427 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer`
6428 * with this CRS, ensure that there are two 256x256 pixel tiles covering the
6429 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
6430 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
6433 var EPSG4326 = extend({}, Earth, {
6436 transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
6443 * A simple CRS that maps longitude and latitude into `x` and `y` directly.
6444 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
6445 * axis should still be inverted (going from bottom to top). `distance()` returns
6446 * simple euclidean distance.
6449 var Simple = extend({}, CRS, {
6451 transformation: toTransformation(1, 0, -1, 0),
6453 scale: function (zoom) {
6454 return Math.pow(2, zoom);
6457 zoom: function (scale) {
6458 return Math.log(scale) / Math.LN2;
6461 distance: function (latlng1, latlng2) {
6462 var dx = latlng2.lng - latlng1.lng,
6463 dy = latlng2.lat - latlng1.lat;
6465 return Math.sqrt(dx * dx + dy * dy);
6472 CRS.EPSG3395 = EPSG3395;
6473 CRS.EPSG3857 = EPSG3857;
6474 CRS.EPSG900913 = EPSG900913;
6475 CRS.EPSG4326 = EPSG4326;
6476 CRS.Simple = Simple;
6484 * A set of methods from the Layer base class that all Leaflet layers use.
6485 * Inherits all methods, options and events from `L.Evented`.
6490 * var layer = L.Marker(latlng).addTo(map);
6496 * Fired after the layer is added to a map
6498 * @event remove: Event
6499 * Fired after the layer is removed from a map
6503 var Layer = Evented.extend({
6505 // Classes extending `L.Layer` will inherit the following options:
6507 // @option pane: String = 'overlayPane'
6508 // 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.
6509 pane: 'overlayPane',
6511 // @option attribution: String = null
6512 // String to be shown in the attribution control, e.g. "© OpenStreetMap contributors". It describes the layer data and is often a legal obligation towards copyright holders and tile providers.
6515 bubblingMouseEvents: true
6519 * Classes extending `L.Layer` will inherit the following methods:
6521 * @method addTo(map: Map|LayerGroup): this
6522 * Adds the layer to the given map or layer group.
6524 addTo: function (map) {
6529 // @method remove: this
6530 // Removes the layer from the map it is currently active on.
6531 remove: function () {
6532 return this.removeFrom(this._map || this._mapToAdd);
6535 // @method removeFrom(map: Map): this
6536 // Removes the layer from the given map
6537 removeFrom: function (obj) {
6539 obj.removeLayer(this);
6544 // @method getPane(name? : String): HTMLElement
6545 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
6546 getPane: function (name) {
6547 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
6550 addInteractiveTarget: function (targetEl) {
6551 this._map._targets[stamp(targetEl)] = this;
6555 removeInteractiveTarget: function (targetEl) {
6556 delete this._map._targets[stamp(targetEl)];
6560 // @method getAttribution: String
6561 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
6562 getAttribution: function () {
6563 return this.options.attribution;
6566 _layerAdd: function (e) {
6569 // check in case layer gets added and then removed before the map is ready
6570 if (!map.hasLayer(this)) { return; }
6573 this._zoomAnimated = map._zoomAnimated;
6575 if (this.getEvents) {
6576 var events = this.getEvents();
6577 map.on(events, this);
6578 this.once('remove', function () {
6579 map.off(events, this);
6585 if (this.getAttribution && map.attributionControl) {
6586 map.attributionControl.addAttribution(this.getAttribution());
6590 map.fire('layeradd', {layer: this});
6594 /* @section Extension methods
6597 * Every layer should extend from `L.Layer` and (re-)implement the following methods.
6599 * @method onAdd(map: Map): this
6600 * 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).
6602 * @method onRemove(map: Map): this
6603 * 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).
6605 * @method getEvents(): Object
6606 * 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.
6608 * @method getAttribution(): String
6609 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
6611 * @method beforeAdd(map: Map): this
6612 * 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.
6617 * @section Layer events
6619 * @event layeradd: LayerEvent
6620 * Fired when a new layer is added to the map.
6622 * @event layerremove: LayerEvent
6623 * Fired when some layer is removed from the map
6625 * @section Methods for Layers and Controls
6628 // @method addLayer(layer: Layer): this
6629 // Adds the given layer to the map
6630 addLayer: function (layer) {
6631 if (!layer._layerAdd) {
6632 throw new Error('The provided object is not a Layer.');
6635 var id = stamp(layer);
6636 if (this._layers[id]) { return this; }
6637 this._layers[id] = layer;
6639 layer._mapToAdd = this;
6641 if (layer.beforeAdd) {
6642 layer.beforeAdd(this);
6645 this.whenReady(layer._layerAdd, layer);
6650 // @method removeLayer(layer: Layer): this
6651 // Removes the given layer from the map.
6652 removeLayer: function (layer) {
6653 var id = stamp(layer);
6655 if (!this._layers[id]) { return this; }
6658 layer.onRemove(this);
6661 if (layer.getAttribution && this.attributionControl) {
6662 this.attributionControl.removeAttribution(layer.getAttribution());
6665 delete this._layers[id];
6668 this.fire('layerremove', {layer: layer});
6669 layer.fire('remove');
6672 layer._map = layer._mapToAdd = null;
6677 // @method hasLayer(layer: Layer): Boolean
6678 // Returns `true` if the given layer is currently added to the map
6679 hasLayer: function (layer) {
6680 return !!layer && (stamp(layer) in this._layers);
6683 /* @method eachLayer(fn: Function, context?: Object): this
6684 * Iterates over the layers of the map, optionally specifying context of the iterator function.
6686 * map.eachLayer(function(layer){
6687 * layer.bindPopup('Hello');
6691 eachLayer: function (method, context) {
6692 for (var i in this._layers) {
6693 method.call(context, this._layers[i]);
6698 _addLayers: function (layers) {
6699 layers = layers ? (isArray(layers) ? layers : [layers]) : [];
6701 for (var i = 0, len = layers.length; i < len; i++) {
6702 this.addLayer(layers[i]);
6706 _addZoomLimit: function (layer) {
6707 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
6708 this._zoomBoundLayers[stamp(layer)] = layer;
6709 this._updateZoomLevels();
6713 _removeZoomLimit: function (layer) {
6714 var id = stamp(layer);
6716 if (this._zoomBoundLayers[id]) {
6717 delete this._zoomBoundLayers[id];
6718 this._updateZoomLevels();
6722 _updateZoomLevels: function () {
6723 var minZoom = Infinity,
6724 maxZoom = -Infinity,
6725 oldZoomSpan = this._getZoomSpan();
6727 for (var i in this._zoomBoundLayers) {
6728 var options = this._zoomBoundLayers[i].options;
6730 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
6731 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
6734 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
6735 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
6737 // @section Map state change events
6738 // @event zoomlevelschange: Event
6739 // Fired when the number of zoomlevels on the map is changed due
6740 // to adding or removing a layer.
6741 if (oldZoomSpan !== this._getZoomSpan()) {
6742 this.fire('zoomlevelschange');
6745 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
6746 this.setZoom(this._layersMaxZoom);
6748 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
6749 this.setZoom(this._layersMinZoom);
6759 * Used to group several layers and handle them as one. If you add it to the map,
6760 * any layers added or removed from the group will be added/removed on the map as
6761 * well. Extends `Layer`.
6766 * L.layerGroup([marker1, marker2])
6767 * .addLayer(polyline)
6772 var LayerGroup = Layer.extend({
6774 initialize: function (layers, options) {
6775 setOptions(this, options);
6782 for (i = 0, len = layers.length; i < len; i++) {
6783 this.addLayer(layers[i]);
6788 // @method addLayer(layer: Layer): this
6789 // Adds the given layer to the group.
6790 addLayer: function (layer) {
6791 var id = this.getLayerId(layer);
6793 this._layers[id] = layer;
6796 this._map.addLayer(layer);
6802 // @method removeLayer(layer: Layer): this
6803 // Removes the given layer from the group.
6805 // @method removeLayer(id: Number): this
6806 // Removes the layer with the given internal ID from the group.
6807 removeLayer: function (layer) {
6808 var id = layer in this._layers ? layer : this.getLayerId(layer);
6810 if (this._map && this._layers[id]) {
6811 this._map.removeLayer(this._layers[id]);
6814 delete this._layers[id];
6819 // @method hasLayer(layer: Layer): Boolean
6820 // Returns `true` if the given layer is currently added to the group.
6822 // @method hasLayer(id: Number): Boolean
6823 // Returns `true` if the given internal ID is currently added to the group.
6824 hasLayer: function (layer) {
6825 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
6828 // @method clearLayers(): this
6829 // Removes all the layers from the group.
6830 clearLayers: function () {
6831 return this.eachLayer(this.removeLayer, this);
6834 // @method invoke(methodName: String, …): this
6835 // Calls `methodName` on every layer contained in this group, passing any
6836 // additional parameters. Has no effect if the layers contained do not
6837 // implement `methodName`.
6838 invoke: function (methodName) {
6839 var args = Array.prototype.slice.call(arguments, 1),
6842 for (i in this._layers) {
6843 layer = this._layers[i];
6845 if (layer[methodName]) {
6846 layer[methodName].apply(layer, args);
6853 onAdd: function (map) {
6854 this.eachLayer(map.addLayer, map);
6857 onRemove: function (map) {
6858 this.eachLayer(map.removeLayer, map);
6861 // @method eachLayer(fn: Function, context?: Object): this
6862 // Iterates over the layers of the group, optionally specifying context of the iterator function.
6864 // group.eachLayer(function (layer) {
6865 // layer.bindPopup('Hello');
6868 eachLayer: function (method, context) {
6869 for (var i in this._layers) {
6870 method.call(context, this._layers[i]);
6875 // @method getLayer(id: Number): Layer
6876 // Returns the layer with the given internal ID.
6877 getLayer: function (id) {
6878 return this._layers[id];
6881 // @method getLayers(): Layer[]
6882 // Returns an array of all the layers added to the group.
6883 getLayers: function () {
6885 this.eachLayer(layers.push, layers);
6889 // @method setZIndex(zIndex: Number): this
6890 // Calls `setZIndex` on every layer contained in this group, passing the z-index.
6891 setZIndex: function (zIndex) {
6892 return this.invoke('setZIndex', zIndex);
6895 // @method getLayerId(layer: Layer): Number
6896 // Returns the internal ID for a layer
6897 getLayerId: function (layer) {
6898 return stamp(layer);
6903 // @factory L.layerGroup(layers?: Layer[], options?: Object)
6904 // Create a layer group, optionally given an initial set of layers and an `options` object.
6905 var layerGroup = function (layers, options) {
6906 return new LayerGroup(layers, options);
6910 * @class FeatureGroup
6911 * @aka L.FeatureGroup
6912 * @inherits LayerGroup
6914 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
6915 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
6916 * * Events are propagated to the `FeatureGroup`, so if the group has an event
6917 * handler, it will handle events from any of the layers. This includes mouse events
6918 * and custom events.
6919 * * Has `layeradd` and `layerremove` events
6924 * L.featureGroup([marker1, marker2, polyline])
6925 * .bindPopup('Hello world!')
6926 * .on('click', function() { alert('Clicked on a member of the group!'); })
6931 var FeatureGroup = LayerGroup.extend({
6933 addLayer: function (layer) {
6934 if (this.hasLayer(layer)) {
6938 layer.addEventParent(this);
6940 LayerGroup.prototype.addLayer.call(this, layer);
6942 // @event layeradd: LayerEvent
6943 // Fired when a layer is added to this `FeatureGroup`
6944 return this.fire('layeradd', {layer: layer});
6947 removeLayer: function (layer) {
6948 if (!this.hasLayer(layer)) {
6951 if (layer in this._layers) {
6952 layer = this._layers[layer];
6955 layer.removeEventParent(this);
6957 LayerGroup.prototype.removeLayer.call(this, layer);
6959 // @event layerremove: LayerEvent
6960 // Fired when a layer is removed from this `FeatureGroup`
6961 return this.fire('layerremove', {layer: layer});
6964 // @method setStyle(style: Path options): this
6965 // Sets the given path options to each layer of the group that has a `setStyle` method.
6966 setStyle: function (style) {
6967 return this.invoke('setStyle', style);
6970 // @method bringToFront(): this
6971 // Brings the layer group to the top of all other layers
6972 bringToFront: function () {
6973 return this.invoke('bringToFront');
6976 // @method bringToBack(): this
6977 // Brings the layer group to the back of all other layers
6978 bringToBack: function () {
6979 return this.invoke('bringToBack');
6982 // @method getBounds(): LatLngBounds
6983 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
6984 getBounds: function () {
6985 var bounds = new LatLngBounds();
6987 for (var id in this._layers) {
6988 var layer = this._layers[id];
6989 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
6995 // @factory L.featureGroup(layers: Layer[])
6996 // Create a feature group, optionally given an initial set of layers.
6997 var featureGroup = function (layers) {
6998 return new FeatureGroup(layers);
7005 * Represents an icon to provide when creating a marker.
7010 * var myIcon = L.icon({
7011 * iconUrl: 'my-icon.png',
7012 * iconRetinaUrl: 'my-icon@2x.png',
7013 * iconSize: [38, 95],
7014 * iconAnchor: [22, 94],
7015 * popupAnchor: [-3, -76],
7016 * shadowUrl: 'my-icon-shadow.png',
7017 * shadowRetinaUrl: 'my-icon-shadow@2x.png',
7018 * shadowSize: [68, 95],
7019 * shadowAnchor: [22, 94]
7022 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
7025 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
7029 var Icon = Class.extend({
7034 * @option iconUrl: String = null
7035 * **(required)** The URL to the icon image (absolute or relative to your script path).
7037 * @option iconRetinaUrl: String = null
7038 * The URL to a retina sized version of the icon image (absolute or relative to your
7039 * script path). Used for Retina screen devices.
7041 * @option iconSize: Point = null
7042 * Size of the icon image in pixels.
7044 * @option iconAnchor: Point = null
7045 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
7046 * will be aligned so that this point is at the marker's geographical location. Centered
7047 * by default if size is specified, also can be set in CSS with negative margins.
7049 * @option popupAnchor: Point = [0, 0]
7050 * The coordinates of the point from which popups will "open", relative to the icon anchor.
7052 * @option tooltipAnchor: Point = [0, 0]
7053 * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
7055 * @option shadowUrl: String = null
7056 * The URL to the icon shadow image. If not specified, no shadow image will be created.
7058 * @option shadowRetinaUrl: String = null
7060 * @option shadowSize: Point = null
7061 * Size of the shadow image in pixels.
7063 * @option shadowAnchor: Point = null
7064 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
7065 * as iconAnchor if not specified).
7067 * @option className: String = ''
7068 * A custom class name to assign to both icon and shadow images. Empty by default.
7072 popupAnchor: [0, 0],
7073 tooltipAnchor: [0, 0]
7076 initialize: function (options) {
7077 setOptions(this, options);
7080 // @method createIcon(oldIcon?: HTMLElement): HTMLElement
7081 // Called internally when the icon has to be shown, returns a `<img>` HTML element
7082 // styled according to the options.
7083 createIcon: function (oldIcon) {
7084 return this._createIcon('icon', oldIcon);
7087 // @method createShadow(oldIcon?: HTMLElement): HTMLElement
7088 // As `createIcon`, but for the shadow beneath it.
7089 createShadow: function (oldIcon) {
7090 return this._createIcon('shadow', oldIcon);
7093 _createIcon: function (name, oldIcon) {
7094 var src = this._getIconUrl(name);
7097 if (name === 'icon') {
7098 throw new Error('iconUrl not set in Icon options (see the docs).');
7103 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
7104 this._setIconStyles(img, name);
7109 _setIconStyles: function (img, name) {
7110 var options = this.options;
7111 var sizeOption = options[name + 'Size'];
7113 if (typeof sizeOption === 'number') {
7114 sizeOption = [sizeOption, sizeOption];
7117 var size = toPoint(sizeOption),
7118 anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
7119 size && size.divideBy(2, true));
7121 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
7124 img.style.marginLeft = (-anchor.x) + 'px';
7125 img.style.marginTop = (-anchor.y) + 'px';
7129 img.style.width = size.x + 'px';
7130 img.style.height = size.y + 'px';
7134 _createImg: function (src, el) {
7135 el = el || document.createElement('img');
7140 _getIconUrl: function (name) {
7141 return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
7146 // @factory L.icon(options: Icon options)
7147 // Creates an icon instance with the given options.
7148 function icon(options) {
7149 return new Icon(options);
7153 * @miniclass Icon.Default (Icon)
7154 * @aka L.Icon.Default
7157 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
7158 * no icon is specified. Points to the blue marker image distributed with Leaflet
7161 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
7162 * (which is a set of `Icon options`).
7164 * If you want to _completely_ replace the default icon, override the
7165 * `L.Marker.prototype.options.icon` with your own icon instead.
7168 var IconDefault = Icon.extend({
7171 iconUrl: 'marker-icon.png',
7172 iconRetinaUrl: 'marker-icon-2x.png',
7173 shadowUrl: 'marker-shadow.png',
7175 iconAnchor: [12, 41],
7176 popupAnchor: [1, -34],
7177 tooltipAnchor: [16, -28],
7178 shadowSize: [41, 41]
7181 _getIconUrl: function (name) {
7182 if (!IconDefault.imagePath) { // Deprecated, backwards-compatibility only
7183 IconDefault.imagePath = this._detectIconPath();
7186 // @option imagePath: String
7187 // `Icon.Default` will try to auto-detect the location of the
7188 // blue icon images. If you are placing these images in a non-standard
7189 // way, set this option to point to the right path.
7190 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
7193 _detectIconPath: function () {
7194 var el = create$1('div', 'leaflet-default-icon-path', document.body);
7195 var path = getStyle(el, 'background-image') ||
7196 getStyle(el, 'backgroundImage'); // IE8
7198 document.body.removeChild(el);
7200 if (path === null || path.indexOf('url') !== 0) {
7203 path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, '');
7211 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7215 /* @namespace Marker
7216 * @section Interaction handlers
7218 * 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:
7221 * marker.dragging.disable();
7224 * @property dragging: Handler
7225 * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
7228 var MarkerDrag = Handler.extend({
7229 initialize: function (marker) {
7230 this._marker = marker;
7233 addHooks: function () {
7234 var icon = this._marker._icon;
7236 if (!this._draggable) {
7237 this._draggable = new Draggable(icon, icon, true);
7240 this._draggable.on({
7241 dragstart: this._onDragStart,
7242 predrag: this._onPreDrag,
7244 dragend: this._onDragEnd
7247 addClass(icon, 'leaflet-marker-draggable');
7250 removeHooks: function () {
7251 this._draggable.off({
7252 dragstart: this._onDragStart,
7253 predrag: this._onPreDrag,
7255 dragend: this._onDragEnd
7258 if (this._marker._icon) {
7259 removeClass(this._marker._icon, 'leaflet-marker-draggable');
7263 moved: function () {
7264 return this._draggable && this._draggable._moved;
7267 _adjustPan: function (e) {
7268 var marker = this._marker,
7270 speed = this._marker.options.autoPanSpeed,
7271 padding = this._marker.options.autoPanPadding,
7272 iconPos = getPosition(marker._icon),
7273 bounds = map.getPixelBounds(),
7274 origin = map.getPixelOrigin();
7276 var panBounds = toBounds(
7277 bounds.min._subtract(origin).add(padding),
7278 bounds.max._subtract(origin).subtract(padding)
7281 if (!panBounds.contains(iconPos)) {
7282 // Compute incremental movement
7283 var movement = toPoint(
7284 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
7285 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
7287 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
7288 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
7289 ).multiplyBy(speed);
7291 map.panBy(movement, {animate: false});
7293 this._draggable._newPos._add(movement);
7294 this._draggable._startPos._add(movement);
7296 setPosition(marker._icon, this._draggable._newPos);
7299 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7303 _onDragStart: function () {
7304 // @section Dragging events
7305 // @event dragstart: Event
7306 // Fired when the user starts dragging the marker.
7308 // @event movestart: Event
7309 // Fired when the marker starts moving (because of dragging).
7311 this._oldLatLng = this._marker.getLatLng();
7318 _onPreDrag: function (e) {
7319 if (this._marker.options.autoPan) {
7320 cancelAnimFrame(this._panRequest);
7321 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7325 _onDrag: function (e) {
7326 var marker = this._marker,
7327 shadow = marker._shadow,
7328 iconPos = getPosition(marker._icon),
7329 latlng = marker._map.layerPointToLatLng(iconPos);
7331 // update shadow position
7333 setPosition(shadow, iconPos);
7336 marker._latlng = latlng;
7338 e.oldLatLng = this._oldLatLng;
7340 // @event drag: Event
7341 // Fired repeatedly while the user drags the marker.
7347 _onDragEnd: function (e) {
7348 // @event dragend: DragEndEvent
7349 // Fired when the user stops dragging the marker.
7351 cancelAnimFrame(this._panRequest);
7353 // @event moveend: Event
7354 // Fired when the marker stops moving (because of dragging).
7355 delete this._oldLatLng;
7358 .fire('dragend', e);
7364 * @inherits Interactive layer
7366 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
7371 * L.marker([50.5, 30.5]).addTo(map);
7375 var Marker = Layer.extend({
7378 // @aka Marker options
7380 // @option icon: Icon = *
7381 // Icon instance to use for rendering the marker.
7382 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
7383 // If not specified, a common instance of `L.Icon.Default` is used.
7384 icon: new IconDefault(),
7386 // Option inherited from "Interactive layer" abstract class
7389 // @option keyboard: Boolean = true
7390 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
7393 // @option title: String = ''
7394 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
7397 // @option alt: String = ''
7398 // Text for the `alt` attribute of the icon image (useful for accessibility).
7401 // @option zIndexOffset: Number = 0
7402 // 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).
7405 // @option opacity: Number = 1.0
7406 // The opacity of the marker.
7409 // @option riseOnHover: Boolean = false
7410 // If `true`, the marker will get on top of others when you hover the mouse over it.
7413 // @option riseOffset: Number = 250
7414 // The z-index offset used for the `riseOnHover` feature.
7417 // @option pane: String = 'markerPane'
7418 // `Map pane` where the markers icon will be added.
7421 // @option bubblingMouseEvents: Boolean = false
7422 // When `true`, a mouse event on this marker will trigger the same event on the map
7423 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7424 bubblingMouseEvents: false,
7426 // @section Draggable marker options
7427 // @option draggable: Boolean = false
7428 // Whether the marker is draggable with mouse/touch or not.
7431 // @option autoPan: Boolean = false
7432 // Whether to pan the map when dragging this marker near its edge or not.
7435 // @option autoPanPadding: Point = Point(50, 50)
7436 // Distance (in pixels to the left/right and to the top/bottom) of the
7437 // map edge to start panning the map.
7438 autoPanPadding: [50, 50],
7440 // @option autoPanSpeed: Number = 10
7441 // Number of pixels the map should pan by.
7447 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
7450 initialize: function (latlng, options) {
7451 setOptions(this, options);
7452 this._latlng = toLatLng(latlng);
7455 onAdd: function (map) {
7456 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
7458 if (this._zoomAnimated) {
7459 map.on('zoomanim', this._animateZoom, this);
7466 onRemove: function (map) {
7467 if (this.dragging && this.dragging.enabled()) {
7468 this.options.draggable = true;
7469 this.dragging.removeHooks();
7471 delete this.dragging;
7473 if (this._zoomAnimated) {
7474 map.off('zoomanim', this._animateZoom, this);
7478 this._removeShadow();
7481 getEvents: function () {
7484 viewreset: this.update
7488 // @method getLatLng: LatLng
7489 // Returns the current geographical position of the marker.
7490 getLatLng: function () {
7491 return this._latlng;
7494 // @method setLatLng(latlng: LatLng): this
7495 // Changes the marker position to the given point.
7496 setLatLng: function (latlng) {
7497 var oldLatLng = this._latlng;
7498 this._latlng = toLatLng(latlng);
7501 // @event move: Event
7502 // 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`.
7503 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7506 // @method setZIndexOffset(offset: Number): this
7507 // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
7508 setZIndexOffset: function (offset) {
7509 this.options.zIndexOffset = offset;
7510 return this.update();
7513 // @method setIcon(icon: Icon): this
7514 // Changes the marker icon.
7515 setIcon: function (icon) {
7517 this.options.icon = icon;
7525 this.bindPopup(this._popup, this._popup.options);
7531 getElement: function () {
7535 update: function () {
7537 if (this._icon && this._map) {
7538 var pos = this._map.latLngToLayerPoint(this._latlng).round();
7545 _initIcon: function () {
7546 var options = this.options,
7547 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
7549 var icon = options.icon.createIcon(this._icon),
7552 // if we're not reusing the icon, remove the old one and init new one
7553 if (icon !== this._icon) {
7559 if (options.title) {
7560 icon.title = options.title;
7563 if (icon.tagName === 'IMG') {
7564 icon.alt = options.alt || '';
7568 addClass(icon, classToAdd);
7570 if (options.keyboard) {
7571 icon.tabIndex = '0';
7576 if (options.riseOnHover) {
7578 mouseover: this._bringToFront,
7579 mouseout: this._resetZIndex
7583 var newShadow = options.icon.createShadow(this._shadow),
7586 if (newShadow !== this._shadow) {
7587 this._removeShadow();
7592 addClass(newShadow, classToAdd);
7595 this._shadow = newShadow;
7598 if (options.opacity < 1) {
7599 this._updateOpacity();
7604 this.getPane().appendChild(this._icon);
7606 this._initInteraction();
7607 if (newShadow && addShadow) {
7608 this.getPane('shadowPane').appendChild(this._shadow);
7612 _removeIcon: function () {
7613 if (this.options.riseOnHover) {
7615 mouseover: this._bringToFront,
7616 mouseout: this._resetZIndex
7621 this.removeInteractiveTarget(this._icon);
7626 _removeShadow: function () {
7628 remove(this._shadow);
7630 this._shadow = null;
7633 _setPos: function (pos) {
7634 setPosition(this._icon, pos);
7637 setPosition(this._shadow, pos);
7640 this._zIndex = pos.y + this.options.zIndexOffset;
7642 this._resetZIndex();
7645 _updateZIndex: function (offset) {
7646 this._icon.style.zIndex = this._zIndex + offset;
7649 _animateZoom: function (opt) {
7650 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
7655 _initInteraction: function () {
7657 if (!this.options.interactive) { return; }
7659 addClass(this._icon, 'leaflet-interactive');
7661 this.addInteractiveTarget(this._icon);
7664 var draggable = this.options.draggable;
7665 if (this.dragging) {
7666 draggable = this.dragging.enabled();
7667 this.dragging.disable();
7670 this.dragging = new MarkerDrag(this);
7673 this.dragging.enable();
7678 // @method setOpacity(opacity: Number): this
7679 // Changes the opacity of the marker.
7680 setOpacity: function (opacity) {
7681 this.options.opacity = opacity;
7683 this._updateOpacity();
7689 _updateOpacity: function () {
7690 var opacity = this.options.opacity;
7692 setOpacity(this._icon, opacity);
7695 setOpacity(this._shadow, opacity);
7699 _bringToFront: function () {
7700 this._updateZIndex(this.options.riseOffset);
7703 _resetZIndex: function () {
7704 this._updateZIndex(0);
7707 _getPopupAnchor: function () {
7708 return this.options.icon.options.popupAnchor;
7711 _getTooltipAnchor: function () {
7712 return this.options.icon.options.tooltipAnchor;
7717 // factory L.marker(latlng: LatLng, options? : Marker options)
7719 // @factory L.marker(latlng: LatLng, options? : Marker options)
7720 // Instantiates a Marker object given a geographical point and optionally an options object.
7721 function marker(latlng, options) {
7722 return new Marker(latlng, options);
7728 * @inherits Interactive layer
7730 * An abstract class that contains options and constants shared between vector
7731 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
7734 var Path = Layer.extend({
7737 // @aka Path options
7739 // @option stroke: Boolean = true
7740 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
7743 // @option color: String = '#3388ff'
7747 // @option weight: Number = 3
7748 // Stroke width in pixels
7751 // @option opacity: Number = 1.0
7755 // @option lineCap: String= 'round'
7756 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
7759 // @option lineJoin: String = 'round'
7760 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
7763 // @option dashArray: String = null
7764 // 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).
7767 // @option dashOffset: String = null
7768 // 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).
7771 // @option fill: Boolean = depends
7772 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
7775 // @option fillColor: String = *
7776 // Fill color. Defaults to the value of the [`color`](#path-color) option
7779 // @option fillOpacity: Number = 0.2
7783 // @option fillRule: String = 'evenodd'
7784 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
7785 fillRule: 'evenodd',
7789 // Option inherited from "Interactive layer" abstract class
7792 // @option bubblingMouseEvents: Boolean = true
7793 // When `true`, a mouse event on this path will trigger the same event on the map
7794 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7795 bubblingMouseEvents: true
7798 beforeAdd: function (map) {
7799 // Renderer is set here because we need to call renderer.getEvents
7800 // before this.getEvents.
7801 this._renderer = map.getRenderer(this);
7804 onAdd: function () {
7805 this._renderer._initPath(this);
7807 this._renderer._addPath(this);
7810 onRemove: function () {
7811 this._renderer._removePath(this);
7814 // @method redraw(): this
7815 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
7816 redraw: function () {
7818 this._renderer._updatePath(this);
7823 // @method setStyle(style: Path options): this
7824 // Changes the appearance of a Path based on the options in the `Path options` object.
7825 setStyle: function (style) {
7826 setOptions(this, style);
7827 if (this._renderer) {
7828 this._renderer._updateStyle(this);
7833 // @method bringToFront(): this
7834 // Brings the layer to the top of all path layers.
7835 bringToFront: function () {
7836 if (this._renderer) {
7837 this._renderer._bringToFront(this);
7842 // @method bringToBack(): this
7843 // Brings the layer to the bottom of all path layers.
7844 bringToBack: function () {
7845 if (this._renderer) {
7846 this._renderer._bringToBack(this);
7851 getElement: function () {
7855 _reset: function () {
7856 // defined in child classes
7861 _clickTolerance: function () {
7862 // used when doing hit detection for Canvas layers
7863 return (this.options.stroke ? this.options.weight / 2 : 0) + this._renderer.options.tolerance;
7868 * @class CircleMarker
7869 * @aka L.CircleMarker
7872 * A circle of a fixed size with radius specified in pixels. Extends `Path`.
7875 var CircleMarker = Path.extend({
7878 // @aka CircleMarker options
7882 // @option radius: Number = 10
7883 // Radius of the circle marker, in pixels
7887 initialize: function (latlng, options) {
7888 setOptions(this, options);
7889 this._latlng = toLatLng(latlng);
7890 this._radius = this.options.radius;
7893 // @method setLatLng(latLng: LatLng): this
7894 // Sets the position of a circle marker to a new location.
7895 setLatLng: function (latlng) {
7896 this._latlng = toLatLng(latlng);
7898 return this.fire('move', {latlng: this._latlng});
7901 // @method getLatLng(): LatLng
7902 // Returns the current geographical position of the circle marker
7903 getLatLng: function () {
7904 return this._latlng;
7907 // @method setRadius(radius: Number): this
7908 // Sets the radius of a circle marker. Units are in pixels.
7909 setRadius: function (radius) {
7910 this.options.radius = this._radius = radius;
7911 return this.redraw();
7914 // @method getRadius(): Number
7915 // Returns the current radius of the circle
7916 getRadius: function () {
7917 return this._radius;
7920 setStyle : function (options) {
7921 var radius = options && options.radius || this._radius;
7922 Path.prototype.setStyle.call(this, options);
7923 this.setRadius(radius);
7927 _project: function () {
7928 this._point = this._map.latLngToLayerPoint(this._latlng);
7929 this._updateBounds();
7932 _updateBounds: function () {
7933 var r = this._radius,
7934 r2 = this._radiusY || r,
7935 w = this._clickTolerance(),
7936 p = [r + w, r2 + w];
7937 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
7940 _update: function () {
7946 _updatePath: function () {
7947 this._renderer._updateCircle(this);
7950 _empty: function () {
7951 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
7954 // Needed by the `Canvas` renderer for interactivity
7955 _containsPoint: function (p) {
7956 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
7961 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
7962 // Instantiates a circle marker object given a geographical point, and an optional options object.
7963 function circleMarker(latlng, options) {
7964 return new CircleMarker(latlng, options);
7970 * @inherits CircleMarker
7972 * A class for drawing circle overlays on a map. Extends `CircleMarker`.
7974 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
7979 * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
7983 var Circle = CircleMarker.extend({
7985 initialize: function (latlng, options, legacyOptions) {
7986 if (typeof options === 'number') {
7987 // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
7988 options = extend({}, legacyOptions, {radius: options});
7990 setOptions(this, options);
7991 this._latlng = toLatLng(latlng);
7993 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
7996 // @aka Circle options
7997 // @option radius: Number; Radius of the circle, in meters.
7998 this._mRadius = this.options.radius;
8001 // @method setRadius(radius: Number): this
8002 // Sets the radius of a circle. Units are in meters.
8003 setRadius: function (radius) {
8004 this._mRadius = radius;
8005 return this.redraw();
8008 // @method getRadius(): Number
8009 // Returns the current radius of a circle. Units are in meters.
8010 getRadius: function () {
8011 return this._mRadius;
8014 // @method getBounds(): LatLngBounds
8015 // Returns the `LatLngBounds` of the path.
8016 getBounds: function () {
8017 var half = [this._radius, this._radiusY || this._radius];
8019 return new LatLngBounds(
8020 this._map.layerPointToLatLng(this._point.subtract(half)),
8021 this._map.layerPointToLatLng(this._point.add(half)));
8024 setStyle: Path.prototype.setStyle,
8026 _project: function () {
8028 var lng = this._latlng.lng,
8029 lat = this._latlng.lat,
8031 crs = map.options.crs;
8033 if (crs.distance === Earth.distance) {
8034 var d = Math.PI / 180,
8035 latR = (this._mRadius / Earth.R) / d,
8036 top = map.project([lat + latR, lng]),
8037 bottom = map.project([lat - latR, lng]),
8038 p = top.add(bottom).divideBy(2),
8039 lat2 = map.unproject(p).lat,
8040 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
8041 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
8043 if (isNaN(lngR) || lngR === 0) {
8044 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
8047 this._point = p.subtract(map.getPixelOrigin());
8048 this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
8049 this._radiusY = p.y - top.y;
8052 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
8054 this._point = map.latLngToLayerPoint(this._latlng);
8055 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
8058 this._updateBounds();
8062 // @factory L.circle(latlng: LatLng, options?: Circle options)
8063 // Instantiates a circle object given a geographical point, and an options object
8064 // which contains the circle radius.
8066 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
8067 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
8068 // Do not use in new applications or plugins.
8069 function circle(latlng, options, legacyOptions) {
8070 return new Circle(latlng, options, legacyOptions);
8078 * A class for drawing polyline overlays on a map. Extends `Path`.
8083 * // create a red polyline from an array of LatLng points
8090 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
8092 * // zoom the map to the polyline
8093 * map.fitBounds(polyline.getBounds());
8096 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
8099 * // create a red polyline from an array of arrays of LatLng points
8101 * [[45.51, -122.68],
8112 var Polyline = Path.extend({
8115 // @aka Polyline options
8117 // @option smoothFactor: Number = 1.0
8118 // How much to simplify the polyline on each zoom level. More means
8119 // better performance and smoother look, and less means more accurate representation.
8122 // @option noClip: Boolean = false
8123 // Disable polyline clipping.
8127 initialize: function (latlngs, options) {
8128 setOptions(this, options);
8129 this._setLatLngs(latlngs);
8132 // @method getLatLngs(): LatLng[]
8133 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8134 getLatLngs: function () {
8135 return this._latlngs;
8138 // @method setLatLngs(latlngs: LatLng[]): this
8139 // Replaces all the points in the polyline with the given array of geographical points.
8140 setLatLngs: function (latlngs) {
8141 this._setLatLngs(latlngs);
8142 return this.redraw();
8145 // @method isEmpty(): Boolean
8146 // Returns `true` if the Polyline has no LatLngs.
8147 isEmpty: function () {
8148 return !this._latlngs.length;
8151 // @method closestLayerPoint(p: Point): Point
8152 // Returns the point closest to `p` on the Polyline.
8153 closestLayerPoint: function (p) {
8154 var minDistance = Infinity,
8156 closest = _sqClosestPointOnSegment,
8159 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8160 var points = this._parts[j];
8162 for (var i = 1, len = points.length; i < len; i++) {
8166 var sqDist = closest(p, p1, p2, true);
8168 if (sqDist < minDistance) {
8169 minDistance = sqDist;
8170 minPoint = closest(p, p1, p2);
8175 minPoint.distance = Math.sqrt(minDistance);
8180 // @method getCenter(): LatLng
8181 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
8182 getCenter: function () {
8183 // throws error when not yet added to map as this center calculation requires projected coordinates
8185 throw new Error('Must add layer to map before using getCenter()');
8188 var i, halfDist, segDist, dist, p1, p2, ratio,
8189 points = this._rings[0],
8190 len = points.length;
8192 if (!len) { return null; }
8194 // polyline centroid algorithm; only uses the first ring if there are multiple
8196 for (i = 0, halfDist = 0; i < len - 1; i++) {
8197 halfDist += points[i].distanceTo(points[i + 1]) / 2;
8200 // The line is so small in the current view that all points are on the same pixel.
8201 if (halfDist === 0) {
8202 return this._map.layerPointToLatLng(points[0]);
8205 for (i = 0, dist = 0; i < len - 1; i++) {
8208 segDist = p1.distanceTo(p2);
8211 if (dist > halfDist) {
8212 ratio = (dist - halfDist) / segDist;
8213 return this._map.layerPointToLatLng([
8214 p2.x - ratio * (p2.x - p1.x),
8215 p2.y - ratio * (p2.y - p1.y)
8221 // @method getBounds(): LatLngBounds
8222 // Returns the `LatLngBounds` of the path.
8223 getBounds: function () {
8224 return this._bounds;
8227 // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
8228 // Adds a given point to the polyline. By default, adds to the first ring of
8229 // the polyline in case of a multi-polyline, but can be overridden by passing
8230 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8231 addLatLng: function (latlng, latlngs) {
8232 latlngs = latlngs || this._defaultShape();
8233 latlng = toLatLng(latlng);
8234 latlngs.push(latlng);
8235 this._bounds.extend(latlng);
8236 return this.redraw();
8239 _setLatLngs: function (latlngs) {
8240 this._bounds = new LatLngBounds();
8241 this._latlngs = this._convertLatLngs(latlngs);
8244 _defaultShape: function () {
8245 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
8248 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8249 _convertLatLngs: function (latlngs) {
8251 flat = isFlat(latlngs);
8253 for (var i = 0, len = latlngs.length; i < len; i++) {
8255 result[i] = toLatLng(latlngs[i]);
8256 this._bounds.extend(result[i]);
8258 result[i] = this._convertLatLngs(latlngs[i]);
8265 _project: function () {
8266 var pxBounds = new Bounds();
8268 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8270 var w = this._clickTolerance(),
8271 p = new Point(w, w);
8273 if (this._bounds.isValid() && pxBounds.isValid()) {
8274 pxBounds.min._subtract(p);
8275 pxBounds.max._add(p);
8276 this._pxBounds = pxBounds;
8280 // recursively turns latlngs into a set of rings with projected coordinates
8281 _projectLatlngs: function (latlngs, result, projectedBounds) {
8282 var flat = latlngs[0] instanceof LatLng,
8283 len = latlngs.length,
8288 for (i = 0; i < len; i++) {
8289 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8290 projectedBounds.extend(ring[i]);
8294 for (i = 0; i < len; i++) {
8295 this._projectLatlngs(latlngs[i], result, projectedBounds);
8300 // clip polyline by renderer bounds so that we have less to render for performance
8301 _clipPoints: function () {
8302 var bounds = this._renderer._bounds;
8305 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8309 if (this.options.noClip) {
8310 this._parts = this._rings;
8314 var parts = this._parts,
8315 i, j, k, len, len2, segment, points;
8317 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8318 points = this._rings[i];
8320 for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8321 segment = clipSegment(points[j], points[j + 1], bounds, j, true);
8323 if (!segment) { continue; }
8325 parts[k] = parts[k] || [];
8326 parts[k].push(segment[0]);
8328 // if segment goes out of screen, or it's the last one, it's the end of the line part
8329 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8330 parts[k].push(segment[1]);
8337 // simplify each clipped part of the polyline for performance
8338 _simplifyPoints: function () {
8339 var parts = this._parts,
8340 tolerance = this.options.smoothFactor;
8342 for (var i = 0, len = parts.length; i < len; i++) {
8343 parts[i] = simplify(parts[i], tolerance);
8347 _update: function () {
8348 if (!this._map) { return; }
8351 this._simplifyPoints();
8355 _updatePath: function () {
8356 this._renderer._updatePoly(this);
8359 // Needed by the `Canvas` renderer for interactivity
8360 _containsPoint: function (p, closed) {
8361 var i, j, k, len, len2, part,
8362 w = this._clickTolerance();
8364 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8366 // hit detection for polylines
8367 for (i = 0, len = this._parts.length; i < len; i++) {
8368 part = this._parts[i];
8370 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8371 if (!closed && (j === 0)) { continue; }
8373 if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
8382 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8383 // Instantiates a polyline object given an array of geographical points and
8384 // optionally an options object. You can create a `Polyline` object with
8385 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8386 // of geographic points.
8387 function polyline(latlngs, options) {
8388 return new Polyline(latlngs, options);
8391 // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
8392 Polyline._flat = _flat;
8397 * @inherits Polyline
8399 * A class for drawing polygon overlays on a map. Extends `Polyline`.
8401 * 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.
8407 * // create a red polygon from an array of LatLng points
8408 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
8410 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8412 * // zoom the map to the polygon
8413 * map.fitBounds(polygon.getBounds());
8416 * 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:
8420 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8421 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8425 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8429 * [ // first polygon
8430 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8431 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8433 * [ // second polygon
8434 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
8440 var Polygon = Polyline.extend({
8446 isEmpty: function () {
8447 return !this._latlngs.length || !this._latlngs[0].length;
8450 getCenter: function () {
8451 // throws error when not yet added to map as this center calculation requires projected coordinates
8453 throw new Error('Must add layer to map before using getCenter()');
8456 var i, j, p1, p2, f, area, x, y, center,
8457 points = this._rings[0],
8458 len = points.length;
8460 if (!len) { return null; }
8462 // polygon centroid algorithm; only uses the first ring if there are multiple
8466 for (i = 0, j = len - 1; i < len; j = i++) {
8470 f = p1.y * p2.x - p2.y * p1.x;
8471 x += (p1.x + p2.x) * f;
8472 y += (p1.y + p2.y) * f;
8477 // Polygon is so small that all points are on same pixel.
8480 center = [x / area, y / area];
8482 return this._map.layerPointToLatLng(center);
8485 _convertLatLngs: function (latlngs) {
8486 var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
8487 len = result.length;
8489 // remove last point if it equals first one
8490 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
8496 _setLatLngs: function (latlngs) {
8497 Polyline.prototype._setLatLngs.call(this, latlngs);
8498 if (isFlat(this._latlngs)) {
8499 this._latlngs = [this._latlngs];
8503 _defaultShape: function () {
8504 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8507 _clipPoints: function () {
8508 // polygons need a different clipping algorithm so we redefine that
8510 var bounds = this._renderer._bounds,
8511 w = this.options.weight,
8512 p = new Point(w, w);
8514 // increase clip padding by stroke width to avoid stroke on clip edges
8515 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
8518 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8522 if (this.options.noClip) {
8523 this._parts = this._rings;
8527 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8528 clipped = clipPolygon(this._rings[i], bounds, true);
8529 if (clipped.length) {
8530 this._parts.push(clipped);
8535 _updatePath: function () {
8536 this._renderer._updatePoly(this, true);
8539 // Needed by the `Canvas` renderer for interactivity
8540 _containsPoint: function (p) {
8542 part, p1, p2, i, j, k, len, len2;
8544 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8546 // ray casting algorithm for detecting if point is in polygon
8547 for (i = 0, len = this._parts.length; i < len; i++) {
8548 part = this._parts[i];
8550 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8554 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)) {
8560 // also check if it's on polygon stroke
8561 return inside || Polyline.prototype._containsPoint.call(this, p, true);
8567 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8568 function polygon(latlngs, options) {
8569 return new Polygon(latlngs, options);
8575 * @inherits FeatureGroup
8577 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
8578 * GeoJSON data and display it on the map. Extends `FeatureGroup`.
8584 * style: function (feature) {
8585 * return {color: feature.properties.color};
8587 * }).bindPopup(function (layer) {
8588 * return layer.feature.properties.description;
8593 var GeoJSON = FeatureGroup.extend({
8596 * @aka GeoJSON options
8598 * @option pointToLayer: Function = *
8599 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
8600 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
8601 * The default is to spawn a default `Marker`:
8603 * function(geoJsonPoint, latlng) {
8604 * return L.marker(latlng);
8608 * @option style: Function = *
8609 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
8610 * called internally when data is added.
8611 * The default value is to not override any defaults:
8613 * function (geoJsonFeature) {
8618 * @option onEachFeature: Function = *
8619 * A `Function` that will be called once for each created `Feature`, after it has
8620 * been created and styled. Useful for attaching events and popups to features.
8621 * The default is to do nothing with the newly created layers:
8623 * function (feature, layer) {}
8626 * @option filter: Function = *
8627 * A `Function` that will be used to decide whether to include a feature or not.
8628 * The default is to include all features:
8630 * function (geoJsonFeature) {
8634 * Note: dynamically changing the `filter` option will have effect only on newly
8635 * added data. It will _not_ re-evaluate already included features.
8637 * @option coordsToLatLng: Function = *
8638 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
8639 * The default is the `coordsToLatLng` static method.
8642 initialize: function (geojson, options) {
8643 setOptions(this, options);
8648 this.addData(geojson);
8652 // @method addData( <GeoJSON> data ): this
8653 // Adds a GeoJSON object to the layer.
8654 addData: function (geojson) {
8655 var features = isArray(geojson) ? geojson : geojson.features,
8659 for (i = 0, len = features.length; i < len; i++) {
8660 // only add this if geometry or geometries are set and not null
8661 feature = features[i];
8662 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
8663 this.addData(feature);
8669 var options = this.options;
8671 if (options.filter && !options.filter(geojson)) { return this; }
8673 var layer = geometryToLayer(geojson, options);
8677 layer.feature = asFeature(geojson);
8679 layer.defaultOptions = layer.options;
8680 this.resetStyle(layer);
8682 if (options.onEachFeature) {
8683 options.onEachFeature(geojson, layer);
8686 return this.addLayer(layer);
8689 // @method resetStyle( <Path> layer ): this
8690 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
8691 resetStyle: function (layer) {
8692 // reset any custom styles
8693 layer.options = extend({}, layer.defaultOptions);
8694 this._setLayerStyle(layer, this.options.style);
8698 // @method setStyle( <Function> style ): this
8699 // Changes styles of GeoJSON vector layers with the given style function.
8700 setStyle: function (style) {
8701 return this.eachLayer(function (layer) {
8702 this._setLayerStyle(layer, style);
8706 _setLayerStyle: function (layer, style) {
8707 if (typeof style === 'function') {
8708 style = style(layer.feature);
8710 if (layer.setStyle) {
8711 layer.setStyle(style);
8717 // There are several static functions which can be called without instantiating L.GeoJSON:
8719 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
8720 // Creates a `Layer` from a given GeoJSON feature. Can use a custom
8721 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
8722 // functions if provided as options.
8723 function geometryToLayer(geojson, options) {
8725 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
8726 coords = geometry ? geometry.coordinates : null,
8728 pointToLayer = options && options.pointToLayer,
8729 _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
8730 latlng, latlngs, i, len;
8732 if (!coords && !geometry) {
8736 switch (geometry.type) {
8738 latlng = _coordsToLatLng(coords);
8739 return pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng);
8742 for (i = 0, len = coords.length; i < len; i++) {
8743 latlng = _coordsToLatLng(coords[i]);
8744 layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng));
8746 return new FeatureGroup(layers);
8749 case 'MultiLineString':
8750 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
8751 return new Polyline(latlngs, options);
8754 case 'MultiPolygon':
8755 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
8756 return new Polygon(latlngs, options);
8758 case 'GeometryCollection':
8759 for (i = 0, len = geometry.geometries.length; i < len; i++) {
8760 var layer = geometryToLayer({
8761 geometry: geometry.geometries[i],
8763 properties: geojson.properties
8770 return new FeatureGroup(layers);
8773 throw new Error('Invalid GeoJSON object.');
8777 // @function coordsToLatLng(coords: Array): LatLng
8778 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
8779 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
8780 function coordsToLatLng(coords) {
8781 return new LatLng(coords[1], coords[0], coords[2]);
8784 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
8785 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
8786 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
8787 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
8788 function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
8791 for (var i = 0, len = coords.length, latlng; i < len; i++) {
8792 latlng = levelsDeep ?
8793 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
8794 (_coordsToLatLng || coordsToLatLng)(coords[i]);
8796 latlngs.push(latlng);
8802 // @function latLngToCoords(latlng: LatLng, precision?: Number): Array
8803 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
8804 function latLngToCoords(latlng, precision) {
8805 precision = typeof precision === 'number' ? precision : 6;
8806 return latlng.alt !== undefined ?
8807 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
8808 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
8811 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
8812 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
8813 // `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.
8814 function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
8817 for (var i = 0, len = latlngs.length; i < len; i++) {
8818 coords.push(levelsDeep ?
8819 latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) :
8820 latLngToCoords(latlngs[i], precision));
8823 if (!levelsDeep && closed) {
8824 coords.push(coords[0]);
8830 function getFeature(layer, newGeometry) {
8831 return layer.feature ?
8832 extend({}, layer.feature, {geometry: newGeometry}) :
8833 asFeature(newGeometry);
8836 // @function asFeature(geojson: Object): Object
8837 // Normalize GeoJSON geometries/features into GeoJSON features.
8838 function asFeature(geojson) {
8839 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
8850 var PointToGeoJSON = {
8851 toGeoJSON: function (precision) {
8852 return getFeature(this, {
8854 coordinates: latLngToCoords(this.getLatLng(), precision)
8859 // @namespace Marker
8860 // @method toGeoJSON(): Object
8861 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
8862 Marker.include(PointToGeoJSON);
8864 // @namespace CircleMarker
8865 // @method toGeoJSON(): Object
8866 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
8867 Circle.include(PointToGeoJSON);
8868 CircleMarker.include(PointToGeoJSON);
8871 // @namespace Polyline
8872 // @method toGeoJSON(): Object
8873 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
8875 toGeoJSON: function (precision) {
8876 var multi = !isFlat(this._latlngs);
8878 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
8880 return getFeature(this, {
8881 type: (multi ? 'Multi' : '') + 'LineString',
8887 // @namespace Polygon
8888 // @method toGeoJSON(): Object
8889 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
8891 toGeoJSON: function (precision) {
8892 var holes = !isFlat(this._latlngs),
8893 multi = holes && !isFlat(this._latlngs[0]);
8895 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
8901 return getFeature(this, {
8902 type: (multi ? 'Multi' : '') + 'Polygon',
8909 // @namespace LayerGroup
8910 LayerGroup.include({
8911 toMultiPoint: function (precision) {
8914 this.eachLayer(function (layer) {
8915 coords.push(layer.toGeoJSON(precision).geometry.coordinates);
8918 return getFeature(this, {
8924 // @method toGeoJSON(): Object
8925 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
8926 toGeoJSON: function (precision) {
8928 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
8930 if (type === 'MultiPoint') {
8931 return this.toMultiPoint(precision);
8934 var isGeometryCollection = type === 'GeometryCollection',
8937 this.eachLayer(function (layer) {
8938 if (layer.toGeoJSON) {
8939 var json = layer.toGeoJSON(precision);
8940 if (isGeometryCollection) {
8941 jsons.push(json.geometry);
8943 var feature = asFeature(json);
8944 // Squash nested feature collections
8945 if (feature.type === 'FeatureCollection') {
8946 jsons.push.apply(jsons, feature.features);
8948 jsons.push(feature);
8954 if (isGeometryCollection) {
8955 return getFeature(this, {
8957 type: 'GeometryCollection'
8962 type: 'FeatureCollection',
8968 // @namespace GeoJSON
8969 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
8970 // Creates a GeoJSON layer. Optionally accepts an object in
8971 // [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
8972 // (you can alternatively add it later with `addData` method) and an `options` object.
8973 function geoJSON(geojson, options) {
8974 return new GeoJSON(geojson, options);
8977 // Backward compatibility.
8978 var geoJson = geoJSON;
8981 * @class ImageOverlay
8982 * @aka L.ImageOverlay
8983 * @inherits Interactive layer
8985 * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
8990 * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
8991 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
8992 * L.imageOverlay(imageUrl, imageBounds).addTo(map);
8996 var ImageOverlay = Layer.extend({
8999 // @aka ImageOverlay options
9001 // @option opacity: Number = 1.0
9002 // The opacity of the image overlay.
9005 // @option alt: String = ''
9006 // Text for the `alt` attribute of the image (useful for accessibility).
9009 // @option interactive: Boolean = false
9010 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
9013 // @option crossOrigin: Boolean|String = false
9014 // Whether the crossOrigin attribute will be added to the image.
9015 // 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.
9016 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
9019 // @option errorOverlayUrl: String = ''
9020 // URL to the overlay image to show in place of the overlay that failed to load.
9021 errorOverlayUrl: '',
9023 // @option zIndex: Number = 1
9024 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
9027 // @option className: String = ''
9028 // A custom class name to assign to the image. Empty by default.
9032 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
9034 this._bounds = toLatLngBounds(bounds);
9036 setOptions(this, options);
9039 onAdd: function () {
9043 if (this.options.opacity < 1) {
9044 this._updateOpacity();
9048 if (this.options.interactive) {
9049 addClass(this._image, 'leaflet-interactive');
9050 this.addInteractiveTarget(this._image);
9053 this.getPane().appendChild(this._image);
9057 onRemove: function () {
9058 remove(this._image);
9059 if (this.options.interactive) {
9060 this.removeInteractiveTarget(this._image);
9064 // @method setOpacity(opacity: Number): this
9065 // Sets the opacity of the overlay.
9066 setOpacity: function (opacity) {
9067 this.options.opacity = opacity;
9070 this._updateOpacity();
9075 setStyle: function (styleOpts) {
9076 if (styleOpts.opacity) {
9077 this.setOpacity(styleOpts.opacity);
9082 // @method bringToFront(): this
9083 // Brings the layer to the top of all overlays.
9084 bringToFront: function () {
9086 toFront(this._image);
9091 // @method bringToBack(): this
9092 // Brings the layer to the bottom of all overlays.
9093 bringToBack: function () {
9095 toBack(this._image);
9100 // @method setUrl(url: String): this
9101 // Changes the URL of the image.
9102 setUrl: function (url) {
9106 this._image.src = url;
9111 // @method setBounds(bounds: LatLngBounds): this
9112 // Update the bounds that this ImageOverlay covers
9113 setBounds: function (bounds) {
9114 this._bounds = toLatLngBounds(bounds);
9122 getEvents: function () {
9125 viewreset: this._reset
9128 if (this._zoomAnimated) {
9129 events.zoomanim = this._animateZoom;
9135 // @method setZIndex(value: Number): this
9136 // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
9137 setZIndex: function (value) {
9138 this.options.zIndex = value;
9139 this._updateZIndex();
9143 // @method getBounds(): LatLngBounds
9144 // Get the bounds that this ImageOverlay covers
9145 getBounds: function () {
9146 return this._bounds;
9149 // @method getElement(): HTMLElement
9150 // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
9151 // used by this overlay.
9152 getElement: function () {
9156 _initImage: function () {
9157 var wasElementSupplied = this._url.tagName === 'IMG';
9158 var img = this._image = wasElementSupplied ? this._url : create$1('img');
9160 addClass(img, 'leaflet-image-layer');
9161 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
9162 if (this.options.className) { addClass(img, this.options.className); }
9164 img.onselectstart = falseFn;
9165 img.onmousemove = falseFn;
9167 // @event load: Event
9168 // Fired when the ImageOverlay layer has loaded its image
9169 img.onload = bind(this.fire, this, 'load');
9170 img.onerror = bind(this._overlayOnError, this, 'error');
9172 if (this.options.crossOrigin || this.options.crossOrigin === '') {
9173 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
9176 if (this.options.zIndex) {
9177 this._updateZIndex();
9180 if (wasElementSupplied) {
9181 this._url = img.src;
9185 img.src = this._url;
9186 img.alt = this.options.alt;
9189 _animateZoom: function (e) {
9190 var scale = this._map.getZoomScale(e.zoom),
9191 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
9193 setTransform(this._image, offset, scale);
9196 _reset: function () {
9197 var image = this._image,
9198 bounds = new Bounds(
9199 this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
9200 this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
9201 size = bounds.getSize();
9203 setPosition(image, bounds.min);
9205 image.style.width = size.x + 'px';
9206 image.style.height = size.y + 'px';
9209 _updateOpacity: function () {
9210 setOpacity(this._image, this.options.opacity);
9213 _updateZIndex: function () {
9214 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
9215 this._image.style.zIndex = this.options.zIndex;
9219 _overlayOnError: function () {
9220 // @event error: Event
9221 // Fired when the ImageOverlay layer fails to load its image
9224 var errorUrl = this.options.errorOverlayUrl;
9225 if (errorUrl && this._url !== errorUrl) {
9226 this._url = errorUrl;
9227 this._image.src = errorUrl;
9232 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
9233 // Instantiates an image overlay object given the URL of the image and the
9234 // geographical bounds it is tied to.
9235 var imageOverlay = function (url, bounds, options) {
9236 return new ImageOverlay(url, bounds, options);
9240 * @class VideoOverlay
9241 * @aka L.VideoOverlay
9242 * @inherits ImageOverlay
9244 * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
9246 * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
9252 * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
9253 * videoBounds = [[ 32, -130], [ 13, -100]];
9254 * L.videoOverlay(videoUrl, videoBounds ).addTo(map);
9258 var VideoOverlay = ImageOverlay.extend({
9261 // @aka VideoOverlay options
9263 // @option autoplay: Boolean = true
9264 // Whether the video starts playing automatically when loaded.
9267 // @option loop: Boolean = true
9268 // Whether the video will loop back to the beginning when played.
9272 _initImage: function () {
9273 var wasElementSupplied = this._url.tagName === 'VIDEO';
9274 var vid = this._image = wasElementSupplied ? this._url : create$1('video');
9276 addClass(vid, 'leaflet-image-layer');
9277 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
9279 vid.onselectstart = falseFn;
9280 vid.onmousemove = falseFn;
9282 // @event load: Event
9283 // Fired when the video has finished loading the first frame
9284 vid.onloadeddata = bind(this.fire, this, 'load');
9286 if (wasElementSupplied) {
9287 var sourceElements = vid.getElementsByTagName('source');
9289 for (var j = 0; j < sourceElements.length; j++) {
9290 sources.push(sourceElements[j].src);
9293 this._url = (sourceElements.length > 0) ? sources : [vid.src];
9297 if (!isArray(this._url)) { this._url = [this._url]; }
9299 vid.autoplay = !!this.options.autoplay;
9300 vid.loop = !!this.options.loop;
9301 for (var i = 0; i < this._url.length; i++) {
9302 var source = create$1('source');
9303 source.src = this._url[i];
9304 vid.appendChild(source);
9308 // @method getElement(): HTMLVideoElement
9309 // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
9310 // used by this overlay.
9314 // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
9315 // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
9316 // geographical bounds it is tied to.
9318 function videoOverlay(video, bounds, options) {
9319 return new VideoOverlay(video, bounds, options);
9326 * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
9329 // @namespace DivOverlay
9330 var DivOverlay = Layer.extend({
9333 // @aka DivOverlay options
9335 // @option offset: Point = Point(0, 7)
9336 // The offset of the popup position. Useful to control the anchor
9337 // of the popup when opening it on some overlays.
9340 // @option className: String = ''
9341 // A custom CSS class name to assign to the popup.
9344 // @option pane: String = 'popupPane'
9345 // `Map pane` where the popup will be added.
9349 initialize: function (options, source) {
9350 setOptions(this, options);
9352 this._source = source;
9355 onAdd: function (map) {
9356 this._zoomAnimated = map._zoomAnimated;
9358 if (!this._container) {
9362 if (map._fadeAnimated) {
9363 setOpacity(this._container, 0);
9366 clearTimeout(this._removeTimeout);
9367 this.getPane().appendChild(this._container);
9370 if (map._fadeAnimated) {
9371 setOpacity(this._container, 1);
9374 this.bringToFront();
9377 onRemove: function (map) {
9378 if (map._fadeAnimated) {
9379 setOpacity(this._container, 0);
9380 this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
9382 remove(this._container);
9387 // @method getLatLng: LatLng
9388 // Returns the geographical point of popup.
9389 getLatLng: function () {
9390 return this._latlng;
9393 // @method setLatLng(latlng: LatLng): this
9394 // Sets the geographical point where the popup will open.
9395 setLatLng: function (latlng) {
9396 this._latlng = toLatLng(latlng);
9398 this._updatePosition();
9404 // @method getContent: String|HTMLElement
9405 // Returns the content of the popup.
9406 getContent: function () {
9407 return this._content;
9410 // @method setContent(htmlContent: String|HTMLElement|Function): this
9411 // 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.
9412 setContent: function (content) {
9413 this._content = content;
9418 // @method getElement: String|HTMLElement
9419 // Alias for [getContent()](#popup-getcontent)
9420 getElement: function () {
9421 return this._container;
9424 // @method update: null
9425 // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
9426 update: function () {
9427 if (!this._map) { return; }
9429 this._container.style.visibility = 'hidden';
9431 this._updateContent();
9432 this._updateLayout();
9433 this._updatePosition();
9435 this._container.style.visibility = '';
9440 getEvents: function () {
9442 zoom: this._updatePosition,
9443 viewreset: this._updatePosition
9446 if (this._zoomAnimated) {
9447 events.zoomanim = this._animateZoom;
9452 // @method isOpen: Boolean
9453 // Returns `true` when the popup is visible on the map.
9454 isOpen: function () {
9455 return !!this._map && this._map.hasLayer(this);
9458 // @method bringToFront: this
9459 // Brings this popup in front of other popups (in the same map pane).
9460 bringToFront: function () {
9462 toFront(this._container);
9467 // @method bringToBack: this
9468 // Brings this popup to the back of other popups (in the same map pane).
9469 bringToBack: function () {
9471 toBack(this._container);
9476 _updateContent: function () {
9477 if (!this._content) { return; }
9479 var node = this._contentNode;
9480 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
9482 if (typeof content === 'string') {
9483 node.innerHTML = content;
9485 while (node.hasChildNodes()) {
9486 node.removeChild(node.firstChild);
9488 node.appendChild(content);
9490 this.fire('contentupdate');
9493 _updatePosition: function () {
9494 if (!this._map) { return; }
9496 var pos = this._map.latLngToLayerPoint(this._latlng),
9497 offset = toPoint(this.options.offset),
9498 anchor = this._getAnchor();
9500 if (this._zoomAnimated) {
9501 setPosition(this._container, pos.add(anchor));
9503 offset = offset.add(pos).add(anchor);
9506 var bottom = this._containerBottom = -offset.y,
9507 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
9509 // bottom position the popup in case the height of the popup changes (images loading etc)
9510 this._container.style.bottom = bottom + 'px';
9511 this._container.style.left = left + 'px';
9514 _getAnchor: function () {
9522 * @inherits DivOverlay
9524 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
9525 * open popups while making sure that only one popup is open at one time
9526 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
9530 * If you want to just bind a popup to marker click and then open it, it's really easy:
9533 * marker.bindPopup(popupContent).openPopup();
9535 * Path overlays like polylines also have a `bindPopup` method.
9536 * Here's a more complicated way to open a popup on a map:
9539 * var popup = L.popup()
9540 * .setLatLng(latlng)
9541 * .setContent('<p>Hello world!<br />This is a nice popup.</p>')
9548 var Popup = DivOverlay.extend({
9551 // @aka Popup options
9553 // @option maxWidth: Number = 300
9554 // Max width of the popup, in pixels.
9557 // @option minWidth: Number = 50
9558 // Min width of the popup, in pixels.
9561 // @option maxHeight: Number = null
9562 // If set, creates a scrollable container of the given height
9563 // inside a popup if its content exceeds it.
9566 // @option autoPan: Boolean = true
9567 // Set it to `false` if you don't want the map to do panning animation
9568 // to fit the opened popup.
9571 // @option autoPanPaddingTopLeft: Point = null
9572 // The margin between the popup and the top left corner of the map
9573 // view after autopanning was performed.
9574 autoPanPaddingTopLeft: null,
9576 // @option autoPanPaddingBottomRight: Point = null
9577 // The margin between the popup and the bottom right corner of the map
9578 // view after autopanning was performed.
9579 autoPanPaddingBottomRight: null,
9581 // @option autoPanPadding: Point = Point(5, 5)
9582 // Equivalent of setting both top left and bottom right autopan padding to the same value.
9583 autoPanPadding: [5, 5],
9585 // @option keepInView: Boolean = false
9586 // Set it to `true` if you want to prevent users from panning the popup
9587 // off of the screen while it is open.
9590 // @option closeButton: Boolean = true
9591 // Controls the presence of a close button in the popup.
9594 // @option autoClose: Boolean = true
9595 // Set it to `false` if you want to override the default behavior of
9596 // the popup closing when another popup is opened.
9599 // @option closeOnEscapeKey: Boolean = true
9600 // Set it to `false` if you want to override the default behavior of
9601 // the ESC key for closing of the popup.
9602 closeOnEscapeKey: true,
9604 // @option closeOnClick: Boolean = *
9605 // Set it if you want to override the default behavior of the popup closing when user clicks
9606 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
9608 // @option className: String = ''
9609 // A custom CSS class name to assign to the popup.
9614 // @method openOn(map: Map): this
9615 // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
9616 openOn: function (map) {
9617 map.openPopup(this);
9621 onAdd: function (map) {
9622 DivOverlay.prototype.onAdd.call(this, map);
9625 // @section Popup events
9626 // @event popupopen: PopupEvent
9627 // Fired when a popup is opened in the map
9628 map.fire('popupopen', {popup: this});
9632 // @section Popup events
9633 // @event popupopen: PopupEvent
9634 // Fired when a popup bound to this layer is opened
9635 this._source.fire('popupopen', {popup: this}, true);
9636 // For non-path layers, we toggle the popup when clicking
9637 // again the layer, so prevent the map to reopen it.
9638 if (!(this._source instanceof Path)) {
9639 this._source.on('preclick', stopPropagation);
9644 onRemove: function (map) {
9645 DivOverlay.prototype.onRemove.call(this, map);
9648 // @section Popup events
9649 // @event popupclose: PopupEvent
9650 // Fired when a popup in the map is closed
9651 map.fire('popupclose', {popup: this});
9655 // @section Popup events
9656 // @event popupclose: PopupEvent
9657 // Fired when a popup bound to this layer is closed
9658 this._source.fire('popupclose', {popup: this}, true);
9659 if (!(this._source instanceof Path)) {
9660 this._source.off('preclick', stopPropagation);
9665 getEvents: function () {
9666 var events = DivOverlay.prototype.getEvents.call(this);
9668 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
9669 events.preclick = this._close;
9672 if (this.options.keepInView) {
9673 events.moveend = this._adjustPan;
9679 _close: function () {
9681 this._map.closePopup(this);
9685 _initLayout: function () {
9686 var prefix = 'leaflet-popup',
9687 container = this._container = create$1('div',
9688 prefix + ' ' + (this.options.className || '') +
9689 ' leaflet-zoom-animated');
9691 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
9692 this._contentNode = create$1('div', prefix + '-content', wrapper);
9694 disableClickPropagation(wrapper);
9695 disableScrollPropagation(this._contentNode);
9696 on(wrapper, 'contextmenu', stopPropagation);
9698 this._tipContainer = create$1('div', prefix + '-tip-container', container);
9699 this._tip = create$1('div', prefix + '-tip', this._tipContainer);
9701 if (this.options.closeButton) {
9702 var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
9703 closeButton.href = '#close';
9704 closeButton.innerHTML = '×';
9706 on(closeButton, 'click', this._onCloseButtonClick, this);
9710 _updateLayout: function () {
9711 var container = this._contentNode,
9712 style = container.style;
9715 style.whiteSpace = 'nowrap';
9717 var width = container.offsetWidth;
9718 width = Math.min(width, this.options.maxWidth);
9719 width = Math.max(width, this.options.minWidth);
9721 style.width = (width + 1) + 'px';
9722 style.whiteSpace = '';
9726 var height = container.offsetHeight,
9727 maxHeight = this.options.maxHeight,
9728 scrolledClass = 'leaflet-popup-scrolled';
9730 if (maxHeight && height > maxHeight) {
9731 style.height = maxHeight + 'px';
9732 addClass(container, scrolledClass);
9734 removeClass(container, scrolledClass);
9737 this._containerWidth = this._container.offsetWidth;
9740 _animateZoom: function (e) {
9741 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
9742 anchor = this._getAnchor();
9743 setPosition(this._container, pos.add(anchor));
9746 _adjustPan: function () {
9747 if (!this.options.autoPan) { return; }
9748 if (this._map._panAnim) { this._map._panAnim.stop(); }
9750 var map = this._map,
9751 marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
9752 containerHeight = this._container.offsetHeight + marginBottom,
9753 containerWidth = this._containerWidth,
9754 layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
9756 layerPos._add(getPosition(this._container));
9758 var containerPos = map.layerPointToContainerPoint(layerPos),
9759 padding = toPoint(this.options.autoPanPadding),
9760 paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
9761 paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
9762 size = map.getSize(),
9766 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
9767 dx = containerPos.x + containerWidth - size.x + paddingBR.x;
9769 if (containerPos.x - dx - paddingTL.x < 0) { // left
9770 dx = containerPos.x - paddingTL.x;
9772 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
9773 dy = containerPos.y + containerHeight - size.y + paddingBR.y;
9775 if (containerPos.y - dy - paddingTL.y < 0) { // top
9776 dy = containerPos.y - paddingTL.y;
9780 // @section Popup events
9781 // @event autopanstart: Event
9782 // Fired when the map starts autopanning when opening a popup.
9785 .fire('autopanstart')
9790 _onCloseButtonClick: function (e) {
9795 _getAnchor: function () {
9796 // Where should we anchor the popup on the source layer?
9797 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
9803 // @factory L.popup(options?: Popup options, source?: Layer)
9804 // 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.
9805 var popup = function (options, source) {
9806 return new Popup(options, source);
9811 * @section Interaction Options
9812 * @option closePopupOnClick: Boolean = true
9813 * Set it to `false` if you don't want popups to close when user clicks the map.
9816 closePopupOnClick: true
9821 // @section Methods for Layers and Controls
9823 // @method openPopup(popup: Popup): this
9824 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
9826 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
9827 // Creates a popup with the specified content and options and opens it in the given point on a map.
9828 openPopup: function (popup, latlng, options) {
9829 if (!(popup instanceof Popup)) {
9830 popup = new Popup(options).setContent(popup);
9834 popup.setLatLng(latlng);
9837 if (this.hasLayer(popup)) {
9841 if (this._popup && this._popup.options.autoClose) {
9845 this._popup = popup;
9846 return this.addLayer(popup);
9849 // @method closePopup(popup?: Popup): this
9850 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
9851 closePopup: function (popup) {
9852 if (!popup || popup === this._popup) {
9853 popup = this._popup;
9857 this.removeLayer(popup);
9865 * @section Popup methods example
9867 * All layers share a set of methods convenient for binding popups to it.
9870 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
9871 * layer.openPopup();
9872 * layer.closePopup();
9875 * 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.
9878 // @section Popup methods
9881 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
9882 // Binds a popup to the layer with the passed `content` and sets up the
9883 // necessary event listeners. If a `Function` is passed it will receive
9884 // the layer as the first argument and should return a `String` or `HTMLElement`.
9885 bindPopup: function (content, options) {
9887 if (content instanceof Popup) {
9888 setOptions(content, options);
9889 this._popup = content;
9890 content._source = this;
9892 if (!this._popup || options) {
9893 this._popup = new Popup(options, this);
9895 this._popup.setContent(content);
9898 if (!this._popupHandlersAdded) {
9900 click: this._openPopup,
9901 keypress: this._onKeyPress,
9902 remove: this.closePopup,
9903 move: this._movePopup
9905 this._popupHandlersAdded = true;
9911 // @method unbindPopup(): this
9912 // Removes the popup previously bound with `bindPopup`.
9913 unbindPopup: function () {
9916 click: this._openPopup,
9917 keypress: this._onKeyPress,
9918 remove: this.closePopup,
9919 move: this._movePopup
9921 this._popupHandlersAdded = false;
9927 // @method openPopup(latlng?: LatLng): this
9928 // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
9929 openPopup: function (layer, latlng) {
9930 if (!(layer instanceof Layer)) {
9935 if (layer instanceof FeatureGroup) {
9936 for (var id in this._layers) {
9937 layer = this._layers[id];
9943 latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
9946 if (this._popup && this._map) {
9947 // set popup source to this layer
9948 this._popup._source = layer;
9950 // update the popup (content, layout, ect...)
9951 this._popup.update();
9953 // open the popup on the map
9954 this._map.openPopup(this._popup, latlng);
9960 // @method closePopup(): this
9961 // Closes the popup bound to this layer if it is open.
9962 closePopup: function () {
9964 this._popup._close();
9969 // @method togglePopup(): this
9970 // Opens or closes the popup bound to this layer depending on its current state.
9971 togglePopup: function (target) {
9973 if (this._popup._map) {
9976 this.openPopup(target);
9982 // @method isPopupOpen(): boolean
9983 // Returns `true` if the popup bound to this layer is currently open.
9984 isPopupOpen: function () {
9985 return (this._popup ? this._popup.isOpen() : false);
9988 // @method setPopupContent(content: String|HTMLElement|Popup): this
9989 // Sets the content of the popup bound to this layer.
9990 setPopupContent: function (content) {
9992 this._popup.setContent(content);
9997 // @method getPopup(): Popup
9998 // Returns the popup bound to this layer.
9999 getPopup: function () {
10000 return this._popup;
10003 _openPopup: function (e) {
10004 var layer = e.layer || e.target;
10006 if (!this._popup) {
10014 // prevent map click
10017 // if this inherits from Path its a vector and we can just
10018 // open the popup at the new location
10019 if (layer instanceof Path) {
10020 this.openPopup(e.layer || e.target, e.latlng);
10024 // otherwise treat it like a marker and figure out
10025 // if we should toggle it open/closed
10026 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
10029 this.openPopup(layer, e.latlng);
10033 _movePopup: function (e) {
10034 this._popup.setLatLng(e.latlng);
10037 _onKeyPress: function (e) {
10038 if (e.originalEvent.keyCode === 13) {
10039 this._openPopup(e);
10046 * @inherits DivOverlay
10048 * Used to display small texts on top of map layers.
10053 * marker.bindTooltip("my tooltip text").openTooltip();
10055 * Note about tooltip offset. Leaflet takes two options in consideration
10056 * for computing tooltip offsetting:
10057 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
10058 * Add a positive x offset to move the tooltip to the right, and a positive y offset to
10059 * move it to the bottom. Negatives will move to the left and top.
10060 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
10061 * should adapt this value if you use a custom icon.
10065 // @namespace Tooltip
10066 var Tooltip = DivOverlay.extend({
10069 // @aka Tooltip options
10071 // @option pane: String = 'tooltipPane'
10072 // `Map pane` where the tooltip will be added.
10073 pane: 'tooltipPane',
10075 // @option offset: Point = Point(0, 0)
10076 // Optional offset of the tooltip position.
10079 // @option direction: String = 'auto'
10080 // Direction where to open the tooltip. Possible values are: `right`, `left`,
10081 // `top`, `bottom`, `center`, `auto`.
10082 // `auto` will dynamically switch between `right` and `left` according to the tooltip
10083 // position on the map.
10086 // @option permanent: Boolean = false
10087 // Whether to open the tooltip permanently or only on mouseover.
10090 // @option sticky: Boolean = false
10091 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
10094 // @option interactive: Boolean = false
10095 // If true, the tooltip will listen to the feature events.
10096 interactive: false,
10098 // @option opacity: Number = 0.9
10099 // Tooltip container opacity.
10103 onAdd: function (map) {
10104 DivOverlay.prototype.onAdd.call(this, map);
10105 this.setOpacity(this.options.opacity);
10108 // @section Tooltip events
10109 // @event tooltipopen: TooltipEvent
10110 // Fired when a tooltip is opened in the map.
10111 map.fire('tooltipopen', {tooltip: this});
10113 if (this._source) {
10114 // @namespace Layer
10115 // @section Tooltip events
10116 // @event tooltipopen: TooltipEvent
10117 // Fired when a tooltip bound to this layer is opened.
10118 this._source.fire('tooltipopen', {tooltip: this}, true);
10122 onRemove: function (map) {
10123 DivOverlay.prototype.onRemove.call(this, map);
10126 // @section Tooltip events
10127 // @event tooltipclose: TooltipEvent
10128 // Fired when a tooltip in the map is closed.
10129 map.fire('tooltipclose', {tooltip: this});
10131 if (this._source) {
10132 // @namespace Layer
10133 // @section Tooltip events
10134 // @event tooltipclose: TooltipEvent
10135 // Fired when a tooltip bound to this layer is closed.
10136 this._source.fire('tooltipclose', {tooltip: this}, true);
10140 getEvents: function () {
10141 var events = DivOverlay.prototype.getEvents.call(this);
10143 if (touch && !this.options.permanent) {
10144 events.preclick = this._close;
10150 _close: function () {
10152 this._map.closeTooltip(this);
10156 _initLayout: function () {
10157 var prefix = 'leaflet-tooltip',
10158 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
10160 this._contentNode = this._container = create$1('div', className);
10163 _updateLayout: function () {},
10165 _adjustPan: function () {},
10167 _setPosition: function (pos) {
10168 var map = this._map,
10169 container = this._container,
10170 centerPoint = map.latLngToContainerPoint(map.getCenter()),
10171 tooltipPoint = map.layerPointToContainerPoint(pos),
10172 direction = this.options.direction,
10173 tooltipWidth = container.offsetWidth,
10174 tooltipHeight = container.offsetHeight,
10175 offset = toPoint(this.options.offset),
10176 anchor = this._getAnchor();
10178 if (direction === 'top') {
10179 pos = pos.add(toPoint(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true));
10180 } else if (direction === 'bottom') {
10181 pos = pos.subtract(toPoint(tooltipWidth / 2 - offset.x, -offset.y, true));
10182 } else if (direction === 'center') {
10183 pos = pos.subtract(toPoint(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true));
10184 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
10185 direction = 'right';
10186 pos = pos.add(toPoint(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true));
10188 direction = 'left';
10189 pos = pos.subtract(toPoint(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true));
10192 removeClass(container, 'leaflet-tooltip-right');
10193 removeClass(container, 'leaflet-tooltip-left');
10194 removeClass(container, 'leaflet-tooltip-top');
10195 removeClass(container, 'leaflet-tooltip-bottom');
10196 addClass(container, 'leaflet-tooltip-' + direction);
10197 setPosition(container, pos);
10200 _updatePosition: function () {
10201 var pos = this._map.latLngToLayerPoint(this._latlng);
10202 this._setPosition(pos);
10205 setOpacity: function (opacity) {
10206 this.options.opacity = opacity;
10208 if (this._container) {
10209 setOpacity(this._container, opacity);
10213 _animateZoom: function (e) {
10214 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
10215 this._setPosition(pos);
10218 _getAnchor: function () {
10219 // Where should we anchor the tooltip on the source layer?
10220 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
10225 // @namespace Tooltip
10226 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
10227 // 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.
10228 var tooltip = function (options, source) {
10229 return new Tooltip(options, source);
10233 // @section Methods for Layers and Controls
10236 // @method openTooltip(tooltip: Tooltip): this
10237 // Opens the specified tooltip.
10239 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
10240 // Creates a tooltip with the specified content and options and open it.
10241 openTooltip: function (tooltip, latlng, options) {
10242 if (!(tooltip instanceof Tooltip)) {
10243 tooltip = new Tooltip(options).setContent(tooltip);
10247 tooltip.setLatLng(latlng);
10250 if (this.hasLayer(tooltip)) {
10254 return this.addLayer(tooltip);
10257 // @method closeTooltip(tooltip?: Tooltip): this
10258 // Closes the tooltip given as parameter.
10259 closeTooltip: function (tooltip) {
10261 this.removeLayer(tooltip);
10270 * @section Tooltip methods example
10272 * All layers share a set of methods convenient for binding tooltips to it.
10275 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
10276 * layer.openTooltip();
10277 * layer.closeTooltip();
10281 // @section Tooltip methods
10284 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
10285 // Binds a tooltip to the layer with the passed `content` and sets up the
10286 // necessary event listeners. If a `Function` is passed it will receive
10287 // the layer as the first argument and should return a `String` or `HTMLElement`.
10288 bindTooltip: function (content, options) {
10290 if (content instanceof Tooltip) {
10291 setOptions(content, options);
10292 this._tooltip = content;
10293 content._source = this;
10295 if (!this._tooltip || options) {
10296 this._tooltip = new Tooltip(options, this);
10298 this._tooltip.setContent(content);
10302 this._initTooltipInteractions();
10304 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
10305 this.openTooltip();
10311 // @method unbindTooltip(): this
10312 // Removes the tooltip previously bound with `bindTooltip`.
10313 unbindTooltip: function () {
10314 if (this._tooltip) {
10315 this._initTooltipInteractions(true);
10316 this.closeTooltip();
10317 this._tooltip = null;
10322 _initTooltipInteractions: function (remove$$1) {
10323 if (!remove$$1 && this._tooltipHandlersAdded) { return; }
10324 var onOff = remove$$1 ? 'off' : 'on',
10326 remove: this.closeTooltip,
10327 move: this._moveTooltip
10329 if (!this._tooltip.options.permanent) {
10330 events.mouseover = this._openTooltip;
10331 events.mouseout = this.closeTooltip;
10332 if (this._tooltip.options.sticky) {
10333 events.mousemove = this._moveTooltip;
10336 events.click = this._openTooltip;
10339 events.add = this._openTooltip;
10341 this[onOff](events);
10342 this._tooltipHandlersAdded = !remove$$1;
10345 // @method openTooltip(latlng?: LatLng): this
10346 // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
10347 openTooltip: function (layer, latlng) {
10348 if (!(layer instanceof Layer)) {
10353 if (layer instanceof FeatureGroup) {
10354 for (var id in this._layers) {
10355 layer = this._layers[id];
10361 latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
10364 if (this._tooltip && this._map) {
10366 // set tooltip source to this layer
10367 this._tooltip._source = layer;
10369 // update the tooltip (content, layout, ect...)
10370 this._tooltip.update();
10372 // open the tooltip on the map
10373 this._map.openTooltip(this._tooltip, latlng);
10375 // Tooltip container may not be defined if not permanent and never
10377 if (this._tooltip.options.interactive && this._tooltip._container) {
10378 addClass(this._tooltip._container, 'leaflet-clickable');
10379 this.addInteractiveTarget(this._tooltip._container);
10386 // @method closeTooltip(): this
10387 // Closes the tooltip bound to this layer if it is open.
10388 closeTooltip: function () {
10389 if (this._tooltip) {
10390 this._tooltip._close();
10391 if (this._tooltip.options.interactive && this._tooltip._container) {
10392 removeClass(this._tooltip._container, 'leaflet-clickable');
10393 this.removeInteractiveTarget(this._tooltip._container);
10399 // @method toggleTooltip(): this
10400 // Opens or closes the tooltip bound to this layer depending on its current state.
10401 toggleTooltip: function (target) {
10402 if (this._tooltip) {
10403 if (this._tooltip._map) {
10404 this.closeTooltip();
10406 this.openTooltip(target);
10412 // @method isTooltipOpen(): boolean
10413 // Returns `true` if the tooltip bound to this layer is currently open.
10414 isTooltipOpen: function () {
10415 return this._tooltip.isOpen();
10418 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10419 // Sets the content of the tooltip bound to this layer.
10420 setTooltipContent: function (content) {
10421 if (this._tooltip) {
10422 this._tooltip.setContent(content);
10427 // @method getTooltip(): Tooltip
10428 // Returns the tooltip bound to this layer.
10429 getTooltip: function () {
10430 return this._tooltip;
10433 _openTooltip: function (e) {
10434 var layer = e.layer || e.target;
10436 if (!this._tooltip || !this._map) {
10439 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
10442 _moveTooltip: function (e) {
10443 var latlng = e.latlng, containerPoint, layerPoint;
10444 if (this._tooltip.options.sticky && e.originalEvent) {
10445 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
10446 layerPoint = this._map.containerPointToLayerPoint(containerPoint);
10447 latlng = this._map.layerPointToLatLng(layerPoint);
10449 this._tooltip.setLatLng(latlng);
10458 * Represents a lightweight icon for markers that uses a simple `<div>`
10459 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
10463 * var myIcon = L.divIcon({className: 'my-div-icon'});
10464 * // you can set .my-div-icon styles in CSS
10466 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
10469 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
10472 var DivIcon = Icon.extend({
10475 // @aka DivIcon options
10476 iconSize: [12, 12], // also can be set through CSS
10478 // iconAnchor: (Point),
10479 // popupAnchor: (Point),
10481 // @option html: String = ''
10482 // Custom HTML code to put inside the div element, empty by default.
10485 // @option bgPos: Point = [0, 0]
10486 // Optional relative position of the background, in pixels
10489 className: 'leaflet-div-icon'
10492 createIcon: function (oldIcon) {
10493 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
10494 options = this.options;
10496 div.innerHTML = options.html !== false ? options.html : '';
10498 if (options.bgPos) {
10499 var bgPos = toPoint(options.bgPos);
10500 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
10502 this._setIconStyles(div, 'icon');
10507 createShadow: function () {
10512 // @factory L.divIcon(options: DivIcon options)
10513 // Creates a `DivIcon` instance with the given options.
10514 function divIcon(options) {
10515 return new DivIcon(options);
10518 Icon.Default = IconDefault;
10525 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
10526 * 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.
10529 * @section Synchronous usage
10532 * 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.
10535 * var CanvasLayer = L.GridLayer.extend({
10536 * createTile: function(coords){
10537 * // create a <canvas> element for drawing
10538 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10540 * // setup tile width and height according to the options
10541 * var size = this.getTileSize();
10542 * tile.width = size.x;
10543 * tile.height = size.y;
10545 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z
10546 * var ctx = tile.getContext('2d');
10548 * // return the tile so it can be rendered on screen
10554 * @section Asynchronous usage
10557 * 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.
10560 * var CanvasLayer = L.GridLayer.extend({
10561 * createTile: function(coords, done){
10564 * // create a <canvas> element for drawing
10565 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10567 * // setup tile width and height according to the options
10568 * var size = this.getTileSize();
10569 * tile.width = size.x;
10570 * tile.height = size.y;
10572 * // draw something asynchronously and pass the tile to the done() callback
10573 * setTimeout(function() {
10574 * done(error, tile);
10586 var GridLayer = Layer.extend({
10589 // @aka GridLayer options
10591 // @option tileSize: Number|Point = 256
10592 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
10595 // @option opacity: Number = 1.0
10596 // Opacity of the tiles. Can be used in the `createTile()` function.
10599 // @option updateWhenIdle: Boolean = (depends)
10600 // Load new tiles only when panning ends.
10601 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
10602 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
10603 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
10604 updateWhenIdle: mobile,
10606 // @option updateWhenZooming: Boolean = true
10607 // 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.
10608 updateWhenZooming: true,
10610 // @option updateInterval: Number = 200
10611 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
10612 updateInterval: 200,
10614 // @option zIndex: Number = 1
10615 // The explicit zIndex of the tile layer.
10618 // @option bounds: LatLngBounds = undefined
10619 // If set, tiles will only be loaded inside the set `LatLngBounds`.
10622 // @option minZoom: Number = 0
10623 // The minimum zoom level down to which this layer will be displayed (inclusive).
10626 // @option maxZoom: Number = undefined
10627 // The maximum zoom level up to which this layer will be displayed (inclusive).
10628 maxZoom: undefined,
10630 // @option maxNativeZoom: Number = undefined
10631 // Maximum zoom number the tile source has available. If it is specified,
10632 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
10633 // from `maxNativeZoom` level and auto-scaled.
10634 maxNativeZoom: undefined,
10636 // @option minNativeZoom: Number = undefined
10637 // Minimum zoom number the tile source has available. If it is specified,
10638 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
10639 // from `minNativeZoom` level and auto-scaled.
10640 minNativeZoom: undefined,
10642 // @option noWrap: Boolean = false
10643 // Whether the layer is wrapped around the antimeridian. If `true`, the
10644 // GridLayer will only be displayed once at low zoom levels. Has no
10645 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
10646 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
10647 // tiles outside the CRS limits.
10650 // @option pane: String = 'tilePane'
10651 // `Map pane` where the grid layer will be added.
10654 // @option className: String = ''
10655 // A custom class name to assign to the tile layer. Empty by default.
10658 // @option keepBuffer: Number = 2
10659 // When panning the map, keep this many rows and columns of tiles before unloading them.
10663 initialize: function (options) {
10664 setOptions(this, options);
10667 onAdd: function () {
10668 this._initContainer();
10677 beforeAdd: function (map) {
10678 map._addZoomLimit(this);
10681 onRemove: function (map) {
10682 this._removeAllTiles();
10683 remove(this._container);
10684 map._removeZoomLimit(this);
10685 this._container = null;
10686 this._tileZoom = undefined;
10689 // @method bringToFront: this
10690 // Brings the tile layer to the top of all tile layers.
10691 bringToFront: function () {
10693 toFront(this._container);
10694 this._setAutoZIndex(Math.max);
10699 // @method bringToBack: this
10700 // Brings the tile layer to the bottom of all tile layers.
10701 bringToBack: function () {
10703 toBack(this._container);
10704 this._setAutoZIndex(Math.min);
10709 // @method getContainer: HTMLElement
10710 // Returns the HTML element that contains the tiles for this layer.
10711 getContainer: function () {
10712 return this._container;
10715 // @method setOpacity(opacity: Number): this
10716 // Changes the [opacity](#gridlayer-opacity) of the grid layer.
10717 setOpacity: function (opacity) {
10718 this.options.opacity = opacity;
10719 this._updateOpacity();
10723 // @method setZIndex(zIndex: Number): this
10724 // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
10725 setZIndex: function (zIndex) {
10726 this.options.zIndex = zIndex;
10727 this._updateZIndex();
10732 // @method isLoading: Boolean
10733 // Returns `true` if any tile in the grid layer has not finished loading.
10734 isLoading: function () {
10735 return this._loading;
10738 // @method redraw: this
10739 // Causes the layer to clear all the tiles and request them again.
10740 redraw: function () {
10742 this._removeAllTiles();
10748 getEvents: function () {
10750 viewprereset: this._invalidateAll,
10751 viewreset: this._resetView,
10752 zoom: this._resetView,
10753 moveend: this._onMoveEnd
10756 if (!this.options.updateWhenIdle) {
10757 // update tiles on move, but not more often than once per given interval
10758 if (!this._onMove) {
10759 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
10762 events.move = this._onMove;
10765 if (this._zoomAnimated) {
10766 events.zoomanim = this._animateZoom;
10772 // @section Extension methods
10773 // Layers extending `GridLayer` shall reimplement the following method.
10774 // @method createTile(coords: Object, done?: Function): HTMLElement
10775 // Called only internally, must be overridden by classes extending `GridLayer`.
10776 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
10777 // is specified, it must be called when the tile has finished loading and drawing.
10778 createTile: function () {
10779 return document.createElement('div');
10783 // @method getTileSize: Point
10784 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
10785 getTileSize: function () {
10786 var s = this.options.tileSize;
10787 return s instanceof Point ? s : new Point(s, s);
10790 _updateZIndex: function () {
10791 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
10792 this._container.style.zIndex = this.options.zIndex;
10796 _setAutoZIndex: function (compare) {
10797 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
10799 var layers = this.getPane().children,
10800 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
10802 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
10804 zIndex = layers[i].style.zIndex;
10806 if (layers[i] !== this._container && zIndex) {
10807 edgeZIndex = compare(edgeZIndex, +zIndex);
10811 if (isFinite(edgeZIndex)) {
10812 this.options.zIndex = edgeZIndex + compare(-1, 1);
10813 this._updateZIndex();
10817 _updateOpacity: function () {
10818 if (!this._map) { return; }
10820 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
10821 if (ielt9) { return; }
10823 setOpacity(this._container, this.options.opacity);
10825 var now = +new Date(),
10829 for (var key in this._tiles) {
10830 var tile = this._tiles[key];
10831 if (!tile.current || !tile.loaded) { continue; }
10833 var fade = Math.min(1, (now - tile.loaded) / 200);
10835 setOpacity(tile.el, fade);
10842 this._onOpaqueTile(tile);
10844 tile.active = true;
10848 if (willPrune && !this._noPrune) { this._pruneTiles(); }
10851 cancelAnimFrame(this._fadeFrame);
10852 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
10856 _onOpaqueTile: falseFn,
10858 _initContainer: function () {
10859 if (this._container) { return; }
10861 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
10862 this._updateZIndex();
10864 if (this.options.opacity < 1) {
10865 this._updateOpacity();
10868 this.getPane().appendChild(this._container);
10871 _updateLevels: function () {
10873 var zoom = this._tileZoom,
10874 maxZoom = this.options.maxZoom;
10876 if (zoom === undefined) { return undefined; }
10878 for (var z in this._levels) {
10879 if (this._levels[z].el.children.length || z === zoom) {
10880 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
10881 this._onUpdateLevel(z);
10883 remove(this._levels[z].el);
10884 this._removeTilesAtZoom(z);
10885 this._onRemoveLevel(z);
10886 delete this._levels[z];
10890 var level = this._levels[zoom],
10894 level = this._levels[zoom] = {};
10896 level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
10897 level.el.style.zIndex = maxZoom;
10899 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
10902 this._setZoomTransform(level, map.getCenter(), map.getZoom());
10904 // force the browser to consider the newly added element for transition
10905 falseFn(level.el.offsetWidth);
10907 this._onCreateLevel(level);
10910 this._level = level;
10915 _onUpdateLevel: falseFn,
10917 _onRemoveLevel: falseFn,
10919 _onCreateLevel: falseFn,
10921 _pruneTiles: function () {
10928 var zoom = this._map.getZoom();
10929 if (zoom > this.options.maxZoom ||
10930 zoom < this.options.minZoom) {
10931 this._removeAllTiles();
10935 for (key in this._tiles) {
10936 tile = this._tiles[key];
10937 tile.retain = tile.current;
10940 for (key in this._tiles) {
10941 tile = this._tiles[key];
10942 if (tile.current && !tile.active) {
10943 var coords = tile.coords;
10944 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
10945 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
10950 for (key in this._tiles) {
10951 if (!this._tiles[key].retain) {
10952 this._removeTile(key);
10957 _removeTilesAtZoom: function (zoom) {
10958 for (var key in this._tiles) {
10959 if (this._tiles[key].coords.z !== zoom) {
10962 this._removeTile(key);
10966 _removeAllTiles: function () {
10967 for (var key in this._tiles) {
10968 this._removeTile(key);
10972 _invalidateAll: function () {
10973 for (var z in this._levels) {
10974 remove(this._levels[z].el);
10975 this._onRemoveLevel(z);
10976 delete this._levels[z];
10978 this._removeAllTiles();
10980 this._tileZoom = undefined;
10983 _retainParent: function (x, y, z, minZoom) {
10984 var x2 = Math.floor(x / 2),
10985 y2 = Math.floor(y / 2),
10987 coords2 = new Point(+x2, +y2);
10990 var key = this._tileCoordsToKey(coords2),
10991 tile = this._tiles[key];
10993 if (tile && tile.active) {
10994 tile.retain = true;
10997 } else if (tile && tile.loaded) {
10998 tile.retain = true;
11001 if (z2 > minZoom) {
11002 return this._retainParent(x2, y2, z2, minZoom);
11008 _retainChildren: function (x, y, z, maxZoom) {
11010 for (var i = 2 * x; i < 2 * x + 2; i++) {
11011 for (var j = 2 * y; j < 2 * y + 2; j++) {
11013 var coords = new Point(i, j);
11016 var key = this._tileCoordsToKey(coords),
11017 tile = this._tiles[key];
11019 if (tile && tile.active) {
11020 tile.retain = true;
11023 } else if (tile && tile.loaded) {
11024 tile.retain = true;
11027 if (z + 1 < maxZoom) {
11028 this._retainChildren(i, j, z + 1, maxZoom);
11034 _resetView: function (e) {
11035 var animating = e && (e.pinch || e.flyTo);
11036 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
11039 _animateZoom: function (e) {
11040 this._setView(e.center, e.zoom, true, e.noUpdate);
11043 _clampZoom: function (zoom) {
11044 var options = this.options;
11046 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
11047 return options.minNativeZoom;
11050 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
11051 return options.maxNativeZoom;
11057 _setView: function (center, zoom, noPrune, noUpdate) {
11058 var tileZoom = this._clampZoom(Math.round(zoom));
11059 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
11060 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
11061 tileZoom = undefined;
11064 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
11066 if (!noUpdate || tileZoomChanged) {
11068 this._tileZoom = tileZoom;
11070 if (this._abortLoading) {
11071 this._abortLoading();
11074 this._updateLevels();
11077 if (tileZoom !== undefined) {
11078 this._update(center);
11082 this._pruneTiles();
11085 // Flag to prevent _updateOpacity from pruning tiles during
11086 // a zoom anim or a pinch gesture
11087 this._noPrune = !!noPrune;
11090 this._setZoomTransforms(center, zoom);
11093 _setZoomTransforms: function (center, zoom) {
11094 for (var i in this._levels) {
11095 this._setZoomTransform(this._levels[i], center, zoom);
11099 _setZoomTransform: function (level, center, zoom) {
11100 var scale = this._map.getZoomScale(zoom, level.zoom),
11101 translate = level.origin.multiplyBy(scale)
11102 .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
11105 setTransform(level.el, translate, scale);
11107 setPosition(level.el, translate);
11111 _resetGrid: function () {
11112 var map = this._map,
11113 crs = map.options.crs,
11114 tileSize = this._tileSize = this.getTileSize(),
11115 tileZoom = this._tileZoom;
11117 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
11119 this._globalTileRange = this._pxBoundsToTileRange(bounds);
11122 this._wrapX = crs.wrapLng && !this.options.noWrap && [
11123 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
11124 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
11126 this._wrapY = crs.wrapLat && !this.options.noWrap && [
11127 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
11128 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
11132 _onMoveEnd: function () {
11133 if (!this._map || this._map._animatingZoom) { return; }
11138 _getTiledPixelBounds: function (center) {
11139 var map = this._map,
11140 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
11141 scale = map.getZoomScale(mapZoom, this._tileZoom),
11142 pixelCenter = map.project(center, this._tileZoom).floor(),
11143 halfSize = map.getSize().divideBy(scale * 2);
11145 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
11148 // Private method to load tiles in the grid's active zoom level according to map bounds
11149 _update: function (center) {
11150 var map = this._map;
11151 if (!map) { return; }
11152 var zoom = this._clampZoom(map.getZoom());
11154 if (center === undefined) { center = map.getCenter(); }
11155 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom
11157 var pixelBounds = this._getTiledPixelBounds(center),
11158 tileRange = this._pxBoundsToTileRange(pixelBounds),
11159 tileCenter = tileRange.getCenter(),
11161 margin = this.options.keepBuffer,
11162 noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
11163 tileRange.getTopRight().add([margin, -margin]));
11165 // Sanity check: panic if the tile range contains Infinity somewhere.
11166 if (!(isFinite(tileRange.min.x) &&
11167 isFinite(tileRange.min.y) &&
11168 isFinite(tileRange.max.x) &&
11169 isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
11171 for (var key in this._tiles) {
11172 var c = this._tiles[key].coords;
11173 if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
11174 this._tiles[key].current = false;
11178 // _update just loads more tiles. If the tile zoom level differs too much
11179 // from the map's, let _setView reset levels and prune old tiles.
11180 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
11182 // create a queue of coordinates to load tiles from
11183 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
11184 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
11185 var coords = new Point(i, j);
11186 coords.z = this._tileZoom;
11188 if (!this._isValidTile(coords)) { continue; }
11190 var tile = this._tiles[this._tileCoordsToKey(coords)];
11192 tile.current = true;
11194 queue.push(coords);
11199 // sort tile queue to load tiles in order of their distance to center
11200 queue.sort(function (a, b) {
11201 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
11204 if (queue.length !== 0) {
11205 // if it's the first batch of tiles to load
11206 if (!this._loading) {
11207 this._loading = true;
11208 // @event loading: Event
11209 // Fired when the grid layer starts loading tiles.
11210 this.fire('loading');
11213 // create DOM fragment to append tiles in one batch
11214 var fragment = document.createDocumentFragment();
11216 for (i = 0; i < queue.length; i++) {
11217 this._addTile(queue[i], fragment);
11220 this._level.el.appendChild(fragment);
11224 _isValidTile: function (coords) {
11225 var crs = this._map.options.crs;
11227 if (!crs.infinite) {
11228 // don't load tile if it's out of bounds and not wrapped
11229 var bounds = this._globalTileRange;
11230 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
11231 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
11234 if (!this.options.bounds) { return true; }
11236 // don't load tile if it doesn't intersect the bounds in options
11237 var tileBounds = this._tileCoordsToBounds(coords);
11238 return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
11241 _keyToBounds: function (key) {
11242 return this._tileCoordsToBounds(this._keyToTileCoords(key));
11245 _tileCoordsToNwSe: function (coords) {
11246 var map = this._map,
11247 tileSize = this.getTileSize(),
11248 nwPoint = coords.scaleBy(tileSize),
11249 sePoint = nwPoint.add(tileSize),
11250 nw = map.unproject(nwPoint, coords.z),
11251 se = map.unproject(sePoint, coords.z);
11255 // converts tile coordinates to its geographical bounds
11256 _tileCoordsToBounds: function (coords) {
11257 var bp = this._tileCoordsToNwSe(coords),
11258 bounds = new LatLngBounds(bp[0], bp[1]);
11260 if (!this.options.noWrap) {
11261 bounds = this._map.wrapLatLngBounds(bounds);
11265 // converts tile coordinates to key for the tile cache
11266 _tileCoordsToKey: function (coords) {
11267 return coords.x + ':' + coords.y + ':' + coords.z;
11270 // converts tile cache key to coordinates
11271 _keyToTileCoords: function (key) {
11272 var k = key.split(':'),
11273 coords = new Point(+k[0], +k[1]);
11278 _removeTile: function (key) {
11279 var tile = this._tiles[key];
11280 if (!tile) { return; }
11284 delete this._tiles[key];
11286 // @event tileunload: TileEvent
11287 // Fired when a tile is removed (e.g. when a tile goes off the screen).
11288 this.fire('tileunload', {
11290 coords: this._keyToTileCoords(key)
11294 _initTile: function (tile) {
11295 addClass(tile, 'leaflet-tile');
11297 var tileSize = this.getTileSize();
11298 tile.style.width = tileSize.x + 'px';
11299 tile.style.height = tileSize.y + 'px';
11301 tile.onselectstart = falseFn;
11302 tile.onmousemove = falseFn;
11304 // update opacity on tiles in IE7-8 because of filter inheritance problems
11305 if (ielt9 && this.options.opacity < 1) {
11306 setOpacity(tile, this.options.opacity);
11309 // without this hack, tiles disappear after zoom on Chrome for Android
11310 // https://github.com/Leaflet/Leaflet/issues/2078
11311 if (android && !android23) {
11312 tile.style.WebkitBackfaceVisibility = 'hidden';
11316 _addTile: function (coords, container) {
11317 var tilePos = this._getTilePos(coords),
11318 key = this._tileCoordsToKey(coords);
11320 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
11322 this._initTile(tile);
11324 // if createTile is defined with a second argument ("done" callback),
11325 // we know that tile is async and will be ready later; otherwise
11326 if (this.createTile.length < 2) {
11327 // mark tile as ready, but delay one frame for opacity animation to happen
11328 requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
11331 setPosition(tile, tilePos);
11333 // save tile in cache
11334 this._tiles[key] = {
11340 container.appendChild(tile);
11341 // @event tileloadstart: TileEvent
11342 // Fired when a tile is requested and starts loading.
11343 this.fire('tileloadstart', {
11349 _tileReady: function (coords, err, tile) {
11351 // @event tileerror: TileErrorEvent
11352 // Fired when there is an error loading a tile.
11353 this.fire('tileerror', {
11360 var key = this._tileCoordsToKey(coords);
11362 tile = this._tiles[key];
11363 if (!tile) { return; }
11365 tile.loaded = +new Date();
11366 if (this._map._fadeAnimated) {
11367 setOpacity(tile.el, 0);
11368 cancelAnimFrame(this._fadeFrame);
11369 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11371 tile.active = true;
11372 this._pruneTiles();
11376 addClass(tile.el, 'leaflet-tile-loaded');
11378 // @event tileload: TileEvent
11379 // Fired when a tile loads.
11380 this.fire('tileload', {
11386 if (this._noTilesToLoad()) {
11387 this._loading = false;
11388 // @event load: Event
11389 // Fired when the grid layer loaded all visible tiles.
11392 if (ielt9 || !this._map._fadeAnimated) {
11393 requestAnimFrame(this._pruneTiles, this);
11395 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
11396 // to trigger a pruning.
11397 setTimeout(bind(this._pruneTiles, this), 250);
11402 _getTilePos: function (coords) {
11403 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
11406 _wrapCoords: function (coords) {
11407 var newCoords = new Point(
11408 this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
11409 this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
11410 newCoords.z = coords.z;
11414 _pxBoundsToTileRange: function (bounds) {
11415 var tileSize = this.getTileSize();
11417 bounds.min.unscaleBy(tileSize).floor(),
11418 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
11421 _noTilesToLoad: function () {
11422 for (var key in this._tiles) {
11423 if (!this._tiles[key].loaded) { return false; }
11429 // @factory L.gridLayer(options?: GridLayer options)
11430 // Creates a new instance of GridLayer with the supplied options.
11431 function gridLayer(options) {
11432 return new GridLayer(options);
11437 * @inherits GridLayer
11439 * Used to load and display tile layers on the map. Note that most tile servers require attribution, which you can set under `Layer`. Extends `GridLayer`.
11444 * L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar', attribution: 'Map data © <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'}).addTo(map);
11447 * @section URL template
11450 * A string of the following form:
11453 * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
11456 * `{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.
11458 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
11461 * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
11466 var TileLayer = GridLayer.extend({
11469 // @aka TileLayer options
11471 // @option minZoom: Number = 0
11472 // The minimum zoom level down to which this layer will be displayed (inclusive).
11475 // @option maxZoom: Number = 18
11476 // The maximum zoom level up to which this layer will be displayed (inclusive).
11479 // @option subdomains: String|String[] = 'abc'
11480 // 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.
11483 // @option errorTileUrl: String = ''
11484 // URL to the tile image to show in place of the tile that failed to load.
11487 // @option zoomOffset: Number = 0
11488 // The zoom number used in tile URLs will be offset with this value.
11491 // @option tms: Boolean = false
11492 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
11495 // @option zoomReverse: Boolean = false
11496 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
11497 zoomReverse: false,
11499 // @option detectRetina: Boolean = false
11500 // 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.
11501 detectRetina: false,
11503 // @option crossOrigin: Boolean|String = false
11504 // Whether the crossOrigin attribute will be added to the tiles.
11505 // 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.
11506 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
11510 initialize: function (url, options) {
11514 options = setOptions(this, options);
11516 // detecting retina displays, adjusting tileSize and zoom levels
11517 if (options.detectRetina && retina && options.maxZoom > 0) {
11519 options.tileSize = Math.floor(options.tileSize / 2);
11521 if (!options.zoomReverse) {
11522 options.zoomOffset++;
11525 options.zoomOffset--;
11529 options.minZoom = Math.max(0, options.minZoom);
11532 if (typeof options.subdomains === 'string') {
11533 options.subdomains = options.subdomains.split('');
11536 // for https://github.com/Leaflet/Leaflet/issues/137
11538 this.on('tileunload', this._onTileRemove);
11542 // @method setUrl(url: String, noRedraw?: Boolean): this
11543 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
11544 // If the URL does not change, the layer will not be redrawn unless
11545 // the noRedraw parameter is set to false.
11546 setUrl: function (url, noRedraw) {
11547 if (this._url === url && noRedraw === undefined) {
11559 // @method createTile(coords: Object, done?: Function): HTMLElement
11560 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
11561 // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
11562 // callback is called when the tile has been loaded.
11563 createTile: function (coords, done) {
11564 var tile = document.createElement('img');
11566 on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
11567 on(tile, 'error', bind(this._tileOnError, this, done, tile));
11569 if (this.options.crossOrigin || this.options.crossOrigin === '') {
11570 tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
11574 Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
11575 http://www.w3.org/TR/WCAG20-TECHS/H67
11580 Set role="presentation" to force screen readers to ignore this
11581 https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
11583 tile.setAttribute('role', 'presentation');
11585 tile.src = this.getTileUrl(coords);
11590 // @section Extension methods
11592 // Layers extending `TileLayer` might reimplement the following method.
11593 // @method getTileUrl(coords: Object): String
11594 // Called only internally, returns the URL for a tile given its coordinates.
11595 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
11596 getTileUrl: function (coords) {
11598 r: retina ? '@2x' : '',
11599 s: this._getSubdomain(coords),
11602 z: this._getZoomForUrl()
11604 if (this._map && !this._map.options.crs.infinite) {
11605 var invertedY = this._globalTileRange.max.y - coords.y;
11606 if (this.options.tms) {
11607 data['y'] = invertedY;
11609 data['-y'] = invertedY;
11612 return template(this._url, extend(data, this.options));
11615 _tileOnLoad: function (done, tile) {
11616 // For https://github.com/Leaflet/Leaflet/issues/3332
11618 setTimeout(bind(done, this, null, tile), 0);
11624 _tileOnError: function (done, tile, e) {
11625 var errorUrl = this.options.errorTileUrl;
11626 if (errorUrl && tile.getAttribute('src') !== errorUrl) {
11627 tile.src = errorUrl;
11632 _onTileRemove: function (e) {
11633 e.tile.onload = null;
11636 _getZoomForUrl: function () {
11637 var zoom = this._tileZoom,
11638 maxZoom = this.options.maxZoom,
11639 zoomReverse = this.options.zoomReverse,
11640 zoomOffset = this.options.zoomOffset;
11643 zoom = maxZoom - zoom;
11646 return zoom + zoomOffset;
11649 _getSubdomain: function (tilePoint) {
11650 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
11651 return this.options.subdomains[index];
11654 // stops loading all tiles in the background layer
11655 _abortLoading: function () {
11657 for (i in this._tiles) {
11658 if (this._tiles[i].coords.z !== this._tileZoom) {
11659 tile = this._tiles[i].el;
11661 tile.onload = falseFn;
11662 tile.onerror = falseFn;
11664 if (!tile.complete) {
11665 tile.src = emptyImageUrl;
11667 delete this._tiles[i];
11673 _removeTile: function (key) {
11674 var tile = this._tiles[key];
11675 if (!tile) { return; }
11677 // Cancels any pending http requests associated with the tile
11678 // unless we're on Android's stock browser,
11679 // see https://github.com/Leaflet/Leaflet/issues/137
11680 if (!androidStock) {
11681 tile.el.setAttribute('src', emptyImageUrl);
11684 return GridLayer.prototype._removeTile.call(this, key);
11687 _tileReady: function (coords, err, tile) {
11688 if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) {
11692 return GridLayer.prototype._tileReady.call(this, coords, err, tile);
11697 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
11698 // Instantiates a tile layer object given a `URL template` and optionally an options object.
11700 function tileLayer(url, options) {
11701 return new TileLayer(url, options);
11705 * @class TileLayer.WMS
11706 * @inherits TileLayer
11707 * @aka L.TileLayer.WMS
11708 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
11713 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
11714 * layers: 'nexrad-n0r-900913',
11715 * format: 'image/png',
11716 * transparent: true,
11717 * attribution: "Weather data © 2012 IEM Nexrad"
11722 var TileLayerWMS = TileLayer.extend({
11725 // @aka TileLayer.WMS options
11726 // If any custom options not documented here are used, they will be sent to the
11727 // WMS server as extra parameters in each request URL. This can be useful for
11728 // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
11729 defaultWmsParams: {
11733 // @option layers: String = ''
11734 // **(required)** Comma-separated list of WMS layers to show.
11737 // @option styles: String = ''
11738 // Comma-separated list of WMS styles.
11741 // @option format: String = 'image/jpeg'
11742 // WMS image format (use `'image/png'` for layers with transparency).
11743 format: 'image/jpeg',
11745 // @option transparent: Boolean = false
11746 // If `true`, the WMS service will return images with transparency.
11747 transparent: false,
11749 // @option version: String = '1.1.1'
11750 // Version of the WMS service to use
11755 // @option crs: CRS = null
11756 // Coordinate Reference System to use for the WMS requests, defaults to
11757 // map CRS. Don't change this if you're not sure what it means.
11760 // @option uppercase: Boolean = false
11761 // If `true`, WMS request parameter keys will be uppercase.
11765 initialize: function (url, options) {
11769 var wmsParams = extend({}, this.defaultWmsParams);
11771 // all keys that are not TileLayer options go to WMS params
11772 for (var i in options) {
11773 if (!(i in this.options)) {
11774 wmsParams[i] = options[i];
11778 options = setOptions(this, options);
11780 var realRetina = options.detectRetina && retina ? 2 : 1;
11781 var tileSize = this.getTileSize();
11782 wmsParams.width = tileSize.x * realRetina;
11783 wmsParams.height = tileSize.y * realRetina;
11785 this.wmsParams = wmsParams;
11788 onAdd: function (map) {
11790 this._crs = this.options.crs || map.options.crs;
11791 this._wmsVersion = parseFloat(this.wmsParams.version);
11793 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
11794 this.wmsParams[projectionKey] = this._crs.code;
11796 TileLayer.prototype.onAdd.call(this, map);
11799 getTileUrl: function (coords) {
11801 var tileBounds = this._tileCoordsToNwSe(coords),
11803 bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
11806 bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
11807 [min.y, min.x, max.y, max.x] :
11808 [min.x, min.y, max.x, max.y]).join(','),
11809 url = TileLayer.prototype.getTileUrl.call(this, coords);
11811 getParamString(this.wmsParams, url, this.options.uppercase) +
11812 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
11815 // @method setParams(params: Object, noRedraw?: Boolean): this
11816 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
11817 setParams: function (params, noRedraw) {
11819 extend(this.wmsParams, params);
11830 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
11831 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
11832 function tileLayerWMS(url, options) {
11833 return new TileLayerWMS(url, options);
11836 TileLayer.WMS = TileLayerWMS;
11837 tileLayer.wms = tileLayerWMS;
11844 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
11845 * DOM container of the renderer, its bounds, and its zoom animation.
11847 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
11848 * itself can be added or removed to the map. All paths use a renderer, which can
11849 * be implicit (the map will decide the type of renderer and use it automatically)
11850 * or explicit (using the [`renderer`](#path-renderer) option of the path).
11852 * Do not use this class directly, use `SVG` and `Canvas` instead.
11854 * @event update: Event
11855 * Fired when the renderer updates its bounds, center and zoom, for example when
11856 * its map has moved
11859 var Renderer = Layer.extend({
11862 // @aka Renderer options
11864 // @option padding: Number = 0.1
11865 // How much to extend the clip area around the map view (relative to its size)
11866 // e.g. 0.1 would be 10% of map view in each direction
11869 // @option tolerance: Number = 0
11870 // How much to extend click tolerance round a path/object on the map
11874 initialize: function (options) {
11875 setOptions(this, options);
11877 this._layers = this._layers || {};
11880 onAdd: function () {
11881 if (!this._container) {
11882 this._initContainer(); // defined by renderer implementations
11884 if (this._zoomAnimated) {
11885 addClass(this._container, 'leaflet-zoom-animated');
11889 this.getPane().appendChild(this._container);
11891 this.on('update', this._updatePaths, this);
11894 onRemove: function () {
11895 this.off('update', this._updatePaths, this);
11896 this._destroyContainer();
11899 getEvents: function () {
11901 viewreset: this._reset,
11902 zoom: this._onZoom,
11903 moveend: this._update,
11904 zoomend: this._onZoomEnd
11906 if (this._zoomAnimated) {
11907 events.zoomanim = this._onAnimZoom;
11912 _onAnimZoom: function (ev) {
11913 this._updateTransform(ev.center, ev.zoom);
11916 _onZoom: function () {
11917 this._updateTransform(this._map.getCenter(), this._map.getZoom());
11920 _updateTransform: function (center, zoom) {
11921 var scale = this._map.getZoomScale(zoom, this._zoom),
11922 position = getPosition(this._container),
11923 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
11924 currentCenterPoint = this._map.project(this._center, zoom),
11925 destCenterPoint = this._map.project(center, zoom),
11926 centerOffset = destCenterPoint.subtract(currentCenterPoint),
11928 topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
11931 setTransform(this._container, topLeftOffset, scale);
11933 setPosition(this._container, topLeftOffset);
11937 _reset: function () {
11939 this._updateTransform(this._center, this._zoom);
11941 for (var id in this._layers) {
11942 this._layers[id]._reset();
11946 _onZoomEnd: function () {
11947 for (var id in this._layers) {
11948 this._layers[id]._project();
11952 _updatePaths: function () {
11953 for (var id in this._layers) {
11954 this._layers[id]._update();
11958 _update: function () {
11959 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
11960 // Subclasses are responsible of firing the 'update' event.
11961 var p = this.options.padding,
11962 size = this._map.getSize(),
11963 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
11965 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
11967 this._center = this._map.getCenter();
11968 this._zoom = this._map.getZoom();
11974 * @inherits Renderer
11977 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
11978 * Inherits `Renderer`.
11980 * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
11981 * available in all web browsers, notably IE8, and overlapping geometries might
11982 * not display properly in some edge cases.
11986 * Use Canvas by default for all paths in the map:
11989 * var map = L.map('map', {
11990 * renderer: L.canvas()
11994 * Use a Canvas renderer with extra padding for specific vector geometries:
11997 * var map = L.map('map');
11998 * var myRenderer = L.canvas({ padding: 0.5 });
11999 * var line = L.polyline( coordinates, { renderer: myRenderer } );
12000 * var circle = L.circle( center, { renderer: myRenderer } );
12004 var Canvas = Renderer.extend({
12005 getEvents: function () {
12006 var events = Renderer.prototype.getEvents.call(this);
12007 events.viewprereset = this._onViewPreReset;
12011 _onViewPreReset: function () {
12012 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
12013 this._postponeUpdatePaths = true;
12016 onAdd: function () {
12017 Renderer.prototype.onAdd.call(this);
12019 // Redraw vectors since canvas is cleared upon removal,
12020 // in case of removing the renderer itself from the map.
12024 _initContainer: function () {
12025 var container = this._container = document.createElement('canvas');
12027 on(container, 'mousemove', throttle(this._onMouseMove, 32, this), this);
12028 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
12029 on(container, 'mouseout', this._handleMouseOut, this);
12031 this._ctx = container.getContext('2d');
12034 _destroyContainer: function () {
12035 cancelAnimFrame(this._redrawRequest);
12037 remove(this._container);
12038 off(this._container);
12039 delete this._container;
12042 _updatePaths: function () {
12043 if (this._postponeUpdatePaths) { return; }
12046 this._redrawBounds = null;
12047 for (var id in this._layers) {
12048 layer = this._layers[id];
12054 _update: function () {
12055 if (this._map._animatingZoom && this._bounds) { return; }
12057 Renderer.prototype._update.call(this);
12059 var b = this._bounds,
12060 container = this._container,
12061 size = b.getSize(),
12062 m = retina ? 2 : 1;
12064 setPosition(container, b.min);
12066 // set canvas size (also clearing it); use double size on retina
12067 container.width = m * size.x;
12068 container.height = m * size.y;
12069 container.style.width = size.x + 'px';
12070 container.style.height = size.y + 'px';
12073 this._ctx.scale(2, 2);
12076 // translate so we use the same path coordinates after canvas element moves
12077 this._ctx.translate(-b.min.x, -b.min.y);
12079 // Tell paths to redraw themselves
12080 this.fire('update');
12083 _reset: function () {
12084 Renderer.prototype._reset.call(this);
12086 if (this._postponeUpdatePaths) {
12087 this._postponeUpdatePaths = false;
12088 this._updatePaths();
12092 _initPath: function (layer) {
12093 this._updateDashArray(layer);
12094 this._layers[stamp(layer)] = layer;
12096 var order = layer._order = {
12098 prev: this._drawLast,
12101 if (this._drawLast) { this._drawLast.next = order; }
12102 this._drawLast = order;
12103 this._drawFirst = this._drawFirst || this._drawLast;
12106 _addPath: function (layer) {
12107 this._requestRedraw(layer);
12110 _removePath: function (layer) {
12111 var order = layer._order;
12112 var next = order.next;
12113 var prev = order.prev;
12118 this._drawLast = prev;
12123 this._drawFirst = next;
12126 delete layer._order;
12128 delete this._layers[stamp(layer)];
12130 this._requestRedraw(layer);
12133 _updatePath: function (layer) {
12134 // Redraw the union of the layer's old pixel
12135 // bounds and the new pixel bounds.
12136 this._extendRedrawBounds(layer);
12139 // The redraw will extend the redraw bounds
12140 // with the new pixel bounds.
12141 this._requestRedraw(layer);
12144 _updateStyle: function (layer) {
12145 this._updateDashArray(layer);
12146 this._requestRedraw(layer);
12149 _updateDashArray: function (layer) {
12150 if (typeof layer.options.dashArray === 'string') {
12151 var parts = layer.options.dashArray.split(/[, ]+/),
12155 for (i = 0; i < parts.length; i++) {
12156 dashValue = Number(parts[i]);
12157 // Ignore dash array containing invalid lengths
12158 if (isNaN(dashValue)) { return; }
12159 dashArray.push(dashValue);
12161 layer.options._dashArray = dashArray;
12163 layer.options._dashArray = layer.options.dashArray;
12167 _requestRedraw: function (layer) {
12168 if (!this._map) { return; }
12170 this._extendRedrawBounds(layer);
12171 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
12174 _extendRedrawBounds: function (layer) {
12175 if (layer._pxBounds) {
12176 var padding = (layer.options.weight || 0) + 1;
12177 this._redrawBounds = this._redrawBounds || new Bounds();
12178 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
12179 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
12183 _redraw: function () {
12184 this._redrawRequest = null;
12186 if (this._redrawBounds) {
12187 this._redrawBounds.min._floor();
12188 this._redrawBounds.max._ceil();
12191 this._clear(); // clear layers in redraw bounds
12192 this._draw(); // draw layers
12194 this._redrawBounds = null;
12197 _clear: function () {
12198 var bounds = this._redrawBounds;
12200 var size = bounds.getSize();
12201 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
12203 this._ctx.clearRect(0, 0, this._container.width, this._container.height);
12207 _draw: function () {
12208 var layer, bounds = this._redrawBounds;
12211 var size = bounds.getSize();
12212 this._ctx.beginPath();
12213 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
12217 this._drawing = true;
12219 for (var order = this._drawFirst; order; order = order.next) {
12220 layer = order.layer;
12221 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
12222 layer._updatePath();
12226 this._drawing = false;
12228 this._ctx.restore(); // Restore state before clipping.
12231 _updatePoly: function (layer, closed) {
12232 if (!this._drawing) { return; }
12235 parts = layer._parts,
12236 len = parts.length,
12239 if (!len) { return; }
12243 for (i = 0; i < len; i++) {
12244 for (j = 0, len2 = parts[i].length; j < len2; j++) {
12246 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
12253 this._fillStroke(ctx, layer);
12255 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
12258 _updateCircle: function (layer) {
12260 if (!this._drawing || layer._empty()) { return; }
12262 var p = layer._point,
12264 r = Math.max(Math.round(layer._radius), 1),
12265 s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
12273 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
12279 this._fillStroke(ctx, layer);
12282 _fillStroke: function (ctx, layer) {
12283 var options = layer.options;
12285 if (options.fill) {
12286 ctx.globalAlpha = options.fillOpacity;
12287 ctx.fillStyle = options.fillColor || options.color;
12288 ctx.fill(options.fillRule || 'evenodd');
12291 if (options.stroke && options.weight !== 0) {
12292 if (ctx.setLineDash) {
12293 ctx.setLineDash(layer.options && layer.options._dashArray || []);
12295 ctx.globalAlpha = options.opacity;
12296 ctx.lineWidth = options.weight;
12297 ctx.strokeStyle = options.color;
12298 ctx.lineCap = options.lineCap;
12299 ctx.lineJoin = options.lineJoin;
12304 // Canvas obviously doesn't have mouse events for individual drawn objects,
12305 // so we emulate that by calculating what's under the mouse on mousemove/click manually
12307 _onClick: function (e) {
12308 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
12310 for (var order = this._drawFirst; order; order = order.next) {
12311 layer = order.layer;
12312 if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
12313 clickedLayer = layer;
12316 if (clickedLayer) {
12318 this._fireEvent([clickedLayer], e);
12322 _onMouseMove: function (e) {
12323 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
12325 var point = this._map.mouseEventToLayerPoint(e);
12326 this._handleMouseHover(e, point);
12330 _handleMouseOut: function (e) {
12331 var layer = this._hoveredLayer;
12333 // if we're leaving the layer, fire mouseout
12334 removeClass(this._container, 'leaflet-interactive');
12335 this._fireEvent([layer], e, 'mouseout');
12336 this._hoveredLayer = null;
12340 _handleMouseHover: function (e, point) {
12341 var layer, candidateHoveredLayer;
12343 for (var order = this._drawFirst; order; order = order.next) {
12344 layer = order.layer;
12345 if (layer.options.interactive && layer._containsPoint(point)) {
12346 candidateHoveredLayer = layer;
12350 if (candidateHoveredLayer !== this._hoveredLayer) {
12351 this._handleMouseOut(e);
12353 if (candidateHoveredLayer) {
12354 addClass(this._container, 'leaflet-interactive'); // change cursor
12355 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
12356 this._hoveredLayer = candidateHoveredLayer;
12360 if (this._hoveredLayer) {
12361 this._fireEvent([this._hoveredLayer], e);
12365 _fireEvent: function (layers, e, type) {
12366 this._map._fireDOMEvent(e, type || e.type, layers);
12369 _bringToFront: function (layer) {
12370 var order = layer._order;
12372 if (!order) { return; }
12374 var next = order.next;
12375 var prev = order.prev;
12386 // Update first entry unless this is the
12388 this._drawFirst = next;
12391 order.prev = this._drawLast;
12392 this._drawLast.next = order;
12395 this._drawLast = order;
12397 this._requestRedraw(layer);
12400 _bringToBack: function (layer) {
12401 var order = layer._order;
12403 if (!order) { return; }
12405 var next = order.next;
12406 var prev = order.prev;
12417 // Update last entry unless this is the
12419 this._drawLast = prev;
12424 order.next = this._drawFirst;
12425 this._drawFirst.prev = order;
12426 this._drawFirst = order;
12428 this._requestRedraw(layer);
12432 // @factory L.canvas(options?: Renderer options)
12433 // Creates a Canvas renderer with the given options.
12434 function canvas$1(options) {
12435 return canvas ? new Canvas(options) : null;
12439 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
12443 var vmlCreate = (function () {
12445 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
12446 return function (name) {
12447 return document.createElement('<lvml:' + name + ' class="lvml">');
12450 return function (name) {
12451 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
12461 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
12462 * with old versions of Internet Explorer.
12465 // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
12468 _initContainer: function () {
12469 this._container = create$1('div', 'leaflet-vml-container');
12472 _update: function () {
12473 if (this._map._animatingZoom) { return; }
12474 Renderer.prototype._update.call(this);
12475 this.fire('update');
12478 _initPath: function (layer) {
12479 var container = layer._container = vmlCreate('shape');
12481 addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
12483 container.coordsize = '1 1';
12485 layer._path = vmlCreate('path');
12486 container.appendChild(layer._path);
12488 this._updateStyle(layer);
12489 this._layers[stamp(layer)] = layer;
12492 _addPath: function (layer) {
12493 var container = layer._container;
12494 this._container.appendChild(container);
12496 if (layer.options.interactive) {
12497 layer.addInteractiveTarget(container);
12501 _removePath: function (layer) {
12502 var container = layer._container;
12504 layer.removeInteractiveTarget(container);
12505 delete this._layers[stamp(layer)];
12508 _updateStyle: function (layer) {
12509 var stroke = layer._stroke,
12510 fill = layer._fill,
12511 options = layer.options,
12512 container = layer._container;
12514 container.stroked = !!options.stroke;
12515 container.filled = !!options.fill;
12517 if (options.stroke) {
12519 stroke = layer._stroke = vmlCreate('stroke');
12521 container.appendChild(stroke);
12522 stroke.weight = options.weight + 'px';
12523 stroke.color = options.color;
12524 stroke.opacity = options.opacity;
12526 if (options.dashArray) {
12527 stroke.dashStyle = isArray(options.dashArray) ?
12528 options.dashArray.join(' ') :
12529 options.dashArray.replace(/( *, *)/g, ' ');
12531 stroke.dashStyle = '';
12533 stroke.endcap = options.lineCap.replace('butt', 'flat');
12534 stroke.joinstyle = options.lineJoin;
12536 } else if (stroke) {
12537 container.removeChild(stroke);
12538 layer._stroke = null;
12541 if (options.fill) {
12543 fill = layer._fill = vmlCreate('fill');
12545 container.appendChild(fill);
12546 fill.color = options.fillColor || options.color;
12547 fill.opacity = options.fillOpacity;
12550 container.removeChild(fill);
12551 layer._fill = null;
12555 _updateCircle: function (layer) {
12556 var p = layer._point.round(),
12557 r = Math.round(layer._radius),
12558 r2 = Math.round(layer._radiusY || r);
12560 this._setPath(layer, layer._empty() ? 'M0 0' :
12561 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
12564 _setPath: function (layer, path) {
12565 layer._path.v = path;
12568 _bringToFront: function (layer) {
12569 toFront(layer._container);
12572 _bringToBack: function (layer) {
12573 toBack(layer._container);
12577 var create$2 = vml ? vmlCreate : svgCreate;
12581 * @inherits Renderer
12584 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
12585 * Inherits `Renderer`.
12587 * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
12588 * available in all web browsers, notably Android 2.x and 3.x.
12590 * Although SVG is not available on IE7 and IE8, these browsers support
12591 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
12592 * (a now deprecated technology), and the SVG renderer will fall back to VML in
12597 * Use SVG by default for all paths in the map:
12600 * var map = L.map('map', {
12601 * renderer: L.svg()
12605 * Use a SVG renderer with extra padding for specific vector geometries:
12608 * var map = L.map('map');
12609 * var myRenderer = L.svg({ padding: 0.5 });
12610 * var line = L.polyline( coordinates, { renderer: myRenderer } );
12611 * var circle = L.circle( center, { renderer: myRenderer } );
12615 var SVG = Renderer.extend({
12617 getEvents: function () {
12618 var events = Renderer.prototype.getEvents.call(this);
12619 events.zoomstart = this._onZoomStart;
12623 _initContainer: function () {
12624 this._container = create$2('svg');
12626 // makes it possible to click through svg root; we'll reset it back in individual paths
12627 this._container.setAttribute('pointer-events', 'none');
12629 this._rootGroup = create$2('g');
12630 this._container.appendChild(this._rootGroup);
12633 _destroyContainer: function () {
12634 remove(this._container);
12635 off(this._container);
12636 delete this._container;
12637 delete this._rootGroup;
12638 delete this._svgSize;
12641 _onZoomStart: function () {
12642 // Drag-then-pinch interactions might mess up the center and zoom.
12643 // In this case, the easiest way to prevent this is re-do the renderer
12644 // bounds and padding when the zooming starts.
12648 _update: function () {
12649 if (this._map._animatingZoom && this._bounds) { return; }
12651 Renderer.prototype._update.call(this);
12653 var b = this._bounds,
12654 size = b.getSize(),
12655 container = this._container;
12657 // set size of svg-container if changed
12658 if (!this._svgSize || !this._svgSize.equals(size)) {
12659 this._svgSize = size;
12660 container.setAttribute('width', size.x);
12661 container.setAttribute('height', size.y);
12664 // movement: update container viewBox so that we don't have to change coordinates of individual layers
12665 setPosition(container, b.min);
12666 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
12668 this.fire('update');
12671 // methods below are called by vector layers implementations
12673 _initPath: function (layer) {
12674 var path = layer._path = create$2('path');
12677 // @option className: String = null
12678 // Custom class name set on an element. Only for SVG renderer.
12679 if (layer.options.className) {
12680 addClass(path, layer.options.className);
12683 if (layer.options.interactive) {
12684 addClass(path, 'leaflet-interactive');
12687 this._updateStyle(layer);
12688 this._layers[stamp(layer)] = layer;
12691 _addPath: function (layer) {
12692 if (!this._rootGroup) { this._initContainer(); }
12693 this._rootGroup.appendChild(layer._path);
12694 layer.addInteractiveTarget(layer._path);
12697 _removePath: function (layer) {
12698 remove(layer._path);
12699 layer.removeInteractiveTarget(layer._path);
12700 delete this._layers[stamp(layer)];
12703 _updatePath: function (layer) {
12708 _updateStyle: function (layer) {
12709 var path = layer._path,
12710 options = layer.options;
12712 if (!path) { return; }
12714 if (options.stroke) {
12715 path.setAttribute('stroke', options.color);
12716 path.setAttribute('stroke-opacity', options.opacity);
12717 path.setAttribute('stroke-width', options.weight);
12718 path.setAttribute('stroke-linecap', options.lineCap);
12719 path.setAttribute('stroke-linejoin', options.lineJoin);
12721 if (options.dashArray) {
12722 path.setAttribute('stroke-dasharray', options.dashArray);
12724 path.removeAttribute('stroke-dasharray');
12727 if (options.dashOffset) {
12728 path.setAttribute('stroke-dashoffset', options.dashOffset);
12730 path.removeAttribute('stroke-dashoffset');
12733 path.setAttribute('stroke', 'none');
12736 if (options.fill) {
12737 path.setAttribute('fill', options.fillColor || options.color);
12738 path.setAttribute('fill-opacity', options.fillOpacity);
12739 path.setAttribute('fill-rule', options.fillRule || 'evenodd');
12741 path.setAttribute('fill', 'none');
12745 _updatePoly: function (layer, closed) {
12746 this._setPath(layer, pointsToPath(layer._parts, closed));
12749 _updateCircle: function (layer) {
12750 var p = layer._point,
12751 r = Math.max(Math.round(layer._radius), 1),
12752 r2 = Math.max(Math.round(layer._radiusY), 1) || r,
12753 arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
12755 // drawing a circle with two half-arcs
12756 var d = layer._empty() ? 'M0 0' :
12757 'M' + (p.x - r) + ',' + p.y +
12758 arc + (r * 2) + ',0 ' +
12759 arc + (-r * 2) + ',0 ';
12761 this._setPath(layer, d);
12764 _setPath: function (layer, path) {
12765 layer._path.setAttribute('d', path);
12768 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
12769 _bringToFront: function (layer) {
12770 toFront(layer._path);
12773 _bringToBack: function (layer) {
12774 toBack(layer._path);
12779 SVG.include(vmlMixin);
12783 // @factory L.svg(options?: Renderer options)
12784 // Creates a SVG renderer with the given options.
12785 function svg$1(options) {
12786 return svg || vml ? new SVG(options) : null;
12790 // @namespace Map; @method getRenderer(layer: Path): Renderer
12791 // Returns the instance of `Renderer` that should be used to render the given
12792 // `Path`. It will ensure that the `renderer` options of the map and paths
12793 // are respected, and that the renderers do exist on the map.
12794 getRenderer: function (layer) {
12795 // @namespace Path; @option renderer: Renderer
12796 // Use this specific instance of `Renderer` for this path. Takes
12797 // precedence over the map's [default renderer](#map-renderer).
12798 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
12801 renderer = this._renderer = this._createRenderer();
12804 if (!this.hasLayer(renderer)) {
12805 this.addLayer(renderer);
12810 _getPaneRenderer: function (name) {
12811 if (name === 'overlayPane' || name === undefined) {
12815 var renderer = this._paneRenderers[name];
12816 if (renderer === undefined) {
12817 renderer = this._createRenderer({pane: name});
12818 this._paneRenderers[name] = renderer;
12823 _createRenderer: function (options) {
12824 // @namespace Map; @option preferCanvas: Boolean = false
12825 // Whether `Path`s should be rendered on a `Canvas` renderer.
12826 // By default, all `Path`s are rendered in a `SVG` renderer.
12827 return (this.options.preferCanvas && canvas$1(options)) || svg$1(options);
12832 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
12838 * @inherits Polygon
12840 * A class for drawing rectangle overlays on a map. Extends `Polygon`.
12845 * // define rectangle geographical bounds
12846 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
12848 * // create an orange rectangle
12849 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
12851 * // zoom the map to the rectangle bounds
12852 * map.fitBounds(bounds);
12858 var Rectangle = Polygon.extend({
12859 initialize: function (latLngBounds, options) {
12860 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
12863 // @method setBounds(latLngBounds: LatLngBounds): this
12864 // Redraws the rectangle with the passed bounds.
12865 setBounds: function (latLngBounds) {
12866 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
12869 _boundsToLatLngs: function (latLngBounds) {
12870 latLngBounds = toLatLngBounds(latLngBounds);
12872 latLngBounds.getSouthWest(),
12873 latLngBounds.getNorthWest(),
12874 latLngBounds.getNorthEast(),
12875 latLngBounds.getSouthEast()
12881 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
12882 function rectangle(latLngBounds, options) {
12883 return new Rectangle(latLngBounds, options);
12886 SVG.create = create$2;
12887 SVG.pointsToPath = pointsToPath;
12889 GeoJSON.geometryToLayer = geometryToLayer;
12890 GeoJSON.coordsToLatLng = coordsToLatLng;
12891 GeoJSON.coordsToLatLngs = coordsToLatLngs;
12892 GeoJSON.latLngToCoords = latLngToCoords;
12893 GeoJSON.latLngsToCoords = latLngsToCoords;
12894 GeoJSON.getFeature = getFeature;
12895 GeoJSON.asFeature = asFeature;
12898 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
12899 * (zoom to a selected bounding box), enabled by default.
12903 // @section Interaction Options
12905 // @option boxZoom: Boolean = true
12906 // Whether the map can be zoomed to a rectangular area specified by
12907 // dragging the mouse while pressing the shift key.
12911 var BoxZoom = Handler.extend({
12912 initialize: function (map) {
12914 this._container = map._container;
12915 this._pane = map._panes.overlayPane;
12916 this._resetStateTimeout = 0;
12917 map.on('unload', this._destroy, this);
12920 addHooks: function () {
12921 on(this._container, 'mousedown', this._onMouseDown, this);
12924 removeHooks: function () {
12925 off(this._container, 'mousedown', this._onMouseDown, this);
12928 moved: function () {
12929 return this._moved;
12932 _destroy: function () {
12933 remove(this._pane);
12937 _resetState: function () {
12938 this._resetStateTimeout = 0;
12939 this._moved = false;
12942 _clearDeferredResetState: function () {
12943 if (this._resetStateTimeout !== 0) {
12944 clearTimeout(this._resetStateTimeout);
12945 this._resetStateTimeout = 0;
12949 _onMouseDown: function (e) {
12950 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
12952 // Clear the deferred resetState if it hasn't executed yet, otherwise it
12953 // will interrupt the interaction and orphan a box element in the container.
12954 this._clearDeferredResetState();
12955 this._resetState();
12957 disableTextSelection();
12958 disableImageDrag();
12960 this._startPoint = this._map.mouseEventToContainerPoint(e);
12964 mousemove: this._onMouseMove,
12965 mouseup: this._onMouseUp,
12966 keydown: this._onKeyDown
12970 _onMouseMove: function (e) {
12971 if (!this._moved) {
12972 this._moved = true;
12974 this._box = create$1('div', 'leaflet-zoom-box', this._container);
12975 addClass(this._container, 'leaflet-crosshair');
12977 this._map.fire('boxzoomstart');
12980 this._point = this._map.mouseEventToContainerPoint(e);
12982 var bounds = new Bounds(this._point, this._startPoint),
12983 size = bounds.getSize();
12985 setPosition(this._box, bounds.min);
12987 this._box.style.width = size.x + 'px';
12988 this._box.style.height = size.y + 'px';
12991 _finish: function () {
12994 removeClass(this._container, 'leaflet-crosshair');
12997 enableTextSelection();
13002 mousemove: this._onMouseMove,
13003 mouseup: this._onMouseUp,
13004 keydown: this._onKeyDown
13008 _onMouseUp: function (e) {
13009 if ((e.which !== 1) && (e.button !== 1)) { return; }
13013 if (!this._moved) { return; }
13014 // Postpone to next JS tick so internal click event handling
13015 // still see it as "moved".
13016 this._clearDeferredResetState();
13017 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
13019 var bounds = new LatLngBounds(
13020 this._map.containerPointToLatLng(this._startPoint),
13021 this._map.containerPointToLatLng(this._point));
13025 .fire('boxzoomend', {boxZoomBounds: bounds});
13028 _onKeyDown: function (e) {
13029 if (e.keyCode === 27) {
13035 // @section Handlers
13036 // @property boxZoom: Handler
13037 // Box (shift-drag with mouse) zoom handler.
13038 Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
13041 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
13045 // @section Interaction Options
13048 // @option doubleClickZoom: Boolean|String = true
13049 // Whether the map can be zoomed in by double clicking on it and
13050 // zoomed out by double clicking while holding shift. If passed
13051 // `'center'`, double-click zoom will zoom to the center of the
13052 // view regardless of where the mouse was.
13053 doubleClickZoom: true
13056 var DoubleClickZoom = Handler.extend({
13057 addHooks: function () {
13058 this._map.on('dblclick', this._onDoubleClick, this);
13061 removeHooks: function () {
13062 this._map.off('dblclick', this._onDoubleClick, this);
13065 _onDoubleClick: function (e) {
13066 var map = this._map,
13067 oldZoom = map.getZoom(),
13068 delta = map.options.zoomDelta,
13069 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
13071 if (map.options.doubleClickZoom === 'center') {
13074 map.setZoomAround(e.containerPoint, zoom);
13079 // @section Handlers
13081 // Map properties include interaction handlers that allow you to control
13082 // interaction behavior in runtime, enabling or disabling certain features such
13083 // as dragging or touch zoom (see `Handler` methods). For example:
13086 // map.doubleClickZoom.disable();
13089 // @property doubleClickZoom: Handler
13090 // Double click zoom handler.
13091 Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
13094 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
13098 // @section Interaction Options
13100 // @option dragging: Boolean = true
13101 // Whether the map be draggable with mouse/touch or not.
13104 // @section Panning Inertia Options
13105 // @option inertia: Boolean = *
13106 // If enabled, panning of the map will have an inertia effect where
13107 // the map builds momentum while dragging and continues moving in
13108 // the same direction for some time. Feels especially nice on touch
13109 // devices. Enabled by default unless running on old Android devices.
13110 inertia: !android23,
13112 // @option inertiaDeceleration: Number = 3000
13113 // The rate with which the inertial movement slows down, in pixels/second².
13114 inertiaDeceleration: 3400, // px/s^2
13116 // @option inertiaMaxSpeed: Number = Infinity
13117 // Max speed of the inertial movement, in pixels/second.
13118 inertiaMaxSpeed: Infinity, // px/s
13120 // @option easeLinearity: Number = 0.2
13121 easeLinearity: 0.2,
13123 // TODO refactor, move to CRS
13124 // @option worldCopyJump: Boolean = false
13125 // With this option enabled, the map tracks when you pan to another "copy"
13126 // of the world and seamlessly jumps to the original one so that all overlays
13127 // like markers and vector layers are still visible.
13128 worldCopyJump: false,
13130 // @option maxBoundsViscosity: Number = 0.0
13131 // If `maxBounds` is set, this option will control how solid the bounds
13132 // are when dragging the map around. The default value of `0.0` allows the
13133 // user to drag outside the bounds at normal speed, higher values will
13134 // slow down map dragging outside bounds, and `1.0` makes the bounds fully
13135 // solid, preventing the user from dragging outside the bounds.
13136 maxBoundsViscosity: 0.0
13139 var Drag = Handler.extend({
13140 addHooks: function () {
13141 if (!this._draggable) {
13142 var map = this._map;
13144 this._draggable = new Draggable(map._mapPane, map._container);
13146 this._draggable.on({
13147 dragstart: this._onDragStart,
13148 drag: this._onDrag,
13149 dragend: this._onDragEnd
13152 this._draggable.on('predrag', this._onPreDragLimit, this);
13153 if (map.options.worldCopyJump) {
13154 this._draggable.on('predrag', this._onPreDragWrap, this);
13155 map.on('zoomend', this._onZoomEnd, this);
13157 map.whenReady(this._onZoomEnd, this);
13160 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
13161 this._draggable.enable();
13162 this._positions = [];
13166 removeHooks: function () {
13167 removeClass(this._map._container, 'leaflet-grab');
13168 removeClass(this._map._container, 'leaflet-touch-drag');
13169 this._draggable.disable();
13172 moved: function () {
13173 return this._draggable && this._draggable._moved;
13176 moving: function () {
13177 return this._draggable && this._draggable._moving;
13180 _onDragStart: function () {
13181 var map = this._map;
13184 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
13185 var bounds = toLatLngBounds(this._map.options.maxBounds);
13187 this._offsetLimit = toBounds(
13188 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
13189 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
13190 .add(this._map.getSize()));
13192 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
13194 this._offsetLimit = null;
13199 .fire('dragstart');
13201 if (map.options.inertia) {
13202 this._positions = [];
13207 _onDrag: function (e) {
13208 if (this._map.options.inertia) {
13209 var time = this._lastTime = +new Date(),
13210 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
13212 this._positions.push(pos);
13213 this._times.push(time);
13215 this._prunePositions(time);
13223 _prunePositions: function (time) {
13224 while (this._positions.length > 1 && time - this._times[0] > 50) {
13225 this._positions.shift();
13226 this._times.shift();
13230 _onZoomEnd: function () {
13231 var pxCenter = this._map.getSize().divideBy(2),
13232 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
13234 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
13235 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
13238 _viscousLimit: function (value, threshold) {
13239 return value - (value - threshold) * this._viscosity;
13242 _onPreDragLimit: function () {
13243 if (!this._viscosity || !this._offsetLimit) { return; }
13245 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
13247 var limit = this._offsetLimit;
13248 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
13249 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
13250 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
13251 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
13253 this._draggable._newPos = this._draggable._startPos.add(offset);
13256 _onPreDragWrap: function () {
13257 // TODO refactor to be able to adjust map pane position after zoom
13258 var worldWidth = this._worldWidth,
13259 halfWidth = Math.round(worldWidth / 2),
13260 dx = this._initialWorldOffset,
13261 x = this._draggable._newPos.x,
13262 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
13263 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
13264 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
13266 this._draggable._absPos = this._draggable._newPos.clone();
13267 this._draggable._newPos.x = newX;
13270 _onDragEnd: function (e) {
13271 var map = this._map,
13272 options = map.options,
13274 noInertia = !options.inertia || this._times.length < 2;
13276 map.fire('dragend', e);
13279 map.fire('moveend');
13282 this._prunePositions(+new Date());
13284 var direction = this._lastPos.subtract(this._positions[0]),
13285 duration = (this._lastTime - this._times[0]) / 1000,
13286 ease = options.easeLinearity,
13288 speedVector = direction.multiplyBy(ease / duration),
13289 speed = speedVector.distanceTo([0, 0]),
13291 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
13292 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
13294 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
13295 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
13297 if (!offset.x && !offset.y) {
13298 map.fire('moveend');
13301 offset = map._limitOffset(offset, map.options.maxBounds);
13303 requestAnimFrame(function () {
13304 map.panBy(offset, {
13305 duration: decelerationDuration,
13306 easeLinearity: ease,
13316 // @section Handlers
13317 // @property dragging: Handler
13318 // Map dragging handler (by both mouse and touch).
13319 Map.addInitHook('addHandler', 'dragging', Drag);
13322 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
13326 // @section Keyboard Navigation Options
13328 // @option keyboard: Boolean = true
13329 // Makes the map focusable and allows users to navigate the map with keyboard
13330 // arrows and `+`/`-` keys.
13333 // @option keyboardPanDelta: Number = 80
13334 // Amount of pixels to pan when pressing an arrow key.
13335 keyboardPanDelta: 80
13338 var Keyboard = Handler.extend({
13345 zoomIn: [187, 107, 61, 171],
13346 zoomOut: [189, 109, 54, 173]
13349 initialize: function (map) {
13352 this._setPanDelta(map.options.keyboardPanDelta);
13353 this._setZoomDelta(map.options.zoomDelta);
13356 addHooks: function () {
13357 var container = this._map._container;
13359 // make the container focusable by tabbing
13360 if (container.tabIndex <= 0) {
13361 container.tabIndex = '0';
13365 focus: this._onFocus,
13366 blur: this._onBlur,
13367 mousedown: this._onMouseDown
13371 focus: this._addHooks,
13372 blur: this._removeHooks
13376 removeHooks: function () {
13377 this._removeHooks();
13379 off(this._map._container, {
13380 focus: this._onFocus,
13381 blur: this._onBlur,
13382 mousedown: this._onMouseDown
13386 focus: this._addHooks,
13387 blur: this._removeHooks
13391 _onMouseDown: function () {
13392 if (this._focused) { return; }
13394 var body = document.body,
13395 docEl = document.documentElement,
13396 top = body.scrollTop || docEl.scrollTop,
13397 left = body.scrollLeft || docEl.scrollLeft;
13399 this._map._container.focus();
13401 window.scrollTo(left, top);
13404 _onFocus: function () {
13405 this._focused = true;
13406 this._map.fire('focus');
13409 _onBlur: function () {
13410 this._focused = false;
13411 this._map.fire('blur');
13414 _setPanDelta: function (panDelta) {
13415 var keys = this._panKeys = {},
13416 codes = this.keyCodes,
13419 for (i = 0, len = codes.left.length; i < len; i++) {
13420 keys[codes.left[i]] = [-1 * panDelta, 0];
13422 for (i = 0, len = codes.right.length; i < len; i++) {
13423 keys[codes.right[i]] = [panDelta, 0];
13425 for (i = 0, len = codes.down.length; i < len; i++) {
13426 keys[codes.down[i]] = [0, panDelta];
13428 for (i = 0, len = codes.up.length; i < len; i++) {
13429 keys[codes.up[i]] = [0, -1 * panDelta];
13433 _setZoomDelta: function (zoomDelta) {
13434 var keys = this._zoomKeys = {},
13435 codes = this.keyCodes,
13438 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
13439 keys[codes.zoomIn[i]] = zoomDelta;
13441 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
13442 keys[codes.zoomOut[i]] = -zoomDelta;
13446 _addHooks: function () {
13447 on(document, 'keydown', this._onKeyDown, this);
13450 _removeHooks: function () {
13451 off(document, 'keydown', this._onKeyDown, this);
13454 _onKeyDown: function (e) {
13455 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
13457 var key = e.keyCode,
13461 if (key in this._panKeys) {
13462 if (!map._panAnim || !map._panAnim._inProgress) {
13463 offset = this._panKeys[key];
13465 offset = toPoint(offset).multiplyBy(3);
13470 if (map.options.maxBounds) {
13471 map.panInsideBounds(map.options.maxBounds);
13474 } else if (key in this._zoomKeys) {
13475 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
13477 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
13488 // @section Handlers
13489 // @section Handlers
13490 // @property keyboard: Handler
13491 // Keyboard navigation handler.
13492 Map.addInitHook('addHandler', 'keyboard', Keyboard);
13495 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
13499 // @section Interaction Options
13501 // @section Mousewheel options
13502 // @option scrollWheelZoom: Boolean|String = true
13503 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
13504 // it will zoom to the center of the view regardless of where the mouse was.
13505 scrollWheelZoom: true,
13507 // @option wheelDebounceTime: Number = 40
13508 // Limits the rate at which a wheel can fire (in milliseconds). By default
13509 // user can't zoom via wheel more often than once per 40 ms.
13510 wheelDebounceTime: 40,
13512 // @option wheelPxPerZoomLevel: Number = 60
13513 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
13514 // mean a change of one full zoom level. Smaller values will make wheel-zooming
13515 // faster (and vice versa).
13516 wheelPxPerZoomLevel: 60
13519 var ScrollWheelZoom = Handler.extend({
13520 addHooks: function () {
13521 on(this._map._container, 'mousewheel', this._onWheelScroll, this);
13526 removeHooks: function () {
13527 off(this._map._container, 'mousewheel', this._onWheelScroll, this);
13530 _onWheelScroll: function (e) {
13531 var delta = getWheelDelta(e);
13533 var debounce = this._map.options.wheelDebounceTime;
13535 this._delta += delta;
13536 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
13538 if (!this._startTime) {
13539 this._startTime = +new Date();
13542 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
13544 clearTimeout(this._timer);
13545 this._timer = setTimeout(bind(this._performZoom, this), left);
13550 _performZoom: function () {
13551 var map = this._map,
13552 zoom = map.getZoom(),
13553 snap = this._map.options.zoomSnap || 0;
13555 map._stop(); // stop panning and fly animations if any
13557 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
13558 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
13559 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
13560 d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
13561 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
13564 this._startTime = null;
13566 if (!delta) { return; }
13568 if (map.options.scrollWheelZoom === 'center') {
13569 map.setZoom(zoom + delta);
13571 map.setZoomAround(this._lastMousePos, zoom + delta);
13576 // @section Handlers
13577 // @property scrollWheelZoom: Handler
13578 // Scroll wheel zoom handler.
13579 Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
13582 * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
13586 // @section Interaction Options
13588 // @section Touch interaction options
13589 // @option tap: Boolean = true
13590 // Enables mobile hacks for supporting instant taps (fixing 200ms click
13591 // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
13594 // @option tapTolerance: Number = 15
13595 // The max number of pixels a user can shift his finger during touch
13596 // for it to be considered a valid tap.
13600 var Tap = Handler.extend({
13601 addHooks: function () {
13602 on(this._map._container, 'touchstart', this._onDown, this);
13605 removeHooks: function () {
13606 off(this._map._container, 'touchstart', this._onDown, this);
13609 _onDown: function (e) {
13610 if (!e.touches) { return; }
13614 this._fireClick = true;
13616 // don't simulate click or track longpress if more than 1 touch
13617 if (e.touches.length > 1) {
13618 this._fireClick = false;
13619 clearTimeout(this._holdTimeout);
13623 var first = e.touches[0],
13626 this._startPos = this._newPos = new Point(first.clientX, first.clientY);
13628 // if touching a link, highlight it
13629 if (el.tagName && el.tagName.toLowerCase() === 'a') {
13630 addClass(el, 'leaflet-active');
13633 // simulate long hold but setting a timeout
13634 this._holdTimeout = setTimeout(bind(function () {
13635 if (this._isTapValid()) {
13636 this._fireClick = false;
13638 this._simulateEvent('contextmenu', first);
13642 this._simulateEvent('mousedown', first);
13645 touchmove: this._onMove,
13646 touchend: this._onUp
13650 _onUp: function (e) {
13651 clearTimeout(this._holdTimeout);
13654 touchmove: this._onMove,
13655 touchend: this._onUp
13658 if (this._fireClick && e && e.changedTouches) {
13660 var first = e.changedTouches[0],
13663 if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
13664 removeClass(el, 'leaflet-active');
13667 this._simulateEvent('mouseup', first);
13669 // simulate click if the touch didn't move too much
13670 if (this._isTapValid()) {
13671 this._simulateEvent('click', first);
13676 _isTapValid: function () {
13677 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
13680 _onMove: function (e) {
13681 var first = e.touches[0];
13682 this._newPos = new Point(first.clientX, first.clientY);
13683 this._simulateEvent('mousemove', first);
13686 _simulateEvent: function (type, e) {
13687 var simulatedEvent = document.createEvent('MouseEvents');
13689 simulatedEvent._simulated = true;
13690 e.target._simulatedClick = true;
13692 simulatedEvent.initMouseEvent(
13693 type, true, true, window, 1,
13694 e.screenX, e.screenY,
13695 e.clientX, e.clientY,
13696 false, false, false, false, 0, null);
13698 e.target.dispatchEvent(simulatedEvent);
13702 // @section Handlers
13703 // @property tap: Handler
13704 // Mobile touch hacks (quick tap and touch hold) handler.
13705 if (touch && !pointer) {
13706 Map.addInitHook('addHandler', 'tap', Tap);
13710 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
13714 // @section Interaction Options
13716 // @section Touch interaction options
13717 // @option touchZoom: Boolean|String = *
13718 // Whether the map can be zoomed by touch-dragging with two fingers. If
13719 // passed `'center'`, it will zoom to the center of the view regardless of
13720 // where the touch events (fingers) were. Enabled for touch-capable web
13721 // browsers except for old Androids.
13722 touchZoom: touch && !android23,
13724 // @option bounceAtZoomLimits: Boolean = true
13725 // Set it to false if you don't want the map to zoom beyond min/max zoom
13726 // and then bounce back when pinch-zooming.
13727 bounceAtZoomLimits: true
13730 var TouchZoom = Handler.extend({
13731 addHooks: function () {
13732 addClass(this._map._container, 'leaflet-touch-zoom');
13733 on(this._map._container, 'touchstart', this._onTouchStart, this);
13736 removeHooks: function () {
13737 removeClass(this._map._container, 'leaflet-touch-zoom');
13738 off(this._map._container, 'touchstart', this._onTouchStart, this);
13741 _onTouchStart: function (e) {
13742 var map = this._map;
13743 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
13745 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
13746 p2 = map.mouseEventToContainerPoint(e.touches[1]);
13748 this._centerPoint = map.getSize()._divideBy(2);
13749 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
13750 if (map.options.touchZoom !== 'center') {
13751 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
13754 this._startDist = p1.distanceTo(p2);
13755 this._startZoom = map.getZoom();
13757 this._moved = false;
13758 this._zooming = true;
13762 on(document, 'touchmove', this._onTouchMove, this);
13763 on(document, 'touchend', this._onTouchEnd, this);
13768 _onTouchMove: function (e) {
13769 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
13771 var map = this._map,
13772 p1 = map.mouseEventToContainerPoint(e.touches[0]),
13773 p2 = map.mouseEventToContainerPoint(e.touches[1]),
13774 scale = p1.distanceTo(p2) / this._startDist;
13776 this._zoom = map.getScaleZoom(scale, this._startZoom);
13778 if (!map.options.bounceAtZoomLimits && (
13779 (this._zoom < map.getMinZoom() && scale < 1) ||
13780 (this._zoom > map.getMaxZoom() && scale > 1))) {
13781 this._zoom = map._limitZoom(this._zoom);
13784 if (map.options.touchZoom === 'center') {
13785 this._center = this._startLatLng;
13786 if (scale === 1) { return; }
13788 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
13789 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
13790 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
13791 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
13794 if (!this._moved) {
13795 map._moveStart(true, false);
13796 this._moved = true;
13799 cancelAnimFrame(this._animRequest);
13801 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
13802 this._animRequest = requestAnimFrame(moveFn, this, true);
13807 _onTouchEnd: function () {
13808 if (!this._moved || !this._zooming) {
13809 this._zooming = false;
13813 this._zooming = false;
13814 cancelAnimFrame(this._animRequest);
13816 off(document, 'touchmove', this._onTouchMove);
13817 off(document, 'touchend', this._onTouchEnd);
13819 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
13820 if (this._map.options.zoomAnimation) {
13821 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
13823 this._map._resetView(this._center, this._map._limitZoom(this._zoom));
13828 // @section Handlers
13829 // @property touchZoom: Handler
13830 // Touch zoom handler.
13831 Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
13833 Map.BoxZoom = BoxZoom;
13834 Map.DoubleClickZoom = DoubleClickZoom;
13836 Map.Keyboard = Keyboard;
13837 Map.ScrollWheelZoom = ScrollWheelZoom;
13839 Map.TouchZoom = TouchZoom;
13841 Object.freeze = freeze;
13843 exports.version = version;
13844 exports.Control = Control;
13845 exports.control = control;
13846 exports.Browser = Browser;
13847 exports.Evented = Evented;
13848 exports.Mixin = Mixin;
13849 exports.Util = Util;
13850 exports.Class = Class;
13851 exports.Handler = Handler;
13852 exports.extend = extend;
13853 exports.bind = bind;
13854 exports.stamp = stamp;
13855 exports.setOptions = setOptions;
13856 exports.DomEvent = DomEvent;
13857 exports.DomUtil = DomUtil;
13858 exports.PosAnimation = PosAnimation;
13859 exports.Draggable = Draggable;
13860 exports.LineUtil = LineUtil;
13861 exports.PolyUtil = PolyUtil;
13862 exports.Point = Point;
13863 exports.point = toPoint;
13864 exports.Bounds = Bounds;
13865 exports.bounds = toBounds;
13866 exports.Transformation = Transformation;
13867 exports.transformation = toTransformation;
13868 exports.Projection = index;
13869 exports.LatLng = LatLng;
13870 exports.latLng = toLatLng;
13871 exports.LatLngBounds = LatLngBounds;
13872 exports.latLngBounds = toLatLngBounds;
13874 exports.GeoJSON = GeoJSON;
13875 exports.geoJSON = geoJSON;
13876 exports.geoJson = geoJson;
13877 exports.Layer = Layer;
13878 exports.LayerGroup = LayerGroup;
13879 exports.layerGroup = layerGroup;
13880 exports.FeatureGroup = FeatureGroup;
13881 exports.featureGroup = featureGroup;
13882 exports.ImageOverlay = ImageOverlay;
13883 exports.imageOverlay = imageOverlay;
13884 exports.VideoOverlay = VideoOverlay;
13885 exports.videoOverlay = videoOverlay;
13886 exports.DivOverlay = DivOverlay;
13887 exports.Popup = Popup;
13888 exports.popup = popup;
13889 exports.Tooltip = Tooltip;
13890 exports.tooltip = tooltip;
13891 exports.Icon = Icon;
13892 exports.icon = icon;
13893 exports.DivIcon = DivIcon;
13894 exports.divIcon = divIcon;
13895 exports.Marker = Marker;
13896 exports.marker = marker;
13897 exports.TileLayer = TileLayer;
13898 exports.tileLayer = tileLayer;
13899 exports.GridLayer = GridLayer;
13900 exports.gridLayer = gridLayer;
13902 exports.svg = svg$1;
13903 exports.Renderer = Renderer;
13904 exports.Canvas = Canvas;
13905 exports.canvas = canvas$1;
13906 exports.Path = Path;
13907 exports.CircleMarker = CircleMarker;
13908 exports.circleMarker = circleMarker;
13909 exports.Circle = Circle;
13910 exports.circle = circle;
13911 exports.Polyline = Polyline;
13912 exports.polyline = polyline;
13913 exports.Polygon = Polygon;
13914 exports.polygon = polygon;
13915 exports.Rectangle = Rectangle;
13916 exports.rectangle = rectangle;
13918 exports.map = createMap;
13920 var oldL = window.L;
13921 exports.noConflict = function() {
13926 // Always export us to window global (see #2364)
13927 window.L = exports;
13930 //# sourceMappingURL=leaflet-src.js.map