@itwin/core-frontend
Version:
iTwin.js frontend components
200 lines • 9.53 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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