This tutorial will teach you how to use Cesium’s Entity API for drawing spatial data such as points, markers, labels, lines, models, shapes, and volumes. No prior knowledge of Cesium is needed, but if you have absolutely no experience with it, you might want to start with our first tutorial, Cesium up and running.

What is the Entity API?

Cesium has a rich API for spatial data that can be split into two categories: a low-level API geared towards graphics developers, commonly referred to as the Primitive API, and a high-level API for data-driven visualization, referred to as the Entity API.

The Primitive API’s primary goal is to expose the minimal amount of abstraction needed to perform the task at hand. It expects us to think like a graphics programmer and uses graphics terminology. It is structured to provide the most performant and flexible implementation for a given visualization type, not for API consistency. Loading a Model is different from creating a Billboard and both are radically different from creating a Polygon. Each type of visualization is its own distinct feature. Furthermore, they each have different performance characteristics and require different best practices to be followed. While it is powerful and flexible, most applications are better served with a higher level of abstraction than what is offered by the Primitive API.

The goal of the Entity API is to expose a set of consistently designed high-level objects that aggregate related visualization and information into a unified data structure, which we call an Entity. It lets us concentrate on the presentation of our data rather than worrying about the underlying mechanism of visualization. It also provides constructs for easily building complex, time-dynamic visualization in a way that fits naturally alongside static data. While the Entity API actually uses the Primitive API under the hood, that’s an implementation detail we will (almost) never have to concern ourselves with. By applying various heuristics to the data we give it, the Entity API is able to provide flexible, high-performance visualization while exposing a consistent, easy to learn, and easy to use interface.

Our First Entity

One of the best ways to learn the basics of the Entity API is by looking at some code. In order to keep things simple, we’re going to build on the Hello World example in Cesium Sandcastle. If you are set up to develop with Cesium locally, feel free to use your own application instead.

Suppose we wanted to add a polygon for the US state of Wyoming from a list of longitude and latitude degrees. (Wyoming was chosen because it’s such a simple polygon.) We can copy and paste the below code into Sandcastle to do it:

var viewer = new Cesium.Viewer('cesiumContainer');

var wyoming = viewer.entities.add({
  name : 'Wyoming',
  polygon : {
    hierarchy : Cesium.Cartesian3.fromDegreesArray([
                              -109.080842,45.002073,
                              -105.91517,45.002073,
                              -104.058488,44.996596,
                              -104.053011,43.002989,
                              -104.053011,41.003906,
                              -105.728954,40.998429,
                              -107.919731,41.003906,
                              -109.04798,40.998429,
                              -111.047063,40.998429,
                              -111.047063,42.000709,
                              -111.047063,44.476286,
                              -111.05254,45.002073]),
    height : 0,
    material : Cesium.Color.RED.withAlpha(0.5),
    outline : true,
    outlineColor : Cesium.Color.BLACK
  }
});

viewer.zoomTo(wyoming);

Hitting the Run button (or F8 for those that prefer the keyboard), gets us the below image:

Our first entity. Wyoming has never been this exciting.

Since one of our goals is to make Cesium code easy to understand, hopefully the above is self explanatory. We create the Viewer widget, which serves as the base for almost all Cesium applications, and then add a new Entity via viewer.entities.add. The object we pass to add is just an options parameter which provides initial values. The return value is the actual entity instance. Finally, we call viewer.zoomTo to make sure the entity is in view.

There are an incredible amount of Entity options available, but for now we specify translucent red for the interior of the polygon and a black outline for the border. We also give the entity a display name of “Wyoming”.

Shapes and Volumes

Armed with the basic knowledge of creating a polygon, and thanks to the homogeneous nature of the Entity API, we can now create a variety of graphics by simply using the examples available in Sandcastle as a reference. Here’s a complete list of supported shapes and volumes.

     
Boxes entity.box - Code example - Reference documentation
Circles and Ellipses entity.ellipse - Code example - Reference documentation
Corridor entity.corridor - Code example - Reference documentation
Cylinder and Cones entity.cylinder - Code example - Reference documentation
Polygons entity.polygon - Code example - Reference documentation
Polylines entity.polyline - Code example - Reference documentation
Polyline Volumes entity.polylineVolume - Code example - Reference documentation
Rectangles entity.rectangle - Code example - Reference documentation
Spheres and Ellipsoids entity.ellipsoid - Code example - Reference documentation
Walls entity.wall - Code example - Reference documentation

Materials and Outlines

Regardless of their geometric definition, all shapes and volumes have a common set of properties that control their appearance. The fill property is a boolean that specifies if the interior of the surface is filled in, while the outline property controls whether the edges of the shape are outlined.

When fill is set to true, the material property determines what that filling looks like. In our next example, let’s create a translucent blue ellipse. By default, fill is true and outline is false, so we only need to specify material.

var entity = viewer.entities.add({
  position: Cesium.Cartesian3.fromDegrees(-103.0, 40.0),
  ellipse : {
    semiMinorAxis : 250000.0,
    semiMajorAxis : 400000.0,
    material : Cesium.Color.BLUE.withAlpha(0.5)
  }
});
viewer.zoomTo(viewer.entities);

var ellipse = entity.ellipse; // For upcoming examples

Image

We can just as easily specify a url to an image instead.

ellipse.material = '//cesiumjs.org/images/2015/02-02/cats.jpg';

In both the above cases, a ColorMaterialProperty or ImageMaterialProperty is created for us automatically on assignment. For more complex materials, we need to create a MaterialProperty instance ourselves. Currently, Entity shapes and volumes support colors, images, checkerboard, stripe, and grid materials.

Checkerboard

ellipse.material = new Cesium.CheckerboardMaterialProperty({
  evenColor : Cesium.Color.WHITE,
  oddColor : Cesium.Color.BLACK,
  repeat : new Cesium.Cartesian2(4, 4)
});

Stripe

ellipse.material = new Cesium.StripeMaterialProperty({
  evenColor : Cesium.Color.WHITE,
  oddColor : Cesium.Color.BLACK,
  repeat : 32
});

Grid

ellipse.material = new Cesium.GridMaterialProperty({
  color : Cesium.Color.YELLOW,
  cellAlpha : 0.2,
  lineCount : new Cesium.Cartesian2(8, 8),
  lineThickness : new Cesium.Cartesian2(2.0, 2.0)
});

Outlines

Unlike the fill property, outline does not have a corresponding material and instead relies on two separate outlineColor and outlineWidth properties. outlineWidth only works on non-Windows systems, such as Android, iOS, Linux, and OS X. On Windows systems, outlines will always have a width of 1. This is due to a limitation of how WebGL is implemented in all three major browser engines on Windows.

ellipse.fill = false;
ellipse.outline = true;
ellipse.outlineColor = Cesium.Color.YELLOW;
ellipse.outlineWidth = 2.0;

Polylines

Polylines are a special case, since they have no fill or outline properties. Instead they rely on specialized materials for anything other than color. Because of these special materials, polylines of varying widths and outline widths will work on all systems.

var entity = viewer.entities.add({
  polyline : {
    positions : Cesium.Cartesian3.fromDegreesArray([-77, 35,
													-77.1, 35]),
    width : 5,
    material : Cesium.Color.RED
  }
});
viewer.zoomTo(viewer.entities);

Polyline Outline

polyline.material = new Cesium.PolylineOutlineMaterialProperty({
  color : Cesium.Color.ORANGE,
  outlineWidth : 3,
  outlineColor : Cesium.Color.BLACK
});

Polyline Glow

polyline.material = new Cesium.PolylineGlowMaterialProperty({
	glowPower : 0.2,
	color : Cesium.Color.BLUE
});

Heights and Extrusions

All shapes that drape over the earth, currently circles, ellipses, polygons, and rectangles, can also be placed at altitude or extruded into a volume. In both cases, the shape or volume will still conform to the curvature of the earth underneath it.

All we need to do for altitude is set the height property on the corresponding graphics object, it’s the same for all of the shapes we listed above. This is probably a good time to mention that Cesium always uses meters, radians, and seconds for units, unless the function explicitly says otherwise, such as Cartesian3.fromDegrees. The below line of code raises the polygon to 250,000 meters above the earth.

wyoming.polygon.height = 250000;

Wyoming at 250,000 meters.

Extruding the shape into a volume is just as easy, we just need to set the extrudedHeight property. The volume will be created between height and extrudedHeight. If height is undefined, the volume starts at 0. So to create a volume that starts at 200,000 meters and extends to 250,000 meters, we could use the code below. This of course means the volume itself is 50,000 meters tall.

wyoming.polygon.height = 200000;
wyoming.polygon.extrudedHeight = 250000;

You can easily extrude a flat polygon into a volume.

Entity features in the Viewer

Before diving into the additional kinds of visualization we can create, let’s look at the out-of-the-box functionality the Viewer provides for working with entities.

Selection and Description

Unless we specifically disable it, clicking on an entity in the Viewer will show the SelectionIndicator widget at the Entity’s location as well as bring up the InfoBox widget to provide more information. Going back to our original example, we only supplied the wyoming entity with a name, which determines the title of the InfoBox, but we can also provide additional data in HTML via the Entity.description property. Let’s add the below block of code to the end of the first example.

wyoming.description = '\
<img\
  width="50%"\
  style="float:left; margin: 0 1em 1em 0;"\
  src="//cesiumjs.org/images/2015/02-02/Flag_of_Wyoming.svg"/>\
<p>\
  Wyoming is a state in the mountain region of the Western \
  United States.\
</p>\
<p>\
  Wyoming is the 10th most extensive, but the least populous \
  and the second least densely populated of the 50 United \
  States. The western two thirds of the state is covered mostly \
  with the mountain ranges and rangelands in the foothills of \
  the eastern Rocky Mountains, while the eastern third of the \
  state is high elevation prairie known as the High Plains. \
  Cheyenne is the capital and the most populous city in Wyoming, \
  with a population estimate of 62,448 in 2013.\
</p>\
<p>\
  Source: \
  <a style="color: WHITE"\
    target="_blank"\
    href="http://en.wikipedia.org/wiki/Wyoming">Wikpedia</a>\
</p>';

Setting the entity description allows for HTML formatted information to be displayed in the InfoBox.

Many applications will retrieve descriptions from the server rather than using a hard coded string, but either approach is valid.

By default, all HTML shown in the InfoBox is sandboxed. This prevents external data sources from injecting malicious code into a Cesium application. If you want to be able to run JavaScript or browser plug-ins inside of a description, you can access the iframe used for sandboxing via the viewer.infoBox.frame property. See this article for more information on controlling iframe sandboxing.

Camera Controls

As we saw in the first example, we can use the zoomTo command to display a particular entity. The same thing can be accomplished by pressing the camera button in the upper left corner of the InfoBox or by double-clicking an entity. There is also a flyTo method that results in the same view but performs an animated camera flight instead of an immediate jump. In addition to taking a single entity, both of these methods can be passed an EntityCollection, which we’ll discuss later on, a DataSource, which is used for loading common data formats such as GeoJSON and is in the process of getting its own tutorial, or a standard JavaScript array of entities

By default either method will calculate a view that ensures the provided entities are visible. The camera will be oriented north and be looking down on the target from a 45 degree angle. We can customize this view by providing our own heading, pitch, and range. The below code will rotate the camera to view Wyoming from the east and look down on the state at a 30 degree angle. Since we do not specify a range, the calculated default will still be used.

var heading = Cesium.Math.toRadians(90);
var pitch = Cesium.Math.toRadians(-30);
viewer.zoomTo(wyoming, new Cesium.HeadingPitchRange(heading, pitch));

Wyoming viewer from the east.

Both zoomTo and flyTo are asynchronous functions, that is, they are not guaranteed to have completed before they return. For example, flying to an Entity happens over many animation frames. Both of these functions return Promises that can be used to schedule a function to be executed after the flight or zoom is completed. Let’s replace the call to zoomTo in our example with the snippet below, which will not only fly to Wyoming but also select it after the flight is completed.

viewer.flyTo(wyoming).then(function(result){
  if (result) {
    viewer.selectedEntity = wyoming;
  }
});

The result parameter passed to our callback will be true if the flight completed successfully or false if the flight was canceled, i.e. a user initiated another flight or zoom before this one completed, or because the target has no corresponding visualization, i.e. there’s nothing to zoom to.

Sometimes, particularly when working with time-dynamic data, we want the camera to remain centered on an entity rather than on the Earth. This is accomplished by setting the viewer.trackedEntity property. Tracking an entity requires that position be set. To try this mode out with our first example, we can add a position to Wyoming and track it like so:

wyoming.position = Cesium.Cartesian3.fromDegrees(-107.724, 42.68);
viewer.trackedEntity = wyoming;

We can stop tracking by either setting viewer.trackedEntity to undefined or clicking the cancel button in the upper left corner of the InfoBox. Calling zoomTo or flyTo will also cancel tracking and viewer.trackedEntity will become undefined.

For many use cases, the entity-specific camera functionality provided by the Viewer is more than enough, but if you would like to further customize views in your application, check out the Camera tutorial.

Managing Entities

EntityCollection is an associative array that makes managing and monitoring a group of entities easy. We’ve already encountered one instance of it in the form of the viewer.entities property. EntityCollection includes traditional methods such as add, remove, and removeAll; as well as Entity specific functionality, which we discuss below.

Many applications rely on data being stored on the server until it is needed by the client. Sometimes that means we need to update an entity that we previously created. All Entity instances have a unique id property that can be used for such cases. Since we didn’t specify an identifier in our first example, Cesium generated a GUID for us, which results in a unique string like 182bdba4-2b3e-47ae-bf0b-83f6fde285fd. The data on a server most likely has its own unique identifier that we’ll want to use instead, so we can specify it at Entity creation time:

viewer.entities.add({
  id : 'uniqueId'
});

Later on, we can retrieve the entity using getById. In the event that no entity with the provided id exists, undefined is returned.

var entity = viewer.entities.getById('uniqueId');

Another common use case is to update an entity if it already exists, but create a new one if it does not. getOrCreateEntity will always return a valid instance with the supplied id, creating a new instance and adding it to the collection if necessary.

var entity = viewer.entities.getOrCreateEntity('uniqueId');

Finally, we can also create a new Entity manually and simply pass it to add. In this case, add detects that we passed it an existing Entity and will simply return the same instance. This method throws if an entity with the same id already exists in the collection.

var entity = new Entity({
  id : 'uniqueId'
});
viewer.entities.add(entity);

Where the power of EntityCollection starts to shine is in the collectionChanged Event, which we can use to be notified when an entity has been added, removed, or updated in the collection. This is particularly useful when building user interfaces or other parts of an application that need to monitor data that might be generated elsewhere.

To see this in action, let’s use the Geometry Showcase example in Sandcastle. Copy and paste the below code near the top, immediately after the viewer is created.

function onChanged(collection, added, removed, changed){
  var msg = 'Added ids';
  for(var i = 0; i < added.length; i++) {
    msg += '\n' + added[i].id;
  }
  console.log(msg);
}
viewer.entities.collectionChanged.addEventListener(onChanged);

When we run the example, we get about 65 messages in the console, one for every call to viewer.entities.add (The removed and changed arrays are always empty in this example since it only adds entities). Cesium also subscribes to this event internally to update visualization when needed. When updating a large amount of entities at once, it’s more perfomant to queue up changes and send one big event at the end. This improves performance because Cesium can process any required changes in a single pass. To do this, we can call viewer.entities.suspendEvents at the start of an update followed by viewer.entities.resumeEvents.

Let’s give this a try. Add a suspend call before the first viewer.entities.add and a resume call at the end of the example. When we run the demo again, we now get a single event containing all 65 entities. These calls are reference counted, so multiple suspend and resume calls can be nested without any issues. However, forgetting to call resume at the end of your processing will result in nothing showing up in the scene, since no messages get sent until every call to suspend has a matching call to resume.

Picking

Picking, i.e. selecting an object at a given screen coordinate, usually with the mouse, is still one of the areas in Cesium where we need to interface briefly with the Primitive API. This will be remedied in a future version of Cesium with the introduction of Entity specific picking capabilities. For now, we can use the lower level functions, scene.pick and scene.drillPick to retrieve an entity. Basic implementations of entity picking is included below and can be dropped into any application as-is.

/**
 * Returns the top-most Entity at the provided window coordinates
 * or undefined if no Entity is at that location.
 *
 * @param {Cartesian2} windowPosition The window coordinates.
 * @returns {Entity} The picked Entity or undefined.
 */
function pickEntity(viewer, windowPosition) {
  var picked = viewer.scene.pick(windowPosition);
  if (defined(picked)) {
    var id = Cesium.defaultValue(picked.id, picked.primitive.id);
    if (id instanceof Cesium.Entity) {
      return id;
    }
  }
  return undefined;
};

/**
 * Returns the list of Entities at the provided window coordinates.
 * The Entities are sorted front to back by their visual order.
 *
 * @param {Cartesian2} windowPosition The window coordinates.
 * @returns {Entity[]} The picked Entities or undefined.
 */
function drillPickEntities(viewer, windowPosition) {
  var i;
  var entity;
  var picked;
  var pickedPrimitives = viewer.scene.drillPick(windowPosition);
  var length = pickedPrimitives.length;
  var result = [];
  var hash = {};

  for (i = 0; i < length; i++) {
    picked = pickedPrimitives[i];
    entity = Cesium.defaultValue(picked.id, picked.primitive.id);
    if (entity instanceof Cesium.Entity &&
        !Cesium.defined(hash[entity.id])) {
      result.push(entity);
      hash[entity.id] = true;
    }
  }
  return result;
};

Here’s how they work. While the various scene picking methods return objects with primitive information instead of entity instances, the Entity API is structured so that every primitive that corresponds to an entity has that entity as its id property. So all we have to do is check if the picked object’s id is an instance of Entity. While these functions are trivial, they aren’t included as part of Cesium yet because we have more robust functionality planned.

Points, Billboards, and Labels

Leaving shapes and volumes behind, let’s look at how to visualize specific points of interest in Cesium.

Creating a graphical point or label is simple, we just need to specify a position for our entity and point and label objects for the visualization. For example, maybe we want to place a point at the home stadium of our favorite sports team.

var viewer = new Cesium.Viewer('cesiumContainer');

var citizensBankPark = viewer.entities.add({
  name : 'Citizens Bank Park',
  position : Cesium.Cartesian3.fromDegrees(-75.166493, 39.9060534),
  point : {
    pixelSize : 5,
	color : Cesium.Color.RED,
	outlineColor : Cesium.Color.WHITE,
	outlineWidth : 2
  },
  label : {
	text : 'Citizens Bank Park',
    font : '14pt monospace',
    style: Cesium.LabelStyle.FILL_AND_OUTLINE,
    outlineWidth : 2,
    verticalOrigin : Cesium.VerticalOrigin.BOTTOM,
    pixelOffset : new Cesium.Cartesian2(0, -9)
  }
});

viewer.zoomTo(viewer.entities);

Citizens Bank Park in Philadelphia, PA. Home of the Philadelphia Phillies.

Most of these options are self-documenting, but a few of them require some explanation. For instance, the font setting follows the CSS compact font syntax.

By default the label is centered both horizontally and vertically at the position. Since both the label and point share the same position, they would normally overlap each other on the screen. In order to draw the label above the point, we can specify that its origin should be the bottom, meaning the bottom of the text is the part that touches the entity location, and also specify an offset in screen space. In the above example, we use (0, -9), which, when combined with VerticalOrigin.BOTTOM, means the label’s bottom is 9 pixels above the entity location. The value is negative because we want to decrease the screen coordinates’ Y value, which starts at the top and increases as we move down the screen. Setting the origin and offset ensures that the label will always be above of our point, not matter what angle or rotation the user is viewing at.

Of course using a point isn’t very exciting, so we can replace the point with a billboard, which is a marker that’s always facing the user.

var citizensBankPark = viewer.entities.add({
  position : Cesium.Cartesian3.fromDegrees(-75.166493, 39.9060534),
  billboard : {
      image : 'http://localhost:81/images/2015/02-02/Philadelphia_Phillies.png',
      width : 64,
      height : 64
  },
  label : {
	text : 'Citizens Bank Park',
    font : '14pt monospace',
    style: Cesium.LabelStyle.FILL_AND_OUTLINE,
    outlineWidth : 2,
    verticalOrigin : Cesium.VerticalOrigin.TOP,
    pixelOffset : new Cesium.Cartesian2(0, 32)
  }
});

The Philadelphia Phillies logo as a billboard at our Entity's position.

In the above example, we are explicitly setting the width and height of the billboard, but they aren’t required. If we omit them, the natural width and height from the file is used.

Labels and billboards have a lot of options that we won’t go into here. See the Sandcastle examples for each to see each of them in action: Labels, Billboards.

3D Models

Cesium supports 3D Models via glTF, the runtime asset format for WebGL, OpenGL ES, and OpenGL. It also includes a few ready-to-use glTF models: an aircraft with animated propellers, a ground vehicle with animated wheels, and a character with a skinned walk cycle. All of which can be seen in the 3D models Sandcastle example

Loading a model is not much different than any other type of visualization we’ve worked with so far. All it requires is a position for the entity and a uri to the glTF model.

var viewer = new Cesium.Viewer('cesiumContainer');
var entity = viewer.entities.add({
    position : Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706),
    model : {
        uri : '../../SampleData/models/CesiumGround/Cesium_Ground.gltf'
    }
});
viewer.trackedEntity = entity;

The example truck model that ships with Cesium.

You can optionally provide a scale property, which uniformly scales the model from it’s native size, as well as a minimumPixelSize property, which prevents the model from ever appearing smaller than the supplied pixel size.

By default, the model is oriented upright and facing east. We can control the orientation of the model by specifying a Quaternion for the Entity.orientation property. This is a little more involved than our position only example, but lets us control the heading, pitch, and roll of the model. We can paste the below code into Sandcastle and play with the values to see how the settings affect the orientation.

var viewer = new Cesium.Viewer('cesiumContainer');
var position = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706);
var heading = Cesium.Math.toRadians(45.0);
var pitch = Cesium.Math.toRadians(15.0);
var roll = Cesium.Math.toRadians(0.0);
var orientation = Cesium.Transforms.headingPitchRollQuaternion(position, heading, pitch, roll);

var entity = viewer.entities.add({
    position : position,
	orientation : orientation,
    model : {
        uri : '../../SampleData/models/CesiumGround/Cesium_Ground.gltf'
    }
});
viewer.trackedEntity = entity;

Since models need to be converted to the gltF format for use with Cesium, we provide a web-based converter that lets you upload a COLLADA model and returns a glTF version.

While the Entity API does not currently support advanced model use cases, such as node picking or direct animation control, we can use the Primitive API to accomplish them. We have a separate 3D Model Primitive tutorial which covers these cases. We definitely plan on enhancing the Entity API to include these features in the future. The primitive tutorial also includes tips for debugging models that don’t render properly in Cesium, so be sure to check it out. If you create your own models, be sure to see our post on glTF Tips for Artists.

The Property System

So far we have only assigned values to our entities and graphics objects, we have never actually read any values back. In fact, if we tried to do so we would be surprised by the result. Take for example the polygon outline property which we we assigned to true in our first example. Intuition would tell us that a call to console.log will simply print true.

console.log(wyoming.polygon.outline);

The actual output of the above code is [object Object]. This is because outline is not a simple boolean but an instance of ConstantProperty. In fact, this entire tutorial we’ve actually been using a form of shorthand called implicit property conversion, which in this case automatically takes raw values and creates a corresponding ConstantProperty instance for us. Without this shorthand, we would have had to write a much longer version of the initial example (positions omitted for brevity):

var wyoming = new Cesium.Entity();
wyoming.name = 'Wyoming';

var polygon = new Cesium.PolygonGraphics();
polygon.material = new Cesium.ColorMaterialProperty(Cesium.Color.RED.withAlpha(0.5));
polygon.outline = new Cesium.ConstantProperty(true);
polygon.outlineColor = new Cesium.ConstantProperty(Cesium.Color.BLACK);
wyoming.polygon = polygon;

viewer.entities.add(wyoming);

But why do these properties exist at all? The reason is simple, the entire Entity API is capable of not only expressing constant values, but values as they change over time.

All properties implement the Property interface, and there are a lot of different kinds of properties in Cesium. Part two of this tutorial will concentrate on the Property system and using it to create animations and other time-dynamic visualization. For now, the only thing we need to know is that in order to read a Property’s value, we need to call the getValue function. So in order to retrieve the outline property from the polygon we would call the below code, where time is the current scene time.

console.log(wyoming.polygon.outline.getValue(viewer.clock.currentTime));

Technically, we could avoid passing a time if we are certain that the property we are querying is a ConstantProperty, but it’s good practice to always specify it.

Where to go from here

We’ve barely scratched the surface of what we can do with spatial data in Cesium, but we’ve already unlocked an immense amount of capabilities. While you wait for part two of this tutorial, you may be interested in Cesium’s support for Imagery Layers or Terrain and Water. As always, can look at the full list of Cesium Tutorials to decide what you would like to learn next.