]> 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 -- Splits the line at the given point and returns the two parts
11 -- in a multilinestring.
12 CREATE OR REPLACE FUNCTION split_line_on_node(line GEOMETRY, point GEOMETRY)
13 RETURNS GEOMETRY
14   AS $$
15 BEGIN
16   RETURN ST_Split(ST_Snap(line, point, 0.0005), point);
17 END;
18 $$
19 LANGUAGE plpgsql IMMUTABLE;
20
21
22 CREATE OR REPLACE FUNCTION get_interpolation_address(in_address HSTORE, wayid BIGINT)
23 RETURNS HSTORE
24   AS $$
25 DECLARE
26   location RECORD;
27   waynodes BIGINT[];
28 BEGIN
29   IF akeys(in_address) != ARRAY['interpolation'] THEN
30     RETURN in_address;
31   END IF;
32
33   SELECT nodes INTO waynodes FROM planet_osm_ways WHERE id = wayid;
34   FOR location IN
35     SELECT placex.address, placex.osm_id FROM placex
36      WHERE osm_type = 'N' and osm_id = ANY(waynodes)
37            and placex.address is not null
38            and (placex.address ? 'street' or placex.address ? 'place')
39            and indexed_status < 100
40   LOOP
41     -- mark it as a derived address
42     RETURN location.address || in_address || hstore('_inherited', '');
43   END LOOP;
44
45   RETURN in_address;
46 END;
47 $$
48 LANGUAGE plpgsql STABLE;
49
50
51
52 -- find the parent road of the cut road parts
53 CREATE OR REPLACE FUNCTION get_interpolation_parent(token_info JSONB,
54                                                     partition SMALLINT,
55                                                     centroid GEOMETRY, geom GEOMETRY)
56   RETURNS BIGINT
57   AS $$
58 DECLARE
59   parent_place_id BIGINT;
60   location RECORD;
61 BEGIN
62   parent_place_id := find_parent_for_address(token_info, partition, centroid);
63
64   IF parent_place_id is null THEN
65     FOR location IN SELECT place_id FROM placex
66         WHERE ST_DWithin(geom, placex.geometry, 0.001) and placex.rank_search = 26
67         ORDER BY (ST_distance(placex.geometry, ST_LineInterpolatePoint(geom,0))+
68                   ST_distance(placex.geometry, ST_LineInterpolatePoint(geom,0.5))+
69                   ST_distance(placex.geometry, ST_LineInterpolatePoint(geom,1))) ASC limit 1
70     LOOP
71       parent_place_id := location.place_id;
72     END LOOP;
73   END IF;
74
75   IF parent_place_id is null THEN
76     RETURN 0;
77   END IF;
78
79   RETURN parent_place_id;
80 END;
81 $$
82 LANGUAGE plpgsql STABLE;
83
84
85 CREATE OR REPLACE FUNCTION osmline_reinsert(node_id BIGINT, geom GEOMETRY)
86   RETURNS BOOLEAN
87   AS $$
88 DECLARE
89   existingline RECORD;
90 BEGIN
91    SELECT w.id FROM planet_osm_ways w, location_property_osmline p
92      WHERE p.linegeo && geom and p.osm_id = w.id and p.indexed_status = 0
93            and node_id = any(w.nodes) INTO existingline;
94
95    IF existingline.id is not NULL THEN
96        DELETE FROM location_property_osmline WHERE osm_id = existingline.id;
97        INSERT INTO location_property_osmline (osm_id, address, linegeo)
98          SELECT osm_id, address, geometry FROM place
99            WHERE osm_type = 'W' and osm_id = existingline.id;
100    END IF;
101
102    RETURN true;
103 END;
104 $$
105 LANGUAGE plpgsql;
106
107
108 CREATE OR REPLACE FUNCTION osmline_insert()
109   RETURNS TRIGGER
110   AS $$
111 BEGIN
112   NEW.place_id := nextval('seq_place');
113   NEW.indexed_date := now();
114
115   IF NEW.indexed_status IS NULL THEN
116       IF NEW.address is NULL OR NOT NEW.address ? 'interpolation'
117          OR NEW.address->'interpolation' NOT IN ('odd', 'even', 'all') THEN
118           -- other interpolation types than odd/even/all (e.g. numeric ones) are not supported
119           RETURN NULL;
120       END IF;
121
122       NEW.indexed_status := 1; --STATUS_NEW
123       NEW.country_code := lower(get_country_code(NEW.linegeo));
124
125       NEW.partition := get_partition(NEW.country_code);
126       NEW.geometry_sector := geometry_sector(NEW.partition, NEW.linegeo);
127   END IF;
128
129   RETURN NEW;
130 END;
131 $$
132 LANGUAGE plpgsql;
133
134
135 CREATE OR REPLACE FUNCTION osmline_update()
136   RETURNS TRIGGER
137   AS $$
138 DECLARE
139   place_centroid GEOMETRY;
140   waynodes BIGINT[];
141   prevnode RECORD;
142   nextnode RECORD;
143   startnumber INTEGER;
144   endnumber INTEGER;
145   housenum INTEGER;
146   linegeo GEOMETRY;
147   splitline GEOMETRY;
148   sectiongeo GEOMETRY;
149   interpol_postcode TEXT;
150   postcode TEXT;
151 BEGIN
152   -- deferred delete
153   IF OLD.indexed_status = 100 THEN
154     delete from location_property_osmline where place_id = OLD.place_id;
155     RETURN NULL;
156   END IF;
157
158   IF NEW.indexed_status != 0 OR OLD.indexed_status = 0 THEN
159     RETURN NEW;
160   END IF;
161
162   NEW.interpolationtype = NEW.address->'interpolation';
163
164   place_centroid := ST_PointOnSurface(NEW.linegeo);
165   NEW.parent_place_id = get_interpolation_parent(NEW.token_info, NEW.partition,
166                                                  place_centroid, NEW.linegeo);
167
168   interpol_postcode := token_normalized_postcode(NEW.address->'postcode');
169
170   NEW.token_info := token_strip_info(NEW.token_info);
171   IF NEW.address ? '_inherited' THEN
172     NEW.address := hstore('interpolation', NEW.interpolationtype);
173   END IF;
174
175   -- if the line was newly inserted, split the line as necessary
176   IF OLD.indexed_status = 1 THEN
177       select nodes from planet_osm_ways where id = NEW.osm_id INTO waynodes;
178
179       IF array_upper(waynodes, 1) IS NULL THEN
180         RETURN NEW;
181       END IF;
182
183       linegeo := NEW.linegeo;
184       startnumber := NULL;
185
186       FOR nodeidpos in 1..array_upper(waynodes, 1) LOOP
187
188         select osm_id, address, geometry
189           from place where osm_type = 'N' and osm_id = waynodes[nodeidpos]::BIGINT
190                            and address is not NULL and address ? 'housenumber' limit 1 INTO nextnode;
191         --RAISE NOTICE 'Nextnode.place_id: %s', nextnode.place_id;
192         IF nextnode.osm_id IS NOT NULL THEN
193           --RAISE NOTICE 'place_id is not null';
194           IF nodeidpos > 1 and nodeidpos < array_upper(waynodes, 1) THEN
195             -- Make sure that the point is actually on the line. That might
196             -- be a bit paranoid but ensures that the algorithm still works
197             -- should osm2pgsql attempt to repair geometries.
198             splitline := split_line_on_node(linegeo, nextnode.geometry);
199             sectiongeo := ST_GeometryN(splitline, 1);
200             linegeo := ST_GeometryN(splitline, 2);
201           ELSE
202             sectiongeo = linegeo;
203           END IF;
204           endnumber := substring(nextnode.address->'housenumber','[0-9]+')::integer;
205
206           IF startnumber IS NOT NULL AND endnumber IS NOT NULL
207              AND startnumber != endnumber
208              AND ST_GeometryType(sectiongeo) = 'ST_LineString' THEN
209
210             IF (startnumber > endnumber) THEN
211               housenum := endnumber;
212               endnumber := startnumber;
213               startnumber := housenum;
214               sectiongeo := ST_Reverse(sectiongeo);
215             END IF;
216
217             -- determine postcode
218             postcode := coalesce(interpol_postcode,
219                                  token_normalized_postcode(prevnode.address->'postcode'),
220                                  token_normalized_postcode(nextnode.address->'postcode'),
221                                  postcode);
222
223             IF postcode is NULL THEN
224                 SELECT token_normalized_postcode(placex.postcode)
225                   FROM placex WHERE place_id = NEW.parent_place_id INTO postcode;
226             END IF;
227             IF postcode is NULL THEN
228                 postcode := get_nearest_postcode(NEW.country_code, nextnode.geometry);
229             END IF;
230
231             IF NEW.startnumber IS NULL THEN
232                 NEW.startnumber := startnumber;
233                 NEW.endnumber := endnumber;
234                 NEW.linegeo := sectiongeo;
235                 NEW.postcode := postcode;
236              ELSE
237               insert into location_property_osmline
238                      (linegeo, partition, osm_id, parent_place_id,
239                       startnumber, endnumber, interpolationtype,
240                       address, postcode, country_code,
241                       geometry_sector, indexed_status)
242               values (sectiongeo, NEW.partition, NEW.osm_id, NEW.parent_place_id,
243                       startnumber, endnumber, NEW.interpolationtype,
244                       NEW.address, postcode,
245                       NEW.country_code, NEW.geometry_sector, 0);
246              END IF;
247           END IF;
248
249           -- early break if we are out of line string,
250           -- might happen when a line string loops back on itself
251           IF ST_GeometryType(linegeo) != 'ST_LineString' THEN
252               RETURN NEW;
253           END IF;
254
255           startnumber := substring(nextnode.address->'housenumber','[0-9]+')::integer;
256           prevnode := nextnode;
257         END IF;
258       END LOOP;
259   END IF;
260
261   -- marking descendants for reparenting is not needed, because there are
262   -- actually no descendants for interpolation lines
263   RETURN NEW;
264 END;
265 $$
266 LANGUAGE plpgsql;