Visualize a Proposed Building in a 3D City

In this tutorial, you’ll learn how to create a Cesium app to replace buildings in a real city with your own 3D model.

You can use this as a way to visualize the impact of a proposed building. How does it change the skyline? And what will the view look like from a specific floor or room?

We’ll cover how to:

  • Set up and deploy your Cesium app on the web
  • Add base layers of global 3D buildings, terrain, and imagery
  • Hide individual buildings and replace them with your own 3D models

This is the app you’ll build. You can toggle the proposed building and view it from multiple angles.

Before you get started

We’re going to get global satellite imagery, 3D buildings, and terrain from Cesium ion, an open platform for streaming and hosting 3D content.

Sign up for a free Cesium ion account if you don’t already have one.

Once you’re logged in:

  1. Go to your Access Tokens tab.
  2. Note the copy button next to the default token. We’ll use this token in the next step.

Step 1. Set up your Cesium app

We’re going to create our app using CesiumJS, an open source JavaScript engine. We’ll use Glitch, an online IDE, to host our app.

  1. Click here to create a new Glitch project using the basic template we put together.
  2. Click on index.html in the left-side panel to see the app’s code.
  3. Replace your_token_here with your access token from your tokens page.
  4. Run the app by clicking Show in the top and select Next to The Code.

The code in index.html so far does three things:

  • Imports the CesiumJS library. The JavaScript and CSS files are loaded in these two lines:
Copy to clipboard. Data copied clipboard.
<script src="https://cesiumjs.org/releases/1.75/Build/Cesium/Cesium.js"></script>
<link href="https://cesiumjs.org/releases/1.75/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
  • Adds an HTML container for the scene: <div id="cesiumContainer"></div>.
  • Initializes the viewer with const viewer = new Cesium.Viewer('cesiumContainer');.

You now have a basic CesiumJS app running in your browser with global satellite imagery from Cesium ion.

Configure auto-refresh

Glitch will automatically refresh the page every time the code changes. You can toggle this by clicking on the project name in the top left and unchecking this box:

Use the refresh button on top of the app window to re-run the app:

Step 2. Add Cesium OSM Buildings and Cesium World Terrain

Cesium OSM Buildings is a global base layer with over 350 million buildings derived from OpenStreetMap data. It’s served as 3D Tiles, an open standard Cesium pioneered that makes it possible to stream 3D content to any compatible client.

Let’s add these layers then move the camera to the city that our fictional new building will be placed in — Denver, Colorado.

  1. Replace your JavaScript code in index.html with the code below, keeping your access token line from before.
  2. Click and drag to move the camera. Hold CTRL while dragging to tilt.
  3. Click on any building to see its metadata.
Copy to clipboard. Data copied clipboard.
// Keep your Cesium.Ion.defaultAccessToken = 'your_token_here' line above. 
// STEP 2 CODE
// Initialize the viewer with Cesium World Terrain.
const viewer = new Cesium.Viewer('cesiumContainer', {
  terrainProvider: Cesium.createWorldTerrain()
});
// Add Cesium OSM Buildings.
const buildingsTileset = viewer.scene.primitives.add(Cesium.createOsmBuildings());
// Fly the camera to Denver, Colorado at the given longitude, latitude, and height.
viewer.camera.flyTo({
  destination: Cesium.Cartesian3.fromDegrees(-104.9965, 39.74248, 4000)
});

At this point, your complete index.html will look like this (except for your access token). In future steps, you’ll add new code below the existing code, inside the script tag.

Copy to clipboard. Data copied clipboard.
<!DOCTYPE html>
<html lang="en">
<head>
  <script src="https://cesiumjs.org/releases/1.75/Build/Cesium/Cesium.js"></script>
  <link href="https://cesiumjs.org/releases/1.75/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
  <link href="style.css" rel="stylesheet">
</head>
<body>
  <div id="cesiumContainer"></div>
  <script>
    // Get your token from https://cesium.com/ion/tokens
    Cesium.Ion.defaultAccessToken = 'your_token_here';
    
    // Keep your Cesium.Ion.defaultAccessToken = 'your_token_here' line above. 
    // STEP 2 CODE
    // Initialize the viewer with Cesium World Terrain.
    const viewer = new Cesium.Viewer('cesiumContainer', {
      terrainProvider: Cesium.createWorldTerrain()
    });
    // Add Cesium OSM Buildings.
    const buildingsTileset = viewer.scene.primitives.add(Cesium.createOsmBuildings());
    // Fly the camera to Denver, Colorado at the given longitude, latitude, and height.
    viewer.camera.flyTo({
      destination: Cesium.Cartesian3.fromDegrees(-104.9965, 39.74248, 4000)
    });
  </script>
</body>
</html>

Cesium OSM Buildings is clamped to a global high resolution 3D terrain layer, Cesium World Terrain. This makes it ideal for applications that require accurate building heights, like flood-analysis tools.

Step 3. Identify the new building area

Before we add the new building, let’s add a GeoJSON file to mark the footprint for it. This will show us which existing buildings need to be removed.

  1. Download the GeoJSON file.
  2. Drag and drop the GeoJSON file in your Cesium ion dashboard.
  3. Press Upload.
  4. After it’s done uploading, note the asset ID under the preview window.

  1. Add the code below in index.html.
    • Replace your_asset_id with your asset ID. The ID is a number, so you don't need quotes.
Copy to clipboard. Data copied clipboard.
// STEP 3 CODE
async function addBuildingGeoJSON() {
  // Load the GeoJSON file from Cesium ion.
  const geoJSONURL = await Cesium.IonResource.fromAssetId(your_asset_id);
  // Create the geometry from the GeoJSON, and clamp it to the ground.
  const geoJSON = await Cesium.GeoJsonDataSource.load(geoJSONURL, { clampToGround: true });
  // Add it to the scene.
  const dataSource = await viewer.dataSources.add(geoJSON);
  // By default, polygons in CesiumJS will be draped over all 3D content in the scene.
  // Modify the polygons so that this draping only applies to the terrain, not 3D buildings.
  for (const entity of dataSource.entities.values) {
    entity.polygon.classificationType = Cesium.ClassificationType.TERRAIN;
  }
  // Move the camera so that the polygon is in view.
  viewer.flyTo(dataSource);
}
addBuildingGeoJSON();

You’ll see the building footprint on the ground now. Zoom in with the mouse wheel or right click + drag to take a closer look.

Step 4. Hide the existing 3D buildings on the site

Now that we’ve identified where the new building will go, we can see which buildings are currently there. We’re going to use 3D Tiles Styling Language to hide them.

In the footprint above, we can see that there are six buildings on the site of our new proposed building — one large building and five much smaller ones.

  1. Add the following code. It hides all the smaller 3D buildings.
Copy to clipboard. Data copied clipboard.
// STEP 4 CODE
// Hide individual buildings in this area using 3D Tiles Styling language.
buildingsTileset.style = new Cesium.Cesium3DTileStyle({
  // Create a style rule to control each building's "show" property.
  show: {
    conditions : [
      // Any building that has this elementId will have `show = false`.
      ['${elementId} === 332469316', false],
      ['${elementId} === 332469317', false],
      ['${elementId} === 235368665', false],
      ['${elementId} === 530288180', false],
      ['${elementId} === 530288179', false],
      // If a building does not have one of these elementIds, set `show = true`.
      [true, true]
    ]
  },
  // Set the default color style for this particular 3D Tileset.
  // For any building that has a `cesium#color` property, use that color, otherwise make it white.
  color: "Boolean(${feature['cesium#color']}) ? color(${feature['cesium#color']}) : color('#ffffff')"
});
  1. Extend this code to hide the remaining 3D building.
    • Click on the building to find its elementId.
    • Add another line like: ['${elementId} === large_building_elementId', false],.

Step 5. Upload and position the new building

Let’s upload the proposed building model.

  1. Download this glTF model.
  2. Drag and drop it in your Cesium ion dashboard.
  3. Select 3D Model (tile as 3D Tiles) and press Upload.
  4. After it’s done tiling, click the Adjust Tileset Location button at the top of the asset preview window.

  1. Enter the address of the building, 1250 Cherokee Street, in the search box and click Next.
  2. Using the controls on the viewer, visually position and rotate the building to line up with the satellite imagery underneath. Your final settings should be approximately:
    • Longitude: -104.9909
    • Latitude: 39.73579
    • Height: 1577
    • Heading: -8
  3. Press Save when you're done.

Ensuring geolocation accurracy

For this tutorial, you manually positioned a new building. If a building is already georeferenced, Cesium ion will automatically place it in the right location. You can also geolocate it using the REST API to pass an the exact longitude, latitude, and height for your 3D model. Learn more with the geolocation guide.

Step 6. Add the new building to the scene

Now let’s add the new building to the scene.

  1. Get the asset ID for the building model we just geolocated, under the asset preview window.
  2. Add the code below in index.html.
    • Replace your_asset_id with your asset ID.
Copy to clipboard. Data copied clipboard.
// STEP 6 CODE
// Add the 3D Tileset you created from your Cesium ion account.
const newBuildingTileset = viewer.scene.primitives.add(
  new Cesium.Cesium3DTileset({
    url: Cesium.IonResource.fromAssetId(your_asset_id)
  })
);
// Move the camera to the new building.
viewer.flyTo(newBuildingTileset);

Step 7. Add a button to toggle the new building

Let’s add a button to toggle the display of this new building.

  1. In index.html, add the button right inside your <body> tag, above <script>.
Copy to clipboard. Data copied clipboard.
<button id="toggle-building">Toggle new building</button>
  1. Add the following CSS style tag inside the head tag.
Copy to clipboard. Data copied clipboard.
  <style type="text/css">
    #toggle-building { z-index: 1; position: fixed; top: 5px; left: 5px; }
  </style>
  1. Add the following JavaScript in index.html.
Copy to clipboard. Data copied clipboard.
// STEP 7 CODE
// Toggle the tileset's show property when the button is clicked.
document.querySelector('#toggle-building').onclick = function() {
  newBuildingTileset.show = !newBuildingTileset.show;
};

Step 8. Consider the building’s impact on its surroundings

Now you can compare the scene with and without this new building!

Denver’s panoramic mountain view is highly prized. How does this building impact the view from other locations, such as the Colorado State Capitol Building?

The impact on the view from the Colorado State Capitol Building.
To reproduce this, search for State Capitol Building, Denver, CO, USA and adjust the camera.

We can even explore how the view will change from the capitol entryway.

The impact on the view from closer to ground level.

Complete tutorial source code

Here is the complete source code for this app with your_token_here and your_asset_id placeholders.

Copy to clipboard. Data copied clipboard.
<!DOCTYPE html>
<html lang="en">
<head>
  <script src="https://cesiumjs.org/releases/1.75/Build/Cesium/Cesium.js"></script>
  <link href="https://cesiumjs.org/releases/1.75/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
  <link href="style.css" rel="stylesheet">
  <style type="text/css">
    #toggle-building { z-index: 1; position: fixed; top: 5px; left: 5px; }
  </style>
</head>
<body>
  <div id="cesiumContainer"></div>
  <button id="toggle-building">Toggle new building</button>
  
  <script>
    // Get your token from https://cesium.com/ion/tokens
    Cesium.Ion.defaultAccessToken = 'your_token_here';

    // Keep your Cesium.Ion.defaultAccessToken = 'your_token_here' line from above. 
    // STEP 2 CODE
    // Initialize the viewer with Cesium World Terrain.
    const viewer = new Cesium.Viewer('cesiumContainer', {
      terrainProvider: Cesium.createWorldTerrain()
    });
    // Add Cesium OSM Buildings.
    const buildingsTileset = viewer.scene.primitives.add(Cesium.createOsmBuildings());
    // Fly the camera to Denver, Colorado at the given longitude, latitude, and height.
    /* viewer.camera.flyTo({
      destination: Cesium.Cartesian3.fromDegrees(-104.9965, 39.74248, 4000)
    }); */

    // STEP 3 CODE
    async function addBuildingGeoJSON() {
      // Load the GeoJSON file from Cesium ion.
      const geoJSONURL = await Cesium.IonResource.fromAssetId(your_asset_id);
      // Create the geometry from the GeoJSON, and clamp it to the ground.
      const geoJSON = await Cesium.GeoJsonDataSource.load(geoJSONURL, { clampToGround: true });
      // Add it to the scene.
      const dataSource = await viewer.dataSources.add(geoJSON);
      // By default, polygons in CesiumJS will be draped over all 3D content in the scene.
      // Modify the polygons so that this draping only applies to the terrain, not 3D buildings.
      for (const entity of dataSource.entities.values) {
        entity.polygon.classificationType = Cesium.ClassificationType.TERRAIN;
      }
      // Move the camera so that the polygon is in view.
      // viewer.flyTo(dataSource);
    }
    addBuildingGeoJSON();

    // STEP 4 CODE
    // Hide individual buildings in this area using 3D Tiles Styling language.
    buildingsTileset.style = new Cesium.Cesium3DTileStyle({
      // Create a style rule to control each building's "show" property.
      show: {
        conditions : [
          // Any building that has this elementId will have `show = false`.
          ['${elementId} === 532245203', false],
          ['${elementId} === 332469316', false],
          ['${elementId} === 332469317', false],
          ['${elementId} === 235368665', false],
          ['${elementId} === 530288180', false],
          ['${elementId} === 530288179', false],
          // If a building does not have one of these elementIds, set `show = true`.
          [true, true]
        ]
      },
      // Set the default color style for this particular 3D Tileset.
      // For any building that has a `cesium#color` property, use that color, otherwise make it white.
      color: "Boolean(${feature['cesium#color']}) ? color(${feature['cesium#color']}) : color('#ffffff')"
    });

    // STEP 6 CODE
    // Add the 3D Tileset you created from your Cesium ion account.
    const newBuildingTileset = viewer.scene.primitives.add(
      new Cesium.Cesium3DTileset({
        url: Cesium.IonResource.fromAssetId(your_asset_id)
      })
    );
    // Move the camera to the new building.
    viewer.flyTo(newBuildingTileset);

    // STEP 7 CODE
    // Toggle the tileset's show property when the button is clicked.
    document.querySelector('#toggle-building').onclick = function() {
      newBuildingTileset.show = !newBuildingTileset.show;
    };
  </script>
</body>
</html>

Next steps

If you have your own building model, try using it instead of the provided sample — glTF, OBJ, and FBX are all supported. Or place the building in your own city.

See the 3D models import guide to learn more.