Learn how to display attributes of 3D objects in a pop-up when clicked.
A pop-up, also known as a "popup", is a visual element that displays information about an object when it is clicked. You typically style and configure a pop-up using HTML and CSS for each layer in a map. Pop-ups can display attribute values, calculated values, or rich content such as images, charts, or videos.
In this tutorial, you customize the interactive pop-up created by CesiumJS for the San Francisco Buildings 3D object layer. When a building is clicked, a pop-up is displayed containing the name of the building and its object ID.
Prerequisites
An ArcGIS Location Platform or ArcGIS Online account.
Steps
Get the starter app
Select a type of authentication below and follow the steps to create a new application.
Set up authentication
Create developer credentials in your portal for the type of authentication you selected.
Set developer credentials
Use the API key or OAuth developer credentials created in the previous step in your application.
Get a Cesium ion access token
All Cesium applications must use an access token provided through Cesium ion. This token allows you to access assets such as Cesium World Terrain in your application.
-
Go to your Cesium ion dashboard to generate an access token. Copy the key to your clipboard.
-
Create a
cesium
variable and replaceAccess Token YOUR
with the access token you copied from the Cesium ion dashboard._CESIUM _ACCESS _TOKEN <script> /* Use for API key authentication */ const accessToken = "YOUR_ACCESS_TOKEN"; // or /* Use for user authentication */ // const session = await arcgisRest.ArcGISIdentityManager.beginOAuth2({ // clientId: "YOUR_CLIENT_ID", // Your client ID from OAuth credentials // redirectUri: "YOUR_REDIRECT_URI", // The redirect URL registered in your OAuth credentials // portal: "YOUR_PORTAL_URL" // Your portal URL // }) // const accessToken = session.token; Cesium.ArcGisMapService.defaultAccessToken = accessToken; const cesiumAccessToken = "YOUR_CESIUM_ACCESS_TOKEN"; </script>
-
Configure
Cesium.
with the Cesium access token to validate the application.Ion.default Access Token <script> /* Use for API key authentication */ const accessToken = "YOUR_ACCESS_TOKEN"; // or /* Use for user authentication */ // const session = await arcgisRest.ArcGISIdentityManager.beginOAuth2({ // clientId: "YOUR_CLIENT_ID", // Your client ID from OAuth credentials // redirectUri: "YOUR_REDIRECT_URI", // The redirect URL registered in your OAuth credentials // portal: "YOUR_PORTAL_URL" // Your portal URL // }) // const accessToken = session.token; Cesium.ArcGisMapService.defaultAccessToken = accessToken; const cesiumAccessToken = "YOUR_CESIUM_ACCESS_TOKEN"; Cesium.Ion.defaultAccessToken = cesiumAccessToken; </script>
Update the camera position
-
Update the camera position to
-122.43305, 37.77655, 500
to focus on San Francisco, California.viewer.camera.setView({ destination: Cesium.Cartesian3.fromDegrees(-122.43305, 37.77655, 500), orientation: { heading: Cesium.Math.toRadians(60), pitch: Cesium.Math.toRadians(-15.0), } });
Add a terrain provider
To ensure that your 3D objects sit properly on top of the terrain, you need to use a geoid service. This service will allow the elevation of the 3D objects to align with the elevation values of Cesium World Terrain.
-
Initialize a terrain provider called
geoid
that references the Earth Gravitational Model EGM2008. This provider will allow for geoid conversion between the gravity-based 3D object layer and the ellipsoidal-based Cesium World Terrain.Service const geoidService = await Cesium.ArcGISTiledElevationTerrainProvider.fromUrl("https://tiles.arcgis.com/tiles/GVgbJbqm8hXASVYi/arcgis/rest/services/EGM2008/ImageServer"); viewer.creditDisplay.addStaticCredit(new Cesium.Credit("National Geospatial-Intelligence Agency (NGA)", false));
-
Add the data attribution for the elevation layer source.
- Go to the EGM2008 item.
- Scroll down to the Credits (Attribution) section and copy its value.
- Create an
attribution
property and paste the attribution value from the item.const geoidService = await Cesium.ArcGISTiledElevationTerrainProvider.fromUrl("https://tiles.arcgis.com/tiles/GVgbJbqm8hXASVYi/arcgis/rest/services/EGM2008/ImageServer"); viewer.creditDisplay.addStaticCredit(new Cesium.Credit("National Geospatial-Intelligence Agency (NGA)", false));
Add 3D objects
-
Reference the service URL of the San Francisco 3D Objects scene layer.
const i3sLayer = "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/SanFrancisco_Bldgs/SceneServer";
-
Create an
I3
by calling theS Data Provider from
method. Pass theUrl geoid
you created and add it to the viewer.Service const i3sLayer = "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/SanFrancisco_Bldgs/SceneServer"; const i3sProvider = await Cesium.I3SDataProvider.fromUrl(i3sLayer, { geoidTiledTerrainProvider: geoidService, token: accessToken, }) viewer.scene.primitives.add(i3sProvider);
-
Add the data attribution for the scene layer source.
- Go to the San Francisco 3D Objects item.
- Scroll down to the Credits (Attribution) section and copy its value.
- Create an
attribution
property and paste the attribution value from the item.const i3sLayer = "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/SanFrancisco_Bldgs/SceneServer"; const i3sProvider = await Cesium.I3SDataProvider.fromUrl(i3sLayer, { geoidTiledTerrainProvider: geoidService, token: accessToken, }) viewer.scene.primitives.add(i3sProvider); // Attribution text retrieved from https://www.arcgis.com/home/item.html?id=d3344ba99c3f4efaa909ccfbcc052ed5 viewer.creditDisplay.addStaticCredit(new Cesium.Credit("Precision Light Works (PLW)", false));
Get feature attributes
-
Create a click handler to detect user clicks on the viewer. Get the clicked feature using
viewer.scene.pick()
, and get the position of the click usingviewer.scene.pick
.Position() // An entity object which will hold info about the currently selected feature for infobox display const selectedEntity = new Cesium.Entity(); // Show metadata in the InfoBox. viewer.screenSpaceEventHandler.setInputAction(function onLeftClick(movement) { // Pick a new feature const pickedFeature = viewer.scene.pick(movement.position); if (!Cesium.defined(pickedFeature)) { return; } const pickedPosition = viewer.scene.pickPosition(movement.position); }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
-
Create a helper function
is
that checks if a property is aName Property name
field. You will use this helper function when displaying information of a clicked feature in a popup.function isNameProperty(propertyName) { const name = propertyName.toLowerCase(); if ( name.localeCompare("name") === 0 || name.localeCompare("objname") === 0 ) { return true; } return false; }
-
Inside the click handler, use
I3
to load the properties of the closest I3S node. Get the exact geometry that was clicked usingS Node.load Fields() I3
.S Geometry.get Closest Point Index On Triangle // An entity object which will hold info about the currently selected feature for infobox display const selectedEntity = new Cesium.Entity(); // Show metadata in the InfoBox. viewer.screenSpaceEventHandler.setInputAction(function onLeftClick(movement) { // Pick a new feature const pickedFeature = viewer.scene.pick(movement.position); if (!Cesium.defined(pickedFeature)) { return; } const pickedPosition = viewer.scene.pickPosition(movement.position); if ( Cesium.defined(pickedFeature.content) && Cesium.defined(pickedFeature.content.tile.i3sNode) ) { const i3sNode = pickedFeature.content.tile.i3sNode; i3sNode.loadFields().then(function () { const geometry = i3sNode.geometryData[0]; if (pickedPosition) { const location = geometry.getClosestPointIndexOnTriangle( pickedPosition.x, pickedPosition.y, pickedPosition.z ); let description = "No attributes"; let name; if (!Cesium.defined(name)) { name = "unknown"; } selectedEntity.name = name; selectedEntity.description = description; viewer.selectedEntity = selectedEntity; } }); } }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
-
Access the properties of the clicked geometry. Display them in an
Info
.Box if ( Cesium.defined(pickedFeature.content) && Cesium.defined(pickedFeature.content.tile.i3sNode) ) { const i3sNode = pickedFeature.content.tile.i3sNode; i3sNode.loadFields().then(function () { const geometry = i3sNode.geometryData[0]; if (pickedPosition) { const location = geometry.getClosestPointIndexOnTriangle( pickedPosition.x, pickedPosition.y, pickedPosition.z ); let description = "No attributes"; let name; if (location.index !== -1 && geometry.customAttributes.featureIndex) { const featureIndex = geometry.customAttributes.featureIndex[location.index]; if (Object.keys(i3sNode.fields).length > 0) { description = '<table class="cesium-infoBox-defaultTable"><tbody>'; for (const fieldName in i3sNode.fields) { if (i3sNode.fields.hasOwnProperty(fieldName)) { const field = i3sNode.fields[fieldName]; description += `<tr><th>${field.name}</th><td>`; description += `${field.values[featureIndex]}</td></tr>`; console.log( `${field.name}: ${field.values[featureIndex]}` ); if ( !Cesium.defined(name) && isNameProperty(field.name) ) { name = field.values[featureIndex]; } } } description += `</tbody></table>`; } } if (!Cesium.defined(name)) { name = "unknown"; } selectedEntity.name = name; selectedEntity.description = description; viewer.selectedEntity = selectedEntity; } }); }
Run the app
Run the app.
When you click on a feature, a popup should appear displaying the attribute information of the selected feature.What's next?
Learn how to use additional ArcGIS location services in these tutorials: