UNPKG

@itwin/reality-data-client

Version:

HTTP Client for the iTwin Platform Reality Management APIs

458 lines 21.7 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 RealityDataClient */ import { BentleyError } from "@itwin/core-bentley"; import axios from "axios"; import { ITwinRealityData } from "./RealityData"; import { getRequestConfig } from "./RequestOptions"; import { Project } from "./Projects"; import { Angle } from "./helper/Angle"; /** Available Reality Management API Versions */ export var ApiVersion; (function (ApiVersion) { ApiVersion[ApiVersion["v1"] = 0] = "v1"; })(ApiVersion || (ApiVersion = {})); /** * Client wrapper to Reality Management API. * An instance of this class is used to extract reality data from the Reality Management API. * Most important methods enable to obtain a specific reality data, fetch all reality data associated with an iTwin and * all reality data of an iTwin within a provided spatial extent. * This class also implements extraction of the Azure blob address. * @beta */ export class RealityDataAccessClient { baseUrl = "https://api.bentley.com/reality-management/reality-data"; apiVersion = ApiVersion.v1; authorizationClient = undefined; /** * Creates an instance of RealityDataAccessClient. */ constructor(realityDataClientOptions) { // runtime config if (realityDataClientOptions) { if (realityDataClientOptions.version) this.apiVersion = realityDataClientOptions.version; if (realityDataClientOptions.baseUrl) this.baseUrl = this.setBaseUrl(realityDataClientOptions.baseUrl); if (realityDataClientOptions.authorizationClient) this.authorizationClient = realityDataClientOptions.authorizationClient; } } /** * Ensures the reality data client points to Reality Management API, as many users hardcode the url to the deprecated Reality Data API. * @param baseUrl base url given by users of this client * @returns base url to Reality Management API */ setBaseUrl(baseUrl) { const url = new URL(baseUrl); switch (url.host) { case "dev-api.bentley.com": return "https://dev-api.bentley.com/reality-management/reality-data"; case "qa-api.bentley.com": return "https://qa-api.bentley.com/reality-management/reality-data"; case "api.bentley.com": return "https://api.bentley.com/reality-management/reality-data"; default: throw new Error("invalid host"); } } /** * Try to use authorizationClient in RealityDataClientOptions to get the access token * otherwise, will return the input token * This is a workaround to support different authorization client for the reality data client and iTwin-core. */ async resolveAccessToken(accessToken) { return this.authorizationClient ? this.authorizationClient.getAccessToken() : accessToken; } /** * This method returns the URL to obtain the Reality Data details. * Technically it should never be required as the RealityData object returned should have all the information to obtain the * data. * @param iTwinId the iTwin identifier * @param realityDataId realityData identifier * @returns string containing the URL to reality data for indicated tile. * @beta */ async getRealityDataUrl(iTwinId, realityDataId) { if (iTwinId) { return `${this.baseUrl}/${realityDataId}?iTwinId=${iTwinId}`; } return `${this.baseUrl}/${realityDataId}`; } /** * Gets reality data with all of its properties * @param accessToken The client request context. * @param iTwinId id of associated iTwin (or project) * @param realityDataId realityData identifier * @returns The requested reality data. * @throws [[BentleyError]] with code 401 when the request lacks valid authentication credentials * @throws [[BentleyError]] with code 404 when the specified reality data is not found * @throws [[BentleyError]] with code 422 when the request is invalid * @beta */ async getRealityData(accessToken, iTwinId, realityDataId) { const accessTokenResolved = await this.resolveAccessToken(accessToken); const url = `${await this.getRealityDataUrl(iTwinId, realityDataId)}`; try { const realityDataResponse = await axios.get(url, getRequestConfig(accessTokenResolved, "GET", url, this.apiVersion)); // Axios throws on 4XX and 5XX; we make sure the response here is 200 if (realityDataResponse.status !== 200) throw new BentleyError(422, iTwinId ? `Could not fetch reality data: ${realityDataId} with iTwinId ${iTwinId}` : `Could not fetch reality data: ${realityDataId}`); const realityData = new ITwinRealityData(this, realityDataResponse.data.realityData, iTwinId); return realityData; } catch (error) { return this.handleError(error); } } /** * Gets all reality data associated with the iTwin. * @param accessToken The client request context. * @param iTwinId id of associated iTwin * @param criteria Criteria by which to query. * @returns an array of RealityData that are associated to the iTwin. * @throws [[BentleyError]] with code 401 when the request lacks valid authentication credentials * @throws [[BentleyError]] with code 422 when the request is invalid * @beta */ async getRealityDatas(accessToken, iTwinId, criteria) { try { const accessTokenResolved = await this.resolveAccessToken(accessToken); const url = new URL(this.baseUrl); if (iTwinId) url.searchParams.append("iTwinId", iTwinId); if (criteria) { if (criteria.continuationToken) { url.searchParams.append("continuationToken", criteria.continuationToken); } if (criteria.top) { const top = criteria.top; if (top > 500) { throw new BentleyError(422, "Maximum value for top parameter is 500."); } url.searchParams.append("$top", top.toString()); } if (criteria.extent) { const iModelRange = criteria.extent.getLongitudeLatitudeBoundingBox(); const extent = `${Angle.radiansToDegrees(iModelRange.low.x)},${Angle.radiansToDegrees(iModelRange.low.y)},${Angle.radiansToDegrees(iModelRange.high.x)},${Angle.radiansToDegrees(iModelRange.high.y)}`; url.searchParams.append("extent", extent); } if (criteria.orderBy) { url.searchParams.append("$orderby", criteria.orderBy); } if (criteria.search) { url.searchParams.append("$search", criteria.search); } if (criteria.types) { url.searchParams.append("types", criteria.types.join(",")); } if (criteria.acquisitionDates) { const startDateTime = this.formatIsoString(criteria.acquisitionDates.startDateTime); const endDateTime = this.formatIsoString(criteria.acquisitionDates.endDateTime); url.searchParams.append("acquisitionDateTime", `${startDateTime}/${endDateTime}`); } if (criteria.createdDateTime) { const startDateTime = this.formatIsoString(criteria.createdDateTime.startDateTime); const endDateTime = this.formatIsoString(criteria.createdDateTime.endDateTime); url.searchParams.append("createdDateTime", `${startDateTime}/${endDateTime}`); } if (criteria.tag) { url.searchParams.append("tag", criteria.tag); } } const response = await axios.get(url.href, getRequestConfig(accessTokenResolved, "GET", url.href, this.apiVersion, (criteria?.getFullRepresentation === true ? true : false))); // Axios throws on 4XX and 5XX; we make sure the response here is 200 if (response.status !== 200) throw new BentleyError(422, iTwinId ? `Could not fetch reality data with iTwinId ${iTwinId}` : "Could not fetch reality data"); const realityDatasResponseBody = response.data; const realityDataResponse = { realityDatas: [], continuationToken: this.extractContinuationToken(response.data._links?.next?.href), }; realityDatasResponseBody.realityData.forEach((realityData) => { realityDataResponse.realityDatas.push(new ITwinRealityData(this, realityData, iTwinId)); }); return realityDataResponse; } catch (error) { return this.handleError(error); } } /** * trims milliseconds from date.toISOString() method to conform to for date parameters in the API. * See https://developer.bentley.com/apis/reality-management/operations/get-all-reality-data/#request-parameters * @param date date to format * @returns dateTime string in format YYYY-MM-DDTHH:mm:ssZ e.g. 2021-08-01T00:00:00Z */ formatIsoString(date) { return `${date.toISOString().slice(0, -5)}Z`; } extractContinuationToken(url) { if (url) { // API returns some case sensitive parameters e.g. "ContinuationToken". Therefore first, set parameters to lowercase const searchParams = new URLSearchParams(url); const newParams = new URLSearchParams(); for (const [name, value] of searchParams) { newParams.append(name.toLowerCase(), value); } // Then get continuation token value in case insensitive manner. const token = newParams.get("continuationtoken"); return token ? token : undefined; } return undefined; } /** * Retrieves the list of Projects associated to the specified realityData. * @deprecated in 1.0.1, getRealityDataProjects is deprecated and no longer used as Projects API is deprecated. Use getRealityDatasITwins method. * @param accessToken The client request context. * @param realityDataId realityData identifier * @returns an array of Projects that are associated to the realityData. * @throws [[BentleyError]] with code 401 when the request lacks valid authentication credentials * @beta */ async getRealityDataProjects(accessToken, realityDataId) { try { const accessTokenResolved = await this.resolveAccessToken(accessToken); const url = `${this.baseUrl}/${realityDataId}/itwins`; const options = getRequestConfig(accessTokenResolved, "GET", url, this.apiVersion); // execute query const response = await axios.get(url, options); const projectsResponseBody = response.data; const projectsResponse = []; // make up projects details link manually const projectsBaseUrl = this.baseUrl.replace("/reality-management/reality-data", "/projects"); projectsResponseBody.iTwins.forEach((itwinValue) => { // build Project object with _links.self.href structure const href = new URL(`${projectsBaseUrl}/${itwinValue}`); const self = { href, }; const newProject = new Project({ id: itwinValue, // eslint-disable-next-line @typescript-eslint/naming-convention _links: { self, }, }); projectsResponse.push(newProject); }); return projectsResponse; } catch (error) { return this.handleError(error); } } /** * Retrieves the list of iTwins associated to the specified realityData. * @param accessToken The client request context. * @param realityDataId realityData identifier * @returns an array of iTwin identifiers that are associated to the realityData. * @throws [[BentleyError]] with code 401 when the request lacks valid authentication credentials * @beta */ async getRealityDataITwins(accessToken, realityDataId) { try { const accessTokenResolved = await this.resolveAccessToken(accessToken); const url = `${this.baseUrl}/${realityDataId}/itwins`; const options = getRequestConfig(accessTokenResolved, "GET", url, this.apiVersion); // execute query const response = await axios.get(url, options); const iTwinsResponseBody = response.data; const itwinsResponse = []; iTwinsResponseBody.iTwins.forEach((itwinValue) => { itwinsResponse.push(itwinValue); }); return itwinsResponse; } catch (error) { return this.handleError(error); } } /** * Creates a RealityData * @param accessToken The client request context. * @param iTwinId id of associated iTwin * @param iTwinRealityData the realityData to create * @throws [[BentleyError]] with code 401 when the request lacks valid authentication credentials * @throws [[BentleyError]] with code 403 when user does not have required permissions to create a reality data * @throws [[BentleyError]] with code 422 when the request is invalid * @beta */ async createRealityData(accessToken, iTwinId, iTwinRealityData) { try { const accessTokenResolved = await this.resolveAccessToken(accessToken); const url = this.baseUrl; const options = getRequestConfig(accessTokenResolved, "POST", url, this.apiVersion); // creation payload const realityDataToCreate = { displayName: iTwinRealityData.displayName, classification: iTwinRealityData.classification, type: iTwinRealityData.type, iTwinId, dataset: iTwinRealityData.dataset, group: iTwinRealityData.group, description: iTwinRealityData.description, rootDocument: iTwinRealityData.rootDocument, tags: iTwinRealityData.tags, acquisition: iTwinRealityData.acquisition, authoring: iTwinRealityData.authoring, extent: iTwinRealityData.extent, }; const response = await axios.post(url, realityDataToCreate, options); iTwinRealityData = new ITwinRealityData(this, response.data.realityData, iTwinId); } catch (error) { return this.handleError(error); } return iTwinRealityData; } /** * Modifies an existing RealityData * @param accessToken The client request context. * @param iTwinId id of associated iTwin * @param iTwinRealityData the realityData to modify * @throws [[BentleyError]] with code 401 when the request lacks valid authentication credentials * @throws [[BentleyError]] with code 404 when the specified reality data was not found * @throws [[BentleyError]] with code 422 when the request is invalid * @beta */ async modifyRealityData(accessToken, iTwinId, iTwinRealityData) { try { const accessTokenResolved = await this.resolveAccessToken(accessToken); const url = new URL(`${this.baseUrl}/${iTwinRealityData.id}`); const options = getRequestConfig(accessTokenResolved, "PATCH", url.href, this.apiVersion); // payload const realityDataToModify = { id: iTwinRealityData.id, displayName: iTwinRealityData.displayName, classification: iTwinRealityData.classification, type: iTwinRealityData.type, iTwinId, dataset: iTwinRealityData.dataset, group: iTwinRealityData.group, description: iTwinRealityData.description, rootDocument: iTwinRealityData.rootDocument, tags: iTwinRealityData.tags, acquisition: iTwinRealityData.acquisition, authoring: iTwinRealityData.authoring, extent: iTwinRealityData.extent, }; const response = await axios.patch(url.href, realityDataToModify, options); iTwinRealityData = new ITwinRealityData(this, response.data.realityData, iTwinId); } catch (error) { return this.handleError(error); } return iTwinRealityData; } /** * Deletes a RealityData * @param accessToken The client request context. * @param realityDataId the realityData to delete * @returns true if successful (204 response), false if not * @throws [[BentleyError]] with code 401 when the request lacks valid authentication credentials * @throws [[BentleyError]] with code 404 when the specified reality data was not found * @throws [[BentleyError]] with code 422 when the request is invalid * @beta */ async deleteRealityData(accessToken, realityDataId) { let response; try { const accessTokenResolved = await this.resolveAccessToken(accessToken); const url = `${this.baseUrl}/${realityDataId}`; const options = getRequestConfig(accessTokenResolved, "POST", url, this.apiVersion); response = await axios.delete(url, options); } catch (error) { return this.handleError(error); } if (response.status === 204) return true; else return false; } /** * Associates a RealityData to an iTwin * @param accessToken The client request context. * @param iTwinId id of iTwin to associate the realityData to. * @param realityDataId id of the RealityData. * @returns true if successful (200 response) or false if not * @throws [[BentleyError]] with code 401 when the request lacks valid authentication credentials * @throws [[BentleyError]] with code 404 when the specified reality data or iTwin was not found * @throws [[BentleyError]] with code 422 when the request is invalid * @beta */ async associateRealityData(accessToken, iTwinId, realityDataId) { let response; try { const accessTokenResolved = await this.resolveAccessToken(accessToken); const url = `${this.baseUrl}/${realityDataId}/iTwins/${iTwinId}`; const options = getRequestConfig(accessTokenResolved, "POST", url, this.apiVersion); response = await axios.post(url, undefined, options); } catch (error) { return this.handleError(error); } if (response.status === 200) return true; else return false; } /** * Dissociates a RealityData from an iTwin * @param accessToken The client request context. * @param iTwinId id of iTwin to dissociate the realityData from. * @param realityDataId id of the RealityData. * @returns true if successful (204 response) or false if not * @throws [[BentleyError]] with code 401 when the request lacks valid authentication credentials * @throws [[BentleyError]] with code 404 when the association between the reality data and iTwin was not found * @throws [[BentleyError]] with code 422 when the request is invalid * @beta */ async dissociateRealityData(accessToken, iTwinId, realityDataId) { let response; try { const accessTokenResolved = await this.resolveAccessToken(accessToken); const url = `${this.baseUrl}/${realityDataId}/iTwins/${iTwinId}`; const options = getRequestConfig(accessTokenResolved, "DELETE", url, this.apiVersion); response = await axios.delete(url, options); } catch (error) { return this.handleError(error); } if (response.status === 204) return true; else return false; } /** * Handle errors thrown. * Handled errors can be of AxiosError type or BentleyError. * @beta */ handleError(error) { // Default error let status = 422; let message = "Unknown error. Please ensure that the request is valid."; if (axios.isAxiosError(error) && error.response) { const axiosResponse = error.response; status = axiosResponse.status; message = axiosResponse.data?.error?.message; } else { const bentleyError = error; if (bentleyError !== undefined) { status = bentleyError.errorNumber; message = bentleyError.message; } } return Promise.reject(new BentleyError(status, message)); } } //# sourceMappingURL=RealityDataClient.js.map