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