Two years ago we did an experiment with visualizing custom terrains in Cesium (see

At the time, the used command ‘ctb-tile’ did create the ‘heightmap’ format of terrain tiles.  Now there is an updated tool which also creates the ‘quantized-mesh’ format of terrain tiles. The ‘quantized-mesh’ format is more memory efficient and renders faster (because irregular) so let’s experiment with that.

Here more information about the various terrain tile formats and the updated tool

Note for Docker Windows users: in the Docker commands replace ‘$(pwd)’ with a fully specified directory (like ‘d:/gisdata/nederland/terrain’) and double check Docker Settings -> Shared Drives.

Step 1: Download a GeoTIFF file

As example GeoTIFF file we’ll use again a part of Texel in the Netherlands.

$ wget

Step 2: Data preprocessing

It’s recommended to transform tif file to WGS84, so lets do that with gdalwarp.

$ docker run -v $(pwd):/data geodata/gdal gdalwarp -t_srs EPSG:3857 /data/i09bz1.tif /data/i09bz13857.tif

Step 3: create output directory

$ mkdir tiles

Step 4: Run tiling

In this step we run ‘tumgis/ctb-quantized-mesh’, this image adds the -f Mesh option to ctb-tile.

$ docker run -it -v $(pwd):/data tumgis/ctb-quantized-mesh ctb-tile -f Mesh -C -o /data/tiles /data/i09bz13857.tif

Step 5: Create the layer.json file for Cesium

File layer.json is a TileJSON file for  representing map metadata ( Add the ‘-l’ option to create this file.

$ docker run -it -v $(pwd):/data tumgis/ctb-quantized-mesh ctb-tile -l -f Mesh -C -o /data/tiles /data/i09bz13857.tif 

Step 6] Download Cesium 1.51

$ wget

Step 7] Unzip Cesium

$ unzip -d cesium

Step 8] Edit helloworld

edit ./cesium/Apps/HelloWorld.html in your favorite editor, insert the following script:

Sample code see:

Step 9: run cesium terrain server

$ docker run -p 9000:8000 -v $(pwd):/data/tilesets/terrain --env WEB_DIR=/data/tilesets/terrain/cesium/ geodata/cesium-terrain-server

Test: The following statement should download 1 terrain tile

$ wget http://localhost:9000/tilesets/tiles/0/0/0.terrain

Step 10: Open browser

Open browser http://localhost:9000/Apps/HelloWorld.html

Result should be something like:

Screenshot 2018-11-26 at 21.05.14

Voila, with a few Docker commands we’ve tiled a GeoTIFF image to the new quantised-mesh format and served it up to Cesium.


Running GDAL in Docker

The Geospatial Data Abstraction Library (GDAL – is a commonly used library for handling geospatial data formats. It’s possible to install this software on your system but as an alternative you can run GDAL from Docker ( and avoid the installation hassle.

In the following example a TIF file with height information is reprojected from the Dutch coordinate system (Amersfoort / RD New) to WGS84 using the GDAL tools ‘gdalinfo’ and ‘gdalwarp’. Docker image ‘geodata/gdal’ is used.

This example demonstrates running console programs from Docker and shows how the Docker mounting option (-v) works.

1] download a TIF image:

$ wget

2] Inspect input image

Let’s inspect the image with ‘gdalinfo’, projection is ‘Amersfoort / RD New’:

$ docker run -v $(pwd):/data  geodata/gdal gdalinfo /data/i09bz1.tif

Driver: GTiff/GeoTIFF

Files: /data/i09bz1.tif

Size is 10000, 12500

Coordinate System is:

PROJCS["Amersfoort / RD New"

NB: If you’re running Docker on Windows the variable for current directory (‘$(pwd)’)  does not work. Do something like this instead:

$ docker run -v d:/gisdata/images:/data  geodata/gdal gdalinfo /data/i09bz1.tif

3] Reproject the image

Reproject with gdalwarp (input=i09bz1.tif, output=i09bz13857.tif, target projection is EPSG:3857):

$ docker run -v $(pwd):/data  geodata/gdal gdalwarp -t_srs EPSG:3857 /data/i09bz1.tif /data/i09bz13857.tif

4] Inspect output image

Run gdalinfo again and see the projection is changed to ‘WGS 84 / Pseudo-Mercator’

$ docker run -v $(pwd):/data  geodata/gdal gdalinfo /data/i09bz13857.tif

Driver: GTiff/GeoTIFF

Files: /data/i09bz13857.tif

Size is 10095, 12600

Coordinate System is:

PROJCS["WGS 84 / Pseudo-Mercator"

Dutch AHN3 point cloud visualization

The current Dutch Elevation (Actueel Hoogtebestand Nederland, AHN) map is a digital elevation map of the whole of the Netherlands. The map is created using massive laseraltimetry pointclouds.

There are multiple versions of AHN, the most recent and detailed one is AHN3.  AHN3 will have a complete coverage next year (2019) with aimed point density of 8 points/m2.


In AHN3 each point has an x,y and z value and is classified into: ‘ground surface, water, buildings, artificial objects, vegetation, and unclassified.’ In addition each point contains scanning information like number of returns, intensity and gps time.

The nice thing is that all the raw data is now available as open data. As these are huge files it takes some care to process. In this blog a method is described to get from a raw pointcloud file to a 3D visualization for a specific area (Johan Cruijff Arena  in Amsterdam) with open source tooling.

The following steps are performed:

1] Download raw pointcloud LAZ file

2] Get summary information about the file using PDAL

3] Crop the file to an area of interest using PDAL and

4] get information about 1 sample point using PDAL

5] Visualize in 3D viewer (

Note: The PDAL steps are executed using Docker. As an alternative you can install PDAL tooling yourself instead of using Docker.

0] Preparation

Create a directory like d:\gisdata\ahn3:

$ cd d:\gisdata\ahn3

1] download a file

Go to and select your favorite area.


We’ll download file C_25GZ1.LAZ as this file contains the Johan Cruijff Arena:

$ wget

warning: its a 2GB file!

2] get summary info with PDAL

After downloading, run the following docker command to get  info about the LAZ file.

$ docker run -v d:/gisdata/ahn3:/data pdal/pdal pdal info /data/C_25GZ1.LAZ –summary


{ "filename": "\/data\/C_25GZ1.LAZ", "pdal_version": "1.7.2 (git-version: 07d19a)", "summary": { "bounds": { "X": { "max": 124999.999, "min": 120000 }, "Y": { "max": 481249.999, "min": 475000 }, "Z": { "max": 108.495, "min": -10.416 } }, "dimensions": "X, Y, Z, Intensity, ReturnNumber, NumberOfReturns, ScanDirectionFlag, EdgeOfFlightLine, Classification, ScanAngleRank, UserData, PointSourceId, GpsTime", "num_points": 470536792 } }

So it contains 470536792 (470 million) points with dimensions X, Y, Z, Intensity, ReturnNumber, NumberOfReturns, ScanDirectionFlag, EdgeOfFlightLine, Classification, ScanAngleRank, UserData, PointSourceId, GpsTime.

3] Find AOI

As the file is quite big, we’ll crop it to our area of interest. First we have to define the area of our interest in Dutch projection (epsg code 28992).

Go to and select your area of interest (Johan Cruijff Arena) in the map.

Switch to 28992 in the ‘epsg’ box.


Our area of interest is: 123606,480216,125816,481280 (xmin, ymin, xmax., ymax)

4] Crop area

Now with the raw pointcloud file and area of interest defined we can crop the file using PDAL pipeline command.

First create a crop.json file containing the following code:

{ "pipeline":[ "/data/C_25GZ1.LAZ", { "type":"filters.crop", "bounds":"([123606, 125816],[ 480216, 481280]) }, "/data/arena.laz" ] }

NB: the bounds are defined as [xmin, xmax], [ymin, ymax]

run with the PDAL pipeline with:

$ docker run -v d:/gisdata/ahn3:/data pdal/pdal pdal pipeline /data/crop.json

A new file ‘arena.laz’ with cropped area will be created.

5] Inspect a point

Now we can inspect a point in the arena.laz file to see its attributes.

$ docker run -v d:/gisdata/ahn3:/data pdal/pdal pdal info /data/arena.laz -p 0


{ "filename": "\/data\/arena.laz", "pdal_version": "1.7.2 (git-version: 07d19a)", "points": { "point": { "Blue": 0, "Classification": 1, "EdgeOfFlightLine": 0, "GpsTime": 328710.2203, "Green": 0, "Intensity": 20, "NumberOfReturns": 1, "PointId": 0, "PointSourceId": 30811, "Red": 0, "ReturnNumber": 1, "ScanAngleRank": 11, "ScanDirectionFlag": 0, "UserData": 2, "X": 123985.86, "Y": 480216.33, "Z": -4.21 } } }

So the first point height is -4.21 (below sealevel) and it’s classification is 1.

6] Visualize in

Using a 3D viewer (like we can visualize the cropped point cloud in 3D.

Open the ‘arena.laz’ file in with ‘choose data to display’ option.


To get the colors fiddle around with the parameters on the right side:

set intensity source -> heightmap greyscale

set colorization -> classification

set intensity blending -> slide halfway 

Another frequently used 3D desktop tool for pointclouds is CloudCompare (

The same method can be applied to another area or another set of pointcloud data so have fun with that Smile

How to debug your Unity3D Android application in Visual Studio

Here’s how to debug an Unity3D Android application on a real device (not in Unity3D editor) over WIFI in Visual Studio. This recipe is written for Unity 2017.4 and Visual Studio 2017, maybe it works also in other versions.


1] Connect your Android phone to the same WIFI station as your development computer
2] Connect your Android phone with USB cable to your development computer

Step 1]  Connect to device with ADB

  • Get the IP number of your Android phone  with command ‘adb shell ip addr show wlan0’:
$ adb shell ip addr show wlan0
 24: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 3000
 link/ether 2c:fd:a1:8c:dc:f1 brd ff:ff:ff:ff:ff:ff
 inet brd scope global wlan0
 valid_lft forever preferred_lft forever
 inet6 fe80::2efd:a1ff:fe8c:dcf1/64 scope link
 valid_lft forever preferred_lft forever

IP adress in this case is:

  • Make a connection using adb connect

If connect fails take a look at

$ adb connect
connected to
  •  Disconnect phone from USB cable
  • Check WIFI connection with to device with:
$ adb devices
 List of devices attached device

Step 2] Prepare debug version of Unity3D app

In Unity3D, go to Android build settings and check ‘Development Build’ and ‘Script Debugging’: Build_Settings.png

Now deploy and run your application (control-b). It’s important to run the application because otherwise the AndroidPlayer debugger will not show up in the next dialog.

Step 3] Set Visual Studio breakpoint and debug

Now in Visual Studio open a script and put a breakpoint.

Go to Debug -> Attach Unity debugger and a dialog with two debuggers should appear (one for Unity3D editor, one for Android device):


Select the ‘AndroidPlayer (USB)’ item and Visual Studio goes in debug mode. Note: This step can take a while if then Unity project is big.

Now run the application on the device and the breakpoint should be hit 🙂


Using .NET 4.6 librarieS in Unity3d 2018


16-1-2018: Updated post for Unity 2018

For a long time it has been a lot of hassle to use existing .NET libraries in Unity3D. Unity3D only supported .NET 2.0/3.5, so more recent libraries (4.0 and up) could not be used without recompiling/rewriting for .NET 2.0/3.5.

Also new C# language constructs (C# 6) could not be used by game developers  Sad smile

At the moment Unity3D is upgrading the Mono version to 4.6 – .NET Standard 2.0. The first beta’s of Unity 2018 with .NET 4.6 support are available for download so let’s take a test drive.

Installation Unity 2018.1.0b2: (Released: Released: January 10, 2018)

In the Unity 2018.1.0b2 editor .NET 4.6 is selectable at Edit –> Project settings –> Player –> Other settings –> Configuration


After selecting ‘Experimental (.NET 4.6 Equivalent) Unity has to be restarted  Sad smile

For the test I’ve created a sample project ( that does the following:

. get a location (latitude, longitude)

. Determine the OpenStreetMap tile on that location using .NET Standard library Tilebelt

. Download and display the tile on a Unity3D Plane GameObject.

Code sample:


void Start()
var coords = new double[] { -84.72, 11.17, -5.62, 61.60 };
var parent = Tilebelt.BboxToTile(coords);

private IEnumerator requestTile(Tile t)
var url = tile2url(t);
var handler = new DownloadHandlerBuffer();
var http = new UnityWebRequest(url);
http.downloadHandler = handler;
yield return http.Send();

    if (!http.isNetworkError)
var texture = new Texture2D(10, 5);
var tileGO = GameObject.CreatePrimitive(PrimitiveType.Plane);
var renderer = tileGO.GetComponent<MeshRenderer>();
renderer.material.mainTexture = texture;

private string tile2url(Tile tile)
return $”{tile.Z}/{tile.X}/{tile.Y}.png”;



The Tilebelt library ( is a small library with some utility functions for working with the OpenStreetMap tiling scheme. It is compiled for .NET Standard 1.1 with the following dependencies:

  • System.Collections (>= 4.3.0)
  • System.Runtime.Extensions (>= 4.3.0)
  • System.Resources.ResourceManager (>= 4.3.0)
  • System.Runtime (>= 4.3.0)

The Tilebelt.dll assembly we have to copy manually to the assets folder, unfortunately the NuGet support is not so great yet. Note: In previous Unity3D versions(2017), we had to copy the dependencies also).

Here a screenshot of the application build for Windows:


This is just a small example of using a .NET Standard 1.1 library in Unity3D. But the concept is working already,now we should be able to use other standard .NET libraries as well Smile

Visualizing terrains with Cesium

2018-11-26: For updated post see

Today we did some experiments to visualize terrains in Cesium, using Cesium Terrain Builder ( and Cesium Terrain Server (

Step 1] Get some raster data with height information

For this demonstration we use a raster file from the Dutch AHN2 in tif format with cells 0.5m X 0.5m, it can be obtained from  (500MB)

This image is about a part of Texel:


Step 2] Tile the raster with Cesium Terrain Builder

The original tif image is quite big (500MB) so we cut it into tiles using the  Cesium Terrain Builder. We use the tool ‘ctb-tile‘ for tiling inside a Docker container:

$ docker run -v  d:/gisdata/nederland/terrain:/data -ti -i homme/cesium-terrain-builder:latest bash

Now create an output directory ’tiles’ (in this example d:/gisdata/nederland/terrain/tiles)

Start generating the tiles with:

root@c1412e2fd51c:/# ctb-tile -o /data/tiles /data/i09bz1.tif


This takes some time to process (like 10 minutes), so grab a coffee and relax…

In the meantime binary .terrain files are generated for 20 zoom levels.


The terrain tiles are encoded in the so called ‘quantized-mesh-1.0 terrain format’, more info about this format at

Step 3: Run the Cesium Terrain Server

If all the tiles are generated we can start the Cesium Terrain Server. We use another Docker command for this:

$ docker run -p 9000:8000 -v d:/gisdata/nederland/terrain:/data/tilesets/terrain geodata/cesium-terrain-server

The server starts working on port 9000.


One tile we can retrieve from the server using a wget command:

$ wget http://localhost:9000/tilesets/tiles/0/0/0.terrain


Step 4: Visualize the terrain in Cesium

Cesium is a WebGL globe viewer that runs in a browser, see for more info.

Open apps/HelloWorld.html in an editor and change the part between script tags to:

Cesium.BingMapsApi.defaultKey = ‘Aht_LtBvKjJfKZu__96G0Uk15cTTM3HRZxp_MD9g69xyIYNtrnDNaRBUSEVDIICQ’;

var viewer = new Cesium.Viewer(‘cesiumContainer’, {
terrainExaggeration : 20.0

var terrainProvider = new Cesium.CesiumTerrainProvider({
url : ‘http://localhost:9000/tilesets/tiles

viewer.scene.terrainProvider = terrainProvider;

So we start a Cesium viewer with Bing maps, and use our local tiles as terrain provider for the heights.

Note in this viewer there is no file ‘layer.json’ needed.

Put the Cesium viewer on a webserver (for example caddy Start the webserver in directory  Cesium-1.36 (or other version).

$ cd Cesium-1.36

$ caddy
Activating privacy features… done.

Open a browser to http://localhost:2015/apps/HelloWorld.html and zoomin to western part of Texel.. If you change the perspective the terrain (hint use the alt key) should become visible:


If you inspect the browser web requests (F12) request to the local terrain server should be there (for example http://localhost:9000/tilesets/tiles/18/269017/208333.terrain).

Play with Docker in the browser

Want to experiment with this thing called Docker but don’t want to go through the hassle of installing it? Go to and you are good to go.

In this website you can spin up 5 instances of Linux running for 4 hours. Lets make a webserver running there and see how it all works.

First press ‘Add new Instance’ and a new machine start up.


The machine is running on ip number We need this number later on Star.

Now start a Nginx webserver on port 80:

$ docker run –name docker-nginx -p 80:80 -d nginx


We need one more thing, we want to test the webserver on some public address. With some Ngrok magic this is possible, but be sure to replace the ip address with your ip address Star.

$ docker run –net host -ti jpetazzo/ngrok http


A new server is started on, we can use this address in a browser to test it all.


And it all works Smile

More advanced Docker topics are now also possible, like running multiple containers with docker-compose or a cluster with Docker Swarm.

Getting started with ASP.NET Core and Docker

Here a post about getting started with .NET Core and Docker. Goal is to run a .NET webservice inside a Docker container.

Step 1] Get the stuff

a. Install .NET Core SDK

Get the .NET Core SDK at

There are installations for Windows, Mac and various Linux machines.

If installation goes well you should be able to run command ‘dotnet‘:


For fun try to run commands like ‘dotnet new‘, ‘dotnet restore‘ and ‘dotnet run‘ and a Hello World app should run:



b. Install Yeoman and ASP.NET generator

Yeoman is a command line tool for generating/scaffolding projects. Installation is easy: run ‘npm install -g yo’ (NodeJS should be installed).

After installation run the ‘yo’ command and install the aspnet generator:


c. Install Docker player

There are various methods to get Docker running on your machine, but most easiest is installing the Docker for Mac or Windows from . Warning: on Windows you need to have Windows 10 and Hyper-V enabled.

If installation goes well you can run the ‘Docker’ command:



Step 2: Create new application

Create a new application by running command ‘yo aspnet’ and choose ‘Web API Application’ option. Give the application a name and the application is created. Change the directory to the new application, run again ‘dotnet restore’ and ‘dotnet run’. Open a browser and navigate to ‘http://localhost:5000/api/values’‘ and the response should be: [“value1″,”value2”]




Step 3: Run from Docker

Now it’s time to run the application from Docker. The ASP.NET generator has also created a sample Dockerfile:

FROM microsoft/dotnet:latest

COPY . /app


RUN ["dotnet", "restore"]

RUN ["dotnet", "build"]

EXPOSE 5000/tcp

CMD ["dotnet", "run", "--server.urls", "http://*:5000"]

Run from the command line the following commands:

$ docker build -t mydemos:aspnetcorehelloworld .


A new Docker image is created containing our new application. Now start a new container by the following command:

$ docker run -d -p 8080:5000 -t mydemos:aspnetcorehelloworld


The container is started and should be available at port 8080:


Voila, we have a new ASP.NET Core application running from Docker!

Unit test Web Api methods with in-memory HttpServer

Here are the steps to test your Web Api methods with an in-memory webserver. We have a simple Web Api project with 1 method:
/api/test returns "hello".

This unit test method can be used in Web API v1. In Web Api v2 there are some other methods (with Owin).

  • Add new class project
  • Add a reference to the Web Api project to be tested
  • Iin the Nuget package window: install-package nunit (or install your favorite unit tester)
  • install-package Microsoft.AspNet.WebApi -version 4.0.30506.0
  • Add reference to System.Web.dll
  • Add a new Class with Unit test code:

    public class UnitTest
        public void Test()
            var config = new HttpConfiguration();
            config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", new { routetemplate = "Version", id = RouteParameter.Optional });

            var server = new HttpServer(config);
            var client = new HttpClient(server);

            // act
            var response = client.GetAsync("http://server/api/test").Result;

            Assert.IsTrue(response.StatusCode == HttpStatusCode.OK);
            var answer= response.Content.ReadAsStringAsync().Result;

In this test a HttpServer is started with the same configuration as the Web Api project, and a HttpClient is created. After that, a request is fired and the response
is analysed.
And the test should work 🙂 However I had some issues to get this working, in the response there was always a HTPP 404 not found result. This is solved by calling in the test first some method of the Web Api project (for example the configuration object).

Create PDF’s with ASP.NET Web Api

This blog describes how to generate PDF’s with ASP.NET Web Api using PdfSharp/MigraDoc.


Sample result:

http://servername/api/test.pdf?author=bert generates a pdf with  the authorname as parameter. This sample can be extended to create

complete dynamically  reports.


Sample code is available on


Step 1: Generate new empty ASP.NET project with Visual Studio


Step 2: Install Microsoft.AspNet.WebApi from NuGet


Step 3: Install ‘PDFsharp-MigraDoc-GDI’ from NuGet:



Step 4: Change web.config

Add the following to in web.config file in the system.webServer section:


<modules runAllManagedModulesForAllRequests="true"/>


This is needed because otherwise the IIS webserver will search for a static PDF file and not a dynamic one generated by ASP.NET.


Step 5: Add global.asax with the following content:


    public class Global : System.Web.HttpApplication
        public static void RegisterRoutes(RouteCollection routes)

                name: "IdWithExt",
                routeTemplate: "api/{controller}.{ext}");

                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }

        protected void Application_Start(object sender, EventArgs e)

            var pdfFormatter = new PdfMediaTypeFormatter();

In the Global.asax we register the PDF Mediatypeformatter, defined in the next step.


Step 6: Add a Mediatype formatter for PDF 

Add a new class ‘PdfMediaTypeFormatter’. This class overrides from MediaTypeFormatter. Important is the ‘SupportedType’, only controllers

who return the SupportedType will use this formatter. Another important parameter is the ‘object value’ in the WriteToStreamAsync method. This parameter

contains the specified PDF parameter (in our case the author name).


   public class PdfMediaTypeFormatter : MediaTypeFormatter
       private static readonly Type SupportedType = typeof(Person); 

       public PdfMediaTypeFormatter()
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/pdf"));
            MediaTypeMappings.Add(new UriPathExtensionMapping("pdf", "application/pdf"));

       public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, System.Net.Http.HttpContent content, TransportContext transportContext)
           var taskSource = new TaskCompletionSource<object>(); 
               var person = (Person)value; 

               var doc = PdfGenerator.CreatePdf(person.Name); 
               var ms = new MemoryStream();

                doc.Save(ms, false); 

               var bytes = ms.ToArray();
                writeStream.Write(bytes, 0, bytes.Length);
           catch (Exception e)
           return taskSource.Task;

       public override bool CanReadType(Type type)
           return SupportedType == type;

       public override bool CanWriteType(Type type)
           return SupportedType == type;

Step 7:  Add a simple test controller class.



This controller returns a Person class, which will be rendered using the PDF Media Type.

    public class TestController:ApiController
        public HttpResponseMessage GetTest(string author)
            var person=new Person{Name=author};
            return Request.CreateResponse(HttpStatusCode.OK,person);

Step 8: Add a Person class

    public class Person
        public String Name { get; set; }

Step 9: Add code to generate a PDF (class PdfGenerator).


This code creates an in-memory PDF and returns it.


    public static class PdfGenerator
        public static PdfDocument CreatePdf(string author)
            var document = new Document();
            var sec = document.Sections.AddSection();
            sec.AddParagraph("Author:" + author);
            return RenderDocument(document);

        private static PdfDocument RenderDocument(Document document)
            var rend = new PdfDocumentRenderer {Document = document};
            return rend.PdfDocument;


Step 10: Test!

Run the project and open http://localhost:64118/api/test.pdf?author=piet

A PDF file with the specified authorname will be returned if all goes well