fifteen/infra/osm2pgsql.lua
2026-03-01 21:59:44 +01:00

153 lines
6.3 KiB
Lua

-- osm2pgsql Flex output script for Transportationer
-- Maps OSM tags to the 5 POI categories
-- Write to a per-city staging table so osm2pgsql can freely drop/recreate it
-- in create mode without touching other cities' rows in raw_pois.
-- extract-pois.ts merges the staging data into raw_pois after the import.
local city_slug = os.getenv('CITY_SLUG') or 'unknown'
local staging_name = 'raw_pois_import_' .. city_slug:gsub('[^%w]', '_')
local pois = osm2pgsql.define_table({
name = staging_name,
ids = { type = 'any', id_column = 'osm_id', type_column = 'osm_type' },
columns = {
{ column = 'city_slug', type = 'text' },
{ column = 'category', type = 'text' },
{ column = 'subcategory', type = 'text' },
{ column = 'name', type = 'text' },
{ column = 'tags', type = 'jsonb' },
{ column = 'geom', type = 'point', projection = 4326 },
}
})
-- Tag → {category, subcategory} mapping
local tag_map = {
amenity = {
-- service_trade
pharmacy = { 'service_trade', 'pharmacy' },
bank = { 'service_trade', 'bank' },
atm = { 'service_trade', 'atm' },
cafe = { 'service_trade', 'cafe' },
restaurant = { 'service_trade', 'restaurant' },
fast_food = { 'service_trade', 'restaurant' },
post_office = { 'service_trade', 'post_office' },
marketplace = { 'service_trade', 'market' },
-- transport
bicycle_rental = { 'transport', 'bike_share' },
car_sharing = { 'transport', 'car_share' },
ferry_terminal = { 'transport', 'ferry' },
-- work_school
kindergarten = { 'work_school', 'kindergarten' },
school = { 'work_school', 'school' },
university = { 'work_school', 'university' },
college = { 'work_school', 'university' },
driving_school = { 'work_school', 'driving_school' },
-- culture_community
library = { 'culture_community', 'library' },
theatre = { 'culture_community', 'theatre' },
cinema = { 'culture_community', 'theatre' },
community_centre= { 'culture_community', 'community_center' },
place_of_worship= { 'culture_community', 'place_of_worship' },
hospital = { 'culture_community', 'hospital' },
clinic = { 'culture_community', 'clinic' },
doctors = { 'culture_community', 'clinic' },
social_facility = { 'culture_community', 'social_services' },
townhall = { 'culture_community', 'government' },
police = { 'culture_community', 'government' },
-- recreation
swimming_pool = { 'recreation', 'swimming_pool' },
},
shop = {
supermarket = { 'service_trade', 'supermarket' },
convenience = { 'service_trade', 'convenience' },
bakery = { 'service_trade', 'cafe' },
pharmacy = { 'service_trade', 'pharmacy' },
laundry = { 'service_trade', 'laundry' },
dry_cleaning = { 'service_trade', 'laundry' },
greengrocer = { 'service_trade', 'market' },
butcher = { 'service_trade', 'market' },
},
highway = {
bus_stop = { 'transport', 'bus_stop' },
},
railway = {
station = { 'transport', 'train_station' },
halt = { 'transport', 'train_station' },
tram_stop = { 'transport', 'tram_stop' },
subway_entrance = { 'transport', 'metro' },
},
-- public_transport stop_position/platform intentionally excluded:
-- they duplicate highway=bus_stop / railway=tram_stop nodes at the same
-- physical location and would double-count those stops in scoring.
office = {
coworking = { 'work_school', 'coworking' },
company = { 'work_school', 'office' },
government = { 'work_school', 'office' },
},
tourism = {
museum = { 'culture_community', 'museum' },
},
leisure = {
park = { 'recreation', 'park' },
playground = { 'recreation', 'playground' },
sports_centre = { 'recreation', 'sports_facility' },
fitness_centre = { 'recreation', 'gym' },
swimming_pool = { 'recreation', 'swimming_pool' },
garden = { 'recreation', 'park' },
nature_reserve = { 'recreation', 'green_space' },
pitch = { 'recreation', 'sports_facility' },
arts_centre = { 'culture_community', 'community_center' },
},
landuse = {
commercial = { 'work_school', 'office' },
office = { 'work_school', 'office' },
recreation_ground = { 'recreation', 'green_space' },
grass = { 'recreation', 'green_space' },
meadow = { 'recreation', 'green_space' },
forest = { 'recreation', 'green_space' },
},
}
local CITY_SLUG = city_slug
local function classify(tags)
for key, values in pairs(tag_map) do
local val = tags[key]
if val and values[val] then
return values[val][1], values[val][2]
end
end
return nil, nil
end
local function get_centroid(object)
if object.type == 'node' then
return object:as_point()
elseif object.type == 'way' then
local geom = object:as_linestring()
if geom then return geom:centroid() end
else
local geom = object:as_multipolygon()
if geom then return geom:centroid() end
end
return nil
end
local function process(object)
local category, subcategory = classify(object.tags)
if not category then return end
local geom = get_centroid(object)
if not geom then return end
pois:insert({
city_slug = CITY_SLUG,
category = category,
subcategory = subcategory,
name = object.tags.name,
tags = object.tags,
geom = geom,
})
end
function osm2pgsql.process_node(object) process(object) end
function osm2pgsql.process_way(object) process(object) end
function osm2pgsql.process_relation(object) process(object) end