]> git.openstreetmap.org Git - rails.git/blobdiff - vendor/assets/leaflet/leaflet.js
Merge remote-tracking branch 'upstream/pull/2574'
[rails.git] / vendor / assets / leaflet / leaflet.js
index 12bf1f0cda8a8bbe9b1d83955d4107f37a87aada..ab74e48b4f2974643f0fcb5530ce946a1c6256dc 100644 (file)
@@ -1,6 +1,6 @@
 /* @preserve
- * Leaflet 1.3.4, a JS library for interactive maps. http://leafletjs.com
- * (c) 2010-2018 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) {
@@ -9,7 +9,7 @@
        (factory((global.L = {})));
 }(this, (function (exports) { 'use strict';
 
-var version = "1.3.4";
+var version = "1.6.0";
 
 /*
  * @namespace Util
@@ -468,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) {
 
@@ -1648,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) {
@@ -1673,7 +1675,7 @@ var SphericalMercator = {
        },
 
        bounds: (function () {
-               var d = 6378137 * Math.PI;
+               var d = earthRadius * Math.PI;
                return new Bounds([-d, -d], [d, d]);
        })()
 };
@@ -1904,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).
@@ -1925,6 +1927,23 @@ var mobileGecko = mobile && gecko;
 // `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).
@@ -1987,6 +2006,7 @@ var Browser = (Object.freeze || Object)({
        mobileOpera: mobileOpera,
        mobileGecko: mobileGecko,
        retina: retina,
+       passiveEvents: passiveEvents,
        canvas: canvas,
        svg: svg,
        vml: vml
@@ -2171,6 +2191,7 @@ function addDoubleTapListener(obj, handler, id) {
                                touch$$1 = newTouch;
                        }
                        touch$$1.type = 'dblclick';
+                       touch$$1.button = 0;
                        handler(touch$$1);
                        last = null;
                }
@@ -2180,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
@@ -2197,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);
        }
@@ -2290,7 +2311,7 @@ function empty(el) {
 // 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) {
+       if (parent && parent.lastChild !== el) {
                parent.appendChild(el);
        }
 }
@@ -2299,7 +2320,7 @@ function toFront(el) {
 // 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) {
+       if (parent && parent.firstChild !== el) {
                parent.insertBefore(el, parent.firstChild);
        }
 }
@@ -2352,6 +2373,11 @@ function setClass(el, name) {
 // @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;
 }
 
@@ -2668,7 +2694,7 @@ function addOne(obj, type, fn, context) {
        } else if ('addEventListener' in obj) {
 
                if (type === 'mousewheel') {
-                       obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
+                       obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, passiveEvents ? {passive: false} : false);
 
                } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
                        handler = function (e) {
@@ -2713,7 +2739,7 @@ function removeOne(obj, type, fn, context) {
        } else if ('removeEventListener' in obj) {
 
                if (type === 'mousewheel') {
-                       obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
+                       obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, passiveEvents ? {passive: false} : false);
 
                } else {
                        obj.removeEventListener(
@@ -3113,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();
 
@@ -3133,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
@@ -3496,6 +3524,51 @@ var Map = Evented.extend({
                return 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
@@ -4256,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);
@@ -4322,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);
                }
@@ -4361,7 +4440,7 @@ var Map = Evented.extend({
                        originalEvent: e
                };
 
-               if (e.type !== 'keypress') {
+               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);
@@ -4550,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();
@@ -4613,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,
@@ -4732,6 +4815,8 @@ var Control = Class.extend({
                        corner.appendChild(container);
                }
 
+               this._map.on('unload', this.remove, this);
+
                return this;
        },
 
@@ -4748,6 +4833,7 @@ var Control = Class.extend({
                        this.onRemove(this._map);
                }
 
+               this._map.off('unload', this.remove, this);
                this._map = null;
 
                return this;
@@ -4970,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;
@@ -5000,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);
@@ -5028,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) {
@@ -5145,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);
@@ -5243,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);
 };
@@ -5530,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) {
@@ -6275,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 = {
@@ -6435,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();
  * ```
@@ -6457,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
@@ -7366,6 +7452,10 @@ 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).
@@ -7458,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) {
@@ -7553,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);
                }
        },
 
@@ -7579,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);
@@ -7591,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) {
@@ -7637,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);
@@ -7774,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;
        },
@@ -7841,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
@@ -8215,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,
@@ -8585,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) {
@@ -8634,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);
@@ -8652,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);
                }
        }
@@ -8684,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);
 
@@ -8722,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.
@@ -8805,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) {
@@ -8833,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) {
@@ -8869,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) {
 
@@ -9214,7 +9353,12 @@ 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 () {
@@ -9223,6 +9367,7 @@ var VideoOverlay = ImageOverlay.extend({
 
                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;
@@ -9244,6 +9389,7 @@ var VideoOverlay = ImageOverlay.extend({
 
                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++) {
@@ -9267,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
@@ -9421,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; }
 
@@ -9692,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,
@@ -9874,28 +10100,8 @@ Layer.include({
        // @method openPopup(latlng?: LatLng): this
        // 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);
@@ -10292,29 +10498,8 @@ Layer.include({
        // @method openTooltip(latlng?: LatLng): this
        // 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);
@@ -10425,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]
@@ -10440,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);
@@ -11383,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('https://{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
@@ -11488,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) {
@@ -11965,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);
 
@@ -11995,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,
@@ -12066,8 +12261,6 @@ var Canvas = Renderer.extend({
                        this._drawFirst = next;
                }
 
-               delete this._drawnLayers[layer._leaflet_id];
-
                delete layer._order;
 
                delete this._layers[stamp(layer)];
@@ -12095,9 +12288,13 @@ var Canvas = Renderer.extend({
                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 {
@@ -12179,8 +12376,6 @@ var Canvas = Renderer.extend({
 
                if (!len) { return; }
 
-               this._drawnLayers[layer._leaflet_id] = layer;
-
                ctx.beginPath();
 
                for (i = 0; i < len; i++) {
@@ -12207,8 +12402,6 @@ var Canvas = Renderer.extend({
                    r = Math.max(Math.round(layer._radius), 1),
                    s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
 
-               this._drawnLayers[layer._leaflet_id] = layer;
-
                if (s !== 1) {
                        ctx.save();
                        ctx.scale(1, s);
@@ -12279,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) {
@@ -12305,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) {
@@ -12313,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;
 
@@ -12341,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;
 
@@ -12396,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.
@@ -13823,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;