]> git.openstreetmap.org Git - nominatim.git/blob - lib-sql/functions/interpolation.sql
Merge remote-tracking branch 'upstream/master'
[nominatim.git] / lib-sql / functions / interpolation.sql
1 -- SPDX-License-Identifier: GPL-2.0-only
2 --
3 -- This file is part of Nominatim. (https://nominatim.org)
4 --
5 -- Copyright (C) 2022 by the Nominatim developer community.
6 -- For a full list of authors see the git log.
7
8 -- Functions for address interpolation objects in location_property_osmline.
9
10
11 CREATE OR REPLACE FUNCTION get_interpolation_address(in_address HSTORE, wayid BIGINT)
12 RETURNS HSTORE
13   AS $$
14 DECLARE
15   location RECORD;
16   waynodes BIGINT[];
17 BEGIN
18   IF in_address ? 'street' or in_address ? 'place' THEN
19     RETURN in_address;
20   END IF;
21
22   SELECT nodes INTO waynodes FROM planet_osm_ways WHERE id = wayid;
23   FOR location IN
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
29   LOOP
30     -- mark it as a derived address
31     RETURN location.address || in_address || hstore('_inherited', '');
32   END LOOP;
33
34   RETURN in_address;
35 END;
36 $$
37 LANGUAGE plpgsql STABLE;
38
39
40
41 -- find the parent road of the cut road parts
42 CREATE OR REPLACE FUNCTION get_interpolation_parent(token_info JSONB,
43                                                     partition SMALLINT,
44                                                     centroid GEOMETRY, geom GEOMETRY)
45   RETURNS BIGINT
46   AS $$
47 DECLARE
48   parent_place_id BIGINT;
49   location RECORD;
50 BEGIN
51   parent_place_id := find_parent_for_address(token_info, partition, centroid);
52
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
63               ASC
64         LIMIT 1
65     LOOP
66       parent_place_id := location.place_id;
67     END LOOP;
68   END IF;
69
70   IF parent_place_id is null THEN
71     RETURN 0;
72   END IF;
73
74   RETURN parent_place_id;
75 END;
76 $$
77 LANGUAGE plpgsql STABLE;
78
79
80 CREATE OR REPLACE FUNCTION reinsert_interpolation(way_id BIGINT, addr HSTORE,
81                                                   geom GEOMETRY)
82   RETURNS INT
83   AS $$
84 DECLARE
85   existing BIGINT[];
86 BEGIN
87   -- Get the existing entry from the interpolation table.
88   SELECT array_agg(place_id) INTO existing
89     FROM location_property_osmline WHERE osm_id = way_id;
90
91   IF existing IS NULL or array_length(existing, 1) = 0 THEN
92     INSERT INTO location_property_osmline (osm_id, address, linegeo)
93       VALUES (way_id, addr, geom);
94   ELSE
95     -- Update the interpolation table:
96     --   The first entry gets the original data, all other entries
97     --   are removed and will be recreated on indexing.
98     --   (An interpolation can be split up, if it has more than 2 address nodes)
99     UPDATE location_property_osmline
100       SET address = addr,
101           linegeo = geom,
102           startnumber = null,
103           indexed_status = 1
104       WHERE place_id = existing[1];
105     IF array_length(existing, 1) > 1 THEN
106       DELETE FROM location_property_osmline
107         WHERE place_id = any(existing[2:]);
108     END IF;
109   END IF;
110
111   RETURN 1;
112 END;
113 $$
114 LANGUAGE plpgsql;
115
116
117 CREATE OR REPLACE FUNCTION osmline_insert()
118   RETURNS TRIGGER
119   AS $$
120 BEGIN
121   NEW.place_id := nextval('seq_place');
122   NEW.indexed_date := now();
123
124   IF NEW.indexed_status IS NULL THEN
125       IF NEW.address is NULL OR NOT NEW.address ? 'interpolation'
126          OR NOT (NEW.address->'interpolation' in ('odd', 'even', 'all')
127                  or NEW.address->'interpolation' similar to '[1-9]')
128       THEN
129           -- alphabetic interpolation is not supported
130           RETURN NULL;
131       END IF;
132
133       NEW.indexed_status := 1; --STATUS_NEW
134       NEW.country_code := lower(get_country_code(NEW.linegeo));
135
136       NEW.partition := get_partition(NEW.country_code);
137       NEW.geometry_sector := geometry_sector(NEW.partition, NEW.linegeo);
138   END IF;
139
140   RETURN NEW;
141 END;
142 $$
143 LANGUAGE plpgsql;
144
145
146 CREATE OR REPLACE FUNCTION osmline_update()
147   RETURNS TRIGGER
148   AS $$
149 DECLARE
150   waynodes BIGINT[];
151   prevnode RECORD;
152   nextnode RECORD;
153   startnumber INTEGER;
154   endnumber INTEGER;
155   newstart INTEGER;
156   newend INTEGER;
157   moddiff SMALLINT;
158   linegeo GEOMETRY;
159   splitline GEOMETRY;
160   sectiongeo GEOMETRY;
161   postcode TEXT;
162   stepmod SMALLINT;
163 BEGIN
164   -- deferred delete
165   IF OLD.indexed_status = 100 THEN
166     delete from location_property_osmline where place_id = OLD.place_id;
167     RETURN NULL;
168   END IF;
169
170   IF NEW.indexed_status != 0 OR OLD.indexed_status = 0 THEN
171     RETURN NEW;
172   END IF;
173
174   NEW.parent_place_id := get_interpolation_parent(NEW.token_info, NEW.partition,
175                                                  ST_PointOnSurface(NEW.linegeo),
176                                                  NEW.linegeo);
177
178   NEW.token_info := token_strip_info(NEW.token_info);
179   IF NEW.address ? '_inherited' THEN
180     NEW.address := hstore('interpolation', NEW.address->'interpolation');
181   END IF;
182
183   -- If the line was newly inserted, split the line as necessary.
184   IF OLD.indexed_status = 1 THEN
185     IF NEW.address->'interpolation' in ('odd', 'even') THEN
186       NEW.step := 2;
187       stepmod := CASE WHEN NEW.address->'interpolation' = 'odd' THEN 1 ELSE 0 END;
188     ELSE
189       NEW.step := CASE WHEN NEW.address->'interpolation' = 'all'
190                        THEN 1
191                        ELSE (NEW.address->'interpolation')::SMALLINT END;
192       stepmod := NULL;
193     END IF;
194
195     SELECT nodes INTO waynodes
196       FROM planet_osm_ways WHERE id = NEW.osm_id;
197
198     IF array_upper(waynodes, 1) IS NULL THEN
199       RETURN NEW;
200     END IF;
201
202     linegeo := null;
203     SELECT null::integer as hnr INTO prevnode;
204
205     -- Go through all nodes on the interpolation line that have a housenumber.
206     FOR nextnode IN
207       SELECT DISTINCT ON (nodeidpos)
208           osm_id, address, geometry,
209           -- Take the postcode from the node only if it has a housenumber itself.
210           -- Note that there is a corner-case where the node has a wrongly
211           -- formatted postcode and therefore 'postcode' contains a derived
212           -- variant.
213           CASE WHEN address ? 'postcode' THEN placex.postcode ELSE NULL::text END as postcode,
214           substring(address->'housenumber','[0-9]+')::integer as hnr
215         FROM placex, generate_series(1, array_upper(waynodes, 1)) nodeidpos
216         WHERE osm_type = 'N' and osm_id = waynodes[nodeidpos]::BIGINT
217               and address is not NULL and address ? 'housenumber'
218         ORDER BY nodeidpos
219     LOOP
220       {% if debug %}RAISE WARNING 'processing point % (%)', nextnode.hnr, ST_AsText(nextnode.geometry);{% endif %}
221       IF linegeo is null THEN
222         linegeo := NEW.linegeo;
223       ELSE
224         splitline := ST_Split(ST_Snap(linegeo, nextnode.geometry, 0.0005), nextnode.geometry);
225         sectiongeo := ST_GeometryN(splitline, 1);
226         linegeo := ST_GeometryN(splitline, 2);
227       END IF;
228
229       IF prevnode.hnr is not null
230          -- Check if there are housenumbers to interpolate between the
231          -- regularly mapped housenumbers.
232          -- (Conveniently also fails if one of the house numbers is not a number.)
233          and abs(prevnode.hnr - nextnode.hnr) > NEW.step
234       THEN
235         IF prevnode.hnr < nextnode.hnr THEN
236           startnumber := prevnode.hnr;
237           endnumber := nextnode.hnr;
238         ELSE
239           startnumber := nextnode.hnr;
240           endnumber := prevnode.hnr;
241           sectiongeo := ST_Reverse(sectiongeo);
242         END IF;
243
244         -- Adjust the interpolation, so that only inner housenumbers
245         -- are taken into account.
246         IF stepmod is null THEN
247           newstart := startnumber + NEW.step;
248         ELSE
249           newstart := startnumber + 1;
250           moddiff := newstart % NEW.step - stepmod;
251           IF moddiff < 0 THEN
252             newstart := newstart + (NEW.step + moddiff);
253           ELSE
254             newstart := newstart + moddiff;
255           END IF;
256         END IF;
257         newend := newstart + ((endnumber - 1 - newstart) / NEW.step) * NEW.step;
258
259         -- If newstart and newend are the same, then this returns a point.
260         sectiongeo := ST_LineSubstring(sectiongeo,
261                               (newstart - startnumber)::float / (endnumber - startnumber)::float,
262                               (newend - startnumber)::float / (endnumber - startnumber)::float);
263         startnumber := newstart;
264         endnumber := newend;
265
266         -- determine postcode
267         postcode := coalesce(prevnode.postcode, nextnode.postcode, postcode);
268         IF postcode is NULL and NEW.parent_place_id > 0 THEN
269             SELECT placex.postcode FROM placex
270               WHERE place_id = NEW.parent_place_id INTO postcode;
271         END IF;
272         IF postcode is NULL THEN
273             postcode := get_nearest_postcode(NEW.country_code, nextnode.geometry);
274         END IF;
275
276         -- Add the interpolation. If this is the first segment, just modify
277         -- the interpolation to be inserted, otherwise add an additional one
278         -- (marking it indexed already).
279         IF NEW.startnumber IS NULL THEN
280             NEW.startnumber := startnumber;
281             NEW.endnumber := endnumber;
282             NEW.linegeo := sectiongeo;
283             NEW.postcode := postcode;
284         ELSE
285           INSERT INTO location_property_osmline
286                  (linegeo, partition, osm_id, parent_place_id,
287                   startnumber, endnumber, step,
288                   address, postcode, country_code,
289                   geometry_sector, indexed_status)
290           VALUES (sectiongeo, NEW.partition, NEW.osm_id, NEW.parent_place_id,
291                   startnumber, endnumber, NEW.step,
292                   NEW.address, postcode,
293                   NEW.country_code, NEW.geometry_sector, 0);
294         END IF;
295
296         -- early break if we are out of line string,
297         -- might happen when a line string loops back on itself
298         IF ST_GeometryType(linegeo) != 'ST_LineString' THEN
299             RETURN NEW;
300         END IF;
301       END IF;
302
303       prevnode := nextnode;
304     END LOOP;
305   END IF;
306
307   RETURN NEW;
308 END;
309 $$
310 LANGUAGE plpgsql;