1 -- Functions for term normalisation and access to the 'word' table.
3 CREATE OR REPLACE FUNCTION transliteration(text) RETURNS text
4 AS '{{ modulepath }}/nominatim.so', 'transliteration'
5 LANGUAGE c IMMUTABLE STRICT;
8 CREATE OR REPLACE FUNCTION gettokenstring(text) RETURNS text
9 AS '{{ modulepath }}/nominatim.so', 'gettokenstring'
10 LANGUAGE c IMMUTABLE STRICT;
13 CREATE OR REPLACE FUNCTION make_standard_name(name TEXT) RETURNS TEXT
18 o := public.gettokenstring(public.transliteration(name));
19 RETURN trim(substr(o,1,length(o)));
22 LANGUAGE plpgsql IMMUTABLE;
24 -- returns NULL if the word is too common
25 CREATE OR REPLACE FUNCTION getorcreate_word_id(lookup_word TEXT)
30 return_word_id INTEGER;
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);
41 IF count > get_maxwordfreq() THEN
42 return_word_id := NULL;
45 RETURN return_word_id;
51 CREATE OR REPLACE FUNCTION create_housenumber_id(housenumber TEXT)
57 SELECT array_to_string(array_agg(trans), ';')
59 FROM (SELECT transliteration(lookup_word) as trans, getorcreate_housenumber_id(lookup_word)
60 FROM (SELECT make_standard_name(h) as lookup_word
61 FROM regexp_split_to_table(housenumber, '[,;]') h) x) y;
65 $$ LANGUAGE plpgsql STABLE STRICT;
67 CREATE OR REPLACE FUNCTION getorcreate_housenumber_id(lookup_word TEXT)
72 return_word_id INTEGER;
74 lookup_token := ' ' || trim(lookup_word);
75 SELECT min(word_id) FROM word
76 WHERE word_token = lookup_token and class='place' and type='house'
78 IF return_word_id IS NULL THEN
79 return_word_id := nextval('seq_word');
80 INSERT INTO word VALUES (return_word_id, lookup_token, null,
81 'place', 'house', null, 0);
83 RETURN return_word_id;
89 CREATE OR REPLACE FUNCTION getorcreate_postcode_id(postcode TEXT)
95 return_word_id INTEGER;
97 lookup_word := upper(trim(postcode));
98 lookup_token := ' ' || make_standard_name(lookup_word);
99 SELECT min(word_id) FROM word
100 WHERE word_token = lookup_token and word = lookup_word
101 and class='place' and type='postcode'
103 IF return_word_id IS NULL THEN
104 return_word_id := nextval('seq_word');
105 INSERT INTO word VALUES (return_word_id, lookup_token, lookup_word,
106 'place', 'postcode', null, 0);
108 RETURN return_word_id;
114 CREATE OR REPLACE FUNCTION getorcreate_country(lookup_word TEXT,
115 lookup_country_code varchar(2))
120 return_word_id INTEGER;
122 lookup_token := ' '||trim(lookup_word);
123 SELECT min(word_id) FROM word
124 WHERE word_token = lookup_token and country_code=lookup_country_code
126 IF return_word_id IS NULL THEN
127 return_word_id := nextval('seq_word');
128 INSERT INTO word VALUES (return_word_id, lookup_token, null,
129 null, null, lookup_country_code, 0);
131 RETURN return_word_id;
137 CREATE OR REPLACE FUNCTION getorcreate_amenity(lookup_word TEXT, normalized_word TEXT,
138 lookup_class text, lookup_type text)
143 return_word_id INTEGER;
145 lookup_token := ' '||trim(lookup_word);
146 SELECT min(word_id) FROM word
147 WHERE word_token = lookup_token and word = normalized_word
148 and class = lookup_class and type = lookup_type
150 IF return_word_id IS NULL THEN
151 return_word_id := nextval('seq_word');
152 INSERT INTO word VALUES (return_word_id, lookup_token, normalized_word,
153 lookup_class, lookup_type, null, 0);
155 RETURN return_word_id;
161 CREATE OR REPLACE FUNCTION getorcreate_amenityoperator(lookup_word TEXT,
162 normalized_word TEXT,
170 return_word_id INTEGER;
172 lookup_token := ' '||trim(lookup_word);
173 SELECT min(word_id) FROM word
174 WHERE word_token = lookup_token and word = normalized_word
175 and class = lookup_class and type = lookup_type and operator = op
177 IF return_word_id IS NULL THEN
178 return_word_id := nextval('seq_word');
179 INSERT INTO word VALUES (return_word_id, lookup_token, normalized_word,
180 lookup_class, lookup_type, null, 0, op);
182 RETURN return_word_id;
188 CREATE OR REPLACE FUNCTION getorcreate_name_id(lookup_word TEXT, src_word TEXT)
193 nospace_lookup_token TEXT;
194 return_word_id INTEGER;
196 lookup_token := ' '||trim(lookup_word);
197 SELECT min(word_id) FROM word
198 WHERE word_token = lookup_token and class is null and type is null
200 IF return_word_id IS NULL THEN
201 return_word_id := nextval('seq_word');
202 INSERT INTO word VALUES (return_word_id, lookup_token, src_word,
203 null, null, null, 0);
205 RETURN return_word_id;
211 CREATE OR REPLACE FUNCTION getorcreate_name_id(lookup_word TEXT)
216 RETURN getorcreate_name_id(lookup_word, '');
221 -- Normalize a string and lookup its word ids (partial words).
222 CREATE OR REPLACE FUNCTION addr_ids_from_name(lookup_word TEXT)
228 return_word_id INTEGER[];
232 words := string_to_array(make_standard_name(lookup_word), ' ');
233 IF array_upper(words, 1) IS NOT NULL THEN
234 FOR j IN 1..array_upper(words, 1) LOOP
235 IF (words[j] != '') THEN
236 SELECT array_agg(word_id) INTO word_ids
238 WHERE word_token = words[j] and class is null and type is null;
240 IF word_ids IS NULL THEN
241 id := nextval('seq_word');
242 INSERT INTO word VALUES (id, words[j], null, null, null, null, 0);
243 return_word_id := return_word_id || id;
245 return_word_id := array_merge(return_word_id, word_ids);
251 RETURN return_word_id;
257 -- Normalize a string and look up its name ids (full words).
258 CREATE OR REPLACE FUNCTION word_ids_from_name(lookup_word TEXT)
263 return_word_ids INTEGER[];
265 lookup_token := ' '|| make_standard_name(lookup_word);
266 SELECT array_agg(word_id) FROM word
267 WHERE word_token = lookup_token and class is null and type is null
268 INTO return_word_ids;
269 RETURN return_word_ids;
272 LANGUAGE plpgsql STABLE STRICT;
275 CREATE OR REPLACE FUNCTION create_country(src HSTORE, country_code varchar(2))
285 FOR item IN SELECT (each(src)).* LOOP
287 s := make_standard_name(item.value);
288 w := getorcreate_country(s, country_code);
290 words := regexp_split_to_array(item.value, E'[,;()]');
291 IF array_upper(words, 1) != 1 THEN
292 FOR j IN 1..array_upper(words, 1) LOOP
293 s := make_standard_name(words[j]);
295 w := getorcreate_country(s, country_code);
305 CREATE OR REPLACE FUNCTION make_keywords(src HSTORE)
316 result := '{}'::INTEGER[];
318 FOR item IN SELECT (each(src)).* LOOP
320 s := make_standard_name(item.value);
321 w := getorcreate_name_id(s, item.value);
323 IF not(ARRAY[w] <@ result) THEN
324 result := result || w;
327 w := getorcreate_word_id(s);
329 IF w IS NOT NULL AND NOT (ARRAY[w] <@ result) THEN
330 result := result || w;
333 words := string_to_array(s, ' ');
334 IF array_upper(words, 1) IS NOT NULL THEN
335 FOR j IN 1..array_upper(words, 1) LOOP
336 IF (words[j] != '') THEN
337 w = getorcreate_word_id(words[j]);
338 IF w IS NOT NULL AND NOT (ARRAY[w] <@ result) THEN
339 result := result || w;
345 words := regexp_split_to_array(item.value, E'[,;()]');
346 IF array_upper(words, 1) != 1 THEN
347 FOR j IN 1..array_upper(words, 1) LOOP
348 s := make_standard_name(words[j]);
350 w := getorcreate_word_id(s);
351 IF w IS NOT NULL AND NOT (ARRAY[w] <@ result) THEN
352 result := result || w;
358 s := regexp_replace(item.value, '市$', '');
359 IF s != item.value THEN
360 s := make_standard_name(s);
362 w := getorcreate_name_id(s, item.value);
363 IF NOT (ARRAY[w] <@ result) THEN
364 result := result || w;
377 CREATE OR REPLACE FUNCTION make_keywords(src TEXT)
388 result := '{}'::INTEGER[];
390 s := make_standard_name(src);
391 w := getorcreate_name_id(s, src);
393 IF NOT (ARRAY[w] <@ result) THEN
394 result := result || w;
397 w := getorcreate_word_id(s);
399 IF w IS NOT NULL AND NOT (ARRAY[w] <@ result) THEN
400 result := result || w;
403 words := string_to_array(s, ' ');
404 IF array_upper(words, 1) IS NOT NULL THEN
405 FOR j IN 1..array_upper(words, 1) LOOP
406 IF (words[j] != '') THEN
407 w = getorcreate_word_id(words[j]);
408 IF w IS NOT NULL AND NOT (ARRAY[w] <@ result) THEN
409 result := result || w;
415 words := regexp_split_to_array(src, E'[,;()]');
416 IF array_upper(words, 1) != 1 THEN
417 FOR j IN 1..array_upper(words, 1) LOOP
418 s := make_standard_name(words[j]);
420 w := getorcreate_word_id(s);
421 IF w IS NOT NULL AND NOT (ARRAY[w] <@ result) THEN
422 result := result || w;
428 s := regexp_replace(src, '市$', '');
430 s := make_standard_name(s);
432 w := getorcreate_name_id(s, src);
433 IF NOT (ARRAY[w] <@ result) THEN
434 result := result || w;
445 CREATE OR REPLACE FUNCTION create_poi_search_terms(obj_place_id BIGINT,
446 in_partition SMALLINT,
447 parent_place_id BIGINT,
451 initial_name_vector INTEGER[],
453 OUT name_vector INTEGER[],
454 OUT nameaddress_vector INTEGER[])
457 parent_name_vector INTEGER[];
458 parent_address_vector INTEGER[];
459 addr_place_ids INTEGER[];
462 parent_address_place_ids BIGINT[];
463 filtered_address HSTORE;
465 nameaddress_vector := '{}'::INTEGER[];
467 SELECT s.name_vector, s.nameaddress_vector
468 INTO parent_name_vector, parent_address_vector
470 WHERE s.place_id = parent_place_id;
472 -- Find all address tags that don't appear in the parent search names.
473 SELECT hstore(array_agg(ARRAY[k, v])) INTO filtered_address
474 FROM (SELECT skeys(address) as k, svals(address) as v) a
475 WHERE not addr_ids_from_name(v) && parent_address_vector
476 AND k not in ('country', 'street', 'place', 'postcode',
477 'housenumber', 'streetnumber', 'conscriptionnumber');
479 -- Compute all search terms from the addr: tags.
480 IF filtered_address IS NOT NULL THEN
483 get_places_for_addr_tags(in_partition, geometry, filtered_address, country)
485 IF addr_item.place_id is null THEN
486 nameaddress_vector := array_merge(nameaddress_vector,
491 IF parent_address_place_ids is null THEN
492 SELECT array_agg(parent_place_id) INTO parent_address_place_ids
493 FROM place_addressline
494 WHERE place_id = parent_place_id;
497 IF not parent_address_place_ids @> ARRAY[addr_item.place_id] THEN
498 nameaddress_vector := array_merge(nameaddress_vector,
501 INSERT INTO place_addressline (place_id, address_place_id, fromarea,
502 isaddress, distance, cached_rank_address)
503 VALUES (obj_place_id, addr_item.place_id, not addr_item.isguess,
504 true, addr_item.distance, addr_item.rank_address);
509 name_vector := initial_name_vector;
511 -- Check if the parent covers all address terms.
512 -- If not, create a search name entry with the house number as the name.
513 -- This is unusual for the search_name table but prevents that the place
514 -- is returned when we only search for the street/place.
516 IF housenumber is not null and not nameaddress_vector <@ parent_address_vector THEN
517 name_vector := array_merge(name_vector,
518 ARRAY[getorcreate_housenumber_id(make_standard_name(housenumber))]);
521 IF not address ? 'street' and address ? 'place' THEN
522 addr_place_ids := addr_ids_from_name(address->'place');
523 IF not addr_place_ids <@ parent_name_vector THEN
524 -- make sure addr:place terms are always searchable
525 nameaddress_vector := array_merge(nameaddress_vector, addr_place_ids);
526 -- If there is a housenumber, also add the place name as a name,
527 -- so we can search it by the usual housenumber+place algorithms.
528 IF housenumber is not null THEN
529 name_vector := array_merge(name_vector,
530 ARRAY[getorcreate_name_id(make_standard_name(address->'place'))]);
535 -- Cheating here by not recomputing all terms but simply using the ones
536 -- from the parent object.
537 nameaddress_vector := array_merge(nameaddress_vector, parent_name_vector);
538 nameaddress_vector := array_merge(nameaddress_vector, parent_address_vector);