Introduction
This tutorial demonstrates how to use the Mesh Export API to view an iModel as Cesium 3D Tiles in CesiumJS.
You will learn the following:
- How to set up a CesiumJS viewer
- How to use the Mesh Export API to get a 3D Tiles tileset url
- How to load the tileset in CesiumJS
To view the source code for the application built in this tutorial, see the 3d-tiles-samples GitHub repository.
Prerequisites
Ensure you meet the following requirements before starting the tutorial:
- Register a single page application to get a client ID. See step 3 "Register an Application" of the Quick Start tutorial.
- Have the iTwin and iModel IDs of iModel you would like to view. You can create an iModel from a Bentley Sample by following the instructions step 4 "Create an iModel" of the Quick Start tutorial.
- Have a Cesium Ion token. You will need to create a free Cesium Ion account, then you can obtain one by following this tutorial.
- Have Node.js (LTS version) installed on your machine.
1. Set up your application
This tutorial uses Vite and Typescript, but you can use Webpack or another build tool, and either Typescript or vanilla Javascript. We will also use this Vite guide and their vanilla-ts
template. We will go through the steps of creating the project and installing CesiumJS, but you can also view this CesiumJS quickstart guide and configuring Vite for CesiumJS guide for further details.
1.1 Set up your Vite project
To start off, run this npm create
command to initialize a new Vite project using the vanilla-ts
template. This will create a new directory with the name imodel-cesium-tutorial
and set up a basic Vite project with Typescript. Now cd
into your new directory and run npm install
.
npm create vite@latest imodel-cesium-tutorial -- --template vanilla-ts
cd imodel-cesium-tutorial
npm install
1.2 Install CesiumJS and configure its static assets
Next, install CesiumJS and the Vite static copy plugin. CesiumJS is a large library that contains many assets, such as images, that need to be copied to your build directory. The Vite static copy plugin will help you do this.
npm install cesium
npm install vite-plugin-static-copy
To configure Cesium with the plugin, create a new file at the root of your project called vite.config.ts
with these contents.
import { defineConfig } from "vite";
import { viteStaticCopy } from "vite-plugin-static-copy";
const cesiumSource = "node_modules/cesium/Build/Cesium";
// This is the base url for static files that CesiumJS needs to load
const cesiumBaseUrl = "cesiumStatic";
export default defineConfig({
define: {
// Define relative base path in Cesium for loading assets
CESIUM_BASE_URL: JSON.stringify(`/${cesiumBaseUrl}`),
},
plugins: [
// Copy Cesium Assets, Widgets, and Workers to a static directory
viteStaticCopy({
targets: [
{ src: `${cesiumSource}/ThirdParty`, dest: cesiumBaseUrl },
{ src: `${cesiumSource}/Workers`, dest: cesiumBaseUrl },
{ src: `${cesiumSource}/Assets`, dest: cesiumBaseUrl },
{ src: `${cesiumSource}/Widgets`, dest: cesiumBaseUrl },
],
}),
],
});
1.3 Start your application
Finally you can use the command npm run dev
to start your application. This command runs a script configured in package.json that simply runs vite
, which starts the Vite development server. Then navigate to http://localhost:5173
to view your application (which should just show some Vite boilerplate).
2. Create your Cesium viewer
2.1 Prepare your HTML and CSS
First, you need to have a div element in your entry point HTML file to hold your new Cesium viewer. Replace your existing index.html
file with the following code, so the viewer can be rendered in the cesiumContainer
div.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>iModel Cesium Tutorial</title>
</head>
<body>
<div id="cesiumContainer"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
Next, replace the existing CSS from the Vite template in src/style.css
with the following code, which simply maximizes the viewer to the full height and width of the browser window.
body {
margin: 0;
}
#cesiumContainer {
height: 100vh;
width: 100%;
}
2.2 Create a basic Cesium viewer
Next, create a new file .env
at the root of your project to hold your environment variables. Add your Cesium Ion token to this file in a variable called VITE_ION_TOKEN
as shown in the code snippet. All environment variables in Vite must start with VITE_
to be accessible in your code.
VITE_ION_TOKEN={your Cesium Ion token}
Finally, to put it all together, replace your existing src/main.ts
file with the following code. This code imports the relevant classes and required CSS from Cesium, gets the Ion token, and initializes a new Cesium viewer in the cesiumContainer
div.
import { Ion, Viewer } from "cesium";
import "cesium/Build/Cesium/Widgets/widgets.css";
import "./style.css";
const ionToken = import.meta.env.VITE_ION_TOKEN;
if (!ionToken) {
throw new Error("Missing required environment variables");
}
// Initialize the Cesium Viewer in the HTML element with the cesiumContainer ID
function setupViewer(): Viewer {
Ion.defaultAccessToken = ionToken;
const viewer = new Viewer("cesiumContainer");
viewer.scene.globe.show = true;
viewer.scene.debugShowFramesPerSecond = true;
return viewer;
}
async function main() {
const viewer = setupViewer();
}
void main();
You should now see a Cesium viewer with a beautiful globe!
2.3 Remove Vite template files
At this point, you can safely delete some of the files that were created when you ran the vite create
command for your project template. Delete counter.ts
and typescript.svg
from your src/
directory. At this point it should only contain main.ts
, style.css
, and vite-env.d.ts
.
3. Add iTwin authorization
To make requests to Mesh Export endpoints, you will need to provide an access token in the Authorization
header. To obtain the access token, you need to register your app with the iTwin Platform to obtain a client ID. You can follow this tutorial to learn how to obtain a client ID for your application.
3.1 Install the iTwin browser authorization package
Once you have a client ID, you can install the @itwin/browser-authorization
package with npm. You can learn more about this package and other iTwin authorization clients in their GitHub repository.
npm install @itwin/browser-authorization
3.2 Create a sign-in function
Now add the following code, also to main.ts
, to configure your authorization client and allow users to sign into the app. You also need to set the environment variable VITE_CLIENT_ID
to your app's client ID in an .env file at your project's root directory. Also, make sure you call this function from main()
.
import { BrowserAuthorizationClient } from "@itwin/browser-authorization";
const clientId = import.meta.env.VITE_CLIENT_ID;
async function signIn(): Promise<any> {
const redirectUri = window.location.origin;
const authClient = new BrowserAuthorizationClient({
authority: "https://ims.bentley.com",
clientId,
scope: "itwin-platform",
redirectUri,
responseType: "code"
});
void authClient.signInRedirect();
await authClient.handleSigninCallback();
return authClient.getAccessToken();
}
async function main() {
const viewer = setupViewer();
const accessToken = await signIn();
}
Another important aspect is the redirectUri
property of the auth client. This URI must be present in the redirect URIs setting where you created your app (in the same location where you got your client ID) on developer.bentley.com. For example, if you run your Vite development server on its default port 5173, http://localhost:5173/
must be a redirect URI in your app's settings.
Note: The authorization client is sensitive to the forward slash character
/
in your redirect URI. If you encounter an invalid redirect URI error, try adding your URI both with and without the forward slash to your app's settings.
3.3 Set the Cesium ITwinPlatform default access token
Finally, import the ITwinPlatform
class from Cesium and set the default access token to the token you get from the signIn()
function. Note that the access token returned by our signIn()
function includes the "Bearer" prefix, while Cesium's ITwinPlatform.defaultAccessToken
expects just the OAuth token with no prefix. This is why we call accessToken.split(" ")
to split the token string at the space character, and just take the second half.
import { Ion, ITwinData, ITwinPlatform, Viewer } from "cesium";
async function main() {
const viewer = setupViewer();
const accessToken = await signIn();
ITwinPlatform.defaultAccessToken = accessToken.split(" ")[1];
}
You can read more about ITwinPlatform
in its CesiumJS documentation.
Setting the Cesium ITwinPlatform.defaultAccessToken
allows you to get your iModel's 3D Tiles tileset easily using just CesiumJS, while the value stored in accessToken
is used to make direct requests to the Mesh Export API for the tileset. This tutorial will cover both of these methods.
4. Create your tileset using CesiumJS and add it to the scene
For the first, simpler method of using CesiumJS to get your tileset, you can use the ITwinData.createTilesetFromIModelId()
method to create a 3D Tiles tileset from an iModel ID. This method returns a Cesium3DTileset
object that you can add to your viewer's scene with viewer.scene.primitives.add(tileset)
. Finally, you can also use viewer.zoomTo(tileset)
to zoom the camera to the tileset.
import { Ion, ITwinData, ITwinPlatform, Viewer } from "cesium";
const iModelId = import.meta.env.VITE_IMODEL_ID;
async function main() {
const viewer = setupViewer();
const accessToken = await signIn();
ITwinPlatform.defaultAccessToken = accessToken.split(" ")[1];
const tileset = await ITwinData.createTilesetFromIModelId(iModelId);
viewer.scene.primitives.add(tileset);
if (tileset)
await viewer.zoomTo(tileset);
}
After this, you should see your iModel in your Cesium viewer! If you iModel is geolocated, it should be placed in the correct location on the globe. If not, it will appear in the ocean, at zero degrees latitude and zero longitude.
This is one way of obtaining a 3D Tiles tileset for your iModel. Next, we will cover how to obtain it using the Mesh Export API.
5. Alternatively, obtain your tileset from the Mesh Export API
5.1 Create a function to call the Mesh Export API
Add the following code to a new file iModelTiles.ts
to create a function that gets an export from the Mesh Export API.
export async function getIModel3dTilesUrl(iModelId: string, changesetId: string, imsPrefix: string, accessToken: string): Promise<URL | undefined> {
const headers = {
"Authorization": accessToken,
"Accept": "application/vnd.bentley.itwin-platform.v1+json",
"Content-Type": "application/json",
"Prefer": "return=representation"
};
let url = `https://${imsPrefix}api.bentley.com/mesh-export/?iModelId=${iModelId}&exportType=3DTILES`;
if (changesetId) {
url += `&changesetId=${changesetId}`;
}
const response = await fetch(url, { headers });
const responseJson = await response.json();
if (responseJson.error) {
throw new Error(responseJson.error);
}
// Get the first export from the response, if it exists
const exportItem = responseJson.exports.shift();
if (exportItem) {
const tilesetUrl = new URL(exportItem._links.mesh.href);
tilesetUrl.pathname = tilesetUrl.pathname + "/tileset.json";
return tilesetUrl;
}
}
This function uses the get exports endpoint to get a list of exports in the 3D Tiles format. The export exportItem
, contains a _links.mesh.href
property which is the URL to the base path in Azure Storage that contains the tileset.json and its tiles. It's necessary to add the string "tileset.json" to the URL pathname to form it correctly.
5.2 Obtain your tileset.json and add it to the scene
Now call this function in main.ts
by adding the following code.
import { Ion, Viewer, ITwinPlatform, Cesium3DTileset } from "cesium";
import { getIModel3dTilesUrl } from "./IModelTiles";
async function main() {
const viewer = setupViewer();
const accessToken = await signIn();
ITwinPlatform.defaultAccessToken = accessToken.split(" ")[1];
const tilesetUrl = await getIModel3dTilesUrl(iModelId, "", "", accessToken);
if (!tilesetUrl) {
throw new Error("Could not get tileset URL");
}
const tileset = await Cesium3DTileset.fromUrl(tilesetUrl.href);
viewer.scene.primitives.add(tileset);
if (tileset)
await viewer.zoomTo(tileset);
}
First, store the result of getIModel3dTilesUrl()
in a variable tilesetUrl
and add a check to make sure the URL is defined. Finally, use Cesium3DTileset.fromUrl()
to create a new 3D Tiles tileset from the URL. Adding it to the scene and zooming to it is the same as your previous code.
Note that this function allows you to pass in the changeset ID of the iModel tileset you want to view, as well as the IMS prefix which specifies if your iModel is in the QA or production environments. In this case we are setting both to an empty string, meaning we are simply defaulting to the latest changeset, and using the production environment.
With this method, you should see your tileset in the viewer, the same as the result from step 4!
What you've learned
In this tutorial, you learned how to set up a CesiumJS project to view an iModel using both Cesium functions and the Mesh Export API.
- You started by creating a project using Vite and Typescript.
- You then set up a basic Cesium viewer.
- You used Cesium functions to create a 3D Tiles tileset of your iModel and added it to the viewer.
- As an alternative method, you called the Mesh Export API directly to view the same tileset.
By completing this tutorial, you now have a foundational understanding of integrating your iModel into Cesium for an interactive 3D visualization! Please also check out this interactive Cesium sandcastle that also demonstrates viewing an iModel in CesiumJS using the Mesh Export API.