/*
- Leaflet 1.0.2, a JS library for interactive maps. http://leafletjs.com
+ Leaflet 1.0.3, a JS library for interactive maps. http://leafletjs.com
(c) 2010-2016 Vladimir Agafonkin, (c) 2010-2011 CloudMade
*/
(function (window, document, undefined) {
var L = {
- version: "1.0.2"
+ version: "1.0.3"
};
function expose() {
}
listeners.push(newListener);
- typeListeners.count++;
},
_off: function (type, fn, context) {
// @property touch: Boolean
// `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
+ // This does not necessarily mean that the browser is running in a computer with
+ // a touchscreen, it only means that the browser is capable of understanding
+ // touch events.
touch: !!touch,
// @property msPointer: Boolean
},
// @method toBounds(sizeInMeters: Number): LatLngBounds
- // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters` meters apart from the `LatLng`.
+ // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
toBounds: function (sizeInMeters) {
var latAccuracy = 180 * sizeInMeters / 40075017,
lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
// @method contains (latlng: LatLng): Boolean
// Returns `true` if the rectangle contains the given point.
contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
- if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
+ if (typeof obj[0] === 'number' || obj instanceof L.LatLng || 'lat' in obj) {
obj = L.latLng(obj);
} else {
obj = L.latLngBounds(obj);
// @method wrapLatLng(latlng: LatLng): LatLng
// Returns a `LatLng` where lat and lng has been wrapped according to the
// CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
+ // Only accepts actual `L.LatLng` instances, not arrays.
wrapLatLng: function (latlng) {
var lng = this.wrapLng ? L.Util.wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
lat = this.wrapLat ? L.Util.wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
alt = latlng.alt;
return L.latLng(lat, lng, alt);
+ },
+
+ // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
+ // Returns a `LatLngBounds` with the same size as the given one, ensuring
+ // that its center is within the CRS's bounds.
+ // Only accepts actual `L.LatLngBounds` instances, not arrays.
+ wrapLatLngBounds: function (bounds) {
+ var center = bounds.getCenter(),
+ newCenter = this.wrapLatLng(center),
+ latShift = center.lat - newCenter.lat,
+ lngShift = center.lng - newCenter.lng;
+
+ if (latShift === 0 && lngShift === 0) {
+ return bounds;
+ }
+
+ var sw = bounds.getSouthWest(),
+ ne = bounds.getNorthEast(),
+ newSw = L.latLng({lat: sw.lat - latShift, lng: sw.lng - lngShift}),
+ newNe = L.latLng({lat: ne.lat - latShift, lng: ne.lng - lngShift});
+
+ return new L.LatLngBounds(newSw, newNe);
}
};
// @option maxBounds: LatLngBounds = null
// When this option is set, the map restricts the view to the given
- // geographical bounds, bouncing the user back when he tries to pan
+ // geographical bounds, bouncing the user back if the user tries to pan
// outside the view. To set the restriction dynamically, use
// [`setMaxBounds`](#map-setmaxbounds) method.
maxBounds: undefined,
};
},
- // @method fitBounds(bounds: LatLngBounds, options: fitBounds options): this
+ // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
// Sets a map view that contains the given geographical bounds with the
// maximum zoom level possible.
fitBounds: function (bounds, options) {
nw = bounds.getNorthWest(),
se = bounds.getSouthEast(),
size = this.getSize().subtract(padding),
- boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)),
+ boundsSize = L.bounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
snap = L.Browser.any3d ? this.options.zoomSnap : 1;
var scale = Math.min(size.x / boundsSize.x, size.y / boundsSize.y);
getSize: function () {
if (!this._size || this._sizeChanged) {
this._size = new L.Point(
- this._container.clientWidth,
- this._container.clientHeight);
+ this._container.clientWidth || 0,
+ this._container.clientHeight || 0);
this._sizeChanged = false;
}
return this.options.crs.wrapLatLng(L.latLng(latlng));
},
+ // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
+ // Returns a `LatLngBounds` with the same size as the given one, ensuring that
+ // its center is within the CRS's bounds.
+ // By default this means the center longitude is wrapped around the dateline so its
+ // value is between -180 and +180 degrees, and the majority of the bounds
+ // overlaps the CRS's bounds.
+ wrapLatLngBounds: function (latlng) {
+ return this.options.crs.wrapLatLngBounds(L.latLngBounds(latlng));
+ },
+
// @method distance(latlng1: LatLng, latlng2: LatLng): Number
// Returns the distance between two geographical coordinates according to
// the map's CRS. By default this measures distance in meters.
return L.point(point).add(this._getMapPanePos());
},
- // @method containerPointToLatLng(point: Point): Point
+ // @method containerPointToLatLng(point: Point): LatLng
// Given a pixel coordinate relative to the map container, returns
// the corresponding geographical coordinate (for the current zoom level).
containerPointToLatLng: function (point) {
// @option attribution: String = null
// String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox".
- attribution: null,
+ attribution: null
},
/* @section
this.onAdd(map);
- if (this.getAttribution && this._map.attributionControl) {
- this._map.attributionControl.addAttribution(this.getAttribution());
+ if (this.getAttribution && map.attributionControl) {
+ map.attributionControl.addAttribution(this.getAttribution());
}
this.fire('add');
if (L.Browser.pointer && type.indexOf('touch') === 0) {
this.addPointerListener(obj, type, handler, id);
- } else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
+ } else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener &&
+ !(L.Browser.pointer && L.Browser.chrome)) {
+ // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
+ // See #5180
this.addDoubleTapListener(obj, handler, id);
} else if ('addEventListener' in obj) {
// @option noWrap: Boolean = false
// Whether the layer is wrapped around the antimeridian. If `true`, the
// GridLayer will only be displayed once at low zoom levels. Has no
- // effect when the [map CRS](#map-crs) doesn't wrap around.
+ // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
+ // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
+ // tiles outside the CRS limits.
noWrap: false,
// @option pane: String = 'tilePane'
sePoint = nwPoint.add(tileSize),
nw = map.unproject(nwPoint, coords.z),
- se = map.unproject(sePoint, coords.z);
+ se = map.unproject(sePoint, coords.z),
+ bounds = new L.LatLngBounds(nw, se);
if (!this.options.noWrap) {
- nw = map.wrapLatLng(nw);
- se = map.wrapLatLng(se);
+ map.wrapLatLngBounds(bounds);
}
- return new L.LatLngBounds(nw, se);
+ return bounds;
},
// converts tile coordinates to key for the tile cache
* @example
*
* ```js
- * L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map);
+ * L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map);
* ```
*
* @section URL template
_tileOnError: function (done, tile, e) {
var errorUrl = this.options.errorTileUrl;
- if (errorUrl) {
+ if (errorUrl && tile.src !== errorUrl) {
tile.src = errorUrl;
}
done(e, tile);
return this;
},
+ // @method setBounds(bounds: LatLngBounds): this
+ // Update the bounds that this ImageOverlay covers
setBounds: function (bounds) {
this._bounds = bounds;
return events;
},
+ // @method getBounds(): LatLngBounds
+ // Get the bounds that this ImageOverlay covers
getBounds: function () {
return this._bounds;
},
+ // @method getElement(): HTMLElement
+ // Get the img element that represents the ImageOverlay on the map
getElement: function () {
return this._image;
},
if (newShadow) {
L.DomUtil.addClass(newShadow, classToAdd);
+ newShadow.alt = '';
}
this._shadow = newShadow;
// @method isPopupOpen(): boolean
// Returns `true` if the popup bound to this layer is currently open.
isPopupOpen: function () {
- return this._popup.isOpen();
+ return (this._popup ? this._popup.isOpen() : false);
},
// @method setPopupContent(content: String|HTMLElement|Popup): this
* ```js
* // create a red polyline from an array of LatLng points
* var latlngs = [
- * [-122.68, 45.51],
- * [-122.43, 37.77],
- * [-118.2, 34.04]
+ * [45.51, -122.68],
+ * [37.77, -122.43],
+ * [34.04, -118.2]
* ];
*
* var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
* ```js
* // create a red polyline from an array of arrays of LatLng points
* var latlngs = [
- * [[-122.68, 45.51],
- * [-122.43, 37.77],
- * [-118.2, 34.04]],
- * [[-73.91, 40.78],
- * [-87.62, 41.83],
- * [-96.72, 32.76]]
+ * [[45.51, -122.68],
+ * [37.77, -122.43],
+ * [34.04, -118.2]],
+ * [[40.78, -73.91],
+ * [41.83, -87.62],
+ * [32.76, -96.72]]
* ];
* ```
*/
*
* ```js
* // create a red polygon from an array of LatLng points
- * var latlngs = [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]];
+ * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
*
* var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
*
*
* ```js
* var latlngs = [
- * [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]], // outer ring
- * [[-108.58,37.29],[-108.58,40.71],[-102.50,40.71],[-102.50,37.29]] // hole
+ * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
+ * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
* ];
* ```
*
* ```js
* var latlngs = [
* [ // first polygon
- * [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]], // outer ring
- * [[-108.58,37.29],[-108.58,40.71],[-102.50,40.71],[-102.50,37.29]] // hole
+ * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
+ * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
* ],
* [ // second polygon
- * [[-109.05, 37],[-109.03, 41],[-102.05, 41],[-102.04, 37],[-109.05, 38]]
+ * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
* ]
* ];
* ```
container.appendChild(layer._path);
this._updateStyle(layer);
+ this._layers[L.stamp(layer)] = layer;
},
_addPath: function (layer) {
var container = layer._container;
L.DomUtil.remove(container);
layer.removeInteractiveTarget(container);
+ delete this._layers[L.stamp(layer)];
},
_updateStyle: function (layer) {
*/
L.Canvas = L.Renderer.extend({
+ getEvents: function () {
+ var events = L.Renderer.prototype.getEvents.call(this);
+ events.viewprereset = this._onViewPreReset;
+ return events;
+ },
+
+ _onViewPreReset: function () {
+ // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
+ this._postponeUpdatePaths = true;
+ },
onAdd: function () {
L.Renderer.prototype.onAdd.call(this);
},
_updatePaths: function () {
+ if (this._postponeUpdatePaths) { return; }
+
var layer;
this._redrawBounds = null;
for (var id in this._layers) {
this.fire('update');
},
+ _reset: function () {
+ L.Renderer.prototype._reset.call(this);
+
+ if (this._postponeUpdatePaths) {
+ this._postponeUpdatePaths = false;
+ this._updatePaths();
+ }
+ },
+
_initPath: function (layer) {
this._updateDashArray(layer);
this._layers[L.stamp(layer)] = layer;
_redraw: function () {
this._redrawRequest = null;
+ if (this._redrawBounds) {
+ this._redrawBounds.min._floor();
+ this._redrawBounds.max._ceil();
+ }
+
this._clear(); // clear layers in redraw bounds
this._draw(); // draw layers
var count;
if (L.Browser.pointer) {
+ if ((!L.Browser.edge) || e.pointerType === 'mouse') { return; }
count = L.DomEvent._pointersCount;
} else {
count = e.touches.length;
last = now;
}
- function onTouchEnd() {
+ function onTouchEnd(e) {
if (doubleTap && !touch.cancelBubble) {
if (L.Browser.pointer) {
+ if ((!L.Browser.edge) || e.pointerType === 'mouse') { return; }
+
// work around .type being readonly with MSPointer* events
var newTouch = {},
prop, i;
obj.addEventListener(touchstart, onTouchStart, false);
obj.addEventListener(touchend, onTouchEnd, false);
- // On some platforms (notably, chrome on win10 + touchscreen + mouse),
+ // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
// the browser doesn't fire touchend/pointerup events but does fire
// native dblclicks. See #4127.
- if (!L.Browser.edge) {
- obj.addEventListener('dblclick', handler, false);
- }
+ // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
+ obj.addEventListener('dblclick', handler, false);
return this;
},
_addPointerStart: function (obj, handler, id) {
var onDown = L.bind(function (e) {
- if (e.pointerType !== '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.
_initLayout: function () {
var className = 'leaflet-control-layers',
- container = this._container = L.DomUtil.create('div', className);
+ container = this._container = L.DomUtil.create('div', className),
+ collapsed = this.options.collapsed;
// makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
container.setAttribute('aria-haspopup', true);
var form = this._form = L.DomUtil.create('form', className + '-list');
- if (!L.Browser.android) {
- L.DomEvent.on(container, {
- mouseenter: this.expand,
- mouseleave: this.collapse
- }, this);
+ if (collapsed) {
+ this._map.on('click', this.collapse, this);
+
+ if (!L.Browser.android) {
+ L.DomEvent.on(container, {
+ mouseenter: this.expand,
+ mouseleave: this.collapse
+ }, this);
+ }
}
var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
setTimeout(L.bind(this._onInputClick, this), 0);
}, this);
- this._map.on('click', this.collapse, this);
// TODO keyboard accessibility
- if (!this.options.collapsed) {
+ if (!collapsed) {
this.expand();
}