UNPKG

terriajs

Version:

Geospatial data visualization platform.

585 lines 24.2 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; import i18next from "i18next"; import uniqWith from "lodash-es/uniqWith"; import { computed, makeObservable, override, runInAction } from "mobx"; import { fromPromise } from "mobx-utils"; import moment from "moment"; import WebMercatorTilingScheme from "terriajs-cesium/Source/Core/WebMercatorTilingScheme"; import ArcGisMapServerImageryProvider from "terriajs-cesium/Source/Scene/ArcGisMapServerImageryProvider"; import URI from "urijs"; import TerriaError, { networkRequestError } from "../../../Core/TerriaError"; import createDiscreteTimesFromIsoSegments from "../../../Core/createDiscreteTimes"; import createTransformerAllowUndefined from "../../../Core/createTransformerAllowUndefined"; import filterOutUndefined from "../../../Core/filterOutUndefined"; import isDefined from "../../../Core/isDefined"; import loadJson from "../../../Core/loadJson"; import replaceUnderscores from "../../../Core/replaceUnderscores"; import { scaleDenominatorToLevel } from "../../../Core/scaleToDenominator"; import { setsAreEqual } from "../../../Core/setsAreEqual"; import Proj4Definitions from "../../../Map/Vector/Proj4Definitions"; import Reproject from "../../../Map/Vector/Reproject"; import CatalogMemberMixin from "../../../ModelMixins/CatalogMemberMixin"; import DiscretelyTimeVaryingMixin from "../../../ModelMixins/DiscretelyTimeVaryingMixin"; import MappableMixin from "../../../ModelMixins/MappableMixin"; import UrlMixin from "../../../ModelMixins/UrlMixin"; import ArcGisMapServerCatalogItemTraits from "../../../Traits/TraitsClasses/ArcGisMapServerCatalogItemTraits"; import { InfoSectionTraits } from "../../../Traits/TraitsClasses/CatalogMemberTraits"; import LegendTraits, { LegendItemTraits } from "../../../Traits/TraitsClasses/LegendTraits"; import { RectangleTraits } from "../../../Traits/TraitsClasses/MappableTraits"; import CreateModel from "../../Definition/CreateModel"; import LoadableStratum from "../../Definition/LoadableStratum"; import StratumOrder from "../../Definition/StratumOrder"; import createStratumInstance from "../../Definition/createStratumInstance"; import getToken from "../../getToken"; import proxyCatalogItemUrl from "../proxyCatalogItemUrl"; import MinMaxLevelMixin from "./../../../ModelMixins/MinMaxLevelMixin"; import proj4 from "proj4"; class MapServerStratum extends LoadableStratum(ArcGisMapServerCatalogItemTraits) { _item; mapServer; allLayers; _legends; _token; static stratumName = "mapServer"; constructor(_item, mapServer, allLayers, _legends, _token) { super(); this._item = _item; this.mapServer = mapServer; this.allLayers = allLayers; this._legends = _legends; this._token = _token; makeObservable(this); } duplicateLoadableStratum(newModel) { return new MapServerStratum(newModel, this.mapServer, this.allLayers, this._legends, this._token); } static async load(item) { if (!isDefined(item.uri) || !isDefined(item.url)) { throw new TerriaError({ title: i18next.t("models.arcGisMapServerCatalogItem.invalidUrlTitle"), message: i18next.t("models.arcGisMapServerCatalogItem.invalidUrlMessage") }); } let token; if (isDefined(item.tokenUrl)) { token = await getToken(item.terria, item.tokenUrl, item.url); } else if (isDefined(item.token)) { token = item.token; } let serviceUri = getBaseURI(item); let layersUri = getBaseURI(item).segment("layers"); let legendUri = getBaseURI(item).segment("legend"); if (isDefined(token)) { serviceUri = serviceUri.addQuery("token", token); layersUri = layersUri.addQuery("token", token); legendUri = legendUri.addQuery("token", token); } // TODO: if tokenUrl, fetch and pass token as parameter const serviceMetadata = await getJson(item, serviceUri); if (!isDefined(serviceMetadata)) { throw networkRequestError({ title: i18next.t("models.arcGisService.invalidServerTitle"), message: i18next.t("models.arcGisService.invalidServerMessage") }); } const legendMetadata = await getJson(item, legendUri); let layers = []; // If this MapServer is a single fused map cache - we can't request individual layers // If it is not - we request layer metadata if (!(serviceMetadata.singleFusedMapCache && serviceMetadata.capabilities?.includes("TilesOnly"))) { const layersMetadataResponse = await getJson(item, layersUri); // Use the slightly more basic layer metadata if (isDefined(serviceMetadata.layers)) { layers = serviceMetadata.layers; } if (isDefined(layersMetadataResponse?.layers)) { layers = layersMetadataResponse.layers; } if (!isDefined(layers) || layers.length === 0) { throw networkRequestError({ title: i18next.t("models.arcGisMapServerCatalogItem.noLayersFoundTitle"), message: i18next.t("models.arcGisMapServerCatalogItem.noLayersFoundMessage", item) }); } } const stratum = new MapServerStratum(item, serviceMetadata, layers, legendMetadata, token); // Add any Proj4 definitions if necessary const epsgCode = serviceMetadata.fullExtent.spatialReference?.latestWkid ?? serviceMetadata.fullExtent.spatialReference?.wkid; if (epsgCode && item.terria.configParameters.proj4ServiceBaseUrl) { await Reproject.checkProjection(item.terria.configParameters.proj4ServiceBaseUrl, `EPSG:${epsgCode}`); } return stratum; } get token() { return this._token; } get maximumScale() { if (this._item.layersArray.length === 0) { return this.mapServer.maxScale; } return Math.min(...filterOutUndefined(this._item.layersArray.map(({ maxScale }) => maxScale))); } get layers() { /** Try to pull out MapServer layer from URL * eg https://exmaple.com/arcgis/rest/services/MapServer/{layer} */ if (isDefined(this._item.uri)) { const lastSegment = this._item.uri.segment(-1); if (isDefined(lastSegment) && lastSegment.match(/\d+/)) { return lastSegment; } } } get name() { // single layer if (this._item.layersArray.length === 1 && this._item.layersArray[0].name && this._item.layersArray[0].name.length > 0) { return replaceUnderscores(this._item.layersArray[0].name); } // group of layers (or single fused map cache) else if (this.mapServer.documentInfo && this.mapServer.documentInfo.Title && this.mapServer.documentInfo.Title.length > 0) { return replaceUnderscores(this.mapServer.documentInfo.Title); } else if (this.mapServer.mapName && this.mapServer.mapName.length > 0) { return replaceUnderscores(this.mapServer.mapName); } } get dataCustodian() { if (this.mapServer.documentInfo && this.mapServer.documentInfo.Author && this.mapServer.documentInfo.Author.length > 0) { return this.mapServer.documentInfo.Author; } } get rectangle() { const rectangle = { west: Infinity, south: Infinity, east: -Infinity, north: -Infinity }; // If we only have the summary layer info if (this._item.layersArray.length === 0 || !("extent" in this._item.layersArray[0])) { getRectangleFromLayer(this.mapServer.fullExtent, rectangle); } else { getRectangleFromLayers(rectangle, this._item.layersArray); } if (rectangle.west === Infinity) return; return createStratumInstance(RectangleTraits, rectangle); } get info() { // If we are requesting a single layer, use it to populate InfoSections // If we are requesting multiple layers - we only show MapServer metadata (not metadata per layer) const singleLayer = this._item.layersArray.length === 1 ? this._item.layersArray[0] : undefined; return filterOutUndefined([ singleLayer ? createStratumInstance(InfoSectionTraits, { name: i18next.t("models.arcGisMapServerCatalogItem.dataDescription"), content: singleLayer.description }) : undefined, createStratumInstance(InfoSectionTraits, { name: i18next.t("models.arcGisMapServerCatalogItem.serviceDescription"), content: this.mapServer.description }), createStratumInstance(InfoSectionTraits, { name: i18next.t("models.arcGisMapServerCatalogItem.copyrightText"), content: singleLayer?.copyrightText ?? this.mapServer.copyrightText }) ]); } get legends() { const layers = this._item.layersArray; const noDataRegex = /^No[\s_-]?Data$/i; const labelsRegex = /_Labels$/; let items = []; (this._legends?.layers || []).forEach((l) => { if (noDataRegex.test(l.layerName) || labelsRegex.test(l.layerName)) { return; } if (layers.length > 0 && !layers.find((layer) => layer.id === l.layerId) && !layers.find((layer) => layer.name === l.layerName)) { // layer not selected return; } l.legend?.forEach((leg) => { const title = replaceUnderscores(leg.label !== "" ? leg.label : l.layerName); const dataUrl = "data:" + leg.contentType + ";base64," + leg.imageData; items.push(createStratumInstance(LegendItemTraits, { title, imageUrl: dataUrl, imageWidth: leg.width, imageHeight: leg.height })); }); }); items = uniqWith(items, (a, b) => a.imageUrl === b.imageUrl); return [createStratumInstance(LegendTraits, { items })]; } /** Only used "pre-cached" tiles if we aren't requesting any specific layers * If the `layersArray` property is specified, we request individual dynamic layers and ignore the fused map cache. */ get usePreCachedTilesIfAvailable() { // Checking tileInfo in MapServer metadata should be handled by cesium - but currently there is a bug in ArcGisMapServerImageryProvider if (!this.mapServer.tileInfo) return false; if (this._item.parameters) return false; return (this._item.layersArray.length === 0 || !this._item.layers || setsAreEqual(this._item.layersArray.map((l) => l.id), this.allLayers.map((l) => l.id))); } } __decorate([ computed ], MapServerStratum.prototype, "maximumScale", null); __decorate([ computed ], MapServerStratum.prototype, "layers", null); __decorate([ computed ], MapServerStratum.prototype, "name", null); __decorate([ computed ], MapServerStratum.prototype, "dataCustodian", null); __decorate([ computed ], MapServerStratum.prototype, "rectangle", null); __decorate([ computed ], MapServerStratum.prototype, "info", null); __decorate([ computed ], MapServerStratum.prototype, "legends", null); __decorate([ computed ], MapServerStratum.prototype, "usePreCachedTilesIfAvailable", null); StratumOrder.addLoadStratum(MapServerStratum.stratumName); export default class ArcGisMapServerCatalogItem extends UrlMixin(DiscretelyTimeVaryingMixin(MinMaxLevelMixin(CatalogMemberMixin(MappableMixin(CreateModel(ArcGisMapServerCatalogItemTraits)))))) { static type = "esri-mapServer"; constructor(...args) { super(...args); makeObservable(this); } get typeName() { return i18next.t("models.arcGisMapServerCatalogItem.name"); } get type() { return ArcGisMapServerCatalogItem.type; } async forceLoadMetadata() { const stratum = await MapServerStratum.load(this); runInAction(() => { this.strata.set(MapServerStratum.stratumName, stratum); }); } forceLoadMapItems() { return Promise.all([ this._currentImageryPromise, this._nextImageryPromise ]).then(() => { }); } get cacheDuration() { if (isDefined(super.cacheDuration)) { return super.cacheDuration; } return "1d"; } get discreteTimes() { const mapServerStratum = this.strata.get(MapServerStratum.stratumName); if (mapServerStratum?.mapServer.timeInfo === undefined) return undefined; // Add union type - as `time` is always defined const result = []; createDiscreteTimesFromIsoSegments(result, new Date(mapServerStratum.mapServer.timeInfo.timeExtent[0]).toISOString(), new Date(mapServerStratum.mapServer.timeInfo.timeExtent[1]).toISOString(), undefined, this.maxRefreshIntervals); return result; } getCurrentTime() { const dateAsUnix = this.currentDiscreteTimeTag === undefined ? undefined : new Date(this.currentDiscreteTimeTag).getTime(); return dateAsUnix; } get timeParams() { const currentTime = this.getCurrentTime(); const timeWindowDuration = this.timeWindowDuration; const timeWindowUnit = this.timeWindowUnit; const isForwardTimeWindow = this.isForwardTimeWindow; const timeParams = { currentTime, timeWindowDuration, timeWindowUnit, isForwardTimeWindow }; return timeParams; } get _currentImageryPromise() { const timeParams = this.timeParams; return this._createImageryProvider(timeParams); } get _currentImageryParts() { const imageryProviderObservablePromise = this._currentImageryPromise; // Return an ImageryPart when the the promise is fulfilled with a valid imageryProvider const imageryPart = imageryProviderObservablePromise.value instanceof ArcGisMapServerImageryProvider ? { imageryProvider: imageryProviderObservablePromise.value, alpha: this.opacity, show: this.show, clippingRectangle: this.clipToRectangle ? this.cesiumRectangle : undefined } : undefined; return imageryPart; } get _nextImageryPromise() { if (this.terria.timelineStack.contains(this) && !this.isPaused && this.nextDiscreteTimeTag) { const timeParams = this.timeParams; return this._createImageryProvider(timeParams); } else { return undefined; } } get _nextImageryParts() { const imageryProviderObservablePromise = this._nextImageryPromise; if (isDefined(imageryProviderObservablePromise)) { imageryProviderObservablePromise.case({ fulfilled: (imageryProvider) => { // Disable feature picking for the next imagery layer if (imageryProvider instanceof ArcGisMapServerImageryProvider) imageryProvider.enablePickFeatures = false; } }); // Return an ImageryPart when the the promise is fulfilled with a valid imageryProvider const imageryPart = imageryProviderObservablePromise.value instanceof ArcGisMapServerImageryProvider ? { imageryProvider: imageryProviderObservablePromise.value, alpha: 0.0, show: true, clippingRectangle: this.clipToRectangle ? this.cesiumRectangle : undefined } : undefined; return imageryPart; } else { return undefined; } } windowDurationInMs(rawTimeWindowDuration, timeWindowUnit) { if (rawTimeWindowDuration === undefined || rawTimeWindowDuration === 0 || timeWindowUnit === undefined) { return undefined; } const rawTimeWindowData = {}; rawTimeWindowData[timeWindowUnit] = rawTimeWindowDuration; const duration = moment.duration(rawTimeWindowData).asMilliseconds(); if (duration === 0) { return undefined; } else { return duration; } } getTimeWindowQueryString(currentTime, duration, isForward = true) { if (isForward) { const toTime = Number(currentTime) + duration; return currentTime + "," + toTime; } else { const fromTime = Number(currentTime) - duration; return "" + fromTime + "," + currentTime; } } _createImageryProvider = createTransformerAllowUndefined((timeParams) => { const stratum = this.strata.get(MapServerStratum.stratumName); if (!isDefined(this.url) || !isDefined(stratum)) { return fromPromise(Promise.resolve(undefined)); } const params = Object.assign({}, this.parameters); const currentTime = timeParams?.currentTime; if (currentTime !== undefined) { const windowDuration = this.windowDurationInMs(timeParams?.timeWindowDuration, timeParams?.timeWindowUnit); if (windowDuration !== undefined) { params.time = this.getTimeWindowQueryString(currentTime, windowDuration, timeParams?.isForwardTimeWindow); } else { params.time = currentTime; } } const maximumLevel = scaleDenominatorToLevel(this.maximumScale, true, false); const imageryProviderPromise = ArcGisMapServerImageryProvider.fromUrl(cleanAndProxyUrl(this, getBaseURI(this).toString()), { layers: this.layersArray.map((l) => l.id).join(","), tilingScheme: new WebMercatorTilingScheme(), maximumLevel: maximumLevel, tileHeight: this.tileHeight, tileWidth: this.tileWidth, parameters: params, enablePickFeatures: this.allowFeaturePicking, usePreCachedTilesIfAvailable: this.usePreCachedTilesIfAvailable, mapServerData: stratum.mapServer, token: stratum.token, credit: this.attribution }); return fromPromise(this.updateRequestImageAsync(imageryProviderPromise, false)); }); get mapItems() { const result = []; const current = this._currentImageryParts; if (current) { result.push(current); } const next = this._nextImageryParts; if (next) { result.push(next); } return result; } /** Return array of MapServer layers from `layers` trait (which is CSV of layer IDs) - this will only return **valid** MapServer layers.*/ get layersArray() { const stratum = this.strata.get(MapServerStratum.stratumName); if (!stratum) return []; return filterOutUndefined(findLayers(stratum.allLayers, this.layers)); } } __decorate([ override ], ArcGisMapServerCatalogItem.prototype, "cacheDuration", null); __decorate([ computed ], ArcGisMapServerCatalogItem.prototype, "discreteTimes", null); __decorate([ computed ], ArcGisMapServerCatalogItem.prototype, "timeParams", null); __decorate([ computed ], ArcGisMapServerCatalogItem.prototype, "_currentImageryPromise", null); __decorate([ computed ], ArcGisMapServerCatalogItem.prototype, "_nextImageryPromise", null); __decorate([ computed ], ArcGisMapServerCatalogItem.prototype, "_nextImageryParts", null); __decorate([ computed ], ArcGisMapServerCatalogItem.prototype, "mapItems", null); __decorate([ computed ], ArcGisMapServerCatalogItem.prototype, "layersArray", null); function getBaseURI(item) { const uri = new URI(item.url); const lastSegment = uri.segment(-1); if (lastSegment && lastSegment.match(/\d+/)) { uri.segment(-1, ""); } return uri; } async function getJson(item, uri) { try { const response = await loadJson(proxyCatalogItemUrl(item, uri.addQuery("f", "json").toString())); return response; } catch (err) { console.log(err); return undefined; } } /* Given a comma-separated string of layer names, returns the layer objects corresponding to them. */ function findLayers(layers, names) { function findLayer(layers, id) { const idLowerCase = id.toLowerCase(); let foundByName; for (let i = 0; i < layers.length; ++i) { const layer = layers[i]; if (layer.id.toString() === id) { return layer; } else if (isDefined(layer.name) && layer.name.toLowerCase() === idLowerCase) { foundByName = layer; } } return foundByName; } if (!isDefined(names)) { // If a list of layers is not specified, we're using all layers. return layers; } return names.split(",").map(function (id) { return findLayer(layers, id); }); } function updateBbox(extent, rectangle) { if (extent.xmin < rectangle.west) rectangle.west = extent.xmin; if (extent.ymin < rectangle.south) rectangle.south = extent.ymin; if (extent.xmax > rectangle.east) rectangle.east = extent.xmax; if (extent.ymax > rectangle.north) rectangle.north = extent.ymax; } export function getRectangleFromLayer(extent, rectangle) { const wkidCode = extent?.spatialReference?.latestWkid ?? extent?.spatialReference?.wkid; if (isDefined(extent) && isDefined(wkidCode)) { if (wkidCode === 4326) { return updateBbox(extent, rectangle); } const wkid = "EPSG:" + wkidCode; if (!isDefined(Proj4Definitions[wkid])) { console.warn("No Proj4 definition for " + wkid); return; } const source = Proj4Definitions[wkid]; const dest = "EPSG:4326"; let p = proj4(source, dest, [extent.xmin, extent.ymin]); const west = p[0]; const south = p[1]; p = proj4(source, dest, [extent.xmax, extent.ymax]); const east = p[0]; const north = p[1]; return updateBbox({ xmin: west, ymin: south, xmax: east, ymax: north }, rectangle); } } function getRectangleFromLayers(rectangle, layers) { layers.forEach(function (item) { if (item.extent) { getRectangleFromLayer(item.extent, rectangle); } }); } function cleanAndProxyUrl(catalogItem, url) { return proxyCatalogItemUrl(catalogItem, cleanUrl(url)); } function cleanUrl(url) { // Strip off the search portion of the URL const uri = new URI(url); uri.search(""); return uri.toString(); } //# sourceMappingURL=ArcGisMapServerCatalogItem.js.map