UNPKG

@itwin/core-frontend

Version:
200 lines • 9.53 kB
/*--------------------------------------------------------------------------------------------- * 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 */ import { assert, BentleyError, IModelStatus } from "@itwin/core-bentley"; import { request } from "../../../../request/Request"; import { IModelApp } from "../../../../IModelApp"; import { MapLayerImageryProvider, MapTile, QuadId, WebMercatorTilingScheme, } from "../../../../tile/internal"; /** Represents one range of geography and tile zoom levels for a bing data provider */ class Coverage { _lowerLeftLatitude; _lowerLeftLongitude; _upperRightLatitude; _upperRightLongitude; _minimumZoomLevel; _maximumZoomLevel; constructor(_lowerLeftLatitude, _lowerLeftLongitude, _upperRightLatitude, _upperRightLongitude, _minimumZoomLevel, _maximumZoomLevel) { this._lowerLeftLatitude = _lowerLeftLatitude; this._lowerLeftLongitude = _lowerLeftLongitude; this._upperRightLatitude = _upperRightLatitude; this._upperRightLongitude = _upperRightLongitude; this._minimumZoomLevel = _minimumZoomLevel; this._maximumZoomLevel = _maximumZoomLevel; } overlaps(quadId, tilingScheme) { const range = quadId.getLatLongRangeDegrees(tilingScheme); if (quadId.level < this._minimumZoomLevel) return false; if (quadId.level > this._maximumZoomLevel) return false; if (range.low.x > this._upperRightLongitude) return false; if (range.low.y > this._upperRightLatitude) return false; if (range.high.x < this._lowerLeftLongitude) return false; if (range.high.y < this._lowerLeftLatitude) return false; return true; } } /** Represents the copyright message and an array of coverage data for one of bing's data providers (HERE for example). */ class BingAttribution { copyrightMessage; _coverages; constructor(copyrightMessage, _coverages) { this.copyrightMessage = copyrightMessage; this._coverages = _coverages; } matchesTile(tile, tilingScheme) { const quadId = QuadId.createFromContentId(tile.contentId); for (const coverage of this._coverages) { if (coverage.overlaps(quadId, tilingScheme)) return true; } return false; } } // in deployed applications, we can only make https requests, but the Bing Maps metadata request returns templates with "http:". // This function fixes those. function replaceHttpWithHttps(originalUrl) { return originalUrl.startsWith("http:") ? "https:".concat(originalUrl.slice(5)) : originalUrl; } export class BingMapsImageryLayerProvider extends MapLayerImageryProvider { _urlTemplate; _urlSubdomains; _zoomMax; _tileHeight; _tileWidth; _attributions; // array of Bing's data providers. _mapTilingScheme; _urlBase; constructor(settings) { super(settings, true); this._urlBase = settings.url; this._zoomMax = 0; this._tileHeight = this._tileWidth = 0; this._mapTilingScheme = new WebMercatorTilingScheme(); } get tileWidth() { return this._tileWidth; } get tileHeight() { return this._tileHeight; } tileXYToQuadKey(tileX, tileY, zoomLevel) { // from C# example in bing documentation https://msdn.microsoft.com/en-us/library/bb259689.aspx let quadKey = ""; // Root tile is not displayable. Returns 0 for _GetMaximumSize(). Should not end up here. assert(0 !== zoomLevel); for (let i = zoomLevel; i > 0; i--) { let digit = 0x30; // '0' const mask = 1 << (i - 1); if ((tileX & mask) !== 0) { digit++; } if ((tileY & mask) !== 0) { digit++; digit++; } quadKey = quadKey.concat(String.fromCharCode(digit)); } return quadKey; } // construct the Url from the desired Tile async constructUrl(row, column, zoomLevel) { // From the tile, get a "quadKey" the Microsoft way. const quadKey = this.tileXYToQuadKey(column, row, zoomLevel); const subdomain = this._urlSubdomains[(row + column) % this._urlSubdomains.length]; // from the template url, construct the tile url. let url = this._urlTemplate.replace("{subdomain}", subdomain); url = url.replace("{quadkey}", quadKey); return url; } // gets the attributions that match the tile set. getMatchingAttributions(tiles) { const matchingAttributions = new Array(); if (!this._attributions || !tiles) return matchingAttributions; const unmatchedSet = this._attributions.slice(); for (const tile of tiles) { if (tile instanceof MapTile) { // compare to the set of Bing attributions that we have not yet matched. for (let iAttr = 0; iAttr < unmatchedSet.length; iAttr++) { const attribution = unmatchedSet[iAttr]; if (attribution.matchesTile(tile, this._mapTilingScheme)) { matchingAttributions.push(attribution); unmatchedSet.splice(iAttr, 1); break; } } } } return matchingAttributions; } /** @deprecated in 5.0 - will not be removed until after 2026-06-13. Use [addAttributions] instead. */ addLogoCards(cards, vp) { const tiles = IModelApp.tileAdmin.getTilesForUser(vp)?.selected; const matchingAttributions = this.getMatchingAttributions(tiles); const copyrights = []; for (const match of matchingAttributions) copyrights.push(match.copyrightMessage); let copyrightMsg = ""; for (let i = 0; i < copyrights.length; ++i) { if (i > 0) copyrightMsg += "<br>"; copyrightMsg += copyrights[i]; } cards.appendChild(IModelApp.makeLogoCard({ iconSrc: `${IModelApp.publicPath}images/bing.svg`, heading: "Microsoft Bing", notice: copyrightMsg })); } async addAttributions(cards, vp) { // eslint-disable-next-line @typescript-eslint/no-deprecated return Promise.resolve(this.addLogoCards(cards, vp)); } // initializes the BingImageryProvider by reading the templateUrl, logo image, and attribution list. async initialize() { // get the template url const bingRequestUrl = this._urlBase.replace("{bingKey}", this._settings.accessKey ? this._settings.accessKey.value : ""); try { const bingResponseProps = await request(bingRequestUrl, "json"); const thisResourceSetProps = bingResponseProps.resourceSets[0]; const thisResourceProps = thisResourceSetProps.resources[0]; this._zoomMax = thisResourceProps.zoomMax; this._tileHeight = thisResourceProps.imageHeight; this._tileWidth = thisResourceProps.imageWidth; this._urlTemplate = replaceHttpWithHttps(thisResourceProps.imageUrl.replace("{culture}", "en-US")); // NEEDSWORK - get locale from somewhere. this._urlSubdomains = thisResourceProps.imageUrlSubdomains; // read the list of Bing's data suppliers and the range of data they provide. Used in calculation of copyright message. this.readAttributions(thisResourceProps.imageryProviders); // Bing sometimes provides tiles that have nothing but a camera icon in the middle of them when you ask // for tiles at zoom levels where they don't have data. Their application stops you from zooming in when that's the // case, but we can't stop - the user might want to look at design data a closer zoom. So we intentionally load such // a tile, and then compare other tiles to it, rejecting them if they match. this.loadTile(0, 0, this._zoomMax - 1).then((tileData) => { if (tileData !== undefined) this._missingTileData = tileData.data; }); } catch { throw new BentleyError(IModelStatus.BadModel, "Error in Bing Server communications"); } } // reads the list of Bing data providers and the map range for which they each provide data. readAttributions(attributionProps) { for (const thisAttributionProps of attributionProps) { const copyrightMessage = thisAttributionProps.attribution; const coverages = new Array(); for (const thisCoverageProps of thisAttributionProps.coverageAreas) { const thisCoverage = new Coverage(thisCoverageProps.bbox[0], thisCoverageProps.bbox[1], thisCoverageProps.bbox[2], thisCoverageProps.bbox[3], thisCoverageProps.zoomMin, thisCoverageProps.zoomMax); coverages.push(thisCoverage); } const thisAttribution = new BingAttribution(copyrightMessage, coverages); if (!this._attributions) this._attributions = new Array(); this._attributions.push(thisAttribution); } } } //# sourceMappingURL=BingImageryProvider.js.map