]> git.openstreetmap.org Git - rails.git/blobdiff - vendor/assets/leaflet/leaflet.js
Update leaflet to 1.3.1
[rails.git] / vendor / assets / leaflet / leaflet.js
index 5e6ab2b54647a6c39ddb25814a355d3b10fd78b6..63f6fb790cac8c3a9674ea57f9b47edcf5611e5a 100644 (file)
@@ -1,14 +1,15 @@
-/*
- * Leaflet 1.1.0, a JS library for interactive maps. http://leafletjs.com
+/* @preserve
+ * Leaflet 1.3.1, a JS library for interactive maps. http://leafletjs.com
  * (c) 2010-2017 Vladimir Agafonkin, (c) 2010-2011 CloudMade
  */
+
 (function (global, factory) {
        typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
        typeof define === 'function' && define.amd ? define(['exports'], factory) :
-       (factory((global.L = global.L || {})));
+       (factory((global.L = {})));
 }(this, (function (exports) { 'use strict';
 
-var version = "1.1.0";
+var version = "1.3.1";
 
 /*
  * @namespace Util
@@ -16,6 +17,9 @@ var version = "1.1.0";
  * Various utility functions, used by Leaflet internally.
  */
 
+var freeze = Object.freeze;
+Object.freeze = function (obj) { return obj; };
+
 // @function extend(dest: Object, src?: Object): Object
 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
 function extend(dest) {
@@ -62,12 +66,12 @@ function bind(fn, obj) {
 var lastId = 0;
 
 // @function stamp(obj: Object): Number
-// Returns the unique ID of an object, assiging it one if it doesn't have it.
+// Returns the unique ID of an object, assigning it one if it doesn't have it.
 function stamp(obj) {
        /*eslint-disable */
        obj._leaflet_id = obj._leaflet_id || ++lastId;
        return obj._leaflet_id;
-       /*eslint-enable */
+       /* eslint-enable */
 }
 
 // @function throttle(fn: Function, time: Number, context: Object): Function
@@ -121,9 +125,9 @@ function wrapNum(x, range, includeMax) {
 function falseFn() { return false; }
 
 // @function formatNum(num: Number, digits?: Number): Number
-// Returns the number `num` rounded to `digits` decimals, or to 5 decimals by default.
+// Returns the number `num` rounded to `digits` decimals, or to 6 decimals by default.
 function formatNum(num, digits) {
-       var pow = Math.pow(10, digits || 5);
+       var pow = Math.pow(10, (digits === undefined ? 6 : digits));
        return Math.round(num * pow) / pow;
 }
 
@@ -164,7 +168,7 @@ function getParamString(obj, existingUrl, uppercase) {
        return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
 }
 
-var templateRe = /\{ *([\w_\-]+) *\}/g;
+var templateRe = /\{ *([\w_-]+) *\}/g;
 
 // @function template(str: String, data: Object): String
 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
@@ -251,6 +255,7 @@ function cancelAnimFrame(id) {
 
 
 var Util = (Object.freeze || Object)({
+       freeze: freeze,
        extend: extend,
        create: create,
        bind: bind,
@@ -386,7 +391,7 @@ Class.addInitHook = function (fn) { // (Function) || (String, args...)
 };
 
 function checkDeprecatedMixinEvents(includes) {
-       if (!L || !L.Mixin) { return; }
+       if (typeof L === 'undefined' || !L || !L.Mixin) { return; }
 
        includes = isArray(includes) ? includes : [includes];
 
@@ -572,7 +577,11 @@ var Events = {
        fire: function (type, data, propagate) {
                if (!this.listens(type, propagate)) { return this; }
 
-               var event = extend({}, data, {type: type, target: this});
+               var event = extend({}, data, {
+                       type: type,
+                       target: this,
+                       sourceTarget: data && data.sourceTarget || this
+               });
 
                if (this._events) {
                        var listeners = this._events[type];
@@ -653,7 +662,10 @@ var Events = {
 
        _propagateEvent: function (e) {
                for (var id in this._eventParents) {
-                       this._eventParents[id].fire(e.type, extend({layer: e.target}, e), true);
+                       this._eventParents[id].fire(e.type, extend({
+                               layer: e.target,
+                               propagatedFrom: e.target
+                       }, e), true);
                }
        }
 };
@@ -703,6 +715,10 @@ var Evented = Class.extend(Events);
  * map.panBy([200, 300]);
  * map.panBy(L.point(200, 300));
  * ```
+ *
+ * Note that `Point` does not inherit from Leafet's `Class` object,
+ * which means new classes can't inherit from it, and new methods
+ * can't be added to it with the `include` function.
  */
 
 function Point(x, y, round) {
@@ -712,6 +728,10 @@ function Point(x, y, round) {
        this.y = (round ? Math.round(y) : y);
 }
 
+var trunc = Math.trunc || function (v) {
+       return v > 0 ? Math.floor(v) : Math.ceil(v);
+};
+
 Point.prototype = {
 
        // @method clone(): Point
@@ -822,6 +842,18 @@ Point.prototype = {
                return this;
        },
 
+       // @method trunc(): Point
+       // Returns a copy of the current point with truncated coordinates (rounded towards zero).
+       trunc: function () {
+               return this.clone()._trunc();
+       },
+
+       _trunc: function () {
+               this.x = trunc(this.x);
+               this.y = trunc(this.y);
+               return this;
+       },
+
        // @method distanceTo(otherPoint: Point): Number
        // Returns the cartesian distance between the current and the given points.
        distanceTo: function (point) {
@@ -905,6 +937,10 @@ function toPoint(x, y, round) {
  * ```js
  * otherBounds.intersects([[10, 10], [40, 60]]);
  * ```
+ *
+ * Note that `Bounds` does not inherit from Leafet's `Class` object,
+ * which means new classes can't inherit from it, and new methods
+ * can't be added to it with the `include` function.
  */
 
 function Bounds(a, b) {
@@ -1078,6 +1114,10 @@ function toBounds(a, b) {
  * ```
  *
  * 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.
+ *
+ * Note that `LatLngBounds` does not inherit from Leafet's `Class` object,
+ * which means new classes can't inherit from it, and new methods
+ * can't be added to it with the `include` function.
  */
 
 function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
@@ -1131,7 +1171,9 @@ LatLngBounds.prototype = {
        },
 
        // @method pad(bufferRatio: Number): LatLngBounds
-       // Returns bigger bounds created by extending the current bounds by a given percentage in each direction.
+       // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
+       // For example, a ratio of 0.5 extends the bounds by 50% in each direction.
+       // Negative values will retract the bounds.
        pad: function (bufferRatio) {
                var sw = this._southWest,
                    ne = this._northEast,
@@ -1266,7 +1308,7 @@ LatLngBounds.prototype = {
        },
 
        // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
-       // 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.
+       // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overridden by setting `maxMargin` to a small number.
        equals: function (bounds, maxMargin) {
                if (!bounds) { return false; }
 
@@ -1317,6 +1359,10 @@ function toLatLngBounds(a, b) {
  * map.panTo({lat: 50, lng: 30});
  * map.panTo(L.latLng(50, 30));
  * ```
+ *
+ * Note that `LatLng` does not inherit from Leafet's `Class` object,
+ * which means new classes can't inherit from it, and new methods
+ * can't be added to it with the `include` function.
  */
 
 function LatLng(lat, lng, alt) {
@@ -1341,7 +1387,7 @@ function LatLng(lat, lng, alt) {
 
 LatLng.prototype = {
        // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
-       // 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.
+       // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overridden by setting `maxMargin` to a small number.
        equals: function (obj, maxMargin) {
                if (!obj) { return false; }
 
@@ -1363,7 +1409,7 @@ LatLng.prototype = {
        },
 
        // @method distanceTo(otherLatLng: LatLng): Number
-       // Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula).
+       // Returns the distance (in meters) to the given `LatLng` calculated using the [Spherical Law of Cosines](https://en.wikipedia.org/wiki/Spherical_law_of_cosines).
        distanceTo: function (other) {
                return Earth.distance(this, toLatLng(other));
        },
@@ -1439,6 +1485,10 @@ function toLatLng(a, b, c) {
  * Leaflet defines the most usual CRSs by default. If you want to use a
  * CRS not defined by default, take a look at the
  * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
+ *
+ * Note that the CRS instances do not inherit from Leafet's `Class` object,
+ * and can't be instantiated. Also, new classes can't inherit from them,
+ * and methods can't be added to them with the `include` function.
  */
 
 var CRS = {
@@ -1581,10 +1631,11 @@ var Earth = extend({}, CRS, {
                var rad = Math.PI / 180,
                    lat1 = latlng1.lat * rad,
                    lat2 = latlng2.lat * rad,
-                   a = Math.sin(lat1) * Math.sin(lat2) +
-                       Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad);
-
-               return this.R * Math.acos(Math.min(a, 1));
+                   sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2),
+                   sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2),
+                   a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon,
+                   c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+               return this.R * c;
        }
 });
 
@@ -1609,8 +1660,8 @@ var SphericalMercator = {
                    sin = Math.sin(lat * d);
 
                return new Point(
-                               this.R * latlng.lng * d,
-                               this.R * Math.log((1 + sin) / (1 - sin)) / 2);
+                       this.R * latlng.lng * d,
+                       this.R * Math.log((1 + sin) / (1 - sin)) / 2);
        },
 
        unproject: function (point) {
@@ -1697,7 +1748,7 @@ Transformation.prototype = {
 
 // @alternative
 // @factory L.transformation(coefficients: Array): Transformation
-// Expects an coeficients array of the form
+// Expects an coefficients array of the form
 // `[a: Number, b: Number, c: Number, d: Number]`.
 
 function toTransformation(a, b, c, d) {
@@ -1798,6 +1849,11 @@ var android = userAgentContains('android');
 // @property android23: Boolean; `true` for browsers running on Android 2 or Android 3.
 var android23 = userAgentContains('android 2') || userAgentContains('android 3');
 
+/* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */
+var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit
+// @property androidStock: Boolean; `true` for the Android stock browser (i.e. not Chrome)
+var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);
+
 // @property opera: Boolean; `true` for the Opera browser
 var opera = !!window.opera;
 
@@ -1910,6 +1966,7 @@ var Browser = (Object.freeze || Object)({
        webkit: webkit,
        android: android,
        android23: android23,
+       androidStock: androidStock,
        opera: opera,
        chrome: chrome,
        gecko: gecko,
@@ -1945,6 +2002,7 @@ var POINTER_MOVE =   msPointer ? 'MSPointerMove'   : 'pointermove';
 var POINTER_UP =     msPointer ? 'MSPointerUp'     : 'pointerup';
 var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel';
 var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION'];
+
 var _pointers = {};
 var _pointerDocListener = false;
 
@@ -1987,7 +2045,7 @@ function removePointerListener(obj, type, id) {
 
 function _addPointerStart(obj, handler, id) {
        var onDown = bind(function (e) {
-               if (e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
+               if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
                        // In IE11, some touch events needs to fire for form controls, or
                        // the controls will stop working. We keep a whitelist of tag names that
                        // need these events. For other target tags, we prevent default on the event.
@@ -2184,18 +2242,13 @@ function on(obj, types, fn, context) {
 var eventsKey = '_leaflet_events';
 
 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
-// Removes a previously added listener function. If no function is specified,
-// it will remove all the listeners of that particular DOM event from the element.
+// Removes a previously added listener function.
 // Note that if you passed a custom context to on, you must pass the same
 // context to `off` in order to remove the listener.
 
 // @alternative
 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
-
-// @alternative
-// @function off(el: HTMLElement): this
-// Removes all known event listeners
 function off(obj, types, fn, context) {
 
        if (typeof types === 'object') {
@@ -2214,6 +2267,8 @@ function off(obj, types, fn, context) {
                }
                delete obj[eventsKey];
        }
+
+       return this;
 }
 
 function addOne(obj, type, fn, context) {
@@ -2278,7 +2333,8 @@ function removeOne(obj, type, fn, context) {
        if (pointer && type.indexOf('touch') === 0) {
                removePointerListener(obj, type, id);
 
-       } else if (touch && (type === 'dblclick') && removeDoubleTapListener) {
+       } else if (touch && (type === 'dblclick') && removeDoubleTapListener &&
+                  !(pointer && chrome)) {
                removeDoubleTapListener(obj, id);
 
        } else if ('removeEventListener' in obj) {
@@ -2323,7 +2379,8 @@ function stopPropagation(e) {
 // @function disableScrollPropagation(el: HTMLElement): this
 // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
 function disableScrollPropagation(el) {
-       return addOne(el, 'mousewheel', stopPropagation);
+       addOne(el, 'mousewheel', stopPropagation);
+       return this;
 }
 
 // @function disableClickPropagation(el: HTMLElement): this
@@ -2349,7 +2406,7 @@ function preventDefault(e) {
        return this;
 }
 
-// @function stop(ev): this
+// @function stop(ev: DOMEvent): this
 // Does `stopPropagation` and `preventDefault` at the same time.
 function stop(e) {
        preventDefault(e);
@@ -2367,9 +2424,11 @@ function getMousePosition(e, container) {
 
        var rect = container.getBoundingClientRect();
 
+       var scaleX = rect.width / container.offsetWidth || 1;
+       var scaleY = rect.height / container.offsetHeight || 1;
        return new Point(
-               e.clientX - rect.left - container.clientLeft,
-               e.clientY - rect.top - container.clientTop);
+               e.clientX / scaleX - rect.left - container.clientLeft,
+               e.clientY / scaleY - rect.top - container.clientTop);
 }
 
 // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
@@ -2687,7 +2746,7 @@ function setPosition(el, point) {
 
        /*eslint-disable */
        el._leaflet_pos = point;
-       /*eslint-enable */
+       /* eslint-enable */
 
        if (any3d) {
                setTransform(el, point);
@@ -3326,7 +3385,7 @@ var Map = Evented.extend({
                        }
                }
 
-               this._moveStart(true);
+               this._moveStart(true, options.noMoveStart);
 
                frame.call(this);
                return this;
@@ -3364,10 +3423,15 @@ var Map = Evented.extend({
        // @method setMinZoom(zoom: Number): this
        // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
        setMinZoom: function (zoom) {
+               var oldZoom = this.options.minZoom;
                this.options.minZoom = zoom;
 
-               if (this._loaded && this.getZoom() < this.options.minZoom) {
-                       return this.setZoom(zoom);
+               if (this._loaded && oldZoom !== zoom) {
+                       this.fire('zoomlevelschange');
+
+                       if (this.getZoom() < this.options.minZoom) {
+                               return this.setZoom(zoom);
+                       }
                }
 
                return this;
@@ -3376,10 +3440,15 @@ var Map = Evented.extend({
        // @method setMaxZoom(zoom: Number): this
        // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
        setMaxZoom: function (zoom) {
+               var oldZoom = this.options.maxZoom;
                this.options.maxZoom = zoom;
 
-               if (this._loaded && (this.getZoom() > this.options.maxZoom)) {
-                       return this.setZoom(zoom);
+               if (this._loaded && oldZoom !== zoom) {
+                       this.fire('zoomlevelschange');
+
+                       if (this.getZoom() > this.options.maxZoom) {
+                               return this.setZoom(zoom);
+                       }
                }
 
                return this;
@@ -3400,7 +3469,7 @@ var Map = Evented.extend({
                return this;
        },
 
-       // @method invalidateSize(options: Zoom/Pan options): this
+       // @method invalidateSize(options: Zoom/pan options): this
        // Checks if the map container size changed and updates the map if so —
        // call it after you've changed the map size dynamically, also animating
        // pan by default. If `options.pan` is `false`, panning will not occur.
@@ -3573,8 +3642,7 @@ var Map = Evented.extend({
                this.fire('locationfound', data);
        },
 
-       // TODO handler.addTo
-       // TODO Appropiate docs section?
+       // TODO Appropriate docs section?
        // @section Other Methods
        // @method addHandler(name: String, HandlerClass: Function): this
        // Adds a new `Handler` to the map, given its name and constructor function.
@@ -3609,10 +3677,16 @@ var Map = Evented.extend({
                } catch (e) {
                        /*eslint-disable */
                        this._container._leaflet_id = undefined;
-                       /*eslint-enable */
+                       /* eslint-enable */
                        this._containerId = undefined;
                }
 
+               if (this._locationWatchId !== undefined) {
+                       this.stopLocate();
+               }
+
+               this._stop();
+
                remove(this._mapPane);
 
                if (this._clearControlPos) {
@@ -3991,7 +4065,7 @@ var Map = Evented.extend({
                // Pane for `GridLayer`s and `TileLayer`s
                this.createPane('tilePane');
                // @pane overlayPane: HTMLElement = 400
-               // Pane for vector overlays (`Path`s), like `Polyline`s and `Polygon`s
+               // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
                this.createPane('shadowPane');
                // @pane shadowPane: HTMLElement = 500
                // Pane for overlay shadows (e.g. `Marker` shadows)
@@ -4000,7 +4074,7 @@ var Map = Evented.extend({
                // Pane for `Icon`s of `Marker`s
                this.createPane('markerPane');
                // @pane tooltipPane: HTMLElement = 650
-               // Pane for tooltip.
+               // Pane for `Tooltip`s.
                this.createPane('tooltipPane');
                // @pane popupPane: HTMLElement = 700
                // Pane for `Popup`s.
@@ -4027,7 +4101,7 @@ var Map = Evented.extend({
 
                var zoomChanged = this._zoom !== zoom;
                this
-                       ._moveStart(zoomChanged)
+                       ._moveStart(zoomChanged, false)
                        ._move(center, zoom)
                        ._moveEnd(zoomChanged);
 
@@ -4044,7 +4118,7 @@ var Map = Evented.extend({
                }
        },
 
-       _moveStart: function (zoomChanged) {
+       _moveStart: function (zoomChanged, noMoveStart) {
                // @event zoomstart: Event
                // Fired when the map zoom is about to change (e.g. before zoom animation).
                // @event movestart: Event
@@ -4052,7 +4126,10 @@ var Map = Evented.extend({
                if (zoomChanged) {
                        this.fire('zoomstart');
                }
-               return this.fire('movestart');
+               if (!noMoveStart) {
+                       this.fire('movestart');
+               }
+               return this;
        },
 
        _move: function (center, zoom, data) {
@@ -4254,9 +4331,9 @@ var Map = Evented.extend({
                };
 
                if (e.type !== 'keypress') {
-                       var isMarker = (target.options && 'icon' in target.options);
+                       var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
                        data.containerPoint = isMarker ?
-                                       this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
+                               this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
                        data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
                        data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
                }
@@ -4415,7 +4492,7 @@ var Map = Evented.extend({
 
        _tryAnimatedPan: function (center, options) {
                // difference between the new and current centers in pixels
-               var offset = this._getCenterOffset(center)._floor();
+               var offset = this._getCenterOffset(center)._trunc();
 
                // don't animate too far unless animate: true specified in options
                if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
@@ -4485,7 +4562,7 @@ var Map = Evented.extend({
 
                requestAnimFrame(function () {
                        this
-                           ._moveStart(true)
+                           ._moveStart(true, false)
                            ._animateZoom(center, zoom, true);
                }, this);
 
@@ -4493,6 +4570,8 @@ var Map = Evented.extend({
        },
 
        _animateZoom: function (center, zoom, startAnim, noUpdate) {
+               if (!this._mapPane) { return; }
+
                if (startAnim) {
                        this._animatingZoom = true;
 
@@ -4518,7 +4597,9 @@ var Map = Evented.extend({
        _onZoomTransitionEnd: function () {
                if (!this._animatingZoom) { return; }
 
-               removeClass(this._mapPane, 'leaflet-zoom-anim');
+               if (this._mapPane) {
+                       removeClass(this._mapPane, 'leaflet-zoom-anim');
+               }
 
                this._animatingZoom = false;
 
@@ -4912,13 +4993,6 @@ var Layers = Control.extend({
                        on(link, 'focus', this.expand, this);
                }
 
-               // work around for Firefox Android issue https://github.com/Leaflet/Leaflet/issues/2033
-               on(form, 'click', function () {
-                       setTimeout(bind(this._onInputClick, this), 0);
-               }, this);
-
-               // TODO keyboard accessibility
-
                if (!collapsed) {
                        this.expand();
                }
@@ -4951,7 +5025,7 @@ var Layers = Control.extend({
                });
 
                if (this.options.sortLayers) {
-                       this._layers.sort(L.bind(function (a, b) {
+                       this._layers.sort(bind(function (a, b) {
                                return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
                        }, this));
                }
@@ -5068,7 +5142,7 @@ var Layers = Control.extend({
 
        _onInputClick: function () {
                var inputs = this._layerControlInputs,
-                   input, layer, hasLayer;
+                   input, layer;
                var addedLayers = [],
                    removedLayers = [];
 
@@ -5077,22 +5151,24 @@ var Layers = Control.extend({
                for (var i = inputs.length - 1; i >= 0; i--) {
                        input = inputs[i];
                        layer = this._getLayer(input.layerId).layer;
-                       hasLayer = this._map.hasLayer(layer);
 
-                       if (input.checked && !hasLayer) {
+                       if (input.checked) {
                                addedLayers.push(layer);
-
-                       } else if (!input.checked && hasLayer) {
+                       } else if (!input.checked) {
                                removedLayers.push(layer);
                        }
                }
 
                // Bugfix issue 2318: Should remove all old layers before readding new ones
                for (i = 0; i < removedLayers.length; i++) {
-                       this._map.removeLayer(removedLayers[i]);
+                       if (this._map.hasLayer(removedLayers[i])) {
+                               this._map.removeLayer(removedLayers[i]);
+                       }
                }
                for (i = 0; i < addedLayers.length; i++) {
-                       this._map.addLayer(addedLayers[i]);
+                       if (!this._map.hasLayer(addedLayers[i])) {
+                               this._map.addLayer(addedLayers[i]);
+                       }
                }
 
                this._handlingClick = false;
@@ -5341,8 +5417,8 @@ var Scale = Control.extend({
                    y = map.getSize().y / 2;
 
                var maxMeters = map.distance(
-                               map.containerPointToLatLng([0, y]),
-                               map.containerPointToLatLng([this.options.maxWidth, y]));
+                       map.containerPointToLatLng([0, y]),
+                       map.containerPointToLatLng([this.options.maxWidth, y]));
 
                this._updateScales(maxMeters);
        },
@@ -5584,6 +5660,14 @@ var Handler = Class.extend({
        // Called when the handler is disabled, should remove the event hooks added previously.
 });
 
+// @section There is static function which can be called without instantiating L.Handler:
+// @function addTo(map: Map, name: String): this
+// Adds a new Handler to the given map with the given name.
+Handler.addTo = function (map, name) {
+       map.addHandler(name, this);
+       return this;
+};
+
 var Mixin = {Events: Events};
 
 /*
@@ -5602,7 +5686,6 @@ var Mixin = {Events: Events};
  * ```
  */
 
-var _dragging = false;
 var START = touch ? 'touchstart mousedown' : 'mousedown';
 var END = {
        mousedown: 'mouseup',
@@ -5656,7 +5739,7 @@ var Draggable = Evented.extend({
 
                // If we're currently dragging this draggable,
                // disabling it counts as first ending the drag.
-               if (L.Draggable._dragging === this) {
+               if (Draggable._dragging === this) {
                        this.finishDrag();
                }
 
@@ -5678,8 +5761,8 @@ var Draggable = Evented.extend({
 
                if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
 
-               if (_dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
-               _dragging = this;  // Prevent dragging multiple objects at once.
+               if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
+               Draggable._dragging = this;  // Prevent dragging multiple objects at once.
 
                if (this._preventOutline) {
                        preventOutline(this._element);
@@ -5803,7 +5886,7 @@ var Draggable = Evented.extend({
                }
 
                this._moving = false;
-               _dragging = false;
+               Draggable._dragging = false;
        }
 
 });
@@ -5811,7 +5894,7 @@ var Draggable = Evented.extend({
 /*
  * @namespace LineUtil
  *
- * Various utility functions for polyine points processing, used by Leaflet internally to make polylines lightning-fast.
+ * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
  */
 
 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
@@ -6036,11 +6119,17 @@ function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
 }
 
 
-function _flat(latlngs) {
-       // true if it's a flat array of latlngs; false if nested
+// @function isFlat(latlngs: LatLng[]): Boolean
+// Returns true if `latlngs` is a flat array, false is nested.
+function isFlat(latlngs) {
        return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
 }
 
+function _flat(latlngs) {
+       console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
+       return isFlat(latlngs);
+}
+
 
 var LineUtil = (Object.freeze || Object)({
        simplify: simplify,
@@ -6050,6 +6139,7 @@ var LineUtil = (Object.freeze || Object)({
        _getEdgeIntersection: _getEdgeIntersection,
        _getBitCode: _getBitCode,
        _sqClosestPointOnSegment: _sqClosestPointOnSegment,
+       isFlat: isFlat,
        _flat: _flat
 });
 
@@ -6059,10 +6149,10 @@ var LineUtil = (Object.freeze || Object)({
  */
 
 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
- * 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)).
+ * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)).
  * Used by Leaflet to only show polygon points that are on the screen or near, increasing
  * performance. Note that polygon points needs different algorithm for clipping
- * than polyline, so there's a seperate method for it.
+ * than polyline, so there's a separate method for it.
  */
 function clipPolygon(points, bounds, round) {
        var clippedPoints,
@@ -6200,6 +6290,10 @@ var Mercator = {
  * The inverse of `project`. Projects a 2D point into a geographical location.
  * Only accepts actual `L.Point` instances, not arrays.
 
+ * Note that the projection instances do not inherit from Leafet's `Class` object,
+ * and can't be instantiated. Also, new classes can't inherit from them,
+ * and methods can't be added to them with the `include` function.
+
  */
 
 
@@ -6328,8 +6422,8 @@ var Layer = Evented.extend({
        /* @section
         * Classes extending `L.Layer` will inherit the following methods:
         *
-        * @method addTo(map: Map): this
-        * Adds the layer to the given map
+        * @method addTo(map: Map|LayerGroup): this
+        * Adds the layer to the given map or layer group.
         */
        addTo: function (map) {
                map.addLayer(this);
@@ -6438,6 +6532,10 @@ Map.include({
        // @method addLayer(layer: Layer): this
        // Adds the given layer to the map
        addLayer: function (layer) {
+               if (!layer._layerAdd) {
+                       throw new Error('The provided object is not a Layer.');
+               }
+
                var id = stamp(layer);
                if (this._layers[id]) { return this; }
                this._layers[id] = layer;
@@ -6577,7 +6675,9 @@ Map.include({
 
 var LayerGroup = Layer.extend({
 
-       initialize: function (layers) {
+       initialize: function (layers, options) {
+               setOptions(this, options);
+
                this._layers = {};
 
                var i, len;
@@ -6632,10 +6732,7 @@ var LayerGroup = Layer.extend({
        // @method clearLayers(): this
        // Removes all the layers from the group.
        clearLayers: function () {
-               for (var i in this._layers) {
-                       this.removeLayer(this._layers[i]);
-               }
-               return this;
+               return this.eachLayer(this.removeLayer, this);
        },
 
        // @method invoke(methodName: String, …): this
@@ -6658,15 +6755,11 @@ var LayerGroup = Layer.extend({
        },
 
        onAdd: function (map) {
-               for (var i in this._layers) {
-                       map.addLayer(this._layers[i]);
-               }
+               this.eachLayer(map.addLayer, map);
        },
 
        onRemove: function (map) {
-               for (var i in this._layers) {
-                       map.removeLayer(this._layers[i]);
-               }
+               this.eachLayer(map.removeLayer, map);
        },
 
        // @method eachLayer(fn: Function, context?: Object): this
@@ -6693,10 +6786,7 @@ var LayerGroup = Layer.extend({
        // Returns an array of all the layers added to the group.
        getLayers: function () {
                var layers = [];
-
-               for (var i in this._layers) {
-                       layers.push(this._layers[i]);
-               }
+               this.eachLayer(layers.push, layers);
                return layers;
        },
 
@@ -6714,10 +6804,10 @@ var LayerGroup = Layer.extend({
 });
 
 
-// @factory L.layerGroup(layers: Layer[])
-// Create a layer group, optionally given an initial set of layers.
-var layerGroup = function (layers) {
-       return new LayerGroup(layers);
+// @factory L.layerGroup(layers?: Layer[], options?: Object)
+// Create a layer group, optionally given an initial set of layers and an `options` object.
+var layerGroup = function (layers, options) {
+       return new LayerGroup(layers, options);
 };
 
 /*
@@ -6788,7 +6878,7 @@ var FeatureGroup = LayerGroup.extend({
        },
 
        // @method bringToBack(): this
-       // Brings the layer group to the top of all other layers
+       // Brings the layer group to the back of all other layers
        bringToBack: function () {
                return this.invoke('bringToBack');
        },
@@ -6860,9 +6950,12 @@ var Icon = Class.extend({
         * will be aligned so that this point is at the marker's geographical location. Centered
         * by default if size is specified, also can be set in CSS with negative margins.
         *
-        * @option popupAnchor: Point = null
+        * @option popupAnchor: Point = [0, 0]
         * The coordinates of the point from which popups will "open", relative to the icon anchor.
         *
+        * @option tooltipAnchor: Point = [0, 0]
+        * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
+        *
         * @option shadowUrl: String = null
         * The URL to the icon shadow image. If not specified, no shadow image will be created.
         *
@@ -6879,6 +6972,11 @@ var Icon = Class.extend({
         * A custom class name to assign to both icon and shadow images. Empty by default.
         */
 
+       options: {
+               popupAnchor: [0, 0],
+               tooltipAnchor: [0, 0],
+       },
+
        initialize: function (options) {
                setOptions(this, options);
        },
@@ -6990,9 +7088,9 @@ var IconDefault = Icon.extend({
                }
 
                // @option imagePath: String
-               // `Icon.Default` will try to auto-detect the absolute location of the
+               // `Icon.Default` will try to auto-detect the location of the
                // blue icon images. If you are placing these images in a non-standard
-               // way, set this option to point to the right absolute path.
+               // way, set this option to point to the right path.
                return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
        },
 
@@ -7006,7 +7104,7 @@ var IconDefault = Icon.extend({
                if (path === null || path.indexOf('url') !== 0) {
                        path = '';
                } else {
-                       path = path.replace(/^url\([\"\']?/, '').replace(/marker-icon\.png[\"\']?\)$/, '');
+                       path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, '');
                }
 
                return path;
@@ -7045,6 +7143,7 @@ var MarkerDrag = Handler.extend({
 
                this._draggable.on({
                        dragstart: this._onDragStart,
+                       predrag: this._onPreDrag,
                        drag: this._onDrag,
                        dragend: this._onDragEnd
                }, this).enable();
@@ -7055,6 +7154,7 @@ var MarkerDrag = Handler.extend({
        removeHooks: function () {
                this._draggable.off({
                        dragstart: this._onDragStart,
+                       predrag: this._onPreDrag,
                        drag: this._onDrag,
                        dragend: this._onDragEnd
                }, this).disable();
@@ -7068,6 +7168,42 @@ var MarkerDrag = Handler.extend({
                return this._draggable && this._draggable._moved;
        },
 
+       _adjustPan: function (e) {
+               var marker = this._marker,
+                   map = marker._map,
+                   speed = this._marker.options.autoPanSpeed,
+                   padding = this._marker.options.autoPanPadding,
+                   iconPos = L.DomUtil.getPosition(marker._icon),
+                   bounds = map.getPixelBounds(),
+                   origin = map.getPixelOrigin();
+
+               var panBounds = toBounds(
+                       bounds.min._subtract(origin).add(padding),
+                       bounds.max._subtract(origin).subtract(padding)
+               );
+
+               if (!panBounds.contains(iconPos)) {
+                       // Compute incremental movement
+                       var movement = toPoint(
+                               (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
+                               (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
+
+                               (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
+                               (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
+                       ).multiplyBy(speed);
+
+                       map.panBy(movement, {animate: false});
+
+                       this._draggable._newPos._add(movement);
+                       this._draggable._startPos._add(movement);
+
+                       L.DomUtil.setPosition(marker._icon, this._draggable._newPos);
+                       this._onDrag(e);
+
+                       this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
+               }
+       },
+
        _onDragStart: function () {
                // @section Dragging events
                // @event dragstart: Event
@@ -7083,6 +7219,13 @@ var MarkerDrag = Handler.extend({
                    .fire('dragstart');
        },
 
+       _onPreDrag: function (e) {
+               if (this._marker.options.autoPan) {
+                       cancelAnimFrame(this._panRequest);
+                       this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
+               }
+       },
+
        _onDrag: function (e) {
                var marker = this._marker,
                    shadow = marker._shadow,
@@ -7109,6 +7252,8 @@ var MarkerDrag = Handler.extend({
                // @event dragend: DragEndEvent
                // Fired when the user stops dragging the marker.
 
+                cancelAnimFrame(this._panRequest);
+
                // @event moveend: Event
                // Fired when the marker stops moving (because of dragging).
                delete this._oldLatLng;
@@ -7149,6 +7294,18 @@ var Marker = Layer.extend({
                // Whether the marker is draggable with mouse/touch or not.
                draggable: false,
 
+               // @option autoPan: Boolean = false
+               // Set it to `true` if you want the map to do panning animation when marker hits the edges.
+               autoPan: false,
+
+               // @option autoPanPadding: Point = Point(50, 50)
+               // Equivalent of setting both top left and bottom right autopan padding to the same value.
+               autoPanPadding: [50, 50],
+
+               // @option autoPanSpeed: Number = 10
+               // Number of pixels the map should move by.
+               autoPanSpeed: 10,
+
                // @option keyboard: Boolean = true
                // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
                keyboard: true,
@@ -7279,7 +7436,7 @@ var Marker = Layer.extend({
 
        update: function () {
 
-               if (this._icon) {
+               if (this._icon && this._map) {
                        var pos = this._map.latLngToLayerPoint(this._latlng).round();
                        this._setPos(pos);
                }
@@ -7304,8 +7461,9 @@ var Marker = Layer.extend({
                        if (options.title) {
                                icon.title = options.title;
                        }
-                       if (options.alt) {
-                               icon.alt = options.alt;
+
+                       if (icon.tagName === 'IMG') {
+                               icon.alt = options.alt || '';
                        }
                }
 
@@ -7449,11 +7607,11 @@ var Marker = Layer.extend({
        },
 
        _getPopupAnchor: function () {
-               return this.options.icon.options.popupAnchor || [0, 0];
+               return this.options.icon.options.popupAnchor;
        },
 
        _getTooltipAnchor: function () {
-               return this.options.icon.options.tooltipAnchor || [0, 0];
+               return this.options.icon.options.tooltipAnchor;
        }
 });
 
@@ -7604,7 +7762,7 @@ var Path = Layer.extend({
 
        _clickTolerance: function () {
                // used when doing hit detection for Canvas layers
-               return (this.options.stroke ? this.options.weight / 2 : 0) + (touch ? 10 : 0);
+               return (this.options.stroke ? this.options.weight / 2 : 0) + this._renderer.options.tolerance;
        }
 });
 
@@ -7789,8 +7947,8 @@ var Circle = CircleMarker.extend({
                        }
 
                        this._point = p.subtract(map.getPixelOrigin());
-                       this._radius = isNaN(lngR) ? 0 : Math.max(Math.round(p.x - map.project([lat2, lng - lngR]).x), 1);
-                       this._radiusY = Math.max(Math.round(p.y - top.y), 1);
+                       this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
+                       this._radiusY = p.y - top.y;
 
                } else {
                        var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
@@ -7892,6 +8050,8 @@ var Polyline = Path.extend({
                return !this._latlngs.length;
        },
 
+       // @method closestLayerPoint: Point
+       // Returns the point closest to `p` on the Polyline.
        closestLayerPoint: function (p) {
                var minDistance = Infinity,
                    minPoint = null,
@@ -7984,13 +8144,13 @@ var Polyline = Path.extend({
        },
 
        _defaultShape: function () {
-               return _flat(this._latlngs) ? this._latlngs : this._latlngs[0];
+               return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
        },
 
        // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
        _convertLatLngs: function (latlngs) {
                var result = [],
-                   flat = _flat(latlngs);
+                   flat = isFlat(latlngs);
 
                for (var i = 0, len = latlngs.length; i < len; i++) {
                        if (flat) {
@@ -8130,6 +8290,9 @@ function polyline(latlngs, options) {
        return new Polyline(latlngs, options);
 }
 
+// Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
+Polyline._flat = _flat;
+
 /*
  * @class Polygon
  * @aka L.Polygon
@@ -8234,13 +8397,13 @@ var Polygon = Polyline.extend({
 
        _setLatLngs: function (latlngs) {
                Polyline.prototype._setLatLngs.call(this, latlngs);
-               if (_flat(this._latlngs)) {
+               if (isFlat(this._latlngs)) {
                        this._latlngs = [this._latlngs];
                }
        },
 
        _defaultShape: function () {
-               return _flat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
+               return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
        },
 
        _clipPoints: function () {
@@ -8529,8 +8692,8 @@ function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
 
        for (var i = 0, len = coords.length, latlng; i < len; i++) {
                latlng = levelsDeep ?
-                               coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
-                               (_coordsToLatLng || coordsToLatLng)(coords[i]);
+                       coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
+                       (_coordsToLatLng || coordsToLatLng)(coords[i]);
 
                latlngs.push(latlng);
        }
@@ -8543,8 +8706,8 @@ function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
 function latLngToCoords(latlng, precision) {
        precision = typeof precision === 'number' ? precision : 6;
        return latlng.alt !== undefined ?
-                       [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
-                       [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
+               [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
+               [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
 }
 
 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
@@ -8568,8 +8731,8 @@ function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
 
 function getFeature(layer, newGeometry) {
        return layer.feature ?
-                       extend({}, layer.feature, {geometry: newGeometry}) :
-                       asFeature(newGeometry);
+               extend({}, layer.feature, {geometry: newGeometry}) :
+               asFeature(newGeometry);
 }
 
 // @function asFeature(geojson: Object): Object
@@ -8612,7 +8775,7 @@ CircleMarker.include(PointToGeoJSON);
 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
 Polyline.include({
        toGeoJSON: function (precision) {
-               var multi = !_flat(this._latlngs);
+               var multi = !isFlat(this._latlngs);
 
                var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
 
@@ -8628,8 +8791,8 @@ Polyline.include({
 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
 Polygon.include({
        toGeoJSON: function (precision) {
-               var holes = !_flat(this._latlngs),
-                   multi = holes && !_flat(this._latlngs[0]);
+               var holes = !isFlat(this._latlngs),
+                   multi = holes && !isFlat(this._latlngs[0]);
 
                var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
 
@@ -8848,7 +9011,7 @@ var ImageOverlay = Layer.extend({
        // @method setBounds(bounds: LatLngBounds): this
        // Update the bounds that this ImageOverlay covers
        setBounds: function (bounds) {
-               this._bounds = bounds;
+               this._bounds = toLatLngBounds(bounds);
 
                if (this._map) {
                        this._reset();
@@ -8891,9 +9054,12 @@ var ImageOverlay = Layer.extend({
        },
 
        _initImage: function () {
-               var img = this._image = create$1('img',
-                               'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : '') +
-                                (this.options.className || ''));
+               var wasElementSupplied = this._url.tagName === 'IMG';
+               var img = this._image = wasElementSupplied ? this._url : create$1('img');
+
+               addClass(img, 'leaflet-image-layer');
+               if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
+               if (this.options.className) { addClass(img, this.options.className); }
 
                img.onselectstart = falseFn;
                img.onmousemove = falseFn;
@@ -8911,6 +9077,11 @@ var ImageOverlay = Layer.extend({
                        this._updateZIndex();
                }
 
+               if (wasElementSupplied) {
+                       this._url = img.src;
+                       return;
+               }
+
                img.src = this._url;
                img.alt = this.options.alt;
        },
@@ -8979,8 +9150,8 @@ var imageOverlay = function (url, bounds, options) {
  *
  * ```js
  * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
- *     imageBounds = [[ 32, -130], [ 13, -100]];
- * L.imageOverlay(imageUrl, imageBounds).addTo(map);
+ *     videoBounds = [[ 32, -130], [ 13, -100]];
+ * L.VideoOverlay(videoUrl, videoBounds ).addTo(map);
  * ```
  */
 
@@ -8999,8 +9170,11 @@ var VideoOverlay = ImageOverlay.extend({
        },
 
        _initImage: function () {
-               var vid = this._image = create$1('video',
-                       'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : ''));
+               var wasElementSupplied = this._url.tagName === 'VIDEO';
+               var vid = this._image = wasElementSupplied ? this._url : create$1('video');
+
+               addClass(vid, 'leaflet-image-layer');
+               if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
 
                vid.onselectstart = falseFn;
                vid.onmousemove = falseFn;
@@ -9009,6 +9183,17 @@ var VideoOverlay = ImageOverlay.extend({
                // Fired when the video has finished loading the first frame
                vid.onloadeddata = bind(this.fire, this, 'load');
 
+               if (wasElementSupplied) {
+                       var sourceElements = vid.getElementsByTagName('source');
+                       var sources = [];
+                       for (var j = 0; j < sourceElements.length; j++) {
+                               sources.push(sourceElements[j].src);
+                       }
+
+                       this._url = (sourceElements.length > 0) ? sources : [vid.src];
+                       return;
+               }
+
                if (!isArray(this._url)) { this._url = [this._url]; }
 
                vid.autoplay = !!this.options.autoplay;
@@ -9026,11 +9211,12 @@ var VideoOverlay = ImageOverlay.extend({
 });
 
 
-// @factory L.videoOverlay(videoUrl: String|Array, bounds: LatLngBounds, options?: VideoOverlay options)
-// Instantiates an image overlay object given the URL of the video (or array of URLs) and the
+// @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
+// Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
 // geographical bounds it is tied to.
-function videoOverlay(url, bounds, options) {
-       return new VideoOverlay(url, bounds, options);
+
+function videoOverlay(video, bounds, options) {
+       return new VideoOverlay(video, bounds, options);
 }
 
 /*
@@ -9310,6 +9496,11 @@ var Popup = DivOverlay.extend({
                // the popup closing when another popup is opened.
                autoClose: true,
 
+               // @option closeOnEscapeKey: Boolean = true
+               // Set it to `false` if you want to override the default behavior of
+               // the ESC key for closing of the popup.
+               closeOnEscapeKey: true,
+
                // @option closeOnClick: Boolean = *
                // Set it if you want to override the default behavior of the popup closing when user clicks
                // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
@@ -9588,7 +9779,7 @@ Layer.include({
 
        // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
        // Binds a popup to the layer with the passed `content` and sets up the
-       // neccessary event listeners. If a `Function` is passed it will receive
+       // necessary event listeners. If a `Function` is passed it will receive
        // the layer as the first argument and should return a `String` or `HTMLElement`.
        bindPopup: function (content, options) {
 
@@ -9633,7 +9824,7 @@ Layer.include({
        },
 
        // @method openPopup(latlng?: LatLng): this
-       // Opens the bound popup at the specificed `latlng` or at the default popup anchor if no `latlng` is passed.
+       // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
        openPopup: function (layer, latlng) {
                if (!(layer instanceof Layer)) {
                        latlng = layer;
@@ -9761,7 +9952,7 @@ Layer.include({
  * marker.bindTooltip("my tooltip text").openTooltip();
  * ```
  * Note about tooltip offset. Leaflet takes two options in consideration
- * for computing tooltip offseting:
+ * for computing tooltip offsetting:
  * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
  *   Add a positive x offset to move the tooltip to the right, and a positive y offset to
  *   move it to the bottom. Negatives will move to the left and top.
@@ -9787,7 +9978,7 @@ var Tooltip = DivOverlay.extend({
                // @option direction: String = 'auto'
                // Direction where to open the tooltip. Possible values are: `right`, `left`,
                // `top`, `bottom`, `center`, `auto`.
-               // `auto` will dynamicaly switch between `right` and `left` according to the tooltip
+               // `auto` will dynamically switch between `right` and `left` according to the tooltip
                // position on the map.
                direction: 'auto',
 
@@ -9991,7 +10182,7 @@ Layer.include({
 
        // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
        // Binds a tooltip to the layer with the passed `content` and sets up the
-       // neccessary event listeners. If a `Function` is passed it will receive
+       // necessary event listeners. If a `Function` is passed it will receive
        // the layer as the first argument and should return a `String` or `HTMLElement`.
        bindTooltip: function (content, options) {
 
@@ -10051,7 +10242,7 @@ Layer.include({
        },
 
        // @method openTooltip(latlng?: LatLng): this
-       // Opens the bound tooltip at the specificed `latlng` or at the default tooltip anchor if no `latlng` is passed.
+       // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
        openTooltip: function (layer, latlng) {
                if (!(layer instanceof Layer)) {
                        latlng = layer;
@@ -10304,8 +10495,11 @@ var GridLayer = Layer.extend({
                // Opacity of the tiles. Can be used in the `createTile()` function.
                opacity: 1,
 
-               // @option updateWhenIdle: Boolean = depends
-               // If `false`, new tiles are loaded during panning, otherwise only after it (for better performance). `true` by default on mobile browsers, otherwise `false`.
+               // @option updateWhenIdle: Boolean = (depends)
+               // Load new tiles only when panning ends.
+               // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
+               // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
+               // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
                updateWhenIdle: mobile,
 
                // @option updateWhenZooming: Boolean = true
@@ -10388,7 +10582,7 @@ var GridLayer = Layer.extend({
                remove(this._container);
                map._removeZoomLimit(this);
                this._container = null;
-               this._tileZoom = null;
+               this._tileZoom = undefined;
        },
 
        // @method bringToFront: this
@@ -10477,7 +10671,7 @@ var GridLayer = Layer.extend({
        // @section Extension methods
        // Layers extending `GridLayer` shall reimplement the following method.
        // @method createTile(coords: Object, done?: Function): HTMLElement
-       // Called only internally, must be overriden by classes extending `GridLayer`.
+       // Called only internally, must be overridden by classes extending `GridLayer`.
        // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
        // is specified, it must be called when the tile has finished loading and drawing.
        createTile: function () {
@@ -10682,7 +10876,7 @@ var GridLayer = Layer.extend({
                }
                this._removeAllTiles();
 
-               this._tileZoom = null;
+               this._tileZoom = undefined;
        },
 
        _retainParent: function (x, y, z, minZoom) {
@@ -10892,7 +11086,10 @@ var GridLayer = Layer.extend({
 
                                if (!this._isValidTile(coords)) { continue; }
 
-                               if (!this._tiles[this._tileCoordsToKey(coords)]) {
+                               var tile = this._tiles[this._tileCoordsToKey(coords)];
+                               if (tile) {
+                                       tile.current = true;
+                               } else {
                                        queue.push(coords);
                                }
                        }
@@ -10944,26 +11141,26 @@ var GridLayer = Layer.extend({
                return this._tileCoordsToBounds(this._keyToTileCoords(key));
        },
 
-       // converts tile coordinates to its geographical bounds
-       _tileCoordsToBounds: function (coords) {
-
+       _tileCoordsToNwSe: function (coords) {
                var map = this._map,
                    tileSize = this.getTileSize(),
-
                    nwPoint = coords.scaleBy(tileSize),
                    sePoint = nwPoint.add(tileSize),
-
                    nw = map.unproject(nwPoint, coords.z),
-                   se = map.unproject(sePoint, coords.z),
-                   bounds = new LatLngBounds(nw, se);
+                   se = map.unproject(sePoint, coords.z);
+               return [nw, se];
+       },
+
+       // converts tile coordinates to its geographical bounds
+       _tileCoordsToBounds: function (coords) {
+               var bp = this._tileCoordsToNwSe(coords),
+                   bounds = new LatLngBounds(bp[0], bp[1]);
 
                if (!this.options.noWrap) {
-                       map.wrapLatLngBounds(bounds);
+                       bounds = this._map.wrapLatLngBounds(bounds);
                }
-
                return bounds;
        },
-
        // converts tile coordinates to key for the tile cache
        _tileCoordsToKey: function (coords) {
                return coords.x + ':' + coords.y + ':' + coords.z;
@@ -10981,6 +11178,12 @@ var GridLayer = Layer.extend({
                var tile = this._tiles[key];
                if (!tile) { return; }
 
+               // Cancels any pending http requests associated with the tile
+               // unless we're on Android's stock browser,
+               // see https://github.com/Leaflet/Leaflet/issues/137
+               if (!androidStock) {
+                       tile.el.setAttribute('src', emptyImageUrl);
+               }
                remove(tile.el);
 
                delete this._tiles[key];
@@ -11157,7 +11360,7 @@ function gridLayer(options) {
  * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
  * ```
  *
- * `{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.
+ * `{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 "&commat;2x" to the URL to load retina tiles.
  *
  * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
  *
@@ -11254,7 +11457,7 @@ var TileLayer = GridLayer.extend({
 
        // @method createTile(coords: Object, done?: Function): HTMLElement
        // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
-       // to return an `<img>` HTML element with the appropiate image URL given `coords`. The `done`
+       // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
        // callback is called when the tile has been loaded.
        createTile: function (coords, done) {
                var tile = document.createElement('img');
@@ -11319,7 +11522,7 @@ var TileLayer = GridLayer.extend({
 
        _tileOnError: function (done, tile, e) {
                var errorUrl = this.options.errorTileUrl;
-               if (errorUrl && tile.src !== errorUrl) {
+               if (errorUrl && tile.getAttribute('src') !== errorUrl) {
                        tile.src = errorUrl;
                }
                done(e, tile);
@@ -11360,6 +11563,7 @@ var TileLayer = GridLayer.extend({
                                if (!tile.complete) {
                                        tile.src = emptyImageUrl;
                                        remove(tile);
+                                       delete this._tiles[i];
                                }
                        }
                }
@@ -11450,7 +11654,10 @@ var TileLayerWMS = TileLayer.extend({
 
                options = setOptions(this, options);
 
-               wmsParams.width = wmsParams.height = options.tileSize * (options.detectRetina && retina ? 2 : 1);
+               var realRetina = options.detectRetina && retina ? 2 : 1;
+               var tileSize = this.getTileSize();
+               wmsParams.width = tileSize.x * realRetina;
+               wmsParams.height = tileSize.y * realRetina;
 
                this.wmsParams = wmsParams;
        },
@@ -11468,16 +11675,15 @@ var TileLayerWMS = TileLayer.extend({
 
        getTileUrl: function (coords) {
 
-               var tileBounds = this._tileCoordsToBounds(coords),
-                   nw = this._crs.project(tileBounds.getNorthWest()),
-                   se = this._crs.project(tileBounds.getSouthEast()),
-
+               var tileBounds = this._tileCoordsToNwSe(coords),
+                   crs = this._crs,
+                   bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
+                   min = bounds.min,
+                   max = bounds.max,
                    bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
-                           [se.y, nw.x, nw.y, se.x] :
-                           [nw.x, se.y, se.x, nw.y]).join(','),
-
-                   url = TileLayer.prototype.getTileUrl.call(this, coords);
-
+                   [min.y, min.x, max.y, max.x] :
+                   [min.x, min.y, max.x, max.y]).join(','),
+               url = L.TileLayer.prototype.getTileUrl.call(this, coords);
                return url +
                        getParamString(this.wmsParams, url, this.options.uppercase) +
                        (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
@@ -11535,7 +11741,11 @@ var Renderer = Layer.extend({
                // @option padding: Number = 0.1
                // How much to extend the clip area around the map view (relative to its size)
                // e.g. 0.1 would be 10% of map view in each direction
-               padding: 0.1
+               padding: 0.1,
+
+               // @option tolerance: Number = 0
+               // How much to extend click tolerance round a path/object on the map
+               tolerance : 0
        },
 
        initialize: function (options) {
@@ -11925,8 +12135,8 @@ var Canvas = Renderer.extend({
 
                var p = layer._point,
                    ctx = this._ctx,
-                   r = layer._radius,
-                   s = (layer._radiusY || r) / r;
+                   r = Math.max(Math.round(layer._radius), 1),
+                   s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
 
                this._drawnLayers[layer._leaflet_id] = layer;
 
@@ -12047,7 +12257,7 @@ var Canvas = Renderer.extend({
                        prev.next = next;
                } else if (next) {
                        // Update first entry unless this is the
-                       // signle entry
+                       // single entry
                        this._drawFirst = next;
                }
 
@@ -12075,7 +12285,7 @@ var Canvas = Renderer.extend({
                        next.prev = prev;
                } else if (prev) {
                        // Update last entry unless this is the
-                       // signle entry
+                       // single entry
                        this._drawLast = prev;
                }
 
@@ -12219,7 +12429,7 @@ var vmlMixin = {
                    r2 = Math.round(layer._radiusY || r);
 
                this._setPath(layer, layer._empty() ? 'M0 0' :
-                               'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
+                       'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
        },
 
        _setPath: function (layer, path) {
@@ -12296,6 +12506,7 @@ var SVG = Renderer.extend({
                off(this._container);
                delete this._container;
                delete this._rootGroup;
+               delete this._svgSize;
        },
 
        _onZoomStart: function () {
@@ -12408,15 +12619,15 @@ var SVG = Renderer.extend({
 
        _updateCircle: function (layer) {
                var p = layer._point,
-                   r = layer._radius,
-                   r2 = layer._radiusY || r,
+                   r = Math.max(Math.round(layer._radius), 1),
+                   r2 = Math.max(Math.round(layer._radiusY), 1) || r,
                    arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
 
                // drawing a circle with two half-arcs
                var d = layer._empty() ? 'M0 0' :
-                               'M' + (p.x - r) + ',' + p.y +
-                               arc + (r * 2) + ',0 ' +
-                               arc + (-r * 2) + ',0 ';
+                       'M' + (p.x - r) + ',' + p.y +
+                       arc + (r * 2) + ',0 ' +
+                       arc + (-r * 2) + ',0 ';
 
                this._setPath(layer, d);
        },
@@ -12439,6 +12650,7 @@ if (vml) {
        SVG.include(vmlMixin);
 }
 
+// @namespace SVG
 // @factory L.svg(options?: Renderer options)
 // Creates a SVG renderer with the given options.
 function svg$1(options) {
@@ -12489,7 +12701,7 @@ Map.include({
 
 /*
  * @class Rectangle
- * @aka L.Retangle
+ * @aka L.Rectangle
  * @inherits Polygon
  *
  * A class for drawing rectangle overlays on a map. Extends `Polygon`.
@@ -12867,10 +13079,7 @@ var Drag = Handler.extend({
                        this._positions.push(pos);
                        this._times.push(time);
 
-                       if (time - this._times[0] > 50) {
-                               this._positions.shift();
-                               this._times.shift();
-                       }
+                       this._prunePositions(time);
                }
 
                this._map
@@ -12878,6 +13087,13 @@ var Drag = Handler.extend({
                    .fire('drag', e);
        },
 
+       _prunePositions: function (time) {
+               while (this._positions.length > 1 && time - this._times[0] > 50) {
+                       this._positions.shift();
+                       this._times.shift();
+               }
+       },
+
        _onZoomEnd: function () {
                var pxCenter = this._map.getSize().divideBy(2),
                    pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
@@ -12930,6 +13146,7 @@ var Drag = Handler.extend({
                        map.fire('moveend');
 
                } else {
+                       this._prunePositions(+new Date());
 
                        var direction = this._lastPos.subtract(this._positions[0]),
                            duration = (this._lastTime - this._times[0]) / 1000,
@@ -13126,7 +13343,7 @@ var Keyboard = Handler.extend({
                } else if (key in this._zoomKeys) {
                        map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
 
-               } else if (key === 27 && map._popup) {
+               } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
                        map.closePopup();
 
                } else {
@@ -13444,7 +13661,7 @@ var TouchZoom = Handler.extend({
                }
 
                if (!this._moved) {
-                       map._moveStart(true);
+                       map._moveStart(true, false);
                        this._moved = true;
                }
 
@@ -13501,6 +13718,8 @@ function noConflict() {
 // Always export us to window global (see #2364)
 window.L = exports;
 
+Object.freeze = freeze;
+
 exports.version = version;
 exports.noConflict = noConflict;
 exports.Control = Control;