From 8665d0f560e23402a0ca1071fc4ec1366a540eda Mon Sep 17 00:00:00 2001 From: jjspace <8007967+jjspace@users.noreply.github.com> Date: Wed, 27 Nov 2024 14:28:51 -0500 Subject: [PATCH 1/5] add support for iTwin reality geojson and kml files --- .../gallery/iTwin Feature Service.html | 126 ++++++++++++++++++ packages/engine/Source/Core/ITwinPlatform.js | 41 +----- packages/engine/Source/Scene/ITwinData.js | 70 ++++++++++ 3 files changed, 199 insertions(+), 38 deletions(-) create mode 100644 Apps/Sandcastle/gallery/iTwin Feature Service.html diff --git a/Apps/Sandcastle/gallery/iTwin Feature Service.html b/Apps/Sandcastle/gallery/iTwin Feature Service.html new file mode 100644 index 00000000000..6ec779436fe --- /dev/null +++ b/Apps/Sandcastle/gallery/iTwin Feature Service.html @@ -0,0 +1,126 @@ + + + + + + + + + iTwin Feature Service + + + + + +
+

Loading...

+
+
+
+ + + diff --git a/packages/engine/Source/Core/ITwinPlatform.js b/packages/engine/Source/Core/ITwinPlatform.js index c6e9e44ba4a..fb05dafc377 100644 --- a/packages/engine/Source/Core/ITwinPlatform.js +++ b/packages/engine/Source/Core/ITwinPlatform.js @@ -38,54 +38,19 @@ ITwinPlatform.ExportType = Object.freeze({ }); /** - * Types of Reality data + * Types of Reality data. This is a partial list of types we know we can support + * * @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", + GeoJSON: "GeoJSON", Unstructured: "Unstructured", - Other: "Other", }); /** diff --git a/packages/engine/Source/Scene/ITwinData.js b/packages/engine/Source/Scene/ITwinData.js index 1b53f27e4c9..25fad57eb0a 100644 --- a/packages/engine/Source/Scene/ITwinData.js +++ b/packages/engine/Source/Scene/ITwinData.js @@ -4,6 +4,8 @@ import Resource from "../Core/Resource.js"; import ITwinPlatform from "../Core/ITwinPlatform.js"; import RuntimeError from "../Core/RuntimeError.js"; import Check from "../Core/Check.js"; +import KmlDataSource from "../DataSources/KmlDataSource.js"; +import GeoJsonDataSource from "../DataSources/GeoJsonDataSource.js"; /** * Methods for loading iTwin platform data into CesiumJS @@ -86,6 +88,8 @@ ITwinData.createTilesetFromIModelId = async function (iModelId, options) { * @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} + * + * @throws {RuntimeError} if the type of reality data is not supported by this function */ ITwinData.createTilesetForRealityDataId = async function ( iTwinId, @@ -135,4 +139,70 @@ ITwinData.createTilesetForRealityDataId = async function ( }); }; +/** + * Create a data source of the correct type for the specified reality data id. + * This function only works for KML and GeoJSON type data. + * + * 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. + * + * @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} + * + * @throws {RuntimeError} if the type of reality data is not supported by this function + */ +ITwinData.createDataSourceForRealityDataId = async function loadRealityData( + 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.KML, + ITwinPlatform.RealityDataType.GeoJSON, + ]; + + if (!supportedRealityDataTypes.includes(type)) { + throw new RuntimeError( + `Reality data type is not a data source type: ${type}`, + ); + } + + const tilesetAccessUrl = await ITwinPlatform.getRealityDataURL( + iTwinId, + realityDataId, + rootDocument, + ); + + if (type === ITwinPlatform.RealityDataType.GeoJSON) { + return GeoJsonDataSource.load(tilesetAccessUrl); + } + + // If we get here it's guaranteed to be a KML type + return KmlDataSource.load(tilesetAccessUrl); +}; + export default ITwinData; From 19fdd8387de03abbc3a171295b80c02c98ecec63 Mon Sep 17 00:00:00 2001 From: jjspace <8007967+jjspace@users.noreply.github.com> Date: Wed, 27 Nov 2024 14:57:12 -0500 Subject: [PATCH 2/5] fix and add tests --- packages/engine/Specs/Scene/ITwinDataSpec.js | 127 ++++++++++++++++++- 1 file changed, 126 insertions(+), 1 deletion(-) diff --git a/packages/engine/Specs/Scene/ITwinDataSpec.js b/packages/engine/Specs/Scene/ITwinDataSpec.js index 8eb4322c577..25cb98cd2f3 100644 --- a/packages/engine/Specs/Scene/ITwinDataSpec.js +++ b/packages/engine/Specs/Scene/ITwinDataSpec.js @@ -3,6 +3,8 @@ import { RuntimeError, Cesium3DTileset, ITwinData, + GeoJsonDataSource, + KmlDataSource, } from "../../index.js"; function createMockExport( @@ -132,7 +134,7 @@ describe("ITwinData", () => { ITwinData.createTilesetForRealityDataId( "imodel-id-1", "reality-data-id-1", - ITwinPlatform.RealityDataType.DGN, + "DGN", "root/path.json", ), ).toBeRejectedWithError(RuntimeError, /type is not/); @@ -221,4 +223,127 @@ describe("ITwinData", () => { }); }); }); + + describe("createDataSourceForRealityDataId", () => { + let getMetadataSpy; + let getUrlSpy; + let geojsonSpy; + let kmlSpy; + beforeEach(() => { + getMetadataSpy = spyOn(ITwinPlatform, "getRealityDataMetadata"); + getUrlSpy = spyOn(ITwinPlatform, "getRealityDataURL"); + geojsonSpy = spyOn(GeoJsonDataSource, "load"); + kmlSpy = spyOn(KmlDataSource, "load"); + }); + + it("rejects if the type is not supported", async () => { + await expectAsync( + ITwinData.createDataSourceForRealityDataId( + "imodel-id-1", + "reality-data-id-1", + "DGN", + "root/path.json", + ), + ).toBeRejectedWithError(RuntimeError, /type is not/); + }); + + it("does not fetch metadata if type and rootDocument are defined", async () => { + await ITwinData.createDataSourceForRealityDataId( + "itwin-id-1", + "reality-data-id-1", + ITwinPlatform.RealityDataType.GeoJSON, + "root/document/path.json", + ); + + expect(getMetadataSpy).not.toHaveBeenCalled(); + expect(getUrlSpy).toHaveBeenCalledOnceWith( + "itwin-id-1", + "reality-data-id-1", + "root/document/path.json", + ); + expect(geojsonSpy).toHaveBeenCalled(); + }); + + it("fetches metadata if type is undefined", async () => { + getMetadataSpy.and.resolveTo({ + iModelId: "itwin-id-1", + id: "reality-data-id-1", + type: ITwinPlatform.RealityDataType.GeoJSON, + rootDocument: "root/document/path.json", + }); + await ITwinData.createDataSourceForRealityDataId( + "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.GeoJSON, + rootDocument: "root/document/path.json", + }); + await ITwinData.createDataSourceForRealityDataId( + "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 GeoJsonDataSource from the constructed blob url if the type is GeoJSON", async () => { + const tilesetUrl = + "https://example.com/root/document/path.json?auth=token"; + getUrlSpy.and.resolveTo(tilesetUrl); + + await ITwinData.createDataSourceForRealityDataId( + "itwin-id-1", + "reality-data-id-1", + ITwinPlatform.RealityDataType.GeoJSON, + "root/document/path.json", + ); + + expect(geojsonSpy).toHaveBeenCalledOnceWith(tilesetUrl); + expect(kmlSpy).not.toHaveBeenCalled(); + }); + + it("creates a KmlDataSource from the constructed blob url if the type is KML", async () => { + const tilesetUrl = + "https://example.com/root/document/path.json?auth=token"; + getUrlSpy.and.resolveTo(tilesetUrl); + + await ITwinData.createDataSourceForRealityDataId( + "itwin-id-1", + "reality-data-id-1", + ITwinPlatform.RealityDataType.KML, + "root/document/path.json", + ); + + expect(kmlSpy).toHaveBeenCalledOnceWith(tilesetUrl); + expect(geojsonSpy).not.toHaveBeenCalled(); + }); + }); }); From b97ce3093fbb0b3e2e8c636b608ab692f71ff220 Mon Sep 17 00:00:00 2001 From: jjspace <8007967+jjspace@users.noreply.github.com> Date: Mon, 2 Dec 2024 11:56:06 -0500 Subject: [PATCH 3/5] update changes --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 6c6aca2d206..61808466ebd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ ##### Additions :tada: - Added an integration with the [iTwin Platform](https://developer.bentley.com/) to load iModels as 3D Tiles. Use `ITwinPlatform.defaultAccessToken` to set the access token. Use `ITwinData.createTilesetFromIModelId(iModelId)` to load the iModel as a `Cesium3DTileset`. [#12289](https://github.com/CesiumGS/cesium/pull/12289) +- Added an integration with the [iTwin Platform](https://developer.bentley.com/) to load Reality Data terrain meshes and GeoJSON. Use `ITwinPlatform.defaultAccessToken` to set the access token. Then use `ITwinData.createTilesetForRealityDataId(iTwinId, dataId)` to load terrain meshes as a `Cesium3DTileset` or `ITwinData.createDataSourceForRealityDataId(iTwinId, dataId)` to load GeoJSON or KML files as data sources. [#12344](https://github.com/CesiumGS/cesium/pull/12344) - Added `getSample` to `SampledProperty` to get the time of samples. [#12253](https://github.com/CesiumGS/cesium/pull/12253) - Added `Entity.trackingReferenceFrame` property to allow tracking entities in various reference frames. [#12194](https://github.com/CesiumGS/cesium/pull/12194), [#12314](https://github.com/CesiumGS/cesium/pull/12314) - `TrackingReferenceFrame.AUTODETECT` (default): uses either VVLH or ENU dependeding on entity's dynamic. Use `TrackingReferenceFrame.ENU` if your camera orientation flips abruptly from time to time. From 6a36391ac7fca683ba794580f2a48bed3e327fe9 Mon Sep 17 00:00:00 2001 From: jjspace <8007967+jjspace@users.noreply.github.com> Date: Tue, 3 Dec 2024 16:07:01 -0500 Subject: [PATCH 4/5] update sandcastle with new itwin and add styling --- .vscode/cspell.json | 1 + .../gallery/iTwin Feature Service.html | 113 +++++++++++++----- 2 files changed, 82 insertions(+), 32 deletions(-) diff --git a/.vscode/cspell.json b/.vscode/cspell.json index 4f3c6c7cb98..e247f0c4a28 100644 --- a/.vscode/cspell.json +++ b/.vscode/cspell.json @@ -62,6 +62,7 @@ "iife", "lerp", "Lilli", + "maki", "MAXAR", "minifiers", "mipmapped", diff --git a/Apps/Sandcastle/gallery/iTwin Feature Service.html b/Apps/Sandcastle/gallery/iTwin Feature Service.html index 6ec779436fe..f7c922661ca 100644 --- a/Apps/Sandcastle/gallery/iTwin Feature Service.html +++ b/Apps/Sandcastle/gallery/iTwin Feature Service.html @@ -34,37 +34,98 @@ Cesium.ITwinPlatform.defaultAccessToken = token; - const iTwinId = "535a24a3-9b29-4e23-bb5d-9cedb524c743"; + const iTwinId = "04ba725f-f3c0-4f30-8014-a4488cbd612d"; const viewer = new Cesium.Viewer("cesiumContainer"); + const birdsEyeView = { + destination: new Cesium.Cartesian3( + -1525359.4318772827, + 6191643.528984093, + 148851.5321709012, + ), + orientation: new Cesium.HeadingPitchRoll( + 0.16657338935967037, + -0.7943050121851765, + 6.283180723449992, + ), + duration: 0, + easingFunction: Cesium.EasingFunction.LINEAR_NONE, + }; + viewer.scene.camera.flyTo(birdsEyeView); - const featureServiceBaseUrl = "https://featureservice-eus.bentley.com"; - const proxyUrl = "http://localhost:3000/proxy"; - + // Load feature service geojson files const points = await Cesium.ITwinData.createDataSourceForRealityDataId( iTwinId, - "60976bd9-3176-4017-974a-4fcea76346db", + "57b975f6-fd92-42ba-8014-79911ed606d1", ); const lines = await Cesium.ITwinData.createDataSourceForRealityDataId( iTwinId, - "5af22b93-cf7e-4879-9305-4c6bdda7987f", + "1099c53f-c568-48a3-a57c-0230a6f37229", ); const areas = await Cesium.ITwinData.createDataSourceForRealityDataId( iTwinId, - "ebec69b5-0b5f-49d8-9081-e29bcd517f6b", + "21eaf0d0-ab90-400f-97cf-adc455b29a78", ); + + // Add some styling to the lines and points to differentiate types + const pinBuilder = new Cesium.PinBuilder(); + points.entities.values.forEach(async (entity) => { + const styleByType = { + Tree: { color: Cesium.Color.GREEN, icon: "park2" }, + Lamp_post: { color: Cesium.Color.WHITE, icon: "lighthouse" }, + Traffic_light: { color: Cesium.Color.CRIMSON, icon: "circle-stroked" }, + Arrow_Marking: { color: Cesium.Color.YELLOW, icon: "car" }, + Road_Sign: { color: Cesium.Color.ORANGE, icon: "triangle" }, + }; + const type = entity.properties.Type?.getValue(); + if (Cesium.defined(type) && Cesium.defined(styleByType[type])) { + const { color, icon } = styleByType[type]; + const canvas = await pinBuilder.fromMakiIconId(icon, color, 48); + entity.billboard.image = canvas.toDataURL(); + entity.billboard.verticalOrigin = Cesium.VerticalOrigin.BOTTOM; + } + }); + lines.entities.values.forEach((entity) => { + const lineColorsByType = { + Contours: Cesium.Color.CRIMSON, + Lane_Marking: Cesium.Color.WHITE, + Kerb: Cesium.Color.BLUEVIOLET, + Chevron_marking: Cesium.Color.DARKORANGE, + Turning_pocket: Cesium.Color.DEEPPINK, + Yellow_Box: Cesium.Color.GOLD, + }; + const type = entity.properties.Type?.getValue(); + if (Cesium.defined(type) && Cesium.defined(lineColorsByType[type])) { + entity.polyline.material = lineColorsByType[type]; + } + }); + + // add the geojsons to the viewer viewer.dataSources.add(points); viewer.dataSources.add(lines); viewer.dataSources.add(areas); - // Create tileset of the reality data mesh - // TODO: swap this out with a different mesh - const realityMeshId = "85897090-3bcc-470b-bec7-20bb639cc1b9"; + // Create tileset of the reality data mesh and pointcloud + const realityMeshId = "62e4432d-621d-489a-87ff-1fc56a2b5369"; const realityMesh = await Cesium.ITwinData.createTilesetForRealityDataId( iTwinId, realityMeshId, ); viewer.scene.primitives.add(realityMesh); + const pointcloudId = "ebf2ee74-f0de-4cd6-a311-19a169c55fdc"; + const pointcloud = await Cesium.ITwinData.createTilesetForRealityDataId( + iTwinId, + pointcloudId, + ); + // increase the size of the pointcloud points and turn on attenuation to + // make them more visible in the viewer + pointcloud.maximumScreenSpaceError = 1; + pointcloud.pointCloudShading.attenuation = true; + pointcloud.style = new Cesium.Cesium3DTileStyle({ + pointSize: 5.0, + }); + pointcloud.show = false; + viewer.scene.primitives.add(pointcloud); Sandcastle.addToolbarButton( "Toggle Points", @@ -86,31 +147,19 @@ () => (realityMesh.show = !realityMesh.show), "layers", ); + Sandcastle.addToolbarButton( + "Toggle Pointcloud", + () => (pointcloud.show = !pointcloud.show), + "layers", + ); - Sandcastle.addToolbarButton("Zoom to Lines", () => { - lines.show = true; - viewer.zoomTo(lines); + Sandcastle.addToolbarButton("Birdseye View", () => { + viewer.scene.camera.flyTo(birdsEyeView); }); - Sandcastle.addToolbarButton("Zoom to Reality Mesh", () => { - realityMesh.show = true; - viewer.zoomTo(realityMesh); + Sandcastle.addToolbarButton("Zoom to Pointcloud", () => { + pointcloud.show = true; + viewer.zoomTo(pointcloud); }); - - const birdsEyeView = { - destination: new Cesium.Cartesian3( - -1525452.5685833949, - 6191771.429542403, - 148747.35086195532, - ), - orientation: new Cesium.HeadingPitchRoll( - 3.552713678800501e-15, - -0.7854791130671286, - 6.283185307179583, - ), - duration: 0, - easingFunction: Cesium.EasingFunction.LINEAR_NONE, - }; - viewer.scene.camera.flyTo(birdsEyeView); //Sandcastle_End Sandcastle.finishedLoading(); }; From b27251bf59e107f540092f855a8af7a82107a716 Mon Sep 17 00:00:00 2001 From: jjspace <8007967+jjspace@users.noreply.github.com> Date: Wed, 4 Dec 2024 12:34:28 -0500 Subject: [PATCH 5/5] adjust viewer in sandcastle --- .../gallery/iTwin Feature Service.html | 13 +++++++++++-- .../gallery/iTwin Feature Service.jpg | Bin 0 -> 14590 bytes 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 Apps/Sandcastle/gallery/iTwin Feature Service.jpg diff --git a/Apps/Sandcastle/gallery/iTwin Feature Service.html b/Apps/Sandcastle/gallery/iTwin Feature Service.html index f7c922661ca..d12f117ec3d 100644 --- a/Apps/Sandcastle/gallery/iTwin Feature Service.html +++ b/Apps/Sandcastle/gallery/iTwin Feature Service.html @@ -36,7 +36,16 @@ const iTwinId = "04ba725f-f3c0-4f30-8014-a4488cbd612d"; - const viewer = new Cesium.Viewer("cesiumContainer"); + const viewer = new Cesium.Viewer("cesiumContainer", { + geocoder: false, + sceneModePicker: false, + homeButton: false, + timeline: false, + animation: false, + }); + viewer.baseLayerPicker.viewModel.selectedImagery = + viewer.baseLayerPicker.viewModel.imageryProviderViewModels[2]; + const birdsEyeView = { destination: new Cesium.Cartesian3( -1525359.4318772827, @@ -88,7 +97,7 @@ lines.entities.values.forEach((entity) => { const lineColorsByType = { Contours: Cesium.Color.CRIMSON, - Lane_Marking: Cesium.Color.WHITE, + Lane_Marking: Cesium.Color.CYAN, Kerb: Cesium.Color.BLUEVIOLET, Chevron_marking: Cesium.Color.DARKORANGE, Turning_pocket: Cesium.Color.DEEPPINK, diff --git a/Apps/Sandcastle/gallery/iTwin Feature Service.jpg b/Apps/Sandcastle/gallery/iTwin Feature Service.jpg new file mode 100644 index 0000000000000000000000000000000000000000..899bd961ab297c6e003835e8aad40b841d14f87e GIT binary patch literal 14590 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 literal 0 HcmV?d00001