]> git.openstreetmap.org Git - nominatim.git/blob - lib-sql/functions/interpolation.sql
do not assign postcodes to long linear features
[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   IF addr is NULL OR NOT addr ? 'interpolation'
88          OR NOT (addr->'interpolation' in ('odd', 'even', 'all')
89                  or addr->'interpolation' similar to '[1-9]')
90   THEN
91     -- the new interpolation is illegal, simply remove existing entries
92     DELETE FROM location_property_osmline WHERE osm_id = way_id;
93   ELSE
94     -- Get the existing entry from the interpolation table.
95     SELECT array_agg(place_id) INTO existing
96       FROM location_property_osmline WHERE osm_id = way_id;
97
98     IF existing IS NULL or array_length(existing, 1) = 0 THEN
99       INSERT INTO location_property_osmline (osm_id, address, linegeo)
100         VALUES (way_id, addr, geom);
101     ELSE
102       -- Update the interpolation table:
103       --   The first entry gets the original data, all other entries
104       --   are removed and will be recreated on indexing.
105       --   (An interpolation can be split up, if it has more than 2 address nodes)
106       UPDATE location_property_osmline
107         SET address = addr,
108             linegeo = geom,
109             startnumber = null,
110             indexed_status = 1
111         WHERE place_id = existing[1];
112       IF array_length(existing, 1) > 1 THEN
113         DELETE FROM location_property_osmline
114           WHERE place_id = any(existing[2:]);
115       END IF;
116     END IF;
117   END IF;
118
119   RETURN 1;
120 END;
121 $$
122 LANGUAGE plpgsql;
123
124
125 CREATE OR REPLACE FUNCTION osmline_insert()
126   RETURNS TRIGGER
127   AS $$
128 BEGIN
129   NEW.place_id := nextval('seq_place');
130   NEW.indexed_date := now();
131
132   IF NEW.indexed_status IS NULL THEN
133       IF NEW.address is NULL OR NOT NEW.address ? 'interpolation'
134          OR NOT (NEW.address->'interpolation' in ('odd', 'even', 'all')
135                  or NEW.address->'interpolation' similar to '[1-9]')
136       THEN
137           -- alphabetic interpolation is not supported
138           RETURN NULL;
139       END IF;
140
141       NEW.indexed_status := 1; --STATUS_NEW
142       NEW.country_code := lower(get_country_code(NEW.linegeo));
143
144       NEW.partition := get_partition(NEW.country_code);
145       NEW.geometry_sector := geometry_sector(NEW.partition, NEW.linegeo);
146   END IF;
147
148   RETURN NEW;
149 END;
150 $$
151 LANGUAGE plpgsql;
152
153
154 CREATE OR REPLACE FUNCTION osmline_update()
155   RETURNS TRIGGER
156   AS $$
157 DECLARE
158   waynodes BIGINT[];
159   prevnode RECORD;
160   nextnode RECORD;
161   startnumber INTEGER;
162   endnumber INTEGER;
163   newstart INTEGER;
164   newend INTEGER;
165   moddiff SMALLINT;
166   linegeo GEOMETRY;
167   splitline GEOMETRY;
168   sectiongeo GEOMETRY;
169   postcode TEXT;
170   stepmod SMALLINT;
171 BEGIN
172   -- deferred delete
173   IF OLD.indexed_status = 100 THEN
174     delete from location_property_osmline where place_id = OLD.place_id;
175     RETURN NULL;
176   END IF;
177
178   IF NEW.indexed_status != 0 OR OLD.indexed_status = 0 THEN
179     RETURN NEW;
180   END IF;
181
182   NEW.parent_place_id := get_interpolation_parent(NEW.token_info, NEW.partition,
183                                                  ST_PointOnSurface(NEW.linegeo),
184                                                  NEW.linegeo);
185
186   NEW.token_info := token_strip_info(NEW.token_info);
187   IF NEW.address ? '_inherited' THEN
188     NEW.address := hstore('interpolation', NEW.address->'interpolation');
189   END IF;
190
191   -- If the line was newly inserted, split the line as necessary.
192   IF OLD.indexed_status = 1 THEN
193     IF NEW.address->'interpolation' in ('odd', 'even') THEN
194       NEW.step := 2;
195       stepmod := CASE WHEN NEW.address->'interpolation' = 'odd' THEN 1 ELSE 0 END;
196     ELSE
197       NEW.step := CASE WHEN NEW.address->'interpolation' = 'all'
198                        THEN 1
199                        ELSE (NEW.address->'interpolation')::SMALLINT END;
200       stepmod := NULL;
201     END IF;
202
203     SELECT nodes INTO waynodes
204       FROM planet_osm_ways WHERE id = NEW.osm_id;
205
206     IF array_upper(waynodes, 1) IS NULL THEN
207       RETURN NEW;
208     END IF;
209
210     linegeo := null;
211     SELECT null::integer as hnr INTO prevnode;
212
213     -- Go through all nodes on the interpolation line that have a housenumber.
214     FOR nextnode IN
215       SELECT DISTINCT ON (nodeidpos)
216           osm_id, address, geometry,
217           -- Take the postcode from the node only if it has a housenumber itself.
218           -- Note that there is a corner-case where the node has a wrongly
219           -- formatted postcode and therefore 'postcode' contains a derived
220           -- variant.
221           CASE WHEN address ? 'postcode' THEN placex.postcode ELSE NULL::text END as postcode,
222           substring(address->'housenumber','[0-9]+')::integer as hnr
223         FROM placex, generate_series(1, array_upper(waynodes, 1)) nodeidpos
224         WHERE osm_type = 'N' and osm_id = waynodes[nodeidpos]::BIGINT
225               and address is not NULL and address ? 'housenumber'
226         ORDER BY nodeidpos
227     LOOP
228       {% if debug %}RAISE WARNING 'processing point % (%)', nextnode.hnr, ST_AsText(nextnode.geometry);{% endif %}
229       IF linegeo is null THEN
230         linegeo := NEW.linegeo;
231       ELSE
232         splitline := ST_Split(ST_Snap(linegeo, nextnode.geometry, 0.0005), nextnode.geometry);
233         sectiongeo := ST_GeometryN(splitline, 1);
234         linegeo := ST_GeometryN(splitline, 2);
235       END IF;
236
237       IF prevnode.hnr is not null
238          -- Check if there are housenumbers to interpolate between the
239          -- regularly mapped housenumbers.
240          -- (Conveniently also fails if one of the house numbers is not a number.)
241          and abs(prevnode.hnr - nextnode.hnr) > NEW.step
242       THEN
243         IF prevnode.hnr < nextnode.hnr THEN
244           startnumber := prevnode.hnr;
245           endnumber := nextnode.hnr;
246         ELSE
247           startnumber := nextnode.hnr;
248           endnumber := prevnode.hnr;
249           sectiongeo := ST_Reverse(sectiongeo);
250         END IF;
251
252         -- Adjust the interpolation, so that only inner housenumbers
253         -- are taken into account.
254         IF stepmod is null THEN
255           newstart := startnumber + NEW.step;
256         ELSE
257           newstart := startnumber + 1;
258           moddiff := newstart % NEW.step - stepmod;
259           IF moddiff < 0 THEN
260             newstart := newstart + (NEW.step + moddiff);
261           ELSE
262             newstart := newstart + moddiff;
263           END IF;
264         END IF;
265         newend := newstart + ((endnumber - 1 - newstart) / NEW.step) * NEW.step;
266
267         -- If newstart and newend are the same, then this returns a point.
268         sectiongeo := ST_LineSubstring(sectiongeo,
269                               (newstart - startnumber)::float / (endnumber - startnumber)::float,
270                               (newend - startnumber)::float / (endnumber - startnumber)::float);
271         startnumber := newstart;
272         endnumber := newend;
273
274         -- determine postcode
275         postcode := coalesce(prevnode.postcode, nextnode.postcode, postcode);
276         IF postcode is NULL and NEW.parent_place_id > 0 THEN
277             SELECT placex.postcode FROM placex
278               WHERE place_id = NEW.parent_place_id INTO postcode;
279         END IF;
280         IF postcode is NULL THEN
281             postcode := get_nearest_postcode(NEW.country_code, nextnode.geometry);
282         END IF;
283
284         -- Add the interpolation. If this is the first segment, just modify
285         -- the interpolation to be inserted, otherwise add an additional one
286         -- (marking it indexed already).
287         IF NEW.startnumber IS NULL THEN
288             NEW.startnumber := startnumber;
289             NEW.endnumber := endnumber;
290             NEW.linegeo := sectiongeo;
291             NEW.postcode := postcode;
292         ELSE
293           INSERT INTO location_property_osmline
294                  (linegeo, partition, osm_id, parent_place_id,
295                   startnumber, endnumber, step,
296                   address, postcode, country_code,
297                   geometry_sector, indexed_status)
298           VALUES (sectiongeo, NEW.partition, NEW.osm_id, NEW.parent_place_id,
299                   startnumber, endnumber, NEW.step,
300                   NEW.address, postcode,
301                   NEW.country_code, NEW.geometry_sector, 0);
302         END IF;
303
304         -- early break if we are out of line string,
305         -- might happen when a line string loops back on itself
306         IF ST_GeometryType(linegeo) != 'ST_LineString' THEN
307             RETURN NEW;
308         END IF;
309       END IF;
310
311       prevnode := nextnode;
312     END LOOP;
313   END IF;
314
315   RETURN NEW;
316 END;
317 $$
318 LANGUAGE plpgsql;