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;
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;
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 := sectiongeo;
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 (sectiongeo, NEW.partition, NEW.osm_id, NEW.parent_place_id,
321 startnumber, endnumber, NEW.step,
322 NEW.address, postcode,
323 NEW.country_code, NEW.geometry_sector, 0);
327 -- early break if we are out of line string,
328 -- might happen when a line string loops back on itself
329 IF linegeo is null or ST_GeometryType(linegeo) != 'ST_LineString' THEN
333 prevnode := nextnode;