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 and pdel.type = NEW.type;
 
  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   IF existing.osm_type IS NULL THEN
 
  51     DELETE FROM place where osm_type = NEW.osm_type and osm_id = NEW.osm_id and class = NEW.class;
 
  54   -- Remove any old logged data.
 
  55   DELETE from import_polygon_error where osm_type = NEW.osm_type and osm_id = NEW.osm_id;
 
  56   DELETE from import_polygon_delete where osm_type = NEW.osm_type and osm_id = NEW.osm_id;
 
  58   -- ---- Interpolation Lines
 
  60   IF NEW.class='place' and NEW.type='houses'
 
  61      and NEW.osm_type='W' and ST_GeometryType(NEW.geometry) = 'ST_LineString'
 
  63     PERFORM reinsert_interpolation(NEW.osm_id, NEW.address, NEW.geometry);
 
  65     -- Now invalidate all address nodes on the line.
 
  66     -- They get their parent from the interpolation.
 
  67     UPDATE placex p SET indexed_status = 2
 
  68       FROM planet_osm_ways w
 
  69       WHERE w.id = NEW.osm_id and p.osm_type = 'N' and p.osm_id = any(w.nodes);
 
  71     -- If there is already an entry in place, just update that, if necessary.
 
  72     IF existing.osm_type is not null THEN
 
  73       IF coalesce(existing.address, ''::hstore) != coalesce(NEW.address, ''::hstore)
 
  74          OR existing.geometry::text != NEW.geometry::text
 
  78               address = NEW.address,
 
  79               extratags = NEW.extratags,
 
  80               admin_level = NEW.admin_level,
 
  81               geometry = NEW.geometry
 
  82           WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id
 
  83                 and class = NEW.class and type = NEW.type;
 
  92   -- ---- Postcode points.
 
  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.
 
  98     -- Remove any old placex entry.
 
  99     DELETE FROM placex WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id;
 
 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
 
 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;
 
 121   -- ---- All other place types.
 
 123   -- When an area is changed from large to small: log and discard change
 
 124   IF existing.geometry is not null AND ST_IsValid(existing.geometry)
 
 125     AND ST_Area(existing.geometry) > 0.02
 
 126     AND ST_GeometryType(NEW.geometry) in ('ST_Polygon','ST_MultiPolygon')
 
 127     AND ST_Area(NEW.geometry) < ST_Area(existing.geometry) * 0.5
 
 129     INSERT INTO import_polygon_error (osm_type, osm_id, class, type, name,
 
 130                                       country_code, updated, errormessage,
 
 131                                       prevgeometry, newgeometry)
 
 132       VALUES (NEW.osm_type, NEW.osm_id, NEW.class, NEW.type, NEW.name,
 
 133               NEW.address->'country', now(),
 
 134               'Area reduced from '||st_area(existing.geometry)||' to '||st_area(NEW.geometry),
 
 135               existing.geometry, NEW.geometry);
 
 140   -- If an address node is part of a interpolation line and changes or is
 
 141   -- newly inserted (happens when the node already existed but now gets address
 
 142   -- information), then mark the interpolation line for reparenting.
 
 143   -- (Already here, because interpolation lines are reindexed before nodes,
 
 144   --  so in the second call it would be too late.)
 
 146      and coalesce(existing.address, ''::hstore) != coalesce(NEW.address, ''::hstore)
 
 149         SELECT DISTINCT osm_id, address, geometry FROM place, planet_osm_ways w
 
 150         WHERE NEW.geometry && place.geometry
 
 151               and place.osm_type = 'W'
 
 152               and place.address ? 'interpolation'
 
 153               and exists (SELECT * FROM location_property_osmline
 
 154                           WHERE osm_id = place.osm_id
 
 155                                 and indexed_status in (0, 2))
 
 156               and w.id = place.osm_id and NEW.osm_id = any (w.nodes)
 
 158         PERFORM reinsert_interpolation(interpol.osm_id, interpol.address,
 
 163   -- Get the existing placex entry.
 
 164   SELECT * INTO existingplacex
 
 166     WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id
 
 167           and class = NEW.class and type = NEW.type;
 
 169   {% if debug %}RAISE WARNING 'Existing PlaceX: %',existingplacex.place_id;{% endif %}
 
 171   -- To paraphrase: if there isn't an existing item, OR if the admin level has changed
 
 172   IF existingplacex.osm_type IS NULL
 
 173      or (existingplacex.class = 'boundary'
 
 174          and ((coalesce(existingplacex.admin_level, 15) != coalesce(NEW.admin_level, 15)
 
 175                and existingplacex.type = 'administrative')
 
 176               or existingplacex.type != NEW.type))
 
 178     {% if config.get_bool('LIMIT_REINDEXING') %}
 
 179     -- sanity check: ignore admin_level changes on places with too many active children
 
 180     -- or we end up reindexing entire countries because somebody accidentally deleted admin_level
 
 181     IF existingplacex.osm_type IS NOT NULL THEN
 
 182       SELECT count(*) INTO i FROM
 
 183         (SELECT 'a' FROM placex, place_addressline
 
 184           WHERE address_place_id = existingplacex.place_id
 
 185                 and placex.place_id = place_addressline.place_id
 
 186                 and indexed_status = 0 and place_addressline.isaddress LIMIT 100001) sub;
 
 193     IF existingplacex.osm_type is not NULL THEN
 
 194       -- Mark any existing place for delete in the placex table
 
 195       UPDATE placex SET indexed_status = 100
 
 196         WHERE placex.osm_type = NEW.osm_type and placex.osm_id = NEW.osm_id
 
 197               and placex.class = NEW.class and placex.type = NEW.type;
 
 200     -- Process it as a new insertion
 
 201     INSERT INTO placex (osm_type, osm_id, class, type, name,
 
 202                         admin_level, address, extratags, geometry)
 
 203       VALUES (NEW.osm_type, NEW.osm_id, NEW.class, NEW.type, NEW.name,
 
 204               NEW.admin_level, NEW.address, NEW.extratags, NEW.geometry);
 
 206     {% if debug %}RAISE WARNING 'insert done % % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type,NEW.name;{% endif %}
 
 208     IF existing.osm_type is not NULL THEN
 
 209       -- If there is already an entry in place, just update that, if necessary.
 
 210       IF coalesce(existing.name, ''::hstore) != coalesce(NEW.name, ''::hstore)
 
 211          or coalesce(existing.address, ''::hstore) != coalesce(NEW.address, ''::hstore)
 
 212          or coalesce(existing.extratags, ''::hstore) != coalesce(NEW.extratags, ''::hstore)
 
 213          or coalesce(existing.admin_level, 15) != coalesce(NEW.admin_level, 15)
 
 214          or existing.geometry::text != NEW.geometry::text
 
 218               address = NEW.address,
 
 219               extratags = NEW.extratags,
 
 220               admin_level = NEW.admin_level,
 
 221               geometry = NEW.geometry
 
 222           WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id
 
 223                 and class = NEW.class and type = NEW.type;
 
 232   -- Special case for polygon shape changes because they tend to be large
 
 233   -- and we can be a bit clever about how we handle them
 
 234   IF existing.geometry::text != NEW.geometry::text
 
 235      AND ST_GeometryType(existing.geometry) in ('ST_Polygon','ST_MultiPolygon')
 
 236      AND ST_GeometryType(NEW.geometry) in ('ST_Polygon','ST_MultiPolygon')
 
 239     IF ST_Area(NEW.geometry) < 0.000000001 AND ST_Area(existingplacex.geometry) < 1
 
 241       -- re-index points that have moved in / out of the polygon.
 
 242       -- Could be done as a single query but postgres gets the index usage wrong.
 
 243       update placex set indexed_status = 2 where indexed_status = 0
 
 244           AND ST_Intersects(NEW.geometry, placex.geometry)
 
 245           AND NOT ST_Intersects(existingplacex.geometry, placex.geometry)
 
 246           AND rank_search > existingplacex.rank_search AND (rank_search < 28 or name is not null);
 
 248       update placex set indexed_status = 2 where indexed_status = 0
 
 249           AND ST_Intersects(existingplacex.geometry, placex.geometry)
 
 250           AND NOT ST_Intersects(NEW.geometry, placex.geometry)
 
 251           AND rank_search > existingplacex.rank_search AND (rank_search < 28 or name is not null);
 
 256   -- Has something relevant changed?
 
 257   IF coalesce(existing.name::text, '') != coalesce(NEW.name::text, '')
 
 258      OR coalesce(existing.extratags::text, '') != coalesce(NEW.extratags::text, '')
 
 259      OR coalesce(existing.address, ''::hstore) != coalesce(NEW.address, ''::hstore)
 
 260      OR coalesce(existing.admin_level, 15) != coalesce(NEW.admin_level, 15)
 
 261      OR existing.geometry::text != NEW.geometry::text
 
 265           address = NEW.address,
 
 266           extratags = NEW.extratags,
 
 267           admin_level = NEW.admin_level,
 
 268           geometry = NEW.geometry
 
 269       WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id
 
 270             and class = NEW.class and type = NEW.type;
 
 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;
 
 280       NEW.name := hstore('ref', NEW.address->'postcode');
 
 283     -- Boundaries must be areas.
 
 284     IF NEW.class in ('boundary')
 
 285        AND ST_GeometryType(NEW.geometry) not in ('ST_Polygon','ST_MultiPolygon')
 
 287       DELETE FROM placex where place_id = existingplacex.place_id;
 
 291     -- Update the placex entry in-place.
 
 294           address = NEW.address,
 
 295           parent_place_id = null,
 
 296           extratags = NEW.extratags,
 
 297           admin_level = NEW.admin_level,
 
 299           geometry = CASE WHEN existingplacex.rank_address = 0
 
 300                           THEN simplify_large_polygons(NEW.geometry)
 
 301                           ELSE NEW.geometry END
 
 302       WHERE place_id = existingplacex.place_id;
 
 304     -- Invalidate linked places: they potentially get a new name and addresses.
 
 305     IF existingplacex.linked_place_id is not NULL THEN
 
 308             extratags = p.extratags,
 
 311         WHERE x.place_id = existingplacex.linked_place_id
 
 312               and x.indexed_status = 0
 
 313               and x.osm_type = p.osm_type
 
 314               and x.osm_id = p.osm_id
 
 315               and x.class = p.class;
 
 318     -- Invalidate dependent objects effected by name changes
 
 319     IF coalesce(existing.name::text, '') != coalesce(NEW.name::text, '')
 
 321       IF existingplacex.rank_address between 26 and 27 THEN
 
 322         -- When streets change their name, this may have an effect on POI objects
 
 323         -- with addr:street tags.
 
 324         UPDATE placex SET indexed_status = 2
 
 325           WHERE indexed_status = 0 and address ? 'street'
 
 326                 and parent_place_id = existingplacex.place_id;
 
 327         UPDATE placex SET indexed_status = 2
 
 328           WHERE indexed_status = 0 and rank_search = 30 and address ? 'street'
 
 329                 and ST_DWithin(NEW.geometry, geometry, 0.002);
 
 330       ELSEIF existingplacex.rank_address between 16 and 25 THEN
 
 331         -- When places change their name, this may have an effect on POI objects
 
 332         -- with addr:place tags.
 
 333         UPDATE placex SET indexed_status = 2
 
 334           WHERE indexed_status = 0 and address ? 'place' and rank_search = 30
 
 335                 and parent_place_id = existingplacex.place_id;
 
 336         -- No update of surrounding objects, potentially too expensive.
 
 341   -- Abort the insertion (we modified the existing place instead)
 
 346 CREATE OR REPLACE FUNCTION place_delete()
 
 352   {% if debug %}RAISE WARNING 'Delete for % % %/%', OLD.osm_type, OLD.osm_id, OLD.class, OLD.type;{% endif %}
 
 354   deferred := ST_IsValid(OLD.geometry) and ST_Area(OLD.geometry) > 2;
 
 356     SELECT bool_or(not (rank_address = 0 or rank_address > 25)) INTO deferred
 
 358       WHERE osm_type = OLD.osm_type and osm_id = OLD.osm_id
 
 359             and class = OLD.class and type = OLD.type;
 
 362   INSERT INTO place_to_be_deleted (osm_type, osm_id, class, type, deferred)
 
 363     VALUES(OLD.osm_type, OLD.osm_id, OLD.class, OLD.type, deferred);