1 -- SPDX-License-Identifier: GPL-2.0-only
3 -- This file is part of Nominatim. (https://nominatim.org)
5 -- Copyright (C) 2022 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[];
20 RAISE WARNING 'place_insert: % % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type,st_area(NEW.geometry);
23 -- Filter tuples with bad geometries.
24 IF ST_IsEmpty(NEW.geometry) OR NOT ST_IsValid(NEW.geometry) THEN
25 INSERT INTO import_polygon_error (osm_type, osm_id, class, type, name,
26 country_code, updated, errormessage,
27 prevgeometry, newgeometry)
28 VALUES (NEW.osm_type, NEW.osm_id, NEW.class, NEW.type, NEW.name,
29 NEW.address->'country', now(), ST_IsValidReason(NEW.geometry),
32 RAISE WARNING 'Invalid Geometry: % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type;
37 -- Remove the place from the list of places to be deleted
38 DELETE FROM place_to_be_deleted pdel
39 WHERE pdel.osm_type = NEW.osm_type and pdel.osm_id = NEW.osm_id
40 and pdel.class = NEW.class;
42 -- Have we already done this place?
43 SELECT * INTO existing
45 WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id
46 and class = NEW.class and type = NEW.type;
48 {% if debug %}RAISE WARNING 'Existing: %',existing.osm_id;{% endif %}
50 -- Handle a place changing type by removing the old data.
51 -- (This trigger is executed BEFORE INSERT of the NEW tuple.)
52 IF existing.osm_type IS NULL THEN
53 DELETE FROM place where osm_type = NEW.osm_type and osm_id = NEW.osm_id and class = NEW.class;
56 -- Remove any old logged data.
57 DELETE from import_polygon_error where osm_type = NEW.osm_type and osm_id = NEW.osm_id;
58 DELETE from import_polygon_delete where osm_type = NEW.osm_type and osm_id = NEW.osm_id;
60 -- ---- Interpolation Lines
62 IF NEW.class='place' and NEW.type='houses'
63 and NEW.osm_type='W' and ST_GeometryType(NEW.geometry) = 'ST_LineString'
65 PERFORM reinsert_interpolation(NEW.osm_id, NEW.address, NEW.geometry);
67 -- Now invalidate all address nodes on the line.
68 -- They get their parent from the interpolation.
69 UPDATE placex p SET indexed_status = 2
70 FROM planet_osm_ways w
71 WHERE w.id = NEW.osm_id and p.osm_type = 'N' and p.osm_id = any(w.nodes);
73 -- If there is already an entry in place, just update that, if necessary.
74 IF existing.osm_type is not null THEN
75 IF coalesce(existing.address, ''::hstore) != coalesce(NEW.address, ''::hstore)
76 OR existing.geometry::text != NEW.geometry::text
80 address = NEW.address,
81 extratags = NEW.extratags,
82 admin_level = NEW.admin_level,
83 geometry = NEW.geometry
84 WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id
85 and class = NEW.class and type = NEW.type;
94 -- ---- Postcode points.
96 IF NEW.class = 'place' AND NEW.type = 'postcode' THEN
97 -- Pure postcodes are never queried from placex so we don't add them.
98 -- location_postcodes is filled from the place table directly.
100 -- Remove any old placex entry.
101 DELETE FROM placex WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id;
103 IF existing.osm_type IS NOT NULL THEN
104 IF coalesce(existing.address, ''::hstore) != coalesce(NEW.address, ''::hstore)
105 OR existing.geometry::text != NEW.geometry::text
109 address = NEW.address,
110 extratags = NEW.extratags,
111 admin_level = NEW.admin_level,
112 geometry = NEW.geometry
113 WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id
114 and class = NEW.class and type = NEW.type;
123 -- ---- All other place types.
125 -- When an area is changed from large to small: log and discard change
126 IF existing.geometry is not null AND ST_IsValid(existing.geometry)
127 AND ST_Area(existing.geometry) > 0.02
128 AND ST_GeometryType(NEW.geometry) in ('ST_Polygon','ST_MultiPolygon')
129 AND ST_Area(NEW.geometry) < ST_Area(existing.geometry) * 0.5
131 INSERT INTO import_polygon_error (osm_type, osm_id, class, type, name,
132 country_code, updated, errormessage,
133 prevgeometry, newgeometry)
134 VALUES (NEW.osm_type, NEW.osm_id, NEW.class, NEW.type, NEW.name,
135 NEW.address->'country', now(),
136 'Area reduced from '||st_area(existing.geometry)||' to '||st_area(NEW.geometry),
137 existing.geometry, NEW.geometry);
142 -- If an address node is part of a interpolation line and changes or is
143 -- newly inserted (happens when the node already existed but now gets address
144 -- information), then mark the interpolation line for reparenting.
145 -- (Already here, because interpolation lines are reindexed before nodes,
146 -- so in the second call it would be too late.)
148 and coalesce(existing.address, ''::hstore) != coalesce(NEW.address, ''::hstore)
151 SELECT DISTINCT osm_id, address, geometry FROM place, planet_osm_ways w
152 WHERE NEW.geometry && place.geometry
153 and place.osm_type = 'W'
154 and place.address ? 'interpolation'
155 and exists (SELECT * FROM location_property_osmline
156 WHERE osm_id = place.osm_id
157 and indexed_status in (0, 2))
158 and w.id = place.osm_id and NEW.osm_id = any (w.nodes)
160 PERFORM reinsert_interpolation(interpol.osm_id, interpol.address,
165 -- Get the existing placex entry.
166 SELECT * INTO existingplacex
168 WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id
169 and class = NEW.class and type = NEW.type;
171 {% if debug %}RAISE WARNING 'Existing PlaceX: %',existingplacex.place_id;{% endif %}
173 -- To paraphrase: if there isn't an existing item, OR if the admin level has changed
174 IF existingplacex.osm_type IS NULL
175 or (existingplacex.class = 'boundary'
176 and ((coalesce(existingplacex.admin_level, 15) != coalesce(NEW.admin_level, 15)
177 and existingplacex.type = 'administrative')
178 or existingplacex.type != NEW.type))
180 {% if config.get_bool('LIMIT_REINDEXING') %}
181 -- sanity check: ignore admin_level changes on places with too many active children
182 -- or we end up reindexing entire countries because somebody accidentally deleted admin_level
183 IF existingplacex.osm_type IS NOT NULL THEN
184 SELECT count(*) INTO i FROM
185 (SELECT 'a' FROM placex, place_addressline
186 WHERE address_place_id = existingplacex.place_id
187 and placex.place_id = place_addressline.place_id
188 and indexed_status = 0 and place_addressline.isaddress LIMIT 100001) sub;
195 IF existing.osm_type IS NOT NULL THEN
196 -- Pathological case caused by the triggerless copy into place during initial import
197 -- force delete even for large areas, it will be reinserted later
198 UPDATE place SET geometry = ST_SetSRID(ST_Point(0,0), 4326)
199 WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id
200 and class = NEW.class and type = NEW.type;
202 WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id
203 and class = NEW.class and type = NEW.type;
206 -- Process it as a new insertion
207 INSERT INTO placex (osm_type, osm_id, class, type, name,
208 admin_level, address, extratags, geometry)
209 VALUES (NEW.osm_type, NEW.osm_id, NEW.class, NEW.type, NEW.name,
210 NEW.admin_level, NEW.address, NEW.extratags, NEW.geometry);
212 {% if debug %}RAISE WARNING 'insert done % % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type,NEW.name;{% endif %}
217 -- Special case for polygon shape changes because they tend to be large
218 -- and we can be a bit clever about how we handle them
219 IF existing.geometry::text != NEW.geometry::text
220 AND ST_GeometryType(existing.geometry) in ('ST_Polygon','ST_MultiPolygon')
221 AND ST_GeometryType(NEW.geometry) in ('ST_Polygon','ST_MultiPolygon')
224 IF ST_Area(NEW.geometry) < 0.000000001 AND ST_Area(existingplacex.geometry) < 1
226 -- re-index points that have moved in / out of the polygon.
227 -- Could be done as a single query but postgres gets the index usage wrong.
228 update placex set indexed_status = 2 where indexed_status = 0
229 AND ST_Intersects(NEW.geometry, placex.geometry)
230 AND NOT ST_Intersects(existingplacex.geometry, placex.geometry)
231 AND rank_search > existingplacex.rank_search AND (rank_search < 28 or name is not null);
233 update placex set indexed_status = 2 where indexed_status = 0
234 AND ST_Intersects(existingplacex.geometry, placex.geometry)
235 AND NOT ST_Intersects(NEW.geometry, placex.geometry)
236 AND rank_search > existingplacex.rank_search AND (rank_search < 28 or name is not null);
241 -- Has something relevant changed?
242 IF coalesce(existing.name::text, '') != coalesce(NEW.name::text, '')
243 OR coalesce(existing.extratags::text, '') != coalesce(NEW.extratags::text, '')
244 OR coalesce(existing.address, ''::hstore) != coalesce(NEW.address, ''::hstore)
245 OR coalesce(existing.admin_level, 15) != coalesce(NEW.admin_level, 15)
246 OR existing.geometry::text != NEW.geometry::text
250 address = NEW.address,
251 extratags = NEW.extratags,
252 admin_level = NEW.admin_level,
253 geometry = NEW.geometry
254 WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id
255 and class = NEW.class and type = NEW.type;
257 -- Postcode areas are only kept, when there is an actual postcode assigned.
258 IF NEW.class = 'boundary' AND NEW.type = 'postal_code' THEN
259 IF NEW.address is NULL OR NOT NEW.address ? 'postcode' THEN
260 -- postcode was deleted, no longer retain in placex
261 DELETE FROM placex where place_id = existingplacex.place_id;
265 NEW.name := hstore('ref', NEW.address->'postcode');
268 -- Boundaries must be areas.
269 IF NEW.class in ('boundary')
270 AND ST_GeometryType(NEW.geometry) not in ('ST_Polygon','ST_MultiPolygon')
272 DELETE FROM placex where place_id = existingplacex.place_id;
276 -- Update the placex entry in-place.
279 address = NEW.address,
280 parent_place_id = null,
281 extratags = NEW.extratags,
282 admin_level = NEW.admin_level,
284 geometry = NEW.geometry
285 WHERE place_id = existingplacex.place_id;
287 -- Invalidate linked places: they potentially get a new name and addresses.
288 IF existingplacex.linked_place_id is not NULL THEN
291 extratags = p.extratags,
294 WHERE x.place_id = existingplacex.linked_place_id
295 and x.indexed_status = 0
296 and x.osm_type = p.osm_type
297 and x.osm_id = p.osm_id
298 and x.class = p.class;
301 -- Invalidate dependent objects effected by name changes
302 IF coalesce(existing.name::text, '') != coalesce(NEW.name::text, '')
304 IF existingplacex.rank_address between 26 and 27 THEN
305 -- When streets change their name, this may have an effect on POI objects
306 -- with addr:street tags.
307 UPDATE placex SET indexed_status = 2
308 WHERE indexed_status = 0 and address ? 'street'
309 and parent_place_id = existingplacex.place_id;
310 UPDATE placex SET indexed_status = 2
311 WHERE indexed_status = 0 and rank_search = 30 and address ? 'street'
312 and ST_DWithin(NEW.geometry, geometry, 0.002);
313 ELSEIF existingplacex.rank_address between 16 and 25 THEN
314 -- When places change their name, this may have an effect on POI objects
315 -- with addr:place tags.
316 UPDATE placex SET indexed_status = 2
317 WHERE indexed_status = 0 and address ? 'place' and rank_search = 30
318 and parent_place_id = existingplacex.place_id;
319 -- No update of surrounding objects, potentially too expensive.
324 -- Abort the insertion (we modified the existing place instead)
329 CREATE OR REPLACE FUNCTION place_delete()
335 {% if debug %}RAISE WARNING 'Delete for % % %/%', OLD.osm_type, OLD.osm_id, OLD.class, OLD.type;{% endif %}
337 deferred := ST_IsValid(OLD.geometry) and ST_Area(OLD.geometry) > 2;
339 SELECT bool_or(not (rank_address = 0 or rank_address > 25)) INTO deferred
341 WHERE osm_type = OLD.osm_type and osm_id = OLD.osm_id
342 and class = OLD.class and type = OLD.type;
345 INSERT INTO place_to_be_deleted (osm_type, osm_id, class, type, deferred)
346 VALUES(OLD.osm_type, OLD.osm_id, OLD.class, OLD.type, deferred);
352 CREATE OR REPLACE FUNCTION flush_deleted_places()
356 -- deleting large polygons can have a massive effect on the system - require manual intervention to let them through
357 INSERT INTO import_polygon_delete (osm_type, osm_id, class, type)
358 SELECT osm_type, osm_id, class, type FROM place_to_be_deleted WHERE deferred;
360 -- delete from place table
361 ALTER TABLE place DISABLE TRIGGER place_before_delete;
362 DELETE FROM place USING place_to_be_deleted
363 WHERE place.osm_type = place_to_be_deleted.osm_type
364 and place.osm_id = place_to_be_deleted.osm_id
365 and place.class = place_to_be_deleted.class
366 and place.type = place_to_be_deleted.type
368 ALTER TABLE place ENABLE TRIGGER place_before_delete;
370 -- Mark for delete in the placex table
371 UPDATE placex SET indexed_status = 100 FROM place_to_be_deleted
372 WHERE placex.osm_type = place_to_be_deleted.osm_type
373 and placex.osm_id = place_to_be_deleted.osm_id
374 and placex.class = place_to_be_deleted.class
375 and placex.type = place_to_be_deleted.type
378 -- Mark for delete in interpolations
379 UPDATE location_property_osmline SET indexed_status = 100 FROM place_to_be_deleted
380 WHERE place_to_be_deleted.osm_type = 'W'
381 and place_to_be_deleted.class = 'place'
382 and place_to_be_deleted.type = 'houses'
383 and location_property_osmline.osm_id = place_to_be_deleted.osm_id
387 TRUNCATE TABLE place_to_be_deleted;