@itwin/reality-data-client
Version:
HTTP Client for the iTwin Platform Reality Management APIs
465 lines • 22.5 kB
JavaScript
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RealityDataAccessClient = exports.ApiVersion = void 0;
/** @packageDocumentation
* @module RealityDataClient
*/
const core_bentley_1 = require("@itwin/core-bentley");
const axios_1 = __importDefault(require("axios"));
const RealityData_1 = require("./RealityData");
const RequestOptions_1 = require("./RequestOptions");
const Projects_1 = require("./Projects");
const Angle_1 = require("./helper/Angle");
/** Available Reality Management API Versions */
var ApiVersion;
(function (ApiVersion) {
ApiVersion[ApiVersion["v1"] = 0] = "v1";
})(ApiVersion || (exports.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
*/
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_1.default.get(url, (0, RequestOptions_1.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 core_bentley_1.BentleyError(422, iTwinId ? `Could not fetch reality data: ${realityDataId} with iTwinId ${iTwinId}`
: `Could not fetch reality data: ${realityDataId}`);
const realityData = new RealityData_1.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 core_bentley_1.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_1.Angle.radiansToDegrees(iModelRange.low.x)},${Angle_1.Angle.radiansToDegrees(iModelRange.low.y)},${Angle_1.Angle.radiansToDegrees(iModelRange.high.x)},${Angle_1.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_1.default.get(url.href, (0, RequestOptions_1.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 core_bentley_1.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 RealityData_1.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 = (0, RequestOptions_1.getRequestConfig)(accessTokenResolved, "GET", url, this.apiVersion);
// execute query
const response = await axios_1.default.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 Projects_1.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 = (0, RequestOptions_1.getRequestConfig)(accessTokenResolved, "GET", url, this.apiVersion);
// execute query
const response = await axios_1.default.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 = (0, RequestOptions_1.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_1.default.post(url, realityDataToCreate, options);
iTwinRealityData = new RealityData_1.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 = (0, RequestOptions_1.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_1.default.patch(url.href, realityDataToModify, options);
iTwinRealityData = new RealityData_1.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 = (0, RequestOptions_1.getRequestConfig)(accessTokenResolved, "POST", url, this.apiVersion);
response = await axios_1.default.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 = (0, RequestOptions_1.getRequestConfig)(accessTokenResolved, "POST", url, this.apiVersion);
response = await axios_1.default.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 = (0, RequestOptions_1.getRequestConfig)(accessTokenResolved, "DELETE", url, this.apiVersion);
response = await axios_1.default.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_1.default.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 core_bentley_1.BentleyError(status, message));
}
}
exports.RealityDataAccessClient = RealityDataAccessClient;
//# sourceMappingURL=RealityDataClient.js.map