Using GDAL in .NET Core

GDAL(, Geospatial Data Abstraction Library) is a frequently used geospatial library for manipulating geospatial data. The library is written in C++.

To use the GDAL library in .NET Core there are some NuGet packages available:

For Windows (on

For Linux (on  MyGet): (Gdal.Core 2.3.0-beta-024-1801) (Gdal.Core.LinuxRuntime 2.3.0-beta-024-1840)

As we want to build are projects cross platform, we want to use the Windows NuGet packages when building on Windows, and use the Linux MyGet packages when building on Linux. How to do that?

First we need to add NuGet and MyGet to the ‘RestoreSources’ in the project file:

<RestoreSources>             $(RestoreSources);;

Second we need a conditional PackageReference depending on operating system:

<ItemGroup Condition="’$(OS)’ == ‘Unix’">
<PackageReference Include="Gdal.Core" Version="2.3.0-beta-024-1801" />
<PackageReference Include="Gdal.Core.LinuxRuntime" Version="2.3.0-beta-024-1840"/>
<ItemGroup Condition="’$(OS)’ != ‘Unix’">
<PackageReference Include="Gdal.Core" Version="2.3.0-beta-023"/>
<PackageReference Include="Gdal.Core.WindowsRuntime" Version="2.3.0-beta-023"/>

And now we can write code using GDAL functionality. For example load a GeoJSON file:


var geojsonDriver = Ogr.GetDriverByName("GeoJSON");
var ds = geojsonDriver.Open(@"gemeenten2016.geojson", 0);
var layer1 = ds.GetLayerByName("gemeenten2016");
var features = layer1.GetFeatureCount(0);
Console.WriteLine("features in geojson: " + features);

Or we can do other GDAL stuff like coordinate transformations. This trick also works in Docker, just remember to install ‘gdal-bin’ and ‘libproj-dev’ on the system when on Linux. For a complete sample console application see

Dockerizing a Cesium node.js app

Today I found on GitHub a repository containing some samples for learning how to use Cesium 3D Tiles (

The sample we’re trying to run is the sample with the trees:


Getting the samples running on a machine can be a complicated task, because all the development dependencies have to be installed correctly.

With some Docker magic getting the samples up and running  will be really easy, so let’s investigate how to do this.

When inspecting the code (see, its clear that it’s a Node.js app (see package.json), running on port 8003 (see server.js).

I’ve forked the repository (see, and made the following changes:

– added index.html (, containing the sample app;

– added  a Dockerfile (see


The Dockerfile does the following things:

– It’s based on node:10 image;

– it copies package.json and runs ‘npm install’ to get all the dependencies;

– It copies the application files (index.html, index.js, server.js and a tileset with trees)

– It opens port 8004 (the sample now will run on 8004 instead of 8003 as before)

– It starts the application (with ‘npm start’)


To build the image run on a terminal:

$ docker build -t bertt/cesium_trees .


To run the image:

$ docker run -it -p 8004:8004 bertt/cesium_trees


Navigate to http://localhost:8004 and the application will show up:


The Docker image is published on Docker hub ( so you can also get the application from there.

Stay hydrated with OpenStreetMap

Now holiday season is nearing it’s time for preparing your hiking/biking trip. One essential thing is to stay hydrated so we need to know all the waterpoints in the destination area.

One good option is to  extract waterpoints from OpenStreetMap and get the data on the mobile.

The process is very simple:

– go to

– Navigate to your favorite area.

– On the left side there is already a query for waterpoints in the area of interest:


– Press ‘run’ button on the upperleft and all the water points will be shown as blue dots.


– Press Export and formats like GeoJSON/GPX/KML will show up. 


– Personally I like to use MAPS.ME app ( on the phone because it offers offline OpenStreetMap maps. MAPS.ME can import KML files, so let’s choose this format for export. E-mail yourself the KML file, open the KML on the Phone and the waterpoints will appear in MAPS.ME.


Sometimes you have to search around a bit to discover a waterpoint so have fun with that. And the good thing with OpenStreetMap is that if you’re missing points or they are located wrongly, you can edit the database yourself! See


Routing with OSRM

OSRM ( is an opensource routing machine written in C++. It can create routes and turn-by-turn instructions from freely available OpenStreetMap data. OSRM runs in Docker containers so let’s see how it works. Lets suppose we want to bike in Utrecht from west to east, but we need a good route.

To get started we need to download some OpenStreetMap data, we’ll use the files on for that. Lets download the Utrecht OSM file (70MB).

$ wget

Now there are some processing steps, basically building a network from the OpenStreetMap data. There are 3 steps: extract, partition and customize.

Only the first step (extract) takes a bit longer (like a few minutes), other steps (partition, customize) are quick.

// extract

In the extract step we only are interested in biking routes (parameter -p /opt/bicycle.lua). Other options are foot or car.

$ docker run -t -v d:/proj/osrm:/data osrm/osrm-backend osrm-extract -p /opt/bicycle.lua /data/utrecht-latest.osm.pbf

// partition

$ docker run -t -v d:/proj/osrm:/data osrm/osrm-backend osrm-partition /data/utrecht-latest.osrm

// customize

$ docker run -t -v d:/proj/osrm:/data osrm/osrm-backend osrm-customize /data/utrecht-latest.osrm

Start a webserver on port 5000 with osrm-routed. Note the ‘algorithm’ parameter (‘mld’): mld is ‘multi level dijkstra’, other option is Contraction Hierarchies (CH).

$ docker run -t -i -p 5000:5000 -v d:/proj/osrm:/data osrm/osrm-backend osrm-routed –algorithm mld /data/utrecht-latest.osrm

And fire a request to bike from the west-side of Utrecht (4.9599903,52.0908023) to the east-side (5.1851206,52.07908):

$ curl http://localhost:5000/route/v1/driving/4.9599903,52.0908023;5.1851206,52.0790854?overview=full&geometries=geojson

A partly GeoJSON formatted result is returned, after some reshuffling omsmurfen we can visualize the route on :


There are a zillion other options in OSRM to play with,  so good luck with that Smile


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 with case-sensitive url 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 (2), water (9), buildings (6), artificial objects (26), vegetation, and unclassified (1). 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).