]> git.openstreetmap.org Git - nominatim.git/blob - sql/functions/ranking.sql
e918f924d9e0771537c01ac9baaf0520d88409d7
[nominatim.git] / sql / functions / ranking.sql
1 -- Functions related to search and address ranks
2
3 -- Return an approximate search radius according to the search rank.
4 CREATE OR REPLACE FUNCTION reverse_place_diameter(rank_search SMALLINT)
5   RETURNS FLOAT
6   AS $$
7 BEGIN
8   IF rank_search <= 4 THEN
9     RETURN 5.0;
10   ELSIF rank_search <= 8 THEN
11     RETURN 1.8;
12   ELSIF rank_search <= 12 THEN
13     RETURN 0.6;
14   ELSIF rank_search <= 17 THEN
15     RETURN 0.16;
16   ELSIF rank_search <= 18 THEN
17     RETURN 0.08;
18   ELSIF rank_search <= 19 THEN
19     RETURN 0.04;
20   END IF;
21
22   RETURN 0.02;
23 END;
24 $$
25 LANGUAGE plpgsql IMMUTABLE;
26
27
28 -- Return an approximate update radius according to the search rank.
29 CREATE OR REPLACE FUNCTION update_place_diameter(rank_search SMALLINT)
30   RETURNS FLOAT
31   AS $$
32 BEGIN
33   -- postcodes
34   IF rank_search = 11 or rank_search = 5 THEN
35     RETURN 0.05;
36   -- anything higher than city is effectively ignored (polygon required)
37   ELSIF rank_search < 16 THEN
38     RETURN 0;
39   ELSIF rank_search < 18 THEN
40     RETURN 0.1;
41   ELSIF rank_search < 20 THEN
42     RETURN 0.05;
43   ELSIF rank_search = 21 THEN
44     RETURN 0.001;
45   ELSIF rank_search < 24 THEN
46     RETURN 0.02;
47   ELSIF rank_search < 26 THEN
48     RETURN 0.002;
49   ELSIF rank_search < 28 THEN
50     RETURN 0.001;
51   END IF;
52
53   RETURN 0;
54 END;
55 $$
56 LANGUAGE plpgsql IMMUTABLE;
57
58 -- Compute a base address rank from the extent of the given geometry.
59 --
60 -- This is all simple guess work. We don't need particularly good estimates
61 -- here. This just avoids to have very high ranked address parts in features
62 -- that span very large areas (or vice versa).
63 CREATE OR REPLACE FUNCTION geometry_to_rank(search_rank SMALLINT, geometry GEOMETRY)
64   RETURNS SMALLINT
65   AS $$
66 DECLARE
67   area FLOAT;
68 BEGIN
69   IF ST_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon') THEN
70       area := ST_Area(geometry);
71   ELSIF ST_GeometryType(geometry) in ('ST_LineString','ST_MultiLineString') THEN
72       area := (ST_Length(geometry)^2) * 0.1;
73   ELSE
74     RETURN search_rank;
75   END IF;
76
77   IF area > 1 THEN
78     RETURN 7;
79   ELSIF area > 0.1 THEN
80     RETURN 9;
81   ELSIF area > 0.01 THEN
82     RETURN 13;
83   ELSIF area > 0.001 THEN
84     RETURN 17;
85   ELSIF area > 0.0001 THEN
86     RETURN 19;
87   ELSIF area > 0.000005 THEN
88     RETURN 21;
89   END IF;
90
91    RETURN 23;
92 END;
93 $$
94 LANGUAGE plpgsql IMMUTABLE;
95
96
97 -- Guess a ranking for postcodes from country and postcode format.
98 CREATE OR REPLACE FUNCTION get_postcode_rank(country_code VARCHAR(2), postcode TEXT,
99                                              OUT rank_search SMALLINT,
100                                              OUT rank_address SMALLINT)
101 AS $$
102 DECLARE
103   part TEXT;
104 BEGIN
105     rank_search := 30;
106     rank_address := 30;
107     postcode := upper(postcode);
108
109     IF country_code = 'gb' THEN
110         IF postcode ~ '^([A-Z][A-Z]?[0-9][0-9A-Z]? [0-9][A-Z][A-Z])$' THEN
111             rank_search := 25;
112             rank_address := 5;
113         ELSEIF postcode ~ '^([A-Z][A-Z]?[0-9][0-9A-Z]? [0-9])$' THEN
114             rank_search := 23;
115             rank_address := 5;
116         ELSEIF postcode ~ '^([A-Z][A-Z]?[0-9][0-9A-Z])$' THEN
117             rank_search := 21;
118             rank_address := 5;
119         END IF;
120
121     ELSEIF country_code = 'sg' THEN
122         IF postcode ~ '^([0-9]{6})$' THEN
123             rank_search := 25;
124             rank_address := 11;
125         END IF;
126
127     ELSEIF country_code = 'de' THEN
128         IF postcode ~ '^([0-9]{5})$' THEN
129             rank_search := 21;
130             rank_address := 11;
131         END IF;
132
133     ELSE
134         -- Guess at the postcode format and coverage (!)
135         IF postcode ~ '^[A-Z0-9]{1,5}$' THEN -- Probably too short to be very local
136             rank_search := 21;
137             rank_address := 11;
138         ELSE
139             -- Does it look splitable into and area and local code?
140             part := substring(postcode from '^([- :A-Z0-9]+)([- :][A-Z0-9]+)$');
141
142             IF part IS NOT NULL THEN
143                 rank_search := 25;
144                 rank_address := 11;
145             ELSEIF postcode ~ '^[- :A-Z0-9]{6,}$' THEN
146                 rank_search := 21;
147                 rank_address := 11;
148             END IF;
149         END IF;
150     END IF;
151
152 END;
153 $$
154 LANGUAGE plpgsql IMMUTABLE;
155
156
157 -- Get standard search and address rank for an object.
158 --
159 -- \param country        Two-letter country code where the object is in.
160 -- \param extended_type  OSM type (N, W, R) or area type (A).
161 -- \param place_class    Class (or tag key) of object.
162 -- \param place_type     Type (or tag value) of object.
163 -- \param admin_level    Value of admin_level tag.
164 -- \param is_major       If true, boost search rank by one.
165 -- \param postcode       Value of addr:postcode tag.
166 -- \param[out] search_rank   Computed search rank.
167 -- \param[out] address_rank  Computed address rank.
168 --
169 CREATE OR REPLACE FUNCTION compute_place_rank(country VARCHAR(2),
170                                               extended_type VARCHAR(1),
171                                               place_class TEXT, place_type TEXT,
172                                               admin_level SMALLINT,
173                                               is_major BOOLEAN,
174                                               postcode TEXT,
175                                               OUT search_rank SMALLINT,
176                                               OUT address_rank SMALLINT)
177 AS $$
178 DECLARE
179   classtype TEXT;
180 BEGIN
181   IF place_class in ('place','boundary')
182      and place_type in ('postcode','postal_code')
183   THEN
184     SELECT * INTO search_rank, address_rank
185       FROM get_postcode_rank(country, postcode);
186   ELSEIF extended_type = 'N' AND place_class = 'highway' THEN
187     search_rank = 30;
188     address_rank = 0;
189   ELSEIF place_class = 'landuse' AND extended_type != 'A' THEN
190     search_rank = 30;
191     address_rank = 0;
192   ELSE
193     IF place_class = 'boundary' and place_type = 'administrative' THEN
194       classtype = place_type || admin_level::TEXT;
195     ELSE
196       classtype = place_type;
197     END IF;
198
199     SELECT l.rank_search, l.rank_address INTO search_rank, address_rank
200       FROM address_levels l
201      WHERE (l.country_code = country or l.country_code is NULL)
202            AND l.class = place_class AND (l.type = classtype or l.type is NULL)
203      ORDER BY l.country_code, l.class, l.type LIMIT 1;
204
205     IF search_rank is NULL THEN
206       search_rank := 30;
207     END IF;
208
209     IF address_rank is NULL THEN
210       address_rank := 30;
211     END IF;
212
213     -- some postcorrections
214     IF place_class = 'waterway' AND extended_type = 'R' THEN
215         -- Slightly promote waterway relations so that they are processed
216         -- before their members.
217         search_rank := search_rank - 1;
218     END IF;
219
220     IF is_major THEN
221       search_rank := search_rank - 1;
222     END IF;
223   END IF;
224 END;
225 $$
226 LANGUAGE plpgsql IMMUTABLE;