Skip to content

Commit

Permalink
Merge pull request #12449 from CesiumGS/geo-features
Browse files Browse the repository at this point in the history
Add support for iTwin Geospatial Features API
  • Loading branch information
ggetz authored Jan 31, 2025
2 parents 4e6dde5 + ce96459 commit 6ffaa7e
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 9 deletions.
16 changes: 8 additions & 8 deletions Apps/Sandcastle/gallery/iTwin Feature Service.html
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,17 @@
viewer.scene.camera.flyTo(birdsEyeView);

// Load feature service geojson files
const points = await Cesium.ITwinData.createDataSourceForRealityDataId(
const points = await Cesium.ITwinData.loadGeospatialFeatures(
iTwinId,
"57b975f6-fd92-42ba-8014-79911ed606d1",
"2380dc1b-1dac-4709-aa5c-f6cb38c4e9f5",
);
const lines = await Cesium.ITwinData.createDataSourceForRealityDataId(
const lines = await Cesium.ITwinData.loadGeospatialFeatures(
iTwinId,
"1099c53f-c568-48a3-a57c-0230a6f37229",
"613d2310-4d01-43b7-bc92-873a2ca4a4a0",
);
const areas = await Cesium.ITwinData.createDataSourceForRealityDataId(
const areas = await Cesium.ITwinData.loadGeospatialFeatures(
iTwinId,
"21eaf0d0-ab90-400f-97cf-adc455b29a78",
"93e7ef51-5210-49f2-92a3-c7f6685e102f",
);

// Add some styling to the lines and points to differentiate types
Expand All @@ -86,7 +86,7 @@
Arrow_Marking: { color: Cesium.Color.YELLOW, icon: "car" },
Road_Sign: { color: Cesium.Color.ORANGE, icon: "triangle" },
};
const type = entity.properties.Type?.getValue();
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);
Expand All @@ -103,7 +103,7 @@
Turning_pocket: Cesium.Color.DEEPPINK,
Yellow_Box: Cesium.Color.GOLD,
};
const type = entity.properties.Type?.getValue();
const type = entity.properties.type?.getValue();
if (Cesium.defined(type) && Cesium.defined(lineColorsByType[type])) {
entity.polyline.material = lineColorsByType[type];
}
Expand Down
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

- Changed behavior of `DataSourceDisplay.ready` to always stay `true` once it is initially set to `true`. [#12429](https://github.com/CesiumGS/cesium/pull/12429)

#### Additions :tada:

- Add `ITwinData.loadGeospatialFeatures(iTwinId, collectionId)` function to load data from the [Geospatial Features API](https://developer.bentley.com/apis/geospatial-features/operations/get-features/) [#12449](https://github.com/CesiumGS/cesium/pull/12449)

#### Fixes :wrench:

- Fixed error when resetting `Cesium3DTileset.modelMatrix` to its initial value. [#12409](https://github.com/CesiumGS/cesium/pull/12409)
Expand Down
48 changes: 47 additions & 1 deletion packages/engine/Source/Scene/ITwinData.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import RuntimeError from "../Core/RuntimeError.js";
import Check from "../Core/Check.js";
import KmlDataSource from "../DataSources/KmlDataSource.js";
import GeoJsonDataSource from "../DataSources/GeoJsonDataSource.js";
import DeveloperError from "../Core/DeveloperError.js";

/**
* Methods for loading iTwin platform data into CesiumJS
Expand Down Expand Up @@ -154,7 +155,7 @@ ITwinData.createTilesetForRealityDataId = async function (
*
* @throws {RuntimeError} if the type of reality data is not supported by this function
*/
ITwinData.createDataSourceForRealityDataId = async function loadRealityData(
ITwinData.createDataSourceForRealityDataId = async function (
iTwinId,
realityDataId,
type,
Expand Down Expand Up @@ -205,4 +206,49 @@ ITwinData.createDataSourceForRealityDataId = async function loadRealityData(
return KmlDataSource.load(tilesetAccessUrl);
};

/**
* Load data from the Geospatial Features API as GeoJSON.
*
* @param {string} iTwinId The id of the iTwin to load data from
* @param {string} collectionId The id of the data collection to load
* @param {number} [limit=10000] number of items per page, must be between 1 and 10,000 inclusive
* @returns {Promise<GeoJsonDataSource>}
*/
ITwinData.loadGeospatialFeatures = async function (
iTwinId,
collectionId,
limit,
) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.string("iTwinId", iTwinId);
Check.typeOf.string("collectionId", collectionId);
if (defined(limit)) {
Check.typeOf.number("limit", limit);
Check.typeOf.number.lessThanOrEquals("limit", limit, 10000);
Check.typeOf.number.greaterThanOrEquals("limit", limit, 1);
}
if (!defined(ITwinPlatform.defaultAccessToken)) {
throw new DeveloperError("Must set ITwinPlatform.defaultAccessToken first");
}
//>>includeEnd('debug')

const pageLimit = limit ?? 10000;

const tilesetUrl = `${ITwinPlatform.apiEndpoint}geospatial-features/itwins/${iTwinId}/ogc/collections/${collectionId}/items`;

const resource = new Resource({
url: tilesetUrl,
headers: {
Authorization: `Bearer ${ITwinPlatform.defaultAccessToken}`,
Accept: "application/vnd.bentley.itwin-platform.v1+json",
},
queryParameters: {
limit: pageLimit,
client: "CesiumJS",
},
});

return GeoJsonDataSource.load(resource);
};

export default ITwinData;
63 changes: 63 additions & 0 deletions packages/engine/Specs/Scene/ITwinDataSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -346,4 +346,67 @@ describe("ITwinData", () => {
expect(geojsonSpy).not.toHaveBeenCalled();
});
});

describe("loadGeospatialFeatures", () => {
let geojsonSpy;
beforeEach(() => {
geojsonSpy = spyOn(GeoJsonDataSource, "load");
});

it("rejects with no iTwinId", async () => {
await expectAsync(
// @ts-expect-error
ITwinData.loadGeospatialFeatures(undefined),
).toBeRejectedWithDeveloperError(
"Expected iTwinId to be typeof string, actual typeof was undefined",
);
});

it("rejects with no collectionId", async () => {
await expectAsync(
// @ts-expect-error
ITwinData.loadGeospatialFeatures("itwin-id-1", undefined),
).toBeRejectedWithDeveloperError(
"Expected collectionId to be typeof string, actual typeof was undefined",
);
});

it("rejects with limit < 1", async () => {
await expectAsync(
ITwinData.loadGeospatialFeatures("itwin-id-1", "collection-id-1", 0),
).toBeRejectedWithDeveloperError(
"Expected limit to be greater than or equal to 1, actual value was 0",
);
});

it("rejects with limit > 10000", async () => {
await expectAsync(
ITwinData.loadGeospatialFeatures(
"itwin-id-1",
"collection-id-1",
20000,
),
).toBeRejectedWithDeveloperError(
"Expected limit to be less than or equal to 10000, actual value was 20000",
);
});

it("rejects with no default access token set", async () => {
ITwinPlatform.defaultAccessToken = undefined;
await expectAsync(
ITwinData.loadGeospatialFeatures("itwin-id-1", "collection-id-1"),
).toBeRejectedWithDeveloperError(
"Must set ITwinPlatform.defaultAccessToken first",
);
});

it("creates a GeoJsonDataSource from the constructed blob url if the type is GeoJSON", async () => {
await ITwinData.loadGeospatialFeatures("itwin-id-1", "collection-id-1");

expect(geojsonSpy).toHaveBeenCalledTimes(1);
expect(geojsonSpy.calls.mostRecent().args[0].url).toEqual(
"https://api.bentley.com/geospatial-features/itwins/itwin-id-1/ogc/collections/collection-id-1/items?limit=10000&client=CesiumJS",
);
});
});
});

0 comments on commit 6ffaa7e

Please sign in to comment.