X-Git-Url: https://git.openstreetmap.org./rails.git/blobdiff_plain/effb1b7f4170bb7244c4dfffcbe6134fe00e2bc4..4b2d6436cee11c0b3d751ef15cace053e26fbf3d:/vendor/assets/leaflet/leaflet.js?ds=sidebyside diff --git a/vendor/assets/leaflet/leaflet.js b/vendor/assets/leaflet/leaflet.js index 0f239b11e..a845eb6ae 100644 --- a/vendor/assets/leaflet/leaflet.js +++ b/vendor/assets/leaflet/leaflet.js @@ -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.5.1+build.2e3e0ff, a JS library for interactive maps. http://leafletjs.com + * (c) 2010-2018 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.5.1+build.2e3e0ffb"; /* * @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,10 +125,10 @@ 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); - return Math.round(num * pow) / pow; + digits = (digits === undefined ? 6 : digits); + return +(Math.round(num + ('e+' + digits)) + ('e-' + digits)); } // @function trim(str: String): String @@ -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; @@ -1870,7 +1924,7 @@ 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; @@ -1914,6 +1968,7 @@ var Browser = (Object.freeze || Object)({ webkit: webkit, android: android, android23: android23, + androidStock: androidStock, opera: opera, chrome: chrome, gecko: gecko, @@ -1949,6 +2004,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 +2047,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 +2173,7 @@ function addDoubleTapListener(obj, handler, id) { touch$$1 = newTouch; } touch$$1.type = 'dblclick'; + touch$$1.button = 0; handler(touch$$1); last = null; } @@ -2153,673 +2210,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'`). +// @property TRANSFORM: String +// Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit). +var TRANSFORM = testProp( + ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']); -// @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) { +// 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 (typeof types === 'object') { - for (var type in types) { - addOne(obj, type, types[type], fn); - } - } else { - types = splitWords(types); +// @property TRANSITION: String +// Vendor-prefixed transition style name. +var TRANSITION = testProp( + ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']); - for (var i = 0, len = types.length; i < len; i++) { - addOne(obj, types[i], fn, context); - } - } +// @property TRANSITION_END: String +// Vendor-prefixed transitionend event name. +var TRANSITION_END = + TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend'; - return this; + +// @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; } -var eventsKey = '_leaflet_events'; +// @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 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. + if ((!value || value === 'auto') && document.defaultView) { + var css = document.defaultView.getComputedStyle(el, null); + value = css ? css[style] : null; + } + return value === 'auto' ? null : value; +} -// @alternative -// @function off(el: HTMLElement, eventMap: Object, context?: Object): this -// Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` +// @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 || ''; -// @alternative -// @function off(el: HTMLElement): this -// Removes all known event listeners -function off(obj, types, fn, context) { + if (container) { + container.appendChild(el); + } + return el; +} - if (typeof types === 'object') { - for (var type in types) { - removeOne(obj, type, types[type], fn); - } - } else if (types) { - types = splitWords(types); +// @function remove(el: HTMLElement) +// Removes `el` from its parent element +function remove(el) { + var parent = el.parentNode; + if (parent) { + parent.removeChild(el); + } +} - 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]; +// @function empty(el: HTMLElement) +// Removes all of `el`'s children elements from `el` +function empty(el) { + while (el.firstChild) { + el.removeChild(el.firstChild); } +} - return this; +// @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); + } } -function addOne(obj, type, fn, context) { - var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''); +// @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); + } +} - if (obj[eventsKey] && obj[eventsKey][id]) { return this; } +// @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); +} - var handler = function (e) { - return fn.call(context || obj, e || window.event); - }; +// @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); + } +} - var originalHandler = handler; +// @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 + ' ', ' '))); + } +} - if (pointer && type.indexOf('touch') === 0) { - // Needs DomEvent.Pointer.js - addPointerListener(obj, type, handler, 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; + } +} - } 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 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; +} - } else if ('addEventListener' in obj) { +// @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); + } +} - if (type === 'mousewheel') { - obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false); +function _setOpacityIE(el, value) { + var filter = false, + filterName = 'DXImageTransform.Microsoft.Alpha'; - } 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); + // 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; } + } - } else { - if (type === 'click' && android) { - handler = function (e) { - filterClick(e, originalHandler); - }; - } - obj.addEventListener(type, handler, false); - } + value = Math.round(value * 100); - } else if ('attachEvent' in obj) { - obj.attachEvent('on' + type, handler); + if (filter) { + filter.Enabled = (value !== 100); + filter.Opacity = value; + } else { + el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')'; } - - obj[eventsKey] = obj[eventsKey] || {}; - obj[eventsKey][id] = handler; } -function removeOne(obj, type, fn, context) { - - var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''), - handler = obj[eventsKey] && obj[eventsKey][id]; - - if (!handler) { 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; - if (pointer && type.indexOf('touch') === 0) { - removePointerListener(obj, type, id); + for (var i = 0; i < props.length; i++) { + if (props[i] in style) { + return props[i]; + } + } + return false; +} - } else if (touch && (type === 'dblclick') && removeDoubleTapListener) { - removeDoubleTapListener(obj, id); +// @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); - } else if ('removeEventListener' in obj) { + el.style[TRANSFORM] = + (ie3d ? + 'translate(' + pos.x + 'px,' + pos.y + 'px)' : + 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') + + (scale ? ' scale(' + scale + ')' : ''); +} - if (type === 'mousewheel') { - obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false); +// @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) { - } else { - obj.removeEventListener( - type === 'mouseenter' ? 'mouseover' : - type === 'mouseleave' ? 'mouseout' : type, handler, false); - } + /*eslint-disable */ + el._leaflet_pos = point; + /* eslint-enable */ - } else if ('detachEvent' in obj) { - obj.detachEvent('on' + type, handler); + if (any3d) { + setTransform(el, point); + } else { + el.style.left = point.x + 'px'; + el.style.top = point.y + 'px'; } +} - obj[eventsKey][id] = null; +// @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 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 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. - if (e.stopPropagation) { - e.stopPropagation(); - } else if (e.originalEvent) { // In case of Leaflet event. - e.originalEvent._stopped = true; - } else { - e.cancelBubble = true; - } - skipped(e); +// @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']); - return this; + 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 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 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 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 enableImageDrag() +// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection). +function enableImageDrag() { + off(window, 'dragstart', preventDefault); } -// @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 `
` is submitted). -// Use it inside listener functions. -function preventDefault(e) { - if (e.preventDefault) { - e.preventDefault(); - } else { - e.returnValue = false; +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; } - return this; + if (!element.style) { return; } + restoreOutline(); + _outlineElement = element; + _outlineStyle = element.style.outline; + element.style.outline = 'none'; + on(window, 'keydown', restoreOutline); } -// @function stop(ev): this -// Does `stopPropagation` and `preventDefault` at the same time. -function stop(e) { - preventDefault(e); - stopPropagation(e); - return this; +// @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); } -// @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 rect = container.getBoundingClientRect(); - - 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 (typeof types === 'object') { + for (var type in types) { + removeOne(obj, type, types[type], fn); + } + } else if (types) { + types = splitWords(types); - if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) { - stop(e); - return; + 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, 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, 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); +// @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) { + + if (e.stopPropagation) { + e.stopPropagation(); + } else if (e.originalEvent) { // In case of Leaflet event. + e.originalEvent._stopped = true; } else { - setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' '))); + e.cancelBubble = true; } + skipped(e); + + return this; } -// @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; - } +// @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 getClass(el: HTMLElement): String -// Returns the element's class. -function getClass(el) { - return el.className.baseVal === undefined ? el.className : el.className.baseVal; +// @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 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 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 `` is submitted). +// Use it inside listener functions. +function preventDefault(e) { + if (e.preventDefault) { + e.preventDefault(); + } else { + e.returnValue = false; } + return this; } -function _setOpacityIE(el, value) { - var filter = false, - filterName = 'DXImageTransform.Microsoft.Alpha'; +// @function stop(ev: DOMEvent): this +// Does `stopPropagation` and `preventDefault` at the same time. +function stop(e) { + preventDefault(e); + stopPropagation(e); + return this; +} - // 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; } +// @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); } - value = Math.round(value * 100); + var scale = getScale(container), + offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y) - if (filter) { - filter.Enabled = (value !== 100); - filter.Opacity = value; - } else { - el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')'; - } + 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 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; +// 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; - for (var i = 0; i < props.length; i++) { - if (props[i] in style) { - return props[i]; - } - } - return false; +// @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 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 skipEvents = {}; - el.style[TRANSFORM] = - (ie3d ? - 'translate(' + pos.x + 'px,' + pos.y + 'px)' : - 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') + - (scale ? ' scale(' + scale + ')' : ''); +function fakeStop(e) { + // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e) + skipEvents[e.type] = true; } -// @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 */ - - if (any3d) { - setTransform(el, point); - } else { - el.style.left = point.x + 'px'; - el.style.top = point.y + 'px'; - } +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 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); -} +// check if element really left/entered the event target (for mouseenter/mouseleave) +function isExternalTarget(el, e) { -// @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. + var related = e.relatedTarget; -// @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']); + if (!related) { return true; } - 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; + 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 +3121,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 +3148,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 +3422,7 @@ var Map = Evented.extend({ } } - this._moveStart(true); + this._moveStart(true, options.noMoveStart); frame.call(this); return this; @@ -3371,10 +3460,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 +3477,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 +3506,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 +3698,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 +3724,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 +3759,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 +3862,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 +4151,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 +4160,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 +4187,7 @@ var Map = Evented.extend({ var zoomChanged = this._zoom !== zoom; this - ._moveStart(zoomChanged) + ._moveStart(zoomChanged, false) ._move(center, zoom) ._moveEnd(zoomChanged); @@ -4051,7 +4204,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 +4212,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 +4311,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 +4383,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 +4422,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 +4584,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; } @@ -4492,7 +4654,7 @@ var Map = Evented.extend({ requestAnimFrame(function () { this - ._moveStart(true) + ._moveStart(true, false) ._animateZoom(center, zoom, true); }, this); @@ -4500,6 +4662,8 @@ var Map = Evented.extend({ }, _animateZoom: function (center, zoom, startAnim, noUpdate) { + if (!this._mapPane) { return; } + if (startAnim) { this._animatingZoom = true; @@ -4511,7 +4675,7 @@ var Map = Evented.extend({ } // @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 +4689,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 +4793,8 @@ var Control = Class.extend({ corner.appendChild(container); } + this._map.on('unload', this.remove, this); + return this; }, @@ -4643,6 +4811,7 @@ var Control = Class.extend({ this.onRemove(this._map); } + this._map.off('unload', this.remove, this); this._map = null; return this; @@ -4865,13 +5034,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 +5064,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 +5092,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 +5209,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); @@ -5264,6 +5433,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 +5516,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 +5594,7 @@ var Attribution = Control.extend({ // @option prefix: String = 'Leaflet' // The HTML text shown before the attributions. Pass `false` to disable. - prefix: 'Leaflet' + prefix: 'Leaflet' }, initialize: function (options) { @@ -5586,6 +5759,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 +5876,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 +5902,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 +6002,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 +6257,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 +6339,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 +6398,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 +6499,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 +6521,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 +6783,9 @@ Map.include({ var LayerGroup = Layer.extend({ - initialize: function (layers) { + initialize: function (layers, options) { + setOptions(this, options); + this._layers = {}; var i, len; @@ -6644,10 +6840,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 +6863,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 +6894,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 +6912,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 +6986,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 +7058,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 +7080,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 +7196,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 +7212,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 +7251,7 @@ var MarkerDrag = Handler.extend({ this._draggable.on({ dragstart: this._onDragStart, + predrag: this._onPreDrag, drag: this._onDrag, dragend: this._onDragEnd }, this).enable(); @@ -7067,6 +7262,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 +7276,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 +7327,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 +7360,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 +7398,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 +7430,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 +7526,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 +7556,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 +7581,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 +7627,7 @@ var Marker = Layer.extend({ } this._initInteraction(); if (newShadow && addShadow) { - this.getPane('shadowPane').appendChild(this._shadow); + this.getPane(options.shadowPane).appendChild(this._shadow); } }, @@ -7445,7 +7711,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 +7729,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 +7850,9 @@ var Path = Layer.extend({ setOptions(this, style); if (this._renderer) { this._renderer._updateStyle(this); + if (this.options.stroke && style.hasOwnProperty('weight')) { + this._updateBounds(); + } } return this; }, @@ -7616,7 +7887,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; } }); @@ -7801,8 +8072,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 +8175,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 +8294,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 +8573,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++) { @@ -8458,10 +8736,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); } } @@ -8544,8 +8822,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 +8836,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 +8861,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 +8889,25 @@ var PointToGeoJSON = { }; // @namespace Marker -// @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 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 +8923,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 +8961,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 +9010,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 +9052,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 +9063,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 +9174,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 +9196,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 +9211,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 +9260,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 +9293,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 +9308,20 @@ 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'); } vid.onselectstart = falseFn; vid.onmousemove = falseFn; @@ -9027,10 +9330,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 +9367,49 @@ 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 [``](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element. + * + * @example + * + * ```js + * var element = '', + * elementBounds = [ [ 32, -130 ], [ 13, -100 ] ]; + * L.svgOverlay(element, elementBounds).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'); } + + 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 +9564,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 +9719,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 +9867,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 +10048,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 +10156,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 +10182,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 +10446,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 +10560,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 +10576,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 +10771,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 +10860,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 +11065,7 @@ var GridLayer = Layer.extend({ } this._removeAllTiles(); - this._tileZoom = null; + this._tileZoom = undefined; }, _retainParent: function (x, y, z, minZoom) { @@ -10916,7 +11275,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 +11330,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 +11435,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 +11524,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 © OpenStreetMap contributors, CC-BY-SA'}).addTo(map); * ``` * * @section URL template @@ -11228,8 +11588,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 +11629,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 +11646,7 @@ var TileLayer = GridLayer.extend({ // @method createTile(coords: Object, done?: Function): HTMLElement // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile) - // to return an `` HTML element with the appropiate image URL given `coords`. The `done` + // to return an `` 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 +11654,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 +11711,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 +11752,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 +11865,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 +11886,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 +11952,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) { @@ -11723,6 +12120,7 @@ var Canvas = Renderer.extend({ }, _destroyContainer: function () { + cancelAnimFrame(this._redrawRequest); delete this._ctx; remove(this._container); off(this._container); @@ -11744,8 +12142,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 +12213,7 @@ var Canvas = Renderer.extend({ delete layer._order; - delete this._layers[L.stamp(layer)]; + delete this._layers[stamp(layer)]; this._requestRedraw(layer); }, @@ -11839,14 +12235,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 +12326,6 @@ var Canvas = Renderer.extend({ if (!len) { return; } - this._drawnLayers[layer._leaflet_id] = layer; - ctx.beginPath(); for (i = 0; i < len; i++) { @@ -11949,10 +12349,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(); @@ -12058,6 +12456,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 +12472,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 +12487,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 +12503,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 +12545,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 +12646,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 +12723,7 @@ var SVG = Renderer.extend({ off(this._container); delete this._container; delete this._rootGroup; + delete this._svgSize; }, _onZoomStart: function () { @@ -12432,15 +12836,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 +12867,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 +12886,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 +12902,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 +12922,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 +13300,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 +13308,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 +13367,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 +13547,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 +13880,7 @@ var TouchZoom = Handler.extend({ } if (!this._moved) { - map._moveStart(true); + map._moveStart(true, false); this._moved = true; } @@ -13514,21 +13926,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 +13971,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 +14007,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