UNPKG

@itwin/core-frontend

Version:
346 lines • 17.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ArcGISMapLayerImageryProvider = exports.ArcGISIdentifyRequestUrl = void 0; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module Tiles */ const core_common_1 = require("@itwin/core-common"); const IModelApp_1 = require("../../../../IModelApp"); const internal_1 = require("../../../../tile/internal"); const appui_abstract_1 = require("@itwin/appui-abstract"); const core_geometry_1 = require("@itwin/core-geometry"); const core_bentley_1 = require("@itwin/core-bentley"); const loggerCategory = "MapLayerImageryProvider.ArcGISMapLayerImageryProvider"; /** @internal */ class ArcGISIdentifyRequestUrl { static fromJSON(baseUrl, json, srFractionDigits) { const newUrl = new URL(baseUrl); newUrl.pathname = `${newUrl.pathname}/identify`; if (json.f) { newUrl.searchParams.append("f", json.f); } const geomPt = core_geometry_1.Point2d.fromJSON(json.geometry); newUrl.searchParams.append("geometry", `${this.toFixed(geomPt.x, srFractionDigits)},${this.toFixed(geomPt.y, srFractionDigits)}`); newUrl.searchParams.append("geometryType", json.geometryType); if (json.sr) { newUrl.searchParams.append("sr", `${json.sr}`); } if (json.layers) { newUrl.searchParams.append("layers", `${json.layers.prefix}${json.layers.layerIds?.length ? `: ${json.layers.layerIds.join(",")}` : ""}`); } newUrl.searchParams.append("tolerance", `${json.tolerance}`); newUrl.searchParams.append("mapExtent", ArcGISIdentifyRequestUrl.getExtentString(json.mapExtent, srFractionDigits)); newUrl.searchParams.append("imageDisplay", `${json.imageDisplay.width},${json.imageDisplay.height},${json.imageDisplay.dpi}`); if (json.returnGeometry !== undefined) { newUrl.searchParams.append("returnGeometry", json.returnGeometry ? "true" : "false"); } if (json.maxAllowableOffset !== undefined) { newUrl.searchParams.append("maxAllowableOffset", `${this.toFixed(json.maxAllowableOffset, srFractionDigits)}`); } return newUrl; } static toFixed(value, srFractionDigits) { return srFractionDigits === undefined ? value.toString() : value.toFixed(srFractionDigits); } static getExtentString(range, srFractionDigits) { const extent = core_geometry_1.Range2d.fromJSON(range); const extentStringArray = []; extent.toFloat64Array().forEach((value) => extentStringArray.push(this.toFixed(value, srFractionDigits))); return extentStringArray.join(","); } } exports.ArcGISIdentifyRequestUrl = ArcGISIdentifyRequestUrl; /** @internal */ class ArcGISMapLayerImageryProvider extends internal_1.ArcGISImageryProvider { _maxDepthFromLod = 0; _minDepthFromLod = 0; _copyrightText = "Copyright"; _tileMapSupported = false; _mapSupported = false; _tilesOnly = false; _tileMap; serviceJson; constructor(settings) { super(settings, false); this._accessClient = IModelApp_1.IModelApp.mapLayerFormatRegistry.getAccessClient(settings.formatId); } get _filterByCartoRange() { return false; } // Can't trust footprint ranges (USGS Hydro) get minimumZoomLevel() { return Math.max(super.minimumZoomLevel, this._minDepthFromLod); } get maximumZoomLevel() { return this._maxDepthFromLod > 0 ? this._maxDepthFromLod : super.maximumZoomLevel; } uintToString(uintArray) { return Buffer.from(uintArray).toJSON(); } async fetchTile(row, column, zoomLevel) { const tileUrl = await this.constructUrl(row, column, zoomLevel); if (tileUrl.length === 0) return undefined; return this.fetch(new URL(tileUrl), { method: "GET" }); } async loadTile(row, column, zoomLevel) { if ((this.status === internal_1.MapLayerImageryProviderStatus.RequireAuth)) { return undefined; } try { const tileResponse = await this.fetchTile(row, column, zoomLevel); if (tileResponse === undefined) return undefined; if (!this._hasSuccessfullyFetchedTile) { this._hasSuccessfullyFetchedTile = true; } return await this.getImageFromTileResponse(tileResponse, zoomLevel); } catch (error) { core_bentley_1.Logger.logError(loggerCategory, `Error occurred when loading tile(${row},${column},${zoomLevel}) : ${error}`); return undefined; } } _generateChildIds(quadId, resolveChildren) { const childIds = this.getPotentialChildIds(quadId); if (quadId.level < Math.max(1, this.minimumZoomLevel - 1)) { resolveChildren(childIds); return; } if (this._tileMap) { // eslint-disable-next-line @typescript-eslint/no-floating-promises this._tileMap.getChildrenAvailability(childIds).then((availability) => { const availableChildIds = new Array(); for (let i = 0; i < availability.length; i++) if (availability[i]) availableChildIds.push(childIds[i]); resolveChildren(availableChildIds); }); } else if (this._usesCachedTiles && this.cartoRange) { // Filter children by range const availableChildIds = new Array(); // eslint-disable-next-line @typescript-eslint/prefer-for-of for (let i = 0; i < childIds.length; i++) { const childExtent = this.getEPSG4326Extent(childIds[i].row, childIds[i].column, childIds[i].level); const childRange = internal_1.MapCartoRectangle.fromDegrees(childExtent.longitudeLeft, childExtent.latitudeBottom, childExtent.longitudeRight, childExtent.latitudeTop); if (childRange.intersectsRange(this.cartoRange)) { availableChildIds.push(childIds[i]); } } resolveChildren(availableChildIds); } else { resolveChildren(childIds); // Resolve all children } } async initialize() { const metadata = await this.getServiceJson(); if (metadata?.content === undefined) throw new core_common_1.ServerError(core_bentley_1.IModelStatus.ValidationFailed, ""); const json = metadata.content; if (json?.error?.code === internal_1.ArcGisErrorCode.TokenRequired || json?.error?.code === internal_1.ArcGisErrorCode.InvalidToken || json?.error?.code === internal_1.ArcGisErrorCode.MissingPermissions) { // Check again layer status, it might have change during await. if (this.status === internal_1.MapLayerImageryProviderStatus.Valid) { this.setStatus(internal_1.MapLayerImageryProviderStatus.RequireAuth); return; // By returning (i.e not throwing), we ensure the tileTree get created and current provider is preserved to report status. } } this.serviceJson = json; if (json.capabilities) { const capabilities = json.capabilities.split(","); this._querySupported = capabilities.includes("Query"); this._tileMapSupported = capabilities.includes("Tilemap"); this._mapSupported = capabilities.includes("Map"); this._tilesOnly = capabilities.includes("TilesOnly"); } if (json.copyrightText) this._copyrightText = json.copyrightText; this._usesCachedTiles = !!json.tileInfo; if (this._usesCachedTiles) { // Only EPSG:3857 is supported with pre-rendered tiles. Fall back to 'Export' queries if possible otherwise throw. if (!internal_1.ArcGisUtilities.isEpsg3857Compatible(json.tileInfo)) { if (this._mapSupported && !this._tilesOnly) { this._usesCachedTiles = false; } else { throw new core_common_1.ServerError(core_bentley_1.IModelStatus.ValidationFailed, "Invalid coordinate system"); } } } if (this._usesCachedTiles) { // Read max LOD if (json.maxScale !== undefined && json.maxScale !== 0 && Array.isArray(json.tileInfo.lods)) { for (; this._maxDepthFromLod < json.tileInfo.lods.length && json.tileInfo.lods[this._maxDepthFromLod].scale > json.maxScale; this._maxDepthFromLod++) ; } // Create tile map object only if we are going to request tiles from this server and it support tilemap requests. if (this._tileMapSupported) { const fetch = async (url, options) => { return this.fetch(url, options); }; this._tileMap = new internal_1.ArcGISTileMap(this._settings.url, this._settings, fetch); } } // Read range using fullextent from service metadata if (json.fullExtent) { if (json.fullExtent.spatialReference.latestWkid === 3857 || json.fullExtent.spatialReference.wkid === 102100) { const range3857 = core_geometry_1.Range2d.createFrom({ low: { x: json.fullExtent.xmin, y: json.fullExtent.ymin }, high: { x: json.fullExtent.xmax, y: json.fullExtent.ymax } }); const west = this.getEPSG4326Lon(range3857.xLow); const south = this.getEPSG4326Lat(range3857.yLow); const east = this.getEPSG4326Lon(range3857.xHigh); const north = this.getEPSG4326Lat(range3857.yHigh); this.cartoRange = internal_1.MapCartoRectangle.fromDegrees(west, south, east, north); } } // Read minLOD if available if (json.minLOD !== undefined) { const minLod = parseInt(json.minLOD, 10); if (!Number.isNaN(minLod)) { this._minDepthFromLod = minLod; } } else if (json.minScale) { // Read min LOD using minScale const minScale = json.minScale; if (json.tileInfo?.lods !== undefined && Array.isArray(json.tileInfo.lods)) { for (const lod of json.tileInfo.lods) { if (lod.scale < minScale) { this._minDepthFromLod = lod.level; break; } } } } } /** @deprecated in 5.0 - will not be removed until after 2026-06-13. Use [addAttributions] instead. */ addLogoCards(cards) { if (!cards.dataset.arcGisLogoCard) { cards.dataset.arcGisLogoCard = "true"; cards.appendChild(IModelApp_1.IModelApp.makeLogoCard({ heading: "ArcGIS", notice: this._copyrightText })); } } async addAttributions(cards, _vp) { // eslint-disable-next-line @typescript-eslint/no-deprecated return Promise.resolve(this.addLogoCards(cards)); } // Translates the provided Cartographic into a EPSG:3857 point, and retrieve information. // tolerance is in pixels async getIdentifyData(quadId, carto, tolerance, returnGeometry, maxAllowableOffset) { const bbox = this.getEPSG3857Extent(quadId.row, quadId.column, quadId.level); const layerIds = new Array(); this._settings.subLayers.forEach((subLayer) => { if (this._settings.isSubLayerVisible(subLayer)) layerIds.push(subLayer.idString); }); const urlObj = ArcGISIdentifyRequestUrl.fromJSON(this._settings.url, { f: "json", geometry: { x: this.getEPSG3857X(carto.longitudeDegrees), y: this.getEPSG3857Y(carto.latitudeDegrees) }, geometryType: "esriGeometryPoint", tolerance, mapExtent: { low: { x: bbox.left, y: bbox.bottom }, high: { x: bbox.right, y: bbox.top } }, sr: 3857, imageDisplay: { width: this.tileSize, height: this.tileSize, dpi: 96 }, layers: { prefix: "top", layerIds }, returnGeometry, maxAllowableOffset }, 3 /* 1mm accuracy*/); const response = await this.fetch(urlObj, { method: "GET" }); return response.json(); } // Makes an identify request to ESRI MapService server, and return it as a list of formatted strings async getToolTip(strings, quadId, carto, tree) { await super.getToolTip(strings, quadId, carto, tree); if (!this._querySupported) return; const stringSet = new Set(); const json = await this.getIdentifyData(quadId, carto, 1); if (json && Array.isArray(json.results)) { for (const result of json.results) { if (result.attributes !== undefined && result.attributes[result.displayFieldName] !== undefined) { const thisString = `${result.displayFieldName}: ${result.attributes[result.displayFieldName]}`; if (!stringSet.has(thisString)) { strings.push(thisString); stringSet.add(thisString); } } } } } // Makes an identify request to ESRI MapService , and return it as a list MapLayerFeatureInfo object async getFeatureInfo(featureInfos, quadId, carto, _tree, hit, options) { if (!this._querySupported) return; const tileExtent = this.getEPSG3857Extent(quadId.row, quadId.column, quadId.level); const toleranceWorld = (tileExtent.top - tileExtent.bottom) / this.tileSize; const maxAllowableOffsetFactor = 2; const maxAllowableOffset = maxAllowableOffsetFactor * toleranceWorld; const tolerancePixel = options?.tolerance ?? 7; const json = await this.getIdentifyData(quadId, carto, tolerancePixel, true, maxAllowableOffset); if (json && Array.isArray(json.results)) { const renderer = new internal_1.FeatureGraphicsRenderer({ viewport: hit.viewport, crs: "webMercator" }); const layerInfo = { layerName: this._settings.name, subLayerInfos: [] }; // The 'identify' service returns us a flat/unordered list of records.. // results may represent features for the a common subLayer. // For simplicity, we group together features for a given sub-layer. const subLayers = new Map(); for (const result of json.results) { let subLayerInfo = subLayers.get(result.layerName); if (!subLayerInfo) { subLayerInfo = { subLayerName: result.layerName ?? "", displayFieldName: result.displayFieldName, features: [], }; subLayers.set(result.layerName, subLayerInfo); } const feature = { geometries: [], attributes: [] }; // Read all feature attributes for (const [key, value] of Object.entries(result.attributes)) { // Convert everything to string for now const strValue = String(value); feature.attributes.push({ value: { valueFormat: appui_abstract_1.PropertyValueFormat.Primitive, value: strValue, displayValue: strValue }, property: { name: key, displayLabel: key, typename: appui_abstract_1.StandardTypeNames.String }, }); } // Read feature geometries const geomReader = new internal_1.ArcGisGeometryReaderJSON(result.geometryType, renderer); await geomReader.readGeometry(result.geometry); const graphics = renderer.moveGraphics(); feature.geometries = graphics.map((graphic) => { return { graphic }; }); subLayerInfo.features.push(feature); } for (const value of subLayers.values()) { layerInfo.subLayerInfos.push(value); } featureInfos.push(layerInfo); } } getLayerString(prefix = "show") { const layers = new Array(); this._settings.subLayers.forEach((subLayer) => { if (this._settings.isSubLayerVisible(subLayer)) layers.push(subLayer.idString); }); return `${prefix}: ${layers.join(",")} `; } // construct the Url from the desired Tile async constructUrl(row, column, zoomLevel) { let tmpUrl; if (this._usesCachedTiles) { tmpUrl = `${this._settings.url}/tile/${zoomLevel}/${row}/${column} `; } else { const bboxString = `${this.getEPSG3857ExtentString(row, column, zoomLevel)}&bboxSR=3857`; tmpUrl = `${this._settings.url}/export?bbox=${bboxString}&size=${this.tileSize},${this.tileSize}&layers=${this.getLayerString()}&format=png&transparent=${this.transparentBackgroundString}&f=image&sr=3857&imagesr=3857`; } return tmpUrl; } } exports.ArcGISMapLayerImageryProvider = ArcGISMapLayerImageryProvider; //# sourceMappingURL=ArcGISMapLayerImageryProvider.js.map