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