Skip to content

Commit df4abfd

Browse files
authored
Merge pull request #3926 from lonvia/rework-postcode-handling
Reorganise postcode handling
2 parents 0b11dd0 + 42d139a commit df4abfd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+1132
-791
lines changed

docs/customize/Import-Styles.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ The following classifications are recognized:
113113
| named | Consider as main tag, when the object has a primary name (see [names](#name-tags) below) |
114114
| named_with_key | Consider as main tag, when the object has a primary name with a domain prefix. For example, if the main tag is `bridge=yes`, then it will only be added as an extra entry, if there is a tag `bridge:name[:XXX]` for the same object. If this property is set, all names that are not domain-specific are ignored. |
115115
| fallback | Consider as main tag only when no other main tag was found. Fallback always implies `named`, i.e. fallbacks are only tried for objects with primary names. |
116+
| postcode_area | Tag indicates a postcode area. Copy area into the table of postcodes but only when the object is a relation and has a postcode tagged. |
116117
| delete | Completely ignore the tag in any further processing |
117118
| extra | Move the tag to extratags and then ignore it for further processing |
118119
| `<function>`| Advanced handling, see [below](#advanced-main-tag-handling) |

docs/develop/Database-Layout.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ the placex table. Only three columns are special:
7979
Address interpolations are always ways in OSM, which is why there is no column
8080
`osm_type`.
8181

82-
The **location_postcode** table holds computed centroids of all postcodes that
82+
The **location_postcodes** table holds computed centroids of all postcodes that
8383
can be found in the OSM data. The meaning of the columns is again the same
8484
as that of the placex table.
8585

docs/develop/search-tables.plantuml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,16 @@ map place_addressline {
7474
isaddress => BOOLEAN
7575
}
7676

77-
map location_postcode {
77+
map location_postcodes {
7878
place_id => BIGINT
79+
osm_id => BIGINT
7980
postcode => TEXT
8081
parent_place_id => BIGINT
8182
rank_search => SMALLINT
82-
rank_address => SMALLINT
8383
indexed_status => SMALLINT
8484
indexed_date => TIMESTAMP
8585
geometry => GEOMETRY
86+
centroid -> GEOMETRY
8687
}
8788

8889
placex::place_id <-- search_name::place_id
@@ -94,6 +95,6 @@ search_name::nameaddress_vector --> word::word_id
9495

9596
place_addressline -[hidden]> location_property_osmline
9697
search_name -[hidden]> place_addressline
97-
location_property_osmline -[hidden]-> location_postcode
98+
location_property_osmline -[hidden]-> location_postcodes
9899

99100
@enduml

lib-lua/themes/nominatim/init.lua

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,19 @@ local table_definitions = {
6565
{ column = 'geometry', type = 'geometry', projection = 'WGS84', not_null = true }
6666
},
6767
indexes = {}
68-
}
68+
},
69+
place_postcode = {
70+
ids = { type = 'any', id_column = 'osm_id', type_column = 'osm_type' },
71+
columns = {
72+
{ column = 'postcode', type = 'text', not_null = true },
73+
{ column = 'country_code', type = 'text' },
74+
{ column = 'centroid', type = 'point', projection = 'WGS84', not_null = true },
75+
{ column = 'geometry', type = 'geometry', projection = 'WGS84' }
76+
},
77+
indexes = {
78+
{ column = 'postcode', method = 'btree' }
79+
}
80+
}
6981
}
7082

7183
local insert_row = {}
@@ -113,6 +125,7 @@ local PlaceTransform = {}
113125

114126
-- Special transform meanings which are interpreted elsewhere
115127
PlaceTransform.fallback = 'fallback'
128+
PlaceTransform.postcode_area = 'postcode_area'
116129
PlaceTransform.delete = 'delete'
117130
PlaceTransform.extra = 'extra'
118131

@@ -419,11 +432,25 @@ function Place:write_place(k, v, mfunc)
419432
return 0
420433
end
421434

422-
function Place:write_row(k, v)
435+
436+
function Place:geometry_is_valid()
423437
if self.geometry == nil then
424438
self.geometry = self.geom_func(self.object)
439+
440+
if self.geometry == nil or self.geometry:is_null() then
441+
self.geometry = false
442+
return false
443+
end
444+
445+
return true
425446
end
426-
if self.geometry == nil or self.geometry:is_null() then
447+
448+
return self.geometry ~= false
449+
end
450+
451+
452+
function Place:write_row(k, v)
453+
if not self:geometry_is_valid() then
427454
return 0
428455
end
429456

@@ -675,30 +702,48 @@ function module.process_tags(o)
675702
if o.address.country ~= nil and #o.address.country ~= 2 then
676703
o.address['country'] = nil
677704
end
678-
if POSTCODE_FALLBACK and fallback == nil and o.address.postcode ~= nil then
679-
fallback = {'place', 'postcode', PlaceTransform.always}
680-
end
681705

682706
if o.address.interpolation ~= nil then
683707
o:write_place('place', 'houses', PlaceTransform.always)
684708
return
685709
end
686710

687711
-- collect main keys
712+
local postcode_collect = false
688713
for k, v in pairs(o.intags) do
689714
local ktable = MAIN_KEYS[k]
690715
if ktable then
691716
local ktype = ktable[v] or ktable[1]
692717
if type(ktype) == 'function' then
693718
o:write_place(k, v, ktype)
719+
elseif ktype == 'postcode_area' then
720+
postcode_collect = true
721+
if o.object.type == 'relation'
722+
and o.address.postcode ~= nil
723+
and o:geometry_is_valid() then
724+
insert_row.place_postcode{
725+
postcode = o.address.postcode,
726+
centroid = o.geometry:centroid(),
727+
geometry = o.geometry
728+
}
729+
end
694730
elseif ktype == 'fallback' and o.has_name then
695731
fallback = {k, v, PlaceTransform.named}
696732
end
697733
end
698734
end
699735

700-
if fallback ~= nil and o.num_entries == 0 then
701-
o:write_place(fallback[1], fallback[2], fallback[3])
736+
if o.num_entries == 0 then
737+
if fallback ~= nil then
738+
o:write_place(fallback[1], fallback[2], fallback[3])
739+
elseif POSTCODE_FALLBACK and not postcode_collect
740+
and o.address.postcode ~= nil
741+
and o:geometry_is_valid() then
742+
insert_row.place_postcode{
743+
postcode = o.address.postcode,
744+
centroid = o.geometry:centroid()
745+
}
746+
end
702747
end
703748
end
704749

lib-lua/themes/nominatim/presets.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ module.MAIN_TAGS.all_boundaries = {
118118
place = 'delete',
119119
land_area = 'delete',
120120
protected_area = 'fallback',
121-
postal_code = 'always'},
121+
postal_code = 'postcode_area'},
122122
landuse = 'fallback',
123123
place = 'always'
124124
}

lib-sql/functions.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
--
33
-- This file is part of Nominatim. (https://nominatim.org)
44
--
5-
-- Copyright (C) 2022 by the Nominatim developer community.
5+
-- Copyright (C) 2025 by the Nominatim developer community.
66
-- For a full list of authors see the git log.
77

88
{% include('functions/utils.sql') %}
@@ -18,7 +18,7 @@
1818
{% include 'functions/placex_triggers.sql' %}
1919
{% endif %}
2020

21-
{% if 'location_postcode' in db.tables %}
21+
{% if 'location_postcodes' in db.tables %}
2222
{% include 'functions/postcode_triggers.sql' %}
2323
{% endif %}
2424

lib-sql/functions/place_triggers.sql

Lines changed: 1 addition & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
--
33
-- This file is part of Nominatim. (https://nominatim.org)
44
--
5-
-- Copyright (C) 2022 by the Nominatim developer community.
5+
-- Copyright (C) 2025 by the Nominatim developer community.
66
-- For a full list of authors see the git log.
77

88
CREATE OR REPLACE FUNCTION place_insert()
@@ -89,35 +89,6 @@ BEGIN
8989
RETURN NEW;
9090
END IF;
9191

92-
-- ---- Postcode points.
93-
94-
IF NEW.class = 'place' AND NEW.type = 'postcode' THEN
95-
-- Pure postcodes are never queried from placex so we don't add them.
96-
-- location_postcodes is filled from the place table directly.
97-
98-
-- Remove any old placex entry.
99-
DELETE FROM placex WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id;
100-
101-
IF existing.osm_type IS NOT NULL THEN
102-
IF coalesce(existing.address, ''::hstore) != coalesce(NEW.address, ''::hstore)
103-
OR existing.geometry::text != NEW.geometry::text
104-
THEN
105-
UPDATE place
106-
SET name = NEW.name,
107-
address = NEW.address,
108-
extratags = NEW.extratags,
109-
admin_level = NEW.admin_level,
110-
geometry = NEW.geometry
111-
WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id
112-
and class = NEW.class and type = NEW.type;
113-
END IF;
114-
115-
RETURN NULL;
116-
END IF;
117-
118-
RETURN NEW;
119-
END IF;
120-
12192
-- ---- All other place types.
12293

12394
-- When an area is changed from large to small: log and discard change
@@ -269,17 +240,6 @@ BEGIN
269240
WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id
270241
and class = NEW.class and type = NEW.type;
271242

272-
-- Postcode areas are only kept, when there is an actual postcode assigned.
273-
IF NEW.class = 'boundary' AND NEW.type = 'postal_code' THEN
274-
IF NEW.address is NULL OR NOT NEW.address ? 'postcode' THEN
275-
-- postcode was deleted, no longer retain in placex
276-
DELETE FROM placex where place_id = existingplacex.place_id;
277-
RETURN NULL;
278-
END IF;
279-
280-
NEW.name := hstore('ref', NEW.address->'postcode');
281-
END IF;
282-
283243
-- Boundaries must be areas.
284244
IF NEW.class in ('boundary')
285245
AND ST_GeometryType(NEW.geometry) not in ('ST_Polygon','ST_MultiPolygon')

lib-sql/functions/placex_triggers.sql

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,6 @@ DECLARE
304304
BEGIN
305305
IF bnd.rank_search >= 26 or bnd.rank_address = 0
306306
or ST_GeometryType(bnd.geometry) NOT IN ('ST_Polygon','ST_MultiPolygon')
307-
or bnd.type IN ('postcode', 'postal_code')
308307
THEN
309308
RETURN NULL;
310309
END IF;
@@ -359,8 +358,7 @@ BEGIN
359358

360359
-- If extratags has a place tag, look for linked nodes by their place type.
361360
-- Area and node still have to have the same name.
362-
IF bnd.extratags ? 'place' and bnd.extratags->'place' != 'postcode'
363-
and bnd_name is not null
361+
IF bnd.extratags ? 'place' and bnd_name is not null
364362
THEN
365363
FOR linked_placex IN
366364
SELECT * FROM placex
@@ -393,7 +391,6 @@ BEGIN
393391
AND placex.class = 'place'
394392
AND (placex.linked_place_id is null or placex.linked_place_id = bnd.place_id)
395393
AND placex.rank_search < 26 -- needed to select the right index
396-
AND placex.type != 'postcode'
397394
AND ST_Covers(bnd.geometry, placex.geometry)
398395
LOOP
399396
{% if debug %}RAISE WARNING 'Found matching place node %', linked_placex.osm_id;{% endif %}
@@ -697,17 +694,7 @@ BEGIN
697694
ELSE
698695
is_area := ST_GeometryType(NEW.geometry) IN ('ST_Polygon','ST_MultiPolygon');
699696

700-
IF NEW.class in ('place','boundary')
701-
AND NEW.type in ('postcode','postal_code')
702-
THEN
703-
IF NEW.address IS NULL OR NOT NEW.address ? 'postcode' THEN
704-
-- most likely just a part of a multipolygon postcode boundary, throw it away
705-
RETURN NULL;
706-
END IF;
707-
708-
NEW.name := hstore('ref', NEW.address->'postcode');
709-
710-
ELSEIF NEW.class = 'highway' AND is_area AND NEW.name is null
697+
IF NEW.class = 'highway' AND is_area AND NEW.name is null
711698
AND NEW.extratags ? 'area' AND NEW.extratags->'area' = 'yes'
712699
THEN
713700
RETURN NULL;
@@ -877,16 +864,6 @@ BEGIN
877864
and (linked_place is null or place_id != linked_place);
878865
-- update not necessary for osmline, cause linked_place_id does not exist
879866

880-
-- Postcodes are just here to compute the centroids. They are not searchable
881-
-- unless they are a boundary=postal_code.
882-
-- There was an error in the style so that boundary=postal_code used to be
883-
-- imported as place=postcode. That's why relations are allowed to pass here.
884-
-- This can go away in a couple of versions.
885-
IF NEW.class = 'place' and NEW.type = 'postcode' and NEW.osm_type != 'R' THEN
886-
NEW.token_info := null;
887-
RETURN NEW;
888-
END IF;
889-
890867
-- Compute a preliminary centroid.
891868
NEW.centroid := get_center_point(NEW.geometry);
892869

@@ -1286,8 +1263,6 @@ BEGIN
12861263
END IF;
12871264
ELSEIF NEW.rank_address > 25 THEN
12881265
max_rank := 25;
1289-
ELSEIF NEW.class in ('place','boundary') and NEW.type in ('postcode','postal_code') THEN
1290-
max_rank := NEW.rank_search;
12911266
ELSE
12921267
max_rank := NEW.rank_address;
12931268
END IF;
@@ -1424,7 +1399,7 @@ BEGIN
14241399

14251400
{% if debug %}RAISE WARNING 'placex_delete:12 % %',OLD.osm_type,OLD.osm_id;{% endif %}
14261401

1427-
UPDATE location_postcode SET indexed_status = 2 WHERE parent_place_id = OLD.place_id;
1402+
UPDATE location_postcodes SET indexed_status = 2 WHERE parent_place_id = OLD.place_id;
14281403

14291404
RETURN OLD;
14301405

0 commit comments

Comments
 (0)