Example: 3D Building Map
You need at least osm2pgsql version 1.7.0 for this example.
For some buildings OSM data contains height information. Sometimes it is
specified directly in the
height
tag, usually in
meters. If not, we can have a look at the
building:levels
tag
which sometimes contains the number of levels a building has. Assuming a level
is on average 4 meters high, we can estimate the building height from that.
In a real world application you’d have to look into more details of those and other tags, the height is sometimes given in feet for instance. But for this demo this simplified setup should suffice.
To get some nice sample data, lets look at New York. You can use the Protomaps
download service to get the extract.
Zoom to New York and draw a polygon around the tip of Manhattan, add the name
“newyork” and click on “Create Extract”. In a few seconds you’ll get a download
button to download a file newyork.osm.pbf
.
Create a database buildings
and import the data:
osm2pgsql -d buildings -O flex -S 3dbuildings.lua newyork.osm.pbf
You can visualize this with QGIS by adding the buildings
layer as usual,
then choosing “2.5 D” as symbolization in the layers Symbology dialog. Choose
the “height” attribute in the “Height” dropdown and play around with the rest
of the settings.
Here is the configuration file you’ll need:
local buildings = osm2pgsql.define_area_table('buildings', {
-- Define an autoincrementing id column, QGIS likes a unique id on the table
{ column = 'id', sql_type = 'serial', create_only = true },
{ column = 'height', type = 'real', not_null = true },
{ column = 'geom', type = 'polygon', not_null = true },
})
-- calculate the height of the building from the tags
function calc_height(tags)
-- if an explicit 'height' tag is available use that
-- could be extended to also allow height in feet
if tags.height then
local h = tonumber(tags.height)
if h and h > 0 and h < 900 then
return h
end
end
-- use 'building:levels' tag and assume each level is 4 meters high
local levels = tags['building:levels']
if levels then
local l = tonumber(levels)
if l and l > 0 and l < 200 then
return l * 4
end
end
-- fall back to default height if we can't find any useful tags
return 12
end
function add_building(tags, geom)
buildings:insert({
height = calc_height(tags),
geom = geom
})
end
function osm2pgsql.process_way(object)
if object.is_closed and object.tags.building then
add_building(object.tags, object:as_polygon())
end
end
function osm2pgsql.process_relation(object)
if object.tags.type == 'multipolygon' and object.tags.building then
geom = object:as_multipolygon()
for g in geom:geometries() do
add_building(object.tags, g)
end
end
end