Skip to content

Commit

Permalink
Merge pull request #12334 from CesiumGS/itwin-reality-data
Browse files Browse the repository at this point in the history
iTwin Reality Data integration
  • Loading branch information
ggetz authored Nov 26, 2024
2 parents 00d0be4 + b0f8821 commit 31fdab5
Show file tree
Hide file tree
Showing 6 changed files with 691 additions and 19 deletions.
22 changes: 16 additions & 6 deletions Apps/Sandcastle/gallery/iTwin Demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -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",
);

Expand Down
Binary file modified Apps/Sandcastle/gallery/iTwin Demo.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
201 changes: 201 additions & 0 deletions packages/engine/Source/Core/ITwinPlatform.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 <code>itwin-platform</code> scope.
*
Expand Down Expand Up @@ -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<RealityDataRepresentation>}
*/
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 <code>return=representation</code>
* 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<string>}
*/
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;
64 changes: 64 additions & 0 deletions packages/engine/Source/Scene/ITwinData.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 <code>type</code> or <code>rootDocument</code> 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<Cesium3DTileset>}
*/
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;
Loading

0 comments on commit 31fdab5

Please sign in to comment.