adapt interpolation handling to use separate place_interpolation table

This commit is contained in:
Sarah Hoffmann
2026-02-16 16:25:19 +01:00
parent a115eeeb40
commit c2d6821f2f
7 changed files with 296 additions and 269 deletions

View File

@@ -2,11 +2,106 @@
-- --
-- This file is part of Nominatim. (https://nominatim.org) -- This file is part of Nominatim. (https://nominatim.org)
-- --
-- Copyright (C) 2022 by the Nominatim developer community. -- Copyright (C) 2026 by the Nominatim developer community.
-- For a full list of authors see the git log. -- For a full list of authors see the git log.
-- Functions for address interpolation objects in location_property_osmline. -- Functions for address interpolation objects in location_property_osmline.
CREATE OR REPLACE FUNCTION place_interpolation_insert()
RETURNS TRIGGER
AS $$
DECLARE
existing RECORD;
existingplacex BIGINT[];
BEGIN
-- Remove the place from the list of places to be deleted
DELETE FROM place_interpolation_to_be_deleted pdel
WHERE pdel.osm_type = NEW.osm_type and pdel.osm_id = NEW.osm_id;
SELECT * INTO existing FROM place_interpolation p
WHERE p.osm_type = NEW.osm_type AND p.osm_id = NEW.osm_id;
IF NOT (NEW.type in ('odd', 'even', 'all') OR NEW.type similar to '[1-9]') THEN
-- the new interpolation is illegal, simply remove existing entries
DELETE FROM location_property_osmline o
WHERE o.osm_type = NEW.osm_type AND o.osm_id = NEW.osm_id;
ELSE
-- Get the existing entry from the interpolation table.
SELECT array_agg(place_id) INTO existingplacex
FROM location_property_osmline o
WHERE o.osm_type = NEW.osm_type AND o.osm_id = NEW.osm_id;
IF array_length(existingplacex, 1) is NULL THEN
INSERT INTO location_property_osmline (osm_type, osm_id, type, address, linegeo)
VALUES (NEW.osm_type, NEW.osm_id, NEW.type, NEW.address, NEW.geometry);
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 unconditionally here as the changes might be coming from the
-- nodes on the interpolation.
UPDATE location_property_osmline
SET type = NEW.type,
address = NEW.address,
linegeo = NEW.geometry,
startnumber = null,
indexed_status = 1
WHERE place_id = existingplacex[1];
IF array_length(existingplacex, 1) > 1 THEN
DELETE FROM location_property_osmline
WHERE place_id = any(existingplacex[2:]);
END IF;
END IF;
-- need to invalidate nodes because they might copy address info
IF NEW.address is not NULL
AND (existing.osm_type is NULL
OR coalesce(existing.address, ''::hstore) != NEW.address)
THEN
UPDATE placex SET indexed_status = 2
WHERE osm_type = 'N' AND osm_id = ANY(NEW.nodes)
AND indexed_status = 0;
END IF;
END IF;
-- finally update/insert place_interpolation itself
IF existing.osm_type is not NULL THEN
IF existing.type != NEW.type
OR coalesce(existing.address, ''::hstore) != coalesce(NEW.address, ''::hstore)
OR existing.geometry::text != NEW.geometry::text
THEN
UPDATE place_interpolation p
SET type = NEW.type,
address = NEW.address,
geometry = NEW.geometry
WHERE p.osm_type = NEW.osm_type AND p.osm_id = NEW.osm_id;
END IF;
RETURN NULL;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION place_interpolation_delete()
RETURNS TRIGGER
AS $$
DECLARE
deferred BOOLEAN;
BEGIN
{% if debug %}RAISE WARNING 'Delete for % % %/%', OLD.osm_type, OLD.osm_id, OLD.class, OLD.type;{% endif %}
INSERT INTO place_interpolation_to_be_deleted (osm_type, osm_id)
VALUES(OLD.osm_type, OLD.osm_id);
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION get_interpolation_address(in_address HSTORE, wayid BIGINT) CREATE OR REPLACE FUNCTION get_interpolation_address(in_address HSTORE, wayid BIGINT)
RETURNS HSTORE RETURNS HSTORE
@@ -28,7 +123,7 @@ BEGIN
and indexed_status < 100 and indexed_status < 100
LOOP LOOP
-- mark it as a derived address -- mark it as a derived address
RETURN location.address || in_address || hstore('_inherited', ''); RETURN location.address || coalesce(in_address, '{}'::hstore) || hstore('_inherited', '');
END LOOP; END LOOP;
RETURN in_address; RETURN in_address;
@@ -73,51 +168,6 @@ $$
LANGUAGE plpgsql STABLE PARALLEL SAFE; LANGUAGE plpgsql STABLE PARALLEL SAFE;
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() CREATE OR REPLACE FUNCTION osmline_insert()
RETURNS TRIGGER RETURNS TRIGGER
AS $$ AS $$
@@ -128,16 +178,28 @@ BEGIN
NEW.indexed_date := now(); NEW.indexed_date := now();
IF NEW.indexed_status IS NULL THEN IF NEW.indexed_status IS NULL THEN
IF NEW.address is NULL OR NOT NEW.address ? 'interpolation' IF NOT(NEW.type in ('odd', 'even', 'all') OR NEW.type similar to '[1-9]') THEN
OR NOT (NEW.address->'interpolation' in ('odd', 'even', 'all')
or NEW.address->'interpolation' similar to '[1-9]')
THEN
-- alphabetic interpolation is not supported -- alphabetic interpolation is not supported
RETURN NULL; RETURN NULL;
END IF; END IF;
NEW.indexed_status := 1; --STATUS_NEW IF NEW.address is not NULL AND NEW.address ? 'housenumber' THEN
IF NEW.address->'housenumber' not similar to '[0-9]+-[0-9]+' THEN
-- housenumber needs to look like an interpolation
RETURN NULL;
END IF;
centroid := ST_Centroid(NEW.linegeo);
-- interpolation of a housenumber, make sure we have a line to interpolate on
NEW.geometry := ST_MakeLine(centroid, ST_Project(centroid, 0.0000001, 0));
ELSE
centroid := get_center_point(NEW.linegeo); centroid := get_center_point(NEW.linegeo);
IF NEW.osm_type != 'W' THEN
RETURN NULL;
END IF;
END IF;
NEW.indexed_status := 1; --STATUS_NEW
NEW.country_code := lower(get_country_code(centroid)); NEW.country_code := lower(get_country_code(centroid));
NEW.partition := get_partition(NEW.country_code); NEW.partition := get_partition(NEW.country_code);
@@ -167,6 +229,7 @@ DECLARE
sectiongeo GEOMETRY; sectiongeo GEOMETRY;
postcode TEXT; postcode TEXT;
stepmod SMALLINT; stepmod SMALLINT;
splitstring TEXT[];
BEGIN BEGIN
-- deferred delete -- deferred delete
IF OLD.indexed_status = 100 THEN IF OLD.indexed_status = 100 THEN
@@ -182,30 +245,45 @@ BEGIN
get_center_point(NEW.linegeo), get_center_point(NEW.linegeo),
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); NEW.token_info := token_strip_info(NEW.token_info);
IF NEW.address ? '_inherited' THEN IF NEW.address ? '_inherited' THEN
NEW.address := hstore('interpolation', NEW.address->'interpolation'); NEW.address := NULL;
END IF; END IF;
-- If the line was newly inserted, split the line as necessary. -- If the line was newly inserted, split the line as necessary.
IF OLD.indexed_status = 1 THEN IF NEW.parent_place_id is not NULL AND NEW.startnumber is NULL THEN
IF NEW.address->'interpolation' in ('odd', 'even') THEN IF NEW.type in ('odd', 'even') THEN
NEW.step := 2; NEW.step := 2;
stepmod := CASE WHEN NEW.address->'interpolation' = 'odd' THEN 1 ELSE 0 END; stepmod := CASE WHEN NEW.type = 'odd' THEN 1 ELSE 0 END;
ELSE ELSE
NEW.step := CASE WHEN NEW.address->'interpolation' = 'all' NEW.step := CASE WHEN NEW.type = 'all' THEN 1 ELSE (NEW.type)::SMALLINT END;
THEN 1
ELSE (NEW.address->'interpolation')::SMALLINT END;
stepmod := NULL; stepmod := NULL;
END IF; END IF;
IF NEW.address is not NULL AND NEW.address ? 'housenumer' THEN
-- interpolation interval is in housenumber
splitstring := string_to_array(NEW.address->'housenumber', '-');
NEW.startnumber := (splitstring[1])::INTEGER;
NEW.endnumber := (splitstring[2])::INTEGER;
IF stepmod is not NULL THEN
IF NEW.startnumber % NEW.step != stepmod THEN
NEW.startnumber := NEW.startnumber + 1;
END IF;
IF NEW.endnumber % NEW.step != stepmod THEN
NEW.endnumber := NEW.endnumber - 1;
END IF;
ELSE
NEW.endnumber := NEW.startnumber + ((NEW.endnumber - NEW.startnumber) / NEW.step) * NEW.step;
END IF;
IF NEW.startnumber = NEW.endnumber THEN
NEW.geometry := ST_PointN(NEW.geometry, 1);
ELSEIF NEW.startnumber > NEW.endnumber THEN
NEW.startnumber := NULL;
END IF;
ELSE
-- classic interpolation way
SELECT nodes INTO waynodes SELECT nodes INTO waynodes
FROM planet_osm_ways WHERE id = NEW.osm_id; FROM planet_osm_ways WHERE id = NEW.osm_id;
@@ -313,13 +391,13 @@ BEGIN
NEW.postcode := postcode; NEW.postcode := postcode;
ELSE ELSE
INSERT INTO location_property_osmline INSERT INTO location_property_osmline
(linegeo, partition, osm_id, parent_place_id, (linegeo, partition, osm_type, osm_id, parent_place_id,
startnumber, endnumber, step, startnumber, endnumber, step, type,
address, postcode, country_code, address, postcode, country_code,
geometry_sector, indexed_status) geometry_sector, indexed_status)
VALUES (ST_ReducePrecision(sectiongeo, 0.0000001), VALUES (ST_ReducePrecision(sectiongeo, 0.0000001),
NEW.partition, NEW.osm_id, NEW.parent_place_id, NEW.partition, NEW.osm_type, NEW.osm_id, NEW.parent_place_id,
startnumber, endnumber, NEW.step, startnumber, endnumber, NEW.step, NEW.type,
NEW.address, postcode, NEW.address, postcode,
NEW.country_code, NEW.geometry_sector, 0); NEW.country_code, NEW.geometry_sector, 0);
END IF; END IF;
@@ -334,6 +412,7 @@ BEGIN
prevnode := nextnode; prevnode := nextnode;
END LOOP; END LOOP;
END IF; END IF;
END IF;
RETURN NEW; RETURN NEW;
END; END;

View File

@@ -14,7 +14,6 @@ DECLARE
existing RECORD; existing RECORD;
existingplacex RECORD; existingplacex RECORD;
existingline BIGINT[]; existingline BIGINT[];
interpol RECORD;
BEGIN BEGIN
{% if debug %} {% if debug %}
RAISE WARNING 'place_insert: % % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type,st_area(NEW.geometry); RAISE WARNING 'place_insert: % % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type,st_area(NEW.geometry);
@@ -55,41 +54,6 @@ BEGIN
DELETE from import_polygon_error where osm_type = NEW.osm_type and osm_id = NEW.osm_id; DELETE from import_polygon_error where osm_type = NEW.osm_type and osm_id = NEW.osm_id;
DELETE from import_polygon_delete where osm_type = NEW.osm_type and osm_id = NEW.osm_id; DELETE from import_polygon_delete where osm_type = NEW.osm_type and osm_id = NEW.osm_id;
-- ---- Interpolation Lines
IF NEW.class='place' and NEW.type='houses'
and NEW.osm_type='W' and ST_GeometryType(NEW.geometry) = 'ST_LineString'
THEN
PERFORM reinsert_interpolation(NEW.osm_id, NEW.address, NEW.geometry);
-- Now invalidate all address nodes on the line.
-- They get their parent from the interpolation.
UPDATE placex p SET indexed_status = 2
FROM planet_osm_ways w
WHERE w.id = NEW.osm_id and p.osm_type = 'N' and p.osm_id = any(w.nodes)
and indexed_status = 0;
-- If there is already an entry in place, just update that, if necessary.
IF existing.osm_type is not null THEN
IF coalesce(existing.address, ''::hstore) != coalesce(NEW.address, ''::hstore)
OR existing.geometry::text != NEW.geometry::text
THEN
UPDATE place
SET name = NEW.name,
address = NEW.address,
extratags = NEW.extratags,
admin_level = NEW.admin_level,
geometry = NEW.geometry
WHERE osm_type = NEW.osm_type and osm_id = NEW.osm_id
and class = NEW.class and type = NEW.type;
END IF;
RETURN NULL;
END IF;
RETURN NEW;
END IF;
-- ---- All other place types. -- ---- All other place types.
-- When an area is changed from large to small: log and discard change -- When an area is changed from large to small: log and discard change
@@ -109,29 +73,6 @@ BEGIN
RETURN null; RETURN null;
END IF; END IF;
-- If an address node is part of a interpolation line and changes or is
-- newly inserted (happens when the node already existed but now gets address
-- information), then mark the interpolation line for reparenting.
-- (Already here, because interpolation lines are reindexed before nodes,
-- so in the second call it would be too late.)
IF NEW.osm_type='N'
and coalesce(existing.address, ''::hstore) != coalesce(NEW.address, ''::hstore)
THEN
FOR interpol IN
SELECT DISTINCT osm_id, address, geometry FROM place, planet_osm_ways w
WHERE NEW.geometry && place.geometry
and place.osm_type = 'W'
and place.address ? 'interpolation'
and exists (SELECT * FROM location_property_osmline
WHERE osm_id = place.osm_id
and indexed_status in (0, 2))
and w.id = place.osm_id and NEW.osm_id = any (w.nodes)
LOOP
PERFORM reinsert_interpolation(interpol.osm_id, interpol.address,
interpol.geometry);
END LOOP;
END IF;
-- Get the existing placex entry. -- Get the existing placex entry.
SELECT * INTO existingplacex SELECT * INTO existingplacex
FROM placex FROM placex

View File

@@ -53,12 +53,10 @@ BEGIN
-- See if we can inherit additional address tags from an interpolation. -- See if we can inherit additional address tags from an interpolation.
-- These will become permanent. -- These will become permanent.
FOR location IN FOR location IN
SELECT (address - 'interpolation'::text - 'housenumber'::text) as address SELECT address as address
FROM place, planet_osm_ways w FROM place_interpolation
WHERE place.osm_type = 'W' and place.address ? 'interpolation' WHERE p.osm_id = any(place_interpolation.nodes)
and place.geometry && p.geometry AND address is not NULL AND not address ? 'housenumber'
and place.osm_id = w.id
and p.osm_id = any(w.nodes)
LOOP LOOP
result.address := location.address || result.address; result.address := location.address || result.address;
END LOOP; END LOOP;

View File

@@ -624,17 +624,21 @@ BEGIN
and placex.type = place_to_be_deleted.type and placex.type = place_to_be_deleted.type
and not deferred; and not deferred;
-- Mark for delete in interpolations
UPDATE location_property_osmline SET indexed_status = 100 FROM place_to_be_deleted
WHERE place_to_be_deleted.osm_type = 'W'
and place_to_be_deleted.class = 'place'
and place_to_be_deleted.type = 'houses'
and location_property_osmline.osm_id = place_to_be_deleted.osm_id
and not deferred;
-- Clear todo list. -- Clear todo list.
TRUNCATE TABLE place_to_be_deleted; TRUNCATE TABLE place_to_be_deleted;
-- delete from place_interpolation table
ALTER TABLE place_interpolation DISABLE TRIGGER place_interpolation_before_delete;
DELETE FROM place_interpolation p USING place_interpolation_to_be_deleted d
WHERE p.osm_type = d.osm_type AND p.osm_id = d.osm_id;
ALTER TABLE place_interpolation ENABLE TRIGGER place_interpolation_before_delete;
UPDATE location_property_osmline o SET indexed_status = 100
FROM place_interpolation_to_be_deleted d
WHERE o.osm_type = d.osm_Type AND o.osm_id = d.osm_id;
TRUNCATE TABLE place_interpolation_to_be_deleted;
RETURN NULL; RETURN NULL;
END; END;
$$ LANGUAGE plpgsql; $$ LANGUAGE plpgsql;

View File

@@ -2,7 +2,7 @@
-- --
-- This file is part of Nominatim. (https://nominatim.org) -- This file is part of Nominatim. (https://nominatim.org)
-- --
-- Copyright (C) 2025 by the Nominatim developer community. -- Copyright (C) 2026 by the Nominatim developer community.
-- For a full list of authors see the git log. -- For a full list of authors see the git log.
-- Indices used only during search and update. -- Indices used only during search and update.
@@ -67,11 +67,16 @@ CREATE INDEX IF NOT EXISTS idx_osmline_parent_osm_id
--- ---
-- Table needed for running updates with osm2pgsql on place. -- Table needed for running updates with osm2pgsql on place.
CREATE TABLE IF NOT EXISTS place_to_be_deleted ( CREATE TABLE IF NOT EXISTS place_to_be_deleted (
osm_type CHAR(1), osm_type CHAR(1) NOT NULL,
osm_id BIGINT, osm_id BIGINT NOT NULL,
class TEXT, class TEXT NOT NULL,
type TEXT, type TEXT NOT NULL,
deferred BOOLEAN deferred BOOLEAN NOT NULL
);
CREATE TABLE IF NOT EXISTS place_interpolation_to_be_deleted (
osm_type CHAR(1) NOT NULL,
osm_id BIGINT NOT NULL
); );
--- ---
CREATE INDEX IF NOT EXISTS idx_location_postcodes_parent_place_id CREATE INDEX IF NOT EXISTS idx_location_postcodes_parent_place_id

View File

@@ -2,7 +2,7 @@
-- --
-- This file is part of Nominatim. (https://nominatim.org) -- This file is part of Nominatim. (https://nominatim.org)
-- --
-- Copyright (C) 2025 by the Nominatim developer community. -- Copyright (C) 2026 by the Nominatim developer community.
-- For a full list of authors see the git log. -- For a full list of authors see the git log.
-- insert creates the location tables, creates location indexes if indexed == true -- insert creates the location tables, creates location indexes if indexed == true
@@ -31,3 +31,8 @@ CREATE TRIGGER location_postcodes_before_delete BEFORE DELETE ON location_postco
FOR EACH ROW EXECUTE PROCEDURE postcodes_delete(); FOR EACH ROW EXECUTE PROCEDURE postcodes_delete();
CREATE TRIGGER location_postcodes_before_insert BEFORE INSERT ON location_postcodes CREATE TRIGGER location_postcodes_before_insert BEFORE INSERT ON location_postcodes
FOR EACH ROW EXECUTE PROCEDURE postcodes_insert(); FOR EACH ROW EXECUTE PROCEDURE postcodes_insert();
CREATE TRIGGER place_interpolation_before_insert BEFORE INSERT ON place_interpolation
FOR EACH ROW EXECUTE PROCEDURE place_interpolation_insert();
CREATE TRIGGER place_interpolation_before_delete BEFORE DELETE ON place_interpolation
FOR EACH ROW EXECUTE PROCEDURE place_interpolation_delete();

View File

@@ -32,8 +32,3 @@ CREATE INDEX planet_osm_rels_relation_members_idx ON planet_osm_rels USING gin(p
WITH (fastupdate=off) WITH (fastupdate=off)
{{db.tablespace.address_index}}; {{db.tablespace.address_index}};
{% endif %} {% endif %}
-- Needed for lookups if a node is part of an interpolation.
CREATE INDEX IF NOT EXISTS idx_place_interpolations
ON place USING gist(geometry) {{db.tablespace.address_index}}
WHERE osm_type = 'W' and address ? 'interpolation';