1 -- SPDX-License-Identifier: GPL-2.0-only
3 -- This file is part of Nominatim. (https://nominatim.org)
5 -- Copyright (C) 2026 by the Nominatim developer community.
6 -- For a full list of authors see the git log.
8 CREATE OR REPLACE FUNCTION place_insert()
15 existingplacex RECORD;
16 existingline BIGINT[];
19 RAISE WARNING 'place_insert: % % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type,st_area(NEW.geometry);
22 -- Filter tuples with bad geometries.
23 IF ST_IsEmpty(NEW.geometry) OR NOT ST_IsValid(NEW.geometry) THEN
24 INSERT INTO import_polygon_error (osm_type, osm_id, class, type, name,
25 country_code, updated, errormessage,
26 prevgeometry, newgeometry)
27 VALUES (NEW.osm_type, NEW.osm_id, NEW.class, NEW.type, NEW.name,
28 NEW.address->'country', now(), ST_IsValidReason(NEW.geometry),
31 RAISE WARNING 'Invalid Geometry: % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type;
36 -- Remove the place from the list of places to be deleted
37 DELETE FROM place_to_be_deleted pdel
38 WHERE pdel.osm_type = NEW.osm_type and pdel.osm_id = NEW.osm_id
39 and pdel.class = NEW.class and pdel.type = NEW.type;
41 -- Have we already done this place?
42 SELECT * INTO existing
44 WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id
45 and class = NEW.class and type = NEW.type;
47 {% if debug %}RAISE WARNING 'Existing: %',existing.osm_id;{% endif %}
49 IF existing.osm_type IS NULL THEN
50 DELETE FROM place where osm_type = NEW.osm_type and osm_id = NEW.osm_id and class = NEW.class;
53 -- Remove any old logged data.
54 DELETE from import_polygon_error where osm_type = NEW.osm_type and osm_id = NEW.osm_id;
55 DELETE from import_polygon_delete where osm_type = NEW.osm_type and osm_id = NEW.osm_id;
57 -- ---- All other place types.
59 -- When an area is changed from large to small: log and discard change
60 IF existing.geometry is not null AND ST_IsValid(existing.geometry)
61 AND ST_Area(existing.geometry) > 0.02
62 AND ST_GeometryType(NEW.geometry) in ('ST_Polygon','ST_MultiPolygon')
63 AND ST_Area(NEW.geometry) < ST_Area(existing.geometry) * 0.5
65 INSERT INTO import_polygon_error (osm_type, osm_id, class, type, name,
66 country_code, updated, errormessage,
67 prevgeometry, newgeometry)
68 VALUES (NEW.osm_type, NEW.osm_id, NEW.class, NEW.type, NEW.name,
69 NEW.address->'country', now(),
70 'Area reduced from '||st_area(existing.geometry)||' to '||st_area(NEW.geometry),
71 existing.geometry, NEW.geometry);
76 -- Get the existing placex entry.
77 SELECT * INTO existingplacex
79 WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id
80 and class = NEW.class and type = NEW.type;
82 {% if debug %}RAISE WARNING 'Existing PlaceX: %',existingplacex.place_id;{% endif %}
84 -- To paraphrase: if there isn't an existing item, OR if the admin level has changed
85 IF existingplacex.osm_type IS NULL
86 or (existingplacex.class = 'boundary'
87 and ((coalesce(existingplacex.admin_level, 15) != coalesce(NEW.admin_level, 15)
88 and existingplacex.type = 'administrative')
89 or existingplacex.type != NEW.type))
91 {% if config.get_bool('LIMIT_REINDEXING') %}
92 -- sanity check: ignore admin_level changes on places with too many active children
93 -- or we end up reindexing entire countries because somebody accidentally deleted admin_level
94 IF existingplacex.osm_type IS NOT NULL THEN
95 SELECT count(*) INTO i FROM
96 (SELECT 'a' FROM placex, place_addressline
97 WHERE address_place_id = existingplacex.place_id
98 and placex.place_id = place_addressline.place_id
99 and indexed_status = 0 and place_addressline.isaddress LIMIT 100001) sub;
106 IF existingplacex.osm_type is not NULL THEN
107 -- Mark any existing place for delete in the placex table
108 UPDATE placex SET indexed_status = 100
109 WHERE placex.osm_type = NEW.osm_type and placex.osm_id = NEW.osm_id
110 and placex.class = NEW.class and placex.type = NEW.type;
113 -- Process it as a new insertion
114 INSERT INTO placex (osm_type, osm_id, class, type, name,
115 admin_level, address, extratags, geometry)
116 VALUES (NEW.osm_type, NEW.osm_id, NEW.class, NEW.type, NEW.name,
117 NEW.admin_level, NEW.address, NEW.extratags, NEW.geometry);
119 {% if debug %}RAISE WARNING 'insert done % % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type,NEW.name;{% endif %}
121 IF existing.osm_type is not NULL THEN
122 -- If there is already an entry in place, just update that, if necessary.
123 IF coalesce(existing.name, ''::hstore) != coalesce(NEW.name, ''::hstore)
124 or coalesce(existing.address, ''::hstore) != coalesce(NEW.address, ''::hstore)
125 or coalesce(existing.extratags, ''::hstore) != coalesce(NEW.extratags, ''::hstore)
126 or coalesce(existing.admin_level, 15) != coalesce(NEW.admin_level, 15)
127 or existing.geometry::text != NEW.geometry::text
131 address = NEW.address,
132 extratags = NEW.extratags,
133 admin_level = NEW.admin_level,
134 geometry = NEW.geometry
135 WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id
136 and class = NEW.class and type = NEW.type;
145 -- Special case for polygon shape changes because they tend to be large
146 -- and we can be a bit clever about how we handle them
147 IF existing.geometry::text != NEW.geometry::text
148 AND ST_GeometryType(existing.geometry) in ('ST_Polygon','ST_MultiPolygon')
149 AND ST_GeometryType(NEW.geometry) in ('ST_Polygon','ST_MultiPolygon')
152 IF ST_Area(NEW.geometry) < 0.000000001 AND ST_Area(existingplacex.geometry) < 1
154 -- re-index points that have moved in / out of the polygon.
155 -- Could be done as a single query but postgres gets the index usage wrong.
156 update placex set indexed_status = 2 where indexed_status = 0
157 AND ST_Intersects(NEW.geometry, placex.geometry)
158 AND NOT ST_Intersects(existingplacex.geometry, placex.geometry)
159 AND rank_search > existingplacex.rank_search AND (rank_search < 28 or name is not null);
161 update placex set indexed_status = 2 where indexed_status = 0
162 AND ST_Intersects(existingplacex.geometry, placex.geometry)
163 AND NOT ST_Intersects(NEW.geometry, placex.geometry)
164 AND rank_search > existingplacex.rank_search AND (rank_search < 28 or name is not null);
169 -- Has something relevant changed?
170 IF coalesce(existing.name::text, '') != coalesce(NEW.name::text, '')
171 OR coalesce(existing.extratags::text, '') != coalesce(NEW.extratags::text, '')
172 OR coalesce(existing.address, ''::hstore) != coalesce(NEW.address, ''::hstore)
173 OR coalesce(existing.admin_level, 15) != coalesce(NEW.admin_level, 15)
174 OR existing.geometry::text != NEW.geometry::text
178 address = NEW.address,
179 extratags = NEW.extratags,
180 admin_level = NEW.admin_level,
181 geometry = NEW.geometry
182 WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id
183 and class = NEW.class and type = NEW.type;
185 -- Boundaries must be areas.
186 IF NEW.class in ('boundary')
187 AND ST_GeometryType(NEW.geometry) not in ('ST_Polygon','ST_MultiPolygon')
189 DELETE FROM placex where place_id = existingplacex.place_id;
193 -- Update the placex entry in-place.
196 address = NEW.address,
197 parent_place_id = null,
198 extratags = NEW.extratags,
199 admin_level = NEW.admin_level,
201 geometry = CASE WHEN existingplacex.rank_address = 0
202 THEN simplify_large_polygons(NEW.geometry)
203 ELSE NEW.geometry END
204 WHERE place_id = existingplacex.place_id;
206 -- Invalidate linked places: they potentially get a new name and addresses.
207 IF existingplacex.linked_place_id is not NULL THEN
210 extratags = p.extratags,
213 WHERE x.place_id = existingplacex.linked_place_id
214 and x.indexed_status = 0
215 and x.osm_type = p.osm_type
216 and x.osm_id = p.osm_id
217 and x.class = p.class;
220 -- Invalidate dependent objects effected by name changes
221 IF coalesce(existing.name::text, '') != coalesce(NEW.name::text, '')
223 IF existingplacex.rank_address between 26 and 27 THEN
224 -- When streets change their name, this may have an effect on POI objects
225 -- with addr:street tags.
226 UPDATE placex SET indexed_status = 2
227 WHERE indexed_status = 0 and address ? 'street'
228 and parent_place_id = existingplacex.place_id;
229 UPDATE placex SET indexed_status = 2
230 WHERE indexed_status = 0 and rank_search = 30 and address ? 'street'
231 and ST_DWithin(NEW.geometry, geometry, 0.002);
232 ELSEIF existingplacex.rank_address between 16 and 25 THEN
233 -- When places change their name, this may have an effect on POI objects
234 -- with addr:place tags.
235 UPDATE placex SET indexed_status = 2
236 WHERE indexed_status = 0 and address ? 'place' and rank_search = 30
237 and parent_place_id = existingplacex.place_id;
238 -- No update of surrounding objects, potentially too expensive.
243 -- When an existing way is updated, recalculate entrances
244 IF existingplacex.osm_type = 'W' and (existingplacex.rank_search > 27 or existingplacex.class IN ('landuse', 'leisure')) THEN
245 PERFORM place_update_entrances(existingplacex.place_id, existingplacex.osm_id);
248 -- Abort the insertion (we modified the existing place instead)
253 CREATE OR REPLACE FUNCTION place_delete()
259 {% if debug %}RAISE WARNING 'Delete for % % %/%', OLD.osm_type, OLD.osm_id, OLD.class, OLD.type;{% endif %}
261 deferred := ST_IsValid(OLD.geometry) and ST_Area(OLD.geometry) > 2;
263 SELECT bool_or(not (rank_address = 0 or rank_address > 25)) INTO deferred
265 WHERE osm_type = OLD.osm_type and osm_id = OLD.osm_id
266 and class = OLD.class and type = OLD.type;
269 INSERT INTO place_to_be_deleted (osm_type, osm_id, class, type, deferred)
270 VALUES(OLD.osm_type, OLD.osm_id, OLD.class, OLD.type, deferred);