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 -- Functions for address interpolation objects in location_property_osmline.
 
  11 CREATE OR REPLACE FUNCTION get_interpolation_address(in_address HSTORE, wayid BIGINT)
 
  18   IF in_address ? 'street' or in_address ? 'place' THEN
 
  22   SELECT nodes INTO waynodes FROM planet_osm_ways WHERE id = wayid;
 
  24     SELECT placex.address, placex.osm_id FROM placex
 
  25      WHERE osm_type = 'N' and osm_id = ANY(waynodes)
 
  26            and placex.address is not null
 
  27            and (placex.address ? 'street' or placex.address ? 'place')
 
  28            and indexed_status < 100
 
  30     -- mark it as a derived address
 
  31     RETURN location.address || in_address || hstore('_inherited', '');
 
  37 LANGUAGE plpgsql STABLE PARALLEL SAFE;
 
  41 -- find the parent road of the cut road parts
 
  42 CREATE OR REPLACE FUNCTION get_interpolation_parent(token_info JSONB,
 
  44                                                     centroid GEOMETRY, geom GEOMETRY)
 
  48   parent_place_id BIGINT;
 
  51   parent_place_id := find_parent_for_address(token_info, partition, centroid);
 
  53   IF parent_place_id is null THEN
 
  54     FOR location IN SELECT place_id FROM placex
 
  55         WHERE ST_DWithin(geom, placex.geometry, 0.001)
 
  56               and placex.rank_search = 26
 
  57               and placex.osm_type = 'W' -- needed for index selection
 
  58         ORDER BY CASE WHEN ST_GeometryType(geom) = 'ST_Line' THEN
 
  59                   (ST_distance(placex.geometry, ST_LineInterpolatePoint(geom,0))+
 
  60                   ST_distance(placex.geometry, ST_LineInterpolatePoint(geom,0.5))+
 
  61                   ST_distance(placex.geometry, ST_LineInterpolatePoint(geom,1)))
 
  62                  ELSE ST_distance(placex.geometry, geom) END
 
  66       parent_place_id := location.place_id;
 
  70   RETURN parent_place_id;
 
  73 LANGUAGE plpgsql STABLE PARALLEL SAFE;
 
  76 CREATE OR REPLACE FUNCTION reinsert_interpolation(way_id BIGINT, addr HSTORE,
 
  83   IF addr is NULL OR NOT addr ? 'interpolation'
 
  84          OR NOT (addr->'interpolation' in ('odd', 'even', 'all')
 
  85                  or addr->'interpolation' similar to '[1-9]')
 
  87     -- the new interpolation is illegal, simply remove existing entries
 
  88     DELETE FROM location_property_osmline WHERE osm_id = way_id;
 
  90     -- Get the existing entry from the interpolation table.
 
  91     SELECT array_agg(place_id) INTO existing
 
  92       FROM location_property_osmline WHERE osm_id = way_id;
 
  94     IF existing IS NULL or array_length(existing, 1) = 0 THEN
 
  95       INSERT INTO location_property_osmline (osm_id, address, linegeo)
 
  96         VALUES (way_id, addr, geom);
 
  98       -- Update the interpolation table:
 
  99       --   The first entry gets the original data, all other entries
 
 100       --   are removed and will be recreated on indexing.
 
 101       --   (An interpolation can be split up, if it has more than 2 address nodes)
 
 102       UPDATE location_property_osmline
 
 107         WHERE place_id = existing[1];
 
 108       IF array_length(existing, 1) > 1 THEN
 
 109         DELETE FROM location_property_osmline
 
 110           WHERE place_id = any(existing[2:]);
 
 121 CREATE OR REPLACE FUNCTION osmline_insert()
 
 127   NEW.place_id := nextval('seq_place');
 
 128   NEW.indexed_date := now();
 
 130   IF NEW.indexed_status IS NULL THEN
 
 131       IF NEW.address is NULL OR NOT NEW.address ? 'interpolation'
 
 132          OR NOT (NEW.address->'interpolation' in ('odd', 'even', 'all')
 
 133                  or NEW.address->'interpolation' similar to '[1-9]')
 
 135           -- alphabetic interpolation is not supported
 
 139       NEW.indexed_status := 1; --STATUS_NEW
 
 140       centroid := get_center_point(NEW.linegeo);
 
 141       NEW.country_code := lower(get_country_code(centroid));
 
 143       NEW.partition := get_partition(NEW.country_code);
 
 144       NEW.geometry_sector := geometry_sector(NEW.partition, centroid);
 
 153 CREATE OR REPLACE FUNCTION osmline_update()
 
 172   IF OLD.indexed_status = 100 THEN
 
 173     delete from location_property_osmline where place_id = OLD.place_id;
 
 177   IF NEW.indexed_status != 0 OR OLD.indexed_status = 0 THEN
 
 181   NEW.parent_place_id := get_interpolation_parent(NEW.token_info, NEW.partition,
 
 182                                                   get_center_point(NEW.linegeo),
 
 185   -- Cannot find a parent street. We will not be able to display a reliable
 
 186   -- address, so drop entire interpolation.
 
 187   IF NEW.parent_place_id is NULL THEN
 
 188     DELETE FROM location_property_osmline where place_id = OLD.place_id;
 
 192   NEW.token_info := token_strip_info(NEW.token_info);
 
 193   IF NEW.address ? '_inherited' THEN
 
 194     NEW.address := hstore('interpolation', NEW.address->'interpolation');
 
 197   -- If the line was newly inserted, split the line as necessary.
 
 198   IF OLD.indexed_status = 1 THEN
 
 199     IF NEW.address->'interpolation' in ('odd', 'even') THEN
 
 201       stepmod := CASE WHEN NEW.address->'interpolation' = 'odd' THEN 1 ELSE 0 END;
 
 203       NEW.step := CASE WHEN NEW.address->'interpolation' = 'all'
 
 205                        ELSE (NEW.address->'interpolation')::SMALLINT END;
 
 209     SELECT nodes INTO waynodes
 
 210       FROM planet_osm_ways WHERE id = NEW.osm_id;
 
 212     IF array_upper(waynodes, 1) IS NULL THEN
 
 217     SELECT null::integer as hnr INTO prevnode;
 
 219     -- Go through all nodes on the interpolation line that have a housenumber.
 
 221       SELECT DISTINCT ON (nodeidpos)
 
 222           osm_id, address, geometry,
 
 223           -- Take the postcode from the node only if it has a housenumber itself.
 
 224           -- Note that there is a corner-case where the node has a wrongly
 
 225           -- formatted postcode and therefore 'postcode' contains a derived
 
 227           CASE WHEN address ? 'postcode' THEN placex.postcode ELSE NULL::text END as postcode,
 
 228           (address->'housenumber')::integer as hnr
 
 229         FROM placex, generate_series(1, array_upper(waynodes, 1)) nodeidpos
 
 230         WHERE osm_type = 'N' and osm_id = waynodes[nodeidpos]::BIGINT
 
 231               and address is not NULL and address ? 'housenumber'
 
 232               and address->'housenumber' ~ '^[0-9]{1,6}$'
 
 233               and ST_Distance(NEW.linegeo, geometry) < 0.0005
 
 236       {% if debug %}RAISE WARNING 'processing point % (%)', nextnode.hnr, ST_AsText(nextnode.geometry);{% endif %}
 
 237       IF linegeo is null THEN
 
 238         linegeo := NEW.linegeo;
 
 240         splitpoint := ST_LineLocatePoint(linegeo, nextnode.geometry);
 
 241         IF splitpoint = 0 THEN
 
 242           -- Corner case where the splitpoint falls on the first point
 
 243           -- and thus would not return a geometry. Skip that section.
 
 245         ELSEIF splitpoint = 1 THEN
 
 246           -- Point is at the end of the line.
 
 247           sectiongeo := linegeo;
 
 251           sectiongeo := ST_LineSubstring(linegeo, 0, splitpoint);
 
 252           linegeo := ST_LineSubstring(linegeo, splitpoint, 1);
 
 256       IF prevnode.hnr is not null
 
 257          -- Check if there are housenumbers to interpolate between the
 
 258          -- regularly mapped housenumbers.
 
 259          -- (Conveniently also fails if one of the house numbers is not a number.)
 
 260          and abs(prevnode.hnr - nextnode.hnr) > NEW.step
 
 261          -- If the interpolation geometry is broken or two nodes are at the
 
 262          -- same place, then splitting might produce a point. Ignore that.
 
 263          and ST_GeometryType(sectiongeo) = 'ST_LineString'
 
 265         IF prevnode.hnr < nextnode.hnr THEN
 
 266           startnumber := prevnode.hnr;
 
 267           endnumber := nextnode.hnr;
 
 269           startnumber := nextnode.hnr;
 
 270           endnumber := prevnode.hnr;
 
 271           sectiongeo := ST_Reverse(sectiongeo);
 
 274         -- Adjust the interpolation, so that only inner housenumbers
 
 275         -- are taken into account.
 
 276         IF stepmod is null THEN
 
 277           newstart := startnumber + NEW.step;
 
 279           newstart := startnumber + 1;
 
 280           moddiff := newstart % NEW.step - stepmod;
 
 282             newstart := newstart + (NEW.step + moddiff);
 
 284             newstart := newstart + moddiff;
 
 287         newend := newstart + ((endnumber - 1 - newstart) / NEW.step) * NEW.step;
 
 289         -- If newstart and newend are the same, then this returns a point.
 
 290         sectiongeo := ST_LineSubstring(sectiongeo,
 
 291                               (newstart - startnumber)::float / (endnumber - startnumber)::float,
 
 292                               (newend - startnumber)::float / (endnumber - startnumber)::float);
 
 293         startnumber := newstart;
 
 296         -- determine postcode
 
 297         postcode := coalesce(prevnode.postcode, nextnode.postcode, postcode);
 
 298         IF postcode is NULL and NEW.parent_place_id > 0 THEN
 
 299             SELECT placex.postcode FROM placex
 
 300               WHERE place_id = NEW.parent_place_id INTO postcode;
 
 302         IF postcode is NULL THEN
 
 303             postcode := get_nearest_postcode(NEW.country_code, nextnode.geometry);
 
 306         -- Add the interpolation. If this is the first segment, just modify
 
 307         -- the interpolation to be inserted, otherwise add an additional one
 
 308         -- (marking it indexed already).
 
 309         IF NEW.startnumber IS NULL THEN
 
 310             NEW.startnumber := startnumber;
 
 311             NEW.endnumber := endnumber;
 
 312             NEW.linegeo := ST_ReducePrecision(sectiongeo, 0.0000001);
 
 313             NEW.postcode := postcode;
 
 315           INSERT INTO location_property_osmline
 
 316                  (linegeo, partition, osm_id, parent_place_id,
 
 317                   startnumber, endnumber, step,
 
 318                   address, postcode, country_code,
 
 319                   geometry_sector, indexed_status)
 
 320           VALUES (ST_ReducePrecision(sectiongeo, 0.0000001),
 
 321                   NEW.partition, NEW.osm_id, NEW.parent_place_id,
 
 322                   startnumber, endnumber, NEW.step,
 
 323                   NEW.address, postcode,
 
 324                   NEW.country_code, NEW.geometry_sector, 0);
 
 328       -- early break if we are out of line string,
 
 329       -- might happen when a line string loops back on itself
 
 330       IF linegeo is null or ST_GeometryType(linegeo) != 'ST_LineString' THEN
 
 334       prevnode := nextnode;