2 * Leaflet 1.1.0, a JS library for interactive maps. http://leafletjs.com
3 * (c) 2010-2017 Vladimir Agafonkin, (c) 2010-2011 CloudMade
5 (function (global, factory) {
6 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
7 typeof define === 'function' && define.amd ? define(['exports'], factory) :
8 (factory((global.L = global.L || {})));
9 }(this, (function (exports) { 'use strict';
11 var version = "1.1.0";
16 * Various utility functions, used by Leaflet internally.
19 // @function extend(dest: Object, src?: Object): Object
20 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
21 function extend(dest) {
24 for (j = 1, len = arguments.length; j < len; j++) {
33 // @function create(proto: Object, properties?: Object): Object
34 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
35 var create = Object.create || (function () {
37 return function (proto) {
43 // @function bind(fn: Function, …): Function
44 // 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).
45 // Has a `L.bind()` shortcut.
46 function bind(fn, obj) {
47 var slice = Array.prototype.slice;
50 return fn.bind.apply(fn, slice.call(arguments, 1));
53 var args = slice.call(arguments, 2);
56 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
60 // @property lastId: Number
61 // Last unique ID used by [`stamp()`](#util-stamp)
64 // @function stamp(obj: Object): Number
65 // Returns the unique ID of an object, assiging it one if it doesn't have it.
68 obj._leaflet_id = obj._leaflet_id || ++lastId;
69 return obj._leaflet_id;
73 // @function throttle(fn: Function, time: Number, context: Object): Function
74 // Returns a function which executes function `fn` with the given scope `context`
75 // (so that the `this` keyword refers to `context` inside `fn`'s code). The function
76 // `fn` will be called no more than one time per given amount of `time`. The arguments
77 // received by the bound function will be any arguments passed when binding the
78 // function, followed by any arguments passed when invoking the bound function.
79 // Has an `L.throttle` shortcut.
80 function throttle(fn, time, context) {
81 var lock, args, wrapperFn, later;
84 // reset lock and call if queued
87 wrapperFn.apply(context, args);
92 wrapperFn = function () {
94 // called too soon, queue to call later
98 // call and lock until later
99 fn.apply(context, arguments);
100 setTimeout(later, time);
108 // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
109 // Returns the number `num` modulo `range` in such a way so it lies within
110 // `range[0]` and `range[1]`. The returned value will be always smaller than
111 // `range[1]` unless `includeMax` is set to `true`.
112 function wrapNum(x, range, includeMax) {
116 return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
119 // @function falseFn(): Function
120 // Returns a function which always returns `false`.
121 function falseFn() { return false; }
123 // @function formatNum(num: Number, digits?: Number): Number
124 // Returns the number `num` rounded to `digits` decimals, or to 5 decimals by default.
125 function formatNum(num, digits) {
126 var pow = Math.pow(10, digits || 5);
127 return Math.round(num * pow) / pow;
130 // @function trim(str: String): String
131 // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
133 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
136 // @function splitWords(str: String): String[]
137 // Trims and splits the string on whitespace and returns the array of parts.
138 function splitWords(str) {
139 return trim(str).split(/\s+/);
142 // @function setOptions(obj: Object, options: Object): Object
143 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
144 function setOptions(obj, options) {
145 if (!obj.hasOwnProperty('options')) {
146 obj.options = obj.options ? create(obj.options) : {};
148 for (var i in options) {
149 obj.options[i] = options[i];
154 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
155 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
156 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
157 // be appended at the end. If `uppercase` is `true`, the parameter names will
158 // be uppercased (e.g. `'?A=foo&B=bar'`)
159 function getParamString(obj, existingUrl, uppercase) {
162 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
164 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
167 var templateRe = /\{ *([\w_\-]+) *\}/g;
169 // @function template(str: String, data: Object): String
170 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
171 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
172 // `('Hello foo, bar')`. You can also specify functions instead of strings for
173 // data values — they will be evaluated passing `data` as an argument.
174 function template(str, data) {
175 return str.replace(templateRe, function (str, key) {
176 var value = data[key];
178 if (value === undefined) {
179 throw new Error('No value provided for variable ' + str);
181 } else if (typeof value === 'function') {
188 // @function isArray(obj): Boolean
189 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
190 var isArray = Array.isArray || function (obj) {
191 return (Object.prototype.toString.call(obj) === '[object Array]');
194 // @function indexOf(array: Array, el: Object): Number
195 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
196 function indexOf(array, el) {
197 for (var i = 0; i < array.length; i++) {
198 if (array[i] === el) { return i; }
203 // @property emptyImageUrl: String
204 // Data URI string containing a base64-encoded empty GIF image.
205 // Used as a hack to free memory from unused images on WebKit-powered
206 // mobile devices (by setting image `src` to this string).
207 var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
209 // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
211 function getPrefixed(name) {
212 return window['webkit' + name] || window['moz' + name] || window['ms' + name];
217 // fallback for IE 7-8
218 function timeoutDefer(fn) {
219 var time = +new Date(),
220 timeToCall = Math.max(0, 16 - (time - lastTime));
222 lastTime = time + timeToCall;
223 return window.setTimeout(fn, timeToCall);
226 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
227 var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
228 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
230 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
231 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
232 // `context` if given. When `immediate` is set, `fn` is called immediately if
233 // the browser doesn't have native support for
234 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
235 // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
236 function requestAnimFrame(fn, context, immediate) {
237 if (immediate && requestFn === timeoutDefer) {
240 return requestFn.call(window, bind(fn, context));
244 // @function cancelAnimFrame(id: Number): undefined
245 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
246 function cancelAnimFrame(id) {
248 cancelFn.call(window, id);
253 var Util = (Object.freeze || Object)({
262 formatNum: formatNum,
264 splitWords: splitWords,
265 setOptions: setOptions,
266 getParamString: getParamString,
270 emptyImageUrl: emptyImageUrl,
271 requestFn: requestFn,
273 requestAnimFrame: requestAnimFrame,
274 cancelAnimFrame: cancelAnimFrame
283 // Thanks to John Resig and Dean Edwards for inspiration!
287 Class.extend = function (props) {
289 // @function extend(props: Object): Function
290 // [Extends the current class](#class-inheritance) given the properties to be included.
291 // Returns a Javascript function that is a class constructor (to be called with `new`).
292 var NewClass = function () {
294 // call the constructor
295 if (this.initialize) {
296 this.initialize.apply(this, arguments);
299 // call all constructor hooks
300 this.callInitHooks();
303 var parentProto = NewClass.__super__ = this.prototype;
305 var proto = create(parentProto);
306 proto.constructor = NewClass;
308 NewClass.prototype = proto;
310 // inherit parent's statics
311 for (var i in this) {
312 if (this.hasOwnProperty(i) && i !== 'prototype' && i !== '__super__') {
313 NewClass[i] = this[i];
317 // mix static properties into the class
319 extend(NewClass, props.statics);
320 delete props.statics;
323 // mix includes into the prototype
324 if (props.includes) {
325 checkDeprecatedMixinEvents(props.includes);
326 extend.apply(null, [proto].concat(props.includes));
327 delete props.includes;
332 props.options = extend(create(proto.options), props.options);
335 // mix given properties into the prototype
336 extend(proto, props);
338 proto._initHooks = [];
340 // add method for calling all hooks
341 proto.callInitHooks = function () {
343 if (this._initHooksCalled) { return; }
345 if (parentProto.callInitHooks) {
346 parentProto.callInitHooks.call(this);
349 this._initHooksCalled = true;
351 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
352 proto._initHooks[i].call(this);
360 // @function include(properties: Object): this
361 // [Includes a mixin](#class-includes) into the current class.
362 Class.include = function (props) {
363 extend(this.prototype, props);
367 // @function mergeOptions(options: Object): this
368 // [Merges `options`](#class-options) into the defaults of the class.
369 Class.mergeOptions = function (options) {
370 extend(this.prototype.options, options);
374 // @function addInitHook(fn: Function): this
375 // Adds a [constructor hook](#class-constructor-hooks) to the class.
376 Class.addInitHook = function (fn) { // (Function) || (String, args...)
377 var args = Array.prototype.slice.call(arguments, 1);
379 var init = typeof fn === 'function' ? fn : function () {
380 this[fn].apply(this, args);
383 this.prototype._initHooks = this.prototype._initHooks || [];
384 this.prototype._initHooks.push(init);
388 function checkDeprecatedMixinEvents(includes) {
389 if (!L || !L.Mixin) { return; }
391 includes = isArray(includes) ? includes : [includes];
393 for (var i = 0; i < includes.length; i++) {
394 if (includes[i] === L.Mixin.Events) {
395 console.warn('Deprecated include of L.Mixin.Events: ' +
396 'this property will be removed in future releases, ' +
397 'please inherit from L.Evented instead.', new Error().stack);
407 * 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).
412 * map.on('click', function(e) {
417 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
420 * function onClick(e) { ... }
422 * map.on('click', onClick);
423 * map.off('click', onClick);
428 /* @method on(type: String, fn: Function, context?: Object): this
429 * 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'`).
432 * @method on(eventMap: Object): this
433 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
435 on: function (types, fn, context) {
437 // types can be a map of types/handlers
438 if (typeof types === 'object') {
439 for (var type in types) {
440 // we don't process space-separated events here for performance;
441 // it's a hot path since Layer uses the on(obj) syntax
442 this._on(type, types[type], fn);
446 // types can be a string of space-separated words
447 types = splitWords(types);
449 for (var i = 0, len = types.length; i < len; i++) {
450 this._on(types[i], fn, context);
457 /* @method off(type: String, fn?: Function, context?: Object): this
458 * 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.
461 * @method off(eventMap: Object): this
462 * Removes a set of type/listener pairs.
466 * Removes all listeners to all events on the object.
468 off: function (types, fn, context) {
471 // clear all listeners if called without arguments
474 } else if (typeof types === 'object') {
475 for (var type in types) {
476 this._off(type, types[type], fn);
480 types = splitWords(types);
482 for (var i = 0, len = types.length; i < len; i++) {
483 this._off(types[i], fn, context);
490 // attach listener (without syntactic sugar now)
491 _on: function (type, fn, context) {
492 this._events = this._events || {};
494 /* get/init listeners for type */
495 var typeListeners = this._events[type];
496 if (!typeListeners) {
498 this._events[type] = typeListeners;
501 if (context === this) {
502 // Less memory footprint.
505 var newListener = {fn: fn, ctx: context},
506 listeners = typeListeners;
508 // check if fn already there
509 for (var i = 0, len = listeners.length; i < len; i++) {
510 if (listeners[i].fn === fn && listeners[i].ctx === context) {
515 listeners.push(newListener);
518 _off: function (type, fn, context) {
523 if (!this._events) { return; }
525 listeners = this._events[type];
532 // Set all removed listeners to noop so they are not called if remove happens in fire
533 for (i = 0, len = listeners.length; i < len; i++) {
534 listeners[i].fn = falseFn;
536 // clear all listeners for a type if function isn't specified
537 delete this._events[type];
541 if (context === this) {
547 // find fn and remove it
548 for (i = 0, len = listeners.length; i < len; i++) {
549 var l = listeners[i];
550 if (l.ctx !== context) { continue; }
553 // set the removed listener to noop so that's not called if remove happens in fire
556 if (this._firingCount) {
557 /* copy array in case events are being fired */
558 this._events[type] = listeners = listeners.slice();
560 listeners.splice(i, 1);
568 // @method fire(type: String, data?: Object, propagate?: Boolean): this
569 // Fires an event of the specified type. You can optionally provide an data
570 // object — the first argument of the listener function will contain its
571 // properties. The event can optionally be propagated to event parents.
572 fire: function (type, data, propagate) {
573 if (!this.listens(type, propagate)) { return this; }
575 var event = extend({}, data, {type: type, target: this});
578 var listeners = this._events[type];
581 this._firingCount = (this._firingCount + 1) || 1;
582 for (var i = 0, len = listeners.length; i < len; i++) {
583 var l = listeners[i];
584 l.fn.call(l.ctx || this, event);
592 // propagate the event to parents (set with addEventParent)
593 this._propagateEvent(event);
599 // @method listens(type: String): Boolean
600 // Returns `true` if a particular event type has any listeners attached to it.
601 listens: function (type, propagate) {
602 var listeners = this._events && this._events[type];
603 if (listeners && listeners.length) { return true; }
606 // also check parents for listeners if event propagates
607 for (var id in this._eventParents) {
608 if (this._eventParents[id].listens(type, propagate)) { return true; }
614 // @method once(…): this
615 // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
616 once: function (types, fn, context) {
618 if (typeof types === 'object') {
619 for (var type in types) {
620 this.once(type, types[type], fn);
625 var handler = bind(function () {
627 .off(types, fn, context)
628 .off(types, handler, context);
631 // add a listener that's executed once and removed after that
633 .on(types, fn, context)
634 .on(types, handler, context);
637 // @method addEventParent(obj: Evented): this
638 // Adds an event parent - an `Evented` that will receive propagated events
639 addEventParent: function (obj) {
640 this._eventParents = this._eventParents || {};
641 this._eventParents[stamp(obj)] = obj;
645 // @method removeEventParent(obj: Evented): this
646 // Removes an event parent, so it will stop receiving propagated events
647 removeEventParent: function (obj) {
648 if (this._eventParents) {
649 delete this._eventParents[stamp(obj)];
654 _propagateEvent: function (e) {
655 for (var id in this._eventParents) {
656 this._eventParents[id].fire(e.type, extend({layer: e.target}, e), true);
661 // aliases; we should ditch those eventually
663 // @method addEventListener(…): this
664 // Alias to [`on(…)`](#evented-on)
665 Events.addEventListener = Events.on;
667 // @method removeEventListener(…): this
668 // Alias to [`off(…)`](#evented-off)
670 // @method clearAllEventListeners(…): this
671 // Alias to [`off()`](#evented-off)
672 Events.removeEventListener = Events.clearAllEventListeners = Events.off;
674 // @method addOneTimeEventListener(…): this
675 // Alias to [`once(…)`](#evented-once)
676 Events.addOneTimeEventListener = Events.once;
678 // @method fireEvent(…): this
679 // Alias to [`fire(…)`](#evented-fire)
680 Events.fireEvent = Events.fire;
682 // @method hasEventListeners(…): Boolean
683 // Alias to [`listens(…)`](#evented-listens)
684 Events.hasEventListeners = Events.listens;
686 var Evented = Class.extend(Events);
692 * Represents a point with `x` and `y` coordinates in pixels.
697 * var point = L.point(200, 300);
700 * 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:
703 * map.panBy([200, 300]);
704 * map.panBy(L.point(200, 300));
708 function Point(x, y, round) {
709 // @property x: Number; The `x` coordinate of the point
710 this.x = (round ? Math.round(x) : x);
711 // @property y: Number; The `y` coordinate of the point
712 this.y = (round ? Math.round(y) : y);
717 // @method clone(): Point
718 // Returns a copy of the current point.
720 return new Point(this.x, this.y);
723 // @method add(otherPoint: Point): Point
724 // Returns the result of addition of the current and the given points.
725 add: function (point) {
726 // non-destructive, returns a new point
727 return this.clone()._add(toPoint(point));
730 _add: function (point) {
731 // destructive, used directly for performance in situations where it's safe to modify existing point
737 // @method subtract(otherPoint: Point): Point
738 // Returns the result of subtraction of the given point from the current.
739 subtract: function (point) {
740 return this.clone()._subtract(toPoint(point));
743 _subtract: function (point) {
749 // @method divideBy(num: Number): Point
750 // Returns the result of division of the current point by the given number.
751 divideBy: function (num) {
752 return this.clone()._divideBy(num);
755 _divideBy: function (num) {
761 // @method multiplyBy(num: Number): Point
762 // Returns the result of multiplication of the current point by the given number.
763 multiplyBy: function (num) {
764 return this.clone()._multiplyBy(num);
767 _multiplyBy: function (num) {
773 // @method scaleBy(scale: Point): Point
774 // Multiply each coordinate of the current point by each coordinate of
775 // `scale`. In linear algebra terms, multiply the point by the
776 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
777 // defined by `scale`.
778 scaleBy: function (point) {
779 return new Point(this.x * point.x, this.y * point.y);
782 // @method unscaleBy(scale: Point): Point
783 // Inverse of `scaleBy`. Divide each coordinate of the current point by
784 // each coordinate of `scale`.
785 unscaleBy: function (point) {
786 return new Point(this.x / point.x, this.y / point.y);
789 // @method round(): Point
790 // Returns a copy of the current point with rounded coordinates.
792 return this.clone()._round();
795 _round: function () {
796 this.x = Math.round(this.x);
797 this.y = Math.round(this.y);
801 // @method floor(): Point
802 // Returns a copy of the current point with floored coordinates (rounded down).
804 return this.clone()._floor();
807 _floor: function () {
808 this.x = Math.floor(this.x);
809 this.y = Math.floor(this.y);
813 // @method ceil(): Point
814 // Returns a copy of the current point with ceiled coordinates (rounded up).
816 return this.clone()._ceil();
820 this.x = Math.ceil(this.x);
821 this.y = Math.ceil(this.y);
825 // @method distanceTo(otherPoint: Point): Number
826 // Returns the cartesian distance between the current and the given points.
827 distanceTo: function (point) {
828 point = toPoint(point);
830 var x = point.x - this.x,
831 y = point.y - this.y;
833 return Math.sqrt(x * x + y * y);
836 // @method equals(otherPoint: Point): Boolean
837 // Returns `true` if the given point has the same coordinates.
838 equals: function (point) {
839 point = toPoint(point);
841 return point.x === this.x &&
845 // @method contains(otherPoint: Point): Boolean
846 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
847 contains: function (point) {
848 point = toPoint(point);
850 return Math.abs(point.x) <= Math.abs(this.x) &&
851 Math.abs(point.y) <= Math.abs(this.y);
854 // @method toString(): String
855 // Returns a string representation of the point for debugging purposes.
856 toString: function () {
858 formatNum(this.x) + ', ' +
859 formatNum(this.y) + ')';
863 // @factory L.point(x: Number, y: Number, round?: Boolean)
864 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
867 // @factory L.point(coords: Number[])
868 // Expects an array of the form `[x, y]` instead.
871 // @factory L.point(coords: Object)
872 // Expects a plain object of the form `{x: Number, y: Number}` instead.
873 function toPoint(x, y, round) {
874 if (x instanceof Point) {
878 return new Point(x[0], x[1]);
880 if (x === undefined || x === null) {
883 if (typeof x === 'object' && 'x' in x && 'y' in x) {
884 return new Point(x.x, x.y);
886 return new Point(x, y, round);
893 * Represents a rectangular area in pixel coordinates.
898 * var p1 = L.point(10, 10),
899 * p2 = L.point(40, 60),
900 * bounds = L.bounds(p1, p2);
903 * 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:
906 * otherBounds.intersects([[10, 10], [40, 60]]);
910 function Bounds(a, b) {
913 var points = b ? [a, b] : a;
915 for (var i = 0, len = points.length; i < len; i++) {
916 this.extend(points[i]);
921 // @method extend(point: Point): this
922 // Extends the bounds to contain the given point.
923 extend: function (point) { // (Point)
924 point = toPoint(point);
926 // @property min: Point
927 // The top left corner of the rectangle.
928 // @property max: Point
929 // The bottom right corner of the rectangle.
930 if (!this.min && !this.max) {
931 this.min = point.clone();
932 this.max = point.clone();
934 this.min.x = Math.min(point.x, this.min.x);
935 this.max.x = Math.max(point.x, this.max.x);
936 this.min.y = Math.min(point.y, this.min.y);
937 this.max.y = Math.max(point.y, this.max.y);
942 // @method getCenter(round?: Boolean): Point
943 // Returns the center point of the bounds.
944 getCenter: function (round) {
946 (this.min.x + this.max.x) / 2,
947 (this.min.y + this.max.y) / 2, round);
950 // @method getBottomLeft(): Point
951 // Returns the bottom-left point of the bounds.
952 getBottomLeft: function () {
953 return new Point(this.min.x, this.max.y);
956 // @method getTopRight(): Point
957 // Returns the top-right point of the bounds.
958 getTopRight: function () { // -> Point
959 return new Point(this.max.x, this.min.y);
962 // @method getTopLeft(): Point
963 // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).
964 getTopLeft: function () {
965 return this.min; // left, top
968 // @method getBottomRight(): Point
969 // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).
970 getBottomRight: function () {
971 return this.max; // right, bottom
974 // @method getSize(): Point
975 // Returns the size of the given bounds
976 getSize: function () {
977 return this.max.subtract(this.min);
980 // @method contains(otherBounds: Bounds): Boolean
981 // Returns `true` if the rectangle contains the given one.
983 // @method contains(point: Point): Boolean
984 // Returns `true` if the rectangle contains the given point.
985 contains: function (obj) {
988 if (typeof obj[0] === 'number' || obj instanceof Point) {
994 if (obj instanceof Bounds) {
1001 return (min.x >= this.min.x) &&
1002 (max.x <= this.max.x) &&
1003 (min.y >= this.min.y) &&
1004 (max.y <= this.max.y);
1007 // @method intersects(otherBounds: Bounds): Boolean
1008 // Returns `true` if the rectangle intersects the given bounds. Two bounds
1009 // intersect if they have at least one point in common.
1010 intersects: function (bounds) { // (Bounds) -> Boolean
1011 bounds = toBounds(bounds);
1017 xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1018 yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1020 return xIntersects && yIntersects;
1023 // @method overlaps(otherBounds: Bounds): Boolean
1024 // Returns `true` if the rectangle overlaps the given bounds. Two bounds
1025 // overlap if their intersection is an area.
1026 overlaps: function (bounds) { // (Bounds) -> Boolean
1027 bounds = toBounds(bounds);
1033 xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1034 yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1036 return xOverlaps && yOverlaps;
1039 isValid: function () {
1040 return !!(this.min && this.max);
1045 // @factory L.bounds(corner1: Point, corner2: Point)
1046 // Creates a Bounds object from two corners coordinate pairs.
1048 // @factory L.bounds(points: Point[])
1049 // Creates a Bounds object from the given array of points.
1050 function toBounds(a, b) {
1051 if (!a || a instanceof Bounds) {
1054 return new Bounds(a, b);
1058 * @class LatLngBounds
1059 * @aka L.LatLngBounds
1061 * Represents a rectangular geographical area on a map.
1066 * var corner1 = L.latLng(40.712, -74.227),
1067 * corner2 = L.latLng(40.774, -74.125),
1068 * bounds = L.latLngBounds(corner1, corner2);
1071 * 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:
1075 * [40.712, -74.227],
1080 * 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.
1083 function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
1084 if (!corner1) { return; }
1086 var latlngs = corner2 ? [corner1, corner2] : corner1;
1088 for (var i = 0, len = latlngs.length; i < len; i++) {
1089 this.extend(latlngs[i]);
1093 LatLngBounds.prototype = {
1095 // @method extend(latlng: LatLng): this
1096 // Extend the bounds to contain the given point
1099 // @method extend(otherBounds: LatLngBounds): this
1100 // Extend the bounds to contain the given bounds
1101 extend: function (obj) {
1102 var sw = this._southWest,
1103 ne = this._northEast,
1106 if (obj instanceof LatLng) {
1110 } else if (obj instanceof LatLngBounds) {
1111 sw2 = obj._southWest;
1112 ne2 = obj._northEast;
1114 if (!sw2 || !ne2) { return this; }
1117 return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
1121 this._southWest = new LatLng(sw2.lat, sw2.lng);
1122 this._northEast = new LatLng(ne2.lat, ne2.lng);
1124 sw.lat = Math.min(sw2.lat, sw.lat);
1125 sw.lng = Math.min(sw2.lng, sw.lng);
1126 ne.lat = Math.max(ne2.lat, ne.lat);
1127 ne.lng = Math.max(ne2.lng, ne.lng);
1133 // @method pad(bufferRatio: Number): LatLngBounds
1134 // Returns bigger bounds created by extending the current bounds by a given percentage in each direction.
1135 pad: function (bufferRatio) {
1136 var sw = this._southWest,
1137 ne = this._northEast,
1138 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1139 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1141 return new LatLngBounds(
1142 new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1143 new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1146 // @method getCenter(): LatLng
1147 // Returns the center point of the bounds.
1148 getCenter: function () {
1150 (this._southWest.lat + this._northEast.lat) / 2,
1151 (this._southWest.lng + this._northEast.lng) / 2);
1154 // @method getSouthWest(): LatLng
1155 // Returns the south-west point of the bounds.
1156 getSouthWest: function () {
1157 return this._southWest;
1160 // @method getNorthEast(): LatLng
1161 // Returns the north-east point of the bounds.
1162 getNorthEast: function () {
1163 return this._northEast;
1166 // @method getNorthWest(): LatLng
1167 // Returns the north-west point of the bounds.
1168 getNorthWest: function () {
1169 return new LatLng(this.getNorth(), this.getWest());
1172 // @method getSouthEast(): LatLng
1173 // Returns the south-east point of the bounds.
1174 getSouthEast: function () {
1175 return new LatLng(this.getSouth(), this.getEast());
1178 // @method getWest(): Number
1179 // Returns the west longitude of the bounds
1180 getWest: function () {
1181 return this._southWest.lng;
1184 // @method getSouth(): Number
1185 // Returns the south latitude of the bounds
1186 getSouth: function () {
1187 return this._southWest.lat;
1190 // @method getEast(): Number
1191 // Returns the east longitude of the bounds
1192 getEast: function () {
1193 return this._northEast.lng;
1196 // @method getNorth(): Number
1197 // Returns the north latitude of the bounds
1198 getNorth: function () {
1199 return this._northEast.lat;
1202 // @method contains(otherBounds: LatLngBounds): Boolean
1203 // Returns `true` if the rectangle contains the given one.
1206 // @method contains (latlng: LatLng): Boolean
1207 // Returns `true` if the rectangle contains the given point.
1208 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1209 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
1210 obj = toLatLng(obj);
1212 obj = toLatLngBounds(obj);
1215 var sw = this._southWest,
1216 ne = this._northEast,
1219 if (obj instanceof LatLngBounds) {
1220 sw2 = obj.getSouthWest();
1221 ne2 = obj.getNorthEast();
1226 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1227 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1230 // @method intersects(otherBounds: LatLngBounds): Boolean
1231 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1232 intersects: function (bounds) {
1233 bounds = toLatLngBounds(bounds);
1235 var sw = this._southWest,
1236 ne = this._northEast,
1237 sw2 = bounds.getSouthWest(),
1238 ne2 = bounds.getNorthEast(),
1240 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1241 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1243 return latIntersects && lngIntersects;
1246 // @method overlaps(otherBounds: Bounds): Boolean
1247 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1248 overlaps: function (bounds) {
1249 bounds = toLatLngBounds(bounds);
1251 var sw = this._southWest,
1252 ne = this._northEast,
1253 sw2 = bounds.getSouthWest(),
1254 ne2 = bounds.getNorthEast(),
1256 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1257 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1259 return latOverlaps && lngOverlaps;
1262 // @method toBBoxString(): String
1263 // 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.
1264 toBBoxString: function () {
1265 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1268 // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
1269 // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overriden by setting `maxMargin` to a small number.
1270 equals: function (bounds, maxMargin) {
1271 if (!bounds) { return false; }
1273 bounds = toLatLngBounds(bounds);
1275 return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
1276 this._northEast.equals(bounds.getNorthEast(), maxMargin);
1279 // @method isValid(): Boolean
1280 // Returns `true` if the bounds are properly initialized.
1281 isValid: function () {
1282 return !!(this._southWest && this._northEast);
1286 // TODO International date line?
1288 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
1289 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
1292 // @factory L.latLngBounds(latlngs: LatLng[])
1293 // 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).
1294 function toLatLngBounds(a, b) {
1295 if (a instanceof LatLngBounds) {
1298 return new LatLngBounds(a, b);
1304 * Represents a geographical point with a certain latitude and longitude.
1309 * var latlng = L.latLng(50.5, 30.5);
1312 * 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:
1315 * map.panTo([50, 30]);
1316 * map.panTo({lon: 30, lat: 50});
1317 * map.panTo({lat: 50, lng: 30});
1318 * map.panTo(L.latLng(50, 30));
1322 function LatLng(lat, lng, alt) {
1323 if (isNaN(lat) || isNaN(lng)) {
1324 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1327 // @property lat: Number
1328 // Latitude in degrees
1331 // @property lng: Number
1332 // Longitude in degrees
1335 // @property alt: Number
1336 // Altitude in meters (optional)
1337 if (alt !== undefined) {
1342 LatLng.prototype = {
1343 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1344 // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overriden by setting `maxMargin` to a small number.
1345 equals: function (obj, maxMargin) {
1346 if (!obj) { return false; }
1348 obj = toLatLng(obj);
1350 var margin = Math.max(
1351 Math.abs(this.lat - obj.lat),
1352 Math.abs(this.lng - obj.lng));
1354 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1357 // @method toString(): String
1358 // Returns a string representation of the point (for debugging purposes).
1359 toString: function (precision) {
1361 formatNum(this.lat, precision) + ', ' +
1362 formatNum(this.lng, precision) + ')';
1365 // @method distanceTo(otherLatLng: LatLng): Number
1366 // Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula).
1367 distanceTo: function (other) {
1368 return Earth.distance(this, toLatLng(other));
1371 // @method wrap(): LatLng
1372 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1374 return Earth.wrapLatLng(this);
1377 // @method toBounds(sizeInMeters: Number): LatLngBounds
1378 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
1379 toBounds: function (sizeInMeters) {
1380 var latAccuracy = 180 * sizeInMeters / 40075017,
1381 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1383 return toLatLngBounds(
1384 [this.lat - latAccuracy, this.lng - lngAccuracy],
1385 [this.lat + latAccuracy, this.lng + lngAccuracy]);
1388 clone: function () {
1389 return new LatLng(this.lat, this.lng, this.alt);
1395 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1396 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1399 // @factory L.latLng(coords: Array): LatLng
1400 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1403 // @factory L.latLng(coords: Object): LatLng
1404 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1406 function toLatLng(a, b, c) {
1407 if (a instanceof LatLng) {
1410 if (isArray(a) && typeof a[0] !== 'object') {
1411 if (a.length === 3) {
1412 return new LatLng(a[0], a[1], a[2]);
1414 if (a.length === 2) {
1415 return new LatLng(a[0], a[1]);
1419 if (a === undefined || a === null) {
1422 if (typeof a === 'object' && 'lat' in a) {
1423 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1425 if (b === undefined) {
1428 return new LatLng(a, b, c);
1434 * Object that defines coordinate reference systems for projecting
1435 * geographical points into pixel (screen) coordinates and back (and to
1436 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
1437 * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
1439 * Leaflet defines the most usual CRSs by default. If you want to use a
1440 * CRS not defined by default, take a look at the
1441 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
1445 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
1446 // Projects geographical coordinates into pixel coordinates for a given zoom.
1447 latLngToPoint: function (latlng, zoom) {
1448 var projectedPoint = this.projection.project(latlng),
1449 scale = this.scale(zoom);
1451 return this.transformation._transform(projectedPoint, scale);
1454 // @method pointToLatLng(point: Point, zoom: Number): LatLng
1455 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
1456 // zoom into geographical coordinates.
1457 pointToLatLng: function (point, zoom) {
1458 var scale = this.scale(zoom),
1459 untransformedPoint = this.transformation.untransform(point, scale);
1461 return this.projection.unproject(untransformedPoint);
1464 // @method project(latlng: LatLng): Point
1465 // Projects geographical coordinates into coordinates in units accepted for
1466 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
1467 project: function (latlng) {
1468 return this.projection.project(latlng);
1471 // @method unproject(point: Point): LatLng
1472 // Given a projected coordinate returns the corresponding LatLng.
1473 // The inverse of `project`.
1474 unproject: function (point) {
1475 return this.projection.unproject(point);
1478 // @method scale(zoom: Number): Number
1479 // Returns the scale used when transforming projected coordinates into
1480 // pixel coordinates for a particular zoom. For example, it returns
1481 // `256 * 2^zoom` for Mercator-based CRS.
1482 scale: function (zoom) {
1483 return 256 * Math.pow(2, zoom);
1486 // @method zoom(scale: Number): Number
1487 // Inverse of `scale()`, returns the zoom level corresponding to a scale
1488 // factor of `scale`.
1489 zoom: function (scale) {
1490 return Math.log(scale / 256) / Math.LN2;
1493 // @method getProjectedBounds(zoom: Number): Bounds
1494 // Returns the projection's bounds scaled and transformed for the provided `zoom`.
1495 getProjectedBounds: function (zoom) {
1496 if (this.infinite) { return null; }
1498 var b = this.projection.bounds,
1499 s = this.scale(zoom),
1500 min = this.transformation.transform(b.min, s),
1501 max = this.transformation.transform(b.max, s);
1503 return new Bounds(min, max);
1506 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
1507 // Returns the distance between two geographical coordinates.
1509 // @property code: String
1510 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
1512 // @property wrapLng: Number[]
1513 // An array of two numbers defining whether the longitude (horizontal) coordinate
1514 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
1515 // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
1517 // @property wrapLat: Number[]
1518 // Like `wrapLng`, but for the latitude (vertical) axis.
1520 // wrapLng: [min, max],
1521 // wrapLat: [min, max],
1523 // @property infinite: Boolean
1524 // If true, the coordinate space will be unbounded (infinite in both axes)
1527 // @method wrapLatLng(latlng: LatLng): LatLng
1528 // Returns a `LatLng` where lat and lng has been wrapped according to the
1529 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
1530 wrapLatLng: function (latlng) {
1531 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
1532 lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
1535 return new LatLng(lat, lng, alt);
1538 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
1539 // Returns a `LatLngBounds` with the same size as the given one, ensuring
1540 // that its center is within the CRS's bounds.
1541 // Only accepts actual `L.LatLngBounds` instances, not arrays.
1542 wrapLatLngBounds: function (bounds) {
1543 var center = bounds.getCenter(),
1544 newCenter = this.wrapLatLng(center),
1545 latShift = center.lat - newCenter.lat,
1546 lngShift = center.lng - newCenter.lng;
1548 if (latShift === 0 && lngShift === 0) {
1552 var sw = bounds.getSouthWest(),
1553 ne = bounds.getNorthEast(),
1554 newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),
1555 newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);
1557 return new LatLngBounds(newSw, newNe);
1565 * Serves as the base for CRS that are global such that they cover the earth.
1566 * Can only be used as the base for other CRS and cannot be used directly,
1567 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
1571 var Earth = extend({}, CRS, {
1572 wrapLng: [-180, 180],
1574 // Mean Earth Radius, as recommended for use by
1575 // the International Union of Geodesy and Geophysics,
1576 // see http://rosettacode.org/wiki/Haversine_formula
1579 // distance between two geographical points using spherical law of cosines approximation
1580 distance: function (latlng1, latlng2) {
1581 var rad = Math.PI / 180,
1582 lat1 = latlng1.lat * rad,
1583 lat2 = latlng2.lat * rad,
1584 a = Math.sin(lat1) * Math.sin(lat2) +
1585 Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad);
1587 return this.R * Math.acos(Math.min(a, 1));
1592 * @namespace Projection
1593 * @projection L.Projection.SphericalMercator
1595 * Spherical Mercator projection — the most common projection for online maps,
1596 * used by almost all free and commercial tile providers. Assumes that Earth is
1597 * a sphere. Used by the `EPSG:3857` CRS.
1600 var SphericalMercator = {
1603 MAX_LATITUDE: 85.0511287798,
1605 project: function (latlng) {
1606 var d = Math.PI / 180,
1607 max = this.MAX_LATITUDE,
1608 lat = Math.max(Math.min(max, latlng.lat), -max),
1609 sin = Math.sin(lat * d);
1612 this.R * latlng.lng * d,
1613 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
1616 unproject: function (point) {
1617 var d = 180 / Math.PI;
1620 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
1621 point.x * d / this.R);
1624 bounds: (function () {
1625 var d = 6378137 * Math.PI;
1626 return new Bounds([-d, -d], [d, d]);
1631 * @class Transformation
1632 * @aka L.Transformation
1634 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1635 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1636 * the reverse. Used by Leaflet in its projections code.
1641 * var transformation = L.transformation(2, 5, -1, 10),
1642 * p = L.point(1, 2),
1643 * p2 = transformation.transform(p), // L.point(7, 8)
1644 * p3 = transformation.untransform(p2); // L.point(1, 2)
1649 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1650 // Creates a `Transformation` object with the given coefficients.
1651 function Transformation(a, b, c, d) {
1653 // use array properties
1666 Transformation.prototype = {
1667 // @method transform(point: Point, scale?: Number): Point
1668 // Returns a transformed point, optionally multiplied by the given scale.
1669 // Only accepts actual `L.Point` instances, not arrays.
1670 transform: function (point, scale) { // (Point, Number) -> Point
1671 return this._transform(point.clone(), scale);
1674 // destructive transform (faster)
1675 _transform: function (point, scale) {
1677 point.x = scale * (this._a * point.x + this._b);
1678 point.y = scale * (this._c * point.y + this._d);
1682 // @method untransform(point: Point, scale?: Number): Point
1683 // Returns the reverse transformation of the given point, optionally divided
1684 // by the given scale. Only accepts actual `L.Point` instances, not arrays.
1685 untransform: function (point, scale) {
1688 (point.x / scale - this._b) / this._a,
1689 (point.y / scale - this._d) / this._c);
1693 // factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1695 // @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1696 // Instantiates a Transformation object with the given coefficients.
1699 // @factory L.transformation(coefficients: Array): Transformation
1700 // Expects an coeficients array of the form
1701 // `[a: Number, b: Number, c: Number, d: Number]`.
1703 function toTransformation(a, b, c, d) {
1704 return new Transformation(a, b, c, d);
1709 * @crs L.CRS.EPSG3857
1711 * The most common CRS for online maps, used by almost all free and commercial
1712 * tile providers. Uses Spherical Mercator projection. Set in by default in
1713 * Map's `crs` option.
1716 var EPSG3857 = extend({}, Earth, {
1718 projection: SphericalMercator,
1720 transformation: (function () {
1721 var scale = 0.5 / (Math.PI * SphericalMercator.R);
1722 return toTransformation(scale, 0.5, -scale, 0.5);
1726 var EPSG900913 = extend({}, EPSG3857, {
1730 // @namespace SVG; @section
1731 // There are several static functions which can be called without instantiating L.SVG:
1733 // @function create(name: String): SVGElement
1734 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
1735 // corresponding to the class name passed. For example, using 'line' will return
1736 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
1737 function svgCreate(name) {
1738 return document.createElementNS('http://www.w3.org/2000/svg', name);
1741 // @function pointsToPath(rings: Point[], closed: Boolean): String
1742 // Generates a SVG path string for multiple rings, with each ring turning
1743 // into "M..L..L.." instructions
1744 function pointsToPath(rings, closed) {
1746 i, j, len, len2, points, p;
1748 for (i = 0, len = rings.length; i < len; i++) {
1751 for (j = 0, len2 = points.length; j < len2; j++) {
1753 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
1756 // closes the ring for polygons; "x" is VML syntax
1757 str += closed ? (svg ? 'z' : 'x') : '';
1760 // SVG complains about empty path strings
1761 return str || 'M0 0';
1765 * @namespace Browser
1768 * A namespace with static properties for browser/feature detection used by Leaflet internally.
1773 * if (L.Browser.ielt9) {
1774 * alert('Upgrade your browser, dude!');
1779 var style$1 = document.documentElement.style;
1781 // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
1782 var ie = 'ActiveXObject' in window;
1784 // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
1785 var ielt9 = ie && !document.addEventListener;
1787 // @property edge: Boolean; `true` for the Edge web browser.
1788 var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
1790 // @property webkit: Boolean;
1791 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
1792 var webkit = userAgentContains('webkit');
1794 // @property android: Boolean
1795 // `true` for any browser running on an Android platform.
1796 var android = userAgentContains('android');
1798 // @property android23: Boolean; `true` for browsers running on Android 2 or Android 3.
1799 var android23 = userAgentContains('android 2') || userAgentContains('android 3');
1801 // @property opera: Boolean; `true` for the Opera browser
1802 var opera = !!window.opera;
1804 // @property chrome: Boolean; `true` for the Chrome browser.
1805 var chrome = userAgentContains('chrome');
1807 // @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
1808 var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
1810 // @property safari: Boolean; `true` for the Safari browser.
1811 var safari = !chrome && userAgentContains('safari');
1813 var phantom = userAgentContains('phantom');
1815 // @property opera12: Boolean
1816 // `true` for the Opera browser supporting CSS transforms (version 12 or later).
1817 var opera12 = 'OTransition' in style$1;
1819 // @property win: Boolean; `true` when the browser is running in a Windows platform
1820 var win = navigator.platform.indexOf('Win') === 0;
1822 // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
1823 var ie3d = ie && ('transition' in style$1);
1825 // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
1826 var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;
1828 // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
1829 var gecko3d = 'MozPerspective' in style$1;
1831 // @property any3d: Boolean
1832 // `true` for all browsers supporting CSS transforms.
1833 var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
1835 // @property mobile: Boolean; `true` for all browsers running in a mobile device.
1836 var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
1838 // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
1839 var mobileWebkit = mobile && webkit;
1841 // @property mobileWebkit3d: Boolean
1842 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
1843 var mobileWebkit3d = mobile && webkit3d;
1845 // @property msPointer: Boolean
1846 // `true` for browsers implementing the Microsoft touch events model (notably IE10).
1847 var msPointer = !window.PointerEvent && window.MSPointerEvent;
1849 // @property pointer: Boolean
1850 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
1851 var pointer = !!(window.PointerEvent || msPointer);
1853 // @property touch: Boolean
1854 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
1855 // This does not necessarily mean that the browser is running in a computer with
1856 // a touchscreen, it only means that the browser is capable of understanding
1858 var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
1859 (window.DocumentTouch && document instanceof window.DocumentTouch));
1861 // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
1862 var mobileOpera = mobile && opera;
1864 // @property mobileGecko: Boolean
1865 // `true` for gecko-based browsers running in a mobile device.
1866 var mobileGecko = mobile && gecko;
1868 // @property retina: Boolean
1869 // `true` for browsers on a high-resolution "retina" screen.
1870 var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
1873 // @property canvas: Boolean
1874 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
1875 var canvas = (function () {
1876 return !!document.createElement('canvas').getContext;
1879 // @property svg: Boolean
1880 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
1881 var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect);
1883 // @property vml: Boolean
1884 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
1885 var vml = !svg && (function () {
1887 var div = document.createElement('div');
1888 div.innerHTML = '<v:shape adj="1"/>';
1890 var shape = div.firstChild;
1891 shape.style.behavior = 'url(#default#VML)';
1893 return shape && (typeof shape.adj === 'object');
1901 function userAgentContains(str) {
1902 return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
1906 var Browser = (Object.freeze || Object)({
1912 android23: android23,
1925 mobileWebkit: mobileWebkit,
1926 mobileWebkit3d: mobileWebkit3d,
1927 msPointer: msPointer,
1930 mobileOpera: mobileOpera,
1931 mobileGecko: mobileGecko,
1939 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
1943 var POINTER_DOWN = msPointer ? 'MSPointerDown' : 'pointerdown';
1944 var POINTER_MOVE = msPointer ? 'MSPointerMove' : 'pointermove';
1945 var POINTER_UP = msPointer ? 'MSPointerUp' : 'pointerup';
1946 var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel';
1947 var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION'];
1949 var _pointerDocListener = false;
1951 // DomEvent.DoubleTap needs to know about this
1952 var _pointersCount = 0;
1954 // Provides a touch events wrapper for (ms)pointer events.
1955 // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
1957 function addPointerListener(obj, type, handler, id) {
1958 if (type === 'touchstart') {
1959 _addPointerStart(obj, handler, id);
1961 } else if (type === 'touchmove') {
1962 _addPointerMove(obj, handler, id);
1964 } else if (type === 'touchend') {
1965 _addPointerEnd(obj, handler, id);
1971 function removePointerListener(obj, type, id) {
1972 var handler = obj['_leaflet_' + type + id];
1974 if (type === 'touchstart') {
1975 obj.removeEventListener(POINTER_DOWN, handler, false);
1977 } else if (type === 'touchmove') {
1978 obj.removeEventListener(POINTER_MOVE, handler, false);
1980 } else if (type === 'touchend') {
1981 obj.removeEventListener(POINTER_UP, handler, false);
1982 obj.removeEventListener(POINTER_CANCEL, handler, false);
1988 function _addPointerStart(obj, handler, id) {
1989 var onDown = bind(function (e) {
1990 if (e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
1991 // In IE11, some touch events needs to fire for form controls, or
1992 // the controls will stop working. We keep a whitelist of tag names that
1993 // need these events. For other target tags, we prevent default on the event.
1994 if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
2001 _handlePointer(e, handler);
2004 obj['_leaflet_touchstart' + id] = onDown;
2005 obj.addEventListener(POINTER_DOWN, onDown, false);
2007 // need to keep track of what pointers and how many are active to provide e.touches emulation
2008 if (!_pointerDocListener) {
2009 // we listen documentElement as any drags that end by moving the touch off the screen get fired there
2010 document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true);
2011 document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true);
2012 document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true);
2013 document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
2015 _pointerDocListener = true;
2019 function _globalPointerDown(e) {
2020 _pointers[e.pointerId] = e;
2024 function _globalPointerMove(e) {
2025 if (_pointers[e.pointerId]) {
2026 _pointers[e.pointerId] = e;
2030 function _globalPointerUp(e) {
2031 delete _pointers[e.pointerId];
2035 function _handlePointer(e, handler) {
2037 for (var i in _pointers) {
2038 e.touches.push(_pointers[i]);
2040 e.changedTouches = [e];
2045 function _addPointerMove(obj, handler, id) {
2046 var onMove = function (e) {
2047 // don't fire touch moves when mouse isn't down
2048 if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
2050 _handlePointer(e, handler);
2053 obj['_leaflet_touchmove' + id] = onMove;
2054 obj.addEventListener(POINTER_MOVE, onMove, false);
2057 function _addPointerEnd(obj, handler, id) {
2058 var onUp = function (e) {
2059 _handlePointer(e, handler);
2062 obj['_leaflet_touchend' + id] = onUp;
2063 obj.addEventListener(POINTER_UP, onUp, false);
2064 obj.addEventListener(POINTER_CANCEL, onUp, false);
2068 * Extends the event handling code with double tap support for mobile browsers.
2071 var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart';
2072 var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend';
2073 var _pre = '_leaflet_';
2075 // inspired by Zepto touch code by Thomas Fuchs
2076 function addDoubleTapListener(obj, handler, id) {
2081 function onTouchStart(e) {
2085 if ((!edge) || e.pointerType === 'mouse') { return; }
2086 count = _pointersCount;
2088 count = e.touches.length;
2091 if (count > 1) { return; }
2093 var now = Date.now(),
2094 delta = now - (last || now);
2096 touch$$1 = e.touches ? e.touches[0] : e;
2097 doubleTap = (delta > 0 && delta <= delay);
2101 function onTouchEnd(e) {
2102 if (doubleTap && !touch$$1.cancelBubble) {
2104 if ((!edge) || e.pointerType === 'mouse') { return; }
2105 // work around .type being readonly with MSPointer* events
2109 for (i in touch$$1) {
2111 newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop;
2113 touch$$1 = newTouch;
2115 touch$$1.type = 'dblclick';
2121 obj[_pre + _touchstart + id] = onTouchStart;
2122 obj[_pre + _touchend + id] = onTouchEnd;
2123 obj[_pre + 'dblclick' + id] = handler;
2125 obj.addEventListener(_touchstart, onTouchStart, false);
2126 obj.addEventListener(_touchend, onTouchEnd, false);
2128 // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
2129 // the browser doesn't fire touchend/pointerup events but does fire
2130 // native dblclicks. See #4127.
2131 // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
2132 obj.addEventListener('dblclick', handler, false);
2137 function removeDoubleTapListener(obj, id) {
2138 var touchstart = obj[_pre + _touchstart + id],
2139 touchend = obj[_pre + _touchend + id],
2140 dblclick = obj[_pre + 'dblclick' + id];
2142 obj.removeEventListener(_touchstart, touchstart, false);
2143 obj.removeEventListener(_touchend, touchend, false);
2145 obj.removeEventListener('dblclick', dblclick, false);
2152 * @namespace DomEvent
2153 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
2156 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
2158 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
2159 // Adds a listener function (`fn`) to a particular DOM event type of the
2160 // element `el`. You can optionally specify the context of the listener
2161 // (object the `this` keyword will point to). You can also pass several
2162 // space-separated types (e.g. `'click dblclick'`).
2165 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
2166 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2167 function on(obj, types, fn, context) {
2169 if (typeof types === 'object') {
2170 for (var type in types) {
2171 addOne(obj, type, types[type], fn);
2174 types = splitWords(types);
2176 for (var i = 0, len = types.length; i < len; i++) {
2177 addOne(obj, types[i], fn, context);
2184 var eventsKey = '_leaflet_events';
2186 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
2187 // Removes a previously added listener function. If no function is specified,
2188 // it will remove all the listeners of that particular DOM event from the element.
2189 // Note that if you passed a custom context to on, you must pass the same
2190 // context to `off` in order to remove the listener.
2193 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
2194 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2197 // @function off(el: HTMLElement): this
2198 // Removes all known event listeners
2199 function off(obj, types, fn, context) {
2201 if (typeof types === 'object') {
2202 for (var type in types) {
2203 removeOne(obj, type, types[type], fn);
2206 types = splitWords(types);
2208 for (var i = 0, len = types.length; i < len; i++) {
2209 removeOne(obj, types[i], fn, context);
2212 for (var j in obj[eventsKey]) {
2213 removeOne(obj, j, obj[eventsKey][j]);
2215 delete obj[eventsKey];
2219 function addOne(obj, type, fn, context) {
2220 var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
2222 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
2224 var handler = function (e) {
2225 return fn.call(context || obj, e || window.event);
2228 var originalHandler = handler;
2230 if (pointer && type.indexOf('touch') === 0) {
2231 // Needs DomEvent.Pointer.js
2232 addPointerListener(obj, type, handler, id);
2234 } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
2235 !(pointer && chrome)) {
2236 // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
2238 addDoubleTapListener(obj, handler, id);
2240 } else if ('addEventListener' in obj) {
2242 if (type === 'mousewheel') {
2243 obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2245 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
2246 handler = function (e) {
2247 e = e || window.event;
2248 if (isExternalTarget(obj, e)) {
2252 obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
2255 if (type === 'click' && android) {
2256 handler = function (e) {
2257 filterClick(e, originalHandler);
2260 obj.addEventListener(type, handler, false);
2263 } else if ('attachEvent' in obj) {
2264 obj.attachEvent('on' + type, handler);
2267 obj[eventsKey] = obj[eventsKey] || {};
2268 obj[eventsKey][id] = handler;
2271 function removeOne(obj, type, fn, context) {
2273 var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
2274 handler = obj[eventsKey] && obj[eventsKey][id];
2276 if (!handler) { return this; }
2278 if (pointer && type.indexOf('touch') === 0) {
2279 removePointerListener(obj, type, id);
2281 } else if (touch && (type === 'dblclick') && removeDoubleTapListener) {
2282 removeDoubleTapListener(obj, id);
2284 } else if ('removeEventListener' in obj) {
2286 if (type === 'mousewheel') {
2287 obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2290 obj.removeEventListener(
2291 type === 'mouseenter' ? 'mouseover' :
2292 type === 'mouseleave' ? 'mouseout' : type, handler, false);
2295 } else if ('detachEvent' in obj) {
2296 obj.detachEvent('on' + type, handler);
2299 obj[eventsKey][id] = null;
2302 // @function stopPropagation(ev: DOMEvent): this
2303 // Stop the given event from propagation to parent elements. Used inside the listener functions:
2305 // L.DomEvent.on(div, 'click', function (ev) {
2306 // L.DomEvent.stopPropagation(ev);
2309 function stopPropagation(e) {
2311 if (e.stopPropagation) {
2312 e.stopPropagation();
2313 } else if (e.originalEvent) { // In case of Leaflet event.
2314 e.originalEvent._stopped = true;
2316 e.cancelBubble = true;
2323 // @function disableScrollPropagation(el: HTMLElement): this
2324 // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
2325 function disableScrollPropagation(el) {
2326 return addOne(el, 'mousewheel', stopPropagation);
2329 // @function disableClickPropagation(el: HTMLElement): this
2330 // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
2331 // `'mousedown'` and `'touchstart'` events (plus browser variants).
2332 function disableClickPropagation(el) {
2333 on(el, 'mousedown touchstart dblclick', stopPropagation);
2334 addOne(el, 'click', fakeStop);
2338 // @function preventDefault(ev: DOMEvent): this
2339 // Prevents the default action of the DOM Event `ev` from happening (such as
2340 // following a link in the href of the a element, or doing a POST request
2341 // with page reload when a `<form>` is submitted).
2342 // Use it inside listener functions.
2343 function preventDefault(e) {
2344 if (e.preventDefault) {
2347 e.returnValue = false;
2352 // @function stop(ev): this
2353 // Does `stopPropagation` and `preventDefault` at the same time.
2360 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
2361 // Gets normalized mouse position from a DOM event relative to the
2362 // `container` or to the whole page if not specified.
2363 function getMousePosition(e, container) {
2365 return new Point(e.clientX, e.clientY);
2368 var rect = container.getBoundingClientRect();
2371 e.clientX - rect.left - container.clientLeft,
2372 e.clientY - rect.top - container.clientTop);
2375 // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
2376 // and Firefox scrolls device pixels, not CSS pixels
2378 (win && chrome) ? 2 * window.devicePixelRatio :
2379 gecko ? window.devicePixelRatio : 1;
2381 // @function getWheelDelta(ev: DOMEvent): Number
2382 // Gets normalized wheel delta from a mousewheel DOM event, in vertical
2383 // pixels scrolled (negative if scrolling down).
2384 // Events from pointing devices without precise scrolling are mapped to
2385 // a best guess of 60 pixels.
2386 function getWheelDelta(e) {
2387 return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
2388 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
2389 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
2390 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
2391 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
2392 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
2393 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
2394 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
2398 var skipEvents = {};
2400 function fakeStop(e) {
2401 // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
2402 skipEvents[e.type] = true;
2405 function skipped(e) {
2406 var events = skipEvents[e.type];
2407 // reset when checking, as it's only used in map container and propagates outside of the map
2408 skipEvents[e.type] = false;
2412 // check if element really left/entered the event target (for mouseenter/mouseleave)
2413 function isExternalTarget(el, e) {
2415 var related = e.relatedTarget;
2417 if (!related) { return true; }
2420 while (related && (related !== el)) {
2421 related = related.parentNode;
2426 return (related !== el);
2431 // this is a horrible workaround for a bug in Android where a single touch triggers two click events
2432 function filterClick(e, handler) {
2433 var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
2434 elapsed = lastClick && (timeStamp - lastClick);
2436 // are they closer together than 500ms yet more than 100ms?
2437 // Android typically triggers them ~300ms apart while multiple listeners
2438 // on the same event should be triggered far faster;
2439 // or check if click is simulated on the element, and if it is, reject any non-simulated events
2441 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
2445 lastClick = timeStamp;
2453 var DomEvent = (Object.freeze || Object)({
2456 stopPropagation: stopPropagation,
2457 disableScrollPropagation: disableScrollPropagation,
2458 disableClickPropagation: disableClickPropagation,
2459 preventDefault: preventDefault,
2461 getMousePosition: getMousePosition,
2462 getWheelDelta: getWheelDelta,
2465 isExternalTarget: isExternalTarget,
2471 * @namespace DomUtil
2473 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
2474 * tree, used by Leaflet internally.
2476 * Most functions expecting or returning a `HTMLElement` also work for
2477 * SVG elements. The only difference is that classes refer to CSS classes
2478 * in HTML and SVG classes in SVG.
2482 // @property TRANSFORM: String
2483 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
2484 var TRANSFORM = testProp(
2485 ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
2487 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
2488 // the same for the transitionend event, in particular the Android 4.1 stock browser
2490 // @property TRANSITION: String
2491 // Vendor-prefixed transition style name.
2492 var TRANSITION = testProp(
2493 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
2495 // @property TRANSITION_END: String
2496 // Vendor-prefixed transitionend event name.
2497 var TRANSITION_END =
2498 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
2501 // @function get(id: String|HTMLElement): HTMLElement
2502 // Returns an element given its DOM id, or returns the element itself
2503 // if it was passed directly.
2505 return typeof id === 'string' ? document.getElementById(id) : id;
2508 // @function getStyle(el: HTMLElement, styleAttrib: String): String
2509 // Returns the value for a certain style attribute on an element,
2510 // including computed values or values set through CSS.
2511 function getStyle(el, style) {
2512 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
2514 if ((!value || value === 'auto') && document.defaultView) {
2515 var css = document.defaultView.getComputedStyle(el, null);
2516 value = css ? css[style] : null;
2518 return value === 'auto' ? null : value;
2521 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
2522 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
2523 function create$1(tagName, className, container) {
2524 var el = document.createElement(tagName);
2525 el.className = className || '';
2528 container.appendChild(el);
2533 // @function remove(el: HTMLElement)
2534 // Removes `el` from its parent element
2535 function remove(el) {
2536 var parent = el.parentNode;
2538 parent.removeChild(el);
2542 // @function empty(el: HTMLElement)
2543 // Removes all of `el`'s children elements from `el`
2544 function empty(el) {
2545 while (el.firstChild) {
2546 el.removeChild(el.firstChild);
2550 // @function toFront(el: HTMLElement)
2551 // Makes `el` the last child of its parent, so it renders in front of the other children.
2552 function toFront(el) {
2553 var parent = el.parentNode;
2554 if (parent.lastChild !== el) {
2555 parent.appendChild(el);
2559 // @function toBack(el: HTMLElement)
2560 // Makes `el` the first child of its parent, so it renders behind the other children.
2561 function toBack(el) {
2562 var parent = el.parentNode;
2563 if (parent.firstChild !== el) {
2564 parent.insertBefore(el, parent.firstChild);
2568 // @function hasClass(el: HTMLElement, name: String): Boolean
2569 // Returns `true` if the element's class attribute contains `name`.
2570 function hasClass(el, name) {
2571 if (el.classList !== undefined) {
2572 return el.classList.contains(name);
2574 var className = getClass(el);
2575 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
2578 // @function addClass(el: HTMLElement, name: String)
2579 // Adds `name` to the element's class attribute.
2580 function addClass(el, name) {
2581 if (el.classList !== undefined) {
2582 var classes = splitWords(name);
2583 for (var i = 0, len = classes.length; i < len; i++) {
2584 el.classList.add(classes[i]);
2586 } else if (!hasClass(el, name)) {
2587 var className = getClass(el);
2588 setClass(el, (className ? className + ' ' : '') + name);
2592 // @function removeClass(el: HTMLElement, name: String)
2593 // Removes `name` from the element's class attribute.
2594 function removeClass(el, name) {
2595 if (el.classList !== undefined) {
2596 el.classList.remove(name);
2598 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
2602 // @function setClass(el: HTMLElement, name: String)
2603 // Sets the element's class.
2604 function setClass(el, name) {
2605 if (el.className.baseVal === undefined) {
2606 el.className = name;
2608 // in case of SVG element
2609 el.className.baseVal = name;
2613 // @function getClass(el: HTMLElement): String
2614 // Returns the element's class.
2615 function getClass(el) {
2616 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
2619 // @function setOpacity(el: HTMLElement, opacity: Number)
2620 // Set the opacity of an element (including old IE support).
2621 // `opacity` must be a number from `0` to `1`.
2622 function setOpacity(el, value) {
2623 if ('opacity' in el.style) {
2624 el.style.opacity = value;
2625 } else if ('filter' in el.style) {
2626 _setOpacityIE(el, value);
2630 function _setOpacityIE(el, value) {
2632 filterName = 'DXImageTransform.Microsoft.Alpha';
2634 // filters collection throws an error if we try to retrieve a filter that doesn't exist
2636 filter = el.filters.item(filterName);
2638 // don't set opacity to 1 if we haven't already set an opacity,
2639 // it isn't needed and breaks transparent pngs.
2640 if (value === 1) { return; }
2643 value = Math.round(value * 100);
2646 filter.Enabled = (value !== 100);
2647 filter.Opacity = value;
2649 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
2653 // @function testProp(props: String[]): String|false
2654 // Goes through the array of style names and returns the first name
2655 // that is a valid style name for an element. If no such name is found,
2656 // it returns false. Useful for vendor-prefixed styles like `transform`.
2657 function testProp(props) {
2658 var style = document.documentElement.style;
2660 for (var i = 0; i < props.length; i++) {
2661 if (props[i] in style) {
2668 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
2669 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
2670 // and optionally scaled by `scale`. Does not have an effect if the
2671 // browser doesn't support 3D CSS transforms.
2672 function setTransform(el, offset, scale) {
2673 var pos = offset || new Point(0, 0);
2675 el.style[TRANSFORM] =
2677 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
2678 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
2679 (scale ? ' scale(' + scale + ')' : '');
2682 // @function setPosition(el: HTMLElement, position: Point)
2683 // Sets the position of `el` to coordinates specified by `position`,
2684 // using CSS translate or top/left positioning depending on the browser
2685 // (used by Leaflet internally to position its layers).
2686 function setPosition(el, point) {
2689 el._leaflet_pos = point;
2693 setTransform(el, point);
2695 el.style.left = point.x + 'px';
2696 el.style.top = point.y + 'px';
2700 // @function getPosition(el: HTMLElement): Point
2701 // Returns the coordinates of an element previously positioned with setPosition.
2702 function getPosition(el) {
2703 // this method is only used for elements previously positioned using setPosition,
2704 // so it's safe to cache the position for performance
2706 return el._leaflet_pos || new Point(0, 0);
2709 // @function disableTextSelection()
2710 // Prevents the user from generating `selectstart` DOM events, usually generated
2711 // when the user drags the mouse through a page with text. Used internally
2712 // by Leaflet to override the behaviour of any click-and-drag interaction on
2713 // the map. Affects drag interactions on the whole document.
2715 // @function enableTextSelection()
2716 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
2717 var disableTextSelection;
2718 var enableTextSelection;
2720 if ('onselectstart' in document) {
2721 disableTextSelection = function () {
2722 on(window, 'selectstart', preventDefault);
2724 enableTextSelection = function () {
2725 off(window, 'selectstart', preventDefault);
2728 var userSelectProperty = testProp(
2729 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
2731 disableTextSelection = function () {
2732 if (userSelectProperty) {
2733 var style = document.documentElement.style;
2734 _userSelect = style[userSelectProperty];
2735 style[userSelectProperty] = 'none';
2738 enableTextSelection = function () {
2739 if (userSelectProperty) {
2740 document.documentElement.style[userSelectProperty] = _userSelect;
2741 _userSelect = undefined;
2746 // @function disableImageDrag()
2747 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
2748 // for `dragstart` DOM events, usually generated when the user drags an image.
2749 function disableImageDrag() {
2750 on(window, 'dragstart', preventDefault);
2753 // @function enableImageDrag()
2754 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
2755 function enableImageDrag() {
2756 off(window, 'dragstart', preventDefault);
2759 var _outlineElement;
2761 // @function preventOutline(el: HTMLElement)
2762 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
2763 // of the element `el` invisible. Used internally by Leaflet to prevent
2764 // focusable elements from displaying an outline when the user performs a
2765 // drag interaction on them.
2766 function preventOutline(element) {
2767 while (element.tabIndex === -1) {
2768 element = element.parentNode;
2770 if (!element.style) { return; }
2772 _outlineElement = element;
2773 _outlineStyle = element.style.outline;
2774 element.style.outline = 'none';
2775 on(window, 'keydown', restoreOutline);
2778 // @function restoreOutline()
2779 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
2780 function restoreOutline() {
2781 if (!_outlineElement) { return; }
2782 _outlineElement.style.outline = _outlineStyle;
2783 _outlineElement = undefined;
2784 _outlineStyle = undefined;
2785 off(window, 'keydown', restoreOutline);
2789 var DomUtil = (Object.freeze || Object)({
2790 TRANSFORM: TRANSFORM,
2791 TRANSITION: TRANSITION,
2792 TRANSITION_END: TRANSITION_END,
2802 removeClass: removeClass,
2805 setOpacity: setOpacity,
2807 setTransform: setTransform,
2808 setPosition: setPosition,
2809 getPosition: getPosition,
2810 disableTextSelection: disableTextSelection,
2811 enableTextSelection: enableTextSelection,
2812 disableImageDrag: disableImageDrag,
2813 enableImageDrag: enableImageDrag,
2814 preventOutline: preventOutline,
2815 restoreOutline: restoreOutline
2819 * @class PosAnimation
2820 * @aka L.PosAnimation
2822 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
2826 * var fx = new L.PosAnimation();
2827 * fx.run(el, [300, 500], 0.5);
2830 * @constructor L.PosAnimation()
2831 * Creates a `PosAnimation` object.
2835 var PosAnimation = Evented.extend({
2837 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
2838 // Run an animation of a given element to a new position, optionally setting
2839 // duration in seconds (`0.25` by default) and easing linearity factor (3rd
2840 // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
2841 // `0.5` by default).
2842 run: function (el, newPos, duration, easeLinearity) {
2846 this._inProgress = true;
2847 this._duration = duration || 0.25;
2848 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
2850 this._startPos = getPosition(el);
2851 this._offset = newPos.subtract(this._startPos);
2852 this._startTime = +new Date();
2854 // @event start: Event
2855 // Fired when the animation starts
2862 // Stops the animation (if currently running).
2864 if (!this._inProgress) { return; }
2870 _animate: function () {
2872 this._animId = requestAnimFrame(this._animate, this);
2876 _step: function (round) {
2877 var elapsed = (+new Date()) - this._startTime,
2878 duration = this._duration * 1000;
2880 if (elapsed < duration) {
2881 this._runFrame(this._easeOut(elapsed / duration), round);
2888 _runFrame: function (progress, round) {
2889 var pos = this._startPos.add(this._offset.multiplyBy(progress));
2893 setPosition(this._el, pos);
2895 // @event step: Event
2896 // Fired continuously during the animation.
2900 _complete: function () {
2901 cancelAnimFrame(this._animId);
2903 this._inProgress = false;
2904 // @event end: Event
2905 // Fired when the animation ends.
2909 _easeOut: function (t) {
2910 return 1 - Math.pow(1 - t, this._easeOutPower);
2919 * The central class of the API — it is used to create a map on a page and manipulate it.
2924 * // initialize the map on the "map" div with a given center and zoom
2925 * var map = L.map('map', {
2926 * center: [51.505, -0.09],
2933 var Map = Evented.extend({
2936 // @section Map State Options
2937 // @option crs: CRS = L.CRS.EPSG3857
2938 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
2939 // sure what it means.
2942 // @option center: LatLng = undefined
2943 // Initial geographic center of the map
2946 // @option zoom: Number = undefined
2947 // Initial map zoom level
2950 // @option minZoom: Number = *
2951 // Minimum zoom level of the map.
2952 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
2953 // the lowest of their `minZoom` options will be used instead.
2956 // @option maxZoom: Number = *
2957 // Maximum zoom level of the map.
2958 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
2959 // the highest of their `maxZoom` options will be used instead.
2962 // @option layers: Layer[] = []
2963 // Array of layers that will be added to the map initially
2966 // @option maxBounds: LatLngBounds = null
2967 // When this option is set, the map restricts the view to the given
2968 // geographical bounds, bouncing the user back if the user tries to pan
2969 // outside the view. To set the restriction dynamically, use
2970 // [`setMaxBounds`](#map-setmaxbounds) method.
2971 maxBounds: undefined,
2973 // @option renderer: Renderer = *
2974 // The default method for drawing vector layers on the map. `L.SVG`
2975 // or `L.Canvas` by default depending on browser support.
2976 renderer: undefined,
2979 // @section Animation Options
2980 // @option zoomAnimation: Boolean = true
2981 // Whether the map zoom animation is enabled. By default it's enabled
2982 // in all browsers that support CSS3 Transitions except Android.
2983 zoomAnimation: true,
2985 // @option zoomAnimationThreshold: Number = 4
2986 // Won't animate zoom if the zoom difference exceeds this value.
2987 zoomAnimationThreshold: 4,
2989 // @option fadeAnimation: Boolean = true
2990 // Whether the tile fade animation is enabled. By default it's enabled
2991 // in all browsers that support CSS3 Transitions except Android.
2992 fadeAnimation: true,
2994 // @option markerZoomAnimation: Boolean = true
2995 // Whether markers animate their zoom with the zoom animation, if disabled
2996 // they will disappear for the length of the animation. By default it's
2997 // enabled in all browsers that support CSS3 Transitions except Android.
2998 markerZoomAnimation: true,
3000 // @option transform3DLimit: Number = 2^23
3001 // Defines the maximum size of a CSS translation transform. The default
3002 // value should not be changed unless a web browser positions layers in
3003 // the wrong place after doing a large `panBy`.
3004 transform3DLimit: 8388608, // Precision limit of a 32-bit float
3006 // @section Interaction Options
3007 // @option zoomSnap: Number = 1
3008 // Forces the map's zoom level to always be a multiple of this, particularly
3009 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
3010 // By default, the zoom level snaps to the nearest integer; lower values
3011 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
3012 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
3015 // @option zoomDelta: Number = 1
3016 // Controls how much the map's zoom level will change after a
3017 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
3018 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
3019 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
3022 // @option trackResize: Boolean = true
3023 // Whether the map automatically handles browser window resize to update itself.
3027 initialize: function (id, options) { // (HTMLElement or String, Object)
3028 options = setOptions(this, options);
3030 this._initContainer(id);
3033 // hack for https://github.com/Leaflet/Leaflet/issues/1980
3034 this._onResize = bind(this._onResize, this);
3038 if (options.maxBounds) {
3039 this.setMaxBounds(options.maxBounds);
3042 if (options.zoom !== undefined) {
3043 this._zoom = this._limitZoom(options.zoom);
3046 if (options.center && options.zoom !== undefined) {
3047 this.setView(toLatLng(options.center), options.zoom, {reset: true});
3050 this._handlers = [];
3052 this._zoomBoundLayers = {};
3053 this._sizeChanged = true;
3055 this.callInitHooks();
3057 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
3058 this._zoomAnimated = TRANSITION && any3d && !mobileOpera &&
3059 this.options.zoomAnimation;
3061 // zoom transitions run with the same duration for all layers, so if one of transitionend events
3062 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
3063 if (this._zoomAnimated) {
3064 this._createAnimProxy();
3065 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
3068 this._addLayers(this.options.layers);
3072 // @section Methods for modifying map state
3074 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
3075 // Sets the view of the map (geographical center and zoom) with the given
3076 // animation options.
3077 setView: function (center, zoom, options) {
3079 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
3080 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
3081 options = options || {};
3085 if (this._loaded && !options.reset && options !== true) {
3087 if (options.animate !== undefined) {
3088 options.zoom = extend({animate: options.animate}, options.zoom);
3089 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
3092 // try animating pan or zoom
3093 var moved = (this._zoom !== zoom) ?
3094 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
3095 this._tryAnimatedPan(center, options.pan);
3098 // prevent resize handler call, the view will refresh after animation anyway
3099 clearTimeout(this._sizeTimer);
3104 // animation didn't start, just reset the map view
3105 this._resetView(center, zoom);
3110 // @method setZoom(zoom: Number, options?: Zoom/pan options): this
3111 // Sets the zoom of the map.
3112 setZoom: function (zoom, options) {
3113 if (!this._loaded) {
3117 return this.setView(this.getCenter(), zoom, {zoom: options});
3120 // @method zoomIn(delta?: Number, options?: Zoom options): this
3121 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3122 zoomIn: function (delta, options) {
3123 delta = delta || (any3d ? this.options.zoomDelta : 1);
3124 return this.setZoom(this._zoom + delta, options);
3127 // @method zoomOut(delta?: Number, options?: Zoom options): this
3128 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3129 zoomOut: function (delta, options) {
3130 delta = delta || (any3d ? this.options.zoomDelta : 1);
3131 return this.setZoom(this._zoom - delta, options);
3134 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
3135 // Zooms the map while keeping a specified geographical point on the map
3136 // stationary (e.g. used internally for scroll zoom and double-click zoom).
3138 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
3139 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
3140 setZoomAround: function (latlng, zoom, options) {
3141 var scale = this.getZoomScale(zoom),
3142 viewHalf = this.getSize().divideBy(2),
3143 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
3145 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
3146 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
3148 return this.setView(newCenter, zoom, {zoom: options});
3151 _getBoundsCenterZoom: function (bounds, options) {
3153 options = options || {};
3154 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
3156 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3157 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3159 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
3161 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
3163 if (zoom === Infinity) {
3165 center: bounds.getCenter(),
3170 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
3172 swPoint = this.project(bounds.getSouthWest(), zoom),
3173 nePoint = this.project(bounds.getNorthEast(), zoom),
3174 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
3182 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
3183 // Sets a map view that contains the given geographical bounds with the
3184 // maximum zoom level possible.
3185 fitBounds: function (bounds, options) {
3187 bounds = toLatLngBounds(bounds);
3189 if (!bounds.isValid()) {
3190 throw new Error('Bounds are not valid.');
3193 var target = this._getBoundsCenterZoom(bounds, options);
3194 return this.setView(target.center, target.zoom, options);
3197 // @method fitWorld(options?: fitBounds options): this
3198 // Sets a map view that mostly contains the whole world with the maximum
3199 // zoom level possible.
3200 fitWorld: function (options) {
3201 return this.fitBounds([[-90, -180], [90, 180]], options);
3204 // @method panTo(latlng: LatLng, options?: Pan options): this
3205 // Pans the map to a given center.
3206 panTo: function (center, options) { // (LatLng)
3207 return this.setView(center, this._zoom, {pan: options});
3210 // @method panBy(offset: Point, options?: Pan options): this
3211 // Pans the map by a given number of pixels (animated).
3212 panBy: function (offset, options) {
3213 offset = toPoint(offset).round();
3214 options = options || {};
3216 if (!offset.x && !offset.y) {
3217 return this.fire('moveend');
3219 // If we pan too far, Chrome gets issues with tiles
3220 // and makes them disappear or appear in the wrong place (slightly offset) #2602
3221 if (options.animate !== true && !this.getSize().contains(offset)) {
3222 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
3226 if (!this._panAnim) {
3227 this._panAnim = new PosAnimation();
3230 'step': this._onPanTransitionStep,
3231 'end': this._onPanTransitionEnd
3235 // don't fire movestart if animating inertia
3236 if (!options.noMoveStart) {
3237 this.fire('movestart');
3240 // animate pan unless animate: false specified
3241 if (options.animate !== false) {
3242 addClass(this._mapPane, 'leaflet-pan-anim');
3244 var newPos = this._getMapPanePos().subtract(offset).round();
3245 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
3247 this._rawPanBy(offset);
3248 this.fire('move').fire('moveend');
3254 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
3255 // Sets the view of the map (geographical center and zoom) performing a smooth
3256 // pan-zoom animation.
3257 flyTo: function (targetCenter, targetZoom, options) {
3259 options = options || {};
3260 if (options.animate === false || !any3d) {
3261 return this.setView(targetCenter, targetZoom, options);
3266 var from = this.project(this.getCenter()),
3267 to = this.project(targetCenter),
3268 size = this.getSize(),
3269 startZoom = this._zoom;
3271 targetCenter = toLatLng(targetCenter);
3272 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
3274 var w0 = Math.max(size.x, size.y),
3275 w1 = w0 * this.getZoomScale(startZoom, targetZoom),
3276 u1 = (to.distanceTo(from)) || 1,
3281 var s1 = i ? -1 : 1,
3283 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
3284 b1 = 2 * s2 * rho2 * u1,
3286 sq = Math.sqrt(b * b + 1) - b;
3288 // workaround for floating point precision bug when sq = 0, log = -Infinite,
3289 // thus triggering an infinite loop in flyTo
3290 var log = sq < 0.000000001 ? -18 : Math.log(sq);
3295 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
3296 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
3297 function tanh(n) { return sinh(n) / cosh(n); }
3301 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
3302 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
3304 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
3306 var start = Date.now(),
3307 S = (r(1) - r0) / rho,
3308 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
3311 var t = (Date.now() - start) / duration,
3315 this._flyToFrame = requestAnimFrame(frame, this);
3318 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
3319 this.getScaleZoom(w0 / w(s), startZoom),
3324 ._move(targetCenter, targetZoom)
3329 this._moveStart(true);
3335 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
3336 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
3337 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
3338 flyToBounds: function (bounds, options) {
3339 var target = this._getBoundsCenterZoom(bounds, options);
3340 return this.flyTo(target.center, target.zoom, options);
3343 // @method setMaxBounds(bounds: Bounds): this
3344 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
3345 setMaxBounds: function (bounds) {
3346 bounds = toLatLngBounds(bounds);
3348 if (!bounds.isValid()) {
3349 this.options.maxBounds = null;
3350 return this.off('moveend', this._panInsideMaxBounds);
3351 } else if (this.options.maxBounds) {
3352 this.off('moveend', this._panInsideMaxBounds);
3355 this.options.maxBounds = bounds;
3358 this._panInsideMaxBounds();
3361 return this.on('moveend', this._panInsideMaxBounds);
3364 // @method setMinZoom(zoom: Number): this
3365 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
3366 setMinZoom: function (zoom) {
3367 this.options.minZoom = zoom;
3369 if (this._loaded && this.getZoom() < this.options.minZoom) {
3370 return this.setZoom(zoom);
3376 // @method setMaxZoom(zoom: Number): this
3377 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
3378 setMaxZoom: function (zoom) {
3379 this.options.maxZoom = zoom;
3381 if (this._loaded && (this.getZoom() > this.options.maxZoom)) {
3382 return this.setZoom(zoom);
3388 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
3389 // 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.
3390 panInsideBounds: function (bounds, options) {
3391 this._enforcingBounds = true;
3392 var center = this.getCenter(),
3393 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
3395 if (!center.equals(newCenter)) {
3396 this.panTo(newCenter, options);
3399 this._enforcingBounds = false;
3403 // @method invalidateSize(options: Zoom/Pan options): this
3404 // Checks if the map container size changed and updates the map if so —
3405 // call it after you've changed the map size dynamically, also animating
3406 // pan by default. If `options.pan` is `false`, panning will not occur.
3407 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
3408 // that it doesn't happen often even if the method is called many
3412 // @method invalidateSize(animate: Boolean): this
3413 // Checks if the map container size changed and updates the map if so —
3414 // call it after you've changed the map size dynamically, also animating
3416 invalidateSize: function (options) {
3417 if (!this._loaded) { return this; }
3422 }, options === true ? {animate: true} : options);
3424 var oldSize = this.getSize();
3425 this._sizeChanged = true;
3426 this._lastCenter = null;
3428 var newSize = this.getSize(),
3429 oldCenter = oldSize.divideBy(2).round(),
3430 newCenter = newSize.divideBy(2).round(),
3431 offset = oldCenter.subtract(newCenter);
3433 if (!offset.x && !offset.y) { return this; }
3435 if (options.animate && options.pan) {
3440 this._rawPanBy(offset);
3445 if (options.debounceMoveend) {
3446 clearTimeout(this._sizeTimer);
3447 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
3449 this.fire('moveend');
3453 // @section Map state change events
3454 // @event resize: ResizeEvent
3455 // Fired when the map is resized.
3456 return this.fire('resize', {
3462 // @section Methods for modifying map state
3463 // @method stop(): this
3464 // Stops the currently running `panTo` or `flyTo` animation, if any.
3466 this.setZoom(this._limitZoom(this._zoom));
3467 if (!this.options.zoomSnap) {
3468 this.fire('viewreset');
3470 return this._stop();
3473 // @section Geolocation methods
3474 // @method locate(options?: Locate options): this
3475 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
3476 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
3477 // and optionally sets the map view to the user's location with respect to
3478 // detection accuracy (or to the world view if geolocation failed).
3479 // Note that, if your page doesn't use HTTPS, this method will fail in
3480 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
3481 // See `Locate options` for more details.
3482 locate: function (options) {
3484 options = this._locateOptions = extend({
3488 // maxZoom: <Number>
3490 // enableHighAccuracy: false
3493 if (!('geolocation' in navigator)) {
3494 this._handleGeolocationError({
3496 message: 'Geolocation not supported.'
3501 var onResponse = bind(this._handleGeolocationResponse, this),
3502 onError = bind(this._handleGeolocationError, this);
3504 if (options.watch) {
3505 this._locationWatchId =
3506 navigator.geolocation.watchPosition(onResponse, onError, options);
3508 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
3513 // @method stopLocate(): this
3514 // Stops watching location previously initiated by `map.locate({watch: true})`
3515 // and aborts resetting the map view if map.locate was called with
3516 // `{setView: true}`.
3517 stopLocate: function () {
3518 if (navigator.geolocation && navigator.geolocation.clearWatch) {
3519 navigator.geolocation.clearWatch(this._locationWatchId);
3521 if (this._locateOptions) {
3522 this._locateOptions.setView = false;
3527 _handleGeolocationError: function (error) {
3529 message = error.message ||
3530 (c === 1 ? 'permission denied' :
3531 (c === 2 ? 'position unavailable' : 'timeout'));
3533 if (this._locateOptions.setView && !this._loaded) {
3537 // @section Location events
3538 // @event locationerror: ErrorEvent
3539 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
3540 this.fire('locationerror', {
3542 message: 'Geolocation error: ' + message + '.'
3546 _handleGeolocationResponse: function (pos) {
3547 var lat = pos.coords.latitude,
3548 lng = pos.coords.longitude,
3549 latlng = new LatLng(lat, lng),
3550 bounds = latlng.toBounds(pos.coords.accuracy),
3551 options = this._locateOptions;
3553 if (options.setView) {
3554 var zoom = this.getBoundsZoom(bounds);
3555 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
3561 timestamp: pos.timestamp
3564 for (var i in pos.coords) {
3565 if (typeof pos.coords[i] === 'number') {
3566 data[i] = pos.coords[i];
3570 // @event locationfound: LocationEvent
3571 // Fired when geolocation (using the [`locate`](#map-locate) method)
3572 // went successfully.
3573 this.fire('locationfound', data);
3576 // TODO handler.addTo
3577 // TODO Appropiate docs section?
3578 // @section Other Methods
3579 // @method addHandler(name: String, HandlerClass: Function): this
3580 // Adds a new `Handler` to the map, given its name and constructor function.
3581 addHandler: function (name, HandlerClass) {
3582 if (!HandlerClass) { return this; }
3584 var handler = this[name] = new HandlerClass(this);
3586 this._handlers.push(handler);
3588 if (this.options[name]) {
3595 // @method remove(): this
3596 // Destroys the map and clears all related event listeners.
3597 remove: function () {
3599 this._initEvents(true);
3601 if (this._containerId !== this._container._leaflet_id) {
3602 throw new Error('Map container is being reused by another instance');
3606 // throws error in IE6-8
3607 delete this._container._leaflet_id;
3608 delete this._containerId;
3611 this._container._leaflet_id = undefined;
3613 this._containerId = undefined;
3616 remove(this._mapPane);
3618 if (this._clearControlPos) {
3619 this._clearControlPos();
3622 this._clearHandlers();
3625 // @section Map state change events
3626 // @event unload: Event
3627 // Fired when the map is destroyed with [remove](#map-remove) method.
3628 this.fire('unload');
3632 for (i in this._layers) {
3633 this._layers[i].remove();
3635 for (i in this._panes) {
3636 remove(this._panes[i]);
3641 delete this._mapPane;
3642 delete this._renderer;
3647 // @section Other Methods
3648 // @method createPane(name: String, container?: HTMLElement): HTMLElement
3649 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
3650 // then returns it. The pane is created as a child of `container`, or
3651 // as a child of the main map pane if not set.
3652 createPane: function (name, container) {
3653 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3654 pane = create$1('div', className, container || this._mapPane);
3657 this._panes[name] = pane;
3662 // @section Methods for Getting Map State
3664 // @method getCenter(): LatLng
3665 // Returns the geographical center of the map view
3666 getCenter: function () {
3667 this._checkIfLoaded();
3669 if (this._lastCenter && !this._moved()) {
3670 return this._lastCenter;
3672 return this.layerPointToLatLng(this._getCenterLayerPoint());
3675 // @method getZoom(): Number
3676 // Returns the current zoom level of the map view
3677 getZoom: function () {
3681 // @method getBounds(): LatLngBounds
3682 // Returns the geographical bounds visible in the current map view
3683 getBounds: function () {
3684 var bounds = this.getPixelBounds(),
3685 sw = this.unproject(bounds.getBottomLeft()),
3686 ne = this.unproject(bounds.getTopRight());
3688 return new LatLngBounds(sw, ne);
3691 // @method getMinZoom(): Number
3692 // 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.
3693 getMinZoom: function () {
3694 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3697 // @method getMaxZoom(): Number
3698 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3699 getMaxZoom: function () {
3700 return this.options.maxZoom === undefined ?
3701 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3702 this.options.maxZoom;
3705 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number
3706 // Returns the maximum zoom level on which the given bounds fit to the map
3707 // view in its entirety. If `inside` (optional) is set to `true`, the method
3708 // instead returns the minimum zoom level on which the map view fits into
3709 // the given bounds in its entirety.
3710 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3711 bounds = toLatLngBounds(bounds);
3712 padding = toPoint(padding || [0, 0]);
3714 var zoom = this.getZoom() || 0,
3715 min = this.getMinZoom(),
3716 max = this.getMaxZoom(),
3717 nw = bounds.getNorthWest(),
3718 se = bounds.getSouthEast(),
3719 size = this.getSize().subtract(padding),
3720 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3721 snap = any3d ? this.options.zoomSnap : 1,
3722 scalex = size.x / boundsSize.x,
3723 scaley = size.y / boundsSize.y,
3724 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
3726 zoom = this.getScaleZoom(scale, zoom);
3729 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3730 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3733 return Math.max(min, Math.min(max, zoom));
3736 // @method getSize(): Point
3737 // Returns the current size of the map container (in pixels).
3738 getSize: function () {
3739 if (!this._size || this._sizeChanged) {
3740 this._size = new Point(
3741 this._container.clientWidth || 0,
3742 this._container.clientHeight || 0);
3744 this._sizeChanged = false;
3746 return this._size.clone();
3749 // @method getPixelBounds(): Bounds
3750 // Returns the bounds of the current map view in projected pixel
3751 // coordinates (sometimes useful in layer and overlay implementations).
3752 getPixelBounds: function (center, zoom) {
3753 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3754 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3757 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3758 // the map pane? "left point of the map layer" can be confusing, specially
3759 // since there can be negative offsets.
3760 // @method getPixelOrigin(): Point
3761 // Returns the projected pixel coordinates of the top left point of
3762 // the map layer (useful in custom layer and overlay implementations).
3763 getPixelOrigin: function () {
3764 this._checkIfLoaded();
3765 return this._pixelOrigin;
3768 // @method getPixelWorldBounds(zoom?: Number): Bounds
3769 // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3770 // If `zoom` is omitted, the map's current zoom level is used.
3771 getPixelWorldBounds: function (zoom) {
3772 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3775 // @section Other Methods
3777 // @method getPane(pane: String|HTMLElement): HTMLElement
3778 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3779 getPane: function (pane) {
3780 return typeof pane === 'string' ? this._panes[pane] : pane;
3783 // @method getPanes(): Object
3784 // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3785 // the panes as values.
3786 getPanes: function () {
3790 // @method getContainer: HTMLElement
3791 // Returns the HTML element that contains the map.
3792 getContainer: function () {
3793 return this._container;
3797 // @section Conversion Methods
3799 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3800 // Returns the scale factor to be applied to a map transition from zoom level
3801 // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3802 getZoomScale: function (toZoom, fromZoom) {
3803 // TODO replace with universal implementation after refactoring projections
3804 var crs = this.options.crs;
3805 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3806 return crs.scale(toZoom) / crs.scale(fromZoom);
3809 // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3810 // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3811 // level and everything is scaled by a factor of `scale`. Inverse of
3812 // [`getZoomScale`](#map-getZoomScale).
3813 getScaleZoom: function (scale, fromZoom) {
3814 var crs = this.options.crs;
3815 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3816 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3817 return isNaN(zoom) ? Infinity : zoom;
3820 // @method project(latlng: LatLng, zoom: Number): Point
3821 // Projects a geographical coordinate `LatLng` according to the projection
3822 // of the map's CRS, then scales it according to `zoom` and the CRS's
3823 // `Transformation`. The result is pixel coordinate relative to
3825 project: function (latlng, zoom) {
3826 zoom = zoom === undefined ? this._zoom : zoom;
3827 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
3830 // @method unproject(point: Point, zoom: Number): LatLng
3831 // Inverse of [`project`](#map-project).
3832 unproject: function (point, zoom) {
3833 zoom = zoom === undefined ? this._zoom : zoom;
3834 return this.options.crs.pointToLatLng(toPoint(point), zoom);
3837 // @method layerPointToLatLng(point: Point): LatLng
3838 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3839 // returns the corresponding geographical coordinate (for the current zoom level).
3840 layerPointToLatLng: function (point) {
3841 var projectedPoint = toPoint(point).add(this.getPixelOrigin());
3842 return this.unproject(projectedPoint);
3845 // @method latLngToLayerPoint(latlng: LatLng): Point
3846 // Given a geographical coordinate, returns the corresponding pixel coordinate
3847 // relative to the [origin pixel](#map-getpixelorigin).
3848 latLngToLayerPoint: function (latlng) {
3849 var projectedPoint = this.project(toLatLng(latlng))._round();
3850 return projectedPoint._subtract(this.getPixelOrigin());
3853 // @method wrapLatLng(latlng: LatLng): LatLng
3854 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
3855 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
3857 // By default this means longitude is wrapped around the dateline so its
3858 // value is between -180 and +180 degrees.
3859 wrapLatLng: function (latlng) {
3860 return this.options.crs.wrapLatLng(toLatLng(latlng));
3863 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
3864 // Returns a `LatLngBounds` with the same size as the given one, ensuring that
3865 // its center is within the CRS's bounds.
3866 // By default this means the center longitude is wrapped around the dateline so its
3867 // value is between -180 and +180 degrees, and the majority of the bounds
3868 // overlaps the CRS's bounds.
3869 wrapLatLngBounds: function (latlng) {
3870 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
3873 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
3874 // Returns the distance between two geographical coordinates according to
3875 // the map's CRS. By default this measures distance in meters.
3876 distance: function (latlng1, latlng2) {
3877 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
3880 // @method containerPointToLayerPoint(point: Point): Point
3881 // Given a pixel coordinate relative to the map container, returns the corresponding
3882 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
3883 containerPointToLayerPoint: function (point) { // (Point)
3884 return toPoint(point).subtract(this._getMapPanePos());
3887 // @method layerPointToContainerPoint(point: Point): Point
3888 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3889 // returns the corresponding pixel coordinate relative to the map container.
3890 layerPointToContainerPoint: function (point) { // (Point)
3891 return toPoint(point).add(this._getMapPanePos());
3894 // @method containerPointToLatLng(point: Point): LatLng
3895 // Given a pixel coordinate relative to the map container, returns
3896 // the corresponding geographical coordinate (for the current zoom level).
3897 containerPointToLatLng: function (point) {
3898 var layerPoint = this.containerPointToLayerPoint(toPoint(point));
3899 return this.layerPointToLatLng(layerPoint);
3902 // @method latLngToContainerPoint(latlng: LatLng): Point
3903 // Given a geographical coordinate, returns the corresponding pixel coordinate
3904 // relative to the map container.
3905 latLngToContainerPoint: function (latlng) {
3906 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
3909 // @method mouseEventToContainerPoint(ev: MouseEvent): Point
3910 // Given a MouseEvent object, returns the pixel coordinate relative to the
3911 // map container where the event took place.
3912 mouseEventToContainerPoint: function (e) {
3913 return getMousePosition(e, this._container);
3916 // @method mouseEventToLayerPoint(ev: MouseEvent): Point
3917 // Given a MouseEvent object, returns the pixel coordinate relative to
3918 // the [origin pixel](#map-getpixelorigin) where the event took place.
3919 mouseEventToLayerPoint: function (e) {
3920 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
3923 // @method mouseEventToLatLng(ev: MouseEvent): LatLng
3924 // Given a MouseEvent object, returns geographical coordinate where the
3925 // event took place.
3926 mouseEventToLatLng: function (e) { // (MouseEvent)
3927 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
3931 // map initialization methods
3933 _initContainer: function (id) {
3934 var container = this._container = get(id);
3937 throw new Error('Map container not found.');
3938 } else if (container._leaflet_id) {
3939 throw new Error('Map container is already initialized.');
3942 on(container, 'scroll', this._onScroll, this);
3943 this._containerId = stamp(container);
3946 _initLayout: function () {
3947 var container = this._container;
3949 this._fadeAnimated = this.options.fadeAnimation && any3d;
3951 addClass(container, 'leaflet-container' +
3952 (touch ? ' leaflet-touch' : '') +
3953 (retina ? ' leaflet-retina' : '') +
3954 (ielt9 ? ' leaflet-oldie' : '') +
3955 (safari ? ' leaflet-safari' : '') +
3956 (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
3958 var position = getStyle(container, 'position');
3960 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
3961 container.style.position = 'relative';
3966 if (this._initControlPos) {
3967 this._initControlPos();
3971 _initPanes: function () {
3972 var panes = this._panes = {};
3973 this._paneRenderers = {};
3977 // Panes are DOM elements used to control the ordering of layers on the map. You
3978 // can access panes with [`map.getPane`](#map-getpane) or
3979 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
3980 // [`map.createPane`](#map-createpane) method.
3982 // Every map has the following default panes that differ only in zIndex.
3984 // @pane mapPane: HTMLElement = 'auto'
3985 // Pane that contains all other map panes
3987 this._mapPane = this.createPane('mapPane', this._container);
3988 setPosition(this._mapPane, new Point(0, 0));
3990 // @pane tilePane: HTMLElement = 200
3991 // Pane for `GridLayer`s and `TileLayer`s
3992 this.createPane('tilePane');
3993 // @pane overlayPane: HTMLElement = 400
3994 // Pane for vector overlays (`Path`s), like `Polyline`s and `Polygon`s
3995 this.createPane('shadowPane');
3996 // @pane shadowPane: HTMLElement = 500
3997 // Pane for overlay shadows (e.g. `Marker` shadows)
3998 this.createPane('overlayPane');
3999 // @pane markerPane: HTMLElement = 600
4000 // Pane for `Icon`s of `Marker`s
4001 this.createPane('markerPane');
4002 // @pane tooltipPane: HTMLElement = 650
4003 // Pane for tooltip.
4004 this.createPane('tooltipPane');
4005 // @pane popupPane: HTMLElement = 700
4006 // Pane for `Popup`s.
4007 this.createPane('popupPane');
4009 if (!this.options.markerZoomAnimation) {
4010 addClass(panes.markerPane, 'leaflet-zoom-hide');
4011 addClass(panes.shadowPane, 'leaflet-zoom-hide');
4016 // private methods that modify map state
4018 // @section Map state change events
4019 _resetView: function (center, zoom) {
4020 setPosition(this._mapPane, new Point(0, 0));
4022 var loading = !this._loaded;
4023 this._loaded = true;
4024 zoom = this._limitZoom(zoom);
4026 this.fire('viewprereset');
4028 var zoomChanged = this._zoom !== zoom;
4030 ._moveStart(zoomChanged)
4031 ._move(center, zoom)
4032 ._moveEnd(zoomChanged);
4034 // @event viewreset: Event
4035 // Fired when the map needs to redraw its content (this usually happens
4036 // on map zoom or load). Very useful for creating custom overlays.
4037 this.fire('viewreset');
4039 // @event load: Event
4040 // Fired when the map is initialized (when its center and zoom are set
4041 // for the first time).
4047 _moveStart: function (zoomChanged) {
4048 // @event zoomstart: Event
4049 // Fired when the map zoom is about to change (e.g. before zoom animation).
4050 // @event movestart: Event
4051 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
4053 this.fire('zoomstart');
4055 return this.fire('movestart');
4058 _move: function (center, zoom, data) {
4059 if (zoom === undefined) {
4062 var zoomChanged = this._zoom !== zoom;
4065 this._lastCenter = center;
4066 this._pixelOrigin = this._getNewPixelOrigin(center);
4068 // @event zoom: Event
4069 // Fired repeatedly during any change in zoom level, including zoom
4070 // and fly animations.
4071 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530
4072 this.fire('zoom', data);
4075 // @event move: Event
4076 // Fired repeatedly during any movement of the map, including pan and
4078 return this.fire('move', data);
4081 _moveEnd: function (zoomChanged) {
4082 // @event zoomend: Event
4083 // Fired when the map has changed, after any animations.
4085 this.fire('zoomend');
4088 // @event moveend: Event
4089 // Fired when the center of the map stops changing (e.g. user stopped
4090 // dragging the map).
4091 return this.fire('moveend');
4094 _stop: function () {
4095 cancelAnimFrame(this._flyToFrame);
4096 if (this._panAnim) {
4097 this._panAnim.stop();
4102 _rawPanBy: function (offset) {
4103 setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
4106 _getZoomSpan: function () {
4107 return this.getMaxZoom() - this.getMinZoom();
4110 _panInsideMaxBounds: function () {
4111 if (!this._enforcingBounds) {
4112 this.panInsideBounds(this.options.maxBounds);
4116 _checkIfLoaded: function () {
4117 if (!this._loaded) {
4118 throw new Error('Set map center and zoom first.');
4122 // DOM event handling
4124 // @section Interaction events
4125 _initEvents: function (remove$$1) {
4127 this._targets[stamp(this._container)] = this;
4129 var onOff = remove$$1 ? off : on;
4131 // @event click: MouseEvent
4132 // Fired when the user clicks (or taps) the map.
4133 // @event dblclick: MouseEvent
4134 // Fired when the user double-clicks (or double-taps) the map.
4135 // @event mousedown: MouseEvent
4136 // Fired when the user pushes the mouse button on the map.
4137 // @event mouseup: MouseEvent
4138 // Fired when the user releases the mouse button on the map.
4139 // @event mouseover: MouseEvent
4140 // Fired when the mouse enters the map.
4141 // @event mouseout: MouseEvent
4142 // Fired when the mouse leaves the map.
4143 // @event mousemove: MouseEvent
4144 // Fired while the mouse moves over the map.
4145 // @event contextmenu: MouseEvent
4146 // Fired when the user pushes the right mouse button on the map, prevents
4147 // default browser context menu from showing if there are listeners on
4148 // this event. Also fired on mobile when the user holds a single touch
4149 // for a second (also called long press).
4150 // @event keypress: KeyboardEvent
4151 // Fired when the user presses a key from the keyboard while the map is focused.
4152 onOff(this._container, 'click dblclick mousedown mouseup ' +
4153 'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);
4155 if (this.options.trackResize) {
4156 onOff(window, 'resize', this._onResize, this);
4159 if (any3d && this.options.transform3DLimit) {
4160 (remove$$1 ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
4164 _onResize: function () {
4165 cancelAnimFrame(this._resizeRequest);
4166 this._resizeRequest = requestAnimFrame(
4167 function () { this.invalidateSize({debounceMoveend: true}); }, this);
4170 _onScroll: function () {
4171 this._container.scrollTop = 0;
4172 this._container.scrollLeft = 0;
4175 _onMoveEnd: function () {
4176 var pos = this._getMapPanePos();
4177 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
4178 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
4179 // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
4180 this._resetView(this.getCenter(), this.getZoom());
4184 _findEventTargets: function (e, type) {
4187 isHover = type === 'mouseout' || type === 'mouseover',
4188 src = e.target || e.srcElement,
4192 target = this._targets[stamp(src)];
4193 if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
4194 // Prevent firing click after you just dragged an object.
4198 if (target && target.listens(type, true)) {
4199 if (isHover && !isExternalTarget(src, e)) { break; }
4200 targets.push(target);
4201 if (isHover) { break; }
4203 if (src === this._container) { break; }
4204 src = src.parentNode;
4206 if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) {
4212 _handleDOMEvent: function (e) {
4213 if (!this._loaded || skipped(e)) { return; }
4217 if (type === 'mousedown' || type === 'keypress') {
4218 // prevents outline when clicking on keyboard-focusable element
4219 preventOutline(e.target || e.srcElement);
4222 this._fireDOMEvent(e, type);
4225 _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
4227 _fireDOMEvent: function (e, type, targets) {
4229 if (e.type === 'click') {
4230 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
4231 // @event preclick: MouseEvent
4232 // Fired before mouse click on the map (sometimes useful when you
4233 // want something to happen on click before any existing click
4234 // handlers start running).
4235 var synth = extend({}, e);
4236 synth.type = 'preclick';
4237 this._fireDOMEvent(synth, synth.type, targets);
4240 if (e._stopped) { return; }
4242 // Find the layer the event is propagating from and its parents.
4243 targets = (targets || []).concat(this._findEventTargets(e, type));
4245 if (!targets.length) { return; }
4247 var target = targets[0];
4248 if (type === 'contextmenu' && target.listens(type, true)) {
4256 if (e.type !== 'keypress') {
4257 var isMarker = (target.options && 'icon' in target.options);
4258 data.containerPoint = isMarker ?
4259 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
4260 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
4261 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
4264 for (var i = 0; i < targets.length; i++) {
4265 targets[i].fire(type, data, true);
4266 if (data.originalEvent._stopped ||
4267 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
4271 _draggableMoved: function (obj) {
4272 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
4273 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
4276 _clearHandlers: function () {
4277 for (var i = 0, len = this._handlers.length; i < len; i++) {
4278 this._handlers[i].disable();
4282 // @section Other Methods
4284 // @method whenReady(fn: Function, context?: Object): this
4285 // Runs the given function `fn` when the map gets initialized with
4286 // a view (center and zoom) and at least one layer, or immediately
4287 // if it's already initialized, optionally passing a function context.
4288 whenReady: function (callback, context) {
4290 callback.call(context || this, {target: this});
4292 this.on('load', callback, context);
4298 // private methods for getting map state
4300 _getMapPanePos: function () {
4301 return getPosition(this._mapPane) || new Point(0, 0);
4304 _moved: function () {
4305 var pos = this._getMapPanePos();
4306 return pos && !pos.equals([0, 0]);
4309 _getTopLeftPoint: function (center, zoom) {
4310 var pixelOrigin = center && zoom !== undefined ?
4311 this._getNewPixelOrigin(center, zoom) :
4312 this.getPixelOrigin();
4313 return pixelOrigin.subtract(this._getMapPanePos());
4316 _getNewPixelOrigin: function (center, zoom) {
4317 var viewHalf = this.getSize()._divideBy(2);
4318 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
4321 _latLngToNewLayerPoint: function (latlng, zoom, center) {
4322 var topLeft = this._getNewPixelOrigin(center, zoom);
4323 return this.project(latlng, zoom)._subtract(topLeft);
4326 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
4327 var topLeft = this._getNewPixelOrigin(center, zoom);
4329 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
4330 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
4331 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
4332 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
4336 // layer point of the current center
4337 _getCenterLayerPoint: function () {
4338 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
4341 // offset of the specified place to the current center in pixels
4342 _getCenterOffset: function (latlng) {
4343 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
4346 // adjust center for view to get inside bounds
4347 _limitCenter: function (center, zoom, bounds) {
4349 if (!bounds) { return center; }
4351 var centerPoint = this.project(center, zoom),
4352 viewHalf = this.getSize().divideBy(2),
4353 viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
4354 offset = this._getBoundsOffset(viewBounds, bounds, zoom);
4356 // If offset is less than a pixel, ignore.
4357 // This prevents unstable projections from getting into
4358 // an infinite loop of tiny offsets.
4359 if (offset.round().equals([0, 0])) {
4363 return this.unproject(centerPoint.add(offset), zoom);
4366 // adjust offset for view to get inside bounds
4367 _limitOffset: function (offset, bounds) {
4368 if (!bounds) { return offset; }
4370 var viewBounds = this.getPixelBounds(),
4371 newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
4373 return offset.add(this._getBoundsOffset(newBounds, bounds));
4376 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
4377 _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
4378 var projectedMaxBounds = toBounds(
4379 this.project(maxBounds.getNorthEast(), zoom),
4380 this.project(maxBounds.getSouthWest(), zoom)
4382 minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
4383 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
4385 dx = this._rebound(minOffset.x, -maxOffset.x),
4386 dy = this._rebound(minOffset.y, -maxOffset.y);
4388 return new Point(dx, dy);
4391 _rebound: function (left, right) {
4392 return left + right > 0 ?
4393 Math.round(left - right) / 2 :
4394 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
4397 _limitZoom: function (zoom) {
4398 var min = this.getMinZoom(),
4399 max = this.getMaxZoom(),
4400 snap = any3d ? this.options.zoomSnap : 1;
4402 zoom = Math.round(zoom / snap) * snap;
4404 return Math.max(min, Math.min(max, zoom));
4407 _onPanTransitionStep: function () {
4411 _onPanTransitionEnd: function () {
4412 removeClass(this._mapPane, 'leaflet-pan-anim');
4413 this.fire('moveend');
4416 _tryAnimatedPan: function (center, options) {
4417 // difference between the new and current centers in pixels
4418 var offset = this._getCenterOffset(center)._floor();
4420 // don't animate too far unless animate: true specified in options
4421 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
4423 this.panBy(offset, options);
4428 _createAnimProxy: function () {
4430 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
4431 this._panes.mapPane.appendChild(proxy);
4433 this.on('zoomanim', function (e) {
4434 var prop = TRANSFORM,
4435 transform = this._proxy.style[prop];
4437 setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
4439 // workaround for case when transform is the same and so transitionend event is not fired
4440 if (transform === this._proxy.style[prop] && this._animatingZoom) {
4441 this._onZoomTransitionEnd();
4445 this.on('load moveend', function () {
4446 var c = this.getCenter(),
4448 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
4451 this._on('unload', this._destroyAnimProxy, this);
4454 _destroyAnimProxy: function () {
4455 remove(this._proxy);
4459 _catchTransitionEnd: function (e) {
4460 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
4461 this._onZoomTransitionEnd();
4465 _nothingToAnimate: function () {
4466 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
4469 _tryAnimatedZoom: function (center, zoom, options) {
4471 if (this._animatingZoom) { return true; }
4473 options = options || {};
4475 // don't animate if disabled, not supported or zoom difference is too large
4476 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
4477 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
4479 // offset is the pixel coords of the zoom origin relative to the current center
4480 var scale = this.getZoomScale(zoom),
4481 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
4483 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
4484 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
4486 requestAnimFrame(function () {
4489 ._animateZoom(center, zoom, true);
4495 _animateZoom: function (center, zoom, startAnim, noUpdate) {
4497 this._animatingZoom = true;
4499 // remember what center/zoom to set after animation
4500 this._animateToCenter = center;
4501 this._animateToZoom = zoom;
4503 addClass(this._mapPane, 'leaflet-zoom-anim');
4506 // @event zoomanim: ZoomAnimEvent
4507 // Fired on every frame of a zoom animation
4508 this.fire('zoomanim', {
4514 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
4515 setTimeout(bind(this._onZoomTransitionEnd, this), 250);
4518 _onZoomTransitionEnd: function () {
4519 if (!this._animatingZoom) { return; }
4521 removeClass(this._mapPane, 'leaflet-zoom-anim');
4523 this._animatingZoom = false;
4525 this._move(this._animateToCenter, this._animateToZoom);
4527 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
4528 requestAnimFrame(function () {
4529 this._moveEnd(true);
4536 // @factory L.map(id: String, options?: Map options)
4537 // Instantiates a map object given the DOM ID of a `<div>` element
4538 // and optionally an object literal with `Map options`.
4541 // @factory L.map(el: HTMLElement, options?: Map options)
4542 // Instantiates a map object given an instance of a `<div>` HTML element
4543 // and optionally an object literal with `Map options`.
4544 function createMap(id, options) {
4545 return new Map(id, options);
4553 * L.Control is a base class for implementing map controls. Handles positioning.
4554 * All other controls extend from this class.
4557 var Control = Class.extend({
4559 // @aka Control options
4561 // @option position: String = 'topright'
4562 // The position of the control (one of the map corners). Possible values are `'topleft'`,
4563 // `'topright'`, `'bottomleft'` or `'bottomright'`
4564 position: 'topright'
4567 initialize: function (options) {
4568 setOptions(this, options);
4572 * Classes extending L.Control will inherit the following methods:
4574 * @method getPosition: string
4575 * Returns the position of the control.
4577 getPosition: function () {
4578 return this.options.position;
4581 // @method setPosition(position: string): this
4582 // Sets the position of the control.
4583 setPosition: function (position) {
4584 var map = this._map;
4587 map.removeControl(this);
4590 this.options.position = position;
4593 map.addControl(this);
4599 // @method getContainer: HTMLElement
4600 // Returns the HTMLElement that contains the control.
4601 getContainer: function () {
4602 return this._container;
4605 // @method addTo(map: Map): this
4606 // Adds the control to the given map.
4607 addTo: function (map) {
4611 var container = this._container = this.onAdd(map),
4612 pos = this.getPosition(),
4613 corner = map._controlCorners[pos];
4615 addClass(container, 'leaflet-control');
4617 if (pos.indexOf('bottom') !== -1) {
4618 corner.insertBefore(container, corner.firstChild);
4620 corner.appendChild(container);
4626 // @method remove: this
4627 // Removes the control from the map it is currently active on.
4628 remove: function () {
4633 remove(this._container);
4635 if (this.onRemove) {
4636 this.onRemove(this._map);
4644 _refocusOnMap: function (e) {
4645 // if map exists and event is not a keyboard event
4646 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
4647 this._map.getContainer().focus();
4652 var control = function (options) {
4653 return new Control(options);
4656 /* @section Extension methods
4659 * Every control should extend from `L.Control` and (re-)implement the following methods.
4661 * @method onAdd(map: Map): HTMLElement
4662 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
4664 * @method onRemove(map: Map)
4665 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
4669 * @section Methods for Layers and Controls
4672 // @method addControl(control: Control): this
4673 // Adds the given control to the map
4674 addControl: function (control) {
4675 control.addTo(this);
4679 // @method removeControl(control: Control): this
4680 // Removes the given control from the map
4681 removeControl: function (control) {
4686 _initControlPos: function () {
4687 var corners = this._controlCorners = {},
4689 container = this._controlContainer =
4690 create$1('div', l + 'control-container', this._container);
4692 function createCorner(vSide, hSide) {
4693 var className = l + vSide + ' ' + l + hSide;
4695 corners[vSide + hSide] = create$1('div', className, container);
4698 createCorner('top', 'left');
4699 createCorner('top', 'right');
4700 createCorner('bottom', 'left');
4701 createCorner('bottom', 'right');
4704 _clearControlPos: function () {
4705 for (var i in this._controlCorners) {
4706 remove(this._controlCorners[i]);
4708 remove(this._controlContainer);
4709 delete this._controlCorners;
4710 delete this._controlContainer;
4715 * @class Control.Layers
4716 * @aka L.Control.Layers
4719 * 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`.
4724 * var baseLayers = {
4726 * "OpenStreetMap": osm
4731 * "Roads": roadsLayer
4734 * L.control.layers(baseLayers, overlays).addTo(map);
4737 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
4741 * "<someName1>": layer1,
4742 * "<someName2>": layer2
4746 * The layer names can contain HTML, which allows you to add additional styling to the items:
4749 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
4753 var Layers = Control.extend({
4755 // @aka Control.Layers options
4757 // @option collapsed: Boolean = true
4758 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
4760 position: 'topright',
4762 // @option autoZIndex: Boolean = true
4763 // 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.
4766 // @option hideSingleBase: Boolean = false
4767 // If `true`, the base layers in the control will be hidden when there is only one.
4768 hideSingleBase: false,
4770 // @option sortLayers: Boolean = false
4771 // Whether to sort the layers. When `false`, layers will keep the order
4772 // in which they were added to the control.
4775 // @option sortFunction: Function = *
4776 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
4777 // that will be used for sorting the layers, when `sortLayers` is `true`.
4778 // The function receives both the `L.Layer` instances and their names, as in
4779 // `sortFunction(layerA, layerB, nameA, nameB)`.
4780 // By default, it sorts layers alphabetically by their name.
4781 sortFunction: function (layerA, layerB, nameA, nameB) {
4782 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
4786 initialize: function (baseLayers, overlays, options) {
4787 setOptions(this, options);
4789 this._layerControlInputs = [];
4791 this._lastZIndex = 0;
4792 this._handlingClick = false;
4794 for (var i in baseLayers) {
4795 this._addLayer(baseLayers[i], i);
4798 for (i in overlays) {
4799 this._addLayer(overlays[i], i, true);
4803 onAdd: function (map) {
4808 map.on('zoomend', this._checkDisabledLayers, this);
4810 for (var i = 0; i < this._layers.length; i++) {
4811 this._layers[i].layer.on('add remove', this._onLayerChange, this);
4814 return this._container;
4817 addTo: function (map) {
4818 Control.prototype.addTo.call(this, map);
4819 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
4820 return this._expandIfNotCollapsed();
4823 onRemove: function () {
4824 this._map.off('zoomend', this._checkDisabledLayers, this);
4826 for (var i = 0; i < this._layers.length; i++) {
4827 this._layers[i].layer.off('add remove', this._onLayerChange, this);
4831 // @method addBaseLayer(layer: Layer, name: String): this
4832 // Adds a base layer (radio button entry) with the given name to the control.
4833 addBaseLayer: function (layer, name) {
4834 this._addLayer(layer, name);
4835 return (this._map) ? this._update() : this;
4838 // @method addOverlay(layer: Layer, name: String): this
4839 // Adds an overlay (checkbox entry) with the given name to the control.
4840 addOverlay: function (layer, name) {
4841 this._addLayer(layer, name, true);
4842 return (this._map) ? this._update() : this;
4845 // @method removeLayer(layer: Layer): this
4846 // Remove the given layer from the control.
4847 removeLayer: function (layer) {
4848 layer.off('add remove', this._onLayerChange, this);
4850 var obj = this._getLayer(stamp(layer));
4852 this._layers.splice(this._layers.indexOf(obj), 1);
4854 return (this._map) ? this._update() : this;
4857 // @method expand(): this
4858 // Expand the control container if collapsed.
4859 expand: function () {
4860 addClass(this._container, 'leaflet-control-layers-expanded');
4861 this._form.style.height = null;
4862 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
4863 if (acceptableHeight < this._form.clientHeight) {
4864 addClass(this._form, 'leaflet-control-layers-scrollbar');
4865 this._form.style.height = acceptableHeight + 'px';
4867 removeClass(this._form, 'leaflet-control-layers-scrollbar');
4869 this._checkDisabledLayers();
4873 // @method collapse(): this
4874 // Collapse the control container if expanded.
4875 collapse: function () {
4876 removeClass(this._container, 'leaflet-control-layers-expanded');
4880 _initLayout: function () {
4881 var className = 'leaflet-control-layers',
4882 container = this._container = create$1('div', className),
4883 collapsed = this.options.collapsed;
4885 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
4886 container.setAttribute('aria-haspopup', true);
4888 disableClickPropagation(container);
4889 disableScrollPropagation(container);
4891 var form = this._form = create$1('form', className + '-list');
4894 this._map.on('click', this.collapse, this);
4898 mouseenter: this.expand,
4899 mouseleave: this.collapse
4904 var link = this._layersLink = create$1('a', className + '-toggle', container);
4906 link.title = 'Layers';
4909 on(link, 'click', stop);
4910 on(link, 'click', this.expand, this);
4912 on(link, 'focus', this.expand, this);
4915 // work around for Firefox Android issue https://github.com/Leaflet/Leaflet/issues/2033
4916 on(form, 'click', function () {
4917 setTimeout(bind(this._onInputClick, this), 0);
4920 // TODO keyboard accessibility
4926 this._baseLayersList = create$1('div', className + '-base', form);
4927 this._separator = create$1('div', className + '-separator', form);
4928 this._overlaysList = create$1('div', className + '-overlays', form);
4930 container.appendChild(form);
4933 _getLayer: function (id) {
4934 for (var i = 0; i < this._layers.length; i++) {
4936 if (this._layers[i] && stamp(this._layers[i].layer) === id) {
4937 return this._layers[i];
4942 _addLayer: function (layer, name, overlay) {
4944 layer.on('add remove', this._onLayerChange, this);
4953 if (this.options.sortLayers) {
4954 this._layers.sort(L.bind(function (a, b) {
4955 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
4959 if (this.options.autoZIndex && layer.setZIndex) {
4961 layer.setZIndex(this._lastZIndex);
4964 this._expandIfNotCollapsed();
4967 _update: function () {
4968 if (!this._container) { return this; }
4970 empty(this._baseLayersList);
4971 empty(this._overlaysList);
4973 this._layerControlInputs = [];
4974 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
4976 for (i = 0; i < this._layers.length; i++) {
4977 obj = this._layers[i];
4979 overlaysPresent = overlaysPresent || obj.overlay;
4980 baseLayersPresent = baseLayersPresent || !obj.overlay;
4981 baseLayersCount += !obj.overlay ? 1 : 0;
4984 // Hide base layers section if there's only one layer.
4985 if (this.options.hideSingleBase) {
4986 baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
4987 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
4990 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
4995 _onLayerChange: function (e) {
4996 if (!this._handlingClick) {
5000 var obj = this._getLayer(stamp(e.target));
5003 // @section Layer events
5004 // @event baselayerchange: LayersControlEvent
5005 // Fired when the base layer is changed through the [layer control](#control-layers).
5006 // @event overlayadd: LayersControlEvent
5007 // Fired when an overlay is selected through the [layer control](#control-layers).
5008 // @event overlayremove: LayersControlEvent
5009 // Fired when an overlay is deselected through the [layer control](#control-layers).
5010 // @namespace Control.Layers
5011 var type = obj.overlay ?
5012 (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
5013 (e.type === 'add' ? 'baselayerchange' : null);
5016 this._map.fire(type, obj);
5020 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
5021 _createRadioElement: function (name, checked) {
5023 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
5024 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
5026 var radioFragment = document.createElement('div');
5027 radioFragment.innerHTML = radioHtml;
5029 return radioFragment.firstChild;
5032 _addItem: function (obj) {
5033 var label = document.createElement('label'),
5034 checked = this._map.hasLayer(obj.layer),
5038 input = document.createElement('input');
5039 input.type = 'checkbox';
5040 input.className = 'leaflet-control-layers-selector';
5041 input.defaultChecked = checked;
5043 input = this._createRadioElement('leaflet-base-layers', checked);
5046 this._layerControlInputs.push(input);
5047 input.layerId = stamp(obj.layer);
5049 on(input, 'click', this._onInputClick, this);
5051 var name = document.createElement('span');
5052 name.innerHTML = ' ' + obj.name;
5054 // Helps from preventing layer control flicker when checkboxes are disabled
5055 // https://github.com/Leaflet/Leaflet/issues/2771
5056 var holder = document.createElement('div');
5058 label.appendChild(holder);
5059 holder.appendChild(input);
5060 holder.appendChild(name);
5062 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
5063 container.appendChild(label);
5065 this._checkDisabledLayers();
5069 _onInputClick: function () {
5070 var inputs = this._layerControlInputs,
5071 input, layer, hasLayer;
5072 var addedLayers = [],
5075 this._handlingClick = true;
5077 for (var i = inputs.length - 1; i >= 0; i--) {
5079 layer = this._getLayer(input.layerId).layer;
5080 hasLayer = this._map.hasLayer(layer);
5082 if (input.checked && !hasLayer) {
5083 addedLayers.push(layer);
5085 } else if (!input.checked && hasLayer) {
5086 removedLayers.push(layer);
5090 // Bugfix issue 2318: Should remove all old layers before readding new ones
5091 for (i = 0; i < removedLayers.length; i++) {
5092 this._map.removeLayer(removedLayers[i]);
5094 for (i = 0; i < addedLayers.length; i++) {
5095 this._map.addLayer(addedLayers[i]);
5098 this._handlingClick = false;
5100 this._refocusOnMap();
5103 _checkDisabledLayers: function () {
5104 var inputs = this._layerControlInputs,
5107 zoom = this._map.getZoom();
5109 for (var i = inputs.length - 1; i >= 0; i--) {
5111 layer = this._getLayer(input.layerId).layer;
5112 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
5113 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
5118 _expandIfNotCollapsed: function () {
5119 if (this._map && !this.options.collapsed) {
5125 _expand: function () {
5126 // Backward compatibility, remove me in 1.1.
5127 return this.expand();
5130 _collapse: function () {
5131 // Backward compatibility, remove me in 1.1.
5132 return this.collapse();
5138 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
5139 // 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.
5140 var layers = function (baseLayers, overlays, options) {
5141 return new Layers(baseLayers, overlays, options);
5145 * @class Control.Zoom
5146 * @aka L.Control.Zoom
5149 * 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`.
5152 var Zoom = Control.extend({
5154 // @aka Control.Zoom options
5156 position: 'topleft',
5158 // @option zoomInText: String = '+'
5159 // The text set on the 'zoom in' button.
5162 // @option zoomInTitle: String = 'Zoom in'
5163 // The title set on the 'zoom in' button.
5164 zoomInTitle: 'Zoom in',
5166 // @option zoomOutText: String = '−'
5167 // The text set on the 'zoom out' button.
5168 zoomOutText: '−',
5170 // @option zoomOutTitle: String = 'Zoom out'
5171 // The title set on the 'zoom out' button.
5172 zoomOutTitle: 'Zoom out'
5175 onAdd: function (map) {
5176 var zoomName = 'leaflet-control-zoom',
5177 container = create$1('div', zoomName + ' leaflet-bar'),
5178 options = this.options;
5180 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle,
5181 zoomName + '-in', container, this._zoomIn);
5182 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
5183 zoomName + '-out', container, this._zoomOut);
5185 this._updateDisabled();
5186 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
5191 onRemove: function (map) {
5192 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
5195 disable: function () {
5196 this._disabled = true;
5197 this._updateDisabled();
5201 enable: function () {
5202 this._disabled = false;
5203 this._updateDisabled();
5207 _zoomIn: function (e) {
5208 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
5209 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5213 _zoomOut: function (e) {
5214 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
5215 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5219 _createButton: function (html, title, className, container, fn) {
5220 var link = create$1('a', className, container);
5221 link.innerHTML = html;
5226 * Will force screen readers like VoiceOver to read this as "Zoom in - button"
5228 link.setAttribute('role', 'button');
5229 link.setAttribute('aria-label', title);
5231 disableClickPropagation(link);
5232 on(link, 'click', stop);
5233 on(link, 'click', fn, this);
5234 on(link, 'click', this._refocusOnMap, this);
5239 _updateDisabled: function () {
5240 var map = this._map,
5241 className = 'leaflet-disabled';
5243 removeClass(this._zoomInButton, className);
5244 removeClass(this._zoomOutButton, className);
5246 if (this._disabled || map._zoom === map.getMinZoom()) {
5247 addClass(this._zoomOutButton, className);
5249 if (this._disabled || map._zoom === map.getMaxZoom()) {
5250 addClass(this._zoomInButton, className);
5256 // @section Control options
5257 // @option zoomControl: Boolean = true
5258 // Whether a [zoom control](#control-zoom) is added to the map by default.
5263 Map.addInitHook(function () {
5264 if (this.options.zoomControl) {
5265 this.zoomControl = new Zoom();
5266 this.addControl(this.zoomControl);
5270 // @namespace Control.Zoom
5271 // @factory L.control.zoom(options: Control.Zoom options)
5272 // Creates a zoom control
5273 var zoom = function (options) {
5274 return new Zoom(options);
5278 * @class Control.Scale
5279 * @aka L.Control.Scale
5282 * 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`.
5287 * L.control.scale().addTo(map);
5291 var Scale = Control.extend({
5293 // @aka Control.Scale options
5295 position: 'bottomleft',
5297 // @option maxWidth: Number = 100
5298 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
5301 // @option metric: Boolean = True
5302 // Whether to show the metric scale line (m/km).
5305 // @option imperial: Boolean = True
5306 // Whether to show the imperial scale line (mi/ft).
5309 // @option updateWhenIdle: Boolean = false
5310 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
5313 onAdd: function (map) {
5314 var className = 'leaflet-control-scale',
5315 container = create$1('div', className),
5316 options = this.options;
5318 this._addScales(options, className + '-line', container);
5320 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5321 map.whenReady(this._update, this);
5326 onRemove: function (map) {
5327 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5330 _addScales: function (options, className, container) {
5331 if (options.metric) {
5332 this._mScale = create$1('div', className, container);
5334 if (options.imperial) {
5335 this._iScale = create$1('div', className, container);
5339 _update: function () {
5340 var map = this._map,
5341 y = map.getSize().y / 2;
5343 var maxMeters = map.distance(
5344 map.containerPointToLatLng([0, y]),
5345 map.containerPointToLatLng([this.options.maxWidth, y]));
5347 this._updateScales(maxMeters);
5350 _updateScales: function (maxMeters) {
5351 if (this.options.metric && maxMeters) {
5352 this._updateMetric(maxMeters);
5354 if (this.options.imperial && maxMeters) {
5355 this._updateImperial(maxMeters);
5359 _updateMetric: function (maxMeters) {
5360 var meters = this._getRoundNum(maxMeters),
5361 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
5363 this._updateScale(this._mScale, label, meters / maxMeters);
5366 _updateImperial: function (maxMeters) {
5367 var maxFeet = maxMeters * 3.2808399,
5368 maxMiles, miles, feet;
5370 if (maxFeet > 5280) {
5371 maxMiles = maxFeet / 5280;
5372 miles = this._getRoundNum(maxMiles);
5373 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
5376 feet = this._getRoundNum(maxFeet);
5377 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
5381 _updateScale: function (scale, text, ratio) {
5382 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
5383 scale.innerHTML = text;
5386 _getRoundNum: function (num) {
5387 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5400 // @factory L.control.scale(options?: Control.Scale options)
5401 // Creates an scale control with the given options.
5402 var scale = function (options) {
5403 return new Scale(options);
5407 * @class Control.Attribution
5408 * @aka L.Control.Attribution
5411 * 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.
5414 var Attribution = Control.extend({
5416 // @aka Control.Attribution options
5418 position: 'bottomright',
5420 // @option prefix: String = 'Leaflet'
5421 // The HTML text shown before the attributions. Pass `false` to disable.
5422 prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
5425 initialize: function (options) {
5426 setOptions(this, options);
5428 this._attributions = {};
5431 onAdd: function (map) {
5432 map.attributionControl = this;
5433 this._container = create$1('div', 'leaflet-control-attribution');
5434 disableClickPropagation(this._container);
5436 // TODO ugly, refactor
5437 for (var i in map._layers) {
5438 if (map._layers[i].getAttribution) {
5439 this.addAttribution(map._layers[i].getAttribution());
5445 return this._container;
5448 // @method setPrefix(prefix: String): this
5449 // Sets the text before the attributions.
5450 setPrefix: function (prefix) {
5451 this.options.prefix = prefix;
5456 // @method addAttribution(text: String): this
5457 // Adds an attribution text (e.g. `'Vector data © Mapbox'`).
5458 addAttribution: function (text) {
5459 if (!text) { return this; }
5461 if (!this._attributions[text]) {
5462 this._attributions[text] = 0;
5464 this._attributions[text]++;
5471 // @method removeAttribution(text: String): this
5472 // Removes an attribution text.
5473 removeAttribution: function (text) {
5474 if (!text) { return this; }
5476 if (this._attributions[text]) {
5477 this._attributions[text]--;
5484 _update: function () {
5485 if (!this._map) { return; }
5489 for (var i in this._attributions) {
5490 if (this._attributions[i]) {
5495 var prefixAndAttribs = [];
5497 if (this.options.prefix) {
5498 prefixAndAttribs.push(this.options.prefix);
5500 if (attribs.length) {
5501 prefixAndAttribs.push(attribs.join(', '));
5504 this._container.innerHTML = prefixAndAttribs.join(' | ');
5509 // @section Control options
5510 // @option attributionControl: Boolean = true
5511 // Whether a [attribution control](#control-attribution) is added to the map by default.
5513 attributionControl: true
5516 Map.addInitHook(function () {
5517 if (this.options.attributionControl) {
5518 new Attribution().addTo(this);
5522 // @namespace Control.Attribution
5523 // @factory L.control.attribution(options: Control.Attribution options)
5524 // Creates an attribution control.
5525 var attribution = function (options) {
5526 return new Attribution(options);
5529 Control.Layers = Layers;
5530 Control.Zoom = Zoom;
5531 Control.Scale = Scale;
5532 Control.Attribution = Attribution;
5534 control.layers = layers;
5535 control.zoom = zoom;
5536 control.scale = scale;
5537 control.attribution = attribution;
5540 L.Handler is a base class for handler classes that are used internally to inject
5541 interaction features like dragging to classes like Map and Marker.
5546 // Abstract class for map interaction handlers
5548 var Handler = Class.extend({
5549 initialize: function (map) {
5553 // @method enable(): this
5554 // Enables the handler
5555 enable: function () {
5556 if (this._enabled) { return this; }
5558 this._enabled = true;
5563 // @method disable(): this
5564 // Disables the handler
5565 disable: function () {
5566 if (!this._enabled) { return this; }
5568 this._enabled = false;
5573 // @method enabled(): Boolean
5574 // Returns `true` if the handler is enabled
5575 enabled: function () {
5576 return !!this._enabled;
5579 // @section Extension methods
5580 // Classes inheriting from `Handler` must implement the two following methods:
5581 // @method addHooks()
5582 // Called when the handler is enabled, should add event hooks.
5583 // @method removeHooks()
5584 // Called when the handler is disabled, should remove the event hooks added previously.
5587 var Mixin = {Events: Events};
5594 * A class for making DOM elements draggable (including touch support).
5595 * Used internally for map and marker dragging. Only works for elements
5596 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
5600 * var draggable = new L.Draggable(elementToDrag);
5601 * draggable.enable();
5605 var _dragging = false;
5606 var START = touch ? 'touchstart mousedown' : 'mousedown';
5608 mousedown: 'mouseup',
5609 touchstart: 'touchend',
5610 pointerdown: 'touchend',
5611 MSPointerDown: 'touchend'
5614 mousedown: 'mousemove',
5615 touchstart: 'touchmove',
5616 pointerdown: 'touchmove',
5617 MSPointerDown: 'touchmove'
5621 var Draggable = Evented.extend({
5625 // @aka Draggable options
5626 // @option clickTolerance: Number = 3
5627 // The max number of pixels a user can shift the mouse pointer during a click
5628 // for it to be considered a valid click (as opposed to a mouse drag).
5632 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
5633 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
5634 initialize: function (element, dragStartTarget, preventOutline$$1, options) {
5635 setOptions(this, options);
5637 this._element = element;
5638 this._dragStartTarget = dragStartTarget || element;
5639 this._preventOutline = preventOutline$$1;
5643 // Enables the dragging ability
5644 enable: function () {
5645 if (this._enabled) { return; }
5647 on(this._dragStartTarget, START, this._onDown, this);
5649 this._enabled = true;
5652 // @method disable()
5653 // Disables the dragging ability
5654 disable: function () {
5655 if (!this._enabled) { return; }
5657 // If we're currently dragging this draggable,
5658 // disabling it counts as first ending the drag.
5659 if (L.Draggable._dragging === this) {
5663 off(this._dragStartTarget, START, this._onDown, this);
5665 this._enabled = false;
5666 this._moved = false;
5669 _onDown: function (e) {
5670 // Ignore simulated events, since we handle both touch and
5671 // mouse explicitly; otherwise we risk getting duplicates of
5672 // touch events, see #4315.
5673 // Also ignore the event if disabled; this happens in IE11
5674 // under some circumstances, see #3666.
5675 if (e._simulated || !this._enabled) { return; }
5677 this._moved = false;
5679 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
5681 if (_dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
5682 _dragging = this; // Prevent dragging multiple objects at once.
5684 if (this._preventOutline) {
5685 preventOutline(this._element);
5689 disableTextSelection();
5691 if (this._moving) { return; }
5693 // @event down: Event
5694 // Fired when a drag is about to start.
5697 var first = e.touches ? e.touches[0] : e;
5699 this._startPoint = new Point(first.clientX, first.clientY);
5701 on(document, MOVE[e.type], this._onMove, this);
5702 on(document, END[e.type], this._onUp, this);
5705 _onMove: function (e) {
5706 // Ignore simulated events, since we handle both touch and
5707 // mouse explicitly; otherwise we risk getting duplicates of
5708 // touch events, see #4315.
5709 // Also ignore the event if disabled; this happens in IE11
5710 // under some circumstances, see #3666.
5711 if (e._simulated || !this._enabled) { return; }
5713 if (e.touches && e.touches.length > 1) {
5718 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
5719 newPoint = new Point(first.clientX, first.clientY),
5720 offset = newPoint.subtract(this._startPoint);
5722 if (!offset.x && !offset.y) { return; }
5723 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
5728 // @event dragstart: Event
5729 // Fired when a drag starts
5730 this.fire('dragstart');
5733 this._startPos = getPosition(this._element).subtract(offset);
5735 addClass(document.body, 'leaflet-dragging');
5737 this._lastTarget = e.target || e.srcElement;
5738 // IE and Edge do not give the <use> element, so fetch it
5740 if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
5741 this._lastTarget = this._lastTarget.correspondingUseElement;
5743 addClass(this._lastTarget, 'leaflet-drag-target');
5746 this._newPos = this._startPos.add(offset);
5747 this._moving = true;
5749 cancelAnimFrame(this._animRequest);
5750 this._lastEvent = e;
5751 this._animRequest = requestAnimFrame(this._updatePosition, this, true);
5754 _updatePosition: function () {
5755 var e = {originalEvent: this._lastEvent};
5757 // @event predrag: Event
5758 // Fired continuously during dragging *before* each corresponding
5759 // update of the element's position.
5760 this.fire('predrag', e);
5761 setPosition(this._element, this._newPos);
5763 // @event drag: Event
5764 // Fired continuously during dragging.
5765 this.fire('drag', e);
5768 _onUp: function (e) {
5769 // Ignore simulated events, since we handle both touch and
5770 // mouse explicitly; otherwise we risk getting duplicates of
5771 // touch events, see #4315.
5772 // Also ignore the event if disabled; this happens in IE11
5773 // under some circumstances, see #3666.
5774 if (e._simulated || !this._enabled) { return; }
5778 finishDrag: function () {
5779 removeClass(document.body, 'leaflet-dragging');
5781 if (this._lastTarget) {
5782 removeClass(this._lastTarget, 'leaflet-drag-target');
5783 this._lastTarget = null;
5786 for (var i in MOVE) {
5787 off(document, MOVE[i], this._onMove, this);
5788 off(document, END[i], this._onUp, this);
5792 enableTextSelection();
5794 if (this._moved && this._moving) {
5795 // ensure drag is not fired after dragend
5796 cancelAnimFrame(this._animRequest);
5798 // @event dragend: DragEndEvent
5799 // Fired when the drag ends.
5800 this.fire('dragend', {
5801 distance: this._newPos.distanceTo(this._startPos)
5805 this._moving = false;
5812 * @namespace LineUtil
5814 * Various utility functions for polyine points processing, used by Leaflet internally to make polylines lightning-fast.
5817 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
5818 // Improves rendering performance dramatically by lessening the number of points to draw.
5820 // @function simplify(points: Point[], tolerance: Number): Point[]
5821 // Dramatically reduces the number of points in a polyline while retaining
5822 // its shape and returns a new array of simplified points, using the
5823 // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
5824 // Used for a huge performance boost when processing/displaying Leaflet polylines for
5825 // each zoom level and also reducing visual noise. tolerance affects the amount of
5826 // simplification (lesser value means higher quality but slower and with more points).
5827 // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
5828 function simplify(points, tolerance) {
5829 if (!tolerance || !points.length) {
5830 return points.slice();
5833 var sqTolerance = tolerance * tolerance;
5835 // stage 1: vertex reduction
5836 points = _reducePoints(points, sqTolerance);
5838 // stage 2: Douglas-Peucker simplification
5839 points = _simplifyDP(points, sqTolerance);
5844 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
5845 // Returns the distance between point `p` and segment `p1` to `p2`.
5846 function pointToSegmentDistance(p, p1, p2) {
5847 return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
5850 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
5851 // Returns the closest point from a point `p` on a segment `p1` to `p2`.
5852 function closestPointOnSegment(p, p1, p2) {
5853 return _sqClosestPointOnSegment(p, p1, p2);
5856 // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
5857 function _simplifyDP(points, sqTolerance) {
5859 var len = points.length,
5860 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
5861 markers = new ArrayConstructor(len);
5863 markers[0] = markers[len - 1] = 1;
5865 _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
5870 for (i = 0; i < len; i++) {
5872 newPoints.push(points[i]);
5879 function _simplifyDPStep(points, markers, sqTolerance, first, last) {
5884 for (i = first + 1; i <= last - 1; i++) {
5885 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
5887 if (sqDist > maxSqDist) {
5893 if (maxSqDist > sqTolerance) {
5896 _simplifyDPStep(points, markers, sqTolerance, first, index);
5897 _simplifyDPStep(points, markers, sqTolerance, index, last);
5901 // reduce points that are too close to each other to a single point
5902 function _reducePoints(points, sqTolerance) {
5903 var reducedPoints = [points[0]];
5905 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
5906 if (_sqDist(points[i], points[prev]) > sqTolerance) {
5907 reducedPoints.push(points[i]);
5911 if (prev < len - 1) {
5912 reducedPoints.push(points[len - 1]);
5914 return reducedPoints;
5919 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
5920 // Clips the segment a to b by rectangular bounds with the
5921 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
5922 // (modifying the segment points directly!). Used by Leaflet to only show polyline
5923 // points that are on the screen or near, increasing performance.
5924 function clipSegment(a, b, bounds, useLastCode, round) {
5925 var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
5926 codeB = _getBitCode(b, bounds),
5928 codeOut, p, newCode;
5930 // save 2nd code to avoid calculating it on the next segment
5934 // if a,b is inside the clip window (trivial accept)
5935 if (!(codeA | codeB)) {
5939 // if a,b is outside the clip window (trivial reject)
5940 if (codeA & codeB) {
5945 codeOut = codeA || codeB;
5946 p = _getEdgeIntersection(a, b, codeOut, bounds, round);
5947 newCode = _getBitCode(p, bounds);
5949 if (codeOut === codeA) {
5959 function _getEdgeIntersection(a, b, code, bounds, round) {
5966 if (code & 8) { // top
5967 x = a.x + dx * (max.y - a.y) / dy;
5970 } else if (code & 4) { // bottom
5971 x = a.x + dx * (min.y - a.y) / dy;
5974 } else if (code & 2) { // right
5976 y = a.y + dy * (max.x - a.x) / dx;
5978 } else if (code & 1) { // left
5980 y = a.y + dy * (min.x - a.x) / dx;
5983 return new Point(x, y, round);
5986 function _getBitCode(p, bounds) {
5989 if (p.x < bounds.min.x) { // left
5991 } else if (p.x > bounds.max.x) { // right
5995 if (p.y < bounds.min.y) { // bottom
5997 } else if (p.y > bounds.max.y) { // top
6004 // square distance (to avoid unnecessary Math.sqrt calls)
6005 function _sqDist(p1, p2) {
6006 var dx = p2.x - p1.x,
6008 return dx * dx + dy * dy;
6011 // return closest point on segment or distance to that point
6012 function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
6017 dot = dx * dx + dy * dy,
6021 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
6035 return sqDist ? dx * dx + dy * dy : new Point(x, y);
6039 function _flat(latlngs) {
6040 // true if it's a flat array of latlngs; false if nested
6041 return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
6045 var LineUtil = (Object.freeze || Object)({
6047 pointToSegmentDistance: pointToSegmentDistance,
6048 closestPointOnSegment: closestPointOnSegment,
6049 clipSegment: clipSegment,
6050 _getEdgeIntersection: _getEdgeIntersection,
6051 _getBitCode: _getBitCode,
6052 _sqClosestPointOnSegment: _sqClosestPointOnSegment,
6057 * @namespace PolyUtil
6058 * Various utility functions for polygon geometries.
6061 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
6062 * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgeman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)).
6063 * Used by Leaflet to only show polygon points that are on the screen or near, increasing
6064 * performance. Note that polygon points needs different algorithm for clipping
6065 * than polyline, so there's a seperate method for it.
6067 function clipPolygon(points, bounds, round) {
6069 edges = [1, 4, 2, 8],
6074 for (i = 0, len = points.length; i < len; i++) {
6075 points[i]._code = _getBitCode(points[i], bounds);
6078 // for each edge (left, bottom, right, top)
6079 for (k = 0; k < 4; k++) {
6083 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
6087 // if a is inside the clip window
6088 if (!(a._code & edge)) {
6089 // if b is outside the clip window (a->b goes out of screen)
6090 if (b._code & edge) {
6091 p = _getEdgeIntersection(b, a, edge, bounds, round);
6092 p._code = _getBitCode(p, bounds);
6093 clippedPoints.push(p);
6095 clippedPoints.push(a);
6097 // else if b is inside the clip window (a->b enters the screen)
6098 } else if (!(b._code & edge)) {
6099 p = _getEdgeIntersection(b, a, edge, bounds, round);
6100 p._code = _getBitCode(p, bounds);
6101 clippedPoints.push(p);
6104 points = clippedPoints;
6111 var PolyUtil = (Object.freeze || Object)({
6112 clipPolygon: clipPolygon
6116 * @namespace Projection
6118 * Leaflet comes with a set of already defined Projections out of the box:
6120 * @projection L.Projection.LonLat
6122 * Equirectangular, or Plate Carree projection — the most simple projection,
6123 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
6124 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
6125 * `EPSG:4326` and `Simple` CRS.
6129 project: function (latlng) {
6130 return new Point(latlng.lng, latlng.lat);
6133 unproject: function (point) {
6134 return new LatLng(point.y, point.x);
6137 bounds: new Bounds([-180, -90], [180, 90])
6141 * @namespace Projection
6142 * @projection L.Projection.Mercator
6144 * 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.
6149 R_MINOR: 6356752.314245179,
6151 bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
6153 project: function (latlng) {
6154 var d = Math.PI / 180,
6157 tmp = this.R_MINOR / r,
6158 e = Math.sqrt(1 - tmp * tmp),
6159 con = e * Math.sin(y);
6161 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
6162 y = -r * Math.log(Math.max(ts, 1E-10));
6164 return new Point(latlng.lng * d * r, y);
6167 unproject: function (point) {
6168 var d = 180 / Math.PI,
6170 tmp = this.R_MINOR / r,
6171 e = Math.sqrt(1 - tmp * tmp),
6172 ts = Math.exp(-point.y / r),
6173 phi = Math.PI / 2 - 2 * Math.atan(ts);
6175 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
6176 con = e * Math.sin(phi);
6177 con = Math.pow((1 - con) / (1 + con), e / 2);
6178 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
6182 return new LatLng(phi * d, point.x * d / r);
6189 * An object with methods for projecting geographical coordinates of the world onto
6190 * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection).
6192 * @property bounds: Bounds
6193 * The bounds (specified in CRS units) where the projection is valid
6195 * @method project(latlng: LatLng): Point
6196 * Projects geographical coordinates into a 2D point.
6197 * Only accepts actual `L.LatLng` instances, not arrays.
6199 * @method unproject(point: Point): LatLng
6200 * The inverse of `project`. Projects a 2D point into a geographical location.
6201 * Only accepts actual `L.Point` instances, not arrays.
6208 var index = (Object.freeze || Object)({
6211 SphericalMercator: SphericalMercator
6216 * @crs L.CRS.EPSG3395
6218 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
6220 var EPSG3395 = extend({}, Earth, {
6222 projection: Mercator,
6224 transformation: (function () {
6225 var scale = 0.5 / (Math.PI * Mercator.R);
6226 return toTransformation(scale, 0.5, -scale, 0.5);
6232 * @crs L.CRS.EPSG4326
6234 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
6236 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
6237 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer`
6238 * with this CRS, ensure that there are two 256x256 pixel tiles covering the
6239 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
6240 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
6243 var EPSG4326 = extend({}, Earth, {
6246 transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
6253 * A simple CRS that maps longitude and latitude into `x` and `y` directly.
6254 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
6255 * axis should still be inverted (going from bottom to top). `distance()` returns
6256 * simple euclidean distance.
6259 var Simple = extend({}, CRS, {
6261 transformation: toTransformation(1, 0, -1, 0),
6263 scale: function (zoom) {
6264 return Math.pow(2, zoom);
6267 zoom: function (scale) {
6268 return Math.log(scale) / Math.LN2;
6271 distance: function (latlng1, latlng2) {
6272 var dx = latlng2.lng - latlng1.lng,
6273 dy = latlng2.lat - latlng1.lat;
6275 return Math.sqrt(dx * dx + dy * dy);
6282 CRS.EPSG3395 = EPSG3395;
6283 CRS.EPSG3857 = EPSG3857;
6284 CRS.EPSG900913 = EPSG900913;
6285 CRS.EPSG4326 = EPSG4326;
6286 CRS.Simple = Simple;
6294 * A set of methods from the Layer base class that all Leaflet layers use.
6295 * Inherits all methods, options and events from `L.Evented`.
6300 * var layer = L.Marker(latlng).addTo(map);
6306 * Fired after the layer is added to a map
6308 * @event remove: Event
6309 * Fired after the layer is removed from a map
6313 var Layer = Evented.extend({
6315 // Classes extending `L.Layer` will inherit the following options:
6317 // @option pane: String = 'overlayPane'
6318 // 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.
6319 pane: 'overlayPane',
6321 // @option attribution: String = null
6322 // String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox".
6325 bubblingMouseEvents: true
6329 * Classes extending `L.Layer` will inherit the following methods:
6331 * @method addTo(map: Map): this
6332 * Adds the layer to the given map
6334 addTo: function (map) {
6339 // @method remove: this
6340 // Removes the layer from the map it is currently active on.
6341 remove: function () {
6342 return this.removeFrom(this._map || this._mapToAdd);
6345 // @method removeFrom(map: Map): this
6346 // Removes the layer from the given map
6347 removeFrom: function (obj) {
6349 obj.removeLayer(this);
6354 // @method getPane(name? : String): HTMLElement
6355 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
6356 getPane: function (name) {
6357 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
6360 addInteractiveTarget: function (targetEl) {
6361 this._map._targets[stamp(targetEl)] = this;
6365 removeInteractiveTarget: function (targetEl) {
6366 delete this._map._targets[stamp(targetEl)];
6370 // @method getAttribution: String
6371 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
6372 getAttribution: function () {
6373 return this.options.attribution;
6376 _layerAdd: function (e) {
6379 // check in case layer gets added and then removed before the map is ready
6380 if (!map.hasLayer(this)) { return; }
6383 this._zoomAnimated = map._zoomAnimated;
6385 if (this.getEvents) {
6386 var events = this.getEvents();
6387 map.on(events, this);
6388 this.once('remove', function () {
6389 map.off(events, this);
6395 if (this.getAttribution && map.attributionControl) {
6396 map.attributionControl.addAttribution(this.getAttribution());
6400 map.fire('layeradd', {layer: this});
6404 /* @section Extension methods
6407 * Every layer should extend from `L.Layer` and (re-)implement the following methods.
6409 * @method onAdd(map: Map): this
6410 * 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).
6412 * @method onRemove(map: Map): this
6413 * 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).
6415 * @method getEvents(): Object
6416 * 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.
6418 * @method getAttribution(): String
6419 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
6421 * @method beforeAdd(map: Map): this
6422 * 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.
6427 * @section Layer events
6429 * @event layeradd: LayerEvent
6430 * Fired when a new layer is added to the map.
6432 * @event layerremove: LayerEvent
6433 * Fired when some layer is removed from the map
6435 * @section Methods for Layers and Controls
6438 // @method addLayer(layer: Layer): this
6439 // Adds the given layer to the map
6440 addLayer: function (layer) {
6441 var id = stamp(layer);
6442 if (this._layers[id]) { return this; }
6443 this._layers[id] = layer;
6445 layer._mapToAdd = this;
6447 if (layer.beforeAdd) {
6448 layer.beforeAdd(this);
6451 this.whenReady(layer._layerAdd, layer);
6456 // @method removeLayer(layer: Layer): this
6457 // Removes the given layer from the map.
6458 removeLayer: function (layer) {
6459 var id = stamp(layer);
6461 if (!this._layers[id]) { return this; }
6464 layer.onRemove(this);
6467 if (layer.getAttribution && this.attributionControl) {
6468 this.attributionControl.removeAttribution(layer.getAttribution());
6471 delete this._layers[id];
6474 this.fire('layerremove', {layer: layer});
6475 layer.fire('remove');
6478 layer._map = layer._mapToAdd = null;
6483 // @method hasLayer(layer: Layer): Boolean
6484 // Returns `true` if the given layer is currently added to the map
6485 hasLayer: function (layer) {
6486 return !!layer && (stamp(layer) in this._layers);
6489 /* @method eachLayer(fn: Function, context?: Object): this
6490 * Iterates over the layers of the map, optionally specifying context of the iterator function.
6492 * map.eachLayer(function(layer){
6493 * layer.bindPopup('Hello');
6497 eachLayer: function (method, context) {
6498 for (var i in this._layers) {
6499 method.call(context, this._layers[i]);
6504 _addLayers: function (layers) {
6505 layers = layers ? (isArray(layers) ? layers : [layers]) : [];
6507 for (var i = 0, len = layers.length; i < len; i++) {
6508 this.addLayer(layers[i]);
6512 _addZoomLimit: function (layer) {
6513 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
6514 this._zoomBoundLayers[stamp(layer)] = layer;
6515 this._updateZoomLevels();
6519 _removeZoomLimit: function (layer) {
6520 var id = stamp(layer);
6522 if (this._zoomBoundLayers[id]) {
6523 delete this._zoomBoundLayers[id];
6524 this._updateZoomLevels();
6528 _updateZoomLevels: function () {
6529 var minZoom = Infinity,
6530 maxZoom = -Infinity,
6531 oldZoomSpan = this._getZoomSpan();
6533 for (var i in this._zoomBoundLayers) {
6534 var options = this._zoomBoundLayers[i].options;
6536 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
6537 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
6540 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
6541 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
6543 // @section Map state change events
6544 // @event zoomlevelschange: Event
6545 // Fired when the number of zoomlevels on the map is changed due
6546 // to adding or removing a layer.
6547 if (oldZoomSpan !== this._getZoomSpan()) {
6548 this.fire('zoomlevelschange');
6551 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
6552 this.setZoom(this._layersMaxZoom);
6554 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
6555 this.setZoom(this._layersMinZoom);
6565 * Used to group several layers and handle them as one. If you add it to the map,
6566 * any layers added or removed from the group will be added/removed on the map as
6567 * well. Extends `Layer`.
6572 * L.layerGroup([marker1, marker2])
6573 * .addLayer(polyline)
6578 var LayerGroup = Layer.extend({
6580 initialize: function (layers) {
6586 for (i = 0, len = layers.length; i < len; i++) {
6587 this.addLayer(layers[i]);
6592 // @method addLayer(layer: Layer): this
6593 // Adds the given layer to the group.
6594 addLayer: function (layer) {
6595 var id = this.getLayerId(layer);
6597 this._layers[id] = layer;
6600 this._map.addLayer(layer);
6606 // @method removeLayer(layer: Layer): this
6607 // Removes the given layer from the group.
6609 // @method removeLayer(id: Number): this
6610 // Removes the layer with the given internal ID from the group.
6611 removeLayer: function (layer) {
6612 var id = layer in this._layers ? layer : this.getLayerId(layer);
6614 if (this._map && this._layers[id]) {
6615 this._map.removeLayer(this._layers[id]);
6618 delete this._layers[id];
6623 // @method hasLayer(layer: Layer): Boolean
6624 // Returns `true` if the given layer is currently added to the group.
6626 // @method hasLayer(id: Number): Boolean
6627 // Returns `true` if the given internal ID is currently added to the group.
6628 hasLayer: function (layer) {
6629 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
6632 // @method clearLayers(): this
6633 // Removes all the layers from the group.
6634 clearLayers: function () {
6635 for (var i in this._layers) {
6636 this.removeLayer(this._layers[i]);
6641 // @method invoke(methodName: String, …): this
6642 // Calls `methodName` on every layer contained in this group, passing any
6643 // additional parameters. Has no effect if the layers contained do not
6644 // implement `methodName`.
6645 invoke: function (methodName) {
6646 var args = Array.prototype.slice.call(arguments, 1),
6649 for (i in this._layers) {
6650 layer = this._layers[i];
6652 if (layer[methodName]) {
6653 layer[methodName].apply(layer, args);
6660 onAdd: function (map) {
6661 for (var i in this._layers) {
6662 map.addLayer(this._layers[i]);
6666 onRemove: function (map) {
6667 for (var i in this._layers) {
6668 map.removeLayer(this._layers[i]);
6672 // @method eachLayer(fn: Function, context?: Object): this
6673 // Iterates over the layers of the group, optionally specifying context of the iterator function.
6675 // group.eachLayer(function (layer) {
6676 // layer.bindPopup('Hello');
6679 eachLayer: function (method, context) {
6680 for (var i in this._layers) {
6681 method.call(context, this._layers[i]);
6686 // @method getLayer(id: Number): Layer
6687 // Returns the layer with the given internal ID.
6688 getLayer: function (id) {
6689 return this._layers[id];
6692 // @method getLayers(): Layer[]
6693 // Returns an array of all the layers added to the group.
6694 getLayers: function () {
6697 for (var i in this._layers) {
6698 layers.push(this._layers[i]);
6703 // @method setZIndex(zIndex: Number): this
6704 // Calls `setZIndex` on every layer contained in this group, passing the z-index.
6705 setZIndex: function (zIndex) {
6706 return this.invoke('setZIndex', zIndex);
6709 // @method getLayerId(layer: Layer): Number
6710 // Returns the internal ID for a layer
6711 getLayerId: function (layer) {
6712 return stamp(layer);
6717 // @factory L.layerGroup(layers: Layer[])
6718 // Create a layer group, optionally given an initial set of layers.
6719 var layerGroup = function (layers) {
6720 return new LayerGroup(layers);
6724 * @class FeatureGroup
6725 * @aka L.FeatureGroup
6726 * @inherits LayerGroup
6728 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
6729 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
6730 * * Events are propagated to the `FeatureGroup`, so if the group has an event
6731 * handler, it will handle events from any of the layers. This includes mouse events
6732 * and custom events.
6733 * * Has `layeradd` and `layerremove` events
6738 * L.featureGroup([marker1, marker2, polyline])
6739 * .bindPopup('Hello world!')
6740 * .on('click', function() { alert('Clicked on a member of the group!'); })
6745 var FeatureGroup = LayerGroup.extend({
6747 addLayer: function (layer) {
6748 if (this.hasLayer(layer)) {
6752 layer.addEventParent(this);
6754 LayerGroup.prototype.addLayer.call(this, layer);
6756 // @event layeradd: LayerEvent
6757 // Fired when a layer is added to this `FeatureGroup`
6758 return this.fire('layeradd', {layer: layer});
6761 removeLayer: function (layer) {
6762 if (!this.hasLayer(layer)) {
6765 if (layer in this._layers) {
6766 layer = this._layers[layer];
6769 layer.removeEventParent(this);
6771 LayerGroup.prototype.removeLayer.call(this, layer);
6773 // @event layerremove: LayerEvent
6774 // Fired when a layer is removed from this `FeatureGroup`
6775 return this.fire('layerremove', {layer: layer});
6778 // @method setStyle(style: Path options): this
6779 // Sets the given path options to each layer of the group that has a `setStyle` method.
6780 setStyle: function (style) {
6781 return this.invoke('setStyle', style);
6784 // @method bringToFront(): this
6785 // Brings the layer group to the top of all other layers
6786 bringToFront: function () {
6787 return this.invoke('bringToFront');
6790 // @method bringToBack(): this
6791 // Brings the layer group to the top of all other layers
6792 bringToBack: function () {
6793 return this.invoke('bringToBack');
6796 // @method getBounds(): LatLngBounds
6797 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
6798 getBounds: function () {
6799 var bounds = new LatLngBounds();
6801 for (var id in this._layers) {
6802 var layer = this._layers[id];
6803 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
6809 // @factory L.featureGroup(layers: Layer[])
6810 // Create a feature group, optionally given an initial set of layers.
6811 var featureGroup = function (layers) {
6812 return new FeatureGroup(layers);
6819 * Represents an icon to provide when creating a marker.
6824 * var myIcon = L.icon({
6825 * iconUrl: 'my-icon.png',
6826 * iconRetinaUrl: 'my-icon@2x.png',
6827 * iconSize: [38, 95],
6828 * iconAnchor: [22, 94],
6829 * popupAnchor: [-3, -76],
6830 * shadowUrl: 'my-icon-shadow.png',
6831 * shadowRetinaUrl: 'my-icon-shadow@2x.png',
6832 * shadowSize: [68, 95],
6833 * shadowAnchor: [22, 94]
6836 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
6839 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
6843 var Icon = Class.extend({
6848 * @option iconUrl: String = null
6849 * **(required)** The URL to the icon image (absolute or relative to your script path).
6851 * @option iconRetinaUrl: String = null
6852 * The URL to a retina sized version of the icon image (absolute or relative to your
6853 * script path). Used for Retina screen devices.
6855 * @option iconSize: Point = null
6856 * Size of the icon image in pixels.
6858 * @option iconAnchor: Point = null
6859 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
6860 * will be aligned so that this point is at the marker's geographical location. Centered
6861 * by default if size is specified, also can be set in CSS with negative margins.
6863 * @option popupAnchor: Point = null
6864 * The coordinates of the point from which popups will "open", relative to the icon anchor.
6866 * @option shadowUrl: String = null
6867 * The URL to the icon shadow image. If not specified, no shadow image will be created.
6869 * @option shadowRetinaUrl: String = null
6871 * @option shadowSize: Point = null
6872 * Size of the shadow image in pixels.
6874 * @option shadowAnchor: Point = null
6875 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
6876 * as iconAnchor if not specified).
6878 * @option className: String = ''
6879 * A custom class name to assign to both icon and shadow images. Empty by default.
6882 initialize: function (options) {
6883 setOptions(this, options);
6886 // @method createIcon(oldIcon?: HTMLElement): HTMLElement
6887 // Called internally when the icon has to be shown, returns a `<img>` HTML element
6888 // styled according to the options.
6889 createIcon: function (oldIcon) {
6890 return this._createIcon('icon', oldIcon);
6893 // @method createShadow(oldIcon?: HTMLElement): HTMLElement
6894 // As `createIcon`, but for the shadow beneath it.
6895 createShadow: function (oldIcon) {
6896 return this._createIcon('shadow', oldIcon);
6899 _createIcon: function (name, oldIcon) {
6900 var src = this._getIconUrl(name);
6903 if (name === 'icon') {
6904 throw new Error('iconUrl not set in Icon options (see the docs).');
6909 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
6910 this._setIconStyles(img, name);
6915 _setIconStyles: function (img, name) {
6916 var options = this.options;
6917 var sizeOption = options[name + 'Size'];
6919 if (typeof sizeOption === 'number') {
6920 sizeOption = [sizeOption, sizeOption];
6923 var size = toPoint(sizeOption),
6924 anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
6925 size && size.divideBy(2, true));
6927 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
6930 img.style.marginLeft = (-anchor.x) + 'px';
6931 img.style.marginTop = (-anchor.y) + 'px';
6935 img.style.width = size.x + 'px';
6936 img.style.height = size.y + 'px';
6940 _createImg: function (src, el) {
6941 el = el || document.createElement('img');
6946 _getIconUrl: function (name) {
6947 return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
6952 // @factory L.icon(options: Icon options)
6953 // Creates an icon instance with the given options.
6954 function icon(options) {
6955 return new Icon(options);
6959 * @miniclass Icon.Default (Icon)
6960 * @aka L.Icon.Default
6963 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
6964 * no icon is specified. Points to the blue marker image distributed with Leaflet
6967 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
6968 * (which is a set of `Icon options`).
6970 * If you want to _completely_ replace the default icon, override the
6971 * `L.Marker.prototype.options.icon` with your own icon instead.
6974 var IconDefault = Icon.extend({
6977 iconUrl: 'marker-icon.png',
6978 iconRetinaUrl: 'marker-icon-2x.png',
6979 shadowUrl: 'marker-shadow.png',
6981 iconAnchor: [12, 41],
6982 popupAnchor: [1, -34],
6983 tooltipAnchor: [16, -28],
6984 shadowSize: [41, 41]
6987 _getIconUrl: function (name) {
6988 if (!IconDefault.imagePath) { // Deprecated, backwards-compatibility only
6989 IconDefault.imagePath = this._detectIconPath();
6992 // @option imagePath: String
6993 // `Icon.Default` will try to auto-detect the absolute location of the
6994 // blue icon images. If you are placing these images in a non-standard
6995 // way, set this option to point to the right absolute path.
6996 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
6999 _detectIconPath: function () {
7000 var el = create$1('div', 'leaflet-default-icon-path', document.body);
7001 var path = getStyle(el, 'background-image') ||
7002 getStyle(el, 'backgroundImage'); // IE8
7004 document.body.removeChild(el);
7006 if (path === null || path.indexOf('url') !== 0) {
7009 path = path.replace(/^url\([\"\']?/, '').replace(/marker-icon\.png[\"\']?\)$/, '');
7017 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7021 /* @namespace Marker
7022 * @section Interaction handlers
7024 * 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:
7027 * marker.dragging.disable();
7030 * @property dragging: Handler
7031 * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
7034 var MarkerDrag = Handler.extend({
7035 initialize: function (marker) {
7036 this._marker = marker;
7039 addHooks: function () {
7040 var icon = this._marker._icon;
7042 if (!this._draggable) {
7043 this._draggable = new Draggable(icon, icon, true);
7046 this._draggable.on({
7047 dragstart: this._onDragStart,
7049 dragend: this._onDragEnd
7052 addClass(icon, 'leaflet-marker-draggable');
7055 removeHooks: function () {
7056 this._draggable.off({
7057 dragstart: this._onDragStart,
7059 dragend: this._onDragEnd
7062 if (this._marker._icon) {
7063 removeClass(this._marker._icon, 'leaflet-marker-draggable');
7067 moved: function () {
7068 return this._draggable && this._draggable._moved;
7071 _onDragStart: function () {
7072 // @section Dragging events
7073 // @event dragstart: Event
7074 // Fired when the user starts dragging the marker.
7076 // @event movestart: Event
7077 // Fired when the marker starts moving (because of dragging).
7079 this._oldLatLng = this._marker.getLatLng();
7086 _onDrag: function (e) {
7087 var marker = this._marker,
7088 shadow = marker._shadow,
7089 iconPos = getPosition(marker._icon),
7090 latlng = marker._map.layerPointToLatLng(iconPos);
7092 // update shadow position
7094 setPosition(shadow, iconPos);
7097 marker._latlng = latlng;
7099 e.oldLatLng = this._oldLatLng;
7101 // @event drag: Event
7102 // Fired repeatedly while the user drags the marker.
7108 _onDragEnd: function (e) {
7109 // @event dragend: DragEndEvent
7110 // Fired when the user stops dragging the marker.
7112 // @event moveend: Event
7113 // Fired when the marker stops moving (because of dragging).
7114 delete this._oldLatLng;
7117 .fire('dragend', e);
7123 * @inherits Interactive layer
7125 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
7130 * L.marker([50.5, 30.5]).addTo(map);
7134 var Marker = Layer.extend({
7137 // @aka Marker options
7139 // @option icon: Icon = *
7140 // Icon instance to use for rendering the marker.
7141 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
7142 // If not specified, a common instance of `L.Icon.Default` is used.
7143 icon: new IconDefault(),
7145 // Option inherited from "Interactive layer" abstract class
7148 // @option draggable: Boolean = false
7149 // Whether the marker is draggable with mouse/touch or not.
7152 // @option keyboard: Boolean = true
7153 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
7156 // @option title: String = ''
7157 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
7160 // @option alt: String = ''
7161 // Text for the `alt` attribute of the icon image (useful for accessibility).
7164 // @option zIndexOffset: Number = 0
7165 // 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).
7168 // @option opacity: Number = 1.0
7169 // The opacity of the marker.
7172 // @option riseOnHover: Boolean = false
7173 // If `true`, the marker will get on top of others when you hover the mouse over it.
7176 // @option riseOffset: Number = 250
7177 // The z-index offset used for the `riseOnHover` feature.
7180 // @option pane: String = 'markerPane'
7181 // `Map pane` where the markers icon will be added.
7184 // @option bubblingMouseEvents: Boolean = false
7185 // When `true`, a mouse event on this marker will trigger the same event on the map
7186 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7187 bubblingMouseEvents: false
7192 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
7195 initialize: function (latlng, options) {
7196 setOptions(this, options);
7197 this._latlng = toLatLng(latlng);
7200 onAdd: function (map) {
7201 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
7203 if (this._zoomAnimated) {
7204 map.on('zoomanim', this._animateZoom, this);
7211 onRemove: function (map) {
7212 if (this.dragging && this.dragging.enabled()) {
7213 this.options.draggable = true;
7214 this.dragging.removeHooks();
7216 delete this.dragging;
7218 if (this._zoomAnimated) {
7219 map.off('zoomanim', this._animateZoom, this);
7223 this._removeShadow();
7226 getEvents: function () {
7229 viewreset: this.update
7233 // @method getLatLng: LatLng
7234 // Returns the current geographical position of the marker.
7235 getLatLng: function () {
7236 return this._latlng;
7239 // @method setLatLng(latlng: LatLng): this
7240 // Changes the marker position to the given point.
7241 setLatLng: function (latlng) {
7242 var oldLatLng = this._latlng;
7243 this._latlng = toLatLng(latlng);
7246 // @event move: Event
7247 // 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`.
7248 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7251 // @method setZIndexOffset(offset: Number): this
7252 // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
7253 setZIndexOffset: function (offset) {
7254 this.options.zIndexOffset = offset;
7255 return this.update();
7258 // @method setIcon(icon: Icon): this
7259 // Changes the marker icon.
7260 setIcon: function (icon) {
7262 this.options.icon = icon;
7270 this.bindPopup(this._popup, this._popup.options);
7276 getElement: function () {
7280 update: function () {
7283 var pos = this._map.latLngToLayerPoint(this._latlng).round();
7290 _initIcon: function () {
7291 var options = this.options,
7292 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
7294 var icon = options.icon.createIcon(this._icon),
7297 // if we're not reusing the icon, remove the old one and init new one
7298 if (icon !== this._icon) {
7304 if (options.title) {
7305 icon.title = options.title;
7308 icon.alt = options.alt;
7312 addClass(icon, classToAdd);
7314 if (options.keyboard) {
7315 icon.tabIndex = '0';
7320 if (options.riseOnHover) {
7322 mouseover: this._bringToFront,
7323 mouseout: this._resetZIndex
7327 var newShadow = options.icon.createShadow(this._shadow),
7330 if (newShadow !== this._shadow) {
7331 this._removeShadow();
7336 addClass(newShadow, classToAdd);
7339 this._shadow = newShadow;
7342 if (options.opacity < 1) {
7343 this._updateOpacity();
7348 this.getPane().appendChild(this._icon);
7350 this._initInteraction();
7351 if (newShadow && addShadow) {
7352 this.getPane('shadowPane').appendChild(this._shadow);
7356 _removeIcon: function () {
7357 if (this.options.riseOnHover) {
7359 mouseover: this._bringToFront,
7360 mouseout: this._resetZIndex
7365 this.removeInteractiveTarget(this._icon);
7370 _removeShadow: function () {
7372 remove(this._shadow);
7374 this._shadow = null;
7377 _setPos: function (pos) {
7378 setPosition(this._icon, pos);
7381 setPosition(this._shadow, pos);
7384 this._zIndex = pos.y + this.options.zIndexOffset;
7386 this._resetZIndex();
7389 _updateZIndex: function (offset) {
7390 this._icon.style.zIndex = this._zIndex + offset;
7393 _animateZoom: function (opt) {
7394 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
7399 _initInteraction: function () {
7401 if (!this.options.interactive) { return; }
7403 addClass(this._icon, 'leaflet-interactive');
7405 this.addInteractiveTarget(this._icon);
7408 var draggable = this.options.draggable;
7409 if (this.dragging) {
7410 draggable = this.dragging.enabled();
7411 this.dragging.disable();
7414 this.dragging = new MarkerDrag(this);
7417 this.dragging.enable();
7422 // @method setOpacity(opacity: Number): this
7423 // Changes the opacity of the marker.
7424 setOpacity: function (opacity) {
7425 this.options.opacity = opacity;
7427 this._updateOpacity();
7433 _updateOpacity: function () {
7434 var opacity = this.options.opacity;
7436 setOpacity(this._icon, opacity);
7439 setOpacity(this._shadow, opacity);
7443 _bringToFront: function () {
7444 this._updateZIndex(this.options.riseOffset);
7447 _resetZIndex: function () {
7448 this._updateZIndex(0);
7451 _getPopupAnchor: function () {
7452 return this.options.icon.options.popupAnchor || [0, 0];
7455 _getTooltipAnchor: function () {
7456 return this.options.icon.options.tooltipAnchor || [0, 0];
7461 // factory L.marker(latlng: LatLng, options? : Marker options)
7463 // @factory L.marker(latlng: LatLng, options? : Marker options)
7464 // Instantiates a Marker object given a geographical point and optionally an options object.
7465 function marker(latlng, options) {
7466 return new Marker(latlng, options);
7472 * @inherits Interactive layer
7474 * An abstract class that contains options and constants shared between vector
7475 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
7478 var Path = Layer.extend({
7481 // @aka Path options
7483 // @option stroke: Boolean = true
7484 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
7487 // @option color: String = '#3388ff'
7491 // @option weight: Number = 3
7492 // Stroke width in pixels
7495 // @option opacity: Number = 1.0
7499 // @option lineCap: String= 'round'
7500 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
7503 // @option lineJoin: String = 'round'
7504 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
7507 // @option dashArray: String = null
7508 // 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).
7511 // @option dashOffset: String = null
7512 // 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).
7515 // @option fill: Boolean = depends
7516 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
7519 // @option fillColor: String = *
7520 // Fill color. Defaults to the value of the [`color`](#path-color) option
7523 // @option fillOpacity: Number = 0.2
7527 // @option fillRule: String = 'evenodd'
7528 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
7529 fillRule: 'evenodd',
7533 // Option inherited from "Interactive layer" abstract class
7536 // @option bubblingMouseEvents: Boolean = true
7537 // When `true`, a mouse event on this path will trigger the same event on the map
7538 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7539 bubblingMouseEvents: true
7542 beforeAdd: function (map) {
7543 // Renderer is set here because we need to call renderer.getEvents
7544 // before this.getEvents.
7545 this._renderer = map.getRenderer(this);
7548 onAdd: function () {
7549 this._renderer._initPath(this);
7551 this._renderer._addPath(this);
7554 onRemove: function () {
7555 this._renderer._removePath(this);
7558 // @method redraw(): this
7559 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
7560 redraw: function () {
7562 this._renderer._updatePath(this);
7567 // @method setStyle(style: Path options): this
7568 // Changes the appearance of a Path based on the options in the `Path options` object.
7569 setStyle: function (style) {
7570 setOptions(this, style);
7571 if (this._renderer) {
7572 this._renderer._updateStyle(this);
7577 // @method bringToFront(): this
7578 // Brings the layer to the top of all path layers.
7579 bringToFront: function () {
7580 if (this._renderer) {
7581 this._renderer._bringToFront(this);
7586 // @method bringToBack(): this
7587 // Brings the layer to the bottom of all path layers.
7588 bringToBack: function () {
7589 if (this._renderer) {
7590 this._renderer._bringToBack(this);
7595 getElement: function () {
7599 _reset: function () {
7600 // defined in child classes
7605 _clickTolerance: function () {
7606 // used when doing hit detection for Canvas layers
7607 return (this.options.stroke ? this.options.weight / 2 : 0) + (touch ? 10 : 0);
7612 * @class CircleMarker
7613 * @aka L.CircleMarker
7616 * A circle of a fixed size with radius specified in pixels. Extends `Path`.
7619 var CircleMarker = Path.extend({
7622 // @aka CircleMarker options
7626 // @option radius: Number = 10
7627 // Radius of the circle marker, in pixels
7631 initialize: function (latlng, options) {
7632 setOptions(this, options);
7633 this._latlng = toLatLng(latlng);
7634 this._radius = this.options.radius;
7637 // @method setLatLng(latLng: LatLng): this
7638 // Sets the position of a circle marker to a new location.
7639 setLatLng: function (latlng) {
7640 this._latlng = toLatLng(latlng);
7642 return this.fire('move', {latlng: this._latlng});
7645 // @method getLatLng(): LatLng
7646 // Returns the current geographical position of the circle marker
7647 getLatLng: function () {
7648 return this._latlng;
7651 // @method setRadius(radius: Number): this
7652 // Sets the radius of a circle marker. Units are in pixels.
7653 setRadius: function (radius) {
7654 this.options.radius = this._radius = radius;
7655 return this.redraw();
7658 // @method getRadius(): Number
7659 // Returns the current radius of the circle
7660 getRadius: function () {
7661 return this._radius;
7664 setStyle : function (options) {
7665 var radius = options && options.radius || this._radius;
7666 Path.prototype.setStyle.call(this, options);
7667 this.setRadius(radius);
7671 _project: function () {
7672 this._point = this._map.latLngToLayerPoint(this._latlng);
7673 this._updateBounds();
7676 _updateBounds: function () {
7677 var r = this._radius,
7678 r2 = this._radiusY || r,
7679 w = this._clickTolerance(),
7680 p = [r + w, r2 + w];
7681 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
7684 _update: function () {
7690 _updatePath: function () {
7691 this._renderer._updateCircle(this);
7694 _empty: function () {
7695 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
7698 // Needed by the `Canvas` renderer for interactivity
7699 _containsPoint: function (p) {
7700 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
7705 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
7706 // Instantiates a circle marker object given a geographical point, and an optional options object.
7707 function circleMarker(latlng, options) {
7708 return new CircleMarker(latlng, options);
7714 * @inherits CircleMarker
7716 * A class for drawing circle overlays on a map. Extends `CircleMarker`.
7718 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
7723 * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
7727 var Circle = CircleMarker.extend({
7729 initialize: function (latlng, options, legacyOptions) {
7730 if (typeof options === 'number') {
7731 // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
7732 options = extend({}, legacyOptions, {radius: options});
7734 setOptions(this, options);
7735 this._latlng = toLatLng(latlng);
7737 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
7740 // @aka Circle options
7741 // @option radius: Number; Radius of the circle, in meters.
7742 this._mRadius = this.options.radius;
7745 // @method setRadius(radius: Number): this
7746 // Sets the radius of a circle. Units are in meters.
7747 setRadius: function (radius) {
7748 this._mRadius = radius;
7749 return this.redraw();
7752 // @method getRadius(): Number
7753 // Returns the current radius of a circle. Units are in meters.
7754 getRadius: function () {
7755 return this._mRadius;
7758 // @method getBounds(): LatLngBounds
7759 // Returns the `LatLngBounds` of the path.
7760 getBounds: function () {
7761 var half = [this._radius, this._radiusY || this._radius];
7763 return new LatLngBounds(
7764 this._map.layerPointToLatLng(this._point.subtract(half)),
7765 this._map.layerPointToLatLng(this._point.add(half)));
7768 setStyle: Path.prototype.setStyle,
7770 _project: function () {
7772 var lng = this._latlng.lng,
7773 lat = this._latlng.lat,
7775 crs = map.options.crs;
7777 if (crs.distance === Earth.distance) {
7778 var d = Math.PI / 180,
7779 latR = (this._mRadius / Earth.R) / d,
7780 top = map.project([lat + latR, lng]),
7781 bottom = map.project([lat - latR, lng]),
7782 p = top.add(bottom).divideBy(2),
7783 lat2 = map.unproject(p).lat,
7784 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
7785 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
7787 if (isNaN(lngR) || lngR === 0) {
7788 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
7791 this._point = p.subtract(map.getPixelOrigin());
7792 this._radius = isNaN(lngR) ? 0 : Math.max(Math.round(p.x - map.project([lat2, lng - lngR]).x), 1);
7793 this._radiusY = Math.max(Math.round(p.y - top.y), 1);
7796 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
7798 this._point = map.latLngToLayerPoint(this._latlng);
7799 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
7802 this._updateBounds();
7806 // @factory L.circle(latlng: LatLng, options?: Circle options)
7807 // Instantiates a circle object given a geographical point, and an options object
7808 // which contains the circle radius.
7810 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
7811 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
7812 // Do not use in new applications or plugins.
7813 function circle(latlng, options, legacyOptions) {
7814 return new Circle(latlng, options, legacyOptions);
7822 * A class for drawing polyline overlays on a map. Extends `Path`.
7827 * // create a red polyline from an array of LatLng points
7834 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
7836 * // zoom the map to the polyline
7837 * map.fitBounds(polyline.getBounds());
7840 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
7843 * // create a red polyline from an array of arrays of LatLng points
7845 * [[45.51, -122.68],
7856 var Polyline = Path.extend({
7859 // @aka Polyline options
7861 // @option smoothFactor: Number = 1.0
7862 // How much to simplify the polyline on each zoom level. More means
7863 // better performance and smoother look, and less means more accurate representation.
7866 // @option noClip: Boolean = false
7867 // Disable polyline clipping.
7871 initialize: function (latlngs, options) {
7872 setOptions(this, options);
7873 this._setLatLngs(latlngs);
7876 // @method getLatLngs(): LatLng[]
7877 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
7878 getLatLngs: function () {
7879 return this._latlngs;
7882 // @method setLatLngs(latlngs: LatLng[]): this
7883 // Replaces all the points in the polyline with the given array of geographical points.
7884 setLatLngs: function (latlngs) {
7885 this._setLatLngs(latlngs);
7886 return this.redraw();
7889 // @method isEmpty(): Boolean
7890 // Returns `true` if the Polyline has no LatLngs.
7891 isEmpty: function () {
7892 return !this._latlngs.length;
7895 closestLayerPoint: function (p) {
7896 var minDistance = Infinity,
7898 closest = _sqClosestPointOnSegment,
7901 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
7902 var points = this._parts[j];
7904 for (var i = 1, len = points.length; i < len; i++) {
7908 var sqDist = closest(p, p1, p2, true);
7910 if (sqDist < minDistance) {
7911 minDistance = sqDist;
7912 minPoint = closest(p, p1, p2);
7917 minPoint.distance = Math.sqrt(minDistance);
7922 // @method getCenter(): LatLng
7923 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
7924 getCenter: function () {
7925 // throws error when not yet added to map as this center calculation requires projected coordinates
7927 throw new Error('Must add layer to map before using getCenter()');
7930 var i, halfDist, segDist, dist, p1, p2, ratio,
7931 points = this._rings[0],
7932 len = points.length;
7934 if (!len) { return null; }
7936 // polyline centroid algorithm; only uses the first ring if there are multiple
7938 for (i = 0, halfDist = 0; i < len - 1; i++) {
7939 halfDist += points[i].distanceTo(points[i + 1]) / 2;
7942 // The line is so small in the current view that all points are on the same pixel.
7943 if (halfDist === 0) {
7944 return this._map.layerPointToLatLng(points[0]);
7947 for (i = 0, dist = 0; i < len - 1; i++) {
7950 segDist = p1.distanceTo(p2);
7953 if (dist > halfDist) {
7954 ratio = (dist - halfDist) / segDist;
7955 return this._map.layerPointToLatLng([
7956 p2.x - ratio * (p2.x - p1.x),
7957 p2.y - ratio * (p2.y - p1.y)
7963 // @method getBounds(): LatLngBounds
7964 // Returns the `LatLngBounds` of the path.
7965 getBounds: function () {
7966 return this._bounds;
7969 // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
7970 // Adds a given point to the polyline. By default, adds to the first ring of
7971 // the polyline in case of a multi-polyline, but can be overridden by passing
7972 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
7973 addLatLng: function (latlng, latlngs) {
7974 latlngs = latlngs || this._defaultShape();
7975 latlng = toLatLng(latlng);
7976 latlngs.push(latlng);
7977 this._bounds.extend(latlng);
7978 return this.redraw();
7981 _setLatLngs: function (latlngs) {
7982 this._bounds = new LatLngBounds();
7983 this._latlngs = this._convertLatLngs(latlngs);
7986 _defaultShape: function () {
7987 return _flat(this._latlngs) ? this._latlngs : this._latlngs[0];
7990 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
7991 _convertLatLngs: function (latlngs) {
7993 flat = _flat(latlngs);
7995 for (var i = 0, len = latlngs.length; i < len; i++) {
7997 result[i] = toLatLng(latlngs[i]);
7998 this._bounds.extend(result[i]);
8000 result[i] = this._convertLatLngs(latlngs[i]);
8007 _project: function () {
8008 var pxBounds = new Bounds();
8010 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8012 var w = this._clickTolerance(),
8013 p = new Point(w, w);
8015 if (this._bounds.isValid() && pxBounds.isValid()) {
8016 pxBounds.min._subtract(p);
8017 pxBounds.max._add(p);
8018 this._pxBounds = pxBounds;
8022 // recursively turns latlngs into a set of rings with projected coordinates
8023 _projectLatlngs: function (latlngs, result, projectedBounds) {
8024 var flat = latlngs[0] instanceof LatLng,
8025 len = latlngs.length,
8030 for (i = 0; i < len; i++) {
8031 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8032 projectedBounds.extend(ring[i]);
8036 for (i = 0; i < len; i++) {
8037 this._projectLatlngs(latlngs[i], result, projectedBounds);
8042 // clip polyline by renderer bounds so that we have less to render for performance
8043 _clipPoints: function () {
8044 var bounds = this._renderer._bounds;
8047 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8051 if (this.options.noClip) {
8052 this._parts = this._rings;
8056 var parts = this._parts,
8057 i, j, k, len, len2, segment, points;
8059 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8060 points = this._rings[i];
8062 for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8063 segment = clipSegment(points[j], points[j + 1], bounds, j, true);
8065 if (!segment) { continue; }
8067 parts[k] = parts[k] || [];
8068 parts[k].push(segment[0]);
8070 // if segment goes out of screen, or it's the last one, it's the end of the line part
8071 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8072 parts[k].push(segment[1]);
8079 // simplify each clipped part of the polyline for performance
8080 _simplifyPoints: function () {
8081 var parts = this._parts,
8082 tolerance = this.options.smoothFactor;
8084 for (var i = 0, len = parts.length; i < len; i++) {
8085 parts[i] = simplify(parts[i], tolerance);
8089 _update: function () {
8090 if (!this._map) { return; }
8093 this._simplifyPoints();
8097 _updatePath: function () {
8098 this._renderer._updatePoly(this);
8101 // Needed by the `Canvas` renderer for interactivity
8102 _containsPoint: function (p, closed) {
8103 var i, j, k, len, len2, part,
8104 w = this._clickTolerance();
8106 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8108 // hit detection for polylines
8109 for (i = 0, len = this._parts.length; i < len; i++) {
8110 part = this._parts[i];
8112 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8113 if (!closed && (j === 0)) { continue; }
8115 if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
8124 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8125 // Instantiates a polyline object given an array of geographical points and
8126 // optionally an options object. You can create a `Polyline` object with
8127 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8128 // of geographic points.
8129 function polyline(latlngs, options) {
8130 return new Polyline(latlngs, options);
8136 * @inherits Polyline
8138 * A class for drawing polygon overlays on a map. Extends `Polyline`.
8140 * 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.
8146 * // create a red polygon from an array of LatLng points
8147 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
8149 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8151 * // zoom the map to the polygon
8152 * map.fitBounds(polygon.getBounds());
8155 * 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:
8159 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8160 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8164 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8168 * [ // first polygon
8169 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8170 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8172 * [ // second polygon
8173 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
8179 var Polygon = Polyline.extend({
8185 isEmpty: function () {
8186 return !this._latlngs.length || !this._latlngs[0].length;
8189 getCenter: function () {
8190 // throws error when not yet added to map as this center calculation requires projected coordinates
8192 throw new Error('Must add layer to map before using getCenter()');
8195 var i, j, p1, p2, f, area, x, y, center,
8196 points = this._rings[0],
8197 len = points.length;
8199 if (!len) { return null; }
8201 // polygon centroid algorithm; only uses the first ring if there are multiple
8205 for (i = 0, j = len - 1; i < len; j = i++) {
8209 f = p1.y * p2.x - p2.y * p1.x;
8210 x += (p1.x + p2.x) * f;
8211 y += (p1.y + p2.y) * f;
8216 // Polygon is so small that all points are on same pixel.
8219 center = [x / area, y / area];
8221 return this._map.layerPointToLatLng(center);
8224 _convertLatLngs: function (latlngs) {
8225 var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
8226 len = result.length;
8228 // remove last point if it equals first one
8229 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
8235 _setLatLngs: function (latlngs) {
8236 Polyline.prototype._setLatLngs.call(this, latlngs);
8237 if (_flat(this._latlngs)) {
8238 this._latlngs = [this._latlngs];
8242 _defaultShape: function () {
8243 return _flat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8246 _clipPoints: function () {
8247 // polygons need a different clipping algorithm so we redefine that
8249 var bounds = this._renderer._bounds,
8250 w = this.options.weight,
8251 p = new Point(w, w);
8253 // increase clip padding by stroke width to avoid stroke on clip edges
8254 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
8257 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8261 if (this.options.noClip) {
8262 this._parts = this._rings;
8266 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8267 clipped = clipPolygon(this._rings[i], bounds, true);
8268 if (clipped.length) {
8269 this._parts.push(clipped);
8274 _updatePath: function () {
8275 this._renderer._updatePoly(this, true);
8278 // Needed by the `Canvas` renderer for interactivity
8279 _containsPoint: function (p) {
8281 part, p1, p2, i, j, k, len, len2;
8283 if (!this._pxBounds.contains(p)) { return false; }
8285 // ray casting algorithm for detecting if point is in polygon
8286 for (i = 0, len = this._parts.length; i < len; i++) {
8287 part = this._parts[i];
8289 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8293 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)) {
8299 // also check if it's on polygon stroke
8300 return inside || Polyline.prototype._containsPoint.call(this, p, true);
8306 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8307 function polygon(latlngs, options) {
8308 return new Polygon(latlngs, options);
8314 * @inherits FeatureGroup
8316 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
8317 * GeoJSON data and display it on the map. Extends `FeatureGroup`.
8323 * style: function (feature) {
8324 * return {color: feature.properties.color};
8326 * }).bindPopup(function (layer) {
8327 * return layer.feature.properties.description;
8332 var GeoJSON = FeatureGroup.extend({
8335 * @aka GeoJSON options
8337 * @option pointToLayer: Function = *
8338 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
8339 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
8340 * The default is to spawn a default `Marker`:
8342 * function(geoJsonPoint, latlng) {
8343 * return L.marker(latlng);
8347 * @option style: Function = *
8348 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
8349 * called internally when data is added.
8350 * The default value is to not override any defaults:
8352 * function (geoJsonFeature) {
8357 * @option onEachFeature: Function = *
8358 * A `Function` that will be called once for each created `Feature`, after it has
8359 * been created and styled. Useful for attaching events and popups to features.
8360 * The default is to do nothing with the newly created layers:
8362 * function (feature, layer) {}
8365 * @option filter: Function = *
8366 * A `Function` that will be used to decide whether to include a feature or not.
8367 * The default is to include all features:
8369 * function (geoJsonFeature) {
8373 * Note: dynamically changing the `filter` option will have effect only on newly
8374 * added data. It will _not_ re-evaluate already included features.
8376 * @option coordsToLatLng: Function = *
8377 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
8378 * The default is the `coordsToLatLng` static method.
8381 initialize: function (geojson, options) {
8382 setOptions(this, options);
8387 this.addData(geojson);
8391 // @method addData( <GeoJSON> data ): this
8392 // Adds a GeoJSON object to the layer.
8393 addData: function (geojson) {
8394 var features = isArray(geojson) ? geojson : geojson.features,
8398 for (i = 0, len = features.length; i < len; i++) {
8399 // only add this if geometry or geometries are set and not null
8400 feature = features[i];
8401 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
8402 this.addData(feature);
8408 var options = this.options;
8410 if (options.filter && !options.filter(geojson)) { return this; }
8412 var layer = geometryToLayer(geojson, options);
8416 layer.feature = asFeature(geojson);
8418 layer.defaultOptions = layer.options;
8419 this.resetStyle(layer);
8421 if (options.onEachFeature) {
8422 options.onEachFeature(geojson, layer);
8425 return this.addLayer(layer);
8428 // @method resetStyle( <Path> layer ): this
8429 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
8430 resetStyle: function (layer) {
8431 // reset any custom styles
8432 layer.options = extend({}, layer.defaultOptions);
8433 this._setLayerStyle(layer, this.options.style);
8437 // @method setStyle( <Function> style ): this
8438 // Changes styles of GeoJSON vector layers with the given style function.
8439 setStyle: function (style) {
8440 return this.eachLayer(function (layer) {
8441 this._setLayerStyle(layer, style);
8445 _setLayerStyle: function (layer, style) {
8446 if (typeof style === 'function') {
8447 style = style(layer.feature);
8449 if (layer.setStyle) {
8450 layer.setStyle(style);
8456 // There are several static functions which can be called without instantiating L.GeoJSON:
8458 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
8459 // Creates a `Layer` from a given GeoJSON feature. Can use a custom
8460 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
8461 // functions if provided as options.
8462 function geometryToLayer(geojson, options) {
8464 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
8465 coords = geometry ? geometry.coordinates : null,
8467 pointToLayer = options && options.pointToLayer,
8468 _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
8469 latlng, latlngs, i, len;
8471 if (!coords && !geometry) {
8475 switch (geometry.type) {
8477 latlng = _coordsToLatLng(coords);
8478 return pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng);
8481 for (i = 0, len = coords.length; i < len; i++) {
8482 latlng = _coordsToLatLng(coords[i]);
8483 layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng));
8485 return new FeatureGroup(layers);
8488 case 'MultiLineString':
8489 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
8490 return new Polyline(latlngs, options);
8493 case 'MultiPolygon':
8494 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
8495 return new Polygon(latlngs, options);
8497 case 'GeometryCollection':
8498 for (i = 0, len = geometry.geometries.length; i < len; i++) {
8499 var layer = geometryToLayer({
8500 geometry: geometry.geometries[i],
8502 properties: geojson.properties
8509 return new FeatureGroup(layers);
8512 throw new Error('Invalid GeoJSON object.');
8516 // @function coordsToLatLng(coords: Array): LatLng
8517 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
8518 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
8519 function coordsToLatLng(coords) {
8520 return new LatLng(coords[1], coords[0], coords[2]);
8523 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
8524 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
8525 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
8526 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
8527 function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
8530 for (var i = 0, len = coords.length, latlng; i < len; i++) {
8531 latlng = levelsDeep ?
8532 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
8533 (_coordsToLatLng || coordsToLatLng)(coords[i]);
8535 latlngs.push(latlng);
8541 // @function latLngToCoords(latlng: LatLng, precision?: Number): Array
8542 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
8543 function latLngToCoords(latlng, precision) {
8544 precision = typeof precision === 'number' ? precision : 6;
8545 return latlng.alt !== undefined ?
8546 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
8547 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
8550 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
8551 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
8552 // `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.
8553 function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
8556 for (var i = 0, len = latlngs.length; i < len; i++) {
8557 coords.push(levelsDeep ?
8558 latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) :
8559 latLngToCoords(latlngs[i], precision));
8562 if (!levelsDeep && closed) {
8563 coords.push(coords[0]);
8569 function getFeature(layer, newGeometry) {
8570 return layer.feature ?
8571 extend({}, layer.feature, {geometry: newGeometry}) :
8572 asFeature(newGeometry);
8575 // @function asFeature(geojson: Object): Object
8576 // Normalize GeoJSON geometries/features into GeoJSON features.
8577 function asFeature(geojson) {
8578 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
8589 var PointToGeoJSON = {
8590 toGeoJSON: function (precision) {
8591 return getFeature(this, {
8593 coordinates: latLngToCoords(this.getLatLng(), precision)
8598 // @namespace Marker
8599 // @method toGeoJSON(): Object
8600 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
8601 Marker.include(PointToGeoJSON);
8603 // @namespace CircleMarker
8604 // @method toGeoJSON(): Object
8605 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
8606 Circle.include(PointToGeoJSON);
8607 CircleMarker.include(PointToGeoJSON);
8610 // @namespace Polyline
8611 // @method toGeoJSON(): Object
8612 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
8614 toGeoJSON: function (precision) {
8615 var multi = !_flat(this._latlngs);
8617 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
8619 return getFeature(this, {
8620 type: (multi ? 'Multi' : '') + 'LineString',
8626 // @namespace Polygon
8627 // @method toGeoJSON(): Object
8628 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
8630 toGeoJSON: function (precision) {
8631 var holes = !_flat(this._latlngs),
8632 multi = holes && !_flat(this._latlngs[0]);
8634 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
8640 return getFeature(this, {
8641 type: (multi ? 'Multi' : '') + 'Polygon',
8648 // @namespace LayerGroup
8649 LayerGroup.include({
8650 toMultiPoint: function (precision) {
8653 this.eachLayer(function (layer) {
8654 coords.push(layer.toGeoJSON(precision).geometry.coordinates);
8657 return getFeature(this, {
8663 // @method toGeoJSON(): Object
8664 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
8665 toGeoJSON: function (precision) {
8667 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
8669 if (type === 'MultiPoint') {
8670 return this.toMultiPoint(precision);
8673 var isGeometryCollection = type === 'GeometryCollection',
8676 this.eachLayer(function (layer) {
8677 if (layer.toGeoJSON) {
8678 var json = layer.toGeoJSON(precision);
8679 if (isGeometryCollection) {
8680 jsons.push(json.geometry);
8682 var feature = asFeature(json);
8683 // Squash nested feature collections
8684 if (feature.type === 'FeatureCollection') {
8685 jsons.push.apply(jsons, feature.features);
8687 jsons.push(feature);
8693 if (isGeometryCollection) {
8694 return getFeature(this, {
8696 type: 'GeometryCollection'
8701 type: 'FeatureCollection',
8707 // @namespace GeoJSON
8708 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
8709 // Creates a GeoJSON layer. Optionally accepts an object in
8710 // [GeoJSON format](http://geojson.org/geojson-spec.html) to display on the map
8711 // (you can alternatively add it later with `addData` method) and an `options` object.
8712 function geoJSON(geojson, options) {
8713 return new GeoJSON(geojson, options);
8716 // Backward compatibility.
8717 var geoJson = geoJSON;
8720 * @class ImageOverlay
8721 * @aka L.ImageOverlay
8722 * @inherits Interactive layer
8724 * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
8729 * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
8730 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
8731 * L.imageOverlay(imageUrl, imageBounds).addTo(map);
8735 var ImageOverlay = Layer.extend({
8738 // @aka ImageOverlay options
8740 // @option opacity: Number = 1.0
8741 // The opacity of the image overlay.
8744 // @option alt: String = ''
8745 // Text for the `alt` attribute of the image (useful for accessibility).
8748 // @option interactive: Boolean = false
8749 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
8752 // @option crossOrigin: Boolean = false
8753 // If true, the image will have its crossOrigin attribute set to ''. This is needed if you want to access image pixel data.
8756 // @option errorOverlayUrl: String = ''
8757 // URL to the overlay image to show in place of the overlay that failed to load.
8758 errorOverlayUrl: '',
8760 // @option zIndex: Number = 1
8761 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the tile layer.
8764 // @option className: String = ''
8765 // A custom class name to assign to the image. Empty by default.
8769 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
8771 this._bounds = toLatLngBounds(bounds);
8773 setOptions(this, options);
8776 onAdd: function () {
8780 if (this.options.opacity < 1) {
8781 this._updateOpacity();
8785 if (this.options.interactive) {
8786 addClass(this._image, 'leaflet-interactive');
8787 this.addInteractiveTarget(this._image);
8790 this.getPane().appendChild(this._image);
8794 onRemove: function () {
8795 remove(this._image);
8796 if (this.options.interactive) {
8797 this.removeInteractiveTarget(this._image);
8801 // @method setOpacity(opacity: Number): this
8802 // Sets the opacity of the overlay.
8803 setOpacity: function (opacity) {
8804 this.options.opacity = opacity;
8807 this._updateOpacity();
8812 setStyle: function (styleOpts) {
8813 if (styleOpts.opacity) {
8814 this.setOpacity(styleOpts.opacity);
8819 // @method bringToFront(): this
8820 // Brings the layer to the top of all overlays.
8821 bringToFront: function () {
8823 toFront(this._image);
8828 // @method bringToBack(): this
8829 // Brings the layer to the bottom of all overlays.
8830 bringToBack: function () {
8832 toBack(this._image);
8837 // @method setUrl(url: String): this
8838 // Changes the URL of the image.
8839 setUrl: function (url) {
8843 this._image.src = url;
8848 // @method setBounds(bounds: LatLngBounds): this
8849 // Update the bounds that this ImageOverlay covers
8850 setBounds: function (bounds) {
8851 this._bounds = bounds;
8859 getEvents: function () {
8862 viewreset: this._reset
8865 if (this._zoomAnimated) {
8866 events.zoomanim = this._animateZoom;
8872 // @method: setZIndex(value: Number) : this
8873 // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
8874 setZIndex: function (value) {
8875 this.options.zIndex = value;
8876 this._updateZIndex();
8880 // @method getBounds(): LatLngBounds
8881 // Get the bounds that this ImageOverlay covers
8882 getBounds: function () {
8883 return this._bounds;
8886 // @method getElement(): HTMLElement
8887 // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
8888 // used by this overlay.
8889 getElement: function () {
8893 _initImage: function () {
8894 var img = this._image = create$1('img',
8895 'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : '') +
8896 (this.options.className || ''));
8898 img.onselectstart = falseFn;
8899 img.onmousemove = falseFn;
8901 // @event load: Event
8902 // Fired when the ImageOverlay layer has loaded its image
8903 img.onload = bind(this.fire, this, 'load');
8904 img.onerror = bind(this._overlayOnError, this, 'error');
8906 if (this.options.crossOrigin) {
8907 img.crossOrigin = '';
8910 if (this.options.zIndex) {
8911 this._updateZIndex();
8914 img.src = this._url;
8915 img.alt = this.options.alt;
8918 _animateZoom: function (e) {
8919 var scale = this._map.getZoomScale(e.zoom),
8920 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
8922 setTransform(this._image, offset, scale);
8925 _reset: function () {
8926 var image = this._image,
8927 bounds = new Bounds(
8928 this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
8929 this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
8930 size = bounds.getSize();
8932 setPosition(image, bounds.min);
8934 image.style.width = size.x + 'px';
8935 image.style.height = size.y + 'px';
8938 _updateOpacity: function () {
8939 setOpacity(this._image, this.options.opacity);
8942 _updateZIndex: function () {
8943 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
8944 this._image.style.zIndex = this.options.zIndex;
8948 _overlayOnError: function () {
8949 // @event error: Event
8950 // Fired when the ImageOverlay layer has loaded its image
8953 var errorUrl = this.options.errorOverlayUrl;
8954 if (errorUrl && this._url !== errorUrl) {
8955 this._url = errorUrl;
8956 this._image.src = errorUrl;
8961 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
8962 // Instantiates an image overlay object given the URL of the image and the
8963 // geographical bounds it is tied to.
8964 var imageOverlay = function (url, bounds, options) {
8965 return new ImageOverlay(url, bounds, options);
8969 * @class VideoOverlay
8970 * @aka L.VideoOverlay
8971 * @inherits ImageOverlay
8973 * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
8975 * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
8981 * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
8982 * imageBounds = [[ 32, -130], [ 13, -100]];
8983 * L.imageOverlay(imageUrl, imageBounds).addTo(map);
8987 var VideoOverlay = ImageOverlay.extend({
8990 // @aka VideoOverlay options
8992 // @option autoplay: Boolean = true
8993 // Whether the video starts playing automatically when loaded.
8996 // @option loop: Boolean = true
8997 // Whether the video will loop back to the beginning when played.
9001 _initImage: function () {
9002 var vid = this._image = create$1('video',
9003 'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : ''));
9005 vid.onselectstart = falseFn;
9006 vid.onmousemove = falseFn;
9008 // @event load: Event
9009 // Fired when the video has finished loading the first frame
9010 vid.onloadeddata = bind(this.fire, this, 'load');
9012 if (!isArray(this._url)) { this._url = [this._url]; }
9014 vid.autoplay = !!this.options.autoplay;
9015 vid.loop = !!this.options.loop;
9016 for (var i = 0; i < this._url.length; i++) {
9017 var source = create$1('source');
9018 source.src = this._url[i];
9019 vid.appendChild(source);
9023 // @method getElement(): HTMLVideoElement
9024 // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
9025 // used by this overlay.
9029 // @factory L.videoOverlay(videoUrl: String|Array, bounds: LatLngBounds, options?: VideoOverlay options)
9030 // Instantiates an image overlay object given the URL of the video (or array of URLs) and the
9031 // geographical bounds it is tied to.
9032 function videoOverlay(url, bounds, options) {
9033 return new VideoOverlay(url, bounds, options);
9040 * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
9043 // @namespace DivOverlay
9044 var DivOverlay = Layer.extend({
9047 // @aka DivOverlay options
9049 // @option offset: Point = Point(0, 7)
9050 // The offset of the popup position. Useful to control the anchor
9051 // of the popup when opening it on some overlays.
9054 // @option className: String = ''
9055 // A custom CSS class name to assign to the popup.
9058 // @option pane: String = 'popupPane'
9059 // `Map pane` where the popup will be added.
9063 initialize: function (options, source) {
9064 setOptions(this, options);
9066 this._source = source;
9069 onAdd: function (map) {
9070 this._zoomAnimated = map._zoomAnimated;
9072 if (!this._container) {
9076 if (map._fadeAnimated) {
9077 setOpacity(this._container, 0);
9080 clearTimeout(this._removeTimeout);
9081 this.getPane().appendChild(this._container);
9084 if (map._fadeAnimated) {
9085 setOpacity(this._container, 1);
9088 this.bringToFront();
9091 onRemove: function (map) {
9092 if (map._fadeAnimated) {
9093 setOpacity(this._container, 0);
9094 this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
9096 remove(this._container);
9101 // @method getLatLng: LatLng
9102 // Returns the geographical point of popup.
9103 getLatLng: function () {
9104 return this._latlng;
9107 // @method setLatLng(latlng: LatLng): this
9108 // Sets the geographical point where the popup will open.
9109 setLatLng: function (latlng) {
9110 this._latlng = toLatLng(latlng);
9112 this._updatePosition();
9118 // @method getContent: String|HTMLElement
9119 // Returns the content of the popup.
9120 getContent: function () {
9121 return this._content;
9124 // @method setContent(htmlContent: String|HTMLElement|Function): this
9125 // 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.
9126 setContent: function (content) {
9127 this._content = content;
9132 // @method getElement: String|HTMLElement
9133 // Alias for [getContent()](#popup-getcontent)
9134 getElement: function () {
9135 return this._container;
9138 // @method update: null
9139 // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
9140 update: function () {
9141 if (!this._map) { return; }
9143 this._container.style.visibility = 'hidden';
9145 this._updateContent();
9146 this._updateLayout();
9147 this._updatePosition();
9149 this._container.style.visibility = '';
9154 getEvents: function () {
9156 zoom: this._updatePosition,
9157 viewreset: this._updatePosition
9160 if (this._zoomAnimated) {
9161 events.zoomanim = this._animateZoom;
9166 // @method isOpen: Boolean
9167 // Returns `true` when the popup is visible on the map.
9168 isOpen: function () {
9169 return !!this._map && this._map.hasLayer(this);
9172 // @method bringToFront: this
9173 // Brings this popup in front of other popups (in the same map pane).
9174 bringToFront: function () {
9176 toFront(this._container);
9181 // @method bringToBack: this
9182 // Brings this popup to the back of other popups (in the same map pane).
9183 bringToBack: function () {
9185 toBack(this._container);
9190 _updateContent: function () {
9191 if (!this._content) { return; }
9193 var node = this._contentNode;
9194 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
9196 if (typeof content === 'string') {
9197 node.innerHTML = content;
9199 while (node.hasChildNodes()) {
9200 node.removeChild(node.firstChild);
9202 node.appendChild(content);
9204 this.fire('contentupdate');
9207 _updatePosition: function () {
9208 if (!this._map) { return; }
9210 var pos = this._map.latLngToLayerPoint(this._latlng),
9211 offset = toPoint(this.options.offset),
9212 anchor = this._getAnchor();
9214 if (this._zoomAnimated) {
9215 setPosition(this._container, pos.add(anchor));
9217 offset = offset.add(pos).add(anchor);
9220 var bottom = this._containerBottom = -offset.y,
9221 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
9223 // bottom position the popup in case the height of the popup changes (images loading etc)
9224 this._container.style.bottom = bottom + 'px';
9225 this._container.style.left = left + 'px';
9228 _getAnchor: function () {
9236 * @inherits DivOverlay
9238 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
9239 * open popups while making sure that only one popup is open at one time
9240 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
9244 * If you want to just bind a popup to marker click and then open it, it's really easy:
9247 * marker.bindPopup(popupContent).openPopup();
9249 * Path overlays like polylines also have a `bindPopup` method.
9250 * Here's a more complicated way to open a popup on a map:
9253 * var popup = L.popup()
9254 * .setLatLng(latlng)
9255 * .setContent('<p>Hello world!<br />This is a nice popup.</p>')
9262 var Popup = DivOverlay.extend({
9265 // @aka Popup options
9267 // @option maxWidth: Number = 300
9268 // Max width of the popup, in pixels.
9271 // @option minWidth: Number = 50
9272 // Min width of the popup, in pixels.
9275 // @option maxHeight: Number = null
9276 // If set, creates a scrollable container of the given height
9277 // inside a popup if its content exceeds it.
9280 // @option autoPan: Boolean = true
9281 // Set it to `false` if you don't want the map to do panning animation
9282 // to fit the opened popup.
9285 // @option autoPanPaddingTopLeft: Point = null
9286 // The margin between the popup and the top left corner of the map
9287 // view after autopanning was performed.
9288 autoPanPaddingTopLeft: null,
9290 // @option autoPanPaddingBottomRight: Point = null
9291 // The margin between the popup and the bottom right corner of the map
9292 // view after autopanning was performed.
9293 autoPanPaddingBottomRight: null,
9295 // @option autoPanPadding: Point = Point(5, 5)
9296 // Equivalent of setting both top left and bottom right autopan padding to the same value.
9297 autoPanPadding: [5, 5],
9299 // @option keepInView: Boolean = false
9300 // Set it to `true` if you want to prevent users from panning the popup
9301 // off of the screen while it is open.
9304 // @option closeButton: Boolean = true
9305 // Controls the presence of a close button in the popup.
9308 // @option autoClose: Boolean = true
9309 // Set it to `false` if you want to override the default behavior of
9310 // the popup closing when another popup is opened.
9313 // @option closeOnClick: Boolean = *
9314 // Set it if you want to override the default behavior of the popup closing when user clicks
9315 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
9317 // @option className: String = ''
9318 // A custom CSS class name to assign to the popup.
9323 // @method openOn(map: Map): this
9324 // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
9325 openOn: function (map) {
9326 map.openPopup(this);
9330 onAdd: function (map) {
9331 DivOverlay.prototype.onAdd.call(this, map);
9334 // @section Popup events
9335 // @event popupopen: PopupEvent
9336 // Fired when a popup is opened in the map
9337 map.fire('popupopen', {popup: this});
9341 // @section Popup events
9342 // @event popupopen: PopupEvent
9343 // Fired when a popup bound to this layer is opened
9344 this._source.fire('popupopen', {popup: this}, true);
9345 // For non-path layers, we toggle the popup when clicking
9346 // again the layer, so prevent the map to reopen it.
9347 if (!(this._source instanceof Path)) {
9348 this._source.on('preclick', stopPropagation);
9353 onRemove: function (map) {
9354 DivOverlay.prototype.onRemove.call(this, map);
9357 // @section Popup events
9358 // @event popupclose: PopupEvent
9359 // Fired when a popup in the map is closed
9360 map.fire('popupclose', {popup: this});
9364 // @section Popup events
9365 // @event popupclose: PopupEvent
9366 // Fired when a popup bound to this layer is closed
9367 this._source.fire('popupclose', {popup: this}, true);
9368 if (!(this._source instanceof Path)) {
9369 this._source.off('preclick', stopPropagation);
9374 getEvents: function () {
9375 var events = DivOverlay.prototype.getEvents.call(this);
9377 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
9378 events.preclick = this._close;
9381 if (this.options.keepInView) {
9382 events.moveend = this._adjustPan;
9388 _close: function () {
9390 this._map.closePopup(this);
9394 _initLayout: function () {
9395 var prefix = 'leaflet-popup',
9396 container = this._container = create$1('div',
9397 prefix + ' ' + (this.options.className || '') +
9398 ' leaflet-zoom-animated');
9400 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
9401 this._contentNode = create$1('div', prefix + '-content', wrapper);
9403 disableClickPropagation(wrapper);
9404 disableScrollPropagation(this._contentNode);
9405 on(wrapper, 'contextmenu', stopPropagation);
9407 this._tipContainer = create$1('div', prefix + '-tip-container', container);
9408 this._tip = create$1('div', prefix + '-tip', this._tipContainer);
9410 if (this.options.closeButton) {
9411 var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
9412 closeButton.href = '#close';
9413 closeButton.innerHTML = '×';
9415 on(closeButton, 'click', this._onCloseButtonClick, this);
9419 _updateLayout: function () {
9420 var container = this._contentNode,
9421 style = container.style;
9424 style.whiteSpace = 'nowrap';
9426 var width = container.offsetWidth;
9427 width = Math.min(width, this.options.maxWidth);
9428 width = Math.max(width, this.options.minWidth);
9430 style.width = (width + 1) + 'px';
9431 style.whiteSpace = '';
9435 var height = container.offsetHeight,
9436 maxHeight = this.options.maxHeight,
9437 scrolledClass = 'leaflet-popup-scrolled';
9439 if (maxHeight && height > maxHeight) {
9440 style.height = maxHeight + 'px';
9441 addClass(container, scrolledClass);
9443 removeClass(container, scrolledClass);
9446 this._containerWidth = this._container.offsetWidth;
9449 _animateZoom: function (e) {
9450 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
9451 anchor = this._getAnchor();
9452 setPosition(this._container, pos.add(anchor));
9455 _adjustPan: function () {
9456 if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; }
9458 var map = this._map,
9459 marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
9460 containerHeight = this._container.offsetHeight + marginBottom,
9461 containerWidth = this._containerWidth,
9462 layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
9464 layerPos._add(getPosition(this._container));
9466 var containerPos = map.layerPointToContainerPoint(layerPos),
9467 padding = toPoint(this.options.autoPanPadding),
9468 paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
9469 paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
9470 size = map.getSize(),
9474 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
9475 dx = containerPos.x + containerWidth - size.x + paddingBR.x;
9477 if (containerPos.x - dx - paddingTL.x < 0) { // left
9478 dx = containerPos.x - paddingTL.x;
9480 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
9481 dy = containerPos.y + containerHeight - size.y + paddingBR.y;
9483 if (containerPos.y - dy - paddingTL.y < 0) { // top
9484 dy = containerPos.y - paddingTL.y;
9488 // @section Popup events
9489 // @event autopanstart: Event
9490 // Fired when the map starts autopanning when opening a popup.
9493 .fire('autopanstart')
9498 _onCloseButtonClick: function (e) {
9503 _getAnchor: function () {
9504 // Where should we anchor the popup on the source layer?
9505 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
9511 // @factory L.popup(options?: Popup options, source?: Layer)
9512 // 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.
9513 var popup = function (options, source) {
9514 return new Popup(options, source);
9519 * @section Interaction Options
9520 * @option closePopupOnClick: Boolean = true
9521 * Set it to `false` if you don't want popups to close when user clicks the map.
9524 closePopupOnClick: true
9529 // @section Methods for Layers and Controls
9531 // @method openPopup(popup: Popup): this
9532 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
9534 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
9535 // Creates a popup with the specified content and options and opens it in the given point on a map.
9536 openPopup: function (popup, latlng, options) {
9537 if (!(popup instanceof Popup)) {
9538 popup = new Popup(options).setContent(popup);
9542 popup.setLatLng(latlng);
9545 if (this.hasLayer(popup)) {
9549 if (this._popup && this._popup.options.autoClose) {
9553 this._popup = popup;
9554 return this.addLayer(popup);
9557 // @method closePopup(popup?: Popup): this
9558 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
9559 closePopup: function (popup) {
9560 if (!popup || popup === this._popup) {
9561 popup = this._popup;
9565 this.removeLayer(popup);
9573 * @section Popup methods example
9575 * All layers share a set of methods convenient for binding popups to it.
9578 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
9579 * layer.openPopup();
9580 * layer.closePopup();
9583 * 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.
9586 // @section Popup methods
9589 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
9590 // Binds a popup to the layer with the passed `content` and sets up the
9591 // neccessary event listeners. If a `Function` is passed it will receive
9592 // the layer as the first argument and should return a `String` or `HTMLElement`.
9593 bindPopup: function (content, options) {
9595 if (content instanceof Popup) {
9596 setOptions(content, options);
9597 this._popup = content;
9598 content._source = this;
9600 if (!this._popup || options) {
9601 this._popup = new Popup(options, this);
9603 this._popup.setContent(content);
9606 if (!this._popupHandlersAdded) {
9608 click: this._openPopup,
9609 keypress: this._onKeyPress,
9610 remove: this.closePopup,
9611 move: this._movePopup
9613 this._popupHandlersAdded = true;
9619 // @method unbindPopup(): this
9620 // Removes the popup previously bound with `bindPopup`.
9621 unbindPopup: function () {
9624 click: this._openPopup,
9625 keypress: this._onKeyPress,
9626 remove: this.closePopup,
9627 move: this._movePopup
9629 this._popupHandlersAdded = false;
9635 // @method openPopup(latlng?: LatLng): this
9636 // Opens the bound popup at the specificed `latlng` or at the default popup anchor if no `latlng` is passed.
9637 openPopup: function (layer, latlng) {
9638 if (!(layer instanceof Layer)) {
9643 if (layer instanceof FeatureGroup) {
9644 for (var id in this._layers) {
9645 layer = this._layers[id];
9651 latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
9654 if (this._popup && this._map) {
9655 // set popup source to this layer
9656 this._popup._source = layer;
9658 // update the popup (content, layout, ect...)
9659 this._popup.update();
9661 // open the popup on the map
9662 this._map.openPopup(this._popup, latlng);
9668 // @method closePopup(): this
9669 // Closes the popup bound to this layer if it is open.
9670 closePopup: function () {
9672 this._popup._close();
9677 // @method togglePopup(): this
9678 // Opens or closes the popup bound to this layer depending on its current state.
9679 togglePopup: function (target) {
9681 if (this._popup._map) {
9684 this.openPopup(target);
9690 // @method isPopupOpen(): boolean
9691 // Returns `true` if the popup bound to this layer is currently open.
9692 isPopupOpen: function () {
9693 return (this._popup ? this._popup.isOpen() : false);
9696 // @method setPopupContent(content: String|HTMLElement|Popup): this
9697 // Sets the content of the popup bound to this layer.
9698 setPopupContent: function (content) {
9700 this._popup.setContent(content);
9705 // @method getPopup(): Popup
9706 // Returns the popup bound to this layer.
9707 getPopup: function () {
9711 _openPopup: function (e) {
9712 var layer = e.layer || e.target;
9722 // prevent map click
9725 // if this inherits from Path its a vector and we can just
9726 // open the popup at the new location
9727 if (layer instanceof Path) {
9728 this.openPopup(e.layer || e.target, e.latlng);
9732 // otherwise treat it like a marker and figure out
9733 // if we should toggle it open/closed
9734 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
9737 this.openPopup(layer, e.latlng);
9741 _movePopup: function (e) {
9742 this._popup.setLatLng(e.latlng);
9745 _onKeyPress: function (e) {
9746 if (e.originalEvent.keyCode === 13) {
9754 * @inherits DivOverlay
9756 * Used to display small texts on top of map layers.
9761 * marker.bindTooltip("my tooltip text").openTooltip();
9763 * Note about tooltip offset. Leaflet takes two options in consideration
9764 * for computing tooltip offseting:
9765 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
9766 * Add a positive x offset to move the tooltip to the right, and a positive y offset to
9767 * move it to the bottom. Negatives will move to the left and top.
9768 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
9769 * should adapt this value if you use a custom icon.
9773 // @namespace Tooltip
9774 var Tooltip = DivOverlay.extend({
9777 // @aka Tooltip options
9779 // @option pane: String = 'tooltipPane'
9780 // `Map pane` where the tooltip will be added.
9781 pane: 'tooltipPane',
9783 // @option offset: Point = Point(0, 0)
9784 // Optional offset of the tooltip position.
9787 // @option direction: String = 'auto'
9788 // Direction where to open the tooltip. Possible values are: `right`, `left`,
9789 // `top`, `bottom`, `center`, `auto`.
9790 // `auto` will dynamicaly switch between `right` and `left` according to the tooltip
9791 // position on the map.
9794 // @option permanent: Boolean = false
9795 // Whether to open the tooltip permanently or only on mouseover.
9798 // @option sticky: Boolean = false
9799 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
9802 // @option interactive: Boolean = false
9803 // If true, the tooltip will listen to the feature events.
9806 // @option opacity: Number = 0.9
9807 // Tooltip container opacity.
9811 onAdd: function (map) {
9812 DivOverlay.prototype.onAdd.call(this, map);
9813 this.setOpacity(this.options.opacity);
9816 // @section Tooltip events
9817 // @event tooltipopen: TooltipEvent
9818 // Fired when a tooltip is opened in the map.
9819 map.fire('tooltipopen', {tooltip: this});
9823 // @section Tooltip events
9824 // @event tooltipopen: TooltipEvent
9825 // Fired when a tooltip bound to this layer is opened.
9826 this._source.fire('tooltipopen', {tooltip: this}, true);
9830 onRemove: function (map) {
9831 DivOverlay.prototype.onRemove.call(this, map);
9834 // @section Tooltip events
9835 // @event tooltipclose: TooltipEvent
9836 // Fired when a tooltip in the map is closed.
9837 map.fire('tooltipclose', {tooltip: this});
9841 // @section Tooltip events
9842 // @event tooltipclose: TooltipEvent
9843 // Fired when a tooltip bound to this layer is closed.
9844 this._source.fire('tooltipclose', {tooltip: this}, true);
9848 getEvents: function () {
9849 var events = DivOverlay.prototype.getEvents.call(this);
9851 if (touch && !this.options.permanent) {
9852 events.preclick = this._close;
9858 _close: function () {
9860 this._map.closeTooltip(this);
9864 _initLayout: function () {
9865 var prefix = 'leaflet-tooltip',
9866 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
9868 this._contentNode = this._container = create$1('div', className);
9871 _updateLayout: function () {},
9873 _adjustPan: function () {},
9875 _setPosition: function (pos) {
9876 var map = this._map,
9877 container = this._container,
9878 centerPoint = map.latLngToContainerPoint(map.getCenter()),
9879 tooltipPoint = map.layerPointToContainerPoint(pos),
9880 direction = this.options.direction,
9881 tooltipWidth = container.offsetWidth,
9882 tooltipHeight = container.offsetHeight,
9883 offset = toPoint(this.options.offset),
9884 anchor = this._getAnchor();
9886 if (direction === 'top') {
9887 pos = pos.add(toPoint(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true));
9888 } else if (direction === 'bottom') {
9889 pos = pos.subtract(toPoint(tooltipWidth / 2 - offset.x, -offset.y, true));
9890 } else if (direction === 'center') {
9891 pos = pos.subtract(toPoint(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true));
9892 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
9893 direction = 'right';
9894 pos = pos.add(toPoint(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true));
9897 pos = pos.subtract(toPoint(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true));
9900 removeClass(container, 'leaflet-tooltip-right');
9901 removeClass(container, 'leaflet-tooltip-left');
9902 removeClass(container, 'leaflet-tooltip-top');
9903 removeClass(container, 'leaflet-tooltip-bottom');
9904 addClass(container, 'leaflet-tooltip-' + direction);
9905 setPosition(container, pos);
9908 _updatePosition: function () {
9909 var pos = this._map.latLngToLayerPoint(this._latlng);
9910 this._setPosition(pos);
9913 setOpacity: function (opacity) {
9914 this.options.opacity = opacity;
9916 if (this._container) {
9917 setOpacity(this._container, opacity);
9921 _animateZoom: function (e) {
9922 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
9923 this._setPosition(pos);
9926 _getAnchor: function () {
9927 // Where should we anchor the tooltip on the source layer?
9928 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
9933 // @namespace Tooltip
9934 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
9935 // 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.
9936 var tooltip = function (options, source) {
9937 return new Tooltip(options, source);
9941 // @section Methods for Layers and Controls
9944 // @method openTooltip(tooltip: Tooltip): this
9945 // Opens the specified tooltip.
9947 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
9948 // Creates a tooltip with the specified content and options and open it.
9949 openTooltip: function (tooltip, latlng, options) {
9950 if (!(tooltip instanceof Tooltip)) {
9951 tooltip = new Tooltip(options).setContent(tooltip);
9955 tooltip.setLatLng(latlng);
9958 if (this.hasLayer(tooltip)) {
9962 return this.addLayer(tooltip);
9965 // @method closeTooltip(tooltip?: Tooltip): this
9966 // Closes the tooltip given as parameter.
9967 closeTooltip: function (tooltip) {
9969 this.removeLayer(tooltip);
9978 * @section Tooltip methods example
9980 * All layers share a set of methods convenient for binding tooltips to it.
9983 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
9984 * layer.openTooltip();
9985 * layer.closeTooltip();
9989 // @section Tooltip methods
9992 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
9993 // Binds a tooltip to the layer with the passed `content` and sets up the
9994 // neccessary event listeners. If a `Function` is passed it will receive
9995 // the layer as the first argument and should return a `String` or `HTMLElement`.
9996 bindTooltip: function (content, options) {
9998 if (content instanceof Tooltip) {
9999 setOptions(content, options);
10000 this._tooltip = content;
10001 content._source = this;
10003 if (!this._tooltip || options) {
10004 this._tooltip = new Tooltip(options, this);
10006 this._tooltip.setContent(content);
10010 this._initTooltipInteractions();
10012 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
10013 this.openTooltip();
10019 // @method unbindTooltip(): this
10020 // Removes the tooltip previously bound with `bindTooltip`.
10021 unbindTooltip: function () {
10022 if (this._tooltip) {
10023 this._initTooltipInteractions(true);
10024 this.closeTooltip();
10025 this._tooltip = null;
10030 _initTooltipInteractions: function (remove$$1) {
10031 if (!remove$$1 && this._tooltipHandlersAdded) { return; }
10032 var onOff = remove$$1 ? 'off' : 'on',
10034 remove: this.closeTooltip,
10035 move: this._moveTooltip
10037 if (!this._tooltip.options.permanent) {
10038 events.mouseover = this._openTooltip;
10039 events.mouseout = this.closeTooltip;
10040 if (this._tooltip.options.sticky) {
10041 events.mousemove = this._moveTooltip;
10044 events.click = this._openTooltip;
10047 events.add = this._openTooltip;
10049 this[onOff](events);
10050 this._tooltipHandlersAdded = !remove$$1;
10053 // @method openTooltip(latlng?: LatLng): this
10054 // Opens the bound tooltip at the specificed `latlng` or at the default tooltip anchor if no `latlng` is passed.
10055 openTooltip: function (layer, latlng) {
10056 if (!(layer instanceof Layer)) {
10061 if (layer instanceof FeatureGroup) {
10062 for (var id in this._layers) {
10063 layer = this._layers[id];
10069 latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
10072 if (this._tooltip && this._map) {
10074 // set tooltip source to this layer
10075 this._tooltip._source = layer;
10077 // update the tooltip (content, layout, ect...)
10078 this._tooltip.update();
10080 // open the tooltip on the map
10081 this._map.openTooltip(this._tooltip, latlng);
10083 // Tooltip container may not be defined if not permanent and never
10085 if (this._tooltip.options.interactive && this._tooltip._container) {
10086 addClass(this._tooltip._container, 'leaflet-clickable');
10087 this.addInteractiveTarget(this._tooltip._container);
10094 // @method closeTooltip(): this
10095 // Closes the tooltip bound to this layer if it is open.
10096 closeTooltip: function () {
10097 if (this._tooltip) {
10098 this._tooltip._close();
10099 if (this._tooltip.options.interactive && this._tooltip._container) {
10100 removeClass(this._tooltip._container, 'leaflet-clickable');
10101 this.removeInteractiveTarget(this._tooltip._container);
10107 // @method toggleTooltip(): this
10108 // Opens or closes the tooltip bound to this layer depending on its current state.
10109 toggleTooltip: function (target) {
10110 if (this._tooltip) {
10111 if (this._tooltip._map) {
10112 this.closeTooltip();
10114 this.openTooltip(target);
10120 // @method isTooltipOpen(): boolean
10121 // Returns `true` if the tooltip bound to this layer is currently open.
10122 isTooltipOpen: function () {
10123 return this._tooltip.isOpen();
10126 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10127 // Sets the content of the tooltip bound to this layer.
10128 setTooltipContent: function (content) {
10129 if (this._tooltip) {
10130 this._tooltip.setContent(content);
10135 // @method getTooltip(): Tooltip
10136 // Returns the tooltip bound to this layer.
10137 getTooltip: function () {
10138 return this._tooltip;
10141 _openTooltip: function (e) {
10142 var layer = e.layer || e.target;
10144 if (!this._tooltip || !this._map) {
10147 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
10150 _moveTooltip: function (e) {
10151 var latlng = e.latlng, containerPoint, layerPoint;
10152 if (this._tooltip.options.sticky && e.originalEvent) {
10153 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
10154 layerPoint = this._map.containerPointToLayerPoint(containerPoint);
10155 latlng = this._map.layerPointToLatLng(layerPoint);
10157 this._tooltip.setLatLng(latlng);
10166 * Represents a lightweight icon for markers that uses a simple `<div>`
10167 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
10171 * var myIcon = L.divIcon({className: 'my-div-icon'});
10172 * // you can set .my-div-icon styles in CSS
10174 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
10177 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
10180 var DivIcon = Icon.extend({
10183 // @aka DivIcon options
10184 iconSize: [12, 12], // also can be set through CSS
10186 // iconAnchor: (Point),
10187 // popupAnchor: (Point),
10189 // @option html: String = ''
10190 // Custom HTML code to put inside the div element, empty by default.
10193 // @option bgPos: Point = [0, 0]
10194 // Optional relative position of the background, in pixels
10197 className: 'leaflet-div-icon'
10200 createIcon: function (oldIcon) {
10201 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
10202 options = this.options;
10204 div.innerHTML = options.html !== false ? options.html : '';
10206 if (options.bgPos) {
10207 var bgPos = toPoint(options.bgPos);
10208 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
10210 this._setIconStyles(div, 'icon');
10215 createShadow: function () {
10220 // @factory L.divIcon(options: DivIcon options)
10221 // Creates a `DivIcon` instance with the given options.
10222 function divIcon(options) {
10223 return new DivIcon(options);
10226 Icon.Default = IconDefault;
10233 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
10234 * 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.
10237 * @section Synchronous usage
10240 * 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.
10243 * var CanvasLayer = L.GridLayer.extend({
10244 * createTile: function(coords){
10245 * // create a <canvas> element for drawing
10246 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10248 * // setup tile width and height according to the options
10249 * var size = this.getTileSize();
10250 * tile.width = size.x;
10251 * tile.height = size.y;
10253 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z
10254 * var ctx = tile.getContext('2d');
10256 * // return the tile so it can be rendered on screen
10262 * @section Asynchronous usage
10265 * 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.
10268 * var CanvasLayer = L.GridLayer.extend({
10269 * createTile: function(coords, done){
10272 * // create a <canvas> element for drawing
10273 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10275 * // setup tile width and height according to the options
10276 * var size = this.getTileSize();
10277 * tile.width = size.x;
10278 * tile.height = size.y;
10280 * // draw something asynchronously and pass the tile to the done() callback
10281 * setTimeout(function() {
10282 * done(error, tile);
10294 var GridLayer = Layer.extend({
10297 // @aka GridLayer options
10299 // @option tileSize: Number|Point = 256
10300 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
10303 // @option opacity: Number = 1.0
10304 // Opacity of the tiles. Can be used in the `createTile()` function.
10307 // @option updateWhenIdle: Boolean = depends
10308 // If `false`, new tiles are loaded during panning, otherwise only after it (for better performance). `true` by default on mobile browsers, otherwise `false`.
10309 updateWhenIdle: mobile,
10311 // @option updateWhenZooming: Boolean = true
10312 // 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.
10313 updateWhenZooming: true,
10315 // @option updateInterval: Number = 200
10316 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
10317 updateInterval: 200,
10319 // @option zIndex: Number = 1
10320 // The explicit zIndex of the tile layer.
10323 // @option bounds: LatLngBounds = undefined
10324 // If set, tiles will only be loaded inside the set `LatLngBounds`.
10327 // @option minZoom: Number = 0
10328 // The minimum zoom level down to which this layer will be displayed (inclusive).
10331 // @option maxZoom: Number = undefined
10332 // The maximum zoom level up to which this layer will be displayed (inclusive).
10333 maxZoom: undefined,
10335 // @option maxNativeZoom: Number = undefined
10336 // Maximum zoom number the tile source has available. If it is specified,
10337 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
10338 // from `maxNativeZoom` level and auto-scaled.
10339 maxNativeZoom: undefined,
10341 // @option minNativeZoom: Number = undefined
10342 // Minimum zoom number the tile source has available. If it is specified,
10343 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
10344 // from `minNativeZoom` level and auto-scaled.
10345 minNativeZoom: undefined,
10347 // @option noWrap: Boolean = false
10348 // Whether the layer is wrapped around the antimeridian. If `true`, the
10349 // GridLayer will only be displayed once at low zoom levels. Has no
10350 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
10351 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
10352 // tiles outside the CRS limits.
10355 // @option pane: String = 'tilePane'
10356 // `Map pane` where the grid layer will be added.
10359 // @option className: String = ''
10360 // A custom class name to assign to the tile layer. Empty by default.
10363 // @option keepBuffer: Number = 2
10364 // When panning the map, keep this many rows and columns of tiles before unloading them.
10368 initialize: function (options) {
10369 setOptions(this, options);
10372 onAdd: function () {
10373 this._initContainer();
10382 beforeAdd: function (map) {
10383 map._addZoomLimit(this);
10386 onRemove: function (map) {
10387 this._removeAllTiles();
10388 remove(this._container);
10389 map._removeZoomLimit(this);
10390 this._container = null;
10391 this._tileZoom = null;
10394 // @method bringToFront: this
10395 // Brings the tile layer to the top of all tile layers.
10396 bringToFront: function () {
10398 toFront(this._container);
10399 this._setAutoZIndex(Math.max);
10404 // @method bringToBack: this
10405 // Brings the tile layer to the bottom of all tile layers.
10406 bringToBack: function () {
10408 toBack(this._container);
10409 this._setAutoZIndex(Math.min);
10414 // @method getContainer: HTMLElement
10415 // Returns the HTML element that contains the tiles for this layer.
10416 getContainer: function () {
10417 return this._container;
10420 // @method setOpacity(opacity: Number): this
10421 // Changes the [opacity](#gridlayer-opacity) of the grid layer.
10422 setOpacity: function (opacity) {
10423 this.options.opacity = opacity;
10424 this._updateOpacity();
10428 // @method setZIndex(zIndex: Number): this
10429 // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
10430 setZIndex: function (zIndex) {
10431 this.options.zIndex = zIndex;
10432 this._updateZIndex();
10437 // @method isLoading: Boolean
10438 // Returns `true` if any tile in the grid layer has not finished loading.
10439 isLoading: function () {
10440 return this._loading;
10443 // @method redraw: this
10444 // Causes the layer to clear all the tiles and request them again.
10445 redraw: function () {
10447 this._removeAllTiles();
10453 getEvents: function () {
10455 viewprereset: this._invalidateAll,
10456 viewreset: this._resetView,
10457 zoom: this._resetView,
10458 moveend: this._onMoveEnd
10461 if (!this.options.updateWhenIdle) {
10462 // update tiles on move, but not more often than once per given interval
10463 if (!this._onMove) {
10464 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
10467 events.move = this._onMove;
10470 if (this._zoomAnimated) {
10471 events.zoomanim = this._animateZoom;
10477 // @section Extension methods
10478 // Layers extending `GridLayer` shall reimplement the following method.
10479 // @method createTile(coords: Object, done?: Function): HTMLElement
10480 // Called only internally, must be overriden by classes extending `GridLayer`.
10481 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
10482 // is specified, it must be called when the tile has finished loading and drawing.
10483 createTile: function () {
10484 return document.createElement('div');
10488 // @method getTileSize: Point
10489 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
10490 getTileSize: function () {
10491 var s = this.options.tileSize;
10492 return s instanceof Point ? s : new Point(s, s);
10495 _updateZIndex: function () {
10496 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
10497 this._container.style.zIndex = this.options.zIndex;
10501 _setAutoZIndex: function (compare) {
10502 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
10504 var layers = this.getPane().children,
10505 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
10507 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
10509 zIndex = layers[i].style.zIndex;
10511 if (layers[i] !== this._container && zIndex) {
10512 edgeZIndex = compare(edgeZIndex, +zIndex);
10516 if (isFinite(edgeZIndex)) {
10517 this.options.zIndex = edgeZIndex + compare(-1, 1);
10518 this._updateZIndex();
10522 _updateOpacity: function () {
10523 if (!this._map) { return; }
10525 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
10526 if (ielt9) { return; }
10528 setOpacity(this._container, this.options.opacity);
10530 var now = +new Date(),
10534 for (var key in this._tiles) {
10535 var tile = this._tiles[key];
10536 if (!tile.current || !tile.loaded) { continue; }
10538 var fade = Math.min(1, (now - tile.loaded) / 200);
10540 setOpacity(tile.el, fade);
10547 this._onOpaqueTile(tile);
10549 tile.active = true;
10553 if (willPrune && !this._noPrune) { this._pruneTiles(); }
10556 cancelAnimFrame(this._fadeFrame);
10557 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
10561 _onOpaqueTile: falseFn,
10563 _initContainer: function () {
10564 if (this._container) { return; }
10566 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
10567 this._updateZIndex();
10569 if (this.options.opacity < 1) {
10570 this._updateOpacity();
10573 this.getPane().appendChild(this._container);
10576 _updateLevels: function () {
10578 var zoom = this._tileZoom,
10579 maxZoom = this.options.maxZoom;
10581 if (zoom === undefined) { return undefined; }
10583 for (var z in this._levels) {
10584 if (this._levels[z].el.children.length || z === zoom) {
10585 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
10586 this._onUpdateLevel(z);
10588 remove(this._levels[z].el);
10589 this._removeTilesAtZoom(z);
10590 this._onRemoveLevel(z);
10591 delete this._levels[z];
10595 var level = this._levels[zoom],
10599 level = this._levels[zoom] = {};
10601 level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
10602 level.el.style.zIndex = maxZoom;
10604 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
10607 this._setZoomTransform(level, map.getCenter(), map.getZoom());
10609 // force the browser to consider the newly added element for transition
10610 falseFn(level.el.offsetWidth);
10612 this._onCreateLevel(level);
10615 this._level = level;
10620 _onUpdateLevel: falseFn,
10622 _onRemoveLevel: falseFn,
10624 _onCreateLevel: falseFn,
10626 _pruneTiles: function () {
10633 var zoom = this._map.getZoom();
10634 if (zoom > this.options.maxZoom ||
10635 zoom < this.options.minZoom) {
10636 this._removeAllTiles();
10640 for (key in this._tiles) {
10641 tile = this._tiles[key];
10642 tile.retain = tile.current;
10645 for (key in this._tiles) {
10646 tile = this._tiles[key];
10647 if (tile.current && !tile.active) {
10648 var coords = tile.coords;
10649 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
10650 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
10655 for (key in this._tiles) {
10656 if (!this._tiles[key].retain) {
10657 this._removeTile(key);
10662 _removeTilesAtZoom: function (zoom) {
10663 for (var key in this._tiles) {
10664 if (this._tiles[key].coords.z !== zoom) {
10667 this._removeTile(key);
10671 _removeAllTiles: function () {
10672 for (var key in this._tiles) {
10673 this._removeTile(key);
10677 _invalidateAll: function () {
10678 for (var z in this._levels) {
10679 remove(this._levels[z].el);
10680 this._onRemoveLevel(z);
10681 delete this._levels[z];
10683 this._removeAllTiles();
10685 this._tileZoom = null;
10688 _retainParent: function (x, y, z, minZoom) {
10689 var x2 = Math.floor(x / 2),
10690 y2 = Math.floor(y / 2),
10692 coords2 = new Point(+x2, +y2);
10695 var key = this._tileCoordsToKey(coords2),
10696 tile = this._tiles[key];
10698 if (tile && tile.active) {
10699 tile.retain = true;
10702 } else if (tile && tile.loaded) {
10703 tile.retain = true;
10706 if (z2 > minZoom) {
10707 return this._retainParent(x2, y2, z2, minZoom);
10713 _retainChildren: function (x, y, z, maxZoom) {
10715 for (var i = 2 * x; i < 2 * x + 2; i++) {
10716 for (var j = 2 * y; j < 2 * y + 2; j++) {
10718 var coords = new Point(i, j);
10721 var key = this._tileCoordsToKey(coords),
10722 tile = this._tiles[key];
10724 if (tile && tile.active) {
10725 tile.retain = true;
10728 } else if (tile && tile.loaded) {
10729 tile.retain = true;
10732 if (z + 1 < maxZoom) {
10733 this._retainChildren(i, j, z + 1, maxZoom);
10739 _resetView: function (e) {
10740 var animating = e && (e.pinch || e.flyTo);
10741 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
10744 _animateZoom: function (e) {
10745 this._setView(e.center, e.zoom, true, e.noUpdate);
10748 _clampZoom: function (zoom) {
10749 var options = this.options;
10751 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
10752 return options.minNativeZoom;
10755 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
10756 return options.maxNativeZoom;
10762 _setView: function (center, zoom, noPrune, noUpdate) {
10763 var tileZoom = this._clampZoom(Math.round(zoom));
10764 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
10765 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
10766 tileZoom = undefined;
10769 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
10771 if (!noUpdate || tileZoomChanged) {
10773 this._tileZoom = tileZoom;
10775 if (this._abortLoading) {
10776 this._abortLoading();
10779 this._updateLevels();
10782 if (tileZoom !== undefined) {
10783 this._update(center);
10787 this._pruneTiles();
10790 // Flag to prevent _updateOpacity from pruning tiles during
10791 // a zoom anim or a pinch gesture
10792 this._noPrune = !!noPrune;
10795 this._setZoomTransforms(center, zoom);
10798 _setZoomTransforms: function (center, zoom) {
10799 for (var i in this._levels) {
10800 this._setZoomTransform(this._levels[i], center, zoom);
10804 _setZoomTransform: function (level, center, zoom) {
10805 var scale = this._map.getZoomScale(zoom, level.zoom),
10806 translate = level.origin.multiplyBy(scale)
10807 .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
10810 setTransform(level.el, translate, scale);
10812 setPosition(level.el, translate);
10816 _resetGrid: function () {
10817 var map = this._map,
10818 crs = map.options.crs,
10819 tileSize = this._tileSize = this.getTileSize(),
10820 tileZoom = this._tileZoom;
10822 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
10824 this._globalTileRange = this._pxBoundsToTileRange(bounds);
10827 this._wrapX = crs.wrapLng && !this.options.noWrap && [
10828 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
10829 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
10831 this._wrapY = crs.wrapLat && !this.options.noWrap && [
10832 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
10833 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
10837 _onMoveEnd: function () {
10838 if (!this._map || this._map._animatingZoom) { return; }
10843 _getTiledPixelBounds: function (center) {
10844 var map = this._map,
10845 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
10846 scale = map.getZoomScale(mapZoom, this._tileZoom),
10847 pixelCenter = map.project(center, this._tileZoom).floor(),
10848 halfSize = map.getSize().divideBy(scale * 2);
10850 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
10853 // Private method to load tiles in the grid's active zoom level according to map bounds
10854 _update: function (center) {
10855 var map = this._map;
10856 if (!map) { return; }
10857 var zoom = this._clampZoom(map.getZoom());
10859 if (center === undefined) { center = map.getCenter(); }
10860 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom
10862 var pixelBounds = this._getTiledPixelBounds(center),
10863 tileRange = this._pxBoundsToTileRange(pixelBounds),
10864 tileCenter = tileRange.getCenter(),
10866 margin = this.options.keepBuffer,
10867 noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
10868 tileRange.getTopRight().add([margin, -margin]));
10870 // Sanity check: panic if the tile range contains Infinity somewhere.
10871 if (!(isFinite(tileRange.min.x) &&
10872 isFinite(tileRange.min.y) &&
10873 isFinite(tileRange.max.x) &&
10874 isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
10876 for (var key in this._tiles) {
10877 var c = this._tiles[key].coords;
10878 if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
10879 this._tiles[key].current = false;
10883 // _update just loads more tiles. If the tile zoom level differs too much
10884 // from the map's, let _setView reset levels and prune old tiles.
10885 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
10887 // create a queue of coordinates to load tiles from
10888 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
10889 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
10890 var coords = new Point(i, j);
10891 coords.z = this._tileZoom;
10893 if (!this._isValidTile(coords)) { continue; }
10895 if (!this._tiles[this._tileCoordsToKey(coords)]) {
10896 queue.push(coords);
10901 // sort tile queue to load tiles in order of their distance to center
10902 queue.sort(function (a, b) {
10903 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
10906 if (queue.length !== 0) {
10907 // if it's the first batch of tiles to load
10908 if (!this._loading) {
10909 this._loading = true;
10910 // @event loading: Event
10911 // Fired when the grid layer starts loading tiles.
10912 this.fire('loading');
10915 // create DOM fragment to append tiles in one batch
10916 var fragment = document.createDocumentFragment();
10918 for (i = 0; i < queue.length; i++) {
10919 this._addTile(queue[i], fragment);
10922 this._level.el.appendChild(fragment);
10926 _isValidTile: function (coords) {
10927 var crs = this._map.options.crs;
10929 if (!crs.infinite) {
10930 // don't load tile if it's out of bounds and not wrapped
10931 var bounds = this._globalTileRange;
10932 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
10933 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
10936 if (!this.options.bounds) { return true; }
10938 // don't load tile if it doesn't intersect the bounds in options
10939 var tileBounds = this._tileCoordsToBounds(coords);
10940 return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
10943 _keyToBounds: function (key) {
10944 return this._tileCoordsToBounds(this._keyToTileCoords(key));
10947 // converts tile coordinates to its geographical bounds
10948 _tileCoordsToBounds: function (coords) {
10950 var map = this._map,
10951 tileSize = this.getTileSize(),
10953 nwPoint = coords.scaleBy(tileSize),
10954 sePoint = nwPoint.add(tileSize),
10956 nw = map.unproject(nwPoint, coords.z),
10957 se = map.unproject(sePoint, coords.z),
10958 bounds = new LatLngBounds(nw, se);
10960 if (!this.options.noWrap) {
10961 map.wrapLatLngBounds(bounds);
10967 // converts tile coordinates to key for the tile cache
10968 _tileCoordsToKey: function (coords) {
10969 return coords.x + ':' + coords.y + ':' + coords.z;
10972 // converts tile cache key to coordinates
10973 _keyToTileCoords: function (key) {
10974 var k = key.split(':'),
10975 coords = new Point(+k[0], +k[1]);
10980 _removeTile: function (key) {
10981 var tile = this._tiles[key];
10982 if (!tile) { return; }
10986 delete this._tiles[key];
10988 // @event tileunload: TileEvent
10989 // Fired when a tile is removed (e.g. when a tile goes off the screen).
10990 this.fire('tileunload', {
10992 coords: this._keyToTileCoords(key)
10996 _initTile: function (tile) {
10997 addClass(tile, 'leaflet-tile');
10999 var tileSize = this.getTileSize();
11000 tile.style.width = tileSize.x + 'px';
11001 tile.style.height = tileSize.y + 'px';
11003 tile.onselectstart = falseFn;
11004 tile.onmousemove = falseFn;
11006 // update opacity on tiles in IE7-8 because of filter inheritance problems
11007 if (ielt9 && this.options.opacity < 1) {
11008 setOpacity(tile, this.options.opacity);
11011 // without this hack, tiles disappear after zoom on Chrome for Android
11012 // https://github.com/Leaflet/Leaflet/issues/2078
11013 if (android && !android23) {
11014 tile.style.WebkitBackfaceVisibility = 'hidden';
11018 _addTile: function (coords, container) {
11019 var tilePos = this._getTilePos(coords),
11020 key = this._tileCoordsToKey(coords);
11022 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
11024 this._initTile(tile);
11026 // if createTile is defined with a second argument ("done" callback),
11027 // we know that tile is async and will be ready later; otherwise
11028 if (this.createTile.length < 2) {
11029 // mark tile as ready, but delay one frame for opacity animation to happen
11030 requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
11033 setPosition(tile, tilePos);
11035 // save tile in cache
11036 this._tiles[key] = {
11042 container.appendChild(tile);
11043 // @event tileloadstart: TileEvent
11044 // Fired when a tile is requested and starts loading.
11045 this.fire('tileloadstart', {
11051 _tileReady: function (coords, err, tile) {
11052 if (!this._map) { return; }
11055 // @event tileerror: TileErrorEvent
11056 // Fired when there is an error loading a tile.
11057 this.fire('tileerror', {
11064 var key = this._tileCoordsToKey(coords);
11066 tile = this._tiles[key];
11067 if (!tile) { return; }
11069 tile.loaded = +new Date();
11070 if (this._map._fadeAnimated) {
11071 setOpacity(tile.el, 0);
11072 cancelAnimFrame(this._fadeFrame);
11073 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11075 tile.active = true;
11076 this._pruneTiles();
11080 addClass(tile.el, 'leaflet-tile-loaded');
11082 // @event tileload: TileEvent
11083 // Fired when a tile loads.
11084 this.fire('tileload', {
11090 if (this._noTilesToLoad()) {
11091 this._loading = false;
11092 // @event load: Event
11093 // Fired when the grid layer loaded all visible tiles.
11096 if (ielt9 || !this._map._fadeAnimated) {
11097 requestAnimFrame(this._pruneTiles, this);
11099 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
11100 // to trigger a pruning.
11101 setTimeout(bind(this._pruneTiles, this), 250);
11106 _getTilePos: function (coords) {
11107 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
11110 _wrapCoords: function (coords) {
11111 var newCoords = new Point(
11112 this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
11113 this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
11114 newCoords.z = coords.z;
11118 _pxBoundsToTileRange: function (bounds) {
11119 var tileSize = this.getTileSize();
11121 bounds.min.unscaleBy(tileSize).floor(),
11122 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
11125 _noTilesToLoad: function () {
11126 for (var key in this._tiles) {
11127 if (!this._tiles[key].loaded) { return false; }
11133 // @factory L.gridLayer(options?: GridLayer options)
11134 // Creates a new instance of GridLayer with the supplied options.
11135 function gridLayer(options) {
11136 return new GridLayer(options);
11141 * @inherits GridLayer
11143 * Used to load and display tile layers on the map. Extends `GridLayer`.
11148 * L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map);
11151 * @section URL template
11154 * A string of the following form:
11157 * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
11160 * `{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.
11162 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
11165 * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
11170 var TileLayer = GridLayer.extend({
11173 // @aka TileLayer options
11175 // @option minZoom: Number = 0
11176 // The minimum zoom level down to which this layer will be displayed (inclusive).
11179 // @option maxZoom: Number = 18
11180 // The maximum zoom level up to which this layer will be displayed (inclusive).
11183 // @option subdomains: String|String[] = 'abc'
11184 // 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.
11187 // @option errorTileUrl: String = ''
11188 // URL to the tile image to show in place of the tile that failed to load.
11191 // @option zoomOffset: Number = 0
11192 // The zoom number used in tile URLs will be offset with this value.
11195 // @option tms: Boolean = false
11196 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
11199 // @option zoomReverse: Boolean = false
11200 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
11201 zoomReverse: false,
11203 // @option detectRetina: Boolean = false
11204 // 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.
11205 detectRetina: false,
11207 // @option crossOrigin: Boolean = false
11208 // If true, all tiles will have their crossOrigin attribute set to ''. This is needed if you want to access tile pixel data.
11212 initialize: function (url, options) {
11216 options = setOptions(this, options);
11218 // detecting retina displays, adjusting tileSize and zoom levels
11219 if (options.detectRetina && retina && options.maxZoom > 0) {
11221 options.tileSize = Math.floor(options.tileSize / 2);
11223 if (!options.zoomReverse) {
11224 options.zoomOffset++;
11227 options.zoomOffset--;
11231 options.minZoom = Math.max(0, options.minZoom);
11234 if (typeof options.subdomains === 'string') {
11235 options.subdomains = options.subdomains.split('');
11238 // for https://github.com/Leaflet/Leaflet/issues/137
11240 this.on('tileunload', this._onTileRemove);
11244 // @method setUrl(url: String, noRedraw?: Boolean): this
11245 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
11246 setUrl: function (url, noRedraw) {
11255 // @method createTile(coords: Object, done?: Function): HTMLElement
11256 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
11257 // to return an `<img>` HTML element with the appropiate image URL given `coords`. The `done`
11258 // callback is called when the tile has been loaded.
11259 createTile: function (coords, done) {
11260 var tile = document.createElement('img');
11262 on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
11263 on(tile, 'error', bind(this._tileOnError, this, done, tile));
11265 if (this.options.crossOrigin) {
11266 tile.crossOrigin = '';
11270 Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
11271 http://www.w3.org/TR/WCAG20-TECHS/H67
11276 Set role="presentation" to force screen readers to ignore this
11277 https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
11279 tile.setAttribute('role', 'presentation');
11281 tile.src = this.getTileUrl(coords);
11286 // @section Extension methods
11288 // Layers extending `TileLayer` might reimplement the following method.
11289 // @method getTileUrl(coords: Object): String
11290 // Called only internally, returns the URL for a tile given its coordinates.
11291 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
11292 getTileUrl: function (coords) {
11294 r: retina ? '@2x' : '',
11295 s: this._getSubdomain(coords),
11298 z: this._getZoomForUrl()
11300 if (this._map && !this._map.options.crs.infinite) {
11301 var invertedY = this._globalTileRange.max.y - coords.y;
11302 if (this.options.tms) {
11303 data['y'] = invertedY;
11305 data['-y'] = invertedY;
11308 return template(this._url, extend(data, this.options));
11311 _tileOnLoad: function (done, tile) {
11312 // For https://github.com/Leaflet/Leaflet/issues/3332
11314 setTimeout(bind(done, this, null, tile), 0);
11320 _tileOnError: function (done, tile, e) {
11321 var errorUrl = this.options.errorTileUrl;
11322 if (errorUrl && tile.src !== errorUrl) {
11323 tile.src = errorUrl;
11328 _onTileRemove: function (e) {
11329 e.tile.onload = null;
11332 _getZoomForUrl: function () {
11333 var zoom = this._tileZoom,
11334 maxZoom = this.options.maxZoom,
11335 zoomReverse = this.options.zoomReverse,
11336 zoomOffset = this.options.zoomOffset;
11339 zoom = maxZoom - zoom;
11342 return zoom + zoomOffset;
11345 _getSubdomain: function (tilePoint) {
11346 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
11347 return this.options.subdomains[index];
11350 // stops loading all tiles in the background layer
11351 _abortLoading: function () {
11353 for (i in this._tiles) {
11354 if (this._tiles[i].coords.z !== this._tileZoom) {
11355 tile = this._tiles[i].el;
11357 tile.onload = falseFn;
11358 tile.onerror = falseFn;
11360 if (!tile.complete) {
11361 tile.src = emptyImageUrl;
11370 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
11371 // Instantiates a tile layer object given a `URL template` and optionally an options object.
11373 function tileLayer(url, options) {
11374 return new TileLayer(url, options);
11378 * @class TileLayer.WMS
11379 * @inherits TileLayer
11380 * @aka L.TileLayer.WMS
11381 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
11386 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
11387 * layers: 'nexrad-n0r-900913',
11388 * format: 'image/png',
11389 * transparent: true,
11390 * attribution: "Weather data © 2012 IEM Nexrad"
11395 var TileLayerWMS = TileLayer.extend({
11398 // @aka TileLayer.WMS options
11399 // If any custom options not documented here are used, they will be sent to the
11400 // WMS server as extra parameters in each request URL. This can be useful for
11401 // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
11402 defaultWmsParams: {
11406 // @option layers: String = ''
11407 // **(required)** Comma-separated list of WMS layers to show.
11410 // @option styles: String = ''
11411 // Comma-separated list of WMS styles.
11414 // @option format: String = 'image/jpeg'
11415 // WMS image format (use `'image/png'` for layers with transparency).
11416 format: 'image/jpeg',
11418 // @option transparent: Boolean = false
11419 // If `true`, the WMS service will return images with transparency.
11420 transparent: false,
11422 // @option version: String = '1.1.1'
11423 // Version of the WMS service to use
11428 // @option crs: CRS = null
11429 // Coordinate Reference System to use for the WMS requests, defaults to
11430 // map CRS. Don't change this if you're not sure what it means.
11433 // @option uppercase: Boolean = false
11434 // If `true`, WMS request parameter keys will be uppercase.
11438 initialize: function (url, options) {
11442 var wmsParams = extend({}, this.defaultWmsParams);
11444 // all keys that are not TileLayer options go to WMS params
11445 for (var i in options) {
11446 if (!(i in this.options)) {
11447 wmsParams[i] = options[i];
11451 options = setOptions(this, options);
11453 wmsParams.width = wmsParams.height = options.tileSize * (options.detectRetina && retina ? 2 : 1);
11455 this.wmsParams = wmsParams;
11458 onAdd: function (map) {
11460 this._crs = this.options.crs || map.options.crs;
11461 this._wmsVersion = parseFloat(this.wmsParams.version);
11463 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
11464 this.wmsParams[projectionKey] = this._crs.code;
11466 TileLayer.prototype.onAdd.call(this, map);
11469 getTileUrl: function (coords) {
11471 var tileBounds = this._tileCoordsToBounds(coords),
11472 nw = this._crs.project(tileBounds.getNorthWest()),
11473 se = this._crs.project(tileBounds.getSouthEast()),
11475 bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
11476 [se.y, nw.x, nw.y, se.x] :
11477 [nw.x, se.y, se.x, nw.y]).join(','),
11479 url = TileLayer.prototype.getTileUrl.call(this, coords);
11482 getParamString(this.wmsParams, url, this.options.uppercase) +
11483 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
11486 // @method setParams(params: Object, noRedraw?: Boolean): this
11487 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
11488 setParams: function (params, noRedraw) {
11490 extend(this.wmsParams, params);
11501 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
11502 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
11503 function tileLayerWMS(url, options) {
11504 return new TileLayerWMS(url, options);
11507 TileLayer.WMS = TileLayerWMS;
11508 tileLayer.wms = tileLayerWMS;
11515 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
11516 * DOM container of the renderer, its bounds, and its zoom animation.
11518 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
11519 * itself can be added or removed to the map. All paths use a renderer, which can
11520 * be implicit (the map will decide the type of renderer and use it automatically)
11521 * or explicit (using the [`renderer`](#path-renderer) option of the path).
11523 * Do not use this class directly, use `SVG` and `Canvas` instead.
11525 * @event update: Event
11526 * Fired when the renderer updates its bounds, center and zoom, for example when
11527 * its map has moved
11530 var Renderer = Layer.extend({
11533 // @aka Renderer options
11535 // @option padding: Number = 0.1
11536 // How much to extend the clip area around the map view (relative to its size)
11537 // e.g. 0.1 would be 10% of map view in each direction
11541 initialize: function (options) {
11542 setOptions(this, options);
11544 this._layers = this._layers || {};
11547 onAdd: function () {
11548 if (!this._container) {
11549 this._initContainer(); // defined by renderer implementations
11551 if (this._zoomAnimated) {
11552 addClass(this._container, 'leaflet-zoom-animated');
11556 this.getPane().appendChild(this._container);
11558 this.on('update', this._updatePaths, this);
11561 onRemove: function () {
11562 this.off('update', this._updatePaths, this);
11563 this._destroyContainer();
11566 getEvents: function () {
11568 viewreset: this._reset,
11569 zoom: this._onZoom,
11570 moveend: this._update,
11571 zoomend: this._onZoomEnd
11573 if (this._zoomAnimated) {
11574 events.zoomanim = this._onAnimZoom;
11579 _onAnimZoom: function (ev) {
11580 this._updateTransform(ev.center, ev.zoom);
11583 _onZoom: function () {
11584 this._updateTransform(this._map.getCenter(), this._map.getZoom());
11587 _updateTransform: function (center, zoom) {
11588 var scale = this._map.getZoomScale(zoom, this._zoom),
11589 position = getPosition(this._container),
11590 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
11591 currentCenterPoint = this._map.project(this._center, zoom),
11592 destCenterPoint = this._map.project(center, zoom),
11593 centerOffset = destCenterPoint.subtract(currentCenterPoint),
11595 topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
11598 setTransform(this._container, topLeftOffset, scale);
11600 setPosition(this._container, topLeftOffset);
11604 _reset: function () {
11606 this._updateTransform(this._center, this._zoom);
11608 for (var id in this._layers) {
11609 this._layers[id]._reset();
11613 _onZoomEnd: function () {
11614 for (var id in this._layers) {
11615 this._layers[id]._project();
11619 _updatePaths: function () {
11620 for (var id in this._layers) {
11621 this._layers[id]._update();
11625 _update: function () {
11626 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
11627 // Subclasses are responsible of firing the 'update' event.
11628 var p = this.options.padding,
11629 size = this._map.getSize(),
11630 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
11632 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
11634 this._center = this._map.getCenter();
11635 this._zoom = this._map.getZoom();
11641 * @inherits Renderer
11644 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
11645 * Inherits `Renderer`.
11647 * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
11648 * available in all web browsers, notably IE8, and overlapping geometries might
11649 * not display properly in some edge cases.
11653 * Use Canvas by default for all paths in the map:
11656 * var map = L.map('map', {
11657 * renderer: L.canvas()
11661 * Use a Canvas renderer with extra padding for specific vector geometries:
11664 * var map = L.map('map');
11665 * var myRenderer = L.canvas({ padding: 0.5 });
11666 * var line = L.polyline( coordinates, { renderer: myRenderer } );
11667 * var circle = L.circle( center, { renderer: myRenderer } );
11671 var Canvas = Renderer.extend({
11672 getEvents: function () {
11673 var events = Renderer.prototype.getEvents.call(this);
11674 events.viewprereset = this._onViewPreReset;
11678 _onViewPreReset: function () {
11679 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
11680 this._postponeUpdatePaths = true;
11683 onAdd: function () {
11684 Renderer.prototype.onAdd.call(this);
11686 // Redraw vectors since canvas is cleared upon removal,
11687 // in case of removing the renderer itself from the map.
11691 _initContainer: function () {
11692 var container = this._container = document.createElement('canvas');
11694 on(container, 'mousemove', throttle(this._onMouseMove, 32, this), this);
11695 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
11696 on(container, 'mouseout', this._handleMouseOut, this);
11698 this._ctx = container.getContext('2d');
11701 _destroyContainer: function () {
11703 remove(this._container);
11704 off(this._container);
11705 delete this._container;
11708 _updatePaths: function () {
11709 if (this._postponeUpdatePaths) { return; }
11712 this._redrawBounds = null;
11713 for (var id in this._layers) {
11714 layer = this._layers[id];
11720 _update: function () {
11721 if (this._map._animatingZoom && this._bounds) { return; }
11723 this._drawnLayers = {};
11725 Renderer.prototype._update.call(this);
11727 var b = this._bounds,
11728 container = this._container,
11729 size = b.getSize(),
11730 m = retina ? 2 : 1;
11732 setPosition(container, b.min);
11734 // set canvas size (also clearing it); use double size on retina
11735 container.width = m * size.x;
11736 container.height = m * size.y;
11737 container.style.width = size.x + 'px';
11738 container.style.height = size.y + 'px';
11741 this._ctx.scale(2, 2);
11744 // translate so we use the same path coordinates after canvas element moves
11745 this._ctx.translate(-b.min.x, -b.min.y);
11747 // Tell paths to redraw themselves
11748 this.fire('update');
11751 _reset: function () {
11752 Renderer.prototype._reset.call(this);
11754 if (this._postponeUpdatePaths) {
11755 this._postponeUpdatePaths = false;
11756 this._updatePaths();
11760 _initPath: function (layer) {
11761 this._updateDashArray(layer);
11762 this._layers[stamp(layer)] = layer;
11764 var order = layer._order = {
11766 prev: this._drawLast,
11769 if (this._drawLast) { this._drawLast.next = order; }
11770 this._drawLast = order;
11771 this._drawFirst = this._drawFirst || this._drawLast;
11774 _addPath: function (layer) {
11775 this._requestRedraw(layer);
11778 _removePath: function (layer) {
11779 var order = layer._order;
11780 var next = order.next;
11781 var prev = order.prev;
11786 this._drawLast = prev;
11791 this._drawFirst = next;
11794 delete layer._order;
11796 delete this._layers[L.stamp(layer)];
11798 this._requestRedraw(layer);
11801 _updatePath: function (layer) {
11802 // Redraw the union of the layer's old pixel
11803 // bounds and the new pixel bounds.
11804 this._extendRedrawBounds(layer);
11807 // The redraw will extend the redraw bounds
11808 // with the new pixel bounds.
11809 this._requestRedraw(layer);
11812 _updateStyle: function (layer) {
11813 this._updateDashArray(layer);
11814 this._requestRedraw(layer);
11817 _updateDashArray: function (layer) {
11818 if (layer.options.dashArray) {
11819 var parts = layer.options.dashArray.split(','),
11822 for (i = 0; i < parts.length; i++) {
11823 dashArray.push(Number(parts[i]));
11825 layer.options._dashArray = dashArray;
11829 _requestRedraw: function (layer) {
11830 if (!this._map) { return; }
11832 this._extendRedrawBounds(layer);
11833 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
11836 _extendRedrawBounds: function (layer) {
11837 if (layer._pxBounds) {
11838 var padding = (layer.options.weight || 0) + 1;
11839 this._redrawBounds = this._redrawBounds || new Bounds();
11840 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
11841 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
11845 _redraw: function () {
11846 this._redrawRequest = null;
11848 if (this._redrawBounds) {
11849 this._redrawBounds.min._floor();
11850 this._redrawBounds.max._ceil();
11853 this._clear(); // clear layers in redraw bounds
11854 this._draw(); // draw layers
11856 this._redrawBounds = null;
11859 _clear: function () {
11860 var bounds = this._redrawBounds;
11862 var size = bounds.getSize();
11863 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
11865 this._ctx.clearRect(0, 0, this._container.width, this._container.height);
11869 _draw: function () {
11870 var layer, bounds = this._redrawBounds;
11873 var size = bounds.getSize();
11874 this._ctx.beginPath();
11875 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
11879 this._drawing = true;
11881 for (var order = this._drawFirst; order; order = order.next) {
11882 layer = order.layer;
11883 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
11884 layer._updatePath();
11888 this._drawing = false;
11890 this._ctx.restore(); // Restore state before clipping.
11893 _updatePoly: function (layer, closed) {
11894 if (!this._drawing) { return; }
11897 parts = layer._parts,
11898 len = parts.length,
11901 if (!len) { return; }
11903 this._drawnLayers[layer._leaflet_id] = layer;
11907 for (i = 0; i < len; i++) {
11908 for (j = 0, len2 = parts[i].length; j < len2; j++) {
11910 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
11917 this._fillStroke(ctx, layer);
11919 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
11922 _updateCircle: function (layer) {
11924 if (!this._drawing || layer._empty()) { return; }
11926 var p = layer._point,
11929 s = (layer._radiusY || r) / r;
11931 this._drawnLayers[layer._leaflet_id] = layer;
11939 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
11945 this._fillStroke(ctx, layer);
11948 _fillStroke: function (ctx, layer) {
11949 var options = layer.options;
11951 if (options.fill) {
11952 ctx.globalAlpha = options.fillOpacity;
11953 ctx.fillStyle = options.fillColor || options.color;
11954 ctx.fill(options.fillRule || 'evenodd');
11957 if (options.stroke && options.weight !== 0) {
11958 if (ctx.setLineDash) {
11959 ctx.setLineDash(layer.options && layer.options._dashArray || []);
11961 ctx.globalAlpha = options.opacity;
11962 ctx.lineWidth = options.weight;
11963 ctx.strokeStyle = options.color;
11964 ctx.lineCap = options.lineCap;
11965 ctx.lineJoin = options.lineJoin;
11970 // Canvas obviously doesn't have mouse events for individual drawn objects,
11971 // so we emulate that by calculating what's under the mouse on mousemove/click manually
11973 _onClick: function (e) {
11974 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
11976 for (var order = this._drawFirst; order; order = order.next) {
11977 layer = order.layer;
11978 if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
11979 clickedLayer = layer;
11982 if (clickedLayer) {
11984 this._fireEvent([clickedLayer], e);
11988 _onMouseMove: function (e) {
11989 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
11991 var point = this._map.mouseEventToLayerPoint(e);
11992 this._handleMouseHover(e, point);
11996 _handleMouseOut: function (e) {
11997 var layer = this._hoveredLayer;
11999 // if we're leaving the layer, fire mouseout
12000 removeClass(this._container, 'leaflet-interactive');
12001 this._fireEvent([layer], e, 'mouseout');
12002 this._hoveredLayer = null;
12006 _handleMouseHover: function (e, point) {
12007 var layer, candidateHoveredLayer;
12009 for (var order = this._drawFirst; order; order = order.next) {
12010 layer = order.layer;
12011 if (layer.options.interactive && layer._containsPoint(point)) {
12012 candidateHoveredLayer = layer;
12016 if (candidateHoveredLayer !== this._hoveredLayer) {
12017 this._handleMouseOut(e);
12019 if (candidateHoveredLayer) {
12020 addClass(this._container, 'leaflet-interactive'); // change cursor
12021 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
12022 this._hoveredLayer = candidateHoveredLayer;
12026 if (this._hoveredLayer) {
12027 this._fireEvent([this._hoveredLayer], e);
12031 _fireEvent: function (layers, e, type) {
12032 this._map._fireDOMEvent(e, type || e.type, layers);
12035 _bringToFront: function (layer) {
12036 var order = layer._order;
12037 var next = order.next;
12038 var prev = order.prev;
12049 // Update first entry unless this is the
12051 this._drawFirst = next;
12054 order.prev = this._drawLast;
12055 this._drawLast.next = order;
12058 this._drawLast = order;
12060 this._requestRedraw(layer);
12063 _bringToBack: function (layer) {
12064 var order = layer._order;
12065 var next = order.next;
12066 var prev = order.prev;
12077 // Update last entry unless this is the
12079 this._drawLast = prev;
12084 order.next = this._drawFirst;
12085 this._drawFirst.prev = order;
12086 this._drawFirst = order;
12088 this._requestRedraw(layer);
12092 // @factory L.canvas(options?: Renderer options)
12093 // Creates a Canvas renderer with the given options.
12094 function canvas$1(options) {
12095 return canvas ? new Canvas(options) : null;
12099 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
12103 var vmlCreate = (function () {
12105 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
12106 return function (name) {
12107 return document.createElement('<lvml:' + name + ' class="lvml">');
12110 return function (name) {
12111 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
12120 * Although SVG is not available on IE7 and IE8, these browsers support [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language), and the SVG renderer will fall back to VML in this case.
12122 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
12123 * with old versions of Internet Explorer.
12126 // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
12129 _initContainer: function () {
12130 this._container = create$1('div', 'leaflet-vml-container');
12133 _update: function () {
12134 if (this._map._animatingZoom) { return; }
12135 Renderer.prototype._update.call(this);
12136 this.fire('update');
12139 _initPath: function (layer) {
12140 var container = layer._container = vmlCreate('shape');
12142 addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
12144 container.coordsize = '1 1';
12146 layer._path = vmlCreate('path');
12147 container.appendChild(layer._path);
12149 this._updateStyle(layer);
12150 this._layers[stamp(layer)] = layer;
12153 _addPath: function (layer) {
12154 var container = layer._container;
12155 this._container.appendChild(container);
12157 if (layer.options.interactive) {
12158 layer.addInteractiveTarget(container);
12162 _removePath: function (layer) {
12163 var container = layer._container;
12165 layer.removeInteractiveTarget(container);
12166 delete this._layers[stamp(layer)];
12169 _updateStyle: function (layer) {
12170 var stroke = layer._stroke,
12171 fill = layer._fill,
12172 options = layer.options,
12173 container = layer._container;
12175 container.stroked = !!options.stroke;
12176 container.filled = !!options.fill;
12178 if (options.stroke) {
12180 stroke = layer._stroke = vmlCreate('stroke');
12182 container.appendChild(stroke);
12183 stroke.weight = options.weight + 'px';
12184 stroke.color = options.color;
12185 stroke.opacity = options.opacity;
12187 if (options.dashArray) {
12188 stroke.dashStyle = isArray(options.dashArray) ?
12189 options.dashArray.join(' ') :
12190 options.dashArray.replace(/( *, *)/g, ' ');
12192 stroke.dashStyle = '';
12194 stroke.endcap = options.lineCap.replace('butt', 'flat');
12195 stroke.joinstyle = options.lineJoin;
12197 } else if (stroke) {
12198 container.removeChild(stroke);
12199 layer._stroke = null;
12202 if (options.fill) {
12204 fill = layer._fill = vmlCreate('fill');
12206 container.appendChild(fill);
12207 fill.color = options.fillColor || options.color;
12208 fill.opacity = options.fillOpacity;
12211 container.removeChild(fill);
12212 layer._fill = null;
12216 _updateCircle: function (layer) {
12217 var p = layer._point.round(),
12218 r = Math.round(layer._radius),
12219 r2 = Math.round(layer._radiusY || r);
12221 this._setPath(layer, layer._empty() ? 'M0 0' :
12222 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
12225 _setPath: function (layer, path) {
12226 layer._path.v = path;
12229 _bringToFront: function (layer) {
12230 toFront(layer._container);
12233 _bringToBack: function (layer) {
12234 toBack(layer._container);
12238 var create$2 = vml ? vmlCreate : svgCreate;
12242 * @inherits Renderer
12245 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
12246 * Inherits `Renderer`.
12248 * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
12249 * available in all web browsers, notably Android 2.x and 3.x.
12251 * Although SVG is not available on IE7 and IE8, these browsers support
12252 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
12253 * (a now deprecated technology), and the SVG renderer will fall back to VML in
12258 * Use SVG by default for all paths in the map:
12261 * var map = L.map('map', {
12262 * renderer: L.svg()
12266 * Use a SVG renderer with extra padding for specific vector geometries:
12269 * var map = L.map('map');
12270 * var myRenderer = L.svg({ padding: 0.5 });
12271 * var line = L.polyline( coordinates, { renderer: myRenderer } );
12272 * var circle = L.circle( center, { renderer: myRenderer } );
12276 var SVG = Renderer.extend({
12278 getEvents: function () {
12279 var events = Renderer.prototype.getEvents.call(this);
12280 events.zoomstart = this._onZoomStart;
12284 _initContainer: function () {
12285 this._container = create$2('svg');
12287 // makes it possible to click through svg root; we'll reset it back in individual paths
12288 this._container.setAttribute('pointer-events', 'none');
12290 this._rootGroup = create$2('g');
12291 this._container.appendChild(this._rootGroup);
12294 _destroyContainer: function () {
12295 remove(this._container);
12296 off(this._container);
12297 delete this._container;
12298 delete this._rootGroup;
12301 _onZoomStart: function () {
12302 // Drag-then-pinch interactions might mess up the center and zoom.
12303 // In this case, the easiest way to prevent this is re-do the renderer
12304 // bounds and padding when the zooming starts.
12308 _update: function () {
12309 if (this._map._animatingZoom && this._bounds) { return; }
12311 Renderer.prototype._update.call(this);
12313 var b = this._bounds,
12314 size = b.getSize(),
12315 container = this._container;
12317 // set size of svg-container if changed
12318 if (!this._svgSize || !this._svgSize.equals(size)) {
12319 this._svgSize = size;
12320 container.setAttribute('width', size.x);
12321 container.setAttribute('height', size.y);
12324 // movement: update container viewBox so that we don't have to change coordinates of individual layers
12325 setPosition(container, b.min);
12326 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
12328 this.fire('update');
12331 // methods below are called by vector layers implementations
12333 _initPath: function (layer) {
12334 var path = layer._path = create$2('path');
12337 // @option className: String = null
12338 // Custom class name set on an element. Only for SVG renderer.
12339 if (layer.options.className) {
12340 addClass(path, layer.options.className);
12343 if (layer.options.interactive) {
12344 addClass(path, 'leaflet-interactive');
12347 this._updateStyle(layer);
12348 this._layers[stamp(layer)] = layer;
12351 _addPath: function (layer) {
12352 if (!this._rootGroup) { this._initContainer(); }
12353 this._rootGroup.appendChild(layer._path);
12354 layer.addInteractiveTarget(layer._path);
12357 _removePath: function (layer) {
12358 remove(layer._path);
12359 layer.removeInteractiveTarget(layer._path);
12360 delete this._layers[stamp(layer)];
12363 _updatePath: function (layer) {
12368 _updateStyle: function (layer) {
12369 var path = layer._path,
12370 options = layer.options;
12372 if (!path) { return; }
12374 if (options.stroke) {
12375 path.setAttribute('stroke', options.color);
12376 path.setAttribute('stroke-opacity', options.opacity);
12377 path.setAttribute('stroke-width', options.weight);
12378 path.setAttribute('stroke-linecap', options.lineCap);
12379 path.setAttribute('stroke-linejoin', options.lineJoin);
12381 if (options.dashArray) {
12382 path.setAttribute('stroke-dasharray', options.dashArray);
12384 path.removeAttribute('stroke-dasharray');
12387 if (options.dashOffset) {
12388 path.setAttribute('stroke-dashoffset', options.dashOffset);
12390 path.removeAttribute('stroke-dashoffset');
12393 path.setAttribute('stroke', 'none');
12396 if (options.fill) {
12397 path.setAttribute('fill', options.fillColor || options.color);
12398 path.setAttribute('fill-opacity', options.fillOpacity);
12399 path.setAttribute('fill-rule', options.fillRule || 'evenodd');
12401 path.setAttribute('fill', 'none');
12405 _updatePoly: function (layer, closed) {
12406 this._setPath(layer, pointsToPath(layer._parts, closed));
12409 _updateCircle: function (layer) {
12410 var p = layer._point,
12412 r2 = layer._radiusY || r,
12413 arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
12415 // drawing a circle with two half-arcs
12416 var d = layer._empty() ? 'M0 0' :
12417 'M' + (p.x - r) + ',' + p.y +
12418 arc + (r * 2) + ',0 ' +
12419 arc + (-r * 2) + ',0 ';
12421 this._setPath(layer, d);
12424 _setPath: function (layer, path) {
12425 layer._path.setAttribute('d', path);
12428 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
12429 _bringToFront: function (layer) {
12430 toFront(layer._path);
12433 _bringToBack: function (layer) {
12434 toBack(layer._path);
12439 SVG.include(vmlMixin);
12442 // @factory L.svg(options?: Renderer options)
12443 // Creates a SVG renderer with the given options.
12444 function svg$1(options) {
12445 return svg || vml ? new SVG(options) : null;
12449 // @namespace Map; @method getRenderer(layer: Path): Renderer
12450 // Returns the instance of `Renderer` that should be used to render the given
12451 // `Path`. It will ensure that the `renderer` options of the map and paths
12452 // are respected, and that the renderers do exist on the map.
12453 getRenderer: function (layer) {
12454 // @namespace Path; @option renderer: Renderer
12455 // Use this specific instance of `Renderer` for this path. Takes
12456 // precedence over the map's [default renderer](#map-renderer).
12457 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
12460 // @namespace Map; @option preferCanvas: Boolean = false
12461 // Whether `Path`s should be rendered on a `Canvas` renderer.
12462 // By default, all `Path`s are rendered in a `SVG` renderer.
12463 renderer = this._renderer = (this.options.preferCanvas && canvas$1()) || svg$1();
12466 if (!this.hasLayer(renderer)) {
12467 this.addLayer(renderer);
12472 _getPaneRenderer: function (name) {
12473 if (name === 'overlayPane' || name === undefined) {
12477 var renderer = this._paneRenderers[name];
12478 if (renderer === undefined) {
12479 renderer = (SVG && svg$1({pane: name})) || (Canvas && canvas$1({pane: name}));
12480 this._paneRenderers[name] = renderer;
12487 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
12493 * @inherits Polygon
12495 * A class for drawing rectangle overlays on a map. Extends `Polygon`.
12500 * // define rectangle geographical bounds
12501 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
12503 * // create an orange rectangle
12504 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
12506 * // zoom the map to the rectangle bounds
12507 * map.fitBounds(bounds);
12513 var Rectangle = Polygon.extend({
12514 initialize: function (latLngBounds, options) {
12515 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
12518 // @method setBounds(latLngBounds: LatLngBounds): this
12519 // Redraws the rectangle with the passed bounds.
12520 setBounds: function (latLngBounds) {
12521 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
12524 _boundsToLatLngs: function (latLngBounds) {
12525 latLngBounds = toLatLngBounds(latLngBounds);
12527 latLngBounds.getSouthWest(),
12528 latLngBounds.getNorthWest(),
12529 latLngBounds.getNorthEast(),
12530 latLngBounds.getSouthEast()
12536 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
12537 function rectangle(latLngBounds, options) {
12538 return new Rectangle(latLngBounds, options);
12541 SVG.create = create$2;
12542 SVG.pointsToPath = pointsToPath;
12544 GeoJSON.geometryToLayer = geometryToLayer;
12545 GeoJSON.coordsToLatLng = coordsToLatLng;
12546 GeoJSON.coordsToLatLngs = coordsToLatLngs;
12547 GeoJSON.latLngToCoords = latLngToCoords;
12548 GeoJSON.latLngsToCoords = latLngsToCoords;
12549 GeoJSON.getFeature = getFeature;
12550 GeoJSON.asFeature = asFeature;
12553 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
12554 * (zoom to a selected bounding box), enabled by default.
12558 // @section Interaction Options
12560 // @option boxZoom: Boolean = true
12561 // Whether the map can be zoomed to a rectangular area specified by
12562 // dragging the mouse while pressing the shift key.
12566 var BoxZoom = Handler.extend({
12567 initialize: function (map) {
12569 this._container = map._container;
12570 this._pane = map._panes.overlayPane;
12571 this._resetStateTimeout = 0;
12572 map.on('unload', this._destroy, this);
12575 addHooks: function () {
12576 on(this._container, 'mousedown', this._onMouseDown, this);
12579 removeHooks: function () {
12580 off(this._container, 'mousedown', this._onMouseDown, this);
12583 moved: function () {
12584 return this._moved;
12587 _destroy: function () {
12588 remove(this._pane);
12592 _resetState: function () {
12593 this._resetStateTimeout = 0;
12594 this._moved = false;
12597 _clearDeferredResetState: function () {
12598 if (this._resetStateTimeout !== 0) {
12599 clearTimeout(this._resetStateTimeout);
12600 this._resetStateTimeout = 0;
12604 _onMouseDown: function (e) {
12605 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
12607 // Clear the deferred resetState if it hasn't executed yet, otherwise it
12608 // will interrupt the interaction and orphan a box element in the container.
12609 this._clearDeferredResetState();
12610 this._resetState();
12612 disableTextSelection();
12613 disableImageDrag();
12615 this._startPoint = this._map.mouseEventToContainerPoint(e);
12619 mousemove: this._onMouseMove,
12620 mouseup: this._onMouseUp,
12621 keydown: this._onKeyDown
12625 _onMouseMove: function (e) {
12626 if (!this._moved) {
12627 this._moved = true;
12629 this._box = create$1('div', 'leaflet-zoom-box', this._container);
12630 addClass(this._container, 'leaflet-crosshair');
12632 this._map.fire('boxzoomstart');
12635 this._point = this._map.mouseEventToContainerPoint(e);
12637 var bounds = new Bounds(this._point, this._startPoint),
12638 size = bounds.getSize();
12640 setPosition(this._box, bounds.min);
12642 this._box.style.width = size.x + 'px';
12643 this._box.style.height = size.y + 'px';
12646 _finish: function () {
12649 removeClass(this._container, 'leaflet-crosshair');
12652 enableTextSelection();
12657 mousemove: this._onMouseMove,
12658 mouseup: this._onMouseUp,
12659 keydown: this._onKeyDown
12663 _onMouseUp: function (e) {
12664 if ((e.which !== 1) && (e.button !== 1)) { return; }
12668 if (!this._moved) { return; }
12669 // Postpone to next JS tick so internal click event handling
12670 // still see it as "moved".
12671 this._clearDeferredResetState();
12672 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
12674 var bounds = new LatLngBounds(
12675 this._map.containerPointToLatLng(this._startPoint),
12676 this._map.containerPointToLatLng(this._point));
12680 .fire('boxzoomend', {boxZoomBounds: bounds});
12683 _onKeyDown: function (e) {
12684 if (e.keyCode === 27) {
12690 // @section Handlers
12691 // @property boxZoom: Handler
12692 // Box (shift-drag with mouse) zoom handler.
12693 Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
12696 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
12700 // @section Interaction Options
12703 // @option doubleClickZoom: Boolean|String = true
12704 // Whether the map can be zoomed in by double clicking on it and
12705 // zoomed out by double clicking while holding shift. If passed
12706 // `'center'`, double-click zoom will zoom to the center of the
12707 // view regardless of where the mouse was.
12708 doubleClickZoom: true
12711 var DoubleClickZoom = Handler.extend({
12712 addHooks: function () {
12713 this._map.on('dblclick', this._onDoubleClick, this);
12716 removeHooks: function () {
12717 this._map.off('dblclick', this._onDoubleClick, this);
12720 _onDoubleClick: function (e) {
12721 var map = this._map,
12722 oldZoom = map.getZoom(),
12723 delta = map.options.zoomDelta,
12724 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
12726 if (map.options.doubleClickZoom === 'center') {
12729 map.setZoomAround(e.containerPoint, zoom);
12734 // @section Handlers
12736 // Map properties include interaction handlers that allow you to control
12737 // interaction behavior in runtime, enabling or disabling certain features such
12738 // as dragging or touch zoom (see `Handler` methods). For example:
12741 // map.doubleClickZoom.disable();
12744 // @property doubleClickZoom: Handler
12745 // Double click zoom handler.
12746 Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
12749 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
12753 // @section Interaction Options
12755 // @option dragging: Boolean = true
12756 // Whether the map be draggable with mouse/touch or not.
12759 // @section Panning Inertia Options
12760 // @option inertia: Boolean = *
12761 // If enabled, panning of the map will have an inertia effect where
12762 // the map builds momentum while dragging and continues moving in
12763 // the same direction for some time. Feels especially nice on touch
12764 // devices. Enabled by default unless running on old Android devices.
12765 inertia: !android23,
12767 // @option inertiaDeceleration: Number = 3000
12768 // The rate with which the inertial movement slows down, in pixels/second².
12769 inertiaDeceleration: 3400, // px/s^2
12771 // @option inertiaMaxSpeed: Number = Infinity
12772 // Max speed of the inertial movement, in pixels/second.
12773 inertiaMaxSpeed: Infinity, // px/s
12775 // @option easeLinearity: Number = 0.2
12776 easeLinearity: 0.2,
12778 // TODO refactor, move to CRS
12779 // @option worldCopyJump: Boolean = false
12780 // With this option enabled, the map tracks when you pan to another "copy"
12781 // of the world and seamlessly jumps to the original one so that all overlays
12782 // like markers and vector layers are still visible.
12783 worldCopyJump: false,
12785 // @option maxBoundsViscosity: Number = 0.0
12786 // If `maxBounds` is set, this option will control how solid the bounds
12787 // are when dragging the map around. The default value of `0.0` allows the
12788 // user to drag outside the bounds at normal speed, higher values will
12789 // slow down map dragging outside bounds, and `1.0` makes the bounds fully
12790 // solid, preventing the user from dragging outside the bounds.
12791 maxBoundsViscosity: 0.0
12794 var Drag = Handler.extend({
12795 addHooks: function () {
12796 if (!this._draggable) {
12797 var map = this._map;
12799 this._draggable = new Draggable(map._mapPane, map._container);
12801 this._draggable.on({
12802 dragstart: this._onDragStart,
12803 drag: this._onDrag,
12804 dragend: this._onDragEnd
12807 this._draggable.on('predrag', this._onPreDragLimit, this);
12808 if (map.options.worldCopyJump) {
12809 this._draggable.on('predrag', this._onPreDragWrap, this);
12810 map.on('zoomend', this._onZoomEnd, this);
12812 map.whenReady(this._onZoomEnd, this);
12815 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
12816 this._draggable.enable();
12817 this._positions = [];
12821 removeHooks: function () {
12822 removeClass(this._map._container, 'leaflet-grab');
12823 removeClass(this._map._container, 'leaflet-touch-drag');
12824 this._draggable.disable();
12827 moved: function () {
12828 return this._draggable && this._draggable._moved;
12831 moving: function () {
12832 return this._draggable && this._draggable._moving;
12835 _onDragStart: function () {
12836 var map = this._map;
12839 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
12840 var bounds = toLatLngBounds(this._map.options.maxBounds);
12842 this._offsetLimit = toBounds(
12843 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
12844 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
12845 .add(this._map.getSize()));
12847 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
12849 this._offsetLimit = null;
12854 .fire('dragstart');
12856 if (map.options.inertia) {
12857 this._positions = [];
12862 _onDrag: function (e) {
12863 if (this._map.options.inertia) {
12864 var time = this._lastTime = +new Date(),
12865 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
12867 this._positions.push(pos);
12868 this._times.push(time);
12870 if (time - this._times[0] > 50) {
12871 this._positions.shift();
12872 this._times.shift();
12881 _onZoomEnd: function () {
12882 var pxCenter = this._map.getSize().divideBy(2),
12883 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
12885 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
12886 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
12889 _viscousLimit: function (value, threshold) {
12890 return value - (value - threshold) * this._viscosity;
12893 _onPreDragLimit: function () {
12894 if (!this._viscosity || !this._offsetLimit) { return; }
12896 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
12898 var limit = this._offsetLimit;
12899 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
12900 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
12901 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
12902 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
12904 this._draggable._newPos = this._draggable._startPos.add(offset);
12907 _onPreDragWrap: function () {
12908 // TODO refactor to be able to adjust map pane position after zoom
12909 var worldWidth = this._worldWidth,
12910 halfWidth = Math.round(worldWidth / 2),
12911 dx = this._initialWorldOffset,
12912 x = this._draggable._newPos.x,
12913 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
12914 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
12915 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
12917 this._draggable._absPos = this._draggable._newPos.clone();
12918 this._draggable._newPos.x = newX;
12921 _onDragEnd: function (e) {
12922 var map = this._map,
12923 options = map.options,
12925 noInertia = !options.inertia || this._times.length < 2;
12927 map.fire('dragend', e);
12930 map.fire('moveend');
12934 var direction = this._lastPos.subtract(this._positions[0]),
12935 duration = (this._lastTime - this._times[0]) / 1000,
12936 ease = options.easeLinearity,
12938 speedVector = direction.multiplyBy(ease / duration),
12939 speed = speedVector.distanceTo([0, 0]),
12941 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
12942 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
12944 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
12945 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
12947 if (!offset.x && !offset.y) {
12948 map.fire('moveend');
12951 offset = map._limitOffset(offset, map.options.maxBounds);
12953 requestAnimFrame(function () {
12954 map.panBy(offset, {
12955 duration: decelerationDuration,
12956 easeLinearity: ease,
12966 // @section Handlers
12967 // @property dragging: Handler
12968 // Map dragging handler (by both mouse and touch).
12969 Map.addInitHook('addHandler', 'dragging', Drag);
12972 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
12976 // @section Keyboard Navigation Options
12978 // @option keyboard: Boolean = true
12979 // Makes the map focusable and allows users to navigate the map with keyboard
12980 // arrows and `+`/`-` keys.
12983 // @option keyboardPanDelta: Number = 80
12984 // Amount of pixels to pan when pressing an arrow key.
12985 keyboardPanDelta: 80
12988 var Keyboard = Handler.extend({
12995 zoomIn: [187, 107, 61, 171],
12996 zoomOut: [189, 109, 54, 173]
12999 initialize: function (map) {
13002 this._setPanDelta(map.options.keyboardPanDelta);
13003 this._setZoomDelta(map.options.zoomDelta);
13006 addHooks: function () {
13007 var container = this._map._container;
13009 // make the container focusable by tabbing
13010 if (container.tabIndex <= 0) {
13011 container.tabIndex = '0';
13015 focus: this._onFocus,
13016 blur: this._onBlur,
13017 mousedown: this._onMouseDown
13021 focus: this._addHooks,
13022 blur: this._removeHooks
13026 removeHooks: function () {
13027 this._removeHooks();
13029 off(this._map._container, {
13030 focus: this._onFocus,
13031 blur: this._onBlur,
13032 mousedown: this._onMouseDown
13036 focus: this._addHooks,
13037 blur: this._removeHooks
13041 _onMouseDown: function () {
13042 if (this._focused) { return; }
13044 var body = document.body,
13045 docEl = document.documentElement,
13046 top = body.scrollTop || docEl.scrollTop,
13047 left = body.scrollLeft || docEl.scrollLeft;
13049 this._map._container.focus();
13051 window.scrollTo(left, top);
13054 _onFocus: function () {
13055 this._focused = true;
13056 this._map.fire('focus');
13059 _onBlur: function () {
13060 this._focused = false;
13061 this._map.fire('blur');
13064 _setPanDelta: function (panDelta) {
13065 var keys = this._panKeys = {},
13066 codes = this.keyCodes,
13069 for (i = 0, len = codes.left.length; i < len; i++) {
13070 keys[codes.left[i]] = [-1 * panDelta, 0];
13072 for (i = 0, len = codes.right.length; i < len; i++) {
13073 keys[codes.right[i]] = [panDelta, 0];
13075 for (i = 0, len = codes.down.length; i < len; i++) {
13076 keys[codes.down[i]] = [0, panDelta];
13078 for (i = 0, len = codes.up.length; i < len; i++) {
13079 keys[codes.up[i]] = [0, -1 * panDelta];
13083 _setZoomDelta: function (zoomDelta) {
13084 var keys = this._zoomKeys = {},
13085 codes = this.keyCodes,
13088 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
13089 keys[codes.zoomIn[i]] = zoomDelta;
13091 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
13092 keys[codes.zoomOut[i]] = -zoomDelta;
13096 _addHooks: function () {
13097 on(document, 'keydown', this._onKeyDown, this);
13100 _removeHooks: function () {
13101 off(document, 'keydown', this._onKeyDown, this);
13104 _onKeyDown: function (e) {
13105 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
13107 var key = e.keyCode,
13111 if (key in this._panKeys) {
13113 if (map._panAnim && map._panAnim._inProgress) { return; }
13115 offset = this._panKeys[key];
13117 offset = toPoint(offset).multiplyBy(3);
13122 if (map.options.maxBounds) {
13123 map.panInsideBounds(map.options.maxBounds);
13126 } else if (key in this._zoomKeys) {
13127 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
13129 } else if (key === 27 && map._popup) {
13140 // @section Handlers
13141 // @section Handlers
13142 // @property keyboard: Handler
13143 // Keyboard navigation handler.
13144 Map.addInitHook('addHandler', 'keyboard', Keyboard);
13147 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
13151 // @section Interaction Options
13153 // @section Mousewheel options
13154 // @option scrollWheelZoom: Boolean|String = true
13155 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
13156 // it will zoom to the center of the view regardless of where the mouse was.
13157 scrollWheelZoom: true,
13159 // @option wheelDebounceTime: Number = 40
13160 // Limits the rate at which a wheel can fire (in milliseconds). By default
13161 // user can't zoom via wheel more often than once per 40 ms.
13162 wheelDebounceTime: 40,
13164 // @option wheelPxPerZoomLevel: Number = 60
13165 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
13166 // mean a change of one full zoom level. Smaller values will make wheel-zooming
13167 // faster (and vice versa).
13168 wheelPxPerZoomLevel: 60
13171 var ScrollWheelZoom = Handler.extend({
13172 addHooks: function () {
13173 on(this._map._container, 'mousewheel', this._onWheelScroll, this);
13178 removeHooks: function () {
13179 off(this._map._container, 'mousewheel', this._onWheelScroll, this);
13182 _onWheelScroll: function (e) {
13183 var delta = getWheelDelta(e);
13185 var debounce = this._map.options.wheelDebounceTime;
13187 this._delta += delta;
13188 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
13190 if (!this._startTime) {
13191 this._startTime = +new Date();
13194 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
13196 clearTimeout(this._timer);
13197 this._timer = setTimeout(bind(this._performZoom, this), left);
13202 _performZoom: function () {
13203 var map = this._map,
13204 zoom = map.getZoom(),
13205 snap = this._map.options.zoomSnap || 0;
13207 map._stop(); // stop panning and fly animations if any
13209 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
13210 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
13211 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
13212 d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
13213 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
13216 this._startTime = null;
13218 if (!delta) { return; }
13220 if (map.options.scrollWheelZoom === 'center') {
13221 map.setZoom(zoom + delta);
13223 map.setZoomAround(this._lastMousePos, zoom + delta);
13228 // @section Handlers
13229 // @property scrollWheelZoom: Handler
13230 // Scroll wheel zoom handler.
13231 Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
13234 * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
13238 // @section Interaction Options
13240 // @section Touch interaction options
13241 // @option tap: Boolean = true
13242 // Enables mobile hacks for supporting instant taps (fixing 200ms click
13243 // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
13246 // @option tapTolerance: Number = 15
13247 // The max number of pixels a user can shift his finger during touch
13248 // for it to be considered a valid tap.
13252 var Tap = Handler.extend({
13253 addHooks: function () {
13254 on(this._map._container, 'touchstart', this._onDown, this);
13257 removeHooks: function () {
13258 off(this._map._container, 'touchstart', this._onDown, this);
13261 _onDown: function (e) {
13262 if (!e.touches) { return; }
13266 this._fireClick = true;
13268 // don't simulate click or track longpress if more than 1 touch
13269 if (e.touches.length > 1) {
13270 this._fireClick = false;
13271 clearTimeout(this._holdTimeout);
13275 var first = e.touches[0],
13278 this._startPos = this._newPos = new Point(first.clientX, first.clientY);
13280 // if touching a link, highlight it
13281 if (el.tagName && el.tagName.toLowerCase() === 'a') {
13282 addClass(el, 'leaflet-active');
13285 // simulate long hold but setting a timeout
13286 this._holdTimeout = setTimeout(bind(function () {
13287 if (this._isTapValid()) {
13288 this._fireClick = false;
13290 this._simulateEvent('contextmenu', first);
13294 this._simulateEvent('mousedown', first);
13297 touchmove: this._onMove,
13298 touchend: this._onUp
13302 _onUp: function (e) {
13303 clearTimeout(this._holdTimeout);
13306 touchmove: this._onMove,
13307 touchend: this._onUp
13310 if (this._fireClick && e && e.changedTouches) {
13312 var first = e.changedTouches[0],
13315 if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
13316 removeClass(el, 'leaflet-active');
13319 this._simulateEvent('mouseup', first);
13321 // simulate click if the touch didn't move too much
13322 if (this._isTapValid()) {
13323 this._simulateEvent('click', first);
13328 _isTapValid: function () {
13329 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
13332 _onMove: function (e) {
13333 var first = e.touches[0];
13334 this._newPos = new Point(first.clientX, first.clientY);
13335 this._simulateEvent('mousemove', first);
13338 _simulateEvent: function (type, e) {
13339 var simulatedEvent = document.createEvent('MouseEvents');
13341 simulatedEvent._simulated = true;
13342 e.target._simulatedClick = true;
13344 simulatedEvent.initMouseEvent(
13345 type, true, true, window, 1,
13346 e.screenX, e.screenY,
13347 e.clientX, e.clientY,
13348 false, false, false, false, 0, null);
13350 e.target.dispatchEvent(simulatedEvent);
13354 // @section Handlers
13355 // @property tap: Handler
13356 // Mobile touch hacks (quick tap and touch hold) handler.
13357 if (touch && !pointer) {
13358 Map.addInitHook('addHandler', 'tap', Tap);
13362 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
13366 // @section Interaction Options
13368 // @section Touch interaction options
13369 // @option touchZoom: Boolean|String = *
13370 // Whether the map can be zoomed by touch-dragging with two fingers. If
13371 // passed `'center'`, it will zoom to the center of the view regardless of
13372 // where the touch events (fingers) were. Enabled for touch-capable web
13373 // browsers except for old Androids.
13374 touchZoom: touch && !android23,
13376 // @option bounceAtZoomLimits: Boolean = true
13377 // Set it to false if you don't want the map to zoom beyond min/max zoom
13378 // and then bounce back when pinch-zooming.
13379 bounceAtZoomLimits: true
13382 var TouchZoom = Handler.extend({
13383 addHooks: function () {
13384 addClass(this._map._container, 'leaflet-touch-zoom');
13385 on(this._map._container, 'touchstart', this._onTouchStart, this);
13388 removeHooks: function () {
13389 removeClass(this._map._container, 'leaflet-touch-zoom');
13390 off(this._map._container, 'touchstart', this._onTouchStart, this);
13393 _onTouchStart: function (e) {
13394 var map = this._map;
13395 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
13397 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
13398 p2 = map.mouseEventToContainerPoint(e.touches[1]);
13400 this._centerPoint = map.getSize()._divideBy(2);
13401 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
13402 if (map.options.touchZoom !== 'center') {
13403 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
13406 this._startDist = p1.distanceTo(p2);
13407 this._startZoom = map.getZoom();
13409 this._moved = false;
13410 this._zooming = true;
13414 on(document, 'touchmove', this._onTouchMove, this);
13415 on(document, 'touchend', this._onTouchEnd, this);
13420 _onTouchMove: function (e) {
13421 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
13423 var map = this._map,
13424 p1 = map.mouseEventToContainerPoint(e.touches[0]),
13425 p2 = map.mouseEventToContainerPoint(e.touches[1]),
13426 scale = p1.distanceTo(p2) / this._startDist;
13428 this._zoom = map.getScaleZoom(scale, this._startZoom);
13430 if (!map.options.bounceAtZoomLimits && (
13431 (this._zoom < map.getMinZoom() && scale < 1) ||
13432 (this._zoom > map.getMaxZoom() && scale > 1))) {
13433 this._zoom = map._limitZoom(this._zoom);
13436 if (map.options.touchZoom === 'center') {
13437 this._center = this._startLatLng;
13438 if (scale === 1) { return; }
13440 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
13441 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
13442 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
13443 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
13446 if (!this._moved) {
13447 map._moveStart(true);
13448 this._moved = true;
13451 cancelAnimFrame(this._animRequest);
13453 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
13454 this._animRequest = requestAnimFrame(moveFn, this, true);
13459 _onTouchEnd: function () {
13460 if (!this._moved || !this._zooming) {
13461 this._zooming = false;
13465 this._zooming = false;
13466 cancelAnimFrame(this._animRequest);
13468 off(document, 'touchmove', this._onTouchMove);
13469 off(document, 'touchend', this._onTouchEnd);
13471 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
13472 if (this._map.options.zoomAnimation) {
13473 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
13475 this._map._resetView(this._center, this._map._limitZoom(this._zoom));
13480 // @section Handlers
13481 // @property touchZoom: Handler
13482 // Touch zoom handler.
13483 Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
13485 Map.BoxZoom = BoxZoom;
13486 Map.DoubleClickZoom = DoubleClickZoom;
13488 Map.Keyboard = Keyboard;
13489 Map.ScrollWheelZoom = ScrollWheelZoom;
13491 Map.TouchZoom = TouchZoom;
13495 var oldL = window.L;
13496 function noConflict() {
13501 // Always export us to window global (see #2364)
13502 window.L = exports;
13504 exports.version = version;
13505 exports.noConflict = noConflict;
13506 exports.Control = Control;
13507 exports.control = control;
13508 exports.Browser = Browser;
13509 exports.Evented = Evented;
13510 exports.Mixin = Mixin;
13511 exports.Util = Util;
13512 exports.Class = Class;
13513 exports.Handler = Handler;
13514 exports.extend = extend;
13515 exports.bind = bind;
13516 exports.stamp = stamp;
13517 exports.setOptions = setOptions;
13518 exports.DomEvent = DomEvent;
13519 exports.DomUtil = DomUtil;
13520 exports.PosAnimation = PosAnimation;
13521 exports.Draggable = Draggable;
13522 exports.LineUtil = LineUtil;
13523 exports.PolyUtil = PolyUtil;
13524 exports.Point = Point;
13525 exports.point = toPoint;
13526 exports.Bounds = Bounds;
13527 exports.bounds = toBounds;
13528 exports.Transformation = Transformation;
13529 exports.transformation = toTransformation;
13530 exports.Projection = index;
13531 exports.LatLng = LatLng;
13532 exports.latLng = toLatLng;
13533 exports.LatLngBounds = LatLngBounds;
13534 exports.latLngBounds = toLatLngBounds;
13536 exports.GeoJSON = GeoJSON;
13537 exports.geoJSON = geoJSON;
13538 exports.geoJson = geoJson;
13539 exports.Layer = Layer;
13540 exports.LayerGroup = LayerGroup;
13541 exports.layerGroup = layerGroup;
13542 exports.FeatureGroup = FeatureGroup;
13543 exports.featureGroup = featureGroup;
13544 exports.ImageOverlay = ImageOverlay;
13545 exports.imageOverlay = imageOverlay;
13546 exports.VideoOverlay = VideoOverlay;
13547 exports.videoOverlay = videoOverlay;
13548 exports.DivOverlay = DivOverlay;
13549 exports.Popup = Popup;
13550 exports.popup = popup;
13551 exports.Tooltip = Tooltip;
13552 exports.tooltip = tooltip;
13553 exports.Icon = Icon;
13554 exports.icon = icon;
13555 exports.DivIcon = DivIcon;
13556 exports.divIcon = divIcon;
13557 exports.Marker = Marker;
13558 exports.marker = marker;
13559 exports.TileLayer = TileLayer;
13560 exports.tileLayer = tileLayer;
13561 exports.GridLayer = GridLayer;
13562 exports.gridLayer = gridLayer;
13564 exports.svg = svg$1;
13565 exports.Renderer = Renderer;
13566 exports.Canvas = Canvas;
13567 exports.canvas = canvas$1;
13568 exports.Path = Path;
13569 exports.CircleMarker = CircleMarker;
13570 exports.circleMarker = circleMarker;
13571 exports.Circle = Circle;
13572 exports.circle = circle;
13573 exports.Polyline = Polyline;
13574 exports.polyline = polyline;
13575 exports.Polygon = Polygon;
13576 exports.polygon = polygon;
13577 exports.Rectangle = Rectangle;
13578 exports.rectangle = rectangle;
13580 exports.map = createMap;
13583 //# sourceMappingURL=leaflet-src.js.map