]> git.openstreetmap.org Git - nominatim.git/blob - lib-sql/functions/normalization.sql
Merge remote-tracking branch 'upstream/master'
[nominatim.git] / lib-sql / functions / normalization.sql
1 -- Functions for term normalisation and access to the 'word' table.
2
3 CREATE OR REPLACE FUNCTION transliteration(text) RETURNS text
4   AS '{{ modulepath }}/nominatim.so', 'transliteration'
5 LANGUAGE c IMMUTABLE STRICT;
6
7
8 CREATE OR REPLACE FUNCTION gettokenstring(text) RETURNS text
9   AS '{{ modulepath }}/nominatim.so', 'gettokenstring'
10 LANGUAGE c IMMUTABLE STRICT;
11
12
13 CREATE OR REPLACE FUNCTION make_standard_name(name TEXT) RETURNS TEXT
14   AS $$
15 DECLARE
16   o TEXT;
17 BEGIN
18   o := public.gettokenstring(public.transliteration(name));
19   RETURN trim(substr(o,1,length(o)));
20 END;
21 $$
22 LANGUAGE plpgsql IMMUTABLE;
23
24 -- returns NULL if the word is too common
25 CREATE OR REPLACE FUNCTION getorcreate_word_id(lookup_word TEXT) 
26   RETURNS INTEGER
27   AS $$
28 DECLARE
29   lookup_token TEXT;
30   return_word_id INTEGER;
31   count INTEGER;
32 BEGIN
33   lookup_token := trim(lookup_word);
34   SELECT min(word_id), max(search_name_count) FROM word
35     WHERE word_token = lookup_token and class is null and type is null
36     INTO return_word_id, count;
37   IF return_word_id IS NULL THEN
38     return_word_id := nextval('seq_word');
39     INSERT INTO word VALUES (return_word_id, lookup_token, null, null, null, null, 0);
40   ELSE
41     IF count > get_maxwordfreq() THEN
42       return_word_id := NULL;
43     END IF;
44   END IF;
45   RETURN return_word_id;
46 END;
47 $$
48 LANGUAGE plpgsql;
49
50 -- Create housenumber tokens from an OSM addr:housenumber.
51 -- The housnumber is split at comma and semicolon as necessary.
52 -- The function returns the normalized form of the housenumber suitable
53 -- for comparison.
54 CREATE OR REPLACE FUNCTION create_housenumber_id(housenumber TEXT)
55   RETURNS TEXT
56   AS $$
57 DECLARE
58   normtext TEXT;
59 BEGIN
60   SELECT array_to_string(array_agg(trans), ';')
61     INTO normtext
62     FROM (SELECT lookup_word as trans, getorcreate_housenumber_id(lookup_word)
63           FROM (SELECT make_standard_name(h) as lookup_word
64                 FROM regexp_split_to_table(housenumber, '[,;]') h) x) y;
65
66   return normtext;
67 END;
68 $$ LANGUAGE plpgsql STABLE STRICT;
69
70 CREATE OR REPLACE FUNCTION getorcreate_housenumber_id(lookup_word TEXT)
71   RETURNS INTEGER
72   AS $$
73 DECLARE
74   lookup_token TEXT;
75   return_word_id INTEGER;
76 BEGIN
77   lookup_token := ' ' || trim(lookup_word);
78   SELECT min(word_id) FROM word
79     WHERE word_token = lookup_token and class='place' and type='house'
80     INTO return_word_id;
81   IF return_word_id IS NULL THEN
82     return_word_id := nextval('seq_word');
83     INSERT INTO word VALUES (return_word_id, lookup_token, null,
84                              'place', 'house', null, 0);
85   END IF;
86   RETURN return_word_id;
87 END;
88 $$
89 LANGUAGE plpgsql;
90
91
92 CREATE OR REPLACE FUNCTION getorcreate_postcode_id(postcode TEXT)
93   RETURNS INTEGER
94   AS $$
95 DECLARE
96   lookup_token TEXT;
97   lookup_word TEXT;
98   return_word_id INTEGER;
99 BEGIN
100   lookup_word := upper(trim(postcode));
101   lookup_token := ' ' || make_standard_name(lookup_word);
102   SELECT min(word_id) FROM word
103     WHERE word_token = lookup_token and word = lookup_word
104           and class='place' and type='postcode'
105     INTO return_word_id;
106   IF return_word_id IS NULL THEN
107     return_word_id := nextval('seq_word');
108     INSERT INTO word VALUES (return_word_id, lookup_token, lookup_word,
109                              'place', 'postcode', null, 0);
110   END IF;
111   RETURN return_word_id;
112 END;
113 $$
114 LANGUAGE plpgsql;
115
116
117 CREATE OR REPLACE FUNCTION getorcreate_country(lookup_word TEXT,
118                                                lookup_country_code varchar(2))
119   RETURNS INTEGER
120   AS $$
121 DECLARE
122   lookup_token TEXT;
123   return_word_id INTEGER;
124 BEGIN
125   lookup_token := ' '||trim(lookup_word);
126   SELECT min(word_id) FROM word
127     WHERE word_token = lookup_token and country_code=lookup_country_code
128     INTO return_word_id;
129   IF return_word_id IS NULL THEN
130     return_word_id := nextval('seq_word');
131     INSERT INTO word VALUES (return_word_id, lookup_token, null,
132                              null, null, lookup_country_code, 0);
133   END IF;
134   RETURN return_word_id;
135 END;
136 $$
137 LANGUAGE plpgsql;
138
139
140 CREATE OR REPLACE FUNCTION getorcreate_amenity(lookup_word TEXT, normalized_word TEXT,
141                                                lookup_class text, lookup_type text)
142   RETURNS INTEGER
143   AS $$
144 DECLARE
145   lookup_token TEXT;
146   return_word_id INTEGER;
147 BEGIN
148   lookup_token := ' '||trim(lookup_word);
149   SELECT min(word_id) FROM word
150   WHERE word_token = lookup_token and word = normalized_word
151         and class = lookup_class and type = lookup_type
152   INTO return_word_id;
153   IF return_word_id IS NULL THEN
154     return_word_id := nextval('seq_word');
155     INSERT INTO word VALUES (return_word_id, lookup_token, normalized_word,
156                              lookup_class, lookup_type, null, 0);
157   END IF;
158   RETURN return_word_id;
159 END;
160 $$
161 LANGUAGE plpgsql;
162
163
164 CREATE OR REPLACE FUNCTION getorcreate_amenityoperator(lookup_word TEXT,
165                                                        normalized_word TEXT,
166                                                        lookup_class text,
167                                                        lookup_type text,
168                                                        op text)
169   RETURNS INTEGER
170   AS $$
171 DECLARE
172   lookup_token TEXT;
173   return_word_id INTEGER;
174 BEGIN
175   lookup_token := ' '||trim(lookup_word);
176   SELECT min(word_id) FROM word
177   WHERE word_token = lookup_token and word = normalized_word
178         and class = lookup_class and type = lookup_type and operator = op
179   INTO return_word_id;
180   IF return_word_id IS NULL THEN
181     return_word_id := nextval('seq_word');
182     INSERT INTO word VALUES (return_word_id, lookup_token, normalized_word,
183                              lookup_class, lookup_type, null, 0, op);
184   END IF;
185   RETURN return_word_id;
186 END;
187 $$
188 LANGUAGE plpgsql;
189
190
191 CREATE OR REPLACE FUNCTION getorcreate_name_id(lookup_word TEXT, src_word TEXT)
192   RETURNS INTEGER
193   AS $$
194 DECLARE
195   lookup_token TEXT;
196   nospace_lookup_token TEXT;
197   return_word_id INTEGER;
198 BEGIN
199   lookup_token := ' '||trim(lookup_word);
200   SELECT min(word_id) FROM word
201   WHERE word_token = lookup_token and class is null and type is null
202   INTO return_word_id;
203   IF return_word_id IS NULL THEN
204     return_word_id := nextval('seq_word');
205     INSERT INTO word VALUES (return_word_id, lookup_token, src_word,
206                              null, null, null, 0);
207   END IF;
208   RETURN return_word_id;
209 END;
210 $$
211 LANGUAGE plpgsql;
212
213
214 CREATE OR REPLACE FUNCTION getorcreate_name_id(lookup_word TEXT)
215   RETURNS INTEGER
216   AS $$
217 DECLARE
218 BEGIN
219   RETURN getorcreate_name_id(lookup_word, '');
220 END;
221 $$
222 LANGUAGE plpgsql;
223
224 -- Normalize a string and lookup its word ids (partial words).
225 CREATE OR REPLACE FUNCTION addr_ids_from_name(lookup_word TEXT)
226   RETURNS INTEGER[]
227   AS $$
228 DECLARE
229   words TEXT[];
230   id INTEGER;
231   return_word_id INTEGER[];
232   word_ids INTEGER[];
233   j INTEGER;
234 BEGIN
235   words := string_to_array(make_standard_name(lookup_word), ' ');
236   IF array_upper(words, 1) IS NOT NULL THEN
237     FOR j IN 1..array_upper(words, 1) LOOP
238       IF (words[j] != '') THEN
239         SELECT array_agg(word_id) INTO word_ids
240           FROM word
241          WHERE word_token = words[j] and class is null and type is null;
242
243         IF word_ids IS NULL THEN
244           id := nextval('seq_word');
245           INSERT INTO word VALUES (id, words[j], null, null, null, null, 0);
246           return_word_id := return_word_id || id;
247         ELSE
248           return_word_id := array_merge(return_word_id, word_ids);
249         END IF;
250       END IF;
251     END LOOP;
252   END IF;
253
254   RETURN return_word_id;
255 END;
256 $$
257 LANGUAGE plpgsql;
258
259
260 -- Normalize a string and look up its name ids (full words).
261 CREATE OR REPLACE FUNCTION word_ids_from_name(lookup_word TEXT)
262   RETURNS INTEGER[]
263   AS $$
264 DECLARE
265   lookup_token TEXT;
266   return_word_ids INTEGER[];
267 BEGIN
268   lookup_token := ' '|| make_standard_name(lookup_word);
269   SELECT array_agg(word_id) FROM word
270     WHERE word_token = lookup_token and class is null and type is null
271     INTO return_word_ids;
272   RETURN return_word_ids;
273 END;
274 $$
275 LANGUAGE plpgsql STABLE STRICT;
276
277
278 CREATE OR REPLACE FUNCTION create_country(src HSTORE, country_code varchar(2))
279   RETURNS VOID
280   AS $$
281 DECLARE
282   s TEXT;
283   w INTEGER;
284   words TEXT[];
285   item RECORD;
286   j INTEGER;
287 BEGIN
288   FOR item IN SELECT (each(src)).* LOOP
289
290     s := make_standard_name(item.value);
291     w := getorcreate_country(s, country_code);
292
293     words := regexp_split_to_array(item.value, E'[,;()]');
294     IF array_upper(words, 1) != 1 THEN
295       FOR j IN 1..array_upper(words, 1) LOOP
296         s := make_standard_name(words[j]);
297         IF s != '' THEN
298           w := getorcreate_country(s, country_code);
299         END IF;
300       END LOOP;
301     END IF;
302   END LOOP;
303 END;
304 $$
305 LANGUAGE plpgsql;
306
307
308 CREATE OR REPLACE FUNCTION make_keywords(src HSTORE)
309   RETURNS INTEGER[]
310   AS $$
311 DECLARE
312   result INTEGER[];
313   s TEXT;
314   w INTEGER;
315   words TEXT[];
316   item RECORD;
317   j INTEGER;
318 BEGIN
319   result := '{}'::INTEGER[];
320
321   FOR item IN SELECT (each(src)).* LOOP
322
323     s := make_standard_name(item.value);
324     w := getorcreate_name_id(s, item.value);
325
326     IF not(ARRAY[w] <@ result) THEN
327       result := result || w;
328     END IF;
329
330     w := getorcreate_word_id(s);
331
332     IF w IS NOT NULL AND NOT (ARRAY[w] <@ result) THEN
333       result := result || w;
334     END IF;
335
336     words := string_to_array(s, ' ');
337     IF array_upper(words, 1) IS NOT NULL THEN
338       FOR j IN 1..array_upper(words, 1) LOOP
339         IF (words[j] != '') THEN
340           w = getorcreate_word_id(words[j]);
341           IF w IS NOT NULL AND NOT (ARRAY[w] <@ result) THEN
342             result := result || w;
343           END IF;
344         END IF;
345       END LOOP;
346     END IF;
347
348     words := regexp_split_to_array(item.value, E'[,;()]');
349     IF array_upper(words, 1) != 1 THEN
350       FOR j IN 1..array_upper(words, 1) LOOP
351         s := make_standard_name(words[j]);
352         IF s != '' THEN
353           w := getorcreate_word_id(s);
354           IF w IS NOT NULL AND NOT (ARRAY[w] <@ result) THEN
355             result := result || w;
356           END IF;
357         END IF;
358       END LOOP;
359     END IF;
360
361     s := regexp_replace(item.value, '市$', '');
362     IF s != item.value THEN
363       s := make_standard_name(s);
364       IF s != '' THEN
365         w := getorcreate_name_id(s, item.value);
366         IF NOT (ARRAY[w] <@ result) THEN
367           result := result || w;
368         END IF;
369       END IF;
370     END IF;
371
372   END LOOP;
373
374   RETURN result;
375 END;
376 $$
377 LANGUAGE plpgsql;
378
379
380 CREATE OR REPLACE FUNCTION make_keywords(src TEXT)
381   RETURNS INTEGER[]
382   AS $$
383 DECLARE
384   result INTEGER[];
385   s TEXT;
386   w INTEGER;
387   words TEXT[];
388   i INTEGER;
389   j INTEGER;
390 BEGIN
391   result := '{}'::INTEGER[];
392
393   s := make_standard_name(src);
394   w := getorcreate_name_id(s, src);
395
396   IF NOT (ARRAY[w] <@ result) THEN
397     result := result || w;
398   END IF;
399
400   w := getorcreate_word_id(s);
401
402   IF w IS NOT NULL AND NOT (ARRAY[w] <@ result) THEN
403     result := result || w;
404   END IF;
405
406   words := string_to_array(s, ' ');
407   IF array_upper(words, 1) IS NOT NULL THEN
408     FOR j IN 1..array_upper(words, 1) LOOP
409       IF (words[j] != '') THEN
410         w = getorcreate_word_id(words[j]);
411         IF w IS NOT NULL AND NOT (ARRAY[w] <@ result) THEN
412           result := result || w;
413         END IF;
414       END IF;
415     END LOOP;
416   END IF;
417
418   words := regexp_split_to_array(src, E'[,;()]');
419   IF array_upper(words, 1) != 1 THEN
420     FOR j IN 1..array_upper(words, 1) LOOP
421       s := make_standard_name(words[j]);
422       IF s != '' THEN
423         w := getorcreate_word_id(s);
424         IF w IS NOT NULL AND NOT (ARRAY[w] <@ result) THEN
425           result := result || w;
426         END IF;
427       END IF;
428     END LOOP;
429   END IF;
430
431   s := regexp_replace(src, '市$', '');
432   IF s != src THEN
433     s := make_standard_name(s);
434     IF s != '' THEN
435       w := getorcreate_name_id(s, src);
436       IF NOT (ARRAY[w] <@ result) THEN
437         result := result || w;
438       END IF;
439     END IF;
440   END IF;
441
442   RETURN result;
443 END;
444 $$
445 LANGUAGE plpgsql;
446
447
448 CREATE OR REPLACE FUNCTION create_poi_search_terms(obj_place_id BIGINT,
449                                                    in_partition SMALLINT,
450                                                    parent_place_id BIGINT,
451                                                    address HSTORE,
452                                                    country TEXT,
453                                                    housenumber TEXT,
454                                                    initial_name_vector INTEGER[],
455                                                    geometry GEOMETRY,
456                                                    OUT name_vector INTEGER[],
457                                                    OUT nameaddress_vector INTEGER[])
458   AS $$
459 DECLARE
460   parent_name_vector INTEGER[];
461   parent_address_vector INTEGER[];
462   addr_place_ids INTEGER[];
463
464   addr_item RECORD;
465   parent_address_place_ids BIGINT[];
466   filtered_address HSTORE;
467 BEGIN
468   nameaddress_vector := '{}'::INTEGER[];
469
470   SELECT s.name_vector, s.nameaddress_vector
471     INTO parent_name_vector, parent_address_vector
472     FROM search_name s
473     WHERE s.place_id = parent_place_id;
474
475   -- Find all address tags that don't appear in the parent search names.
476   SELECT hstore(array_agg(ARRAY[k, v])) INTO filtered_address
477     FROM (SELECT skeys(address) as k, svals(address) as v) a
478    WHERE not addr_ids_from_name(v) && parent_address_vector
479          AND k not in ('country', 'street', 'place', 'postcode',
480                        'housenumber', 'streetnumber', 'conscriptionnumber');
481
482   -- Compute all search terms from the addr: tags.
483   IF filtered_address IS NOT NULL THEN
484     FOR addr_item IN
485       SELECT * FROM
486         get_places_for_addr_tags(in_partition, geometry, filtered_address, country)
487     LOOP
488         IF addr_item.place_id is null THEN
489             nameaddress_vector := array_merge(nameaddress_vector,
490                                               addr_item.keywords);
491             CONTINUE;
492         END IF;
493
494         IF parent_address_place_ids is null THEN
495             SELECT array_agg(parent_place_id) INTO parent_address_place_ids
496               FROM place_addressline
497              WHERE place_id = parent_place_id;
498         END IF;
499
500         IF not parent_address_place_ids @> ARRAY[addr_item.place_id] THEN
501             nameaddress_vector := array_merge(nameaddress_vector,
502                                               addr_item.keywords);
503
504             INSERT INTO place_addressline (place_id, address_place_id, fromarea,
505                                            isaddress, distance, cached_rank_address)
506             VALUES (obj_place_id, addr_item.place_id, not addr_item.isguess,
507                     true, addr_item.distance, addr_item.rank_address);
508         END IF;
509     END LOOP;
510   END IF;
511
512   name_vector := initial_name_vector;
513
514   -- Check if the parent covers all address terms.
515   -- If not, create a search name entry with the house number as the name.
516   -- This is unusual for the search_name table but prevents that the place
517   -- is returned when we only search for the street/place.
518
519   IF housenumber is not null and not nameaddress_vector <@ parent_address_vector THEN
520     name_vector := array_merge(name_vector,
521                                ARRAY[getorcreate_housenumber_id(make_standard_name(housenumber))]);
522   END IF;
523
524   IF not address ? 'street' and address ? 'place' THEN
525     addr_place_ids := addr_ids_from_name(address->'place');
526     IF not addr_place_ids <@ parent_name_vector THEN
527       -- make sure addr:place terms are always searchable
528       nameaddress_vector := array_merge(nameaddress_vector, addr_place_ids);
529       -- If there is a housenumber, also add the place name as a name,
530       -- so we can search it by the usual housenumber+place algorithms.
531       IF housenumber is not null THEN
532         name_vector := array_merge(name_vector,
533                                    ARRAY[getorcreate_name_id(make_standard_name(address->'place'))]);
534       END IF;
535     END IF;
536   END IF;
537
538   -- Cheating here by not recomputing all terms but simply using the ones
539   -- from the parent object.
540   nameaddress_vector := array_merge(nameaddress_vector, parent_name_vector);
541   nameaddress_vector := array_merge(nameaddress_vector, parent_address_vector);
542
543 END;
544 $$
545 LANGUAGE plpgsql;