[Tile-serving] [osm2pgsql-dev/osm2pgsql] Some postal_code boundaries of OSM fail to import using as_multipolygon() (Issue #2414)
Marc Erdmann
notifications at github.com
Tue Sep 9 15:32:33 UTC 2025
MarcErdmann created an issue (osm2pgsql-dev/osm2pgsql#2414)
## What version of osm2pgsql are you using?
osm2pgsql version 2.1.1
Build: Release
Compiled using the following library versions:
Libosmium 2.22.0
Proj 9.6.2
Lua 5.1.4 (LuaJIT 2.1.1744318430)
## What operating system and PostgreSQL/PostGIS version are you using?
Database version: 17.5 (Ubuntu 17.5-1.pgdg22.04+1) running in Docker
PostGIS version: 3.5
## Tell us something about your system
32 GB RAM, Apple M4
## What did you do exactly?
```bash
osm2pgsql --output=flex --style=./osm-flex.lua --create --database=<connectionString> <pathToOsmFile>
```
```lua
-- https://osm2pgsql.org/doc/manual.html
-- OSM Flex Import Script for German Geodata
-- Extracts zip code areas, places, and administrative boundaries with multilingual names
-- No need for manual JSON encoding - osm2pgsql handles jsonb conversion automatically
-- Helper function to extract multilingual names following OSM standards
-- Returns: default_name (string), language_names (table)
local function extract_names(object)
local default_name = object.tags.name -- Common default name in local language
local language_names = {}
-- Extract language-specific names (name:lang=*)
for key, value in pairs(object.tags) do
if key:match('^name:') then
local lang = key:sub(6) -- Remove 'name:' prefix
language_names[lang] = value
end
end
-- Skip alt_name, prefix, etc. to maintain consistent language-only structure
-- These could be stored in separate fields if needed for the application
return default_name, language_names
end
-- Define tables with correct osm2pgsql flex syntax
local postal_areas_table = osm2pgsql.define_table({
name = 'postal_areas',
ids = { type = 'area', id_column = 'area_id', create_index = 'primary_key' },
columns = {
{ column = 'postal_code', type = 'text' },
{ column = 'name', type = 'text' },
{ column = 'names', type = 'jsonb' },
{ column = 'population', type = 'int' },
{ column = 'admin_level', type = 'int' },
{ column = 'area_sqkm', type = 'real' },
{ column = 'geom', type = 'multipolygon', srid = 4326, not_null = true }
}
})
-- Process relations (for postal code and administrative boundaries)
function osm2pgsql.process_relation(object)
local postal_code = object.tags.postal_code or object.tags['addr:postcode']
local boundary = object.tags.boundary
local relation_type = object.tags.type
local admin_level = tonumber(object.tags.admin_level)
-- Process postal code boundary relations (exact match to https://github.com/yetzt/postleitzahlen)
-- Query: relation["type"="boundary"]["boundary"="postal_code"]
if boundary == 'postal_code' and relation_type == 'boundary' and postal_code then
local default_name, language_names = extract_names(object)
local population = tonumber(object.tags.population)
-- Calculate area in square kilometers if possible
local area_sqkm = nil
if object.tags['area:km2'] then
area_sqkm = tonumber(object.tags['area:km2'])
end
-- Create multipolygon geometry
local geom = object:as_multipolygon()
if geom then
local inserted, message = postal_areas_table:insert({
postal_code = postal_code,
name = default_name,
names = language_names, -- osm2pgsql will convert table to jsonb automatically
population = population,
admin_level = admin_level,
area_sqkm = area_sqkm,
geom = geom
})
-- Debug failed inserts only
if not inserted and message then
print("Failed to insert postal code boundary " .. postal_code .. ": " .. message)
end
end
end
-- Process administrative boundary relations with admin_level 2-8
-- Query: relation["type"="boundary"]["boundary"="administrative"]["admin_level"~"^[2-8]$"]
if boundary == 'administrative' and relation_type == 'boundary' and
admin_level and admin_level >= 2 and admin_level <= 8 then
local default_name, language_names = extract_names(object)
local population = tonumber(object.tags.population)
-- Calculate area in square kilometers if possible
local area_sqkm = nil
if object.tags['area:km2'] then
area_sqkm = tonumber(object.tags['area:km2'])
end
-- Create multipolygon geometry
local geom = object:as_multipolygon()
if geom then
local inserted, message = admin_boundaries_table:insert({
name = default_name,
names = language_names,
admin_level = admin_level,
population = population,
area_sqkm = area_sqkm,
is_lowest_level = false, -- Will be updated later by the hierarchy creation process
geom = geom
})
-- Debug failed inserts only
if not inserted and message then
print("Failed to insert admin boundary " .. (default_name or "unnamed") ..
" (level " .. admin_level .. "): " .. message)
end
end
end
end
```
## What did you expect to happen?
I expect that all postal_code boundaries are imported.
## What did happen instead?
99% of postal_code boundaries are imported successfully. However, some fail because the geometry becomes null. Two postal_code boundaries that fail are "79104" and "79108".
The corresponding OSM data can be found using the following Overpass query:
```
[out:json];
relation["type"="boundary"]["boundary"="postal_code"]["postal_code"="79108"];
out geom;
```
## What did you do to try analyzing the problem?
--
Reply to this email directly or view it on GitHub:
https://github.com/osm2pgsql-dev/osm2pgsql/issues/2414
You are receiving this because you are subscribed to this thread.
Message ID: <osm2pgsql-dev/osm2pgsql/issues/2414 at github.com>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.openstreetmap.org/pipermail/tile-serving/attachments/20250909/e6ab46ca/attachment.htm>
More information about the Tile-serving
mailing list