DROP TABLE IF EXISTS buildings CASCADE; CREATE TABLE buildings AS WITH raw AS ( SELECT osm_id, way, NULLIF(building, '') AS building_type, substring(replace(COALESCE(tags -> 'height', ''), ',', '.') FROM '[-+]?[0-9]+(\.[0-9]+)?')::real AS height_value, substring(replace(COALESCE(tags -> 'building:levels', ''), ',', '.') FROM '[-+]?[0-9]+(\.[0-9]+)?')::real AS levels_value FROM planet_osm_polygon WHERE building IS NOT NULL AND building <> '' AND building <> 'no' ), normalized AS ( SELECT osm_id, ST_SetSRID( ST_Multi(ST_CollectionExtract(ST_MakeValid(way), 3)), 4326 ) AS geom, building_type, height_value, levels_value FROM raw ) SELECT osm_id, geom, CASE WHEN height_value IS NOT NULL AND height_value > 0 THEN height_value WHEN levels_value IS NOT NULL AND levels_value > 0 THEN levels_value * 3.0 WHEN building_type IN ('garage', 'garages', 'shed') THEN 3.0 WHEN building_type IN ('industrial', 'warehouse') THEN 8.0 WHEN building_type IN ('church', 'cathedral') THEN 12.0 ELSE 9.0 END::real AS height_m, CASE WHEN levels_value IS NOT NULL AND levels_value > 0 THEN levels_value::integer ELSE NULL END AS levels, building_type, CASE WHEN height_value IS NOT NULL AND height_value > 0 THEN 'measured' ELSE 'estimated' END AS source FROM normalized WHERE NOT ST_IsEmpty(geom); ALTER TABLE buildings ALTER COLUMN geom TYPE geometry(MultiPolygon, 4326) USING ST_SetSRID(geom, 4326); CREATE INDEX IF NOT EXISTS buildings_geom_gix ON buildings USING GIST (geom); CREATE INDEX IF NOT EXISTS buildings_height_idx ON buildings (height_m); CREATE INDEX IF NOT EXISTS buildings_type_idx ON buildings (building_type); ANALYZE buildings;