SQL Extraction Pipeline¶
This section documents the SQL scripts that transform raw 3D building geometry from the CityDB database into a structured set of building features ready for TABULA classification.
The per-batch scripts in sql/scripts/main/ run in order within a batch of buildings, each reading the output of the previous one. Once every batch has finished, the post scripts in sql/scripts/post/ run once over the whole table, because they compare buildings across batches.
What it does¶
Given a batch of building IDs, the pipeline:
- Finds all surface polygons that belong to each building (roof, wall, ground faces), across every solid of the building.
- Flattens compound multi-polygon geometries into individual polygon faces.
- Computes a surface normal for each face, then derives tilt, azimuth, area, and height, and marks the faces that lie between two solids of the same building as internal.
- Aggregates those per-surface values into one row per solid, then one summary row per building.
- Approximates building volume from height × footprint area.
- Counts the storeys and sets the total floor area.
After all batches, over the whole table:
- Marks buildings that share a wall with a neighbour.
- Finds the part of each wall that lies against a neighbour's wall (party walls).
- Writes the served surface table: one row per exterior piece of each face, and one per party-wall piece, flagged.
- Recomputes each building's wall area and wall count without the party walls.
- Matches each building to its closest TABULA archetype using nearest-neighbour search in feature space.
Data flow¶
flowchart TD
A[("CityDB<br>(lod2 / lod3 schema)")] -->|script 01| B["_child_feature<br>One row per surface feature"]
B -->|script 02| C["_child_feature_geom_dump<br>One row per polygon face"]
C -->|script 03| D["_surface_raw<br>Tilt, azimuth, area, height,<br>internal area per face"]
D -->|script 04| P["_building_part<br>One row per solid"]
D -->|script 04| E["_building<br>Aggregated building summary"]
P -->|script 04| E
E -->|script 05| F["_building<br>Volume added"]
F -->|script 06| G["_building<br>Storeys + floor area refined"]
G -->|post 01| N["_building<br>Attached neighbours marked"]
N -->|post 02| Q["_surface_raw<br>Party-wall pieces split off"]
D -->|post 02| Q
Q -->|post 03| J["_surface<br>Exterior and party-wall pieces"]
J -->|post 04| W["_building<br>Wall area without party walls"]
W -->|post 05| H["_building<br>TABULA variant code assigned"]
I[("tabula.tabula_variant<br>(reference archetypes)")] -->|post 05| H
Script reference¶
| Script | Purpose | Writes to |
|---|---|---|
| 01 Get child features | Collect the surface features of every solid of each Building | _child_feature |
| 02 Dump geometry | Explode multi-polygon surfaces to individual polygon faces | _child_feature_geom_dump |
| 03 Surface attributes | Compute surface normal, tilt, azimuth, area, and height per face; mark faces between solids of a building as internal | _surface_raw |
| 04 Building features | Aggregate surface attributes into one row per solid, then one row per building | _building_part, _building |
| 05 Volume | Approximate building volume from height × footprint | _building (UPDATE) |
| 06 Storeys | Refine storey count; overwrite floor area as footprint × storeys | _building (UPDATE) |
| Post 01 Neighbour detection | Mark buildings that share a wall with another building, once over the whole table | _building (UPDATE) |
| Post 02 Party walls | Split each wall into the part shared with a neighbour and the exterior part | _surface_raw (UPDATE) |
| Post 03 Build surface | Serve every exterior and party-wall piece of every face | _surface |
| Post 04 Wall totals | Recompute area_total_wall, area_party_wall and surface_count_wall |
_building (UPDATE) |
| Post 05 TABULA labelling | Nearest-neighbour match to closest TABULA archetype, once over the whole table | _building (UPDATE) |
Key concepts¶
LoD (Level of Detail)¶
CityGML defines several levels of geometric detail for buildings. City2TABULA works with LoD2 (simple roof shapes, no interior) and LoD3 (detailed facades). Each LoD is stored in its own database schema (lod2 or lod3); the scripts use {lod_schema} as a placeholder that is substituted at runtime.
Buildings and BuildingParts¶
A building is a CityGML Building feature (objectclass 901), and every output row is keyed by the Building's own object_id. CityGML lets a Building carry its geometry itself or split it across BuildingPart features (902), and sources use that freedom differently:
| Source | Features owning a solid | Wall between two solids of one building |
|---|---|---|
| Bremen LoD2 | The Building; no parts | Not applicable |
| 3DBAG (NL) | Exactly one BuildingPart (<id>-0); the Building carries LoD0 only |
Not applicable |
| Vienna LoD2 | The Building when it has no parts, otherwise two or more BuildingParts | Modelled on both solids |
| Prague LoD3 | The Building and each of its BuildingParts | Omitted on both solids |
The pipeline applies one rule to all of them. A building's geometry is every lodNSolid owned by the Building or by any BuildingPart below it. A face of one solid that lies against a face of another solid of the same building is internal and is excluded from areas and from _surface; where a source omits those faces, nothing is found to exclude. A building's heights are the footprint-weighted mean of its solids' heights, so height × footprint and footprint × storeys equal the sums over its solids.
BuildingInstallation features (905) are not part of the envelope and are ignored. Faces between two different buildings (party walls) are a separate step, not yet wired in.
Surface types¶
Each building solid is decomposed into typed surface features:
| CityGML type | Meaning |
|---|---|
RoofSurface |
The roof polygon(s) |
WallSurface |
Facade/wall polygon(s) |
GroundSurface |
The building footprint polygon(s) on the ground |
Scripts 03 and 04 treat these three types differently. For example, tilt and azimuth are computed differently for roofs versus walls, and the footprint area comes from GroundSurface only.
Idempotency¶
Every INSERT script checks whether the building has already been processed and skips it if so. This means the pipeline is safe to re-run against a partially-processed batch without creating duplicate rows.
SQL templating¶
Scripts contain {placeholder} tokens that are resolved before execution:
| Placeholder | Example value |
|---|---|
{lod_schema} |
lod2 |
{lod_level} |
2 |
{city2tabula_schema} |
city2tabula |
{building_ids} |
(1, 2, 3, ...) |
{srid} |
25832 |