UNPKG

esri-leaflet

Version:

Leaflet plugins for consuming ArcGIS Online and ArcGIS Server services.

399 lines (334 loc) 10.4 kB
import { ImageOverlay, CRS, DomUtil, Util, Layer, popup, latLng, bounds, } from "leaflet"; import { cors } from "../Support.js"; import { setEsriAttribution, removeEsriAttribution } from "../Util.js"; const Overlay = ImageOverlay.extend({ onAdd(map) { this._topLeft = map.getPixelBounds().min; ImageOverlay.prototype.onAdd.call(this, map); }, _reset() { if (this._map.options.crs === CRS.EPSG3857) { ImageOverlay.prototype._reset.call(this); } else { DomUtil.setPosition( this._image, this._topLeft.subtract(this._map.getPixelOrigin()), ); } }, }); export const RasterLayer = Layer.extend({ options: { opacity: 1, position: "front", f: "image", useCors: cors, attribution: null, interactive: false, alt: "", }, onAdd(map) { // include 'Powered by Esri' in map attribution setEsriAttribution(map); if (this.options.zIndex) { this.options.position = null; } this._update = Util.throttle( this._update, this.options.updateInterval, this, ); map.on("moveend", this._update, this); // if we had an image loaded and it matches the // current bounds show the image otherwise remove it if ( this._currentImage && this._currentImage._bounds.equals(this._map.getBounds()) ) { map.addLayer(this._currentImage); } else if (this._currentImage) { this._map.removeLayer(this._currentImage); this._currentImage = null; } this._update(); if (this._popup) { this._map.on("click", this._getPopupData, this); this._map.on("dblclick", this._resetPopupState, this); } // add copyright text listed in service metadata this.metadata(function (err, metadata) { if ( !err && !this.options.attribution && map.attributionControl && metadata.copyrightText ) { this.options.attribution = metadata.copyrightText; map.attributionControl.addAttribution(this.getAttribution()); } }, this); }, onRemove(map) { removeEsriAttribution(map); if (this._currentImage) { this._map.removeLayer(this._currentImage); } if (this._popup) { this._map.off("click", this._getPopupData, this); this._map.off("dblclick", this._resetPopupState, this); } this._map.off("moveend", this._update, this); }, bindPopup(fn, popupOptions) { this._shouldRenderPopup = false; this._lastClick = false; this._popup = popup(popupOptions); this._popupFunction = fn; if (this._map) { this._map.on("click", this._getPopupData, this); this._map.on("dblclick", this._resetPopupState, this); } return this; }, unbindPopup() { if (this._map) { this._map.closePopup(this._popup); this._map.off("click", this._getPopupData, this); this._map.off("dblclick", this._resetPopupState, this); } this._popup = false; return this; }, bringToFront() { this.options.position = "front"; if (this._currentImage) { this._currentImage.bringToFront(); this._setAutoZIndex(Math.max); } return this; }, bringToBack() { this.options.position = "back"; if (this._currentImage) { this._currentImage.bringToBack(); this._setAutoZIndex(Math.min); } return this; }, setZIndex(value) { this.options.zIndex = value; if (this._currentImage) { this._currentImage.setZIndex(value); } return this; }, _setAutoZIndex(compare) { // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back) if (!this._currentImage) { return; } const layers = this._currentImage.getPane().children; let edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min for (let i = 0, len = layers.length, zIndex; i < len; i++) { zIndex = layers[i].style.zIndex; if (layers[i] !== this._currentImage._image && zIndex) { edgeZIndex = compare(edgeZIndex, +zIndex); } } if (isFinite(edgeZIndex)) { this.options.zIndex = edgeZIndex + compare(-1, 1); this.setZIndex(this.options.zIndex); } }, getAttribution() { return this.options.attribution; }, getOpacity() { return this.options.opacity; }, setOpacity(opacity) { this.options.opacity = opacity; if (this._currentImage) { this._currentImage.setOpacity(opacity); } return this; }, getTimeRange() { return [this.options.from, this.options.to]; }, setTimeRange(from, to) { this.options.from = from; this.options.to = to; this._update(); return this; }, metadata(callback, context) { this.service.metadata(callback, context); return this; }, authenticate(token) { this.service.authenticate(token); return this; }, redraw() { this._update(); }, _renderImage(url, bounds, contentType) { if (this._map) { // if no output directory has been specified for a service, MIME data will be returned if (contentType) { url = `data:${contentType};base64,${url}`; } // if server returns an inappropriate response, abort. if (!url) { return; } // create a new image overlay and add it to the map // to start loading the image // opacity is 0 while the image is loading const image = new Overlay(url, bounds, { opacity: 0, crossOrigin: this.options.withCredentials ? "use-credentials" : this.options.useCors, alt: this.options.alt, pane: this.options.pane || this.getPane(), interactive: this.options.interactive, }).addTo(this._map); // eslint-disable-next-line prefer-const let onOverlayLoad; const onOverlayError = function () { this._map.removeLayer(image); this.fire("error"); image.off("load", onOverlayLoad, this); }; onOverlayLoad = function (e) { image.off("error", onOverlayError, this); if (this._map) { const newImage = e.target; const oldImage = this._currentImage; // if the bounds of this image matches the bounds that // _renderImage was called with and we have a map with the same bounds // hide the old image if there is one and set the opacity // of the new image otherwise remove the new image if ( newImage._bounds.equals(bounds) && newImage._bounds.equals(this._map.getBounds()) ) { this._currentImage = newImage; if (this.options.position === "front") { this.bringToFront(); } else if (this.options.position === "back") { this.bringToBack(); } if (this.options.zIndex) { this.setZIndex(this.options.zIndex); } if (this._map && this._currentImage._map) { this._currentImage.setOpacity(this.options.opacity); } else { this._currentImage._map.removeLayer(this._currentImage); } if (oldImage && this._map) { this._map.removeLayer(oldImage); } if (oldImage && oldImage._map) { oldImage._map.removeLayer(oldImage); } } else { this._map.removeLayer(newImage); } } this.fire("load", { bounds, }); }; // If loading the image fails image.once("error", onOverlayError, this); // once the image loads image.once("load", onOverlayLoad, this); } }, _update() { if (!this._map) { return; } const zoom = this._map.getZoom(); const bounds = this._map.getBounds(); if (this._animatingZoom) { return; } if (this._map._panTransition && this._map._panTransition._inProgress) { return; } if (zoom > this.options.maxZoom || zoom < this.options.minZoom) { if (this._currentImage) { this._currentImage._map.removeLayer(this._currentImage); this._currentImage = null; } return; } const params = this._buildExportParams(); Util.extend(params, this.options.requestParams); if (params) { this._requestExport(params, bounds); this.fire("loading", { bounds, }); } else if (this._currentImage) { this._currentImage._map.removeLayer(this._currentImage); this._currentImage = null; } }, _renderPopup(latlng, error, results, response) { latlng = latLng(latlng); if (this._shouldRenderPopup && this._lastClick.equals(latlng)) { // add the popup to the map where the mouse was clicked at const content = this._popupFunction(error, results, response); if (content) { this._popup.setLatLng(latlng).setContent(content).openOn(this._map); } } }, _resetPopupState(e) { this._shouldRenderPopup = false; this._lastClick = e.latlng; }, _calculateBbox() { const pixelBounds = this._map.getPixelBounds(); const sw = this._map.unproject(pixelBounds.getBottomLeft()); const ne = this._map.unproject(pixelBounds.getTopRight()); const neProjected = this._map.options.crs.project(ne); const swProjected = this._map.options.crs.project(sw); // this ensures ne/sw are switched in polar maps where north/top bottom/south is inverted const boundsProjected = bounds(neProjected, swProjected); return [ boundsProjected.getBottomLeft().x, boundsProjected.getBottomLeft().y, boundsProjected.getTopRight().x, boundsProjected.getTopRight().y, ].join(","); }, _calculateImageSize() { // ensure that we don't ask ArcGIS Server for a taller image than we have actual map displaying within the div const bounds = this._map.getPixelBounds(); const size = this._map.getSize(); const sw = this._map.unproject(bounds.getBottomLeft()); const ne = this._map.unproject(bounds.getTopRight()); const top = this._map.latLngToLayerPoint(ne).y; const bottom = this._map.latLngToLayerPoint(sw).y; if (top > 0 || bottom < size.y) { size.y = bottom - top; } return `${size.x},${size.y}`; }, });