]> git.openstreetmap.org Git - nominatim.git/blob - lib-sql/functions/ranking.sql
Merge remote-tracking branch 'upstream/master'
[nominatim.git] / lib-sql / functions / ranking.sql
1 -- SPDX-License-Identifier: GPL-2.0-only
2 --
3 -- This file is part of Nominatim. (https://nominatim.org)
4 --
5 -- Copyright (C) 2026 by the Nominatim developer community.
6 -- For a full list of authors see the git log.
7
8 -- Functions related to search and address ranks
9
10 -- Check if a place is rankable at all.
11 -- These really should be dropped at the lua level eventually.
12 CREATE OR REPLACE FUNCTION is_rankable_place(osm_type TEXT, class TEXT,
13                                              admin_level SMALLINT, name HSTORE,
14                                              extratags HSTORE, is_area BOOLEAN)
15   RETURNS BOOLEAN
16   AS $$
17 BEGIN
18   IF class = 'highway' AND is_area AND name is null
19          AND extratags ? 'area' AND extratags->'area' = 'yes'
20   THEN
21       RETURN FALSE;
22   END IF;
23
24   IF class = 'boundary' THEN
25     IF NOT is_area
26        OR (admin_level <= 4 AND osm_type = 'W')
27     THEN
28       RETURN FALSE;
29     END IF;
30   END IF;
31
32   RETURN TRUE;
33 END;
34 $$
35 LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE;
36
37
38 -- Return an approximate search radius according to the search rank.
39 CREATE OR REPLACE FUNCTION reverse_place_diameter(rank_search SMALLINT)
40   RETURNS FLOAT
41   AS $$
42 BEGIN
43   IF rank_search <= 4 THEN
44     RETURN 5.0;
45   ELSIF rank_search <= 8 THEN
46     RETURN 1.8;
47   ELSIF rank_search <= 12 THEN
48     RETURN 0.6;
49   ELSIF rank_search <= 17 THEN
50     RETURN 0.16;
51   ELSIF rank_search <= 18 THEN
52     RETURN 0.08;
53   ELSIF rank_search <= 19 THEN
54     RETURN 0.04;
55   END IF;
56
57   RETURN 0.02;
58 END;
59 $$
60 LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE;
61
62
63 -- Return an approximate update radius according to the search rank.
64 CREATE OR REPLACE FUNCTION update_place_diameter(rank_search SMALLINT)
65   RETURNS FLOAT
66   AS $$
67 BEGIN
68   -- postcodes
69   IF rank_search = 11 or rank_search = 5 THEN
70     RETURN 0.05;
71   -- anything higher than city is effectively ignored (polygon required)
72   ELSIF rank_search < 16 THEN
73     RETURN 0;
74   ELSIF rank_search < 18 THEN
75     RETURN 0.1;
76   ELSIF rank_search < 20 THEN
77     RETURN 0.05;
78   ELSIF rank_search = 21 THEN
79     RETURN 0.001;
80   ELSIF rank_search < 24 THEN
81     RETURN 0.02;
82   ELSIF rank_search < 26 THEN
83     RETURN 0.002;
84   ELSIF rank_search < 28 THEN
85     RETURN 0.001;
86   END IF;
87
88   RETURN 0;
89 END;
90 $$
91 LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE;
92
93 -- Compute a base address rank from the extent of the given geometry.
94 --
95 -- This is all simple guess work. We don't need particularly good estimates
96 -- here. This just avoids to have very high ranked address parts in features
97 -- that span very large areas (or vice versa).
98 CREATE OR REPLACE FUNCTION geometry_to_rank(search_rank SMALLINT, geometry GEOMETRY, country_code TEXT)
99   RETURNS SMALLINT
100   AS $$
101 DECLARE
102   area FLOAT;
103 BEGIN
104   IF ST_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon') THEN
105       area := ST_Area(geometry);
106   ELSIF ST_GeometryType(geometry) in ('ST_LineString','ST_MultiLineString') THEN
107       area := (ST_Length(geometry)^2) * 0.1;
108   ELSE
109     RETURN search_rank;
110   END IF;
111
112   -- adjust for the fact that countries come in different sizes
113   IF country_code IN ('ca', 'au', 'ru') THEN
114     area := area / 5;
115   ELSIF country_code IN ('br', 'kz', 'cn', 'us', 'ne', 'gb', 'za', 'sa', 'id', 'eh', 'ml', 'tm') THEN
116     area := area / 3;
117   ELSIF country_code IN ('bo', 'ar', 'sd', 'mn', 'in', 'et', 'cd', 'mz', 'ly', 'cl', 'zm') THEN
118     area := area / 2;
119   ELSIF country_code IN ('sg', 'ws', 'st', 'kn') THEN
120     area := area * 5;
121   ELSIF country_code IN ('dm', 'mt', 'lc', 'gg', 'sc', 'nr') THEN
122     area := area * 20;
123   END IF;
124
125   IF area > 1 THEN
126     RETURN 7;
127   ELSIF area > 0.1 THEN
128     RETURN 9;
129   ELSIF area > 0.01 THEN
130     RETURN 13;
131   ELSIF area > 0.001 THEN
132     RETURN 17;
133   ELSIF area > 0.0001 THEN
134     RETURN 19;
135   ELSIF area > 0.000005 THEN
136     RETURN 21;
137   END IF;
138
139    RETURN 23;
140 END;
141 $$
142 LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE;
143
144
145 -- Get standard search and address rank for an object.
146 --
147 -- \param country        Two-letter country code where the object is in.
148 -- \param extended_type  OSM type (N, W, R) or area type (A).
149 -- \param place_class    Class (or tag key) of object.
150 -- \param place_type     Type (or tag value) of object.
151 -- \param admin_level    Value of admin_level tag.
152 -- \param is_major       If true, boost search rank by one.
153 -- \param postcode       Value of addr:postcode tag.
154 -- \param[out] search_rank   Computed search rank.
155 -- \param[out] address_rank  Computed address rank.
156 --
157 CREATE OR REPLACE FUNCTION compute_place_rank(country VARCHAR(2),
158                                               extended_type VARCHAR(1),
159                                               place_class TEXT, place_type TEXT,
160                                               admin_level SMALLINT,
161                                               is_major BOOLEAN,
162                                               postcode TEXT,
163                                               OUT search_rank SMALLINT,
164                                               OUT address_rank SMALLINT)
165 AS $$
166 DECLARE
167   classtype TEXT;
168 BEGIN
169   IF extended_type = 'N' AND place_class = 'highway' THEN
170     search_rank = 30;
171     address_rank = 30;
172   ELSEIF place_class = 'landuse' AND extended_type != 'A' THEN
173     search_rank = 30;
174     address_rank = 30;
175   ELSE
176     IF place_class = 'boundary' and place_type = 'administrative' THEN
177       classtype = place_type || admin_level::TEXT;
178     ELSE
179       classtype = place_type;
180     END IF;
181
182     SELECT l.rank_search, l.rank_address INTO search_rank, address_rank
183       FROM address_levels l
184      WHERE (l.country_code = country or l.country_code is NULL)
185            AND l.class = place_class AND (l.type = classtype or l.type is NULL)
186      ORDER BY l.country_code, l.class, l.type LIMIT 1;
187
188     IF search_rank is NULL OR address_rank is NULL THEN
189       search_rank := 30;
190       address_rank := 30;
191     END IF;
192
193     -- some postcorrections
194     IF place_class = 'waterway' AND extended_type = 'R' THEN
195         -- Slightly promote waterway relations so that they are processed
196         -- before their members.
197         search_rank := search_rank - 1;
198     END IF;
199
200     IF is_major THEN
201       search_rank := search_rank - 1;
202     END IF;
203   END IF;
204 END;
205 $$
206 LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE;
207
208 CREATE OR REPLACE FUNCTION get_addr_tag_rank(key TEXT, country TEXT,
209                                              OUT from_rank SMALLINT,
210                                              OUT to_rank SMALLINT,
211                                              OUT extent FLOAT)
212   AS $$
213 DECLARE
214   ranks RECORD;
215 BEGIN
216   from_rank := null;
217
218   FOR ranks IN
219     SELECT * FROM
220       (SELECT l.rank_search, l.rank_address FROM address_levels l
221         WHERE (l.country_code = country or l.country_code is NULL)
222                AND l.class = 'place' AND l.type = key
223         ORDER BY l.country_code LIMIT 1) r
224       WHERE rank_address > 0
225   LOOP
226     extent := reverse_place_diameter(ranks.rank_search);
227
228     IF ranks.rank_address <= 4 THEN
229         from_rank := 4;
230         to_rank := 4;
231     ELSEIF ranks.rank_address <= 9 THEN
232         from_rank := 5;
233         to_rank := 9;
234     ELSEIF ranks.rank_address <= 12 THEN
235         from_rank := 10;
236         to_rank := 12;
237     ELSEIF ranks.rank_address <= 16 THEN
238         from_rank := 13;
239         to_rank := 16;
240     ELSEIF ranks.rank_address <= 21 THEN
241         from_rank := 17;
242         to_rank := 21;
243     ELSEIF ranks.rank_address <= 24 THEN
244         from_rank := 22;
245         to_rank := 24;
246     ELSE
247         from_rank := 25;
248         to_rank := 25;
249     END IF;
250   END LOOP;
251 END;
252 $$
253 LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE;
254
255
256 CREATE OR REPLACE FUNCTION weigh_search(search_vector INT[],
257                                         rankings TEXT,
258                                         def_weight FLOAT)
259   RETURNS FLOAT
260   AS $$
261 DECLARE
262   rank JSON;
263 BEGIN
264   FOR rank IN
265     SELECT * FROM json_array_elements(rankings::JSON)
266   LOOP
267     IF true = ALL(SELECT x::int = ANY(search_vector) FROM json_array_elements_text(rank->1) as x) THEN
268       RETURN (rank->>0)::float;
269     END IF;
270   END LOOP;
271   RETURN def_weight;
272 END;
273 $$
274 LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE;