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 -- Have we already done this place?
38 SELECT * INTO existing
40 WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id
41 and class = NEW.class and type = NEW.type;
43 {% if debug %}RAISE WARNING 'Existing: %',existing.osm_id;{% endif %}
45 -- Handle a place changing type by removing the old data.
46 -- (This trigger is executed BEFORE INSERT of the NEW tuple.)
47 IF existing.osm_type IS NULL THEN
48 DELETE FROM place where osm_type = NEW.osm_type and osm_id = NEW.osm_id and class = NEW.class;
51 -- Remove any old logged data.
52 DELETE from import_polygon_error where osm_type = NEW.osm_type and osm_id = NEW.osm_id;
53 DELETE from import_polygon_delete where osm_type = NEW.osm_type and osm_id = NEW.osm_id;
55 -- ---- Interpolation Lines
57 IF NEW.class='place' and NEW.type='houses'
58 and NEW.osm_type='W' and ST_GeometryType(NEW.geometry) = 'ST_LineString'
60 -- Get the existing entry from the interpolation table.
61 SELECT array_agg(place_id) INTO existingline
62 FROM location_property_osmline WHERE osm_id = NEW.osm_id;
64 IF existingline IS NULL or array_length(existingline, 1) = 0 THEN
65 INSERT INTO location_property_osmline (osm_id, address, linegeo)
66 VALUES (NEW.osm_id, NEW.address, NEW.geometry);
68 -- Update the interpolation table:
69 -- The first entry gets the original data, all other entries
70 -- are removed and will be recreated on indexing.
71 -- (An interpolation can be split up, if it has more than 2 address nodes)
72 UPDATE location_property_osmline
73 SET address = NEW.address,
74 linegeo = NEW.geometry,
77 WHERE place_id = existingline[1];
78 IF array_length(existingline, 1) > 1 THEN
79 DELETE FROM location_property_osmline
80 WHERE place_id = any(existingline[2:]);
84 -- Now invalidate all address nodes on the line.
85 -- They get their parent from the interpolation.
86 UPDATE placex p SET indexed_status = 2
87 FROM planet_osm_ways w
88 WHERE w.id = NEW.osm_id and p.osm_type = 'N' and p.osm_id = any(w.nodes);
90 -- If there is already an entry in place, just update that, if necessary.
91 IF existing.osm_type is not null THEN
92 IF coalesce(existing.address, ''::hstore) != coalesce(NEW.address, ''::hstore)
93 OR existing.geometry::text != NEW.geometry::text
97 address = NEW.address,
98 extratags = NEW.extratags,
99 admin_level = NEW.admin_level,
100 geometry = NEW.geometry
101 WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id
102 and class = NEW.class and type = NEW.type;
111 -- ---- Postcode points.
113 IF NEW.class = 'place' AND NEW.type = 'postcode' THEN
114 -- Pure postcodes are never queried from placex so we don't add them.
115 -- location_postcodes is filled from the place table directly.
117 -- Remove any old placex entry.
118 DELETE FROM placex WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id;
120 IF existing.osm_type IS NOT NULL THEN
121 IF coalesce(existing.address, ''::hstore) != coalesce(NEW.address, ''::hstore)
122 OR existing.geometry::text != NEW.geometry::text
126 address = NEW.address,
127 extratags = NEW.extratags,
128 admin_level = NEW.admin_level,
129 geometry = NEW.geometry
130 WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id
131 and class = NEW.class and type = NEW.type;
140 -- ---- All other place types.
142 -- Patch in additional country names
143 IF NEW.admin_level = 2 and NEW.type = 'administrative' and NEW.address ? 'country'
146 SELECT name FROM country_name WHERE country_code = lower(NEW.address->'country')
148 NEW.name = country.name || NEW.name;
152 -- When an area is changed from large to small: log and discard change
153 IF existing.geometry is not null AND ST_IsValid(existing.geometry)
154 AND ST_Area(existing.geometry) > 0.02
155 AND ST_GeometryType(NEW.geometry) in ('ST_Polygon','ST_MultiPolygon')
156 AND ST_Area(NEW.geometry) < ST_Area(existing.geometry) * 0.5
158 INSERT INTO import_polygon_error (osm_type, osm_id, class, type, name,
159 country_code, updated, errormessage,
160 prevgeometry, newgeometry)
161 VALUES (NEW.osm_type, NEW.osm_id, NEW.class, NEW.type, NEW.name,
162 NEW.address->'country', now(),
163 'Area reduced from '||st_area(existing.geometry)||' to '||st_area(NEW.geometry),
164 existing.geometry, NEW.geometry);
169 -- Get the existing placex entry.
170 SELECT * INTO existingplacex
172 WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id
173 and class = NEW.class and type = NEW.type;
175 {% if debug %}RAISE WARNING 'Existing PlaceX: %',existingplacex.place_id;{% endif %}
177 -- To paraphrase: if there isn't an existing item, OR if the admin level has changed
178 IF existingplacex.osm_type IS NULL
179 or (existingplacex.class = 'boundary'
180 and ((coalesce(existingplacex.admin_level, 15) != coalesce(NEW.admin_level, 15)
181 and existingplacex.type = 'administrative')
182 or existingplacex.type != NEW.type))
184 {% if config.get_bool('LIMIT_REINDEXING') %}
185 -- sanity check: ignore admin_level changes on places with too many active children
186 -- or we end up reindexing entire countries because somebody accidentally deleted admin_level
187 IF existingplacex.osm_type IS NOT NULL THEN
188 SELECT count(*) INTO i FROM
189 (SELECT 'a' FROM placex, place_addressline
190 WHERE address_place_id = existingplacex.place_id
191 and placex.place_id = place_addressline.place_id
192 and indexed_status = 0 and place_addressline.isaddress LIMIT 100001) sub;
199 IF existing.osm_type IS NOT NULL THEN
200 -- Pathological case caused by the triggerless copy into place during initial import
201 -- force delete even for large areas, it will be reinserted later
202 UPDATE place SET geometry = ST_SetSRID(ST_Point(0,0), 4326)
203 WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id
204 and class = NEW.class and type = NEW.type;
206 WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id
207 and class = NEW.class and type = NEW.type;
210 -- Process it as a new insertion
211 INSERT INTO placex (osm_type, osm_id, class, type, name,
212 admin_level, address, extratags, geometry)
213 VALUES (NEW.osm_type, NEW.osm_id, NEW.class, NEW.type, NEW.name,
214 NEW.admin_level, NEW.address, NEW.extratags, NEW.geometry);
216 {% if debug %}RAISE WARNING 'insert done % % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type,NEW.name;{% endif %}
221 -- Special case for polygon shape changes because they tend to be large
222 -- and we can be a bit clever about how we handle them
223 IF existing.geometry::text != NEW.geometry::text
224 AND ST_GeometryType(existing.geometry) in ('ST_Polygon','ST_MultiPolygon')
225 AND ST_GeometryType(NEW.geometry) in ('ST_Polygon','ST_MultiPolygon')
228 IF ST_Area(NEW.geometry) < 0.000000001 AND ST_Area(existingplacex.geometry) < 1
230 -- re-index points that have moved in / out of the polygon.
231 -- Could be done as a single query but postgres gets the index usage wrong.
232 update placex set indexed_status = 2 where indexed_status = 0
233 AND ST_Intersects(NEW.geometry, placex.geometry)
234 AND NOT ST_Intersects(existingplacex.geometry, placex.geometry)
235 AND rank_search > existingplacex.rank_search AND (rank_search < 28 or name is not null);
237 update placex set indexed_status = 2 where indexed_status = 0
238 AND ST_Intersects(existingplacex.geometry, placex.geometry)
239 AND NOT ST_Intersects(NEW.geometry, placex.geometry)
240 AND rank_search > existingplacex.rank_search AND (rank_search < 28 or name is not null);
245 -- Has something relevant changed?
246 IF coalesce(existing.name::text, '') != coalesce(NEW.name::text, '')
247 OR coalesce(existing.extratags::text, '') != coalesce(NEW.extratags::text, '')
248 OR coalesce(existing.address, ''::hstore) != coalesce(NEW.address, ''::hstore)
249 OR coalesce(existing.admin_level, 15) != coalesce(NEW.admin_level, 15)
250 OR existing.geometry::text != NEW.geometry::text
254 address = NEW.address,
255 extratags = NEW.extratags,
256 admin_level = NEW.admin_level,
257 geometry = NEW.geometry
258 WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id
259 and class = NEW.class and type = NEW.type;
261 -- Postcode areas are only kept, when there is an actual postcode assigned.
262 IF NEW.class = 'boundary' AND NEW.type = 'postal_code' THEN
263 IF NEW.address is NULL OR NOT NEW.address ? 'postcode' THEN
264 -- postcode was deleted, no longer retain in placex
265 DELETE FROM placex where place_id = existingplacex.place_id;
269 NEW.name := hstore('ref', NEW.address->'postcode');
272 -- Boundaries must be areas.
273 IF NEW.class in ('boundary')
274 AND ST_GeometryType(NEW.geometry) not in ('ST_Polygon','ST_MultiPolygon')
276 DELETE FROM placex where place_id = existingplacex.place_id;
280 -- Update the placex entry in-place.
283 address = NEW.address,
284 parent_place_id = null,
285 extratags = NEW.extratags,
286 admin_level = NEW.admin_level,
288 geometry = NEW.geometry
289 WHERE place_id = existingplacex.place_id;
291 -- If an address node which is part of a interpolation line changes
292 -- mark this line for reparenting.
293 -- (Already here, because interpolation lines are reindexed before nodes,
294 -- so in the second call it would be too late.)
296 and coalesce(existing.address, ''::hstore) != coalesce(NEW.address, ''::hstore)
298 result:= osmline_reinsert(NEW.osm_id, NEW.geometry);
301 -- Invalidate linked places: they potentially get a new name and addresses.
302 IF existingplacex.linked_place_id is not NULL THEN
305 extratags = p.extratags,
308 WHERE x.place_id = existingplacex.linked_place_id
309 and x.indexed_status = 0
310 and x.osm_type = p.osm_type
311 and x.osm_id = p.osm_id
312 and x.class = p.class;
315 -- Invalidate dependent objects effected by name changes
316 IF coalesce(existing.name::text, '') != coalesce(NEW.name::text, '')
318 IF existingplacex.rank_address between 26 and 27 THEN
319 -- When streets change their name, this may have an effect on POI objects
320 -- with addr:street tags.
321 UPDATE placex SET indexed_status = 2
322 WHERE indexed_status = 0 and address ? 'street'
323 and parent_place_id = existingplacex.place_id;
324 UPDATE placex SET indexed_status = 2
325 WHERE indexed_status = 0 and rank_search = 30 and address ? 'street'
326 and ST_DWithin(NEW.geometry, geometry, 0.002);
327 ELSEIF existingplacex.rank_address between 16 and 25 THEN
328 -- When places change their name, this may have an effect on POI objects
329 -- with addr:place tags.
330 UPDATE placex SET indexed_status = 2
331 WHERE indexed_status = 0 and address ? 'place' and rank_search = 30
332 and parent_place_id = existingplacex.place_id;
333 -- No update of surrounding objects, potentially too expensive.
338 -- Abort the insertion (we modified the existing place instead)
344 CREATE OR REPLACE FUNCTION place_delete()
351 {% if debug %}RAISE WARNING 'delete: % % % %',OLD.osm_type,OLD.osm_id,OLD.class,OLD.type;{% endif %}
353 -- deleting large polygons can have a massive effect on the system - require manual intervention to let them through
354 IF st_area(OLD.geometry) > 2 and st_isvalid(OLD.geometry) THEN
355 SELECT bool_or(not (rank_address = 0 or rank_address > 25)) as ranked FROM placex WHERE osm_type = OLD.osm_type and osm_id = OLD.osm_id and class = OLD.class and type = OLD.type INTO has_rank;
357 insert into import_polygon_delete (osm_type, osm_id, class, type) values (OLD.osm_type,OLD.osm_id,OLD.class,OLD.type);
363 UPDATE placex set indexed_status = 100 where osm_type = OLD.osm_type and osm_id = OLD.osm_id and class = OLD.class and type = OLD.type;
365 -- interpolations are special
366 IF OLD.osm_type='W' and OLD.class = 'place' and OLD.type = 'houses' THEN
367 UPDATE location_property_osmline set indexed_status = 100 where osm_id = OLD.osm_id; -- osm_id = wayid (=old.osm_id)