diff --git a/Apps/Sandcastle/gallery/iTwin Demo.html b/Apps/Sandcastle/gallery/iTwin Demo.html
index 19e0156a005..e78ab95f4c1 100644
--- a/Apps/Sandcastle/gallery/iTwin Demo.html
+++ b/Apps/Sandcastle/gallery/iTwin Demo.html
@@ -120,18 +120,28 @@
scene.primitives.add(surroundingArea);
scene.primitives.add(station);
+ // Create tileset of the reality data mesh
+ const iTwinId = "535a24a3-9b29-4e23-bb5d-9cedb524c743";
+ const realityMeshId = "85897090-3bcc-470b-bec7-20bb639cc1b9";
+ const realityMesh = await Cesium.ITwinData.createTilesetForRealityDataId(
+ iTwinId,
+ realityMeshId,
+ );
+ scene.primitives.add(realityMesh);
+
Sandcastle.addToolbarButton(
"Toggle Surrounding Area",
- function () {
- surroundingArea.show = !surroundingArea.show;
- },
+ () => (surroundingArea.show = !surroundingArea.show),
"layers",
);
Sandcastle.addToolbarButton(
"Toggle Station Model",
- function () {
- station.show = !station.show;
- },
+ () => (station.show = !station.show),
+ "layers",
+ );
+ Sandcastle.addToolbarButton(
+ "Toggle Reality Mesh",
+ () => (realityMesh.show = !realityMesh.show),
"layers",
);
diff --git a/Apps/Sandcastle/gallery/iTwin Demo.jpg b/Apps/Sandcastle/gallery/iTwin Demo.jpg
index 98316311825..ec74f8279e2 100644
Binary files a/Apps/Sandcastle/gallery/iTwin Demo.jpg and b/Apps/Sandcastle/gallery/iTwin Demo.jpg differ
diff --git a/packages/engine/Source/Core/ITwinPlatform.js b/packages/engine/Source/Core/ITwinPlatform.js
index ca6f8e63063..c6e9e44ba4a 100644
--- a/packages/engine/Source/Core/ITwinPlatform.js
+++ b/packages/engine/Source/Core/ITwinPlatform.js
@@ -37,6 +37,57 @@ ITwinPlatform.ExportType = Object.freeze({
"3DTILES": "3DTILES",
});
+/**
+ * Types of Reality data
+ * @see https://developer.bentley.com/apis/reality-management/rm-rd-details/#types
+ * @enum {string}
+ */
+ITwinPlatform.RealityDataType = Object.freeze({
+ Cesium3DTiles: "Cesium3DTiles",
+ PNTS: "PNTS",
+ OPC: "OPC",
+ RealityMesh3DTiles: "RealityMesh3DTiles",
+ Terrain3DTiles: "Terrain3DTiles",
+ "3MX": "3MX",
+ "3SM": "3SM",
+ CCCloudProject: "CCCloudProject",
+ CCImageCollection: "CCImageCollection",
+ CCOrientations: "CCOrientations",
+ ContextCaptureInputs: "ContextCaptureInputs",
+ ContextDetector: "ContextDetector",
+ ContextScene: "ContextScene",
+ DAE: "DAE",
+ DGN: "DGN",
+ DSM: "DSM",
+ FBX: "FBX",
+ GLB: "GLB",
+ GLTF: "GLTF",
+ KML: "KML",
+ LAS: "LAS",
+ LAZ: "LAZ",
+ LOD: "LOD",
+ LodTree: "LodTree",
+ OBJ: "OBJ",
+ OMI: "OMI",
+ OMR: "OMR",
+ Orthophoto: "Orthophoto",
+ OrthophotoDSM: "OrthophotoDSM",
+ OSGB: "OSGB",
+ OVF: "OVF",
+ OBT: "OBT",
+ PLY: "PLY",
+ PointCloud: "PointCloud",
+ S3C: "S3C",
+ ScanCollection: "ScanCollection",
+ SHP: "SHP",
+ SLPK: "SLPK",
+ SpaceEyes3D: "SpaceEyes3D",
+ STL: "STL",
+ TSM: "TSM",
+ Unstructured: "Unstructured",
+ Other: "Other",
+});
+
/**
* Gets or sets the default iTwin access token. This token should have the itwin-platform
scope.
*
@@ -155,4 +206,154 @@ ITwinPlatform.getExports = async function (iModelId) {
}
};
+/**
+ * @typedef {Object} RealityDataExtent
+ * @private
+ * @property {{latitude: number, longitude: number}} southWest
+ * @property {{latitude: number, longitude: number}} northEast
+ */
+
+/**
+ * @typedef {Object} RealityDataRepresentation
+ * @private
+ * @property {string} id "95d8dccd-d89e-4287-bb5f-3219acbc71ae",
+ * @property {string} displayName "Name of reality data",
+ * @property {string} dataset "Dataset",
+ * @property {string} group "73d09423-28c3-4fdb-ab4a-03a47a5b04f8",
+ * @property {string} description "Description of reality data",
+ * @property {string} rootDocument "Directory/SubDirectory/realityData.3mx",
+ * @property {number} size 6521212,
+ * @property {string} classification "Model",
+ * @property {ITwinPlatform.RealityDataType} type "3MX",
+ * @property {{startDateTime: string, endDateTime: string, acquirer: string}} acquisition
+ * @property {RealityDataExtent} extent
+ * @property {boolean} authoring false,
+ * @property {string} dataCenterLocation "North Europe",
+ * @property {string} modifiedDateTime "2021-04-09T19:03:12Z",
+ * @property {string} lastAccessedDateTime "2021-04-09T00:00:00Z",
+ * @property {string} createdDateTime "2021-02-22T20:03:40Z",
+ * @property {string} ownerId "f1d49cc7-f9b3-494f-9c67-563ea5597063",
+ */
+
+/**
+ * Load the full metadata for the given iTwin id and reality data id.
+ *
+ * @private
+ *
+ * @param {string} iTwinId The id of the iTwin to load data from
+ * @param {string} realityDataId The id of the reality data to load
+ * @returns {Promise}
+ */
+ITwinPlatform.getRealityDataMetadata = async function (iTwinId, realityDataId) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.string("iTwinId", iTwinId);
+ Check.typeOf.string("realityDataId", realityDataId);
+ if (!defined(ITwinPlatform.defaultAccessToken)) {
+ throw new DeveloperError("Must set ITwinPlatform.defaultAccessToken first");
+ }
+ //>>includeEnd('debug')
+
+ const resource = new Resource({
+ url: `${ITwinPlatform.apiEndpoint}reality-management/reality-data/${realityDataId}`,
+ headers: {
+ Authorization: `Bearer ${ITwinPlatform.defaultAccessToken}`,
+ Accept: "application/vnd.bentley.itwin-platform.v1+json",
+ },
+ queryParameters: { iTwinId: iTwinId },
+ });
+
+ try {
+ const response = await resource.fetchJson();
+ return response.realityData;
+ } catch (error) {
+ const result = JSON.parse(error.response);
+ if (error.statusCode === 401) {
+ throw new RuntimeError(
+ `Unauthorized, bad token, wrong scopes or headers bad. ${result.error.details[0].code}`,
+ );
+ } else if (error.statusCode === 403) {
+ console.error(result.error.code, result.error.message);
+ throw new RuntimeError("Not allowed, forbidden");
+ } else if (error.statusCode === 404) {
+ throw new RuntimeError(
+ `Reality data not found: ${iTwinId}, ${realityDataId}`,
+ );
+ } else if (error.statusCode === 422) {
+ throw new RuntimeError(
+ `Unprocessable Entity:${result.error.code} ${result.error.message}`,
+ );
+ } else if (error.statusCode === 429) {
+ throw new RuntimeError("Too many requests");
+ }
+ throw new RuntimeError(`Unknown request failure ${error.statusCode}`);
+ }
+};
+
+/**
+ * Request the access url for the given iTwin id, reality data id and root document.
+ * The root document can be requested from the list using return=representation
+ * or the metadata route from {@link ITwinPlatform.getRealityDataMetadata}
+ *
+ * @private
+ *
+ * @param {string} iTwinId The id of the iTwin to load data from
+ * @param {string} realityDataId The id of the reality data to load
+ * @param {string} rootDocument The path of the root document for this reality data
+ * @returns {Promise}
+ */
+ITwinPlatform.getRealityDataURL = async function (
+ iTwinId,
+ realityDataId,
+ rootDocument,
+) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.string("iTwinId", iTwinId);
+ Check.typeOf.string("realityDataId", realityDataId);
+ Check.typeOf.string("rootDocument", rootDocument);
+ if (!defined(ITwinPlatform.defaultAccessToken)) {
+ throw new DeveloperError("Must set ITwinPlatform.defaultAccessToken first");
+ }
+ //>>includeEnd('debug')
+
+ const resource = new Resource({
+ url: `${ITwinPlatform.apiEndpoint}reality-management/reality-data/${realityDataId}/readaccess`,
+ headers: {
+ Authorization: `Bearer ${ITwinPlatform.defaultAccessToken}`,
+ Accept: "application/vnd.bentley.itwin-platform.v1+json",
+ },
+ queryParameters: { iTwinId: iTwinId },
+ });
+
+ try {
+ const result = await resource.fetchJson();
+
+ const containerUrl = result._links.containerUrl.href;
+ const tilesetUrl = new URL(containerUrl);
+ tilesetUrl.pathname = `${tilesetUrl.pathname}/${rootDocument}`;
+
+ return tilesetUrl.toString();
+ } catch (error) {
+ const result = JSON.parse(error.response);
+ if (error.statusCode === 401) {
+ throw new RuntimeError(
+ `Unauthorized, bad token, wrong scopes or headers bad. ${result.error.details[0].code}`,
+ );
+ } else if (error.statusCode === 403) {
+ console.error(result.error.code, result.error.message);
+ throw new RuntimeError("Not allowed, forbidden");
+ } else if (error.statusCode === 404) {
+ throw new RuntimeError(
+ `Reality data not found: ${iTwinId}, ${realityDataId}`,
+ );
+ } else if (error.statusCode === 422) {
+ throw new RuntimeError(
+ `Unprocessable Entity:${result.error.code} ${result.error.message}`,
+ );
+ } else if (error.statusCode === 429) {
+ throw new RuntimeError("Too many requests");
+ }
+ throw new RuntimeError(`Unknown request failure ${error.statusCode}`);
+ }
+};
+
export default ITwinPlatform;
diff --git a/packages/engine/Source/Scene/ITwinData.js b/packages/engine/Source/Scene/ITwinData.js
index 1e970ef1a3e..1b53f27e4c9 100644
--- a/packages/engine/Source/Scene/ITwinData.js
+++ b/packages/engine/Source/Scene/ITwinData.js
@@ -3,6 +3,7 @@ import defined from "../Core/defined.js";
import Resource from "../Core/Resource.js";
import ITwinPlatform from "../Core/ITwinPlatform.js";
import RuntimeError from "../Core/RuntimeError.js";
+import Check from "../Core/Check.js";
/**
* Methods for loading iTwin platform data into CesiumJS
@@ -71,4 +72,67 @@ ITwinData.createTilesetFromIModelId = async function (iModelId, options) {
return Cesium3DTileset.fromUrl(resource, options);
};
+/**
+ * Create a tileset for the specified reality data id. This function only works
+ * with 3D Tiles meshes and point clouds.
+ *
+ * If the type
or rootDocument
are not provided this function
+ * will first request the full metadata for the specified reality data to fill these values.
+ *
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ *
+ * @param {string} iTwinId The id of the iTwin to load data from
+ * @param {string} realityDataId The id of the reality data to load
+ * @param {ITwinPlatform.RealityDataType} [type] The type of this reality data
+ * @param {string} [rootDocument] The path of the root document for this reality data
+ * @returns {Promise}
+ */
+ITwinData.createTilesetForRealityDataId = async function (
+ iTwinId,
+ realityDataId,
+ type,
+ rootDocument,
+) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.string("iTwinId", iTwinId);
+ Check.typeOf.string("realityDataId", realityDataId);
+ if (defined(type)) {
+ Check.typeOf.string("type", type);
+ }
+ if (defined(rootDocument)) {
+ Check.typeOf.string("rootDocument", rootDocument);
+ }
+ //>>includeEnd('debug')
+
+ if (!defined(type) || !defined(rootDocument)) {
+ const metadata = await ITwinPlatform.getRealityDataMetadata(
+ iTwinId,
+ realityDataId,
+ );
+ rootDocument = metadata.rootDocument;
+ type = metadata.type;
+ }
+
+ const supportedRealityDataTypes = [
+ ITwinPlatform.RealityDataType.Cesium3DTiles,
+ ITwinPlatform.RealityDataType.PNTS,
+ ITwinPlatform.RealityDataType.RealityMesh3DTiles,
+ ITwinPlatform.RealityDataType.Terrain3DTiles,
+ ];
+
+ if (!supportedRealityDataTypes.includes(type)) {
+ throw new RuntimeError(`Reality data type is not a mesh type: ${type}`);
+ }
+
+ const tilesetAccessUrl = await ITwinPlatform.getRealityDataURL(
+ iTwinId,
+ realityDataId,
+ rootDocument,
+ );
+
+ return Cesium3DTileset.fromUrl(tilesetAccessUrl, {
+ maximumScreenSpaceError: 4,
+ });
+};
+
export default ITwinData;
diff --git a/packages/engine/Specs/Core/ITwinPlatformSpec.js b/packages/engine/Specs/Core/ITwinPlatformSpec.js
index eefb376a6b2..b43089c3f88 100644
--- a/packages/engine/Specs/Core/ITwinPlatformSpec.js
+++ b/packages/engine/Specs/Core/ITwinPlatformSpec.js
@@ -1,8 +1,9 @@
-import DeveloperError from "../../Source/Core/DeveloperError.js";
-import ITwinPlatform from "../../Source/Core/ITwinPlatform.js";
-import RequestErrorEvent from "../../Source/Core/RequestErrorEvent.js";
-import Resource from "../../Source/Core/Resource.js";
-import RuntimeError from "../../Source/Core/RuntimeError.js";
+import {
+ ITwinPlatform,
+ RequestErrorEvent,
+ Resource,
+ RuntimeError,
+} from "../../index.js";
describe("ITwinPlatform", () => {
let previousAccessToken;
@@ -22,10 +23,10 @@ describe("ITwinPlatform", () => {
});
it("rejects with no iModelId", async () => {
- // @ts-expect-error
- await expectAsync(ITwinPlatform.getExports()).toBeRejectedWithError(
- DeveloperError,
- /iModelId/,
+ await expectAsync(
+ ITwinPlatform.getExports(undefined),
+ ).toBeRejectedWithDeveloperError(
+ "Expected iModelId to be typeof string, actual typeof was undefined",
);
});
@@ -33,9 +34,8 @@ describe("ITwinPlatform", () => {
ITwinPlatform.defaultAccessToken = undefined;
await expectAsync(
ITwinPlatform.getExports("imodel-id-1"),
- ).toBeRejectedWithError(
- DeveloperError,
- /Must set ITwinPlatform.defaultAccessToken/,
+ ).toBeRejectedWithDeveloperError(
+ "Must set ITwinPlatform.defaultAccessToken first",
);
});
@@ -99,7 +99,7 @@ describe("ITwinPlatform", () => {
let resource;
requestSpy.and.callFake(function () {
resource = this;
- return JSON.stringify({ exports: [] });
+ return { exports: [] };
});
await ITwinPlatform.getExports("imodel-id-1");
expect(resource).toBeDefined();
@@ -119,4 +119,296 @@ describe("ITwinPlatform", () => {
expect(resource.url).toContain("imodel-id-1");
});
});
+
+ describe("getRealityDataMetadata", () => {
+ let requestSpy;
+ beforeEach(() => {
+ requestSpy = spyOn(Resource.prototype, "fetchJson");
+ });
+
+ it("rejects with no iTwinId", async () => {
+ await expectAsync(
+ ITwinPlatform.getRealityDataMetadata(undefined),
+ ).toBeRejectedWithDeveloperError(
+ "Expected iTwinId to be typeof string, actual typeof was undefined",
+ );
+ });
+
+ it("rejects with no realityDataId", async () => {
+ await expectAsync(
+ ITwinPlatform.getRealityDataMetadata("itwin-id-1", undefined),
+ ).toBeRejectedWithDeveloperError(
+ "Expected realityDataId to be typeof string, actual typeof was undefined",
+ );
+ });
+
+ it("rejects with no default access token set", async () => {
+ ITwinPlatform.defaultAccessToken = undefined;
+ await expectAsync(
+ ITwinPlatform.getRealityDataMetadata("itwin-id-1", "reality-data-id-1"),
+ ).toBeRejectedWithDeveloperError(
+ "Must set ITwinPlatform.defaultAccessToken first",
+ );
+ });
+
+ it("rejects for API 401 errors", async () => {
+ requestSpy.and.rejectWith(
+ new RequestErrorEvent(
+ 401,
+ JSON.stringify({
+ error: { message: "failed", details: [{ code: "InvalidToken" }] },
+ }),
+ ),
+ );
+ await expectAsync(
+ ITwinPlatform.getRealityDataMetadata("itwin-id-1", "reality-data-id-1"),
+ ).toBeRejectedWithError(RuntimeError, /Unauthorized/);
+ });
+
+ it("rejects for API 403 errors", async () => {
+ requestSpy.and.rejectWith(
+ new RequestErrorEvent(
+ 403,
+ JSON.stringify({
+ error: { message: "failed", code: "Forbidden" },
+ }),
+ ),
+ );
+ await expectAsync(
+ ITwinPlatform.getRealityDataMetadata("itwin-id-1", "reality-data-id-1"),
+ ).toBeRejectedWithError(RuntimeError, /forbidden/);
+ });
+
+ it("rejects for API 404 errors", async () => {
+ requestSpy.and.rejectWith(
+ new RequestErrorEvent(
+ 404,
+ JSON.stringify({
+ error: { message: "" },
+ }),
+ ),
+ );
+ await expectAsync(
+ ITwinPlatform.getRealityDataMetadata("itwin-id-1", "reality-data-id-1"),
+ ).toBeRejectedWithError(RuntimeError, /not found/);
+ });
+
+ it("rejects for API 422 errors", async () => {
+ requestSpy.and.rejectWith(
+ new RequestErrorEvent(
+ 422,
+ JSON.stringify({
+ error: { message: "failed", code: "BadEntity" },
+ }),
+ ),
+ );
+ await expectAsync(
+ ITwinPlatform.getRealityDataMetadata("itwin-id-1", "reality-data-id-1"),
+ ).toBeRejectedWithError(RuntimeError, /Unprocessable/);
+ });
+
+ it("rejects for API 429 errors", async () => {
+ requestSpy.and.rejectWith(
+ new RequestErrorEvent(
+ 429,
+ JSON.stringify({
+ error: { message: "" },
+ }),
+ ),
+ );
+ await expectAsync(
+ ITwinPlatform.getRealityDataMetadata("itwin-id-1", "reality-data-id-1"),
+ ).toBeRejectedWithError(RuntimeError, /Too many/);
+ });
+
+ it("uses the default access token for the API request", async () => {
+ let resource;
+ requestSpy.and.callFake(function () {
+ resource = this;
+ return { realityData: {} };
+ });
+ await ITwinPlatform.getRealityDataMetadata(
+ "itwin-id-1",
+ "reality-data-id-1",
+ );
+ expect(resource).toBeDefined();
+ expect(resource.headers["Authorization"]).toEqual(
+ "Bearer default-access-token",
+ );
+ });
+ });
+
+ describe("getRealityDataURL", () => {
+ let requestSpy;
+ beforeEach(() => {
+ requestSpy = spyOn(Resource.prototype, "fetchJson");
+ });
+
+ it("rejects with no iTwinId", async () => {
+ await expectAsync(
+ ITwinPlatform.getRealityDataURL(undefined),
+ ).toBeRejectedWithDeveloperError(
+ "Expected iTwinId to be typeof string, actual typeof was undefined",
+ );
+ });
+
+ it("rejects with no realityDataId", async () => {
+ await expectAsync(
+ ITwinPlatform.getRealityDataURL("itwin-id-1", undefined),
+ ).toBeRejectedWithDeveloperError(
+ "Expected realityDataId to be typeof string, actual typeof was undefined",
+ );
+ });
+ it("rejects with no rootDocument", async () => {
+ await expectAsync(
+ ITwinPlatform.getRealityDataURL(
+ "itwin-id-1",
+ "reality-data-id-1",
+ undefined,
+ ),
+ ).toBeRejectedWithDeveloperError(
+ "Expected rootDocument to be typeof string, actual typeof was undefined",
+ );
+ });
+
+ it("rejects with no default access token set", async () => {
+ ITwinPlatform.defaultAccessToken = undefined;
+ await expectAsync(
+ ITwinPlatform.getRealityDataURL(
+ "itwin-id-1",
+ "reality-data-id-1",
+ "root/document/path.json",
+ ),
+ ).toBeRejectedWithDeveloperError(
+ "Must set ITwinPlatform.defaultAccessToken first",
+ );
+ });
+
+ it("rejects for API 401 errors", async () => {
+ requestSpy.and.rejectWith(
+ new RequestErrorEvent(
+ 401,
+ JSON.stringify({
+ error: { message: "failed", details: [{ code: "InvalidToken" }] },
+ }),
+ ),
+ );
+ await expectAsync(
+ ITwinPlatform.getRealityDataURL(
+ "itwin-id-1",
+ "reality-data-id-1",
+ "root/document/path.json",
+ ),
+ ).toBeRejectedWithError(RuntimeError, /Unauthorized/);
+ });
+
+ it("rejects for API 403 errors", async () => {
+ requestSpy.and.rejectWith(
+ new RequestErrorEvent(
+ 403,
+ JSON.stringify({
+ error: { message: "failed", code: "Forbidden" },
+ }),
+ ),
+ );
+ await expectAsync(
+ ITwinPlatform.getRealityDataURL(
+ "itwin-id-1",
+ "reality-data-id-1",
+ "root/document/path.json",
+ ),
+ ).toBeRejectedWithError(RuntimeError, /forbidden/);
+ });
+
+ it("rejects for API 404 errors", async () => {
+ requestSpy.and.rejectWith(
+ new RequestErrorEvent(
+ 404,
+ JSON.stringify({
+ error: { message: "" },
+ }),
+ ),
+ );
+ await expectAsync(
+ ITwinPlatform.getRealityDataURL(
+ "itwin-id-1",
+ "reality-data-id-1",
+ "root/document/path.json",
+ ),
+ ).toBeRejectedWithError(RuntimeError, /not found/);
+ });
+
+ it("rejects for API 422 errors", async () => {
+ requestSpy.and.rejectWith(
+ new RequestErrorEvent(
+ 422,
+ JSON.stringify({
+ error: { message: "failed", code: "BadEntity" },
+ }),
+ ),
+ );
+ await expectAsync(
+ ITwinPlatform.getRealityDataURL(
+ "itwin-id-1",
+ "reality-data-id-1",
+ "root/document/path.json",
+ ),
+ ).toBeRejectedWithError(RuntimeError, /Unprocessable/);
+ });
+
+ it("rejects for API 429 errors", async () => {
+ requestSpy.and.rejectWith(
+ new RequestErrorEvent(
+ 429,
+ JSON.stringify({
+ error: { message: "" },
+ }),
+ ),
+ );
+ await expectAsync(
+ ITwinPlatform.getRealityDataURL(
+ "itwin-id-1",
+ "reality-data-id-1",
+ "root/document/path.json",
+ ),
+ ).toBeRejectedWithError(RuntimeError, /Too many/);
+ });
+
+ it("uses the default access token for the API request", async () => {
+ let resource;
+ requestSpy.and.callFake(function () {
+ resource = this;
+ return {
+ _links: {
+ containerUrl: { href: "https://example.com/base/path?auth=token" },
+ },
+ };
+ });
+ await ITwinPlatform.getRealityDataURL(
+ "itwin-id-1",
+ "reality-data-id-1",
+ "root/document/path.json",
+ );
+ expect(resource).toBeDefined();
+ expect(resource.headers["Authorization"]).toEqual(
+ "Bearer default-access-token",
+ );
+ });
+
+ it("combines the rootDocument with the access url", async () => {
+ requestSpy.and.resolveTo({
+ _links: {
+ containerUrl: { href: "https://example.com/base/path?auth=token" },
+ },
+ });
+ const tilesetUrl = await ITwinPlatform.getRealityDataURL(
+ "itwin-id-1",
+ "reality-data-id-1",
+ "root/document/path.json",
+ );
+ expect(tilesetUrl).toEqual(
+ "https://example.com/base/path/root/document/path.json?auth=token",
+ );
+ });
+ });
});
diff --git a/packages/engine/Specs/Scene/ITwinDataSpec.js b/packages/engine/Specs/Scene/ITwinDataSpec.js
index f407483eec0..8eb4322c577 100644
--- a/packages/engine/Specs/Scene/ITwinDataSpec.js
+++ b/packages/engine/Specs/Scene/ITwinDataSpec.js
@@ -116,4 +116,109 @@ describe("ITwinData", () => {
expect(tilesetSpy.calls.mostRecent().args[1]).toEqual(tilesetOptions);
});
});
+
+ describe("createTilesetForRealityDataId", () => {
+ let getMetadataSpy;
+ let getUrlSpy;
+ let tilesetSpy;
+ beforeEach(() => {
+ getMetadataSpy = spyOn(ITwinPlatform, "getRealityDataMetadata");
+ getUrlSpy = spyOn(ITwinPlatform, "getRealityDataURL");
+ tilesetSpy = spyOn(Cesium3DTileset, "fromUrl");
+ });
+
+ it("rejects if the type is not supported", async () => {
+ await expectAsync(
+ ITwinData.createTilesetForRealityDataId(
+ "imodel-id-1",
+ "reality-data-id-1",
+ ITwinPlatform.RealityDataType.DGN,
+ "root/path.json",
+ ),
+ ).toBeRejectedWithError(RuntimeError, /type is not/);
+ });
+
+ it("does not fetch metadata if type and rootDocument are defined", async () => {
+ await ITwinData.createTilesetForRealityDataId(
+ "itwin-id-1",
+ "reality-data-id-1",
+ ITwinPlatform.RealityDataType.Cesium3DTiles,
+ "root/document/path.json",
+ );
+
+ expect(getMetadataSpy).not.toHaveBeenCalled();
+ expect(getUrlSpy).toHaveBeenCalledOnceWith(
+ "itwin-id-1",
+ "reality-data-id-1",
+ "root/document/path.json",
+ );
+ });
+
+ it("fetches metadata if type is undefined", async () => {
+ getMetadataSpy.and.resolveTo({
+ iModelId: "itwin-id-1",
+ id: "reality-data-id-1",
+ type: ITwinPlatform.RealityDataType.Cesium3DTiles,
+ rootDocument: "root/document/path.json",
+ });
+ await ITwinData.createTilesetForRealityDataId(
+ "itwin-id-1",
+ "reality-data-id-1",
+ undefined,
+ "root/document/path.json",
+ );
+
+ expect(getMetadataSpy).toHaveBeenCalledOnceWith(
+ "itwin-id-1",
+ "reality-data-id-1",
+ );
+ expect(getUrlSpy).toHaveBeenCalledOnceWith(
+ "itwin-id-1",
+ "reality-data-id-1",
+ "root/document/path.json",
+ );
+ });
+
+ it("fetches metadata if rootDocument is undefined", async () => {
+ getMetadataSpy.and.resolveTo({
+ iModelId: "itwin-id-1",
+ id: "reality-data-id-1",
+ type: ITwinPlatform.RealityDataType.Cesium3DTiles,
+ rootDocument: "root/document/path.json",
+ });
+ await ITwinData.createTilesetForRealityDataId(
+ "itwin-id-1",
+ "reality-data-id-1",
+ ITwinPlatform.RealityDataType.Cesium3DTiles,
+ undefined,
+ );
+
+ expect(getMetadataSpy).toHaveBeenCalledOnceWith(
+ "itwin-id-1",
+ "reality-data-id-1",
+ );
+ expect(getUrlSpy).toHaveBeenCalledOnceWith(
+ "itwin-id-1",
+ "reality-data-id-1",
+ "root/document/path.json",
+ );
+ });
+
+ it("creates a tileset from the constructed blob url", async () => {
+ const tilesetUrl =
+ "https://example.com/root/document/path.json?auth=token";
+ getUrlSpy.and.resolveTo(tilesetUrl);
+
+ await ITwinData.createTilesetForRealityDataId(
+ "itwin-id-1",
+ "reality-data-id-1",
+ ITwinPlatform.RealityDataType.Cesium3DTiles,
+ "root/document/path.json",
+ );
+
+ expect(tilesetSpy).toHaveBeenCalledOnceWith(tilesetUrl, {
+ maximumScreenSpaceError: 4,
+ });
+ });
+ });
});