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()
13 existingplacex RECORD;
16 existing_is_area BOOLEAN;
18 address_rank SMALLINT;
22 RAISE WARNING 'place_insert: % % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type,st_area(NEW.geometry);
25 -- Filter tuples with bad geometries.
26 IF ST_IsEmpty(NEW.geometry) OR NOT ST_IsValid(NEW.geometry) THEN
27 INSERT INTO import_polygon_error (osm_type, osm_id, class, type, name,
28 country_code, updated, errormessage,
29 prevgeometry, newgeometry)
30 VALUES (NEW.osm_type, NEW.osm_id, NEW.class, NEW.type, NEW.name,
31 NEW.address->'country', now(), ST_IsValidReason(NEW.geometry),
34 RAISE WARNING 'Invalid Geometry: % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type;
39 -- Remove the place from the list of places to be deleted
40 DELETE FROM place_to_be_deleted pdel
41 WHERE pdel.osm_type = NEW.osm_type and pdel.osm_id = NEW.osm_id
42 and pdel.class = NEW.class and pdel.type = NEW.type;
44 -- Have we already done this place?
45 SELECT * INTO existing
47 WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id
48 and class = NEW.class and type = NEW.type;
50 {% if debug %}RAISE WARNING 'Existing: %',existing.osm_id;{% endif %}
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 is_area := ST_GeometryType(NEW.geometry) in ('ST_Polygon','ST_MultiPolygon');
62 area_size = ST_Area(NEW.geometry);
65 -- When an area is changed from large to small: log and discard change
66 IF existing.geometry is not null AND ST_IsValid(existing.geometry)
67 AND ST_Area(existing.geometry) > 0.02
68 AND is_area AND area_size < ST_Area(existing.geometry) * 0.5
70 INSERT INTO import_polygon_error (osm_type, osm_id, class, type, name,
71 country_code, updated, errormessage,
72 prevgeometry, newgeometry)
73 VALUES (NEW.osm_type, NEW.osm_id, NEW.class, NEW.type, NEW.name,
74 NEW.address->'country', now(),
75 'Area reduced from '||st_area(existing.geometry)||' to '||st_area(NEW.geometry),
76 existing.geometry, NEW.geometry);
81 -- Get the existing placex entry.
82 SELECT * INTO existingplacex
84 WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id
85 and class = NEW.class and type = NEW.type;
87 {% if debug %}RAISE WARNING 'Existing PlaceX: %',existingplacex.place_id;{% endif %}
89 IF existingplacex.osm_type IS NULL THEN
90 -- Inserting a new placex.
92 INSERT INTO placex (osm_type, osm_id, class, type, name,
93 admin_level, address, extratags, geometry)
94 VALUES (NEW.osm_type, NEW.osm_id, NEW.class, NEW.type, NEW.name,
95 NEW.admin_level, NEW.address, NEW.extratags, NEW.geometry)
96 RETURNING rank_address
98 PERFORM update_invalidate_for_new_place(newplacex.rank_address,
100 NEW.osm_Type, NEW.geometry);
103 -- Modify an existing placex.
104 IF is_rankable_place(NEW.osm_type, NEW.class, NEW.admin_level,
105 NEW.name, NEW.extratags, is_area)
107 -- Recompute the ranks to look out for changes.
108 -- We use the old country assignment here which is good enough for the
110 SELECT * INTO search_rank, address_rank
111 FROM compute_place_rank(existingplacex.country_code,
112 CASE WHEN is_area THEN 'A' ELSE NEW.osm_type END,
113 NEW.class, NEW.type, NEW.admin_level,
114 (NEW.extratags->'capital') = 'yes',
115 NEW.address->'postcode');
117 existing_is_area := ST_GeometryType(existingplacex.geometry) in ('ST_Polygon','ST_MultiPolygon');
119 IF (address_rank BETWEEN 4 and 27
120 AND (existingplacex.rank_address <= 0 OR existingplacex.rank_address > 27))
121 OR (not existing_is_area and is_area)
123 -- object newly relevant for addressing, invalidate new version
124 PERFORM update_invalidate_for_new_place(address_rank,
126 NEW.osm_type, NEW.geometry);
127 ELSEIF (existingplacex.rank_address BETWEEN 4 and 27
128 AND (address_rank <= 0 OR address_rank > 27))
129 OR (existing_is_area and not is_area)
131 -- object no longer relevant for addressing, invalidate old version
132 PERFORM update_invalidate_for_new_place(existingplacex.rank_address,
133 existing_is_area, null,
134 existingplacex.osm_type,
135 existingplacex.geometry);
136 ELSEIF address_rank BETWEEN 4 and 27 THEN
137 IF coalesce(existing.name, ''::hstore) != coalesce(NEW.name, ''::hstore)
138 OR existing.admin_level != NEW.admin_level
140 -- Name changes may have an effect on searchable objects and parenting
141 -- for new and old areas.
142 PERFORM update_invalidate_for_new_place(address_rank, is_area, null,
144 CASE WHEN is_area and existing_is_area
145 THEN ST_Union(NEW.geometry, existingplacex.geometry)
146 ELSE ST_Collect(NEW.geometry, existingplacex.geometry)
148 ELSEIF existingplacex.geometry::text != NEW.geometry::text THEN
149 -- Geometry change, just invalidate the changed areas.
150 -- Changes of other geometry types are currently ignored.
151 IF is_area and existing_is_area THEN
152 PERFORM update_invalidate_for_new_place(address_rank, true, null,
154 ST_SymDifference(existingplacex.geometry,
160 -- Invalidate linked places: they potentially get a new name and addresses.
161 IF existingplacex.linked_place_id is not NULL THEN
164 extratags = p.extratags,
167 WHERE x.place_id = existingplacex.linked_place_id
168 and x.indexed_status = 0
169 and x.osm_type = p.osm_type
170 and x.osm_id = p.osm_id
171 and x.class = p.class;
174 -- update placex in place
177 address = NEW.address,
178 parent_place_id = null,
179 extratags = NEW.extratags,
180 admin_level = NEW.admin_level,
181 rank_address = address_rank,
182 rank_search = search_rank,
184 geometry = CASE WHEN address_rank = 0
185 THEN simplify_large_polygons(NEW.geometry)
186 ELSE NEW.geometry END
187 WHERE place_id = existingplacex.place_id;
189 -- New place is not really valid, remove the placex entry
190 UPDATE placex SET indexed_status = 100 WHERE place_id = existingplacex.place_id;
193 -- When an existing way is updated, recalculate entrances
194 IF existingplacex.osm_type = 'W' and (existingplacex.rank_search > 27 or existingplacex.class IN ('landuse', 'leisure')) THEN
195 PERFORM place_update_entrances(existingplacex.place_id, existingplacex.osm_id);
199 -- Finally update place itself.
200 IF existing.osm_type is not NULL THEN
201 -- If there is already an entry in place, just update that, if necessary.
202 IF coalesce(existing.name, ''::hstore) != coalesce(NEW.name, ''::hstore)
203 or coalesce(existing.address, ''::hstore) != coalesce(NEW.address, ''::hstore)
204 or coalesce(existing.extratags, ''::hstore) != coalesce(NEW.extratags, ''::hstore)
205 or coalesce(existing.admin_level, 15) != coalesce(NEW.admin_level, 15)
206 or existing.geometry::text != NEW.geometry::text
210 address = NEW.address,
211 extratags = NEW.extratags,
212 admin_level = NEW.admin_level,
213 geometry = NEW.geometry
214 WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id
215 and class = NEW.class and type = NEW.type;
225 CREATE OR REPLACE FUNCTION place_delete()
231 {% if debug %}RAISE WARNING 'Delete for % % %/%', OLD.osm_type, OLD.osm_id, OLD.class, OLD.type;{% endif %}
233 deferred := ST_IsValid(OLD.geometry) and ST_Area(OLD.geometry) > 2;
235 SELECT bool_or(not (rank_address = 0 or rank_address > 25)) INTO deferred
237 WHERE osm_type = OLD.osm_type and osm_id = OLD.osm_id
238 and class = OLD.class and type = OLD.type;
241 INSERT INTO place_to_be_deleted (osm_type, osm_id, class, type, deferred)
242 VALUES(OLD.osm_type, OLD.osm_id, OLD.class, OLD.type, COALESCE(deferred, FALSE));