]> git.openstreetmap.org Git - rails.git/blobdiff - vendor/assets/leaflet/leaflet.js
Merge remote-tracking branch 'upstream/pull/2431'
[rails.git] / vendor / assets / leaflet / leaflet.js
index 0f239b11e75f0d0f647a1747255ddc63308aa7f2..ab74e48b4f2974643f0fcb5530ce946a1c6256dc 100644 (file)
@@ -1,14 +1,15 @@
 /* @preserve
- * Leaflet 1.2.0, a JS library for interactive maps. http://leafletjs.com
- * (c) 2010-2017 Vladimir Agafonkin, (c) 2010-2011 CloudMade
+ * Leaflet 1.6.0, a JS library for interactive maps. http://leafletjs.com
+ * (c) 2010-2019 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 = {})));
 }(this, (function (exports) { 'use strict';
 
-var version = "1.2.0";
+var version = "1.6.0";
 
 /*
  * @namespace Util
@@ -65,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
@@ -124,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;
 }
 
@@ -167,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}'`
@@ -390,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];
 
@@ -467,7 +468,7 @@ var Events = {
         *
         * @alternative
         * @method off: this
-        * Removes all listeners to all events on the object.
+        * Removes all listeners to all events on the object. This includes implicitly attached events.
         */
        off: function (types, fn, context) {
 
@@ -576,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];
@@ -657,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);
                }
        }
 };
@@ -707,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) {
@@ -716,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
@@ -826,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) {
@@ -909,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) {
@@ -1082,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[])
@@ -1135,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,
@@ -1270,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; }
 
@@ -1321,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 Leaflet'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) {
@@ -1345,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; }
 
@@ -1367,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));
        },
@@ -1443,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 = {
@@ -1585,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;
        }
 });
 
@@ -1601,9 +1648,11 @@ var Earth = extend({}, CRS, {
  * a sphere. Used by the `EPSG:3857` CRS.
  */
 
+var earthRadius = 6378137;
+
 var SphericalMercator = {
 
-       R: 6378137,
+       R: earthRadius,
        MAX_LATITUDE: 85.0511287798,
 
        project: function (latlng) {
@@ -1613,8 +1662,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) {
@@ -1626,7 +1675,7 @@ var SphericalMercator = {
        },
 
        bounds: (function () {
-               var d = 6378137 * Math.PI;
+               var d = earthRadius * Math.PI;
                return new Bounds([-d, -d], [d, d]);
        })()
 };
@@ -1701,7 +1750,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) {
@@ -1802,6 +1851,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;
 
@@ -1852,7 +1906,7 @@ var msPointer = !window.PointerEvent && window.MSPointerEvent;
 
 // @property pointer: Boolean
 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
-var pointer = !!(window.PointerEvent || msPointer);
+var pointer = !webkit && !!(window.PointerEvent || msPointer);
 
 // @property touch: Boolean
 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
@@ -1870,9 +1924,26 @@ var mobileOpera = mobile && opera;
 var mobileGecko = mobile && gecko;
 
 // @property retina: Boolean
-// `true` for browsers on a high-resolution "retina" screen.
+// `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%.
 var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
 
+// @property passiveEvents: Boolean
+// `true` for browsers that support passive events.
+var passiveEvents = (function () {
+       var supportsPassiveOption = false;
+       try {
+               var opts = Object.defineProperty({}, 'passive', {
+                       get: function () {
+                               supportsPassiveOption = true;
+                       }
+               });
+               window.addEventListener('testPassiveEventSupport', falseFn, opts);
+               window.removeEventListener('testPassiveEventSupport', falseFn, opts);
+       } catch (e) {
+               // Errors can safely be ignored since this is only a browser support test.
+       }
+       return supportsPassiveOption;
+});
 
 // @property canvas: Boolean
 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
@@ -1914,6 +1985,7 @@ var Browser = (Object.freeze || Object)({
        webkit: webkit,
        android: android,
        android23: android23,
+       androidStock: androidStock,
        opera: opera,
        chrome: chrome,
        gecko: gecko,
@@ -1934,6 +2006,7 @@ var Browser = (Object.freeze || Object)({
        mobileOpera: mobileOpera,
        mobileGecko: mobileGecko,
        retina: retina,
+       passiveEvents: passiveEvents,
        canvas: canvas,
        svg: svg,
        vml: vml
@@ -1949,6 +2022,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;
 
@@ -1991,7 +2065,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.
@@ -2117,6 +2191,7 @@ function addDoubleTapListener(obj, handler, id) {
                                touch$$1 = newTouch;
                        }
                        touch$$1.type = 'dblclick';
+                       touch$$1.button = 0;
                        handler(touch$$1);
                        last = null;
                }
@@ -2126,8 +2201,8 @@ function addDoubleTapListener(obj, handler, id) {
        obj[_pre + _touchend + id] = onTouchEnd;
        obj[_pre + 'dblclick' + id] = handler;
 
-       obj.addEventListener(_touchstart, onTouchStart, false);
-       obj.addEventListener(_touchend, onTouchEnd, false);
+       obj.addEventListener(_touchstart, onTouchStart, passiveEvents ? {passive: false} : false);
+       obj.addEventListener(_touchend, onTouchEnd, passiveEvents ? {passive: false} : false);
 
        // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
        // the browser doesn't fire touchend/pointerup events but does fire
@@ -2143,8 +2218,8 @@ function removeDoubleTapListener(obj, id) {
            touchend = obj[_pre + _touchend + id],
            dblclick = obj[_pre + 'dblclick' + id];
 
-       obj.removeEventListener(_touchstart, touchstart, false);
-       obj.removeEventListener(_touchend, touchend, false);
+       obj.removeEventListener(_touchstart, touchstart, passiveEvents ? {passive: false} : false);
+       obj.removeEventListener(_touchend, touchend, passiveEvents ? {passive: false} : false);
        if (!edge) {
                obj.removeEventListener('dblclick', dblclick, false);
        }
@@ -2153,673 +2228,703 @@ function removeDoubleTapListener(obj, id) {
 }
 
 /*
- * @namespace DomEvent
- * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
+ * @namespace DomUtil
+ *
+ * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
+ * tree, used by Leaflet internally.
+ *
+ * Most functions expecting or returning a `HTMLElement` also work for
+ * SVG elements. The only difference is that classes refer to CSS classes
+ * in HTML and SVG classes in SVG.
  */
 
-// Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
-
-// @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
-// Adds a listener function (`fn`) to a particular DOM event type of the
-// element `el`. 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'`).
-
-// @alternative
-// @function on(el: HTMLElement, eventMap: Object, context?: Object): this
-// Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
-function on(obj, types, fn, context) {
-
-       if (typeof types === 'object') {
-               for (var type in types) {
-                       addOne(obj, type, types[type], fn);
-               }
-       } else {
-               types = splitWords(types);
 
-               for (var i = 0, len = types.length; i < len; i++) {
-                       addOne(obj, types[i], fn, context);
-               }
-       }
+// @property TRANSFORM: String
+// Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
+var TRANSFORM = testProp(
+       ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
 
-       return this;
-}
+// webkitTransition comes first because some browser versions that drop vendor prefix don't do
+// the same for the transitionend event, in particular the Android 4.1 stock browser
 
-var eventsKey = '_leaflet_events';
+// @property TRANSITION: String
+// Vendor-prefixed transition style name.
+var TRANSITION = testProp(
+       ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
 
-// @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.
-// Note that if you passed a custom context to on, you must pass the same
-// context to `off` in order to remove the listener.
+// @property TRANSITION_END: String
+// Vendor-prefixed transitionend event name.
+var TRANSITION_END =
+       TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
 
-// @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) {
+// @function get(id: String|HTMLElement): HTMLElement
+// Returns an element given its DOM id, or returns the element itself
+// if it was passed directly.
+function get(id) {
+       return typeof id === 'string' ? document.getElementById(id) : id;
+}
 
-       if (typeof types === 'object') {
-               for (var type in types) {
-                       removeOne(obj, type, types[type], fn);
-               }
-       } else if (types) {
-               types = splitWords(types);
+// @function getStyle(el: HTMLElement, styleAttrib: String): String
+// Returns the value for a certain style attribute on an element,
+// including computed values or values set through CSS.
+function getStyle(el, style) {
+       var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
 
-               for (var i = 0, len = types.length; i < len; i++) {
-                       removeOne(obj, types[i], fn, context);
-               }
-       } else {
-               for (var j in obj[eventsKey]) {
-                       removeOne(obj, j, obj[eventsKey][j]);
-               }
-               delete obj[eventsKey];
+       if ((!value || value === 'auto') && document.defaultView) {
+               var css = document.defaultView.getComputedStyle(el, null);
+               value = css ? css[style] : null;
        }
-
-       return this;
+       return value === 'auto' ? null : value;
 }
 
-function addOne(obj, type, fn, context) {
-       var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
-
-       if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
-
-       var handler = function (e) {
-               return fn.call(context || obj, e || window.event);
-       };
+// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
+// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
+function create$1(tagName, className, container) {
+       var el = document.createElement(tagName);
+       el.className = className || '';
 
-       var originalHandler = handler;
+       if (container) {
+               container.appendChild(el);
+       }
+       return el;
+}
 
-       if (pointer && type.indexOf('touch') === 0) {
-               // Needs DomEvent.Pointer.js
-               addPointerListener(obj, type, handler, id);
+// @function remove(el: HTMLElement)
+// Removes `el` from its parent element
+function remove(el) {
+       var parent = el.parentNode;
+       if (parent) {
+               parent.removeChild(el);
+       }
+}
 
-       } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
-                  !(pointer && chrome)) {
-               // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
-               // See #5180
-               addDoubleTapListener(obj, handler, id);
+// @function empty(el: HTMLElement)
+// Removes all of `el`'s children elements from `el`
+function empty(el) {
+       while (el.firstChild) {
+               el.removeChild(el.firstChild);
+       }
+}
 
-       } else if ('addEventListener' in obj) {
+// @function toFront(el: HTMLElement)
+// Makes `el` the last child of its parent, so it renders in front of the other children.
+function toFront(el) {
+       var parent = el.parentNode;
+       if (parent && parent.lastChild !== el) {
+               parent.appendChild(el);
+       }
+}
 
-               if (type === 'mousewheel') {
-                       obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
+// @function toBack(el: HTMLElement)
+// Makes `el` the first child of its parent, so it renders behind the other children.
+function toBack(el) {
+       var parent = el.parentNode;
+       if (parent && parent.firstChild !== el) {
+               parent.insertBefore(el, parent.firstChild);
+       }
+}
 
-               } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
-                       handler = function (e) {
-                               e = e || window.event;
-                               if (isExternalTarget(obj, e)) {
-                                       originalHandler(e);
-                               }
-                       };
-                       obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
+// @function hasClass(el: HTMLElement, name: String): Boolean
+// Returns `true` if the element's class attribute contains `name`.
+function hasClass(el, name) {
+       if (el.classList !== undefined) {
+               return el.classList.contains(name);
+       }
+       var className = getClass(el);
+       return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
+}
 
-               } else {
-                       if (type === 'click' && android) {
-                               handler = function (e) {
-                                       filterClick(e, originalHandler);
-                               };
-                       }
-                       obj.addEventListener(type, handler, false);
+// @function addClass(el: HTMLElement, name: String)
+// Adds `name` to the element's class attribute.
+function addClass(el, name) {
+       if (el.classList !== undefined) {
+               var classes = splitWords(name);
+               for (var i = 0, len = classes.length; i < len; i++) {
+                       el.classList.add(classes[i]);
                }
-
-       } else if ('attachEvent' in obj) {
-               obj.attachEvent('on' + type, handler);
+       } else if (!hasClass(el, name)) {
+               var className = getClass(el);
+               setClass(el, (className ? className + ' ' : '') + name);
        }
-
-       obj[eventsKey] = obj[eventsKey] || {};
-       obj[eventsKey][id] = handler;
 }
 
-function removeOne(obj, type, fn, context) {
+// @function removeClass(el: HTMLElement, name: String)
+// Removes `name` from the element's class attribute.
+function removeClass(el, name) {
+       if (el.classList !== undefined) {
+               el.classList.remove(name);
+       } else {
+               setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
+       }
+}
 
-       var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
-           handler = obj[eventsKey] && obj[eventsKey][id];
+// @function setClass(el: HTMLElement, name: String)
+// Sets the element's class.
+function setClass(el, name) {
+       if (el.className.baseVal === undefined) {
+               el.className = name;
+       } else {
+               // in case of SVG element
+               el.className.baseVal = name;
+       }
+}
 
-       if (!handler) { return this; }
+// @function getClass(el: HTMLElement): String
+// Returns the element's class.
+function getClass(el) {
+       // Check if the element is an SVGElementInstance and use the correspondingElement instead
+       // (Required for linked SVG elements in IE11.)
+       if (el.correspondingElement) {
+               el = el.correspondingElement;
+       }
+       return el.className.baseVal === undefined ? el.className : el.className.baseVal;
+}
 
-       if (pointer && type.indexOf('touch') === 0) {
-               removePointerListener(obj, type, id);
+// @function setOpacity(el: HTMLElement, opacity: Number)
+// Set the opacity of an element (including old IE support).
+// `opacity` must be a number from `0` to `1`.
+function setOpacity(el, value) {
+       if ('opacity' in el.style) {
+               el.style.opacity = value;
+       } else if ('filter' in el.style) {
+               _setOpacityIE(el, value);
+       }
+}
 
-       } else if (touch && (type === 'dblclick') && removeDoubleTapListener) {
-               removeDoubleTapListener(obj, id);
+function _setOpacityIE(el, value) {
+       var filter = false,
+           filterName = 'DXImageTransform.Microsoft.Alpha';
 
-       } else if ('removeEventListener' in obj) {
+       // filters collection throws an error if we try to retrieve a filter that doesn't exist
+       try {
+               filter = el.filters.item(filterName);
+       } catch (e) {
+               // don't set opacity to 1 if we haven't already set an opacity,
+               // it isn't needed and breaks transparent pngs.
+               if (value === 1) { return; }
+       }
 
-               if (type === 'mousewheel') {
-                       obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
+       value = Math.round(value * 100);
 
-               } else {
-                       obj.removeEventListener(
-                               type === 'mouseenter' ? 'mouseover' :
-                               type === 'mouseleave' ? 'mouseout' : type, handler, false);
-               }
+       if (filter) {
+               filter.Enabled = (value !== 100);
+               filter.Opacity = value;
+       } else {
+               el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
+       }
+}
 
-       } else if ('detachEvent' in obj) {
-               obj.detachEvent('on' + type, handler);
+// @function testProp(props: String[]): String|false
+// Goes through the array of style names and returns the first name
+// that is a valid style name for an element. If no such name is found,
+// it returns false. Useful for vendor-prefixed styles like `transform`.
+function testProp(props) {
+       var style = document.documentElement.style;
+
+       for (var i = 0; i < props.length; i++) {
+               if (props[i] in style) {
+                       return props[i];
+               }
        }
+       return false;
+}
 
-       obj[eventsKey][id] = null;
+// @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
+// Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
+// and optionally scaled by `scale`. Does not have an effect if the
+// browser doesn't support 3D CSS transforms.
+function setTransform(el, offset, scale) {
+       var pos = offset || new Point(0, 0);
+
+       el.style[TRANSFORM] =
+               (ie3d ?
+                       'translate(' + pos.x + 'px,' + pos.y + 'px)' :
+                       'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
+               (scale ? ' scale(' + scale + ')' : '');
 }
 
-// @function stopPropagation(ev: DOMEvent): this
-// Stop the given event from propagation to parent elements. Used inside the listener functions:
-// ```js
-// L.DomEvent.on(div, 'click', function (ev) {
-//     L.DomEvent.stopPropagation(ev);
-// });
-// ```
-function stopPropagation(e) {
+// @function setPosition(el: HTMLElement, position: Point)
+// Sets the position of `el` to coordinates specified by `position`,
+// using CSS translate or top/left positioning depending on the browser
+// (used by Leaflet internally to position its layers).
+function setPosition(el, point) {
 
-       if (e.stopPropagation) {
-               e.stopPropagation();
-       } else if (e.originalEvent) {  // In case of Leaflet event.
-               e.originalEvent._stopped = true;
+       /*eslint-disable */
+       el._leaflet_pos = point;
+       /* eslint-enable */
+
+       if (any3d) {
+               setTransform(el, point);
        } else {
-               e.cancelBubble = true;
+               el.style.left = point.x + 'px';
+               el.style.top = point.y + 'px';
        }
-       skipped(e);
-
-       return this;
 }
 
-// @function disableScrollPropagation(el: HTMLElement): this
-// Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
-function disableScrollPropagation(el) {
-       addOne(el, 'mousewheel', stopPropagation);
-       return this;
+// @function getPosition(el: HTMLElement): Point
+// Returns the coordinates of an element previously positioned with setPosition.
+function getPosition(el) {
+       // this method is only used for elements previously positioned using setPosition,
+       // so it's safe to cache the position for performance
+
+       return el._leaflet_pos || new Point(0, 0);
 }
 
-// @function disableClickPropagation(el: HTMLElement): this
-// Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
-// `'mousedown'` and `'touchstart'` events (plus browser variants).
-function disableClickPropagation(el) {
-       on(el, 'mousedown touchstart dblclick', stopPropagation);
-       addOne(el, 'click', fakeStop);
-       return this;
+// @function disableTextSelection()
+// Prevents the user from generating `selectstart` DOM events, usually generated
+// when the user drags the mouse through a page with text. Used internally
+// by Leaflet to override the behaviour of any click-and-drag interaction on
+// the map. Affects drag interactions on the whole document.
+
+// @function enableTextSelection()
+// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
+var disableTextSelection;
+var enableTextSelection;
+var _userSelect;
+if ('onselectstart' in document) {
+       disableTextSelection = function () {
+               on(window, 'selectstart', preventDefault);
+       };
+       enableTextSelection = function () {
+               off(window, 'selectstart', preventDefault);
+       };
+} else {
+       var userSelectProperty = testProp(
+               ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
+
+       disableTextSelection = function () {
+               if (userSelectProperty) {
+                       var style = document.documentElement.style;
+                       _userSelect = style[userSelectProperty];
+                       style[userSelectProperty] = 'none';
+               }
+       };
+       enableTextSelection = function () {
+               if (userSelectProperty) {
+                       document.documentElement.style[userSelectProperty] = _userSelect;
+                       _userSelect = undefined;
+               }
+       };
 }
 
-// @function preventDefault(ev: DOMEvent): this
-// Prevents the default action of the DOM Event `ev` from happening (such as
-// following a link in the href of the a element, or doing a POST request
-// with page reload when a `<form>` is submitted).
-// Use it inside listener functions.
-function preventDefault(e) {
-       if (e.preventDefault) {
-               e.preventDefault();
-       } else {
-               e.returnValue = false;
-       }
-       return this;
+// @function disableImageDrag()
+// As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
+// for `dragstart` DOM events, usually generated when the user drags an image.
+function disableImageDrag() {
+       on(window, 'dragstart', preventDefault);
 }
 
-// @function stop(ev): this
-// Does `stopPropagation` and `preventDefault` at the same time.
-function stop(e) {
-       preventDefault(e);
-       stopPropagation(e);
-       return this;
+// @function enableImageDrag()
+// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
+function enableImageDrag() {
+       off(window, 'dragstart', preventDefault);
 }
 
-// @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
-// Gets normalized mouse position from a DOM event relative to the
-// `container` or to the whole page if not specified.
-function getMousePosition(e, container) {
-       if (!container) {
-               return new Point(e.clientX, e.clientY);
+var _outlineElement;
+var _outlineStyle;
+// @function preventOutline(el: HTMLElement)
+// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
+// of the element `el` invisible. Used internally by Leaflet to prevent
+// focusable elements from displaying an outline when the user performs a
+// drag interaction on them.
+function preventOutline(element) {
+       while (element.tabIndex === -1) {
+               element = element.parentNode;
        }
+       if (!element.style) { return; }
+       restoreOutline();
+       _outlineElement = element;
+       _outlineStyle = element.style.outline;
+       element.style.outline = 'none';
+       on(window, 'keydown', restoreOutline);
+}
 
-       var rect = container.getBoundingClientRect();
+// @function restoreOutline()
+// Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
+function restoreOutline() {
+       if (!_outlineElement) { return; }
+       _outlineElement.style.outline = _outlineStyle;
+       _outlineElement = undefined;
+       _outlineStyle = undefined;
+       off(window, 'keydown', restoreOutline);
+}
 
-       return new Point(
-               e.clientX - rect.left - container.clientLeft,
-               e.clientY - rect.top - container.clientTop);
+// @function getSizedParentNode(el: HTMLElement): HTMLElement
+// Finds the closest parent node which size (width and height) is not null.
+function getSizedParentNode(element) {
+       do {
+               element = element.parentNode;
+       } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
+       return element;
 }
 
-// Chrome on Win scrolls double the pixels as in other platforms (see #4538),
-// and Firefox scrolls device pixels, not CSS pixels
-var wheelPxFactor =
-       (win && chrome) ? 2 * window.devicePixelRatio :
-       gecko ? window.devicePixelRatio : 1;
+// @function getScale(el: HTMLElement): Object
+// Computes the CSS scale currently applied on the element.
+// Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
+// and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
+function getScale(element) {
+       var rect = element.getBoundingClientRect(); // Read-only in old browsers.
 
-// @function getWheelDelta(ev: DOMEvent): Number
-// Gets normalized wheel delta from a mousewheel DOM event, in vertical
-// pixels scrolled (negative if scrolling down).
-// Events from pointing devices without precise scrolling are mapped to
-// a best guess of 60 pixels.
-function getWheelDelta(e) {
-       return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
-              (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
-              (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
-              (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
-              (e.deltaX || e.deltaZ) ? 0 :     // Skip horizontal/depth wheel events
-              e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
-              (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
-              e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
-              0;
+       return {
+               x: rect.width / element.offsetWidth || 1,
+               y: rect.height / element.offsetHeight || 1,
+               boundingClientRect: rect
+       };
 }
 
-var skipEvents = {};
 
-function fakeStop(e) {
-       // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
-       skipEvents[e.type] = true;
-}
+var DomUtil = (Object.freeze || Object)({
+       TRANSFORM: TRANSFORM,
+       TRANSITION: TRANSITION,
+       TRANSITION_END: TRANSITION_END,
+       get: get,
+       getStyle: getStyle,
+       create: create$1,
+       remove: remove,
+       empty: empty,
+       toFront: toFront,
+       toBack: toBack,
+       hasClass: hasClass,
+       addClass: addClass,
+       removeClass: removeClass,
+       setClass: setClass,
+       getClass: getClass,
+       setOpacity: setOpacity,
+       testProp: testProp,
+       setTransform: setTransform,
+       setPosition: setPosition,
+       getPosition: getPosition,
+       disableTextSelection: disableTextSelection,
+       enableTextSelection: enableTextSelection,
+       disableImageDrag: disableImageDrag,
+       enableImageDrag: enableImageDrag,
+       preventOutline: preventOutline,
+       restoreOutline: restoreOutline,
+       getSizedParentNode: getSizedParentNode,
+       getScale: getScale
+});
 
-function skipped(e) {
-       var events = skipEvents[e.type];
-       // reset when checking, as it's only used in map container and propagates outside of the map
-       skipEvents[e.type] = false;
-       return events;
-}
+/*
+ * @namespace DomEvent
+ * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
+ */
 
-// check if element really left/entered the event target (for mouseenter/mouseleave)
-function isExternalTarget(el, e) {
+// Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
 
-       var related = e.relatedTarget;
+// @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
+// Adds a listener function (`fn`) to a particular DOM event type of the
+// element `el`. 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'`).
 
-       if (!related) { return true; }
+// @alternative
+// @function on(el: HTMLElement, eventMap: Object, context?: Object): this
+// Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
+function on(obj, types, fn, context) {
 
-       try {
-               while (related && (related !== el)) {
-                       related = related.parentNode;
+       if (typeof types === 'object') {
+               for (var type in types) {
+                       addOne(obj, type, types[type], fn);
+               }
+       } else {
+               types = splitWords(types);
+
+               for (var i = 0, len = types.length; i < len; i++) {
+                       addOne(obj, types[i], fn, context);
                }
-       } catch (err) {
-               return false;
        }
-       return (related !== el);
+
+       return this;
 }
 
-var lastClick;
+var eventsKey = '_leaflet_events';
 
-// this is a horrible workaround for a bug in Android where a single touch triggers two click events
-function filterClick(e, handler) {
-       var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
-           elapsed = lastClick && (timeStamp - lastClick);
+// @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
+// 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.
 
-       // are they closer together than 500ms yet more than 100ms?
-       // Android typically triggers them ~300ms apart while multiple listeners
-       // on the same event should be triggered far faster;
-       // or check if click is simulated on the element, and if it is, reject any non-simulated events
+// @alternative
+// @function off(el: HTMLElement, eventMap: Object, context?: Object): this
+// Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
+function off(obj, types, fn, context) {
 
-       if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
-               stop(e);
-               return;
+       if (typeof types === 'object') {
+               for (var type in types) {
+                       removeOne(obj, type, types[type], fn);
+               }
+       } else if (types) {
+               types = splitWords(types);
+
+               for (var i = 0, len = types.length; i < len; i++) {
+                       removeOne(obj, types[i], fn, context);
+               }
+       } else {
+               for (var j in obj[eventsKey]) {
+                       removeOne(obj, j, obj[eventsKey][j]);
+               }
+               delete obj[eventsKey];
        }
-       lastClick = timeStamp;
 
-       handler(e);
+       return this;
 }
 
+function addOne(obj, type, fn, context) {
+       var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
 
+       if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
 
+       var handler = function (e) {
+               return fn.call(context || obj, e || window.event);
+       };
 
-var DomEvent = (Object.freeze || Object)({
-       on: on,
-       off: off,
-       stopPropagation: stopPropagation,
-       disableScrollPropagation: disableScrollPropagation,
-       disableClickPropagation: disableClickPropagation,
-       preventDefault: preventDefault,
-       stop: stop,
-       getMousePosition: getMousePosition,
-       getWheelDelta: getWheelDelta,
-       fakeStop: fakeStop,
-       skipped: skipped,
-       isExternalTarget: isExternalTarget,
-       addListener: on,
-       removeListener: off
-});
+       var originalHandler = handler;
 
-/*
- * @namespace DomUtil
- *
- * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
- * tree, used by Leaflet internally.
- *
- * Most functions expecting or returning a `HTMLElement` also work for
- * SVG elements. The only difference is that classes refer to CSS classes
- * in HTML and SVG classes in SVG.
- */
+       if (pointer && type.indexOf('touch') === 0) {
+               // Needs DomEvent.Pointer.js
+               addPointerListener(obj, type, handler, id);
 
+       } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
+                  !(pointer && chrome)) {
+               // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
+               // See #5180
+               addDoubleTapListener(obj, handler, id);
 
-// @property TRANSFORM: String
-// Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
-var TRANSFORM = testProp(
-       ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
+       } else if ('addEventListener' in obj) {
 
-// webkitTransition comes first because some browser versions that drop vendor prefix don't do
-// the same for the transitionend event, in particular the Android 4.1 stock browser
+               if (type === 'mousewheel') {
+                       obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, passiveEvents ? {passive: false} : false);
 
-// @property TRANSITION: String
-// Vendor-prefixed transition style name.
-var TRANSITION = testProp(
-       ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
+               } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
+                       handler = function (e) {
+                               e = e || window.event;
+                               if (isExternalTarget(obj, e)) {
+                                       originalHandler(e);
+                               }
+                       };
+                       obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
 
-// @property TRANSITION_END: String
-// Vendor-prefixed transitionend event name.
-var TRANSITION_END =
-       TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
+               } else {
+                       if (type === 'click' && android) {
+                               handler = function (e) {
+                                       filterClick(e, originalHandler);
+                               };
+                       }
+                       obj.addEventListener(type, handler, false);
+               }
 
+       } else if ('attachEvent' in obj) {
+               obj.attachEvent('on' + type, handler);
+       }
 
-// @function get(id: String|HTMLElement): HTMLElement
-// Returns an element given its DOM id, or returns the element itself
-// if it was passed directly.
-function get(id) {
-       return typeof id === 'string' ? document.getElementById(id) : id;
+       obj[eventsKey] = obj[eventsKey] || {};
+       obj[eventsKey][id] = handler;
 }
 
-// @function getStyle(el: HTMLElement, styleAttrib: String): String
-// Returns the value for a certain style attribute on an element,
-// including computed values or values set through CSS.
-function getStyle(el, style) {
-       var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
+function removeOne(obj, type, fn, context) {
 
-       if ((!value || value === 'auto') && document.defaultView) {
-               var css = document.defaultView.getComputedStyle(el, null);
-               value = css ? css[style] : null;
-       }
-       return value === 'auto' ? null : value;
-}
+       var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
+           handler = obj[eventsKey] && obj[eventsKey][id];
 
-// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
-// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
-function create$1(tagName, className, container) {
-       var el = document.createElement(tagName);
-       el.className = className || '';
+       if (!handler) { return this; }
 
-       if (container) {
-               container.appendChild(el);
-       }
-       return el;
-}
+       if (pointer && type.indexOf('touch') === 0) {
+               removePointerListener(obj, type, id);
 
-// @function remove(el: HTMLElement)
-// Removes `el` from its parent element
-function remove(el) {
-       var parent = el.parentNode;
-       if (parent) {
-               parent.removeChild(el);
-       }
-}
+       } else if (touch && (type === 'dblclick') && removeDoubleTapListener &&
+                  !(pointer && chrome)) {
+               removeDoubleTapListener(obj, id);
 
-// @function empty(el: HTMLElement)
-// Removes all of `el`'s children elements from `el`
-function empty(el) {
-       while (el.firstChild) {
-               el.removeChild(el.firstChild);
-       }
-}
+       } else if ('removeEventListener' in obj) {
 
-// @function toFront(el: HTMLElement)
-// Makes `el` the last child of its parent, so it renders in front of the other children.
-function toFront(el) {
-       var parent = el.parentNode;
-       if (parent.lastChild !== el) {
-               parent.appendChild(el);
-       }
-}
+               if (type === 'mousewheel') {
+                       obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, passiveEvents ? {passive: false} : false);
 
-// @function toBack(el: HTMLElement)
-// Makes `el` the first child of its parent, so it renders behind the other children.
-function toBack(el) {
-       var parent = el.parentNode;
-       if (parent.firstChild !== el) {
-               parent.insertBefore(el, parent.firstChild);
-       }
-}
+               } else {
+                       obj.removeEventListener(
+                               type === 'mouseenter' ? 'mouseover' :
+                               type === 'mouseleave' ? 'mouseout' : type, handler, false);
+               }
 
-// @function hasClass(el: HTMLElement, name: String): Boolean
-// Returns `true` if the element's class attribute contains `name`.
-function hasClass(el, name) {
-       if (el.classList !== undefined) {
-               return el.classList.contains(name);
+       } else if ('detachEvent' in obj) {
+               obj.detachEvent('on' + type, handler);
        }
-       var className = getClass(el);
-       return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
-}
 
-// @function addClass(el: HTMLElement, name: String)
-// Adds `name` to the element's class attribute.
-function addClass(el, name) {
-       if (el.classList !== undefined) {
-               var classes = splitWords(name);
-               for (var i = 0, len = classes.length; i < len; i++) {
-                       el.classList.add(classes[i]);
-               }
-       } else if (!hasClass(el, name)) {
-               var className = getClass(el);
-               setClass(el, (className ? className + ' ' : '') + name);
-       }
+       obj[eventsKey][id] = null;
 }
 
-// @function removeClass(el: HTMLElement, name: String)
-// Removes `name` from the element's class attribute.
-function removeClass(el, name) {
-       if (el.classList !== undefined) {
-               el.classList.remove(name);
-       } else {
-               setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
-       }
-}
+// @function stopPropagation(ev: DOMEvent): this
+// Stop the given event from propagation to parent elements. Used inside the listener functions:
+// ```js
+// L.DomEvent.on(div, 'click', function (ev) {
+//     L.DomEvent.stopPropagation(ev);
+// });
+// ```
+function stopPropagation(e) {
 
-// @function setClass(el: HTMLElement, name: String)
-// Sets the element's class.
-function setClass(el, name) {
-       if (el.className.baseVal === undefined) {
-               el.className = name;
+       if (e.stopPropagation) {
+               e.stopPropagation();
+       } else if (e.originalEvent) {  // In case of Leaflet event.
+               e.originalEvent._stopped = true;
        } else {
-               // in case of SVG element
-               el.className.baseVal = name;
+               e.cancelBubble = true;
        }
-}
+       skipped(e);
 
-// @function getClass(el: HTMLElement): String
-// Returns the element's class.
-function getClass(el) {
-       return el.className.baseVal === undefined ? el.className : el.className.baseVal;
+       return this;
 }
 
-// @function setOpacity(el: HTMLElement, opacity: Number)
-// Set the opacity of an element (including old IE support).
-// `opacity` must be a number from `0` to `1`.
-function setOpacity(el, value) {
-       if ('opacity' in el.style) {
-               el.style.opacity = value;
-       } else if ('filter' in el.style) {
-               _setOpacityIE(el, value);
-       }
+// @function disableScrollPropagation(el: HTMLElement): this
+// Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
+function disableScrollPropagation(el) {
+       addOne(el, 'mousewheel', stopPropagation);
+       return this;
 }
 
-function _setOpacityIE(el, value) {
-       var filter = false,
-           filterName = 'DXImageTransform.Microsoft.Alpha';
-
-       // filters collection throws an error if we try to retrieve a filter that doesn't exist
-       try {
-               filter = el.filters.item(filterName);
-       } catch (e) {
-               // don't set opacity to 1 if we haven't already set an opacity,
-               // it isn't needed and breaks transparent pngs.
-               if (value === 1) { return; }
-       }
-
-       value = Math.round(value * 100);
+// @function disableClickPropagation(el: HTMLElement): this
+// Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
+// `'mousedown'` and `'touchstart'` events (plus browser variants).
+function disableClickPropagation(el) {
+       on(el, 'mousedown touchstart dblclick', stopPropagation);
+       addOne(el, 'click', fakeStop);
+       return this;
+}
 
-       if (filter) {
-               filter.Enabled = (value !== 100);
-               filter.Opacity = value;
+// @function preventDefault(ev: DOMEvent): this
+// Prevents the default action of the DOM Event `ev` from happening (such as
+// following a link in the href of the a element, or doing a POST request
+// with page reload when a `<form>` is submitted).
+// Use it inside listener functions.
+function preventDefault(e) {
+       if (e.preventDefault) {
+               e.preventDefault();
        } else {
-               el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
+               e.returnValue = false;
        }
+       return this;
 }
 
-// @function testProp(props: String[]): String|false
-// Goes through the array of style names and returns the first name
-// that is a valid style name for an element. If no such name is found,
-// it returns false. Useful for vendor-prefixed styles like `transform`.
-function testProp(props) {
-       var style = document.documentElement.style;
+// @function stop(ev: DOMEvent): this
+// Does `stopPropagation` and `preventDefault` at the same time.
+function stop(e) {
+       preventDefault(e);
+       stopPropagation(e);
+       return this;
+}
 
-       for (var i = 0; i < props.length; i++) {
-               if (props[i] in style) {
-                       return props[i];
-               }
+// @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
+// Gets normalized mouse position from a DOM event relative to the
+// `container` (border excluded) or to the whole page if not specified.
+function getMousePosition(e, container) {
+       if (!container) {
+               return new Point(e.clientX, e.clientY);
        }
-       return false;
-}
 
-// @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
-// Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
-// and optionally scaled by `scale`. Does not have an effect if the
-// browser doesn't support 3D CSS transforms.
-function setTransform(el, offset, scale) {
-       var pos = offset || new Point(0, 0);
+       var scale = getScale(container),
+           offset = scale.boundingClientRect; // left and top  values are in page scale (like the event clientX/Y)
 
-       el.style[TRANSFORM] =
-               (ie3d ?
-                       'translate(' + pos.x + 'px,' + pos.y + 'px)' :
-                       'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
-               (scale ? ' scale(' + scale + ')' : '');
+       return new Point(
+               // offset.left/top values are in page scale (like clientX/Y),
+               // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
+               (e.clientX - offset.left) / scale.x - container.clientLeft,
+               (e.clientY - offset.top) / scale.y - container.clientTop
+       );
 }
 
-// @function setPosition(el: HTMLElement, position: Point)
-// Sets the position of `el` to coordinates specified by `position`,
-// using CSS translate or top/left positioning depending on the browser
-// (used by Leaflet internally to position its layers).
-function setPosition(el, point) {
-
-       /*eslint-disable */
-       el._leaflet_pos = point;
-       /*eslint-enable */
+// Chrome on Win scrolls double the pixels as in other platforms (see #4538),
+// and Firefox scrolls device pixels, not CSS pixels
+var wheelPxFactor =
+       (win && chrome) ? 2 * window.devicePixelRatio :
+       gecko ? window.devicePixelRatio : 1;
 
-       if (any3d) {
-               setTransform(el, point);
-       } else {
-               el.style.left = point.x + 'px';
-               el.style.top = point.y + 'px';
-       }
+// @function getWheelDelta(ev: DOMEvent): Number
+// Gets normalized wheel delta from a mousewheel DOM event, in vertical
+// pixels scrolled (negative if scrolling down).
+// Events from pointing devices without precise scrolling are mapped to
+// a best guess of 60 pixels.
+function getWheelDelta(e) {
+       return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
+              (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
+              (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
+              (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
+              (e.deltaX || e.deltaZ) ? 0 :     // Skip horizontal/depth wheel events
+              e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
+              (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
+              e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
+              0;
 }
 
-// @function getPosition(el: HTMLElement): Point
-// Returns the coordinates of an element previously positioned with setPosition.
-function getPosition(el) {
-       // this method is only used for elements previously positioned using setPosition,
-       // so it's safe to cache the position for performance
+var skipEvents = {};
 
-       return el._leaflet_pos || new Point(0, 0);
+function fakeStop(e) {
+       // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
+       skipEvents[e.type] = true;
 }
 
-// @function disableTextSelection()
-// Prevents the user from generating `selectstart` DOM events, usually generated
-// when the user drags the mouse through a page with text. Used internally
-// by Leaflet to override the behaviour of any click-and-drag interaction on
-// the map. Affects drag interactions on the whole document.
+function skipped(e) {
+       var events = skipEvents[e.type];
+       // reset when checking, as it's only used in map container and propagates outside of the map
+       skipEvents[e.type] = false;
+       return events;
+}
 
-// @function enableTextSelection()
-// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
-var disableTextSelection;
-var enableTextSelection;
-var _userSelect;
-if ('onselectstart' in document) {
-       disableTextSelection = function () {
-               on(window, 'selectstart', preventDefault);
-       };
-       enableTextSelection = function () {
-               off(window, 'selectstart', preventDefault);
-       };
-} else {
-       var userSelectProperty = testProp(
-               ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
+// check if element really left/entered the event target (for mouseenter/mouseleave)
+function isExternalTarget(el, e) {
 
-       disableTextSelection = function () {
-               if (userSelectProperty) {
-                       var style = document.documentElement.style;
-                       _userSelect = style[userSelectProperty];
-                       style[userSelectProperty] = 'none';
-               }
-       };
-       enableTextSelection = function () {
-               if (userSelectProperty) {
-                       document.documentElement.style[userSelectProperty] = _userSelect;
-                       _userSelect = undefined;
+       var related = e.relatedTarget;
+
+       if (!related) { return true; }
+
+       try {
+               while (related && (related !== el)) {
+                       related = related.parentNode;
                }
-       };
+       } catch (err) {
+               return false;
+       }
+       return (related !== el);
 }
 
-// @function disableImageDrag()
-// As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
-// for `dragstart` DOM events, usually generated when the user drags an image.
-function disableImageDrag() {
-       on(window, 'dragstart', preventDefault);
-}
+var lastClick;
 
-// @function enableImageDrag()
-// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
-function enableImageDrag() {
-       off(window, 'dragstart', preventDefault);
-}
+// this is a horrible workaround for a bug in Android where a single touch triggers two click events
+function filterClick(e, handler) {
+       var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
+           elapsed = lastClick && (timeStamp - lastClick);
 
-var _outlineElement;
-var _outlineStyle;
-// @function preventOutline(el: HTMLElement)
-// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
-// of the element `el` invisible. Used internally by Leaflet to prevent
-// focusable elements from displaying an outline when the user performs a
-// drag interaction on them.
-function preventOutline(element) {
-       while (element.tabIndex === -1) {
-               element = element.parentNode;
+       // are they closer together than 500ms yet more than 100ms?
+       // Android typically triggers them ~300ms apart while multiple listeners
+       // on the same event should be triggered far faster;
+       // or check if click is simulated on the element, and if it is, reject any non-simulated events
+
+       if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
+               stop(e);
+               return;
        }
-       if (!element.style) { return; }
-       restoreOutline();
-       _outlineElement = element;
-       _outlineStyle = element.style.outline;
-       element.style.outline = 'none';
-       on(window, 'keydown', restoreOutline);
-}
+       lastClick = timeStamp;
 
-// @function restoreOutline()
-// Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
-function restoreOutline() {
-       if (!_outlineElement) { return; }
-       _outlineElement.style.outline = _outlineStyle;
-       _outlineElement = undefined;
-       _outlineStyle = undefined;
-       off(window, 'keydown', restoreOutline);
+       handler(e);
 }
 
 
-var DomUtil = (Object.freeze || Object)({
-       TRANSFORM: TRANSFORM,
-       TRANSITION: TRANSITION,
-       TRANSITION_END: TRANSITION_END,
-       get: get,
-       getStyle: getStyle,
-       create: create$1,
-       remove: remove,
-       empty: empty,
-       toFront: toFront,
-       toBack: toBack,
-       hasClass: hasClass,
-       addClass: addClass,
-       removeClass: removeClass,
-       setClass: setClass,
-       getClass: getClass,
-       setOpacity: setOpacity,
-       testProp: testProp,
-       setTransform: setTransform,
-       setPosition: setPosition,
-       getPosition: getPosition,
-       disableTextSelection: disableTextSelection,
-       enableTextSelection: enableTextSelection,
-       disableImageDrag: disableImageDrag,
-       enableImageDrag: enableImageDrag,
-       preventOutline: preventOutline,
-       restoreOutline: restoreOutline
+
+
+var DomEvent = (Object.freeze || Object)({
+       on: on,
+       off: off,
+       stopPropagation: stopPropagation,
+       disableScrollPropagation: disableScrollPropagation,
+       disableClickPropagation: disableClickPropagation,
+       preventDefault: preventDefault,
+       stop: stop,
+       getMousePosition: getMousePosition,
+       getWheelDelta: getWheelDelta,
+       fakeStop: fakeStop,
+       skipped: skipped,
+       isExternalTarget: isExternalTarget,
+       addListener: on,
+       removeListener: off
 });
 
 /*
@@ -3034,6 +3139,13 @@ var Map = Evented.extend({
        initialize: function (id, options) { // (HTMLElement or String, Object)
                options = setOptions(this, options);
 
+               // Make sure to assign internal flags at the beginning,
+               // to avoid inconsistent state in some edge cases.
+               this._handlers = [];
+               this._layers = {};
+               this._zoomBoundLayers = {};
+               this._sizeChanged = true;
+
                this._initContainer(id);
                this._initLayout();
 
@@ -3054,11 +3166,6 @@ var Map = Evented.extend({
                        this.setView(toLatLng(options.center), options.zoom, {reset: true});
                }
 
-               this._handlers = [];
-               this._layers = {};
-               this._zoomBoundLayers = {};
-               this._sizeChanged = true;
-
                this.callInitHooks();
 
                // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
@@ -3333,7 +3440,7 @@ var Map = Evented.extend({
                        }
                }
 
-               this._moveStart(true);
+               this._moveStart(true, options.noMoveStart);
 
                frame.call(this);
                return this;
@@ -3371,10 +3478,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;
@@ -3383,10 +3495,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;
@@ -3407,7 +3524,52 @@ var Map = Evented.extend({
                return this;
        },
 
-       // @method invalidateSize(options: Zoom/Pan options): this
+       // @method panInside(latlng: LatLng, options?: options): this
+       // Pans the map the minimum amount to make the `latlng` visible. Use
+       // `padding`, `paddingTopLeft` and `paddingTopRight` options to fit
+       // the display to more restricted bounds, like [`fitBounds`](#map-fitbounds).
+       // If `latlng` is already within the (optionally padded) display bounds,
+       // the map will not be panned.
+       panInside: function (latlng, options) {
+               options = options || {};
+
+               var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
+                   paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
+                   center = this.getCenter(),
+                   pixelCenter = this.project(center),
+                   pixelPoint = this.project(latlng),
+                   pixelBounds = this.getPixelBounds(),
+                   halfPixelBounds = pixelBounds.getSize().divideBy(2),
+                   paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]);
+
+               if (!paddedBounds.contains(pixelPoint)) {
+                       this._enforcingBounds = true;
+                       var diff = pixelCenter.subtract(pixelPoint),
+                           newCenter = toPoint(pixelPoint.x + diff.x, pixelPoint.y + diff.y);
+
+                       if (pixelPoint.x < paddedBounds.min.x || pixelPoint.x > paddedBounds.max.x) {
+                               newCenter.x = pixelCenter.x - diff.x;
+                               if (diff.x > 0) {
+                                       newCenter.x += halfPixelBounds.x - paddingTL.x;
+                               } else {
+                                       newCenter.x -= halfPixelBounds.x - paddingBR.x;
+                               }
+                       }
+                       if (pixelPoint.y < paddedBounds.min.y || pixelPoint.y > paddedBounds.max.y) {
+                               newCenter.y = pixelCenter.y - diff.y;
+                               if (diff.y > 0) {
+                                       newCenter.y += halfPixelBounds.y - paddingTL.y;
+                               } else {
+                                       newCenter.y -= halfPixelBounds.y - paddingBR.y;
+                               }
+                       }
+                       this.panTo(this.unproject(newCenter), options);
+                       this._enforcingBounds = false;
+               }
+               return 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.
@@ -3554,7 +3716,7 @@ var Map = Evented.extend({
                var lat = pos.coords.latitude,
                    lng = pos.coords.longitude,
                    latlng = new LatLng(lat, lng),
-                   bounds = latlng.toBounds(pos.coords.accuracy),
+                   bounds = latlng.toBounds(pos.coords.accuracy * 2),
                    options = this._locateOptions;
 
                if (options.setView) {
@@ -3580,8 +3742,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.
@@ -3616,15 +3777,25 @@ 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) {
                        this._clearControlPos();
                }
+               if (this._resizeRequest) {
+                       cancelAnimFrame(this._resizeRequest);
+                       this._resizeRequest = null;
+               }
 
                this._clearHandlers();
 
@@ -3709,7 +3880,7 @@ var Map = Evented.extend({
                        this.options.maxZoom;
        },
 
-       // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number
+       // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
        // Returns the maximum zoom level on which the given bounds fit to the map
        // view in its entirety. If `inside` (optional) is set to `true`, the method
        // instead returns the minimum zoom level on which the map view fits into
@@ -3998,7 +4169,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)
@@ -4007,7 +4178,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.
@@ -4034,7 +4205,7 @@ var Map = Evented.extend({
 
                var zoomChanged = this._zoom !== zoom;
                this
-                       ._moveStart(zoomChanged)
+                       ._moveStart(zoomChanged, false)
                        ._move(center, zoom)
                        ._moveEnd(zoomChanged);
 
@@ -4051,7 +4222,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
@@ -4059,7 +4230,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) {
@@ -4155,9 +4329,15 @@ var Map = Evented.extend({
                // this event. Also fired on mobile when the user holds a single touch
                // for a second (also called long press).
                // @event keypress: KeyboardEvent
-               // Fired when the user presses a key from the keyboard while the map is focused.
+               // Fired when the user presses a key from the keyboard that produces a character value while the map is focused.
+               // @event keydown: KeyboardEvent
+               // Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event,
+               // the `keydown` event is fired for keys that produce a character value and for keys
+               // that do not produce a character value.
+               // @event keyup: KeyboardEvent
+               // Fired when the user releases a key from the keyboard while the map is focused.
                onOff(this._container, 'click dblclick mousedown mouseup ' +
-                       'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);
+                       'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this);
 
                if (this.options.trackResize) {
                        onOff(window, 'resize', this._onResize, this);
@@ -4221,7 +4401,7 @@ var Map = Evented.extend({
 
                var type = e.type;
 
-               if (type === 'mousedown' || type === 'keypress') {
+               if (type === 'mousedown' || type === 'keypress' || type === 'keyup' || type === 'keydown') {
                        // prevents outline when clicking on keyboard-focusable element
                        preventOutline(e.target || e.srcElement);
                }
@@ -4260,10 +4440,10 @@ var Map = Evented.extend({
                        originalEvent: e
                };
 
-               if (e.type !== 'keypress') {
-                       var isMarker = (target.options && 'icon' in target.options);
+               if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') {
+                       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);
                }
@@ -4422,7 +4602,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; }
@@ -4449,20 +4629,23 @@ var Map = Evented.extend({
                        }
                }, this);
 
-               this.on('load moveend', function () {
-                       var c = this.getCenter(),
-                           z = this.getZoom();
-                       setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
-               }, this);
+               this.on('load moveend', this._animMoveEnd, this);
 
                this._on('unload', this._destroyAnimProxy, this);
        },
 
        _destroyAnimProxy: function () {
                remove(this._proxy);
+               this.off('load moveend', this._animMoveEnd, this);
                delete this._proxy;
        },
 
+       _animMoveEnd: function () {
+               var c = this.getCenter(),
+                   z = this.getZoom();
+               setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
+       },
+
        _catchTransitionEnd: function (e) {
                if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
                        this._onZoomTransitionEnd();
@@ -4492,7 +4675,7 @@ var Map = Evented.extend({
 
                requestAnimFrame(function () {
                        this
-                           ._moveStart(true)
+                           ._moveStart(true, false)
                            ._animateZoom(center, zoom, true);
                }, this);
 
@@ -4500,6 +4683,8 @@ var Map = Evented.extend({
        },
 
        _animateZoom: function (center, zoom, startAnim, noUpdate) {
+               if (!this._mapPane) { return; }
+
                if (startAnim) {
                        this._animatingZoom = true;
 
@@ -4510,8 +4695,9 @@ var Map = Evented.extend({
                        addClass(this._mapPane, 'leaflet-zoom-anim');
                }
 
+               // @section Other Events
                // @event zoomanim: ZoomAnimEvent
-               // Fired on every frame of a zoom animation
+               // Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom.
                this.fire('zoomanim', {
                        center: center,
                        zoom: zoom,
@@ -4525,7 +4711,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;
 
@@ -4627,6 +4815,8 @@ var Control = Class.extend({
                        corner.appendChild(container);
                }
 
+               this._map.on('unload', this.remove, this);
+
                return this;
        },
 
@@ -4643,6 +4833,7 @@ var Control = Class.extend({
                        this.onRemove(this._map);
                }
 
+               this._map.off('unload', this.remove, this);
                this._map = null;
 
                return this;
@@ -4865,13 +5056,13 @@ var Layers = Control.extend({
        // Expand the control container if collapsed.
        expand: function () {
                addClass(this._container, 'leaflet-control-layers-expanded');
-               this._form.style.height = null;
+               this._section.style.height = null;
                var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
-               if (acceptableHeight < this._form.clientHeight) {
-                       addClass(this._form, 'leaflet-control-layers-scrollbar');
-                       this._form.style.height = acceptableHeight + 'px';
+               if (acceptableHeight < this._section.clientHeight) {
+                       addClass(this._section, 'leaflet-control-layers-scrollbar');
+                       this._section.style.height = acceptableHeight + 'px';
                } else {
-                       removeClass(this._form, 'leaflet-control-layers-scrollbar');
+                       removeClass(this._section, 'leaflet-control-layers-scrollbar');
                }
                this._checkDisabledLayers();
                return this;
@@ -4895,7 +5086,7 @@ var Layers = Control.extend({
                disableClickPropagation(container);
                disableScrollPropagation(container);
 
-               var form = this._form = create$1('form', className + '-list');
+               var section = this._section = create$1('section', className + '-list');
 
                if (collapsed) {
                        this._map.on('click', this.collapse, this);
@@ -4923,11 +5114,11 @@ var Layers = Control.extend({
                        this.expand();
                }
 
-               this._baseLayersList = create$1('div', className + '-base', form);
-               this._separator = create$1('div', className + '-separator', form);
-               this._overlaysList = create$1('div', className + '-overlays', form);
+               this._baseLayersList = create$1('div', className + '-base', section);
+               this._separator = create$1('div', className + '-separator', section);
+               this._overlaysList = create$1('div', className + '-overlays', section);
 
-               container.appendChild(form);
+               container.appendChild(section);
        },
 
        _getLayer: function (id) {
@@ -5040,7 +5231,7 @@ var Layers = Control.extend({
                        input.className = 'leaflet-control-layers-selector';
                        input.defaultChecked = checked;
                } else {
-                       input = this._createRadioElement('leaflet-base-layers', checked);
+                       input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked);
                }
 
                this._layerControlInputs.push(input);
@@ -5138,7 +5329,7 @@ var Layers = Control.extend({
 
 
 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
-// 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.
+// Creates a layers control with the given layers. Base layers will be switched with radio buttons, while overlays will be switched with checkboxes. Note that all base layers should be passed in the base layers object, but only one should be added to the map during map instantiation.
 var layers = function (baseLayers, overlays, options) {
        return new Layers(baseLayers, overlays, options);
 };
@@ -5264,6 +5455,10 @@ Map.mergeOptions({
 
 Map.addInitHook(function () {
        if (this.options.zoomControl) {
+               // @section Controls
+               // @property zoomControl: Control.Zoom
+               // The default zoom control (only available if the
+               // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
                this.zoomControl = new Zoom();
                this.addControl(this.zoomControl);
        }
@@ -5343,8 +5538,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);
        },
@@ -5421,7 +5616,7 @@ var Attribution = Control.extend({
 
                // @option prefix: String = 'Leaflet'
                // The HTML text shown before the attributions. Pass `false` to disable.
-               prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
+               prefix: '<a href="https://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
        },
 
        initialize: function (options) {
@@ -5586,6 +5781,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};
 
 /*
@@ -5695,10 +5898,14 @@ var Draggable = Evented.extend({
                // Fired when a drag is about to start.
                this.fire('down');
 
-               var first = e.touches ? e.touches[0] : e;
+               var first = e.touches ? e.touches[0] : e,
+                   sizedParent = getSizedParentNode(this._element);
 
                this._startPoint = new Point(first.clientX, first.clientY);
 
+               // Cache the scale, so that we can continuously compensate for it during drag (_onMove).
+               this._parentScale = getScale(sizedParent);
+
                on(document, MOVE[e.type], this._onMove, this);
                on(document, END[e.type], this._onUp, this);
        },
@@ -5717,12 +5924,17 @@ var Draggable = Evented.extend({
                }
 
                var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
-                   newPoint = new Point(first.clientX, first.clientY),
-                   offset = newPoint.subtract(this._startPoint);
+                   offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
 
                if (!offset.x && !offset.y) { return; }
                if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
 
+               // We assume that the parent container's position, border and scale do not change for the duration of the drag.
+               // Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
+               // and we can use the cached value for the scale.
+               offset.x /= this._parentScale.x;
+               offset.y /= this._parentScale.y;
+
                preventDefault(e);
 
                if (!this._moved) {
@@ -5812,7 +6024,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.
@@ -6067,10 +6279,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,
@@ -6149,7 +6361,7 @@ var LonLat = {
  * @namespace Projection
  * @projection L.Projection.Mercator
  *
- * 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.
+ * Elliptical Mercator projection â€” more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS.
  */
 
 var Mercator = {
@@ -6208,6 +6420,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.
+
  */
 
 
@@ -6305,7 +6521,7 @@ CRS.Simple = Simple;
  * @example
  *
  * ```js
- * var layer = L.Marker(latlng).addTo(map);
+ * var layer = L.marker(latlng).addTo(map);
  * layer.addTo(map);
  * layer.remove();
  * ```
@@ -6327,7 +6543,7 @@ var Layer = Evented.extend({
                pane: 'overlayPane',
 
                // @option attribution: String = null
-               // String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox".
+               // String to be shown in the attribution control, e.g. "© OpenStreetMap contributors". It describes the layer data and is often a legal obligation towards copyright holders and tile providers.
                attribution: null,
 
                bubblingMouseEvents: true
@@ -6589,7 +6805,9 @@ Map.include({
 
 var LayerGroup = Layer.extend({
 
-       initialize: function (layers) {
+       initialize: function (layers, options) {
+               setOptions(this, options);
+
                this._layers = {};
 
                var i, len;
@@ -6644,10 +6862,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
@@ -6670,15 +6885,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
@@ -6705,10 +6916,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;
        },
 
@@ -6726,10 +6934,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);
 };
 
 /*
@@ -6800,7 +7008,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');
        },
@@ -6872,9 +7080,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.
         *
@@ -6891,6 +7102,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);
        },
@@ -7002,9 +7218,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);
        },
 
@@ -7018,7 +7234,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;
@@ -7057,6 +7273,7 @@ var MarkerDrag = Handler.extend({
 
                this._draggable.on({
                        dragstart: this._onDragStart,
+                       predrag: this._onPreDrag,
                        drag: this._onDrag,
                        dragend: this._onDragEnd
                }, this).enable();
@@ -7067,6 +7284,7 @@ var MarkerDrag = Handler.extend({
        removeHooks: function () {
                this._draggable.off({
                        dragstart: this._onDragStart,
+                       predrag: this._onPreDrag,
                        drag: this._onDrag,
                        dragend: this._onDragEnd
                }, this).disable();
@@ -7080,6 +7298,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 = 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);
+
+                       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
@@ -7095,10 +7349,17 @@ 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,
-               iconPos = getPosition(marker._icon),
+                   iconPos = getPosition(marker._icon),
                    latlng = marker._map.layerPointToLatLng(iconPos);
 
                // update shadow position
@@ -7121,6 +7382,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;
@@ -7157,10 +7420,6 @@ var Marker = Layer.extend({
                // Option inherited from "Interactive layer" abstract class
                interactive: true,
 
-               // @option draggable: Boolean = false
-               // Whether the marker is draggable with mouse/touch or not.
-               draggable: false,
-
                // @option keyboard: Boolean = true
                // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
                keyboard: true,
@@ -7193,10 +7452,32 @@ var Marker = Layer.extend({
                // `Map pane` where the markers icon will be added.
                pane: 'markerPane',
 
+               // @option pane: String = 'shadowPane'
+               // `Map pane` where the markers shadow will be added.
+               shadowPane: 'shadowPane',
+
                // @option bubblingMouseEvents: Boolean = false
                // When `true`, a mouse event on this marker will trigger the same event on the map
                // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
-               bubblingMouseEvents: false
+               bubblingMouseEvents: false,
+
+               // @section Draggable marker options
+               // @option draggable: Boolean = false
+               // Whether the marker is draggable with mouse/touch or not.
+               draggable: false,
+
+               // @option autoPan: Boolean = false
+               // Whether to pan the map when dragging this marker near its edge or not.
+               autoPan: false,
+
+               // @option autoPanPadding: Point = Point(50, 50)
+               // Distance (in pixels to the left/right and to the top/bottom) of the
+               // map edge to start panning the map.
+               autoPanPadding: [50, 50],
+
+               // @option autoPanSpeed: Number = 10
+               // Number of pixels the map should pan by.
+               autoPanSpeed: 10
        },
 
        /* @section
@@ -7267,6 +7548,12 @@ var Marker = Layer.extend({
                return this.update();
        },
 
+       // @method getIcon: Icon
+       // Returns the current icon used by the marker
+       getIcon: function () {
+               return this.options.icon;
+       },
+
        // @method setIcon(icon: Icon): this
        // Changes the marker icon.
        setIcon: function (icon) {
@@ -7291,7 +7578,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);
                }
@@ -7316,8 +7603,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 || '';
                        }
                }
 
@@ -7361,7 +7649,7 @@ var Marker = Layer.extend({
                }
                this._initInteraction();
                if (newShadow && addShadow) {
-                       this.getPane('shadowPane').appendChild(this._shadow);
+                       this.getPane(options.shadowPane).appendChild(this._shadow);
                }
        },
 
@@ -7387,7 +7675,10 @@ var Marker = Layer.extend({
        },
 
        _setPos: function (pos) {
-               setPosition(this._icon, pos);
+
+               if (this._icon) {
+                       setPosition(this._icon, pos);
+               }
 
                if (this._shadow) {
                        setPosition(this._shadow, pos);
@@ -7399,7 +7690,9 @@ var Marker = Layer.extend({
        },
 
        _updateZIndex: function (offset) {
-               this._icon.style.zIndex = this._zIndex + offset;
+               if (this._icon) {
+                       this._icon.style.zIndex = this._zIndex + offset;
+               }
        },
 
        _animateZoom: function (opt) {
@@ -7445,7 +7738,9 @@ var Marker = Layer.extend({
        _updateOpacity: function () {
                var opacity = this.options.opacity;
 
-               setOpacity(this._icon, opacity);
+               if (this._icon) {
+                       setOpacity(this._icon, opacity);
+               }
 
                if (this._shadow) {
                        setOpacity(this._shadow, opacity);
@@ -7461,11 +7756,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;
        }
 });
 
@@ -7582,6 +7877,9 @@ var Path = Layer.extend({
                setOptions(this, style);
                if (this._renderer) {
                        this._renderer._updateStyle(this);
+                       if (this.options.stroke && style && style.hasOwnProperty('weight')) {
+                               this._updateBounds();
+                       }
                }
                return this;
        },
@@ -7616,7 +7914,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;
        }
 });
 
@@ -7649,9 +7947,13 @@ var CircleMarker = Path.extend({
        // @method setLatLng(latLng: LatLng): this
        // Sets the position of a circle marker to a new location.
        setLatLng: function (latlng) {
+               var oldLatLng = this._latlng;
                this._latlng = toLatLng(latlng);
                this.redraw();
-               return this.fire('move', {latlng: this._latlng});
+
+               // @event move: Event
+               // Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
+               return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
        },
 
        // @method getLatLng(): LatLng
@@ -7801,8 +8103,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]));
@@ -7904,6 +8206,8 @@ var Polyline = Path.extend({
                return !this._latlngs.length;
        },
 
+       // @method closestLayerPoint(p: Point): Point
+       // Returns the point closest to `p` on the Polyline.
        closestLayerPoint: function (p) {
                var minDistance = Infinity,
                    minPoint = null,
@@ -8021,16 +8325,21 @@ var Polyline = Path.extend({
                this._rings = [];
                this._projectLatlngs(this._latlngs, this._rings, pxBounds);
 
-               var w = this._clickTolerance(),
-                   p = new Point(w, w);
-
                if (this._bounds.isValid() && pxBounds.isValid()) {
-                       pxBounds.min._subtract(p);
-                       pxBounds.max._add(p);
-                       this._pxBounds = pxBounds;
+                       this._rawPxBounds = pxBounds;
+                       this._updateBounds();
                }
        },
 
+       _updateBounds: function () {
+               var w = this._clickTolerance(),
+                   p = new Point(w, w);
+               this._pxBounds = new Bounds([
+                       this._rawPxBounds.min.subtract(p),
+                       this._rawPxBounds.max.add(p)
+               ]);
+       },
+
        // recursively turns latlngs into a set of rings with projected coordinates
        _projectLatlngs: function (latlngs, result, projectedBounds) {
                var flat = latlngs[0] instanceof LatLng,
@@ -8295,7 +8604,7 @@ var Polygon = Polyline.extend({
                var inside = false,
                    part, p1, p2, i, j, k, len, len2;
 
-               if (!this._pxBounds.contains(p)) { return false; }
+               if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
 
                // ray casting algorithm for detecting if point is in polygon
                for (i = 0, len = this._parts.length; i < len; i++) {
@@ -8391,6 +8700,9 @@ var GeoJSON = FeatureGroup.extend({
         * @option coordsToLatLng: Function = *
         * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
         * The default is the `coordsToLatLng` static method.
+        *
+        * @option markersInheritOptions: Boolean = false
+        * Whether default Markers for "Point" type Features inherit from group options.
         */
 
        initialize: function (geojson, options) {
@@ -8440,9 +8752,13 @@ var GeoJSON = FeatureGroup.extend({
                return this.addLayer(layer);
        },
 
-       // @method resetStyle( <Path> layer ): this
+       // @method resetStyle( <Path> layer? ): this
        // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
+       // If `layer` is omitted, the style of all features in the current layer is reset.
        resetStyle: function (layer) {
+               if (layer === undefined) {
+                       return this.eachLayer(this.resetStyle, this);
+               }
                // reset any custom styles
                layer.options = extend({}, layer.defaultOptions);
                this._setLayerStyle(layer, this.options.style);
@@ -8458,10 +8774,10 @@ var GeoJSON = FeatureGroup.extend({
        },
 
        _setLayerStyle: function (layer, style) {
-               if (typeof style === 'function') {
-                       style = style(layer.feature);
-               }
                if (layer.setStyle) {
+                       if (typeof style === 'function') {
+                               style = style(layer.feature);
+                       }
                        layer.setStyle(style);
                }
        }
@@ -8490,12 +8806,12 @@ function geometryToLayer(geojson, options) {
        switch (geometry.type) {
        case 'Point':
                latlng = _coordsToLatLng(coords);
-               return pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng);
+               return _pointToLayer(pointToLayer, geojson, latlng, options);
 
        case 'MultiPoint':
                for (i = 0, len = coords.length; i < len; i++) {
                        latlng = _coordsToLatLng(coords[i]);
-                       layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng));
+                       layers.push(_pointToLayer(pointToLayer, geojson, latlng, options));
                }
                return new FeatureGroup(layers);
 
@@ -8528,6 +8844,12 @@ function geometryToLayer(geojson, options) {
        }
 }
 
+function _pointToLayer(pointToLayerFn, geojson, latlng, options) {
+       return pointToLayerFn ?
+               pointToLayerFn(geojson, latlng) :
+               new Marker(latlng, options && options.markersInheritOptions && options);
+}
+
 // @function coordsToLatLng(coords: Array): LatLng
 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
@@ -8544,8 +8866,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);
        }
@@ -8558,8 +8880,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
@@ -8583,8 +8905,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
@@ -8611,19 +8933,26 @@ var PointToGeoJSON = {
 };
 
 // @namespace Marker
-// @method toGeoJSON(): Object
+// @section Other methods
+// @method toGeoJSON(precision?: Number): Object
+// `precision` is the number of decimal places for coordinates.
+// The default value is 6 places.
 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
 Marker.include(PointToGeoJSON);
 
 // @namespace CircleMarker
-// @method toGeoJSON(): Object
+// @method toGeoJSON(precision?: Number): Object
+// `precision` is the number of decimal places for coordinates.
+// The default value is 6 places.
 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
 Circle.include(PointToGeoJSON);
 CircleMarker.include(PointToGeoJSON);
 
 
 // @namespace Polyline
-// @method toGeoJSON(): Object
+// @method toGeoJSON(precision?: Number): Object
+// `precision` is the number of decimal places for coordinates.
+// The default value is 6 places.
 // 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) {
@@ -8639,7 +8968,9 @@ Polyline.include({
 });
 
 // @namespace Polygon
-// @method toGeoJSON(): Object
+// @method toGeoJSON(precision?: Number): Object
+// `precision` is the number of decimal places for coordinates.
+// The default value is 6 places.
 // 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) {
@@ -8675,7 +9006,9 @@ LayerGroup.include({
                });
        },
 
-       // @method toGeoJSON(): Object
+       // @method toGeoJSON(precision?: Number): Object
+       // `precision` is the number of decimal places for coordinates.
+       // The default value is 6 places.
        // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
        toGeoJSON: function (precision) {
 
@@ -8722,7 +9055,7 @@ LayerGroup.include({
 // @namespace GeoJSON
 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
 // Creates a GeoJSON layer. Optionally accepts an object in
-// [GeoJSON format](http://geojson.org/geojson-spec.html) to display on the map
+// [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
 // (you can alternatively add it later with `addData` method) and an `options` object.
 function geoJSON(geojson, options) {
        return new GeoJSON(geojson, options);
@@ -8764,8 +9097,10 @@ var ImageOverlay = Layer.extend({
                // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
                interactive: false,
 
-               // @option crossOrigin: Boolean = false
-               // If true, the image will have its crossOrigin attribute set to ''. This is needed if you want to access image pixel data.
+               // @option crossOrigin: Boolean|String = false
+               // Whether the crossOrigin attribute will be added to the image.
+               // If a String is provided, the image will have its crossOrigin attribute set to the String provided. This is needed if you want to access image pixel data.
+               // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
                crossOrigin: false,
 
                // @option errorOverlayUrl: String = ''
@@ -8773,12 +9108,12 @@ var ImageOverlay = Layer.extend({
                errorOverlayUrl: '',
 
                // @option zIndex: Number = 1
-               // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the tile layer.
+               // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
                zIndex: 1,
 
                // @option className: String = ''
                // A custom class name to assign to the image. Empty by default.
-               className: '',
+               className: ''
        },
 
        initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
@@ -8884,7 +9219,7 @@ var ImageOverlay = Layer.extend({
                return events;
        },
 
-       // @method: setZIndex(value: Number) : this
+       // @method setZIndex(value: Number): this
        // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
        setZIndex: function (value) {
                this.options.zIndex = value;
@@ -8906,9 +9241,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;
@@ -8918,14 +9256,19 @@ var ImageOverlay = Layer.extend({
                img.onload = bind(this.fire, this, 'load');
                img.onerror = bind(this._overlayOnError, this, 'error');
 
-               if (this.options.crossOrigin) {
-                       img.crossOrigin = '';
+               if (this.options.crossOrigin || this.options.crossOrigin === '') {
+                       img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
                }
 
                if (this.options.zIndex) {
                        this._updateZIndex();
                }
 
+               if (wasElementSupplied) {
+                       this._url = img.src;
+                       return;
+               }
+
                img.src = this._url;
                img.alt = this.options.alt;
        },
@@ -8962,7 +9305,7 @@ var ImageOverlay = Layer.extend({
 
        _overlayOnError: function () {
                // @event error: Event
-               // Fired when the ImageOverlay layer has loaded its image
+               // Fired when the ImageOverlay layer fails to load its image
                this.fire('error');
 
                var errorUrl = this.options.errorOverlayUrl;
@@ -8995,7 +9338,7 @@ var imageOverlay = function (url, bounds, options) {
  * ```js
  * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
  *     videoBounds = [[ 32, -130], [ 13, -100]];
- * L.VideoOverlay(videoUrl, videoBounds ).addTo(map);
+ * L.videoOverlay(videoUrl, videoBounds ).addTo(map);
  * ```
  */
 
@@ -9010,15 +9353,21 @@ var VideoOverlay = ImageOverlay.extend({
 
                // @option loop: Boolean = true
                // Whether the video will loop back to the beginning when played.
-               loop: true
+               loop: true,
+
+               // @option keepAspectRatio: Boolean = true
+               // Whether the video will save aspect ratio after the projection.
+               // Relevant for supported browsers. Browser compatibility- https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit
+               keepAspectRatio: true
        },
 
        _initImage: function () {
                var wasElementSupplied = this._url.tagName === 'VIDEO';
                var vid = this._image = wasElementSupplied ? this._url : create$1('video');
 
-               vid.class = vid.class || '';
-               vid.class += 'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : '');
+               addClass(vid, 'leaflet-image-layer');
+               if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
+               if (this.options.className) { addClass(vid, this.options.className); }
 
                vid.onselectstart = falseFn;
                vid.onmousemove = falseFn;
@@ -9027,10 +9376,20 @@ var VideoOverlay = ImageOverlay.extend({
                // Fired when the video has finished loading the first frame
                vid.onloadeddata = bind(this.fire, this, 'load');
 
-               if (wasElementSupplied) { return; }
+               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]; }
 
+               if (!this.options.keepAspectRatio && vid.style.hasOwnProperty('objectFit')) { vid.style['objectFit'] = 'fill'; }
                vid.autoplay = !!this.options.autoplay;
                vid.loop = !!this.options.loop;
                for (var i = 0; i < this._url.length; i++) {
@@ -9054,6 +9413,53 @@ function videoOverlay(video, bounds, options) {
        return new VideoOverlay(video, bounds, options);
 }
 
+/*
+ * @class SVGOverlay
+ * @aka L.SVGOverlay
+ * @inherits ImageOverlay
+ *
+ * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`.
+ *
+ * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element.
+ *
+ * @example
+ *
+ * ```js
+ * var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ * svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg");
+ * svgElement.setAttribute('viewBox', "0 0 200 200");
+ * svgElement.innerHTML = '<rect width="200" height="200"/><rect x="75" y="23" width="50" height="50" style="fill:red"/><rect x="75" y="123" width="50" height="50" style="fill:#0013ff"/>';
+ * var svgElementBounds = [ [ 32, -130 ], [ 13, -100 ] ];
+ * L.svgOverlay(svgElement, svgElementBounds).addTo(map);
+ * ```
+ */
+
+var SVGOverlay = ImageOverlay.extend({
+       _initImage: function () {
+               var el = this._image = this._url;
+
+               addClass(el, 'leaflet-image-layer');
+               if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); }
+               if (this.options.className) { addClass(el, this.options.className); }
+
+               el.onselectstart = falseFn;
+               el.onmousemove = falseFn;
+       }
+
+       // @method getElement(): SVGElement
+       // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement)
+       // used by this overlay.
+});
+
+
+// @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options)
+// Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to.
+// A viewBox attribute is required on the SVG element to zoom in and out properly.
+
+function svgOverlay(el, bounds, options) {
+       return new SVGOverlay(el, bounds, options);
+}
+
 /*
  * @class DivOverlay
  * @inherits Layer
@@ -9208,6 +9614,38 @@ var DivOverlay = Layer.extend({
                return this;
        },
 
+       _prepareOpen: function (parent, layer, latlng) {
+               if (!(layer instanceof Layer)) {
+                       latlng = layer;
+                       layer = parent;
+               }
+
+               if (layer instanceof FeatureGroup) {
+                       for (var id in parent._layers) {
+                               layer = parent._layers[id];
+                               break;
+                       }
+               }
+
+               if (!latlng) {
+                       if (layer.getCenter) {
+                               latlng = layer.getCenter();
+                       } else if (layer.getLatLng) {
+                               latlng = layer.getLatLng();
+                       } else {
+                               throw new Error('Unable to get source layer LatLng.');
+                       }
+               }
+
+               // set overlay source to this layer
+               this._source = layer;
+
+               // update the overlay (content, layout, ect...)
+               this.update();
+
+               return latlng;
+       },
+
        _updateContent: function () {
                if (!this._content) { return; }
 
@@ -9331,6 +9769,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.
@@ -9474,7 +9917,8 @@ var Popup = DivOverlay.extend({
        },
 
        _adjustPan: function () {
-               if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; }
+               if (!this.options.autoPan) { return; }
+               if (this._map._panAnim) { this._map._panAnim.stop(); }
 
                var map = this._map,
                    marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
@@ -9654,30 +10098,10 @@ 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;
-                       layer = this;
-               }
-
-               if (layer instanceof FeatureGroup) {
-                       for (var id in this._layers) {
-                               layer = this._layers[id];
-                               break;
-                       }
-               }
-
-               if (!latlng) {
-                       latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
-               }
-
                if (this._popup && this._map) {
-                       // set popup source to this layer
-                       this._popup._source = layer;
-
-                       // update the popup (content, layout, ect...)
-                       this._popup.update();
+                       latlng = this._popup._prepareOpen(this, layer, latlng);
 
                        // open the popup on the map
                        this._map.openPopup(this._popup, latlng);
@@ -9782,7 +10206,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.
@@ -9808,7 +10232,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',
 
@@ -10072,31 +10496,10 @@ 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;
-                       layer = this;
-               }
-
-               if (layer instanceof FeatureGroup) {
-                       for (var id in this._layers) {
-                               layer = this._layers[id];
-                               break;
-                       }
-               }
-
-               if (!latlng) {
-                       latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
-               }
-
                if (this._tooltip && this._map) {
-
-                       // set tooltip source to this layer
-                       this._tooltip._source = layer;
-
-                       // update the tooltip (content, layout, ect...)
-                       this._tooltip.update();
+                       latlng = this._tooltip._prepareOpen(this, layer, latlng);
 
                        // open the tooltip on the map
                        this._map.openTooltip(this._tooltip, latlng);
@@ -10207,8 +10610,9 @@ var DivIcon = Icon.extend({
                // iconAnchor: (Point),
                // popupAnchor: (Point),
 
-               // @option html: String = ''
-               // Custom HTML code to put inside the div element, empty by default.
+               // @option html: String|HTMLElement = ''
+               // Custom HTML code to put inside the div element, empty by default. Alternatively,
+               // an instance of `HTMLElement`.
                html: false,
 
                // @option bgPos: Point = [0, 0]
@@ -10222,7 +10626,12 @@ var DivIcon = Icon.extend({
                var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
                    options = this.options;
 
-               div.innerHTML = options.html !== false ? options.html : '';
+               if (options.html instanceof Element) {
+                       empty(div);
+                       div.appendChild(options.html);
+               } else {
+                       div.innerHTML = options.html !== false ? options.html : '';
+               }
 
                if (options.bgPos) {
                        var bgPos = toPoint(options.bgPos);
@@ -10412,7 +10821,7 @@ var GridLayer = Layer.extend({
                remove(this._container);
                map._removeZoomLimit(this);
                this._container = null;
-               this._tileZoom = null;
+               this._tileZoom = undefined;
        },
 
        // @method bringToFront: this
@@ -10501,7 +10910,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 () {
@@ -10706,7 +11115,7 @@ var GridLayer = Layer.extend({
                }
                this._removeAllTiles();
 
-               this._tileZoom = null;
+               this._tileZoom = undefined;
        },
 
        _retainParent: function (x, y, z, minZoom) {
@@ -10916,7 +11325,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);
                                }
                        }
@@ -10968,26 +11380,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;
@@ -11073,8 +11485,6 @@ var GridLayer = Layer.extend({
        },
 
        _tileReady: function (coords, err, tile) {
-               if (!this._map) { return; }
-
                if (err) {
                        // @event tileerror: TileErrorEvent
                        // Fired when there is an error loading a tile.
@@ -11164,12 +11574,12 @@ function gridLayer(options) {
  * @class TileLayer
  * @inherits GridLayer
  * @aka L.TileLayer
- * Used to load and display tile layers on the map. Extends `GridLayer`.
+ * Used to load and display tile layers on the map. Note that most tile servers require attribution, which you can set under `Layer`. Extends `GridLayer`.
  *
  * @example
  *
  * ```js
- * L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map);
+ * L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar', attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'}).addTo(map);
  * ```
  *
  * @section URL template
@@ -11228,8 +11638,10 @@ var TileLayer = GridLayer.extend({
                // 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.
                detectRetina: false,
 
-               // @option crossOrigin: Boolean = false
-               // If true, all tiles will have their crossOrigin attribute set to ''. This is needed if you want to access tile pixel data.
+               // @option crossOrigin: Boolean|String = false
+               // Whether the crossOrigin attribute will be added to the tiles.
+               // If a String is provided, all tiles will have their crossOrigin attribute set to the String provided. This is needed if you want to access tile pixel data.
+               // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
                crossOrigin: false
        },
 
@@ -11267,7 +11679,13 @@ var TileLayer = GridLayer.extend({
 
        // @method setUrl(url: String, noRedraw?: Boolean): this
        // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
+       // If the URL does not change, the layer will not be redrawn unless
+       // the noRedraw parameter is set to false.
        setUrl: function (url, noRedraw) {
+               if (this._url === url && noRedraw === undefined) {
+                       noRedraw = true;
+               }
+
                this._url = url;
 
                if (!noRedraw) {
@@ -11278,7 +11696,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');
@@ -11286,8 +11704,8 @@ var TileLayer = GridLayer.extend({
                on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
                on(tile, 'error', bind(this._tileOnError, this, done, tile));
 
-               if (this.options.crossOrigin) {
-                       tile.crossOrigin = '';
+               if (this.options.crossOrigin || this.options.crossOrigin === '') {
+                       tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
                }
 
                /*
@@ -11343,7 +11761,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);
@@ -11384,9 +11802,32 @@ var TileLayer = GridLayer.extend({
                                if (!tile.complete) {
                                        tile.src = emptyImageUrl;
                                        remove(tile);
+                                       delete this._tiles[i];
                                }
                        }
                }
+       },
+
+       _removeTile: function (key) {
+               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);
+               }
+
+               return GridLayer.prototype._removeTile.call(this, key);
+       },
+
+       _tileReady: function (coords, err, tile) {
+               if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) {
+                       return;
+               }
+
+               return GridLayer.prototype._tileReady.call(this, coords, err, tile);
        }
 });
 
@@ -11474,7 +11915,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;
        },
@@ -11492,16 +11936,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(','),
-
+                   [min.y, min.x, max.y, max.x] :
+                   [min.x, min.y, max.x, max.y]).join(','),
                    url = TileLayer.prototype.getTileUrl.call(this, coords);
-
                return url +
                        getParamString(this.wmsParams, url, this.options.uppercase) +
                        (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
@@ -11559,7 +12002,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) {
@@ -11715,7 +12162,7 @@ var Canvas = Renderer.extend({
        _initContainer: function () {
                var container = this._container = document.createElement('canvas');
 
-               on(container, 'mousemove', throttle(this._onMouseMove, 32, this), this);
+               on(container, 'mousemove', this._onMouseMove, this);
                on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
                on(container, 'mouseout', this._handleMouseOut, this);
 
@@ -11723,6 +12170,7 @@ var Canvas = Renderer.extend({
        },
 
        _destroyContainer: function () {
+               cancelAnimFrame(this._redrawRequest);
                delete this._ctx;
                remove(this._container);
                off(this._container);
@@ -11744,8 +12192,6 @@ var Canvas = Renderer.extend({
        _update: function () {
                if (this._map._animatingZoom && this._bounds) { return; }
 
-               this._drawnLayers = {};
-
                Renderer.prototype._update.call(this);
 
                var b = this._bounds,
@@ -11817,7 +12263,7 @@ var Canvas = Renderer.extend({
 
                delete layer._order;
 
-               delete this._layers[L.stamp(layer)];
+               delete this._layers[stamp(layer)];
 
                this._requestRedraw(layer);
        },
@@ -11839,14 +12285,20 @@ var Canvas = Renderer.extend({
        },
 
        _updateDashArray: function (layer) {
-               if (layer.options.dashArray) {
-                       var parts = layer.options.dashArray.split(','),
+               if (typeof layer.options.dashArray === 'string') {
+                       var parts = layer.options.dashArray.split(/[, ]+/),
                            dashArray = [],
+                           dashValue,
                            i;
                        for (i = 0; i < parts.length; i++) {
-                               dashArray.push(Number(parts[i]));
+                               dashValue = Number(parts[i]);
+                               // Ignore dash array containing invalid lengths
+                               if (isNaN(dashValue)) { return; }
+                               dashArray.push(dashValue);
                        }
                        layer.options._dashArray = dashArray;
+               } else {
+                       layer.options._dashArray = layer.options.dashArray;
                }
        },
 
@@ -11924,8 +12376,6 @@ var Canvas = Renderer.extend({
 
                if (!len) { return; }
 
-               this._drawnLayers[layer._leaflet_id] = layer;
-
                ctx.beginPath();
 
                for (i = 0; i < len; i++) {
@@ -11949,10 +12399,8 @@ var Canvas = Renderer.extend({
 
                var p = layer._point,
                    ctx = this._ctx,
-                   r = layer._radius,
-                   s = (layer._radiusY || r) / r;
-
-               this._drawnLayers[layer._leaflet_id] = layer;
+                   r = Math.max(Math.round(layer._radius), 1),
+                   s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
 
                if (s !== 1) {
                        ctx.save();
@@ -12024,10 +12472,15 @@ var Canvas = Renderer.extend({
                        removeClass(this._container, 'leaflet-interactive');
                        this._fireEvent([layer], e, 'mouseout');
                        this._hoveredLayer = null;
+                       this._mouseHoverThrottled = false;
                }
        },
 
        _handleMouseHover: function (e, point) {
+               if (this._mouseHoverThrottled) {
+                       return;
+               }
+
                var layer, candidateHoveredLayer;
 
                for (var order = this._drawFirst; order; order = order.next) {
@@ -12050,6 +12503,11 @@ var Canvas = Renderer.extend({
                if (this._hoveredLayer) {
                        this._fireEvent([this._hoveredLayer], e);
                }
+
+               this._mouseHoverThrottled = true;
+               setTimeout(L.bind(function () {
+                       this._mouseHoverThrottled = false;
+               }, this), 32);
        },
 
        _fireEvent: function (layers, e, type) {
@@ -12058,6 +12516,9 @@ var Canvas = Renderer.extend({
 
        _bringToFront: function (layer) {
                var order = layer._order;
+
+               if (!order) { return; }
+
                var next = order.next;
                var prev = order.prev;
 
@@ -12071,7 +12532,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;
                }
 
@@ -12086,6 +12547,9 @@ var Canvas = Renderer.extend({
 
        _bringToBack: function (layer) {
                var order = layer._order;
+
+               if (!order) { return; }
+
                var next = order.next;
                var prev = order.prev;
 
@@ -12099,7 +12563,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;
                }
 
@@ -12141,7 +12605,6 @@ var vmlCreate = (function () {
 /*
  * @class SVG
  *
- * 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.
  *
  * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
  * with old versions of Internet Explorer.
@@ -12243,7 +12706,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) {
@@ -12320,6 +12783,7 @@ var SVG = Renderer.extend({
                off(this._container);
                delete this._container;
                delete this._rootGroup;
+               delete this._svgSize;
        },
 
        _onZoomStart: function () {
@@ -12432,15 +12896,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);
        },
@@ -12463,6 +12927,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) {
@@ -12481,10 +12946,7 @@ Map.include({
                var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
 
                if (!renderer) {
-                       // @namespace Map; @option preferCanvas: Boolean = false
-                       // Whether `Path`s should be rendered on a `Canvas` renderer.
-                       // By default, all `Path`s are rendered in a `SVG` renderer.
-                       renderer = this._renderer = (this.options.preferCanvas && canvas$1()) || svg$1();
+                       renderer = this._renderer = this._createRenderer();
                }
 
                if (!this.hasLayer(renderer)) {
@@ -12500,10 +12962,17 @@ Map.include({
 
                var renderer = this._paneRenderers[name];
                if (renderer === undefined) {
-                       renderer = (SVG && svg$1({pane: name})) || (Canvas && canvas$1({pane: name}));
+                       renderer = this._createRenderer({pane: name});
                        this._paneRenderers[name] = renderer;
                }
                return renderer;
+       },
+
+       _createRenderer: function (options) {
+               // @namespace Map; @option preferCanvas: Boolean = false
+               // Whether `Path`s should be rendered on a `Canvas` renderer.
+               // By default, all `Path`s are rendered in a `SVG` renderer.
+               return (this.options.preferCanvas && canvas$1(options)) || svg$1(options);
        }
 });
 
@@ -12513,7 +12982,7 @@ Map.include({
 
 /*
  * @class Rectangle
- * @aka L.Retangle
+ * @aka L.Rectangle
  * @inherits Polygon
  *
  * A class for drawing rectangle overlays on a map. Extends `Polygon`.
@@ -12891,10 +13360,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
@@ -12902,6 +13368,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]);
@@ -12954,6 +13427,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,
@@ -13133,24 +13607,22 @@ var Keyboard = Handler.extend({
                    offset;
 
                if (key in this._panKeys) {
+                       if (!map._panAnim || !map._panAnim._inProgress) {
+                               offset = this._panKeys[key];
+                               if (e.shiftKey) {
+                                       offset = toPoint(offset).multiplyBy(3);
+                               }
 
-                       if (map._panAnim && map._panAnim._inProgress) { return; }
-
-                       offset = this._panKeys[key];
-                       if (e.shiftKey) {
-                               offset = toPoint(offset).multiplyBy(3);
-                       }
-
-                       map.panBy(offset);
+                               map.panBy(offset);
 
-                       if (map.options.maxBounds) {
-                               map.panInsideBounds(map.options.maxBounds);
+                               if (map.options.maxBounds) {
+                                       map.panInsideBounds(map.options.maxBounds);
+                               }
                        }
-
                } 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 {
@@ -13468,7 +13940,7 @@ var TouchZoom = Handler.extend({
                }
 
                if (!this._moved) {
-                       map._moveStart(true);
+                       map._moveStart(true, false);
                        this._moved = true;
                }
 
@@ -13514,21 +13986,9 @@ Map.ScrollWheelZoom = ScrollWheelZoom;
 Map.Tap = Tap;
 Map.TouchZoom = TouchZoom;
 
-// misc
-
-var oldL = window.L;
-function noConflict() {
-       window.L = oldL;
-       return this;
-}
-
-// Always export us to window global (see #2364)
-window.L = exports;
-
 Object.freeze = freeze;
 
 exports.version = version;
-exports.noConflict = noConflict;
 exports.Control = Control;
 exports.control = control;
 exports.Browser = Browser;
@@ -13571,6 +14031,8 @@ exports.ImageOverlay = ImageOverlay;
 exports.imageOverlay = imageOverlay;
 exports.VideoOverlay = VideoOverlay;
 exports.videoOverlay = videoOverlay;
+exports.SVGOverlay = SVGOverlay;
+exports.svgOverlay = svgOverlay;
 exports.DivOverlay = DivOverlay;
 exports.Popup = Popup;
 exports.popup = popup;
@@ -13605,5 +14067,14 @@ exports.rectangle = rectangle;
 exports.Map = Map;
 exports.map = createMap;
 
+var oldL = window.L;
+exports.noConflict = function() {
+       window.L = oldL;
+       return this;
+}
+
+// Always export us to window global (see #2364)
+window.L = exports;
+
 })));
 //# sourceMappingURL=leaflet-src.js.map