Sunday, 12 October 2014

Creating 3D terrains with Cesium

Previously, I’ve used three.js to create 3D terrain maps in the browser (1, 2, 3, 4, 5, 6). It worked great for smaller areas, but three.js doesn’t have built-in support for tiling and advanced LOD algorithms needed to render large terrains. So I decided to take Cesium for a spin.

Cesium is a JavaScript library for creating 3D globes and 2D maps in the browser without a plugin. Like three.js, it uses WebGL for hardware-accelerated graphics. Cesium allows you to add your own terrain data, and this blog post will show you how.

Compared to the dying Google Earth plugin, it's quite complicated to get started with Cesium. The source code is well documented and the live coding Sandcastle is great, but there is a lack of tutorials and my development slows down when I have to deal with a lot of math.

That said, I was able to create an app streaming my own terrain and imagery with a few lines of code. There is also WebGL Earth, a wrapper around Cesium giving you an API similar to well-known Leaflet. I expect to see more functions or wrappers to make stuff like camera positioning easier in the future.

How can you add your own terrain data to Cesium? 

First, you need to check if you really need it. You have the option to stream high-resolution terrain data directly from the servers at AGI. It's free to use on public sites under the terms of use. If you want to host the terrain data on your own servers, AGI provides a commercial product - the STK Terrain Server. Give it a try, if you have a budget!

I was looking for an open source solution, and found out that Cesium supports two terrain formats:
  1. heightmap
  2. quantized-mesh
The tiled heightmap format is similar to the one I used for three.js. Each tile contains 65 x 65 height values, which overlap their neighbors at their edges to create a seamless terrain. Cesium translates the heightmap tiles into a uniform triangle mesh, as I did in three.js. The downside of this format is the uniform grid, you use the same amount of data to represent both flat and hilly terrain.

The regular terrain mesh made from heightmap tiles. 

The quantized-mesh format follows the same tile structure as heightmap tiles, but each tile is better optimised for large-scale terrain rendering. Instead of creating a dense, uniform triangle mesh in the browser, an irregular triangle mesh is pre-rendered for each tile. It's a better representation of the landscape, having less detail in flat areas while increasing the density in steep terrain. The mesh terrain is also more memory efficient and renders faster.

The irregular terrain mesh from quantized-mesh tiles. Larger triangles have less height variation. 

Unfortunately, I haven't found any open source tools to create tiles in the quantized-mesh format - please notify me if you know how to do it!

You can generate heightmap tiles with Cesium Terrain Builder, a great command-line utility by Homme Zwaagstra at the GeoData Institute, University of Southampton.

I'm using the same elevation data as I did for my three.js maps, but this time in full 10 meter resolution. I'm just clipping the data to my focus area (Jotunheimen) using EPSG:4326, the World Geodetic System (WGS 84).

gdalwarp -t_srs EPSG:4326 -te 7.2 60.9 9.0 61.7 -co compress=lzw -r bilinear jotunheimen.vrt jotunheimen.tif

I went for the easy option, and installed Cesium Terrain Builder using the Docker image. First I installed Docker via Homebrew.  I was not able to mount my hard drive with this method, so I downloaded the elevation data from my public Dropbox folder:


I used the ctb-tile command to generate the tileset:

ctb-tile --output-dir ./tiles jotunheimen.tif

The command returned 65 000 tiles down to zoom level 15. I compressed the tiles into one file:

tar cvzf tiles.tar.gz tiles

and used the Dropbox uploader to get the tiles back to my hard drive:

./ upload tiles.tar.gz tiles.tar.gz

So I got 65 000 terrain tiles on my server, how can I see the beauty in Cesium? It required some extra work:
  1. First I had to add a missing top level tile that Cesium was expecting. 
  2. Cesium was also looking for a layer.json file which I had to create:

      "tilejson": "2.1.0",
      "format": "heightmap-1.0",
      "version": "1.0.0",
      "scheme": "tms",
      "tiles": ["{z}/{x}/{y}.terrain?v={version}"]
  3. Lastly, I added a .htaccess file to support CORS and gzipped terrain tiles: 

Then I was ready to go!

Beautiful terrain rendered with 10 m elevation data from the Norwegian Mapping Authority. Those who know Jotunheimen, will notice Skogadalsbøen by the river and Stølsnostind and Falketind surrounded by glaciers in the background.

The terrain is a bit blocky (see the mount Falketind to the left), but I'm not sure if this is happening in Cesium Terrain Builder or Cesium itself. The quantized-mesh tiles from AGI gives a better result. 

I'm not able so show an interactive version, as I'm using detailed aerial imagery from "Norge i bilder", which are not publicly available.



Ivan Pašić said...

Great blog post, I can't wait to find some more time to try it by myself too.
If you continue with exploring this 3d web mapping please write about it :)

Anonymous said...

Awesome stuff! I definitely want to try this out, and you make it easy to get started.

I have only recently started using open-source GIS platforms - most of my 15 years' experience has been with the big commercial packages -- but I can't quite believe there is not a TIN (triangular irregular network) generator in one or more of the open-source options. I'm sure people more familiar will let you know, but a quick search I just did turned up at least one tool which seems to do exactly what you want:

"Triangle generates exact Delaunay triangulations, constrained Delaunay triangulations, conforming Delaunay triangulations, Voronoi diagrams, and high-quality triangular meshes."

It's terms of use are described as: "freely available, it is copyrighted by the author and may not be sold or included in commercial products without a license".


vncbmerter said...

Triangle does a good stuff, fast and robust triangulation.
IMHO, for the generation of TIN, the problem is more the selection of the points to triangulate than the triangulation itself.
OpenMesh provides good simplification routines that could be use here.

Thomas said...

I contacted AGI and they are interested in integrating the terrain model into their public STK dataset. That might be the easiest solution to that. ;)

Anonymous said...

very nice, great work
for me is to difficult. i'm waiting for another tutorial
i use qgis and trejs plugin but the result is not like this...
i'm searching for some webgl app or plugin for qgis if exists or somebody wants to develop...
it'possible in theory develop a plugin for qgis with cesium for terrain 3d viewer?
best regards mark skama

Bjørn Sandvik said...

A bit more information about the quantized-mesh format is provided in the Cesium forum. I hoping for an open source solution soon.

BB said...

Excellent post!
I have been trying this for two days with no success unfortunately.
I am stuck at the error you also described at issue 2:

I have the same values you had:
- buffer.byteLength = 48
- provider._heightmapWidth = 65 (65*65 = 4225)

Terrain tiles were generated with ctb-tile tool in Docker.

Somehow your solution (adding last two lines into .htaccess file) doesn't work for me. I am trying this on Windows machine using WAMP Server and right now I have no idea left.

Of course, before that I also had issue #1: blank terrain tile :)

If you remember any hack/trick you had to made, please share with us :)

Kind regards, BB

x. lhomme said...

Hi Bj0rn

Another way : I use geoserver to server my geotiff with WCS 2.0.1.

I wrote a WCSTerrainProvider which use a GeotiffParser in order to decode each tile and transforme the tile into an Heightmap.

Quite slow but easy to use ans standard.

I will publish soon a new version on github of the WCSTerrainProvider. This new version use IndexedDB to store localy the Heighmap.

Bikram Gangwar said...

Thanks for the post. I created tiles, added missing top level tile and created layer.json file along with .htaccess file. But I am not able to get generate terrain on cesium globe. I am using

var viewer = new Cesium.Viewer('cesiumContainer');
var terrainProvider = new Cesium.CesiumTerrainProvider({
url: '/terrain/tiles'

viewer.scene.terrainProvider = terrainProvider;

I am getting these errors-
An error occurred in "CesiumTerrainProvider": Failed to obtain image tile X: 1 Y: 0 Level: 1.
An error occurred in "CesiumTerrainProvider": Failed to obtain image tile X: 0 Y: 0 Level: 1.

I think I am not saving layer.json file in the cesium directory properly.
For the missing top layer tile, I am using

Please let me know where I am wrong.

Klemen Špruk said...

I have the same issue with : An error occurred in "CesiumTerrainProvider": Failed to obtain terrain tile X: 0 Y: 0 Level: 0.

I have missing tiles and layer.json file.

Have you figure it out what colud be the problem?

Thanks, Klemen!