Give your data depth:
from 2D to 3D with OS tools

· Workshop 3D Tiles ·

After this workshop
  • You have knowledge of the 3D Tiles standard
  • You can create 3D tilesets from 2D data
  • You can load a 3D tileset into a CesiumJS web environment
  • You are familiar with the data visualisation possibilities of CesiumJS
Requirements
  • Laptop with internet connection
  • Docker
  • QGIS 3.40 or higher

Program

  1. Introduction 3D Tiles
  2. Exercise 1: Convert 3D BAG Buildings to 3D Tiles (Mesh)
  3. Exercise 2: Convert Trees to 3D Tiles (Instancing)
  4. Exercise 3: Place an animated Wind Turbine
  5. Exercise 4: Place street lights from the BGT as Instanced 3D Tiles 🌶️

Workshop workflow

%%{init: {'flowchart': {'htmlLabels': true}}}%% graph LR A[Import data] --> B[Prepare data] B --> C[Generate tiles] C --> D[Serve] D --> E[Visualise] classDef step fill:#06D6A0,stroke:#333,stroke-width:2px,color:#000; class A,B,C,D,E step;

3D Tiles: an OGC standard

  • Open standard for visualising (large amounts of) 3D geodata on the web
  • Developed by Cesium and standardised by OGC
  • Hierarchical data structure (spatial tiling)
  • Supports various formats:
    • Batched 3D Models (b3dm)
    • Instanced 3D Models (i3dm)
    • Point Clouds (pnts)
    • Composite (cmpt)
3D Tiles Ecosystem

For a detailed overview of the 3D Tiles specification, see: 3D Tiles Reference Card

Why are 3D Tiles useful?

  • Interoperability
    • Open standard
    • Supported by various viewers (Cesium, ArcGIS, QGIS, Unity3D, Unreal, Godot)
    • New: 3D Tiles in BabylonJS, Maplibre, Giro3D, ITowns
  • Streaming of 3D content
    • Only the necessary data is loaded
    • Progressive loading of data
    • Efficient for web-based applications
  • Styling capabilities
    • 3D Tiles Styling Language

Let's get started!

  • Case: Proefpolderdijk near Andijk
  • Data:
    • The 3D BAG for buildings
    • DTB for trees and other objects
    • Wind turbine 3D model (animated)
    • Street lights from the BGT as Instanced 3D Tiles
The final result of the digital twin of Andijk

Docker environment

Note: Make sure Docker (https://docs.docker.com/get-started/) is installed and running before continuing.

docker ps
Docker ps Result

To start the Docker environment, two files are needed: docker-compose.yml and Dockerfile. These files are in the workshop repository.

Copy these files to an empty directory on your local machine, for example c:\3dtiles-workshop. An easy way to do this is to right-click the link and choose 'Save link as...'

Tip: An alternative way to retrieve the two files is to clone the entire repository via 'git clone'

Open a terminal (cmd, PowerShell or Git Bash) and navigate to the directory where you saved the files

Tip: For ARM64 use docker-compose-arm64.yml and Dockerfile-arm64

Setting up workspace with Docker

To set up the workspace, run the following command from the directory where the files docker-compose.yml and Dockerfile are located:

docker compose up -d
Docker Compose Up Result

2 containers will be started:

  • postgis: a PostgreSQL database with PostGIS extension
  • workspace: a container with all necessary tools installed (alpine)

The two containers run in a separate network and can communicate with each other. The database is accessible from the host machine via port 5439, the workspace container is accessible via port 8080 (for the web server we will use later).

Tip:For ARM64 use:

docker compose -f docker-compose-arm64.yml up -d 

Tip: Use the command docker compose down to stop and remove the containers.

Docker containers

Docker Containers

QGIS connection to database

Use username: postgres | password: postgres

QGIS Database new Connection QGIS Database Connection

Connecting to the workspace

  1. Use the following command to open a shell connection to the workspace container:

    docker exec -it workspace bash
  2. Docker Exec Result
  3. You will land in the /workspace directory. This folder is directly linked via a volume mapping to the ./container folder on your local machine. All files you place in ./container are also available in /workspace, and vice versa.

Tip: Use the command exit or Ctrl + D to disconnect from the container.

Exercise 1: 3D BAG Buildings

In this part we will:

  1. Download and import 3D BAG buildings
  2. Create 3D Tiles from 3D BAG buildings (Mesh)
  3. Visualise tileset

Tip: See folder 'resultaten' for pre-created tilesets

Download and import 3D BAG

  1. Go to https://3dbag.nl/en/download and click the 'Pick a tile' button. Select the map sheet containing Andijk (sheet 8-512-888), click 'Confirm Selection' and download the BAG data (GeoPackage format).

  2. Download the data (8-512-888.gpkg) and place it in the ./container folder.
  3. Inspect the contents with GDAL (from your container shell):
ogrinfo 8-512-888.gpkg

The GeoPackage contains 7 layers, we now only use lod22_3d:

  • lod12_3d (3D Multi Polygon)
  • lod12_2d (Polygon)
  • lod13_3d (3D Multi Polygon)
  • lod13_2d (Polygon)
  • lod22_3d (3D Multi Polygon)
  • lod22_2d (Polygon)
  • pand (Polygon)

Question: what is the projection of the files?

Andijk

Import data into PostgreSQL

Now we will load the 3D BAG into the database. Run the following commands from the container shell.

ogr2ogr -f "PostgreSQL" PG: 8-512-888.gpkg lod22_3d -nln public.panden_andijk -nlt MULTIPOLYGONZ

Create spatial index:

psql -c "CREATE INDEX ON public.panden_andijk USING gist(st_centroid(st_envelope(geom)));" 

Check that the data was loaded correctly (990 buildings):

psql -c "SELECT count(*) from public.panden_andijk;"

Create 3D Tiles from 3D BAG Buildings

pg2b3dm --connection "Host=postgis" -t public.panden_andijk -a identificatie -o ./output/panden -c geom

Result: a tileset.json file, a 'content' directory with the 3D tiles in glTF 2.0 format and a subtree folder.

b3dm Tileset

Visualise tilesets - set up web server

A web server is installed in the Docker container. The web server serves all files from the /workspace folder. The server runs by default on port 8080.

Start the server:

http-server

View 3D Tiles in a CesiumJS Viewer

We will now use the CesiumJS Viewer to view the 3D Tiles.

Copy index.html to the container folder. This index.html page contains Javascript code that loads 3D data using the CesiumJS library.

The code loads 4 tilesets:

  • PDOK background map (grey)
  • PDOK quantized mesh terrain model
  • 3D BAG buildings

Open a browser and go to http://localhost:8080

Click on a building to view details; the 'identificatie' attribute will be shown.

Cesium Viewer

Exercise 2: Trees from DTB (Digital Topographic Bestand)

In this exercise we will:

  1. Download and import DTB
  2. Create 3D Tiles from trees
  3. Visualise tileset

Tip: See folder 'resultaten' for pre-created tilesets

Download and import data

  1. Search via the RWS DTB Sheet Index viewer for the Andijk map sheet (DTB sheet d15cz)
  2. Download the data (d15cz.zip) and place the ZIP in the ./container folder.
  3. Inspect the contents with GDAL (from your container shell):
ogrinfo /vsizip/d15cz.zip

The zip contains 3 shapefiles:

  • d15cz_lin.shp (DTB lines)
  • d15cz_reg.shp (DTB polygons)
  • d15cz_sym.shp (DTB points)

Good to know: the /vsizip/ prefix before the file path in ogrinfo allows you to inspect ZIPs.

Question: what is the projection of the files?

Andijk

Import data into PostgreSQL

Now we will load the DTB point data into the database. Run the following command from the container shell.

ogr2ogr -f "PostgreSQL" PG: /vsizip/d15cz.zip/d15cz_sym.shp -nln public.dtb_punt_andijk -nlt POINTZ

Check that the data was loaded correctly (15318 points):

psql -c "SELECT count(*) from public.dtb_punt_andijk;"

Create spatial index + view

Create a spatial index for the DTB points with the following SQL command:

psql -c "CREATE INDEX ON public.dtb_punt_andijk USING gist(wkb_geometry)"

Create a database view for trees only:

psql -c "CREATE or replace view public.v_dtb_punt_andijk AS
SELECT
    RANDOM()*360 AS yaw,
    0 AS pitch,
    0 AS roll,
    (RANDOM()*1.5)+0.5 AS scale,
    json_build_array(json_build_object('dtb id',dtb_id),
    json_build_object('omschrijving',omschr),
    json_build_object('datum',datum)) AS tags,
    'tree.glb' AS model,
    wkb_geometry AS geom
FROM public.dtb_punt_andijk
WHERE omschr = 'Boom'"

Good to know: we give the trees a random rotation and scale to add some variation. Also note that we add 'tree.glb' as a string, which links the 3D model for the tree.

Create Instanced 3D Tiles from trees

We can draw a 3D model (e.g. a 3D tree model) at each point.

Copy tree.glb to the container directory.

i3dm.export -c "Host=postgis;Username=postgres;Password=postgres;Database=postgres;Port=5432" -t public.v_dtb_punt_andijk -o ./output/dtb_punten --use_gpu_instancing true

Result: a tileset.json file, a 'content' directory with the instance3D tiles in glTF 2.0 format, and a subtree folder.

Tree Model

View Instanced 3D Tiles in a CesiumJS Viewer

We will now use the CesiumJS Viewer to view the 3D Tiles of the trees.

In index.html add the following code under // todo: add code here


                        const tilesetDtbBomen = await Cesium.Cesium3DTileset.fromUrl("output/dtb_punten/tileset.json");
                        viewer.scene.primitives.add(tilesetDtbBomen);

Start the web server in the container:

http-server

Open a browser and go to http://localhost:8080

Click on a tree to view details; the attributes 'dtb id', 'omschrijving' and 'datum' will be shown.

Cesium Viewer

Exercise 3: Adding 3D models

Copy windturbine.glb to the container directory.

Add the following code to index.html

const windturbine = viewer.entities.add({ 
  position: Cesium.Cartesian3.fromDegrees(5.193486,52.754867), 
  model: { 
    uri: "windturbine.glb"         
  }, 
});
Windturbine Windturbine Animated

🌶️Exercise 4: Adding streetlights from BGT🌶️

Try to add streetlights from the BGT as Instanced 3D Tiles. The workflow is similar to the trees from the DTB, but now you need to use the BGT data and create a different database view for the streetlights.

Source data: https://app.pdok.nl/lv/bgt/download-viewer/ (choose GML Light) - dataset bgt_paal.gml

DEM data: get from https://www.ahn.nl/dataroom

Put street lights on correct elevation using QGIS Drape (set z value from raster)

In database view use plus_type = 'lichtmast', as model use 'pole.glb'

Bonus: rotate street lights towards the road using dataset bgt_wegdeel.gml

BGT Pole Model

Cleanup after the workshop

Run the following commands to stop and remove the Docker containers:

docker compose down

Remove docker images:

docker rmi 3dtiles_workshop-workspace
docker rmi postgis/postgis

Remove database data:

docker volume rm 3dtiles_workshop_postgis_data

Wrap-up

What have we learned?

  • Knowledge of the 3D Tiles standard
  • Insight into the software and techniques for creating 3D tilesets
  • Loading a 3D tileset in a web environment

Sources and links