Welcome to the Cesium community! We are happy to have you join us. In order to get you on your way to developing your own web map applications, this tutorial will walk you through the development of a simple but wide-reaching Cesium application from beginning to end. This tutorial will touch on many of the most important aspects of the CesiumJS API, but is no means comprehensive (CesiumJS 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 and take full advantage of our 3D visualization.
By the end of this tutorial, you will have a working overview of Cesium’s features including configuring a Cesium viewer, loading datasets, creating and styling geometry, working with 3D Tiles, controlling the camera, and adding 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 Cesium Viewer.
- 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 show the following.
Don’t close the console! We’ll need to keep this process running.
Next, navigate to
localhost:8080 in your browser. You should see our workshop application up and running.
The app directory
Within the application directory, we should see the following directories and files. Note that this application directory was designed to be as simple as possible and only includes the CesiumJS library.
Source/: Our application code and data.
ThirdParty/: External libraries, in this case just the CesiumJS library.
index.html: Our main html page, requiring our app code and containing the app structure.
server.js: The simple 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 CesiumJS library.
Optionally, you can include individual modules from the Cesium source
ThirdParty/Cesium/Source/. This is preferred for a production application as it will reduce the overall size of the deployed application. Here, we include the entire module for flexibility and easy experimentation with the API.
Structure the HTML
In the HTML body, we create a new
div for the Cesium Viewer widget.
Let’s add some styling to our app’s html elements. Here, we’ve create an
index.css file and added it within the
<head> element. This will allow us to style our own html, as well as any html overlays within our Cesium Viewer.
Cesium ships with a collection of widgets that require this CSS. We’ll include that before our app-specific CSS.
After, we have some sample css styling on our html div elements. Additional styling can be added to override the default Cesium CSS.
To follow along with this tutorial:
Source/App.jsin your favorite text editor and delete the contents.
- Copy the contents of
- Make sure your server is still running in the
cesium-workshopdirectory, as described in Setup.
- Open your favorite web browser and navigate to localhost:8080. Most of us use Chrome, but Cesium runs in all modern web browsers. 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.
Create the viewer and attach it to the div with id
"cesiumContainer" with the following line.
There is 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.
Geocoder: A location search tool that flies the camera to queried location. Uses Bing Maps data by default.
HomeButton: Flies the viewer back to a default view.
SceneModePicker: Switches between 3D, 2D and Columbus View (CV) modes.
BaseLayerPicker: Chooses the imagery and terrain to display on the globe.
NavigationHelpButton: Displays the default camera controls.
Animation: Controls the play speed for view animation.
CreditsDisplay: Displays data attributions. Almost always required!
Timeline: Indicates current time and allows users to jump to a specific time using the scrubber.
FullscreenButton: 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
Cesium ion is a platform for tiling and hosting 3D geospatial data which you can then add to your CesiumJS app. Here, we’ll be using Sentinal-2 imagery and Cesium World Terrain, both of which are hosted on ion.
To gain access to these and other terrain and imagery assets, we’ll first need to get an access token by signing up for a free Cesium ion account.
Go to https://cesium.com/ion/ and create a new account or sign in using a previous account.
Click on “Access Tokens” to navigate to the Access Tokens page.
Find your “Default” access token and copy the contents.
Add the following line before the block creating the Cesium Viewer, replacing the access token with your own.
This will give your app access to all of your assets in Cesium ion.
For more information on uploading and processing your own data, take a look at Getting Started with Cesium ion.
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. Depending on the camera’s orientation and distance from the globe’s surface, Cesium will requests and render imagery tiles at different levels of detail or “zoom level”.
Multiple imagery layers can be added, removed, ordered, and adjusted.
Cesium provides lots of methods 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
Be careful, different data providers have different attribution requirements – make sure you have permission to use data from a particular provider, and use the credit option to attribute the sources accordingly.
By default, Cesium uses Bing Maps for imagery. The imagery packaged with the Viewer is mostly for demo purposes. Cesium requires you to create an ion account and generate an access key to use the imagery.
For this demo, we’re going to use the Sentinel-2 imagery in Cesium ion.
First, go to Cesium ion and add the Sentinel-2 imagery to your assets. Click “Asset Depot” in the navbar to navigate to the Asset Depot page.
Click “Add to my assets”. Sentinel-2 will now be available in the list of assets when you go to the “My Assets” page, and will now be available to use in our app.
With the above code additions, our application should look like this when you zoom in:
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
- PAMAP Terrain : High resolution Pennsylvania terrain
- Terrain display options : Some terrain configuration options and formats
- Terrain exaggeration : Making terrain elevation differences more dramatic
Supported Terrain Formats:
- Quantized-mesh, an open format developed by the Cesium team
- Google Earth Enterprise
Here, we’ll use the Cesium World Terrain tileset hosted on Cesium ion, which is included in “My Assets” by default. In this case, we can use the
createWorldTerrain helper function to create the Cesium World Terrain tileset hosted on Cesium ion.
requestVertexNormals are optional 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 Cesium Terrain Tutorial.
Configuring the Scene
The next step is to add a little more setup to start our viewer in the right location and time.
This involves interacting with
viewer.scene, the class that controls all of the graphical elements in our viewer.
To start, we can configure our scene to 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. If you zoom out, you’ll see that part of the globe is dark because the sun has set in that part of the world.
Next, before we get started with setting up our initial view, let’s go over a few basic Cesium types:
Cartesian3: a 3D Cartesian coordinate – when used as a position it is relative to the center of the globe in meters using the Earth fixed-frame (ECEF)
Cartographic: a position defined by longitude, latitude (in radians) and height from the WGS84 ellipsoid surface
HeadingPitchRoll: 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. See the documentation for each type to learn more.
Now let’s position the camera to view our scene in NYC, where our data is located.
Camera is a property of
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 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): create an animated camera flight from the current camera 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:
Let’s try one of these methods by moving the camera to New York. Set the initial view with
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. 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 sets the rate of the scene animation, the start and stop times, and tells the clock to loop back to the beginning when it hits the stop time. It also sets the timeline widget 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 add the main focus of our application – the sample geocache data.
For easy visualization, Cesium supports popular vector formats GeoJson and KML, as well as an open format we developed specifically for describing a scene in Cesium called CZML.
Regardless of the initial format, all spatial data in Cesium is represented using the Entity API. The Entity API 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 the Visualizing Spatial Data tutorial.
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(optinos) with a few options.
For a KmlDataSource, the camera and canvas options are required. The
clampToGround option enables ground clamping,
a popular display option that makes ground geometry entities like polygons and ellipses conform to terrain rather than curve to the WGS84 ellipsoid surface.
If you’re not familiar with the
Promise API for working with asynchronous functions, the “asynchronous” here basically means you should do what you need to do with
the data in a callback function 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
viewer.datasources. Uncomment the following lines:
These newly-created entities come with useful functionality by default. Clicking will display the
with metadata related to the entity, and double-clicking zooms in and looks at the entity. To stop looking at the entity,
click the home button or click the crossed out camera icon on the infobox. 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
Labels 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
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 longitude and latitude of our points.
First, we’ll convert the entity’s position into a Cartographic, then read the longitude and latitude 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
And like with the previous datasource, we need to add it to
viewer.datasources to actually add data to the scene.
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
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 positions that define the polygon border.
We can generate a position by taking the center of the polygon positions:
This gives us labelled polygons that look like this:
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. There are several property types in the Entity API that can be used for handling time dynamic data. See the below demo for an example:
The CZML file has Cesium display the drone flight using a
Path, 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.
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), an open-specification the Cesium team developed in conjunction with the Khronos group for efficiently loading 3D models by applications by minimizing file size and runtime processing. Don’t have a glTF model? Cesium ion includes a model converter for for converting COLLADA and OBJ files to the glTF format.
Let’s load a drone
Model with nice physically-based shading and some animations:
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 forward.
Fortunately, Cesium provides
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! This New York City tilese is hosted in Cesium Ion and we can add it using
You may notice that the buildings are not correctly positioned at ground level. Fortunately it’s easy to fix. We can adjust the
position of the tileset by modifying its
We can find the model’s current offset from the ground by converting the tileset’s bounding sphere into a
then adding the desired offset and resetting the
We now have over 1.1 million building models streaming into our scene!
3D Tiles also allows us to style parts of our tileset using the 3D Tiles styling language.
A 3D Tiles style defines expressions to evaluate color (RGB and translucency) and show properties for a
a part of the tileset such as an individual building in a city. Styling is often based on the feature’s properties stored in the tile’s batch table. A feature property can be anything like height,
name, coordinates, construction date, etc. but is built into the tileset asset.
Additionally the styling language provides a set of built-in functions to support common math operations.
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 have data and need help converting it to 3D tiles, stay tuned for updates about the Cesium ion platform! Subscribe for updates 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
a set of handler that triggers specified functions on user input actions.
ScreenSpaceEventHandler.setInputAction() listens for a type of user action –
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 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
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.
Let’s start by creating an easy way to toggle the neighborhood polygons. In general, we can hide
entities by setting visibility with
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
We can then set visibility for all the child entities at once by changing the value of
We can do something similar for toggling the visiblity of shadows:
Finally, 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! You have successfully completed your CesiumJS application! Feel free to explore and experiment with the code we’ve provided here to further your Cesium education. We’re excited to welcome you to the Cesium community and look forward to seeing the amazing apps you build with the CesiumJS library.
So what’s next?
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.
Showcase your work on our blog
We love sharing all of the incredible apps the Cesium community builds. Developers around the world have created more interesting applications than we could have ever imagined! Once your application is ready to share with the world, get in touch with us about featuring your app on our blog. See this blog post for information about submitting your application for a showcase.
Discover and process content on Cesium ion
Cesium ion is new commercial platform composed of web services and tools to complement CesiumJS’s visualization, creating a complete 3D mapping platform. The Cesium team is particularly excited about ion since it is our first product built around CesiumJS to support developing open-source CesiumJS.
Our vision for ion includes subscriptions for
- 3D content, such as imagery, terrain, 3D tilesets, and glTF models curated from open data and commercial data providers;
- 3D tiling and hosting of your own massive datasets, such as imagery, terrain, photogrammetry, point clouds, BIM, CAD, 3D buildings, and vector data;
- Analytics, such as measurement tools, volume and visibility computations, and terrain profiles; and
- Map making and sharing workflows for creating 3D maps without coding.
Learn more on cesium.com.