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