mirror of
https://github.com/osm-search/Nominatim.git
synced 2024-12-23 21:14:11 +03:00
341 lines
12 KiB
PL/PgSQL
341 lines
12 KiB
PL/PgSQL
-- SPDX-License-Identifier: GPL-2.0-only
|
|
--
|
|
-- This file is part of Nominatim. (https://nominatim.org)
|
|
--
|
|
-- Copyright (C) 2022 by the Nominatim developer community.
|
|
-- For a full list of authors see the git log.
|
|
|
|
-- Functions for address interpolation objects in location_property_osmline.
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION get_interpolation_address(in_address HSTORE, wayid BIGINT)
|
|
RETURNS HSTORE
|
|
AS $$
|
|
DECLARE
|
|
location RECORD;
|
|
waynodes BIGINT[];
|
|
BEGIN
|
|
IF in_address ? 'street' or in_address ? 'place' THEN
|
|
RETURN in_address;
|
|
END IF;
|
|
|
|
SELECT nodes INTO waynodes FROM planet_osm_ways WHERE id = wayid;
|
|
FOR location IN
|
|
SELECT placex.address, placex.osm_id FROM placex
|
|
WHERE osm_type = 'N' and osm_id = ANY(waynodes)
|
|
and placex.address is not null
|
|
and (placex.address ? 'street' or placex.address ? 'place')
|
|
and indexed_status < 100
|
|
LOOP
|
|
-- mark it as a derived address
|
|
RETURN location.address || in_address || hstore('_inherited', '');
|
|
END LOOP;
|
|
|
|
RETURN in_address;
|
|
END;
|
|
$$
|
|
LANGUAGE plpgsql STABLE;
|
|
|
|
|
|
|
|
-- find the parent road of the cut road parts
|
|
CREATE OR REPLACE FUNCTION get_interpolation_parent(token_info JSONB,
|
|
partition SMALLINT,
|
|
centroid GEOMETRY, geom GEOMETRY)
|
|
RETURNS BIGINT
|
|
AS $$
|
|
DECLARE
|
|
parent_place_id BIGINT;
|
|
location RECORD;
|
|
BEGIN
|
|
parent_place_id := find_parent_for_address(token_info, partition, centroid);
|
|
|
|
IF parent_place_id is null THEN
|
|
FOR location IN SELECT place_id FROM placex
|
|
WHERE ST_DWithin(geom, placex.geometry, 0.001)
|
|
and placex.rank_search = 26
|
|
and placex.osm_type = 'W' -- needed for index selection
|
|
ORDER BY CASE WHEN ST_GeometryType(geom) = 'ST_Line' THEN
|
|
(ST_distance(placex.geometry, ST_LineInterpolatePoint(geom,0))+
|
|
ST_distance(placex.geometry, ST_LineInterpolatePoint(geom,0.5))+
|
|
ST_distance(placex.geometry, ST_LineInterpolatePoint(geom,1)))
|
|
ELSE ST_distance(placex.geometry, geom) END
|
|
ASC
|
|
LIMIT 1
|
|
LOOP
|
|
parent_place_id := location.place_id;
|
|
END LOOP;
|
|
END IF;
|
|
|
|
RETURN parent_place_id;
|
|
END;
|
|
$$
|
|
LANGUAGE plpgsql STABLE;
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION reinsert_interpolation(way_id BIGINT, addr HSTORE,
|
|
geom GEOMETRY)
|
|
RETURNS INT
|
|
AS $$
|
|
DECLARE
|
|
existing BIGINT[];
|
|
BEGIN
|
|
IF addr is NULL OR NOT addr ? 'interpolation'
|
|
OR NOT (addr->'interpolation' in ('odd', 'even', 'all')
|
|
or addr->'interpolation' similar to '[1-9]')
|
|
THEN
|
|
-- the new interpolation is illegal, simply remove existing entries
|
|
DELETE FROM location_property_osmline WHERE osm_id = way_id;
|
|
ELSE
|
|
-- Get the existing entry from the interpolation table.
|
|
SELECT array_agg(place_id) INTO existing
|
|
FROM location_property_osmline WHERE osm_id = way_id;
|
|
|
|
IF existing IS NULL or array_length(existing, 1) = 0 THEN
|
|
INSERT INTO location_property_osmline (osm_id, address, linegeo)
|
|
VALUES (way_id, addr, geom);
|
|
ELSE
|
|
-- Update the interpolation table:
|
|
-- The first entry gets the original data, all other entries
|
|
-- are removed and will be recreated on indexing.
|
|
-- (An interpolation can be split up, if it has more than 2 address nodes)
|
|
UPDATE location_property_osmline
|
|
SET address = addr,
|
|
linegeo = geom,
|
|
startnumber = null,
|
|
indexed_status = 1
|
|
WHERE place_id = existing[1];
|
|
IF array_length(existing, 1) > 1 THEN
|
|
DELETE FROM location_property_osmline
|
|
WHERE place_id = any(existing[2:]);
|
|
END IF;
|
|
END IF;
|
|
END IF;
|
|
|
|
RETURN 1;
|
|
END;
|
|
$$
|
|
LANGUAGE plpgsql;
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION osmline_insert()
|
|
RETURNS TRIGGER
|
|
AS $$
|
|
DECLARE
|
|
centroid GEOMETRY;
|
|
BEGIN
|
|
NEW.place_id := nextval('seq_place');
|
|
NEW.indexed_date := now();
|
|
|
|
IF NEW.indexed_status IS NULL THEN
|
|
IF NEW.address is NULL OR NOT NEW.address ? 'interpolation'
|
|
OR NOT (NEW.address->'interpolation' in ('odd', 'even', 'all')
|
|
or NEW.address->'interpolation' similar to '[1-9]')
|
|
THEN
|
|
-- alphabetic interpolation is not supported
|
|
RETURN NULL;
|
|
END IF;
|
|
|
|
NEW.indexed_status := 1; --STATUS_NEW
|
|
centroid := get_center_point(NEW.linegeo);
|
|
NEW.country_code := lower(get_country_code(centroid));
|
|
|
|
NEW.partition := get_partition(NEW.country_code);
|
|
NEW.geometry_sector := geometry_sector(NEW.partition, centroid);
|
|
END IF;
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$
|
|
LANGUAGE plpgsql;
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION osmline_update()
|
|
RETURNS TRIGGER
|
|
AS $$
|
|
DECLARE
|
|
waynodes BIGINT[];
|
|
prevnode RECORD;
|
|
nextnode RECORD;
|
|
startnumber INTEGER;
|
|
endnumber INTEGER;
|
|
newstart INTEGER;
|
|
newend INTEGER;
|
|
moddiff SMALLINT;
|
|
linegeo GEOMETRY;
|
|
splitpoint FLOAT;
|
|
sectiongeo GEOMETRY;
|
|
postcode TEXT;
|
|
stepmod SMALLINT;
|
|
BEGIN
|
|
-- deferred delete
|
|
IF OLD.indexed_status = 100 THEN
|
|
delete from location_property_osmline where place_id = OLD.place_id;
|
|
RETURN NULL;
|
|
END IF;
|
|
|
|
IF NEW.indexed_status != 0 OR OLD.indexed_status = 0 THEN
|
|
RETURN NEW;
|
|
END IF;
|
|
|
|
NEW.parent_place_id := get_interpolation_parent(NEW.token_info, NEW.partition,
|
|
get_center_point(NEW.linegeo),
|
|
NEW.linegeo);
|
|
|
|
-- Cannot find a parent street. We will not be able to display a reliable
|
|
-- address, so drop entire interpolation.
|
|
IF NEW.parent_place_id is NULL THEN
|
|
DELETE FROM location_property_osmline where place_id = OLD.place_id;
|
|
RETURN NULL;
|
|
END IF;
|
|
|
|
NEW.token_info := token_strip_info(NEW.token_info);
|
|
IF NEW.address ? '_inherited' THEN
|
|
NEW.address := hstore('interpolation', NEW.address->'interpolation');
|
|
END IF;
|
|
|
|
-- If the line was newly inserted, split the line as necessary.
|
|
IF OLD.indexed_status = 1 THEN
|
|
IF NEW.address->'interpolation' in ('odd', 'even') THEN
|
|
NEW.step := 2;
|
|
stepmod := CASE WHEN NEW.address->'interpolation' = 'odd' THEN 1 ELSE 0 END;
|
|
ELSE
|
|
NEW.step := CASE WHEN NEW.address->'interpolation' = 'all'
|
|
THEN 1
|
|
ELSE (NEW.address->'interpolation')::SMALLINT END;
|
|
stepmod := NULL;
|
|
END IF;
|
|
|
|
SELECT nodes INTO waynodes
|
|
FROM planet_osm_ways WHERE id = NEW.osm_id;
|
|
|
|
IF array_upper(waynodes, 1) IS NULL THEN
|
|
RETURN NEW;
|
|
END IF;
|
|
|
|
linegeo := null;
|
|
SELECT null::integer as hnr INTO prevnode;
|
|
|
|
-- Go through all nodes on the interpolation line that have a housenumber.
|
|
FOR nextnode IN
|
|
SELECT DISTINCT ON (nodeidpos)
|
|
osm_id, address, geometry,
|
|
-- Take the postcode from the node only if it has a housenumber itself.
|
|
-- Note that there is a corner-case where the node has a wrongly
|
|
-- formatted postcode and therefore 'postcode' contains a derived
|
|
-- variant.
|
|
CASE WHEN address ? 'postcode' THEN placex.postcode ELSE NULL::text END as postcode,
|
|
(address->'housenumber')::integer as hnr
|
|
FROM placex, generate_series(1, array_upper(waynodes, 1)) nodeidpos
|
|
WHERE osm_type = 'N' and osm_id = waynodes[nodeidpos]::BIGINT
|
|
and address is not NULL and address ? 'housenumber'
|
|
and address->'housenumber' ~ '^[0-9]{1,6}$'
|
|
and ST_Distance(NEW.linegeo, geometry) < 0.0005
|
|
ORDER BY nodeidpos
|
|
LOOP
|
|
{% if debug %}RAISE WARNING 'processing point % (%)', nextnode.hnr, ST_AsText(nextnode.geometry);{% endif %}
|
|
IF linegeo is null THEN
|
|
linegeo := NEW.linegeo;
|
|
ELSE
|
|
splitpoint := ST_LineLocatePoint(linegeo, nextnode.geometry);
|
|
IF splitpoint = 0 THEN
|
|
-- Corner case where the splitpoint falls on the first point
|
|
-- and thus would not return a geometry. Skip that section.
|
|
sectiongeo := NULL;
|
|
ELSEIF splitpoint = 1 THEN
|
|
-- Point is at the end of the line.
|
|
sectiongeo := linegeo;
|
|
linegeo := NULL;
|
|
ELSE
|
|
-- Split the line.
|
|
sectiongeo := ST_LineSubstring(linegeo, 0, splitpoint);
|
|
linegeo := ST_LineSubstring(linegeo, splitpoint, 1);
|
|
END IF;
|
|
END IF;
|
|
|
|
IF prevnode.hnr is not null
|
|
-- Check if there are housenumbers to interpolate between the
|
|
-- regularly mapped housenumbers.
|
|
-- (Conveniently also fails if one of the house numbers is not a number.)
|
|
and abs(prevnode.hnr - nextnode.hnr) > NEW.step
|
|
-- If the interpolation geometry is broken or two nodes are at the
|
|
-- same place, then splitting might produce a point. Ignore that.
|
|
and ST_GeometryType(sectiongeo) = 'ST_LineString'
|
|
THEN
|
|
IF prevnode.hnr < nextnode.hnr THEN
|
|
startnumber := prevnode.hnr;
|
|
endnumber := nextnode.hnr;
|
|
ELSE
|
|
startnumber := nextnode.hnr;
|
|
endnumber := prevnode.hnr;
|
|
sectiongeo := ST_Reverse(sectiongeo);
|
|
END IF;
|
|
|
|
-- Adjust the interpolation, so that only inner housenumbers
|
|
-- are taken into account.
|
|
IF stepmod is null THEN
|
|
newstart := startnumber + NEW.step;
|
|
ELSE
|
|
newstart := startnumber + 1;
|
|
moddiff := newstart % NEW.step - stepmod;
|
|
IF moddiff < 0 THEN
|
|
newstart := newstart + (NEW.step + moddiff);
|
|
ELSE
|
|
newstart := newstart + moddiff;
|
|
END IF;
|
|
END IF;
|
|
newend := newstart + ((endnumber - 1 - newstart) / NEW.step) * NEW.step;
|
|
|
|
-- If newstart and newend are the same, then this returns a point.
|
|
sectiongeo := ST_LineSubstring(sectiongeo,
|
|
(newstart - startnumber)::float / (endnumber - startnumber)::float,
|
|
(newend - startnumber)::float / (endnumber - startnumber)::float);
|
|
startnumber := newstart;
|
|
endnumber := newend;
|
|
|
|
-- determine postcode
|
|
postcode := coalesce(prevnode.postcode, nextnode.postcode, postcode);
|
|
IF postcode is NULL and NEW.parent_place_id > 0 THEN
|
|
SELECT placex.postcode FROM placex
|
|
WHERE place_id = NEW.parent_place_id INTO postcode;
|
|
END IF;
|
|
IF postcode is NULL THEN
|
|
postcode := get_nearest_postcode(NEW.country_code, nextnode.geometry);
|
|
END IF;
|
|
|
|
-- Add the interpolation. If this is the first segment, just modify
|
|
-- the interpolation to be inserted, otherwise add an additional one
|
|
-- (marking it indexed already).
|
|
IF NEW.startnumber IS NULL THEN
|
|
NEW.startnumber := startnumber;
|
|
NEW.endnumber := endnumber;
|
|
NEW.linegeo := sectiongeo;
|
|
NEW.postcode := postcode;
|
|
ELSE
|
|
INSERT INTO location_property_osmline
|
|
(linegeo, partition, osm_id, parent_place_id,
|
|
startnumber, endnumber, step,
|
|
address, postcode, country_code,
|
|
geometry_sector, indexed_status)
|
|
VALUES (sectiongeo, NEW.partition, NEW.osm_id, NEW.parent_place_id,
|
|
startnumber, endnumber, NEW.step,
|
|
NEW.address, postcode,
|
|
NEW.country_code, NEW.geometry_sector, 0);
|
|
END IF;
|
|
END IF;
|
|
|
|
-- early break if we are out of line string,
|
|
-- might happen when a line string loops back on itself
|
|
IF linegeo is null or ST_GeometryType(linegeo) != 'ST_LineString' THEN
|
|
RETURN NEW;
|
|
END IF;
|
|
|
|
prevnode := nextnode;
|
|
END LOOP;
|
|
END IF;
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$
|
|
LANGUAGE plpgsql;
|