TOP10NL 3D brought to life with OSM Buildings

11 Dec 2014 Edward Mac Gillavry

At the end of November, we created an interactive map of the centre of Valkenburg (Limburg) based on the buildings in the TOP10NL 3D data set using OSM Buildings. In this experiment, we explain how we created the map and outline some recommendations for the publication of 3D data.

Valkenburg in 3D.

From geodatabase to PostGIS

The Netherlands' cadastre, land registry and mapping agency Kadaster published the TOP10NL 3D data as a compressed Esri geodatabase. You can actually open this file format without Esri ArcGIS software. The latest version of GDAL's OGR Simple Feature Library has a so-called driver to read files in this file format. Having decompressed the ZIP file, use the ogrinfo command to list information about the file:

ogrinfo -so Valkenburg.gdb
-so (summary only)
Use this to only show the summary information about a data set.

This command list the following summary about the file Valkenburg.gdb:

Had to open data source read-only.
INFO: Open of `Valkenburg.gdb'
      using driver `OpenFileGDB' successful.
1: terreinVlak (Multi Polygon)
2: waterdeelVlak (Multi Polygon)
3: wegdeelVlak (Multi Polygon)
4: gebouwVlak (Multi Polygon)
5: gebouwVlak_stat (Multi Polygon)
6: terreinpunten (3D Point)
7: wegdeelVlak_3D_LOD0 (3D Multi Polygon)
8: terreinVlak_3D_LOD0 (3D Multi Polygon)
9: gebouw_3D_LOD0 (3D Multi Polygon)
10: waterdeelVlak_3D_LOD0 (3D Multi Polygon)
11: gebouw_3D_LOD1 (3D Multi Polygon)
12: brugWater (3D Multi Polygon)
13: brugWeg (3D Multi Polygon)
14: TerreinOnder (3D Multi Polygon)

From the 14 layers in this file, the layer gebouw_3D_LOD0 is the most appropriate for our experiment with OSM Buildings. Again, we use the ogrinfo command to find out what information is available in this layer:

ogrinfo -so Valkenburg.gdb gebouw_3D_LOD0

One of the attributes in the layer gebouw_3D_LOD0 is HOOGTE (height). We use the ogrinfo command to find out the range of values for this attribute:

ogrinfo -sql 'SELECT DISTINCT HOOGTE FROM gebouw_3D_LOD0' Valkenburg.gdb

This command returns the following output:

Had to open data source read-only.
INFO: Open of `Valkenburg.gdb'
      using driver `OpenFileGDB' successful.

Layer name: gebouw_3D_LOD0
Geometry: None
Feature Count: 0
Layer SRS WKT:
(unknown)
HOOGTE: String (0.0)

Although the geometries of the buildings have been modeled as 3D multi-polygons, but the attribute HOOGTE does not contain any information! OSM Buildings requires a GeoJSON file representing the 2D geometry in WGS-84 coordinates and an attribute height, containing the actual building height in meters. We have to derive the height from the 3D geometry. PostGIS provides the spatial functions to achieve this. We use the ogr2ogr command to import the layer gebouw_3D_LOD0 from Valkenburg.gdb into the database top10nl using the valkenburg schema:

ogr2ogr -f "PostgreSQL" PG:"host=localhost port=5432 dbname=top10nl user=*** password=***" Valkenburg.gdb gebouw_3D_LOD1 -nln valkenburg.buildings -overwrite -progress --config PG_USE_COPY YES

Data prepartion in PostGIS

First, the geometries of the buildings have to be reprojected from the Dutch RD-coordinates to longitude and latitudes in WGS-84 (ST_Transform). In the next step, the 3D geometries have to be converted to 2D geometries (ST_Force_2D) in order to reduce the GeoJSON file size: OSM Buildings ignores the Z-coordinate anyway. Finally, we force the orientation of the vertices in the polygons to follow the Right-Hand-Rule (ST_ForceRHR). This ensures, that OSM Buildings can represent the walls of the buildings properly. Thank you very much for your suggestion, @OSMBuildings!

OSM Buildings suggests to change the orientation of the vertices in the polygons.

This is an easy job for PostGIS once you have identified the correct functions:

SELECT AddGeometryColumn ('valkenburg','buildings','geometry',4326,'MULTIPOLYGON',2);
UPDATE
  valkenburg.buildings
SET
  geometry = ST_Transform(ST_ForceRHR(ST_Force_2D(wkb_geometry)),4326);

The 3D geometries are not only input for creating the 2D geometries, but also for deriving the height. Using OGR, we already learned, that the attribute HOOGTE in the source data is empty. Furthermore, the attribute has been defined as an alphanumeric field (string). Again, we can address these issues using PostGIS. First, we change the definition of the attribute HOOGTE to a numeric field, i.e. an integer, to limit the size of the final GeoJSON file. Subsequently, we calculate the actual height difference, not the maximum height: the buildings are already situated at an altitude!

ALTER TABLE valkenburg.buildings ALTER COLUMN hoogte TYPE integer USING CAST(hoogte AS integer);
UPDATE valkenburg.buildings SET hoogte = ST_ZMAX(wkb_geometry) - ST_ZMIN(wkb_geometry);

Finally, we take OGR for another spin to convert the data from a table in PostGIS to a GeoJSON file. We do not only include the geometries and altitudes, but also the building types. We can leverage this attribute to graphically classify the buildings:

ogr2ogr -f "GeoJSON" buildings.js PG:"host=localhost port=5432 dbname=top10nl user=*** password=***" -sql "SELECT geometry, type_gebouw AS building_type, hoogte AS height FROM valkenburg.buildings" -lco COORDINATE_PRECISION=6
-lco (layer creation option) COORDINATE_PRECISION=6
This ensures that coordinates have just 6 decimals: sufficiently accurate for a web map!

Now we have a GeoJSON at last, that we can use in conjunction with OSM Buildings.

OSM Buildings

OSM Buildings is a JavaScript library to visualise buildings and currently works with OpenLayers 2.x and Leaflet. Indeed, the designation OSM in OSM Buildings stems from OpenStreetMap. By default, OSM Buildings use the building information from the OpenStreetMap-database for its visualisations, but also provides the option to visualise buildings from your own GeoJSON file. Its the latter option that we use in this experiment.

We paint the walls and roof tops different colours based on the building type attribute:

var osmb = new OSMBuildings(map)
    .each(function(item) {
        var prop = item.properties;
        if (Legend.hasOwnProperty(prop.building_type)) {
            prop.wallColor = Legend[ prop.building_type ]['wall'];
            prop.roofColor = Legend[ prop.building_type ]['roof'];
        } else {
            prop.wallColor = 'rgba(235,207,190,1.0)';
            prop.roofColor = 'rgba(235,207,190,1.0)';
        }
    })
    .set(BldgJSON);

The colours for the different building types have been defined in a legend object:

var Legend = {
    gemeentehuis: { wall: 'rgba(200,155,54,0.8)', roof: 'rgba(236,173,42,0.7)'}, //town hall
    huizenblok: { wall: 'rgba(232,184,174,0.8)', roof: 'rgba(232,184,174,0.7)'}, //terraced houses
    kasteel: {wall: 'rgba(153,51,0,0.8)', roof: 'rgba(153,51,0,0.7)'}, //castle
    overig: {wall: 'rgba(235,207,190,1.0)', roof: 'rgba(235,207,190,1.0)'}, //others
    politiebureau: { wall: 'rgba(200,155,54,0.8)', roof: 'rgba(236,173,42,0.7)'}, //police station
    postkantoor: {wall: 'rgba(200,155,54,0.8)', roof: 'rgba(236,173,42,0.7)'}, //post office
    ruïne: { wall: 'rgba(153,51,0,0.8)', roof: 'rgba(153,51,0,0.7)'},//ruin
    sporthal: { wall: 'rgba(137,159,165,0.8)', roof: 'rgba(137,159,165,0.7)'},//sports facility
    toren: { wall: 'rgba(153,51,0,0.8)', roof: 'rgba(153,51,0,0.7)'},//tower
    zwembad: { wall: 'rgba(137,159,165,0.8)', roof: 'rgba(137,159,165,0.7)'}//swimming pool
}

The various files are available as a gist on GitHub. This gives us the interactive map of Valkenburg with buildings in 3D:

Recommendations

Based on our experiences, we have three recommendations for supplying TOP10NL 3D:

  1. Populate the attribute HOOGTE (height): while the geometry is available in 3D, for most applications a height value is sufficient. We now derived the maximum height difference for each building, but we could also use the most frequently observed (modal) height instead. Also, define the attribute HOOGTE as a numeric field!
  2. Arrange the coordinates in such a way, that the vertices in a polygon follow the Right-Hand-Rule. This is a convention to describe vectors in 3 dimensions. The exterior ring is orientated in a clockwise direction and the interior rings in a counter-clockwise direction. When we used the original coordinate sequence from the source file, OSM Buildings painted the walls incorrectly. This may be the case for other graphical libraries as well.
  3. Remove physical descriptions from the attribute GEBOUW_TYPE (building type). In the source file, this attribute is a mixture of functional descriptions (town hall, police station, swimming pool) and more general descriptions (castle, ruin). The value religieus gebouw, toren (religious building, tower) indicates a physical attribute. However, in a 3D data set, this physical description has already been recorded implicitly.

In this lab experiment we focused on the buildings in TOP10NL 3D. To be honest, the release of TOP10NL 3D was first and foremost an opportunity to play with OSM Buildings without having to do too much data preprocessing. Nevertheless, working with 3D geographic data was an interesting ride. Let's be clear: we're only at the beginning of a 3D breakthrough!