Skip to main content

Visualize Travel Time in CesiumJS with Photorealistic 3D Tiles from Google Maps Platform and Esri ArcGIS Location Services

By combining Esri’s geospatial analysis capabilities through ArcGIS location services, interactive capabilities of CesiumJS, and the lifelike realism of Google Maps Platform’s Photorealistic 3D Tiles, developers can easily and quickly create powerful 3D geospatial visualizations with immersive environments.

In this tutorial, we’ll build a Cesium app highlighting areas accessible via five-, ten-, and fifteen-minute drives from a selected location. You’ll learn how to:

  • Stream Photorealistic 3D Tiles in CesiumJS.
  • Query ArcGIS location services via a REST API for geospatial analysis.
  • Classify 3D Tiles with polygons and add styling.
  • Add interactivity with picking.
Travel time in San Francisco, CA, USA, queried from ArcGIS location services and visualized with Photorealistic 3D Tiles in CesiumJS. Five-, ten-, and fifteen-minute distances are shown in shades of gray and blue.

Travel time in San Francisco, CA, USA, queried from ArcGIS location services and visualized with Photorealistic 3D Tiles in CesiumJS.

1Load Photorealistic 3D Tiles as a global base layer

Google provides a worldwide dataset with high-resolution coverage spanning numerous countries and thousands of cities. The Google Maps Platform Map Tiles API streams 3D Tiles, an open standard for streaming 3D geospatial data, which allows developers to easily integrate Google's extensive dataset into a CesiumJS app, providing users the necessary context and realism required to make our travel time data come to life.

To begin, we’ll use the CesiumJS Quickstart as our base. Add the following script tags to an HTML document to load CesiumJS and the ArcGIS REST API library.

<script src="https://cesium.com/downloads/cesiumjs/releases/1.110/Build/Cesium/Cesium.js"></script>
<link
  href="https://cesium.com/downloads/cesiumjs/releases/1.110/Build/Cesium/Widgets/widgets.css"
  rel="stylesheet"
/>
<script src="https://unpkg.com/@esri/arcgis-rest-request@4.0.0/dist/bundled/request.umd.js"></script>
<script src="https://unpkg.com/@esri/arcgis-rest-routing@4.0.0/dist/bundled/routing.umd.js"></script>

After setting our Cesium ion access token, create a minimal Cesium viewer. To do so, add the following JavaScript code to a script tag, replacing YOUR_CESIUM_ACCESS_TOKEN with your Cesium ion access token.

  Cesium.Ion.defaultAccessToken = "YOUR_CESIUM_ACCESS_TOKEN";

  const viewer = new Cesium.Viewer("cesiumContainer", {
    timeline: false,
    animation: false,
    globe: false,
  });

Now, add a global base layer using the Google Maps Platform Map Tiles API as a 3D tileset:

  try {
    const tileset = await Cesium.createGooglePhotorealistic3DTileset();
    viewer.scene.primitives.add(tileset);
  } catch (error) {
    console.log(`Failed to load tileset: ${error}`);
  }
A global base layer of Photorealistic 3D Tiles in CesiumJS, focused on North America.
Information

2Query ArcGIS location services for GeoJSON representing travel time

According to the ArcGIS developer documentation, a service area, or isochrone, represents the geographical extent that can be reached by either driving or walking on a street network. The shape and size of the service area are determined by restrictions on either time or distance. The ability to calculate service areas is invaluable for a range of applications. It aids in optimizing routes, evaluating accessibility, conducting demographic analysis, and facilitating decision-making in areas such as logistics, transportation planning, and emergency response.

To calculate and retrieve service areas, we’ll use ArcGIS location services through a REST API.  We’ll query for the service area as GeoJSON data, which will contain polygons that represent areas reachable within a fifteen-minute travel time from a specified position, and visualize that result in CesiumJS.

After creating an ArcGIS location services API key, let’s create a new function, getServiceArea. In the following example, replace YOUR_ARCGIS_API_KEY with your ArcGIS API key.

  const authentication = arcgisRest.ApiKeyManager.fromKey("YOUR_ARCGIS_API_KEY");

  async function getServiceArea(cartographic) {
    const coordinates = [
      Cesium.Math.toDegrees(cartographic.longitude),
      Cesium.Math.toDegrees(cartographic.latitude),
    ];

    let geojson;
    try {
      const response = await arcgisRest.serviceArea({
        facilities: [coordinates],
        authentication,
      });

      geojson = response.saPolygons.geoJson;
    } catch (error) {
      console.log(`Failed to load service area: ${error}`);
    }

    if (!Cesium.defined(geojson)) {
      return;
    }

    let dataSource;
    try {
      dataSource = await Cesium.GeoJsonDataSource.load(geojson, {
        clampToGround: true,
      });
      viewer.dataSources.add(dataSource);
    } catch (error) {
      console.log(`Failed to load geojson: ${error}`);
    }

    if (!Cesium.defined(dataSource)) {
      return;
    }
  }

Then, let’s call the function at a location in San Francisco.

  viewer.camera.setView({
    destination: Cesium.Cartesian3.fromDegrees(-122.38329, 37.74015, 16000),
    orientation: {
      pitch: Cesium.Math.toRadians(-70.0),
    },
  });
  
  const cartesian = Cesium.Cartesian3.fromDegrees(-122.39429, 37.78988);
  getServiceArea(Cesium.Cartographic.fromCartesian(cartesian));

You should see the following result:

Service area queried from ArcGIS location services as a GeoJSON and visualized in CesiumJS classifying Photorealistic 3D Tiles.

3Style the results

To make these results more useful and visually appealing, we’ll use CesiumJS to apply styling techniques that will provide valuable insights at a glance.

There are three polygons in the GeoJSON data representing areas accessible via five-, ten-, and fifteen-minute drives. Based on the travel time metadata associated with each polygon, we will assign varying shades of blue to represent the different time intervals. This color gradient allows users to quickly identify areas with shorter or longer travel times.

To our getServiceArea function, add this block to set the polygon color based on the FromBreak property:

    const entities = dataSource.entities.values;

    for (let i = 0; i < entities.length; i++) {
      const feature = entities[i];
      feature.polygon.outline = false;

      if (feature.properties.FromBreak == 0) {
        feature.polygon.material = Cesium.Color.fromHsl(
          0.5833,
          0.8,
          0.9,
          0.5
        );
      } else if (feature.properties.FromBreak == 5) {
        feature.polygon.material = Cesium.Color.fromHsl(
          0.5833,
          0.9,
          0.7,
          0.5
        );
      } else {
        feature.polygon.material = Cesium.Color.fromHsl(
          0.5833,
          1.0,
          0.4,
          0.5
        );
      }
    }
Polygons shaded based on travel time metadata.

Next, we’ll use inverse classification to dim areas outside of the service area. This dimming effect helps users focus on the relevant information.

To the end of our getServiceArea function, add the following lines:

  const scene = viewer.scene;
  scene.invertClassification = true;
  scene.invertClassificationColor = new Cesium.Color(0.4, 0.4, 0.4, 1.0);
Inverse classification used in CesiumJS to dim Photorealistic 3D Tiles in the area outside of a polygon.

Lastly, let’s mark the position used as input. I used a maki icon, marker.svg, but you can use any image.

Create a billboard, and then set the position to the location used in Step 2.

 const marker = viewer.entities.add({
    name: "start",
    billboard: {
      verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
      heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
      image: "./marker.svg",
      disableDepthTestDistance: Number.POSITIVE_INFINITY,
      scale: 0.5,
    },
  });

  marker.position = cartesian;
Starting location for travel time analysis marked on Photorealistic 3D Tiles.

4Use picking to add interactivity

Finally, let’s add utility to our app by allowing the user to choose the position used as input for the spatial query. Scene.pickPosition can be used to get the position under the cursor on press, and we can use the result as input to the getServiceArea function we wrote in the previous steps.

  viewer.screenSpaceEventHandler.setInputAction((movement) => {
    viewer.dataSources.removeAll();
    viewer.scene.invertClassification = false;
    marker.show = false;

    const pickedPosition = viewer.scene.pickPosition(movement.position);

    if (!Cesium.defined(pickedPosition)) {
        return;
    }

    marker.position = pickedPosition;
    marker.show = true;
    viewer.scene.invertClassification = true;

    const cartographic = Cesium.Cartographic.fromCartesian(pickedPosition);
    getServiceArea(cartographic);
  }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

And there you have it! This app will work anywhere on Earth, so make sure to use the geocoder to search out your specific area.

Travel time from Cesium headquarters in Philadelphia, PA, USA, presented in shades of gray and blue.

Travel time from Cesium headquarters in Philadelphia, PA, USA.

Next steps

The example presented here is just one of many possible use cases. ArcGIS location services offer a wide range of additional data layers, mapping, and spatial analysis functions. Combined with the worldwide coverage of Google Maps Platform's Photorealistic 3D Tiles, now accessible through all of Cesium’s runtime engines, there’s more potential than ever for easy collaboration and innovation within the geospatial field.

Learn more in the developer documentation:

Finally, we encourage you to explore the full potential of these technologies. Share your creations and experiences with the wider community on Twitter or LinkedIn or the dedicated community forum. We’re excited to see what you build!

Content and code examples at cesium.com/learn are available under the Apache 2.0 license. You can use the code examples in your commercial or non-commercial applications.