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