]> git.openstreetmap.org Git - nominatim.git/blob - sql/functions/placex_triggers.sql
953c54302c8262d480053645949917c6f438354f
[nominatim.git] / sql / functions / placex_triggers.sql
1 -- Trigger functions for the placex table.
2
3 CREATE OR REPLACE FUNCTION placex_insert()
4   RETURNS TRIGGER
5   AS $$
6 DECLARE
7   i INTEGER;
8   postcode TEXT;
9   result BOOLEAN;
10   is_area BOOLEAN;
11   country_code VARCHAR(2);
12   default_language VARCHAR(10);
13   diameter FLOAT;
14   classtable TEXT;
15   classtype TEXT;
16 BEGIN
17   --DEBUG: RAISE WARNING '% % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type;
18
19   NEW.place_id := nextval('seq_place');
20   NEW.indexed_status := 1; --STATUS_NEW
21
22   NEW.country_code := lower(get_country_code(NEW.geometry));
23
24   NEW.partition := get_partition(NEW.country_code);
25   NEW.geometry_sector := geometry_sector(NEW.partition, NEW.geometry);
26
27   -- copy 'name' to or from the default language (if there is a default language)
28   IF NEW.name is not null AND array_upper(akeys(NEW.name),1) > 1 THEN
29     default_language := get_country_language_code(NEW.country_code);
30     IF default_language IS NOT NULL THEN
31       IF NEW.name ? 'name' AND NOT NEW.name ? ('name:'||default_language) THEN
32         NEW.name := NEW.name || hstore(('name:'||default_language), (NEW.name -> 'name'));
33       ELSEIF NEW.name ? ('name:'||default_language) AND NOT NEW.name ? 'name' THEN
34         NEW.name := NEW.name || hstore('name', (NEW.name -> ('name:'||default_language)));
35       END IF;
36     END IF;
37   END IF;
38
39   IF NEW.osm_type = 'X' THEN
40     -- E'X'ternal records should already be in the right format so do nothing
41   ELSE
42     is_area := ST_GeometryType(NEW.geometry) IN ('ST_Polygon','ST_MultiPolygon');
43
44     IF NEW.class in ('place','boundary')
45        AND NEW.type in ('postcode','postal_code') THEN
46
47       IF NEW.address IS NULL OR NOT NEW.address ? 'postcode' THEN
48           -- most likely just a part of a multipolygon postcode boundary, throw it away
49           RETURN NULL;
50       END IF;
51
52       NEW.name := hstore('ref', NEW.address->'postcode');
53
54       SELECT * FROM get_postcode_rank(NEW.country_code, NEW.address->'postcode')
55         INTO NEW.rank_search, NEW.rank_address;
56
57       IF NOT is_area THEN
58           NEW.rank_address := 0;
59       END IF;
60     ELSEIF NEW.class = 'boundary' AND NOT is_area THEN
61         return NULL;
62     ELSEIF NEW.class = 'boundary' AND NEW.type = 'administrative'
63            AND NEW.admin_level <= 4 AND NEW.osm_type = 'W' THEN
64         return NULL;
65     ELSEIF NEW.class = 'railway' AND NEW.type in ('rail') THEN
66         return NULL;
67     ELSEIF NEW.osm_type = 'N' AND NEW.class = 'highway' THEN
68         NEW.rank_search = 30;
69         NEW.rank_address = 0;
70     ELSEIF NEW.class = 'landuse' AND NOT is_area THEN
71         NEW.rank_search = 30;
72         NEW.rank_address = 0;
73     ELSE
74       -- do table lookup stuff
75       IF NEW.class = 'boundary' and NEW.type = 'administrative' THEN
76         classtype = NEW.type || NEW.admin_level::TEXT;
77       ELSE
78         classtype = NEW.type;
79       END IF;
80       SELECT l.rank_search, l.rank_address FROM address_levels l
81        WHERE (l.country_code = NEW.country_code or l.country_code is NULL)
82              AND l.class = NEW.class AND (l.type = classtype or l.type is NULL)
83        ORDER BY l.country_code, l.class, l.type LIMIT 1
84         INTO NEW.rank_search, NEW.rank_address;
85
86       IF NEW.rank_search is NULL THEN
87         NEW.rank_search := 30;
88       END IF;
89
90       IF NEW.rank_address is NULL THEN
91         NEW.rank_address := 30;
92       END IF;
93     END IF;
94
95     -- some postcorrections
96     IF NEW.class = 'waterway' AND NEW.osm_type = 'R' THEN
97         -- Slightly promote waterway relations so that they are processed
98         -- before their members.
99         NEW.rank_search := NEW.rank_search - 1;
100     END IF;
101
102     IF (NEW.extratags -> 'capital') = 'yes' THEN
103       NEW.rank_search := NEW.rank_search - 1;
104     END IF;
105
106   END IF;
107
108   -- a country code make no sense below rank 4 (country)
109   IF NEW.rank_search < 4 THEN
110     NEW.country_code := NULL;
111   END IF;
112
113   --DEBUG: RAISE WARNING 'placex_insert:END: % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type;
114
115   RETURN NEW; -- %DIFFUPDATES% The following is not needed until doing diff updates, and slows the main index process down
116
117   IF NEW.osm_type = 'N' and NEW.rank_search > 28 THEN
118       -- might be part of an interpolation
119       result := osmline_reinsert(NEW.osm_id, NEW.geometry);
120   ELSEIF NEW.rank_address > 0 THEN
121     IF (ST_GeometryType(NEW.geometry) in ('ST_Polygon','ST_MultiPolygon') AND ST_IsValid(NEW.geometry)) THEN
122       -- Performance: We just can't handle re-indexing for country level changes
123       IF st_area(NEW.geometry) < 1 THEN
124         -- mark items within the geometry for re-indexing
125   --    RAISE WARNING 'placex poly insert: % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type;
126
127         -- work around bug in postgis, this may have been fixed in 2.0.0 (see http://trac.osgeo.org/postgis/ticket/547)
128         update placex set indexed_status = 2 where (st_covers(NEW.geometry, placex.geometry) OR ST_Intersects(NEW.geometry, placex.geometry)) 
129          AND rank_search > NEW.rank_search and indexed_status = 0 and ST_geometrytype(placex.geometry) = 'ST_Point' and (rank_search < 28 or name is not null or (NEW.rank_search >= 16 and address ? 'place'));
130         update placex set indexed_status = 2 where (st_covers(NEW.geometry, placex.geometry) OR ST_Intersects(NEW.geometry, placex.geometry)) 
131          AND rank_search > NEW.rank_search and indexed_status = 0 and ST_geometrytype(placex.geometry) != 'ST_Point' and (rank_search < 28 or name is not null or (NEW.rank_search >= 16 and address ? 'place'));
132       END IF;
133     ELSE
134       -- mark nearby items for re-indexing, where 'nearby' depends on the features rank_search and is a complete guess :(
135       diameter := 0;
136       -- 16 = city, anything higher than city is effectively ignored (polygon required!)
137       IF NEW.type='postcode' THEN
138         diameter := 0.05;
139       ELSEIF NEW.rank_search < 16 THEN
140         diameter := 0;
141       ELSEIF NEW.rank_search < 18 THEN
142         diameter := 0.1;
143       ELSEIF NEW.rank_search < 20 THEN
144         diameter := 0.05;
145       ELSEIF NEW.rank_search = 21 THEN
146         diameter := 0.001;
147       ELSEIF NEW.rank_search < 24 THEN
148         diameter := 0.02;
149       ELSEIF NEW.rank_search < 26 THEN
150         diameter := 0.002; -- 100 to 200 meters
151       ELSEIF NEW.rank_search < 28 THEN
152         diameter := 0.001; -- 50 to 100 meters
153       END IF;
154       IF diameter > 0 THEN
155   --      RAISE WARNING 'placex point insert: % % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type,diameter;
156         IF NEW.rank_search >= 26 THEN
157           -- roads may cause reparenting for >27 rank places
158           update placex set indexed_status = 2 where indexed_status = 0 and rank_search > NEW.rank_search and ST_DWithin(placex.geometry, NEW.geometry, diameter);
159           -- reparenting also for OSM Interpolation Lines (and for Tiger?)
160           update location_property_osmline set indexed_status = 2 where indexed_status = 0 and ST_DWithin(location_property_osmline.linegeo, NEW.geometry, diameter);
161         ELSEIF NEW.rank_search >= 16 THEN
162           -- up to rank 16, street-less addresses may need reparenting
163           update placex set indexed_status = 2 where indexed_status = 0 and rank_search > NEW.rank_search and ST_DWithin(placex.geometry, NEW.geometry, diameter) and (rank_search < 28 or name is not null or address ? 'place');
164         ELSE
165           -- for all other places the search terms may change as well
166           update placex set indexed_status = 2 where indexed_status = 0 and rank_search > NEW.rank_search and ST_DWithin(placex.geometry, NEW.geometry, diameter) and (rank_search < 28 or name is not null);
167         END IF;
168       END IF;
169     END IF;
170   END IF;
171
172
173    -- add to tables for special search
174    -- Note: won't work on initial import because the classtype tables
175    -- do not yet exist. It won't hurt either.
176   classtable := 'place_classtype_' || NEW.class || '_' || NEW.type;
177   SELECT count(*)>0 FROM pg_tables WHERE tablename = classtable and schemaname = current_schema() INTO result;
178   IF result THEN
179     EXECUTE 'INSERT INTO ' || classtable::regclass || ' (place_id, centroid) VALUES ($1,$2)' 
180     USING NEW.place_id, ST_Centroid(NEW.geometry);
181   END IF;
182
183   RETURN NEW;
184
185 END;
186 $$
187 LANGUAGE plpgsql;
188
189
190 CREATE OR REPLACE FUNCTION placex_update()
191   RETURNS TRIGGER
192   AS $$
193 DECLARE
194
195   place_centroid GEOMETRY;
196   near_centroid GEOMETRY;
197
198   search_maxdistance FLOAT[];
199   search_mindistance FLOAT[];
200   address_havelevel BOOLEAN[];
201
202   i INTEGER;
203   iMax FLOAT;
204   location RECORD;
205   way RECORD;
206   relation RECORD;
207   relation_members TEXT[];
208   relMember RECORD;
209   linkedplacex RECORD;
210   addr_item RECORD;
211   search_diameter FLOAT;
212   search_prevdiameter FLOAT;
213   search_maxrank INTEGER;
214   address_maxrank INTEGER;
215   address_street_word_id INTEGER;
216   address_street_word_ids INTEGER[];
217   parent_place_id_rank BIGINT;
218
219   addr_street TEXT;
220   addr_place TEXT;
221
222   isin TEXT[];
223   isin_tokens INT[];
224
225   location_rank_search INTEGER;
226   location_distance FLOAT;
227   location_parent GEOMETRY;
228   location_isaddress BOOLEAN;
229   location_keywords INTEGER[];
230
231   default_language TEXT;
232   name_vector INTEGER[];
233   nameaddress_vector INTEGER[];
234
235   linked_node_id BIGINT;
236   linked_importance FLOAT;
237   linked_wikipedia TEXT;
238
239   result BOOLEAN;
240 BEGIN
241   -- deferred delete
242   IF OLD.indexed_status = 100 THEN
243     --DEBUG: RAISE WARNING 'placex_update delete % %',NEW.osm_type,NEW.osm_id;
244     delete from placex where place_id = OLD.place_id;
245     RETURN NULL;
246   END IF;
247
248   IF NEW.indexed_status != 0 OR OLD.indexed_status = 0 THEN
249     RETURN NEW;
250   END IF;
251
252   --DEBUG: RAISE WARNING 'placex_update % % (%)',NEW.osm_type,NEW.osm_id,NEW.place_id;
253
254   NEW.indexed_date = now();
255
256   IF NOT %REVERSE-ONLY% THEN
257     DELETE from search_name WHERE place_id = NEW.place_id;
258   END IF;
259   result := deleteSearchName(NEW.partition, NEW.place_id);
260   DELETE FROM place_addressline WHERE place_id = NEW.place_id;
261   result := deleteRoad(NEW.partition, NEW.place_id);
262   result := deleteLocationArea(NEW.partition, NEW.place_id, NEW.rank_search);
263   UPDATE placex set linked_place_id = null, indexed_status = 2
264          where linked_place_id = NEW.place_id;
265   -- update not necessary for osmline, cause linked_place_id does not exist
266
267   IF NEW.linked_place_id is not null THEN
268     --DEBUG: RAISE WARNING 'place already linked to %', NEW.linked_place_id;
269     RETURN NEW;
270   END IF;
271
272   --DEBUG: RAISE WARNING 'Copy over address tags';
273   -- housenumber is a computed field, so start with an empty value
274   NEW.housenumber := NULL;
275   IF NEW.address is not NULL THEN
276       IF NEW.address ? 'conscriptionnumber' THEN
277         i := getorcreate_housenumber_id(make_standard_name(NEW.address->'conscriptionnumber'));
278         IF NEW.address ? 'streetnumber' THEN
279             i := getorcreate_housenumber_id(make_standard_name(NEW.address->'streetnumber'));
280             NEW.housenumber := (NEW.address->'conscriptionnumber') || '/' || (NEW.address->'streetnumber');
281         ELSE
282             NEW.housenumber := NEW.address->'conscriptionnumber';
283         END IF;
284       ELSEIF NEW.address ? 'streetnumber' THEN
285         NEW.housenumber := NEW.address->'streetnumber';
286         i := getorcreate_housenumber_id(make_standard_name(NEW.address->'streetnumber'));
287       ELSEIF NEW.address ? 'housenumber' THEN
288         NEW.housenumber := NEW.address->'housenumber';
289         i := getorcreate_housenumber_id(make_standard_name(NEW.housenumber));
290       END IF;
291
292       addr_street := NEW.address->'street';
293       addr_place := NEW.address->'place';
294
295       IF NEW.address ? 'postcode' and NEW.address->'postcode' not similar to '%(,|;)%' THEN
296         i := getorcreate_postcode_id(NEW.address->'postcode');
297       END IF;
298   END IF;
299
300   -- Speed up searches - just use the centroid of the feature
301   -- cheaper but less acurate
302   place_centroid := ST_PointOnSurface(NEW.geometry);
303   -- For searching near features rather use the centroid
304   near_centroid := ST_Envelope(NEW.geometry);
305   NEW.centroid := null;
306   NEW.postcode := null;
307   --DEBUG: RAISE WARNING 'Computing preliminary centroid at %',ST_AsText(place_centroid);
308
309   -- recalculate country and partition
310   IF NEW.rank_search = 4 AND NEW.address is not NULL AND NEW.address ? 'country' THEN
311     -- for countries, believe the mapped country code,
312     -- so that we remain in the right partition if the boundaries
313     -- suddenly expand.
314     NEW.country_code := lower(NEW.address->'country');
315     NEW.partition := get_partition(lower(NEW.country_code));
316     IF NEW.partition = 0 THEN
317       NEW.country_code := lower(get_country_code(place_centroid));
318       NEW.partition := get_partition(NEW.country_code);
319     END IF;
320   ELSE
321     IF NEW.rank_search >= 4 THEN
322       NEW.country_code := lower(get_country_code(place_centroid));
323     ELSE
324       NEW.country_code := NULL;
325     END IF;
326     NEW.partition := get_partition(NEW.country_code);
327   END IF;
328   --DEBUG: RAISE WARNING 'Country updated: "%"', NEW.country_code;
329
330   -- waterway ways are linked when they are part of a relation and have the same class/type
331   IF NEW.osm_type = 'R' and NEW.class = 'waterway' THEN
332       FOR relation_members IN select members from planet_osm_rels r where r.id = NEW.osm_id and r.parts != array[]::bigint[]
333       LOOP
334           FOR i IN 1..array_upper(relation_members, 1) BY 2 LOOP
335               IF relation_members[i+1] in ('', 'main_stream', 'side_stream') AND substring(relation_members[i],1,1) = 'w' THEN
336                 --DEBUG: RAISE WARNING 'waterway parent %, child %/%', NEW.osm_id, i, relation_members[i];
337                 FOR linked_node_id IN SELECT place_id FROM placex
338                   WHERE osm_type = 'W' and osm_id = substring(relation_members[i],2,200)::bigint
339                   and class = NEW.class and type in ('river', 'stream', 'canal', 'drain', 'ditch')
340                   and ( relation_members[i+1] != 'side_stream' or NEW.name->'name' = name->'name')
341                 LOOP
342                   UPDATE placex SET linked_place_id = NEW.place_id WHERE place_id = linked_node_id;
343                 END LOOP;
344               END IF;
345           END LOOP;
346       END LOOP;
347       --DEBUG: RAISE WARNING 'Waterway processed';
348   END IF;
349
350   -- What level are we searching from
351   search_maxrank := NEW.rank_search;
352
353   -- Thought this wasn't needed but when we add new languages to the country_name table
354   -- we need to update the existing names
355   IF NEW.name is not null AND array_upper(akeys(NEW.name),1) > 1 THEN
356     default_language := get_country_language_code(NEW.country_code);
357     IF default_language IS NOT NULL THEN
358       IF NEW.name ? 'name' AND NOT NEW.name ? ('name:'||default_language) THEN
359         NEW.name := NEW.name || hstore(('name:'||default_language), (NEW.name -> 'name'));
360       ELSEIF NEW.name ? ('name:'||default_language) AND NOT NEW.name ? 'name' THEN
361         NEW.name := NEW.name || hstore('name', (NEW.name -> ('name:'||default_language)));
362       END IF;
363     END IF;
364   END IF;
365   --DEBUG: RAISE WARNING 'Local names updated';
366
367   -- Initialise the name vector using our name
368   name_vector := make_keywords(NEW.name);
369   nameaddress_vector := '{}'::int[];
370
371   FOR i IN 1..28 LOOP
372     address_havelevel[i] := false;
373   END LOOP;
374
375   NEW.importance := null;
376   SELECT wikipedia, importance
377     FROM compute_importance(NEW.extratags, NEW.country_code, NEW.osm_type, NEW.osm_id)
378     INTO NEW.wikipedia,NEW.importance;
379
380 --DEBUG: RAISE WARNING 'Importance computed from wikipedia: %', NEW.importance;
381
382   -- ---------------------------------------------------------------------------
383   -- For low level elements we inherit from our parent road
384   IF (NEW.rank_search > 27 OR (NEW.type = 'postcode' AND NEW.rank_search = 25)) THEN
385
386     --DEBUG: RAISE WARNING 'finding street for % %', NEW.osm_type, NEW.osm_id;
387
388     -- We won't get a better centroid, besides these places are too small to care
389     NEW.centroid := place_centroid;
390
391     NEW.parent_place_id := null;
392
393     -- if we have a POI and there is no address information,
394     -- see if we can get it from a surrounding building
395     IF NEW.osm_type = 'N' AND addr_street IS NULL AND addr_place IS NULL
396        AND NEW.housenumber IS NULL THEN
397       FOR location IN select address from placex where ST_Covers(geometry, place_centroid)
398             and address is not null
399             and (address ? 'housenumber' or address ? 'street' or address ? 'place')
400             and rank_search > 28 AND ST_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon')
401             limit 1
402       LOOP
403         NEW.housenumber := location.address->'housenumber';
404         addr_street := location.address->'street';
405         addr_place := location.address->'place';
406         --DEBUG: RAISE WARNING 'Found surrounding building % %', location.osm_type, location.osm_id;
407       END LOOP;
408     END IF;
409
410     -- We have to find our parent road.
411     -- Copy data from linked items (points on ways, addr:street links, relations)
412
413     -- Is this object part of a relation?
414     FOR relation IN select * from planet_osm_rels where parts @> ARRAY[NEW.osm_id] and members @> ARRAY[lower(NEW.osm_type)||NEW.osm_id]
415     LOOP
416       -- At the moment we only process one type of relation - associatedStreet
417       IF relation.tags @> ARRAY['associatedStreet'] THEN
418         FOR i IN 1..array_upper(relation.members, 1) BY 2 LOOP
419           IF NEW.parent_place_id IS NULL AND relation.members[i+1] = 'street' THEN
420 --RAISE WARNING 'node in relation %',relation;
421             SELECT place_id from placex where osm_type = 'W'
422               and osm_id = substring(relation.members[i],2,200)::bigint
423               and rank_search = 26 and name is not null INTO NEW.parent_place_id;
424           END IF;
425         END LOOP;
426       END IF;
427     END LOOP;
428     --DEBUG: RAISE WARNING 'Checked for street relation (%)', NEW.parent_place_id;
429
430     -- Note that addr:street links can only be indexed once the street itself is indexed
431     IF NEW.parent_place_id IS NULL AND addr_street IS NOT NULL THEN
432       address_street_word_ids := get_name_ids(make_standard_name(addr_street));
433       IF address_street_word_ids IS NOT NULL THEN
434         SELECT place_id from getNearestNamedRoadFeature(NEW.partition, near_centroid, address_street_word_ids) INTO NEW.parent_place_id;
435       END IF;
436     END IF;
437     --DEBUG: RAISE WARNING 'Checked for addr:street (%)', NEW.parent_place_id;
438
439     IF NEW.parent_place_id IS NULL AND addr_place IS NOT NULL THEN
440       address_street_word_ids := get_name_ids(make_standard_name(addr_place));
441       IF address_street_word_ids IS NOT NULL THEN
442         SELECT place_id from getNearestNamedPlaceFeature(NEW.partition, near_centroid, address_street_word_ids) INTO NEW.parent_place_id;
443       END IF;
444     END IF;
445     --DEBUG: RAISE WARNING 'Checked for addr:place (%)', NEW.parent_place_id;
446
447     -- Is this node part of an interpolation?
448     IF NEW.parent_place_id IS NULL AND NEW.osm_type = 'N' THEN
449       SELECT q.parent_place_id FROM location_property_osmline q, planet_osm_ways x
450         WHERE q.linegeo && NEW.geometry and x.id = q.osm_id and NEW.osm_id = any(x.nodes)
451         LIMIT 1 INTO NEW.parent_place_id;
452     END IF;
453     --DEBUG: RAISE WARNING 'Checked for interpolation (%)', NEW.parent_place_id;
454
455     -- Is this node part of a way?
456     IF NEW.parent_place_id IS NULL AND NEW.osm_type = 'N' THEN
457
458       FOR location IN
459         SELECT p.place_id, p.osm_id, p.rank_search, p.address from placex p, planet_osm_ways w
460          WHERE p.osm_type = 'W' and p.rank_search >= 26 and p.geometry && NEW.geometry and w.id = p.osm_id and NEW.osm_id = any(w.nodes)
461       LOOP
462         --DEBUG: RAISE WARNING 'Node is part of way % ', location.osm_id;
463
464         -- Way IS a road then we are on it - that must be our road
465         IF location.rank_search < 28 THEN
466 --RAISE WARNING 'node in way that is a street %',location;
467           NEW.parent_place_id := location.place_id;
468           EXIT;
469         END IF;
470         --DEBUG: RAISE WARNING 'Checked if way is street (%)', NEW.parent_place_id;
471
472         -- If the way mentions a street or place address, try that for parenting.
473         IF location.address is not null THEN
474           IF location.address ? 'street' THEN
475             address_street_word_ids := get_name_ids(make_standard_name(location.address->'street'));
476             IF address_street_word_ids IS NOT NULL THEN
477               SELECT place_id from getNearestNamedRoadFeature(NEW.partition, near_centroid, address_street_word_ids) INTO NEW.parent_place_id;
478               EXIT WHEN NEW.parent_place_id is not NULL;
479             END IF;
480           END IF;
481           --DEBUG: RAISE WARNING 'Checked for addr:street in way (%)', NEW.parent_place_id;
482
483           IF location.address ? 'place' THEN
484             address_street_word_ids := get_name_ids(make_standard_name(location.address->'place'));
485             IF address_street_word_ids IS NOT NULL THEN
486               SELECT place_id from getNearestNamedPlaceFeature(NEW.partition, near_centroid, address_street_word_ids) INTO NEW.parent_place_id;
487               EXIT WHEN NEW.parent_place_id is not NULL;
488             END IF;
489           END IF;
490         --DEBUG: RAISE WARNING 'Checked for addr:place in way (%)', NEW.parent_place_id;
491         END IF;
492
493         -- Is the WAY part of a relation
494         FOR relation IN select * from planet_osm_rels where parts @> ARRAY[location.osm_id] and members @> ARRAY['w'||location.osm_id]
495         LOOP
496           -- At the moment we only process one type of relation - associatedStreet
497           IF relation.tags @> ARRAY['associatedStreet'] AND array_upper(relation.members, 1) IS NOT NULL THEN
498             FOR i IN 1..array_upper(relation.members, 1) BY 2 LOOP
499               IF NEW.parent_place_id IS NULL AND relation.members[i+1] = 'street' THEN
500 --RAISE WARNING 'node in way that is in a relation %',relation;
501                 SELECT place_id from placex where osm_type='W' and osm_id = substring(relation.members[i],2,200)::bigint 
502                   and rank_search = 26 and name is not null INTO NEW.parent_place_id;
503               END IF;
504             END LOOP;
505           END IF;
506         END LOOP;
507         EXIT WHEN NEW.parent_place_id is not null;
508         --DEBUG: RAISE WARNING 'Checked for street relation in way (%)', NEW.parent_place_id;
509
510       END LOOP;
511     END IF;
512
513     -- Still nothing, just use the nearest road
514     IF NEW.parent_place_id IS NULL THEN
515       SELECT place_id FROM getNearestRoadFeature(NEW.partition, near_centroid) INTO NEW.parent_place_id;
516     END IF;
517     --DEBUG: RAISE WARNING 'Checked for nearest way (%)', NEW.parent_place_id;
518
519
520     -- If we didn't find any road fallback to standard method
521     IF NEW.parent_place_id IS NOT NULL THEN
522
523       -- Get the details of the parent road
524       SELECT p.country_code, p.postcode FROM placex p
525        WHERE p.place_id = NEW.parent_place_id INTO location;
526
527       NEW.country_code := location.country_code;
528       --DEBUG: RAISE WARNING 'Got parent details from search name';
529
530       -- determine postcode
531       IF NEW.rank_search > 4 THEN
532           IF NEW.address is not null AND NEW.address ? 'postcode' THEN
533               NEW.postcode = upper(trim(NEW.address->'postcode'));
534           ELSE
535              NEW.postcode := location.postcode;
536           END IF;
537           IF NEW.postcode is null THEN
538             NEW.postcode := get_nearest_postcode(NEW.country_code, NEW.geometry);
539           END IF;
540       END IF;
541
542       -- If there is no name it isn't searchable, don't bother to create a search record
543       IF NEW.name is NULL THEN
544         --DEBUG: RAISE WARNING 'Not a searchable place % %', NEW.osm_type, NEW.osm_id;
545         return NEW;
546       END IF;
547
548       -- Performance, it would be more acurate to do all the rest of the import
549       -- process but it takes too long
550       -- Just be happy with inheriting from parent road only
551       IF NEW.rank_search <= 25 and NEW.rank_address > 0 THEN
552         result := add_location(NEW.place_id, NEW.country_code, NEW.partition, name_vector, NEW.rank_search, NEW.rank_address, upper(trim(NEW.address->'postcode')), NEW.geometry);
553         --DEBUG: RAISE WARNING 'Place added to location table';
554       END IF;
555
556       result := insertSearchName(NEW.partition, NEW.place_id, name_vector,
557                                  NEW.rank_search, NEW.rank_address, NEW.geometry);
558
559       IF NOT %REVERSE-ONLY% THEN
560           -- Merge address from parent
561           SELECT s.name_vector, s.nameaddress_vector FROM search_name s
562            WHERE s.place_id = NEW.parent_place_id INTO location;
563
564           nameaddress_vector := array_merge(nameaddress_vector,
565                                             location.nameaddress_vector);
566           nameaddress_vector := array_merge(nameaddress_vector, location.name_vector);
567
568           INSERT INTO search_name (place_id, search_rank, address_rank,
569                                    importance, country_code, name_vector,
570                                    nameaddress_vector, centroid)
571                  VALUES (NEW.place_id, NEW.rank_search, NEW.rank_address,
572                          NEW.importance, NEW.country_code, name_vector,
573                          nameaddress_vector, place_centroid);
574           --DEBUG: RAISE WARNING 'Place added to search table';
575         END IF;
576
577       return NEW;
578     END IF;
579
580   END IF;
581
582   -- ---------------------------------------------------------------------------
583   -- Full indexing
584   --DEBUG: RAISE WARNING 'Using full index mode for % %', NEW.osm_type, NEW.osm_id;
585
586   IF NEW.osm_type = 'R' AND NEW.rank_search < 26 THEN
587
588     -- see if we have any special relation members
589     select members from planet_osm_rels where id = NEW.osm_id INTO relation_members;
590     --DEBUG: RAISE WARNING 'Got relation members';
591
592     IF relation_members IS NOT NULL THEN
593       FOR relMember IN select get_osm_rel_members(relation_members,ARRAY['label']) as member LOOP
594         --DEBUG: RAISE WARNING 'Found label member %', relMember.member;
595
596         FOR linkedPlacex IN select * from placex where osm_type = upper(substring(relMember.member,1,1))::char(1) 
597           and osm_id = substring(relMember.member,2,10000)::bigint
598           and class = 'place' order by rank_search desc limit 1 LOOP
599
600           -- If we don't already have one use this as the centre point of the geometry
601           IF NEW.centroid IS NULL THEN
602             NEW.centroid := coalesce(linkedPlacex.centroid,st_centroid(linkedPlacex.geometry));
603           END IF;
604
605           -- merge in the label name, re-init word vector
606           IF NOT linkedPlacex.name IS NULL THEN
607             NEW.name := linkedPlacex.name || NEW.name;
608             name_vector := array_merge(name_vector, make_keywords(linkedPlacex.name));
609           END IF;
610
611           -- merge in extra tags
612           NEW.extratags := hstore(linkedPlacex.class, linkedPlacex.type) || coalesce(linkedPlacex.extratags, ''::hstore) || coalesce(NEW.extratags, ''::hstore);
613
614           -- mark the linked place (excludes from search results)
615           UPDATE placex set linked_place_id = NEW.place_id where place_id = linkedPlacex.place_id;
616
617           select wikipedia, importance
618             FROM compute_importance(linkedPlacex.extratags, NEW.country_code,
619                                     'N', linkedPlacex.osm_id)
620             INTO linked_wikipedia,linked_importance;
621           --DEBUG: RAISE WARNING 'Linked label member';
622         END LOOP;
623
624       END LOOP;
625
626       IF NEW.centroid IS NULL THEN
627
628         FOR relMember IN select get_osm_rel_members(relation_members,ARRAY['admin_center','admin_centre']) as member LOOP
629           --DEBUG: RAISE WARNING 'Found admin_center member %', relMember.member;
630
631           FOR linkedPlacex IN select * from placex where osm_type = upper(substring(relMember.member,1,1))::char(1) 
632             and osm_id = substring(relMember.member,2,10000)::bigint
633             and class = 'place' order by rank_search desc limit 1 LOOP
634
635             -- For an admin centre we also want a name match - still not perfect, for example 'new york, new york'
636             -- But that can be fixed by explicitly setting the label in the data
637             IF make_standard_name(NEW.name->'name') = make_standard_name(linkedPlacex.name->'name') 
638               AND NEW.rank_address = linkedPlacex.rank_address THEN
639
640               -- If we don't already have one use this as the centre point of the geometry
641               IF NEW.centroid IS NULL THEN
642                 NEW.centroid := coalesce(linkedPlacex.centroid,st_centroid(linkedPlacex.geometry));
643               END IF;
644
645               -- merge in the name, re-init word vector
646               IF NOT linkedPlacex.name IS NULL THEN
647                 NEW.name := linkedPlacex.name || NEW.name;
648                 name_vector := make_keywords(NEW.name);
649               END IF;
650
651               -- merge in extra tags
652               NEW.extratags := hstore(linkedPlacex.class, linkedPlacex.type) || coalesce(linkedPlacex.extratags, ''::hstore) || coalesce(NEW.extratags, ''::hstore);
653
654               -- mark the linked place (excludes from search results)
655               UPDATE placex set linked_place_id = NEW.place_id where place_id = linkedPlacex.place_id;
656
657               select wikipedia, importance
658                 FROM compute_importance(linkedPlacex.extratags, NEW.country_code,
659                                         'N', linkedPlacex.osm_id)
660                 INTO linked_wikipedia,linked_importance;
661               --DEBUG: RAISE WARNING 'Linked admin_center';
662             END IF;
663
664           END LOOP;
665
666         END LOOP;
667
668       END IF;
669     END IF;
670
671   END IF;
672
673   -- Name searches can be done for ways as well as relations
674   IF NEW.osm_type in ('W','R') AND NEW.rank_search < 26 AND NEW.rank_address > 0 THEN
675
676     -- not found one yet? how about doing a name search
677     IF NEW.centroid IS NULL AND (NEW.name->'name') is not null and make_standard_name(NEW.name->'name') != '' THEN
678
679       --DEBUG: RAISE WARNING 'Looking for nodes with matching names';
680       FOR linkedPlacex IN select placex.* from placex WHERE
681         make_standard_name(name->'name') = make_standard_name(NEW.name->'name')
682         AND placex.rank_address = NEW.rank_address
683         AND placex.place_id != NEW.place_id
684         AND placex.osm_type = 'N'::char(1) AND placex.rank_search < 26
685         AND st_covers(NEW.geometry, placex.geometry)
686       LOOP
687         --DEBUG: RAISE WARNING 'Found matching place node %', linkedPlacex.osm_id;
688         -- If we don't already have one use this as the centre point of the geometry
689         IF NEW.centroid IS NULL THEN
690           NEW.centroid := coalesce(linkedPlacex.centroid,st_centroid(linkedPlacex.geometry));
691         END IF;
692
693         -- merge in the name, re-init word vector
694         NEW.name := linkedPlacex.name || NEW.name;
695         name_vector := make_keywords(NEW.name);
696
697         -- merge in extra tags
698         NEW.extratags := hstore(linkedPlacex.class, linkedPlacex.type) || coalesce(linkedPlacex.extratags, ''::hstore) || coalesce(NEW.extratags, ''::hstore);
699
700         -- mark the linked place (excludes from search results)
701         UPDATE placex set linked_place_id = NEW.place_id where place_id = linkedPlacex.place_id;
702
703         select wikipedia, importance
704           FROM compute_importance(linkedPlacex.extratags, NEW.country_code,
705                                   'N', linkedPlacex.osm_id)
706           INTO linked_wikipedia,linked_importance;
707         --DEBUG: RAISE WARNING 'Linked named place';
708       END LOOP;
709     END IF;
710
711     IF NEW.centroid IS NOT NULL THEN
712       place_centroid := NEW.centroid;
713       -- Place might have had only a name tag before but has now received translations
714       -- from the linked place. Make sure a name tag for the default language exists in
715       -- this case. 
716       IF NEW.name is not null AND array_upper(akeys(NEW.name),1) > 1 THEN
717         default_language := get_country_language_code(NEW.country_code);
718         IF default_language IS NOT NULL THEN
719           IF NEW.name ? 'name' AND NOT NEW.name ? ('name:'||default_language) THEN
720             NEW.name := NEW.name || hstore(('name:'||default_language), (NEW.name -> 'name'));
721           ELSEIF NEW.name ? ('name:'||default_language) AND NOT NEW.name ? 'name' THEN
722             NEW.name := NEW.name || hstore('name', (NEW.name -> ('name:'||default_language)));
723           END IF;
724         END IF;
725       END IF;
726       --DEBUG: RAISE WARNING 'Names updated from linked places';
727     END IF;
728
729     -- Use the maximum importance if a one could be computed from the linked object.
730     IF linked_importance is not null AND
731         (NEW.importance is null or NEW.importance < linked_importance) THEN
732         NEW.importance = linked_importance;
733     END IF;
734   END IF;
735
736   -- make sure all names are in the word table
737   IF NEW.admin_level = 2 AND NEW.class = 'boundary' AND NEW.type = 'administrative' AND NEW.country_code IS NOT NULL AND NEW.osm_type = 'R' THEN
738     perform create_country(NEW.name, lower(NEW.country_code));
739     --DEBUG: RAISE WARNING 'Country names updated';
740   END IF;
741
742   NEW.parent_place_id = 0;
743   parent_place_id_rank = 0;
744
745
746   -- convert address store to array of tokenids
747   --DEBUG: RAISE WARNING 'Starting address search';
748   isin_tokens := '{}'::int[];
749   IF NEW.address IS NOT NULL THEN
750     FOR addr_item IN SELECT * FROM each(NEW.address)
751     LOOP
752       IF addr_item.key IN ('city', 'tiger:county', 'state', 'suburb', 'province', 'district', 'region', 'county', 'municipality', 'hamlet', 'village', 'subdistrict', 'town', 'neighbourhood', 'quarter', 'parish') THEN
753         address_street_word_id := get_name_id(make_standard_name(addr_item.value));
754         IF address_street_word_id IS NOT NULL AND NOT(ARRAY[address_street_word_id] <@ isin_tokens) THEN
755           isin_tokens := isin_tokens || address_street_word_id;
756         END IF;
757         IF NOT %REVERSE-ONLY% THEN
758           address_street_word_id := get_word_id(make_standard_name(addr_item.value));
759           IF address_street_word_id IS NOT NULL THEN
760             nameaddress_vector := array_merge(nameaddress_vector, ARRAY[address_street_word_id]);
761           END IF;
762         END IF;
763       END IF;
764       IF addr_item.key = 'is_in' THEN
765         -- is_in items need splitting
766         isin := regexp_split_to_array(addr_item.value, E'[;,]');
767         IF array_upper(isin, 1) IS NOT NULL THEN
768           FOR i IN 1..array_upper(isin, 1) LOOP
769             address_street_word_id := get_name_id(make_standard_name(isin[i]));
770             IF address_street_word_id IS NOT NULL AND NOT(ARRAY[address_street_word_id] <@ isin_tokens) THEN
771               isin_tokens := isin_tokens || address_street_word_id;
772             END IF;
773
774             -- merge word into address vector
775             IF NOT %REVERSE-ONLY% THEN
776               address_street_word_id := get_word_id(make_standard_name(isin[i]));
777               IF address_street_word_id IS NOT NULL THEN
778                 nameaddress_vector := array_merge(nameaddress_vector, ARRAY[address_street_word_id]);
779               END IF;
780             END IF;
781           END LOOP;
782         END IF;
783       END IF;
784     END LOOP;
785   END IF;
786   IF NOT %REVERSE-ONLY% THEN
787     nameaddress_vector := array_merge(nameaddress_vector, isin_tokens);
788   END IF;
789
790 -- RAISE WARNING 'ISIN: %', isin_tokens;
791
792   -- Process area matches
793   location_rank_search := 0;
794   location_distance := 0;
795   location_parent := NULL;
796   -- added ourself as address already
797   address_havelevel[NEW.rank_address] := true;
798   --DEBUG: RAISE WARNING '  getNearFeatures(%,''%'',%,''%'')',NEW.partition, place_centroid, search_maxrank, isin_tokens;
799   FOR location IN
800     SELECT * from getNearFeatures(NEW.partition,
801                                   CASE WHEN NEW.rank_search >= 26
802                                              AND NEW.rank_search < 30
803                                        THEN NEW.geometry
804                                        ELSE place_centroid END,
805                                   search_maxrank, isin_tokens)
806   LOOP
807     IF location.rank_address != location_rank_search THEN
808       location_rank_search := location.rank_address;
809       IF location.isguess THEN
810         location_distance := location.distance * 1.5;
811       ELSE
812         IF location.rank_address <= 12 THEN
813           -- for county and above, if we have an area consider that exact
814           -- (It would be nice to relax the constraint for places close to
815           --  the boundary but we'd need the exact geometry for that. Too
816           --  expensive.)
817           location_distance = 0;
818         ELSE
819           -- Below county level remain slightly fuzzy.
820           location_distance := location.distance * 0.5;
821         END IF;
822       END IF;
823     ELSE
824       CONTINUE WHEN location.keywords <@ location_keywords;
825     END IF;
826
827     IF location.distance < location_distance OR NOT location.isguess THEN
828       location_keywords := location.keywords;
829
830       location_isaddress := NOT address_havelevel[location.rank_address];
831       IF location_isaddress AND location.isguess AND location_parent IS NOT NULL THEN
832           location_isaddress := ST_Contains(location_parent,location.centroid);
833       END IF;
834
835       -- RAISE WARNING '% isaddress: %', location.place_id, location_isaddress;
836       -- Add it to the list of search terms
837       IF NOT %REVERSE-ONLY% THEN
838           nameaddress_vector := array_merge(nameaddress_vector, location.keywords::integer[]);
839       END IF;
840       INSERT INTO place_addressline (place_id, address_place_id, fromarea, isaddress, distance, cached_rank_address)
841         VALUES (NEW.place_id, location.place_id, true, location_isaddress, location.distance, location.rank_address);
842
843       IF location_isaddress THEN
844         -- add postcode if we have one
845         -- (If multiple postcodes are available, we end up with the highest ranking one.)
846         IF location.postcode is not null THEN
847             NEW.postcode = location.postcode;
848         END IF;
849
850         address_havelevel[location.rank_address] := true;
851         IF NOT location.isguess THEN
852           SELECT geometry FROM placex WHERE place_id = location.place_id INTO location_parent;
853         END IF;
854
855         IF location.rank_address > parent_place_id_rank THEN
856           NEW.parent_place_id = location.place_id;
857           parent_place_id_rank = location.rank_address;
858         END IF;
859
860       END IF;
861
862     --DEBUG: RAISE WARNING '  Terms: (%) %',location, nameaddress_vector;
863
864     END IF;
865
866   END LOOP;
867   --DEBUG: RAISE WARNING 'address computed';
868
869   IF NEW.address is not null AND NEW.address ? 'postcode' 
870      AND NEW.address->'postcode' not similar to '%(,|;)%' THEN
871     NEW.postcode := upper(trim(NEW.address->'postcode'));
872   END IF;
873
874   IF NEW.postcode is null AND NEW.rank_search > 8 THEN
875     NEW.postcode := get_nearest_postcode(NEW.country_code, NEW.geometry);
876   END IF;
877
878   -- if we have a name add this to the name search table
879   IF NEW.name IS NOT NULL THEN
880
881     IF NEW.rank_search <= 25 and NEW.rank_address > 0 THEN
882       result := add_location(NEW.place_id, NEW.country_code, NEW.partition, name_vector, NEW.rank_search, NEW.rank_address, upper(trim(NEW.address->'postcode')), NEW.geometry);
883       --DEBUG: RAISE WARNING 'added to location (full)';
884     END IF;
885
886     IF NEW.rank_search between 26 and 27 and NEW.class = 'highway' THEN
887       result := insertLocationRoad(NEW.partition, NEW.place_id, NEW.country_code, NEW.geometry);
888       --DEBUG: RAISE WARNING 'insert into road location table (full)';
889     END IF;
890
891     result := insertSearchName(NEW.partition, NEW.place_id, name_vector,
892                                NEW.rank_search, NEW.rank_address, NEW.geometry);
893     --DEBUG: RAISE WARNING 'added to search name (full)';
894
895     IF NOT %REVERSE-ONLY% THEN
896         INSERT INTO search_name (place_id, search_rank, address_rank,
897                                  importance, country_code, name_vector,
898                                  nameaddress_vector, centroid)
899                VALUES (NEW.place_id, NEW.rank_search, NEW.rank_address,
900                        NEW.importance, NEW.country_code, name_vector,
901                        nameaddress_vector, place_centroid);
902     END IF;
903
904   END IF;
905
906   -- If we've not managed to pick up a better one - default centroid
907   IF NEW.centroid IS NULL THEN
908     NEW.centroid := place_centroid;
909   END IF;
910
911   --DEBUG: RAISE WARNING 'place update % % finsihed.', NEW.osm_type, NEW.osm_id;
912
913   RETURN NEW;
914 END;
915 $$
916 LANGUAGE plpgsql;
917
918
919 CREATE OR REPLACE FUNCTION placex_delete()
920   RETURNS TRIGGER
921   AS $$
922 DECLARE
923   b BOOLEAN;
924   classtable TEXT;
925 BEGIN
926   -- RAISE WARNING 'placex_delete % %',OLD.osm_type,OLD.osm_id;
927
928   update placex set linked_place_id = null, indexed_status = 2 where linked_place_id = OLD.place_id and indexed_status = 0;
929   --DEBUG: RAISE WARNING 'placex_delete:01 % %',OLD.osm_type,OLD.osm_id;
930   update placex set linked_place_id = null where linked_place_id = OLD.place_id;
931   --DEBUG: RAISE WARNING 'placex_delete:02 % %',OLD.osm_type,OLD.osm_id;
932
933   IF OLD.rank_address < 30 THEN
934
935     -- mark everything linked to this place for re-indexing
936     --DEBUG: RAISE WARNING 'placex_delete:03 % %',OLD.osm_type,OLD.osm_id;
937     UPDATE placex set indexed_status = 2 from place_addressline where address_place_id = OLD.place_id 
938       and placex.place_id = place_addressline.place_id and indexed_status = 0 and place_addressline.isaddress;
939
940     --DEBUG: RAISE WARNING 'placex_delete:04 % %',OLD.osm_type,OLD.osm_id;
941     DELETE FROM place_addressline where address_place_id = OLD.place_id;
942
943     --DEBUG: RAISE WARNING 'placex_delete:05 % %',OLD.osm_type,OLD.osm_id;
944     b := deleteRoad(OLD.partition, OLD.place_id);
945
946     --DEBUG: RAISE WARNING 'placex_delete:06 % %',OLD.osm_type,OLD.osm_id;
947     update placex set indexed_status = 2 where parent_place_id = OLD.place_id and indexed_status = 0;
948     --DEBUG: RAISE WARNING 'placex_delete:07 % %',OLD.osm_type,OLD.osm_id;
949     -- reparenting also for OSM Interpolation Lines (and for Tiger?)
950     update location_property_osmline set indexed_status = 2 where indexed_status = 0 and parent_place_id = OLD.place_id;
951
952   END IF;
953
954   --DEBUG: RAISE WARNING 'placex_delete:08 % %',OLD.osm_type,OLD.osm_id;
955
956   IF OLD.rank_address < 26 THEN
957     b := deleteLocationArea(OLD.partition, OLD.place_id, OLD.rank_search);
958   END IF;
959
960   --DEBUG: RAISE WARNING 'placex_delete:09 % %',OLD.osm_type,OLD.osm_id;
961
962   IF OLD.name is not null THEN
963     IF NOT %REVERSE-ONLY% THEN
964       DELETE from search_name WHERE place_id = OLD.place_id;
965     END IF;
966     b := deleteSearchName(OLD.partition, OLD.place_id);
967   END IF;
968
969   --DEBUG: RAISE WARNING 'placex_delete:10 % %',OLD.osm_type,OLD.osm_id;
970
971   DELETE FROM place_addressline where place_id = OLD.place_id;
972
973   --DEBUG: RAISE WARNING 'placex_delete:11 % %',OLD.osm_type,OLD.osm_id;
974
975   -- remove from tables for special search
976   classtable := 'place_classtype_' || OLD.class || '_' || OLD.type;
977   SELECT count(*)>0 FROM pg_tables WHERE tablename = classtable and schemaname = current_schema() INTO b;
978   IF b THEN
979     EXECUTE 'DELETE FROM ' || classtable::regclass || ' WHERE place_id = $1' USING OLD.place_id;
980   END IF;
981
982   --DEBUG: RAISE WARNING 'placex_delete:12 % %',OLD.osm_type,OLD.osm_id;
983
984   RETURN OLD;
985
986 END;
987 $$
988 LANGUAGE plpgsql;