- Creating the Viewer
- Adding Imagery
- Adding Terrain
- Configuring the Scene
- Loading and Styling Entities
- 3D Tiles
- Camera Modes
Welcome to the Cesium community! We’re happy to have you. In order to get you developing your own web map applications as soon as possible, this tutorial will walk you through the development of a simple Cesium application from beginning to end. This tutorial will touch on many of the most important aspects of the Cesium API, but is no means comprehensive (Cesium has a lot of features!). Our goal is to introduce the fundamentals and the tools you’ll need to explore the rest of Cesium.
We’ll create a simple application for visualizing sample geocache locations in New York City. We’ll load and style multiple types of 2D and 3D data and create several camera and display options that a user can set interactively. Finally, as high-tech geocachers, we’ll load a 3D drone model to scout the geocache locations for us to take full advantage of our 3D visualization.
By the end of this tutorial, you will have a working overview of Cesium’s features and know how to configure a Cesium viewer, load datasets, create and style geometry, work with 3D Tiles, control the camera, and add mouse interactivity to your application.
Our application for interactively visualizing sample geocache locations.
Just a few setup steps before we can get to development.
- Make sure your system is Cesium compatible by visiting Hello World. No globe? See Troubleshooting.
- Install Node.js.
- Get the workshop code. Either clone or download the zip and extract the contents.
- In your console, navigate to the root
The console should tell you “Cesium development server running locally. Connect to http://localhost:8080/”. Don’t close the console! We’ll need to keep this process running.
The Application Directory
Now for a tour of our application directory! Note that this application directory was designed to be as simple as possible, and totally ignores the many varied modern JS frameworks in use today. But once you have a handle on things, feel free to experiment!
- Source : Our application code and data.
- ThirdParty : External libraries, in this case just Cesium.
- index.html : Our main html page.
- server.js : The server we’ll run our application from.
Now take a look at
index.html. This creates a
div for our Cesium widget and a few basic input elements. Observe that Cesium Widget is just an ordinary
div that can be styled and interacted with like any other
There are a few crucial lines to set this up:
First we include
Cesium.js in a script tag in the HTML head. This defines the
Cesium object, which contains the entire Cesium library.
Cesium ships with a collection of widgets that require this CSS.
In the HTML body, we create a
div for the Cesium Viewer widget.
That’s about it! The rest of the HTML in this file is for collecting user input, which we’ll use later.
For this tutorial and throughout the rest of your Cesium development career, we encourage you to rely on the following resources:
- Reference Documentation : A complete guide to the Cesium API containing many code snippets.
- Sandcastle : A live-coding environment with a large gallery of code examples.
- Tutorials : Detailed introductions to areas of Cesium development.
- Cesium Forum : A resource for asking and answering Cesium-related questions.
Any time you get stuck, odds are one of these resources will have the answers you’re looking for.
To follow along with this tutorial:
Source/App.jsin your favorite text editor and delete the contents.
- Copy into
Source/App.jsthe contents of
Source/AppSkeleton.js, which contains the commented version of the code.
- Make sure your server is still running in the
cesium-workshopdirectory, as described in Setup.
- Navigate to localhost:8080. You should see a mostly black page now.
- As the tutorial directs you, uncomment code, save
Source/App.jsand refresh the page to see your new changes reflected.
Really stuck? You can follow along in sandcastle with a simplified version of the app (no UI):
Now let’s get started!
Creating the Viewer
The basis of any Cesium application is the Viewer, an interactive 3D globe with lots of functionality right out of the box. Add the viewer to the ‘cesiumContainer’
div by uncommenting the first line.
There’s a lot included in that one line! You should see a basic globe like this:
By default, the scene handles mouse and touch input. Try exploring the globe using the default camera controls:
- Left click and drag - Pans the camera over the surface of the globe.
- Right click and drag - Zooms the camera in and out.
- Middle wheel scrolling - Also zooms the camera in and out.
- Middle click and drag - Rotates the camera around the point on the surface of the globe.
In addition to the globe itself, the Viewer comes with some helpful widgets by default, labelled in the above image.
- Geocoder : A location search tool that flies the camera to queried location. Uses Bing Maps data by default.
- Home Button : Flies the viewer back to a default view.
- Scene Mode Picker : Switches between 3D, 2D and Columbus View (CV) modes.
- Base Layer Picker : Chooses the imagery and terrain to display on the globe.
- Navigation Help Button : Displays the default camera controls.
- Animation : Controls the play speed for view animation.
- Timeline : Indicates current time and allows users to jump to a specific time using the scrubber.
- Credits Display : Displays data attributions. Almost always required!
- Fullscreen Button : Makes the Viewer fullscreen.
We can configure our viewer to include or exclude these features and more by passing in a options object as a parameter when we create it. For this application, delete that first line and configure a new viewer by uncommenting the next few lines:
This will create a viewer without selection indicators, base layer picker or scene mode picker widgets, since these will be unnecessary for our app. For the full set of Viewer options, see the Viewer documentation.
The next key element of our Cesium application is imagery. This is the set of images that tile over our virtual globe at various resolutions. To provide optimal performance, Cesium only requests and renders imagery tiles that are visible in the current view and that are at a resolution (also known as zoom level) appropriate to the camera’s distance from the globe’s surface and the globe’s maximumScreenSpaceError.
For a more visual understanding of how imagery works, check out the Cesium Inspector.
Cesium provides lots of tools for working with imagery layers, such as color adjustment and layer blending. Some code examples:
- Adding basic imagery
- Adjusting imagery colors
- Manipulating and ordering imagery layers
- Splitting imagery layers
Cesium provides support for imagery from many different providers out of the box.
Supported Imagery Formats:
- WMTS (with time dynamic imagery)
- Bing Maps
- Google Earth
- Open Street Map servers
- Single tile
By default, Cesium uses Bing Maps for imagery. Be careful, different data providers have different attribution requirements – make sure you have permission to use data from a particular provider, crediting them in the credits container if applicable. The imagery packaged with the Viewer is mostly for demo purposes. In order to use the Bing imagery set in our application, we need to acquire a Bing key of our own. Set the Bing key with a line like this (at the top of our application, before the viewer is created):
Again, different imagery providers will have different requirements for usage. Now that we have permission to use this imagery set, we can actually add the imagery layer. First, we create an ImageryProvider, passing in a data url and a few configuration options, then we add the ImageryProvider to viewer.imageryLayers.
With the above code additions, our application should look like this when you zoom in:
This is actually the same as the default imagery styling, but feel free to play with the
BingMapsStyle to see the differences.
For more information on Imagery, see our Imagery Layers Tutorial.
Cesium supports streaming and visualizing global high-resolution terrain and water effects for oceans, lakes, and rivers. Mountain peaks, valleys, and other terrain features really show the benefit of a 3D globe compared to a 2D map. Like imagery, the Cesium engine will stream terrain data from a server, only requesting and rendering tiles as needed based on the current camera position.
Here are some demos of terrain datasets and configuration options:
- ArcticDEM : High resolution arctic terrain
- PATerrain : High resolution Pennsylvania terrain
- Terrain display options : Some terrain configuration options and formats
- Terrain exaggeration : Making terrain elevation differences more dramatic
Supported Terrain Formats:
- Our own quantized-mesh format
- Google Earth Enterprise
requestVertexNormals are configuration options which tell Cesium to request extra data for water and lighting effects. By default these are set to false.
Finally, now that we have have terrain, we need just one more line to make sure objects behind the terrain are correctly occluded. Only the front-most objects will be visible.
We now have terrain and animated water. New York is pretty flat, so feel free to explore in order to see the new terrain in action. For a particularly obvious example, you can navigate to a more rugged area like the Grand Canyon or San Francisco.
For more information on terrain, see the Terrain Tutorial.
Configuring the Scene
Now just a little more setup to start our viewer in the right location and time. This involves interacting with viewer.scene, a container for all the graphical elements in our viewer.
To start, we can configure our scene to optionally enable lighting based on the sun’s position with this line.
This will make the lighting in our scene change with the time of day, such that you can see part of the globe go dark from space when the sun is out of view.
Next, before we get started with setting up our initial view, let’s go over a few basic Cesium types:
- Cartesian3 : a 3D Cartesian point – when used as a position it is relative to the center of the globe in meters using the Earth fixed-frame (ECR)
- Cartographic : a position defined by latitude/longitude (in radians) and height off the globe’s surface
- Heading Pitch Roll : A rotation (in radians) about the local axes in the East-North-Up frame. Heading is the rotation about the negative z axis. Pitch is the rotation about the negative y axis. Roll is the rotation about the positive x axis.
- Quaternion : A 3D rotation represented as 4D coordinates.
These are the basic types necessary to position and orient Cesium objects within a scene and have a number of helpful conversion methods.
Now let’s position our scene in NYC, where our data is located.
The Camera is a property of viewer.scene and controls what is currently visible. We can control the camera by setting its position and orientation directly, or by using the Cesium camera API, which is designed to flexibly specify camera position and orientation over time.
Some of the most commonly used methods are:
- Camera.setView(options) : set camera at specific position and orientation immediately
- Camera.zoomIn(amount) : move camera forward along the view vector
- Camera.zoomOut(amount) : move camera backwards along the view vector
- Camera.flyTo(options) : animates movement from current position to a new position
- Camera.lookAt(target, offset) : orient and position the camera to look at target point with given offset
- Camera.move(direction, amount) : move the camera in any direction
- Camera.rotate(axis, angle) : rotate the camera about any axis
To get an idea of what the API can do, check out these camera demos:
Now let’s try one of these methods by moving the camera to New York. Set the initial view with camera.setView(), using a Cartesian3 and a HeadingPitchRoll for position and orientation :
The camera is now positioned and oriented to look down at Manhattan, and our view parameters are saved in a object that we can pass to other camera methods.
In fact, we can use this same view to update the effect of pressing the home button. Rather than having it return us to the default view of the globe from a distance, we can override the button to bring us
to that initial view of Manhattan. We can adjust the animation by adding a few more options, then add an event listener that cancels the default flight, and calls
flyTo() our new home view:
For more on basic camera controls, check out our Camera Tutorial.
Here’s the clock API in action.
When working with specific times, Cesium uses the JulianDate type, which stores the number of days since noon on January 1, -4712 (4713 BC). For increased precision, this class stores the whole number part of the date and the seconds part of the date in separate components, and in order to be safe for arithmetic and represent leap seconds, the date is always stored in the International Atomic Time standard.
Here’s an example of how we can set up our scene time options:
This will set the rate of the scene animation, the start and end times and tell the clock to loop when it hits the end time. It also sets the timeline to the appropriate time range. Check out this clock example code to experiment with clock settings.
That’s it for our initial scene configuration! Now when you run your application, you should see the following:
Loading and Styling Entities
Now that we’ve set the stage for our application with viewer configuration, imagery and terrain, we can go ahead and add the main focus of our application, sample geocache data.
For easy visualization, Cesium supports popular vector formats GeoJson and KML, as well as our own vector format, CZML.
Regardless of the initial format, all spatial data in Cesium are represented using the Entity API, a geometry library that provides flexible visualization in a format that is efficient for Cesium to render. A Cesium Entity is a data object that can be paired with a styled graphical representation and positioned in space and time. The sandcastle gallery provides many examples of simple entities. To get up to speed on the basics of the Entity API, take a break from this application and read Visualizing Spatial Data first.
Here are examples of different entity types:
Once you’ve got a handle on what an Entity looks like, loading datasets with Cesium will be easy to understand. To read in a data file, create a DataSource appropriate to your data’s format, which will parse the data file hosted at a specified url and create an EntityCollection containing an Entity for each geospatial object in the dataset. DataSource just defines an interface – the exact kind of data source you’ll need will depend on the data format. For example, a KML uses a KmlDataSource. Here’s what it looks like:
This code reads our sample geocache points from a KML file by calling KmlDataSource.load with a few options. For a KmlDataSource, the camera and canvas options are required (necessary for working with network links). The clampToGround option enables ground clamping, a popular display option that moves entities to conform to terrain rather than just the ellipsoid surface.
If you’re not familiar with the
Promise API for working with asynchronous loads, the “asynchronous” here basically means you should do what you need to do with the data in a callback provided to
.then. In order to actually add this collection of entities to the scene, we must wait until the promise resolves, then add the KmlDataSource to viewer.datasources. Uncomment the following lines:
These newly-created Entities come with useful functionality by default. Try clicking (display infobox) and double-clicking (zoom in) on a point or neighborhood. Next we’ll work on adding custom-styling to improve the look of our app.
For KML and CZML files, declarative styling can be built into the file. However, for this application, let’s practice manually styling our entities. To do this, we’ll take a similar approach to this styling example by waiting for our datasources to load, then iterating though all the entities in a datasource collection and modifying and adding attributes. Our geocache point markers are created as Billboards by default, so to modify the appearance of any of those entities, we do this:
We can improve the appearance of our markers by adjusting their anchor points, removing the labels to reduce clutter and setting the displayDistanceCondition so that only points within a set distance from the camera are visible.
For more help with distanceDisplayCondition, see the sandcastle example.
Next, let’s improve the infobox for each of our geocache entities. The title of the info box is the entity name, and the contents are the entity description, displayed as HTML.
You’ll notice that the default descriptions aren’t very helpful. Since we’re displaying geocache locations, let’s update them to display the latitude and longitude of our points.
First, we’ll convert the entity’s position into a Cartographic, then read the latitude and longitude from the Cartographic and add it to the description in an HTML table.
On click, our geocache point entities will now display a nicely-formatted infobox with just the data we need.
Our geocache markers now should look like this:
For our geocaching application, it might also be helpful to visualize what neighborhood a particular point will fall into. Let’s try loading a GeoJson file containing polygons for each of the NYC neighborhoods. Loading a GeoJson file is ultimately very similar to the load process we just used for a KML. But in this case, we use a GeoJsonDataSource instead, and exclude the camera and canvas options. And like with the previous datasource, we need to add it to viewer.datasources to actually add data to the scene.
Since our geocache points are already looking clean, let’s style the neighborhood polygons we loaded. Just like with the billboard styling we just did, we begin by iterating through the neighborhood dataSource entities once the dataSource has loaded, this time checking that each entity’s polygon is defined:
Since we’re displaying neighborhoods, let’s rename each entity to use the neighborhood as its name. The original GeoJson file that we read in has neighborhood as a property. Cesium stores the GeoJson properties under entity.properties, so we can set the neighborhood names like this:
Finally, let’s generate a Label for each entity with a few basic styling options. To keep things neat, we can use disableDepthTestDistance to have Cesium always render the labels in front of whatever 3D object might occlude it.
However, note that a Label is always positioned at
Polygon is created with an undefined position since it has a list of constituent point positions. We can
generate a polygon position by taking the center of the polygon positions:
This gives us labelled polygons that look like this:
Although our labelled polygons are now nicely styled, our entire scene has become a bit cluttered, so let’s make create a way to hide the polygons. In general, we can hide entities by setting visibility with Entity.show. However, this only sets visibility for a single entity, and we’d like to hide or show all the neighborhood entities at once.
We can do this by adding all our neighborhood entities to a parent entity, as shown in this example or by simply using the
show property of Entity Collection.
We can then set visibility for all the child entities at once by changing the value of
Finally, let’s add a high-tech view of our nyc geocaches by adding a drone flight over the city.
Since a flight path is just a series of positions over time, we can add this data from a CZML file. CZML is a format for describing a time-dynamic graphical scene, primarily for display in a web browser running Cesium. It describes lines, points, billboards, models, and other graphical primitives, and specifies how they change with time. CZML is to Cesium what KML is to Google Earth, a standard format that allows for most Cesium features to be used via a declarative styling language (in this case a JSON schema)
Our CZML file defines an entity (visualized by default as a point) with its position defined as a series of positions at different time points. It’s a nice example of the Property system that the Entity API uses to store dynamic values.
In this case, the CZML file has Cesium display the drone flight using a PathGraphics, a property of the entity which displays its position over time. A path joins discrete samples into a continuous line to be visualized using interpolation.
We now have all the data we need to finish our nyc geocache application, all loaded and styled as entities within our scene.
Finally, let’s improve the look of our drone flight. First of all, rather then settling for a simple point, we can load a 3D model to represent our drone and attach it to the entity.
Cesium supports loading 3D models based on glTF (GL Transmission Format), a royalty-free specification for the efficient transmission and loading of 3D scenes and models by applications which minimizes file size and runtime processing. Don’t have a glTF? We provide an online converter for converting COLLADA and OBJ files to glTF.
Let’s load a drone Model with nice physically-based shading and some animation:
Now our model looks nice, but unlike the original point, the drone model has orientation, which looks strange when the drone doesn’t turn as it moves. Fortunately, Cesium provides VelocityOrientationProperty that will automatically compute an orientation based on an entity’s positions sampled forward and backwards in time:
Now our drone model will turn as expected.
There’s one more thing we can do to improve the look of our drone flight. It may not be obvious from a distance, but the drone’s path is made of linear segments that look unnatural – this is because Cesium uses linear interpolation to construct a path from sampled points by default. However, the interpolation options can be configured.
To get a smoother looking flight path, we can change the interpolation options like this:
Our team sometimes describes Cesium as being like a 3D game engine for real world data. However, working with real world data is much more difficult than working with typical video game assets since real data can be incredibly high-resolution, and require accurate visualization. Fortunately, Cesium in collaboration with the open source community has developed 3D Tiles, an open specification for streaming massive heterogeneous 3D geospatial datasets.
Using a technique conceptually similar to Cesium’s terrain and imagery streaming, 3D Tiles make it possible to view gigantic models, including buildings datasets, CAD (or BIM) models, point clouds, and photogrammetry models, which would otherwise be impossible to view interactively.
- The 3D Tiles Inspector is a debugging tool that offers a glimpse under the hood.
Here are some 3D Tiles demos showcasing different formats:
In our application, we’ll use a Cesium3DTileset to add realism to our visualization by displaying full 3D models of all the buildings in New York! Adding a tileset is easy:
Like adding an ordinary 3D model, we specify a url from which to retrive our data, then add the object to the scene. In this case, we add the tileset to scene.primitives, not scene.entities because 3D Tiles is not yet part of the Entity API. The maximumScreenSpaceError specifies how much how much detail Cesium will render for a given view, so the lower the number, the more detailed the visuals. Highly detailed visuals of course come with a performance cost, so wbe aware when changing this setting.
You may notice that the buildings are not correctly positioned at ground level. This is a common problem with 3D tilesets, and fortunately it’s easy to fix. We can adjust the position of the tileset by modifying its modelMatrix.
We can find the model’s current offset from the ground by converting the tileset’s bounding sphere into a Cartographic, then adding the desired offset and resetting the modelMatrix:
We now have over 1.1 million building models streaming into our scene!
A Cesium3DTileStyle is defined like this:
This style simply will make all the buildings in our NYC tileset white and always visible. In order to actually set the tileset to use this style, we set
We can define as many styles as we’d like. Here’s another, making the building transparent:
Applying the same style to every feature in our tileset is only scratching the surface. We can also use properties specific to each feature to determine styling. Here’s an example that colors buildings based on their height:
In order to swap between styles, we can add just a little more code to listen for HTML input:
For more examples of 3D Tiles and how to use and style them, check out the 3D Tiles sandcastle demos.
3D Tiles demos:
If you’re curious about how 3D Tilesets are generated or have some data of your own to convert, you can read more here.
Finally, let’s add some mouse interactivity. To improve the visibility of our geocache markers, we can change their styling when a user hovers over a marker to highlight it.
To achieve this, we’ll use picking, a Cesium feature that returns data from the 3D scene given a pixel position on the viewer canvas.
There are several different types of picking.
- Scene.pick : returns an object containing the primitive at the given window position.
- Scene.drillPick : returns a list of objects containing all the primitives at the given window position.
- Globe.pick : returns the intersection point of a given ray with the terrain.
Here are some examples of picking in action:
Since we want our highlight effect to trigger on hover, first we’ll need to create a mouse action handler. For this we’ll use a ScreenSpaceEventHandler, a set of handler that triggers specified functions on user input actions. ScreenSpaceEventHandler.setInputAction() listens for a type of user action – a ScreenSpaceEventType, and runs a specific function, passing in the user action as a parameter. Here, we’ll pass it a function that take movement as input:
Next let’s actually write our highlighting function. The handler will pass in a mouse movement from which we can extract a window position to use with
If the pick returns a billboard object, we know that we’re hovering over a marker. Then, using what we learned about
Entity styling, we can apply a highlight style.
This successfully triggers the highlight style change for markers. However, you’ll notice that the markers stay highlighted when we move the cursor away. We can fix that by keeping track of the last marker that was highlighted, and restoring the original styling.
Here’s the full function, with the marker highlighting and unhighlighting working:
That’s it! We’ve now successfully added a mouse movement handler and on hover behavior for our marker entities.
To show off our drone flight, let’s experiment with camera modes. We’ll keep it simple with two basic camera modes that users can toggle between.
- Free Mode : default camera controls
- Drone Mode : have the camera follow the drone through its flight at a fixed distance away
No code is necessary for free mode, since it uses the default controls. As for the drone follow mode, we can position the camera looking at the drone with an offset using the viewer’s built in entity tracking functionality. This sets the camera to be at a fixed offset from a specified entity, even as it moves. To track an entity, we simply set viewer.trackedEntity.
To switch back to the free camera mode, we can just set
viewer.trackedEntity back to undefined, then use
camera.flyTo() to return to our home view.
Here’s the camera mode function:
In order to attach this to the HTML input, we can attach this function to
change events on the appropriate elements:
Finally, entities are tracked automatically when a user double-clicks on them. We can add some handling to automatically update the UI if the user starts tracking the drone via clicking:
That’s it for our two camera modes – we can now freely switch to a drone camera view that looks like this:
The rest of the code just adds a few extra visualization options. Similar to our previous interactions with the HTML elements, we can attach listener functions to toggle shadows, and the neighborhood polygon visibility.
And since the 3D Tiles may take not load instantaneously, we can also add a loading indicator that is removed only when the tileset has loaded (and hence the promise has resolved).
Congratulations! That’s it for our app! You’ve now walked through the development of a complete Cesium application from end-to-end. Feel free to explore and experiment with the code we’ve provided here to further your Cesium education.
What’s next? Try looking at a few more tutorials and sandcastle examples for more ideas about what you can do with Cesium. And remember, Cesium is more than an API – it’s a community! Keep in touch about your Cesium projects on the forum.