TOP10NL 3D tot leven gebracht met OSM Buildings

12 dec 2014 Edward Mac Gillavry

Eind november maakten we een interactieve kaart van het centrum van Valkenburg (Limburg) op basis van de gebouwen uit TOP10NL 3D met behulp van OSM Buildings. In dit experiment leggen we uit hoe we deze kaart hebben gemaakt en doen enkele aanbevelingen voor het leveren van 3D-gegevens.

Valkenburg in 3D.

Van geodatabase naar PostGIS

Het Kadaster biedt het TOP10NL 3D bestand aan als een gecomprimeerde Esri geodatabase. Dit bestandsformaat kan je ook zonder Esri ArcGIS software openen. De meest recente versie van GDAL's OGR Simple Feature Library heeft een zogenaamde driver om bestanden van dit bestandsformaat te lezen. Na het decomprimeren van het ZIP-betand, kan met het commando ogrinfo informatie worden opgevraagd over het bestand:

ogrinfo -so Valkenburg.gdb
-so (summary only)
Hiermee zorg je ervoor, dat alleen een korte samenvatting wordt weergegeven.

Dit commando geeft de volgende samenvatting weer over het bestand 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)

Van de 14 thema's in dit bestand lijkt het thema gebouw_3D_LOD0 het meest geëigend voor ons experiment met OSM Buildings. We gebruiken opnieuw het commando ogrinfo om uit te vinden, welke informatie in dit thema beschikbaar is:

ogrinfo -so Valkenburg.gdb gebouw_3D_LOD0

Een van de eigenschappen van het thema gebouw_3D_LOD0 is HOOGTE. We gebruiken opnieuw het commando ogrinfo om uit te vinden welke waarden deze eigenschap heeft:

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

Dit commando geeft de volgende uitvoer:

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)

Weliswaar is de geometrie van de gebouwen gemodelleerd als 3D Multi-polygonen, maar de eigenschap HOOGTE bevat geen informatie! OSM Buildings heeft een GeoJSON-bestand nodig met de 2D-geometrie in WGS-84 coördinaten en met een eigenschap height, die de hoogte in meters bevat. Die hoogte zullen we dus zelf moeten afleiden uit de 3D-geometrie. Hiervoor gebruiken we de ruimtelijke functies van PostGIS. Met het ogr2ogr commando van OGR importeren we het thema gebouw_3D_LOD0 uit Valkenburg.gdb in de database top10nl met als schema valkenburg:

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

Gegevensbewerking in PostGIS

In de eerste plaats moet de geometrie van de gebouwen worden geherprojecteerd van RD-coördinaten naar lengte- en breedtegraden in WGS-84 (ST_Transform). Vervolgens wordt de 3D-geometrie teruggebracht naar 2D (ST_Force_2D) om de GeoJSON kleiner te maken: OSM Buildings negeert de Z-coördinaat toch. Ten slotte draaien we de volgorde van de coördinaten om (ST_ForceRHR). Dit zorgt ervoor, dat OSM Buildings de muren kan visualiseren. Dankjewel voor de tip, @OSMBuildings!

Suggestie van OSM Buildings om de volgorde van de coördinaten aan te passen.

Met de juiste functies is dit natuurlijk een koud kunstje voor PostGIS:

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

Naast de 2D-geometrie willen we ook de hoogte van de gebouwen afleiden uit 3D-geometrie in het originele bestand. Met OGR hadden we namelijk geconstateerd, dat de eigenschap HOOGTE leeg is. Bovendien is deze eigenschap in het originele bestand als alfanumeriek veld (string) gedefinieerd. Dit kunnen we met PostGIS oplossen. In de eerste plaats veranderen we de definitie van de eigenschap HOOGTE naar een numeriek veld tot op de meter om ruimte te besparen in het uiteindelijke GeoJSON-bestand. Vervolgens berekenen we dan het daadwerkelijke hoogteverschil, niet de maximale hoogte: de gebouwen zijn namelijk ook al op een hoogte geplaatst!

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);

Ten slotte gebruiken we weer OGR om de gegevens uit een tabel in PostGIS te converteren naar een GeoJSON-bestand. Als extra informatie nemen we het gebouwtype mee. Dit kunnen we gebruiken om gebouwen grafisch te classificeren:

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
Hiermee zorg je ervoor, dat je coördinaten slechts 6 decimalen hebben: nauwkeurig genoeg voor een webkaart!

Nu hebben we een GeoJSON-bestand, dat we kunnen gebruiken voor OSM Buildings.

OSM Buildings

Met de JavaScript-bibliotheek OSM Buildings kan je gebouwen visualiseren in combinatie met OpenLayers 2.x en Leaflet. Inderdaad, de aanduiding OSM in OSM Buildings komt van OpenStreetMap. OSM Buildings gebruikt namelijk standaard de gebouwen uit de OpenStreetMap-database voor haar visualisaties, maar biedt je ook de optie om op basis van een eigen GeoJSON-bestand gebouwen te visualiseren. Deze laatste optie gebruiken we hier.

Op basis van het gebouwtype (de eigenschap building_type) geven we muren en de daken een andere kleur:

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);

De kleuren voor de verschillende gebouwtypes hebben we in een legenda-object gedefinieerd:

var Legend = {
    gemeentehuis: { wall: 'rgba(200,155,54,0.8)', roof: 'rgba(236,173,42,0.7)'},
    huizenblok: { wall: 'rgba(232,184,174,0.8)', roof: 'rgba(232,184,174,0.7)'},
    kasteel: {wall: 'rgba(153,51,0,0.8)', roof: 'rgba(153,51,0,0.7)'},
    overig: {wall: 'rgba(235,207,190,1.0)', roof: 'rgba(235,207,190,1.0)'},
    politiebureau: { wall: 'rgba(200,155,54,0.8)', roof: 'rgba(236,173,42,0.7)'},
    postkantoor: {wall: 'rgba(200,155,54,0.8)', roof: 'rgba(236,173,42,0.7)'},
    ruïne: { wall: 'rgba(153,51,0,0.8)', roof: 'rgba(153,51,0,0.7)'},
    sporthal: { wall: 'rgba(137,159,165,0.8)', roof: 'rgba(137,159,165,0.7)'},
    toren: { wall: 'rgba(153,51,0,0.8)', roof: 'rgba(153,51,0,0.7)'},
    zwembad: { wall: 'rgba(137,159,165,0.8)', roof: 'rgba(137,159,165,0.7)'}
}

De benodigde bestanden zijn beschikbaar als een gist op GitHub. Zo krijgen we uiteindelijk de interactieve kaart van Valkenburg met gebouwen in 3D:

Aanbevelingen

Op basis van onze ervaringen hebben we drie aanbevelingen voor het leveren van TOP10NL 3D:

  1. Gebruik het veld HOOGTE: weliswaar is de geometrie beschikbaar in 3D, maar voor de meeste toepassingen is een hoogte al voldoende. We hebben nu het maximale hoogteverschil op basis van de 3D-geometrie gebruikt, maar je kan je ook voorstellen, dat je hiervoor bijvoorbeeld de meest voorkomende (modale) hoogte gebruikt. Definieer ten slotte de eigenschap HOOGTE als een numeriek veld!
  2. Maak de volgorde van de coördinaten rechtsdraaiend. Dit is een conventie om vectoren in 3 dimensies vast te leggen. De buitenste ring van een polygoon draait dan met de klok mee en de binnenringen draaien dan tegen de klok in. Gebruikten we de geometrienotatie 1:1 uit het bronbestand, dan tekende OSM Buildings de muren niet correct. Dit is wellicht ook voor andere grafische bibliotheken het geval.
  3. Verwijder fysieke kenmerken uit de eigenschap GEBOUW_TYPE. In het bronbestand is deze eigenschap een mengeling van gebruiksfuncties (gemeentehuis, politiebureau, sporthal) en algemenere omschrijvingen (kasteel, ruïne). De waarde religieus gebouw, toren voor de eigenschap GEBOUW_TYPE duidt weer op een fysiek kenmerk. Dit fysieke kenmerk is in een 3D-bestand natuurlijk al impliciet vastgelegd.

In dit lab-experiment hebben we ons gericht op de gebouwen in TOP10NL 3D. Het vrijgeven van TOP10NL 3D was eerlijk gezegd vooral een aanleiding om eens met OSM Buildings aan de slag te gaan zónder eerst veel gegevensbewerking te moeten doen. Desalniettemin hebben we de smaak te pakken. Het is duidelijk: we staan aan het begin van een 3D-doorbraak!