]> git.openstreetmap.org Git - nominatim.git/blob - sql/functions.sql
move place triggers into own file
[nominatim.git] / sql / functions.sql
1 CREATE OR REPLACE FUNCTION geometry_sector(partition INTEGER, place geometry) RETURNS INTEGER
2   AS $$
3 DECLARE
4   NEWgeometry geometry;
5 BEGIN
6 --  RAISE WARNING '%',place;
7   NEWgeometry := ST_PointOnSurface(place);
8   RETURN (partition*1000000) + (500-ST_X(NEWgeometry)::integer)*1000 + (500-ST_Y(NEWgeometry)::integer);
9 END;
10 $$
11 LANGUAGE plpgsql IMMUTABLE;
12
13
14 CREATE OR REPLACE FUNCTION array_merge(a INTEGER[], b INTEGER[])
15   RETURNS INTEGER[]
16   AS $$
17 DECLARE
18   i INTEGER;
19   r INTEGER[];
20 BEGIN
21   IF array_upper(a, 1) IS NULL THEN
22     RETURN b;
23   END IF;
24   IF array_upper(b, 1) IS NULL THEN
25     RETURN a;
26   END IF;
27   r := a;
28   FOR i IN 1..array_upper(b, 1) LOOP  
29     IF NOT (ARRAY[b[i]] <@ r) THEN
30       r := r || b[i];
31     END IF;
32   END LOOP;
33   RETURN r;
34 END;
35 $$
36 LANGUAGE plpgsql IMMUTABLE;
37
38 CREATE OR REPLACE FUNCTION reverse_place_diameter(rank_search SMALLINT)
39   RETURNS FLOAT
40   AS $$
41 BEGIN
42   IF rank_search <= 4 THEN
43     RETURN 5.0;
44   ELSIF rank_search <= 8 THEN
45     RETURN 1.8;
46   ELSIF rank_search <= 12 THEN
47     RETURN 0.6;
48   ELSIF rank_search <= 17 THEN
49     RETURN 0.16;
50   ELSIF rank_search <= 18 THEN
51     RETURN 0.08;
52   ELSIF rank_search <= 19 THEN
53     RETURN 0.04;
54   END IF;
55
56   RETURN 0.02;
57 END;
58 $$
59 LANGUAGE plpgsql IMMUTABLE;
60
61 CREATE OR REPLACE FUNCTION get_postcode_rank(country_code VARCHAR(2), postcode TEXT,
62                                       OUT rank_search SMALLINT, OUT rank_address SMALLINT)
63 AS $$
64 DECLARE
65   part TEXT;
66 BEGIN
67     rank_search := 30;
68     rank_address := 30;
69     postcode := upper(postcode);
70
71     IF country_code = 'gb' THEN
72         IF postcode ~ '^([A-Z][A-Z]?[0-9][0-9A-Z]? [0-9][A-Z][A-Z])$' THEN
73             rank_search := 25;
74             rank_address := 5;
75         ELSEIF postcode ~ '^([A-Z][A-Z]?[0-9][0-9A-Z]? [0-9])$' THEN
76             rank_search := 23;
77             rank_address := 5;
78         ELSEIF postcode ~ '^([A-Z][A-Z]?[0-9][0-9A-Z])$' THEN
79             rank_search := 21;
80             rank_address := 5;
81         END IF;
82
83     ELSEIF country_code = 'sg' THEN
84         IF postcode ~ '^([0-9]{6})$' THEN
85             rank_search := 25;
86             rank_address := 11;
87         END IF;
88
89     ELSEIF country_code = 'de' THEN
90         IF postcode ~ '^([0-9]{5})$' THEN
91             rank_search := 21;
92             rank_address := 11;
93         END IF;
94
95     ELSE
96         -- Guess at the postcode format and coverage (!)
97         IF postcode ~ '^[A-Z0-9]{1,5}$' THEN -- Probably too short to be very local
98             rank_search := 21;
99             rank_address := 11;
100         ELSE
101             -- Does it look splitable into and area and local code?
102             part := substring(postcode from '^([- :A-Z0-9]+)([- :][A-Z0-9]+)$');
103
104             IF part IS NOT NULL THEN
105                 rank_search := 25;
106                 rank_address := 11;
107             ELSEIF postcode ~ '^[- :A-Z0-9]{6,}$' THEN
108                 rank_search := 21;
109                 rank_address := 11;
110             END IF;
111         END IF;
112     END IF;
113
114 END;
115 $$
116 LANGUAGE plpgsql IMMUTABLE;
117
118 -- Find the nearest artificial postcode for the given geometry.
119 -- TODO For areas there should not be more than two inside the geometry.
120 CREATE OR REPLACE FUNCTION get_nearest_postcode(country VARCHAR(2), geom GEOMETRY) RETURNS TEXT
121   AS $$
122 DECLARE
123   outcode TEXT;
124   cnt INTEGER;
125 BEGIN
126     -- If the geometry is an area then only one postcode must be within
127     -- that area, otherwise consider the area as not having a postcode.
128     IF ST_GeometryType(geom) in ('ST_Polygon','ST_MultiPolygon') THEN
129         SELECT min(postcode), count(*) FROM
130               (SELECT postcode FROM location_postcode
131                 WHERE ST_Contains(geom, location_postcode.geometry) LIMIT 2) sub
132           INTO outcode, cnt;
133
134         IF cnt = 1 THEN
135             RETURN outcode;
136         ELSE
137             RETURN null;
138         END IF;
139     END IF;
140
141     SELECT postcode FROM location_postcode
142      WHERE ST_DWithin(geom, location_postcode.geometry, 0.05)
143           AND location_postcode.country_code = country
144      ORDER BY ST_Distance(geom, location_postcode.geometry) LIMIT 1
145     INTO outcode;
146
147     RETURN outcode;
148 END;
149 $$
150 LANGUAGE plpgsql;
151
152
153 CREATE OR REPLACE FUNCTION get_country_code(place geometry) RETURNS TEXT
154   AS $$
155 DECLARE
156   place_centre GEOMETRY;
157   nearcountry RECORD;
158 BEGIN
159   place_centre := ST_PointOnSurface(place);
160
161 -- RAISE WARNING 'get_country_code, start: %', ST_AsText(place_centre);
162
163   -- Try for a OSM polygon
164   FOR nearcountry IN select country_code from location_area_country where country_code is not null and st_covers(geometry, place_centre) limit 1
165   LOOP
166     RETURN nearcountry.country_code;
167   END LOOP;
168
169 -- RAISE WARNING 'osm fallback: %', ST_AsText(place_centre);
170
171   -- Try for OSM fallback data
172   -- The order is to deal with places like HongKong that are 'states' within another polygon
173   FOR nearcountry IN select country_code from country_osm_grid where st_covers(geometry, place_centre) order by area asc limit 1
174   LOOP
175     RETURN nearcountry.country_code;
176   END LOOP;
177
178 -- RAISE WARNING 'near osm fallback: %', ST_AsText(place_centre);
179
180   -- 
181   FOR nearcountry IN select country_code from country_osm_grid where st_dwithin(geometry, place_centre, 0.5) order by st_distance(geometry, place_centre) asc, area asc limit 1
182   LOOP
183     RETURN nearcountry.country_code;
184   END LOOP;
185
186   RETURN NULL;
187 END;
188 $$
189 LANGUAGE plpgsql IMMUTABLE;
190
191 CREATE OR REPLACE FUNCTION get_country_language_code(search_country_code VARCHAR(2)) RETURNS TEXT
192   AS $$
193 DECLARE
194   nearcountry RECORD;
195 BEGIN
196   FOR nearcountry IN select distinct country_default_language_code from country_name where country_code = search_country_code limit 1
197   LOOP
198     RETURN lower(nearcountry.country_default_language_code);
199   END LOOP;
200   RETURN NULL;
201 END;
202 $$
203 LANGUAGE plpgsql IMMUTABLE;
204
205 CREATE OR REPLACE FUNCTION get_country_language_codes(search_country_code VARCHAR(2)) RETURNS TEXT[]
206   AS $$
207 DECLARE
208   nearcountry RECORD;
209 BEGIN
210   FOR nearcountry IN select country_default_language_codes from country_name where country_code = search_country_code limit 1
211   LOOP
212     RETURN lower(nearcountry.country_default_language_codes);
213   END LOOP;
214   RETURN NULL;
215 END;
216 $$
217 LANGUAGE plpgsql IMMUTABLE;
218
219 CREATE OR REPLACE FUNCTION get_partition(in_country_code VARCHAR(10)) RETURNS INTEGER
220   AS $$
221 DECLARE
222   nearcountry RECORD;
223 BEGIN
224   FOR nearcountry IN select partition from country_name where country_code = in_country_code
225   LOOP
226     RETURN nearcountry.partition;
227   END LOOP;
228   RETURN 0;
229 END;
230 $$
231 LANGUAGE plpgsql IMMUTABLE;
232
233 CREATE OR REPLACE FUNCTION delete_location(OLD_place_id BIGINT) RETURNS BOOLEAN
234   AS $$
235 DECLARE
236 BEGIN
237   DELETE FROM location_area where place_id = OLD_place_id;
238 -- TODO:location_area
239   RETURN true;
240 END;
241 $$
242 LANGUAGE plpgsql;
243
244 CREATE OR REPLACE FUNCTION add_location(
245     place_id BIGINT,
246     country_code varchar(2),
247     partition INTEGER,
248     keywords INTEGER[],
249     rank_search INTEGER,
250     rank_address INTEGER,
251     in_postcode TEXT,
252     geometry GEOMETRY
253   ) 
254   RETURNS BOOLEAN
255   AS $$
256 DECLARE
257   locationid INTEGER;
258   centroid GEOMETRY;
259   diameter FLOAT;
260   x BOOLEAN;
261   splitGeom RECORD;
262   secgeo GEOMETRY;
263   postcode TEXT;
264 BEGIN
265
266   IF rank_search > 25 THEN
267     RAISE EXCEPTION 'Adding location with rank > 25 (% rank %)', place_id, rank_search;
268   END IF;
269
270   x := deleteLocationArea(partition, place_id, rank_search);
271
272   -- add postcode only if it contains a single entry, i.e. ignore postcode lists
273   postcode := NULL;
274   IF in_postcode is not null AND in_postcode not similar to '%(,|;)%' THEN
275       postcode := upper(trim (in_postcode));
276   END IF;
277
278   IF ST_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon') THEN
279     centroid := ST_Centroid(geometry);
280
281     FOR secgeo IN select split_geometry(geometry) AS geom LOOP
282       x := insertLocationAreaLarge(partition, place_id, country_code, keywords, rank_search, rank_address, false, postcode, centroid, secgeo);
283     END LOOP;
284
285   ELSE
286
287     diameter := 0.02;
288     IF rank_address = 0 THEN
289       diameter := 0.02;
290     ELSEIF rank_search <= 14 THEN
291       diameter := 1.2;
292     ELSEIF rank_search <= 15 THEN
293       diameter := 1;
294     ELSEIF rank_search <= 16 THEN
295       diameter := 0.5;
296     ELSEIF rank_search <= 17 THEN
297       diameter := 0.2;
298     ELSEIF rank_search <= 21 THEN
299       diameter := 0.05;
300     ELSEIF rank_search = 25 THEN
301       diameter := 0.005;
302     END IF;
303
304 --    RAISE WARNING 'adding % diameter %', place_id, diameter;
305
306     secgeo := ST_Buffer(geometry, diameter);
307     x := insertLocationAreaLarge(partition, place_id, country_code, keywords, rank_search, rank_address, true, postcode, ST_Centroid(geometry), secgeo);
308
309   END IF;
310
311   RETURN true;
312 END;
313 $$
314 LANGUAGE plpgsql;
315
316
317 CREATE OR REPLACE FUNCTION placex_insert() RETURNS TRIGGER
318   AS $$
319 DECLARE
320   i INTEGER;
321   postcode TEXT;
322   result BOOLEAN;
323   is_area BOOLEAN;
324   country_code VARCHAR(2);
325   default_language VARCHAR(10);
326   diameter FLOAT;
327   classtable TEXT;
328   classtype TEXT;
329 BEGIN
330   --DEBUG: RAISE WARNING '% % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type;
331
332   NEW.place_id := nextval('seq_place');
333   NEW.indexed_status := 1; --STATUS_NEW
334
335   NEW.country_code := lower(get_country_code(NEW.geometry));
336
337   NEW.partition := get_partition(NEW.country_code);
338   NEW.geometry_sector := geometry_sector(NEW.partition, NEW.geometry);
339
340   -- copy 'name' to or from the default language (if there is a default language)
341   IF NEW.name is not null AND array_upper(akeys(NEW.name),1) > 1 THEN
342     default_language := get_country_language_code(NEW.country_code);
343     IF default_language IS NOT NULL THEN
344       IF NEW.name ? 'name' AND NOT NEW.name ? ('name:'||default_language) THEN
345         NEW.name := NEW.name || hstore(('name:'||default_language), (NEW.name -> 'name'));
346       ELSEIF NEW.name ? ('name:'||default_language) AND NOT NEW.name ? 'name' THEN
347         NEW.name := NEW.name || hstore('name', (NEW.name -> ('name:'||default_language)));
348       END IF;
349     END IF;
350   END IF;
351
352   IF NEW.osm_type = 'X' THEN
353     -- E'X'ternal records should already be in the right format so do nothing
354   ELSE
355     is_area := ST_GeometryType(NEW.geometry) IN ('ST_Polygon','ST_MultiPolygon');
356
357     IF NEW.class in ('place','boundary')
358        AND NEW.type in ('postcode','postal_code') THEN
359
360       IF NEW.address IS NULL OR NOT NEW.address ? 'postcode' THEN
361           -- most likely just a part of a multipolygon postcode boundary, throw it away
362           RETURN NULL;
363       END IF;
364
365       NEW.name := hstore('ref', NEW.address->'postcode');
366
367       SELECT * FROM get_postcode_rank(NEW.country_code, NEW.address->'postcode')
368         INTO NEW.rank_search, NEW.rank_address;
369
370       IF NOT is_area THEN
371           NEW.rank_address := 0;
372       END IF;
373     ELSEIF NEW.class = 'boundary' AND NOT is_area THEN
374         return NULL;
375     ELSEIF NEW.class = 'boundary' AND NEW.type = 'administrative'
376            AND NEW.admin_level <= 4 AND NEW.osm_type = 'W' THEN
377         return NULL;
378     ELSEIF NEW.class = 'railway' AND NEW.type in ('rail') THEN
379         return NULL;
380     ELSEIF NEW.osm_type = 'N' AND NEW.class = 'highway' THEN
381         NEW.rank_search = 30;
382         NEW.rank_address = 0;
383     ELSEIF NEW.class = 'landuse' AND NOT is_area THEN
384         NEW.rank_search = 30;
385         NEW.rank_address = 0;
386     ELSE
387       -- do table lookup stuff
388       IF NEW.class = 'boundary' and NEW.type = 'administrative' THEN
389         classtype = NEW.type || NEW.admin_level::TEXT;
390       ELSE
391         classtype = NEW.type;
392       END IF;
393       SELECT l.rank_search, l.rank_address FROM address_levels l
394        WHERE (l.country_code = NEW.country_code or l.country_code is NULL)
395              AND l.class = NEW.class AND (l.type = classtype or l.type is NULL)
396        ORDER BY l.country_code, l.class, l.type LIMIT 1
397         INTO NEW.rank_search, NEW.rank_address;
398
399       IF NEW.rank_search is NULL THEN
400         NEW.rank_search := 30;
401       END IF;
402
403       IF NEW.rank_address is NULL THEN
404         NEW.rank_address := 30;
405       END IF;
406     END IF;
407
408     -- some postcorrections
409     IF NEW.class = 'waterway' AND NEW.osm_type = 'R' THEN
410         -- Slightly promote waterway relations so that they are processed
411         -- before their members.
412         NEW.rank_search := NEW.rank_search - 1;
413     END IF;
414
415     IF (NEW.extratags -> 'capital') = 'yes' THEN
416       NEW.rank_search := NEW.rank_search - 1;
417     END IF;
418
419   END IF;
420
421   -- a country code make no sense below rank 4 (country)
422   IF NEW.rank_search < 4 THEN
423     NEW.country_code := NULL;
424   END IF;
425
426   --DEBUG: RAISE WARNING 'placex_insert:END: % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type;
427
428   RETURN NEW; -- %DIFFUPDATES% The following is not needed until doing diff updates, and slows the main index process down
429
430   IF NEW.osm_type = 'N' and NEW.rank_search > 28 THEN
431       -- might be part of an interpolation
432       result := osmline_reinsert(NEW.osm_id, NEW.geometry);
433   ELSEIF NEW.rank_address > 0 THEN
434     IF (ST_GeometryType(NEW.geometry) in ('ST_Polygon','ST_MultiPolygon') AND ST_IsValid(NEW.geometry)) THEN
435       -- Performance: We just can't handle re-indexing for country level changes
436       IF st_area(NEW.geometry) < 1 THEN
437         -- mark items within the geometry for re-indexing
438   --    RAISE WARNING 'placex poly insert: % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type;
439
440         -- work around bug in postgis, this may have been fixed in 2.0.0 (see http://trac.osgeo.org/postgis/ticket/547)
441         update placex set indexed_status = 2 where (st_covers(NEW.geometry, placex.geometry) OR ST_Intersects(NEW.geometry, placex.geometry)) 
442          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'));
443         update placex set indexed_status = 2 where (st_covers(NEW.geometry, placex.geometry) OR ST_Intersects(NEW.geometry, placex.geometry)) 
444          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'));
445       END IF;
446     ELSE
447       -- mark nearby items for re-indexing, where 'nearby' depends on the features rank_search and is a complete guess :(
448       diameter := 0;
449       -- 16 = city, anything higher than city is effectively ignored (polygon required!)
450       IF NEW.type='postcode' THEN
451         diameter := 0.05;
452       ELSEIF NEW.rank_search < 16 THEN
453         diameter := 0;
454       ELSEIF NEW.rank_search < 18 THEN
455         diameter := 0.1;
456       ELSEIF NEW.rank_search < 20 THEN
457         diameter := 0.05;
458       ELSEIF NEW.rank_search = 21 THEN
459         diameter := 0.001;
460       ELSEIF NEW.rank_search < 24 THEN
461         diameter := 0.02;
462       ELSEIF NEW.rank_search < 26 THEN
463         diameter := 0.002; -- 100 to 200 meters
464       ELSEIF NEW.rank_search < 28 THEN
465         diameter := 0.001; -- 50 to 100 meters
466       END IF;
467       IF diameter > 0 THEN
468   --      RAISE WARNING 'placex point insert: % % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type,diameter;
469         IF NEW.rank_search >= 26 THEN
470           -- roads may cause reparenting for >27 rank places
471           update placex set indexed_status = 2 where indexed_status = 0 and rank_search > NEW.rank_search and ST_DWithin(placex.geometry, NEW.geometry, diameter);
472           -- reparenting also for OSM Interpolation Lines (and for Tiger?)
473           update location_property_osmline set indexed_status = 2 where indexed_status = 0 and ST_DWithin(location_property_osmline.linegeo, NEW.geometry, diameter);
474         ELSEIF NEW.rank_search >= 16 THEN
475           -- up to rank 16, street-less addresses may need reparenting
476           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');
477         ELSE
478           -- for all other places the search terms may change as well
479           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);
480         END IF;
481       END IF;
482     END IF;
483   END IF;
484
485
486    -- add to tables for special search
487    -- Note: won't work on initial import because the classtype tables
488    -- do not yet exist. It won't hurt either.
489   classtable := 'place_classtype_' || NEW.class || '_' || NEW.type;
490   SELECT count(*)>0 FROM pg_tables WHERE tablename = classtable and schemaname = current_schema() INTO result;
491   IF result THEN
492     EXECUTE 'INSERT INTO ' || classtable::regclass || ' (place_id, centroid) VALUES ($1,$2)' 
493     USING NEW.place_id, ST_Centroid(NEW.geometry);
494   END IF;
495
496   RETURN NEW;
497
498 END;
499 $$
500 LANGUAGE plpgsql;
501
502 -- Trigger for updates of location_postcode
503 --
504 -- Computes the parent object the postcode most likely refers to.
505 -- This will be the place that determines the address displayed when
506 -- searching for this postcode.
507 CREATE OR REPLACE FUNCTION postcode_update() RETURNS
508 TRIGGER
509   AS $$
510 DECLARE
511   partition SMALLINT;
512   location RECORD;
513 BEGIN
514     IF NEW.indexed_status != 0 OR OLD.indexed_status = 0 THEN
515         RETURN NEW;
516     END IF;
517
518     NEW.indexed_date = now();
519
520     partition := get_partition(NEW.country_code);
521
522     SELECT * FROM get_postcode_rank(NEW.country_code, NEW.postcode)
523       INTO NEW.rank_search, NEW.rank_address;
524
525     NEW.parent_place_id = 0;
526     FOR location IN
527       SELECT place_id
528         FROM getNearFeatures(partition, NEW.geometry, NEW.rank_search, '{}'::int[])
529         WHERE NOT isguess ORDER BY rank_address DESC LIMIT 1
530     LOOP
531         NEW.parent_place_id = location.place_id;
532     END LOOP;
533
534     RETURN NEW;
535 END;
536 $$
537 LANGUAGE plpgsql;
538
539 CREATE OR REPLACE FUNCTION placex_update() RETURNS
540 TRIGGER
541   AS $$
542 DECLARE
543
544   place_centroid GEOMETRY;
545   near_centroid GEOMETRY;
546
547   search_maxdistance FLOAT[];
548   search_mindistance FLOAT[];
549   address_havelevel BOOLEAN[];
550
551   i INTEGER;
552   iMax FLOAT;
553   location RECORD;
554   way RECORD;
555   relation RECORD;
556   relation_members TEXT[];
557   relMember RECORD;
558   linkedplacex RECORD;
559   addr_item RECORD;
560   search_diameter FLOAT;
561   search_prevdiameter FLOAT;
562   search_maxrank INTEGER;
563   address_maxrank INTEGER;
564   address_street_word_id INTEGER;
565   address_street_word_ids INTEGER[];
566   parent_place_id_rank BIGINT;
567
568   addr_street TEXT;
569   addr_place TEXT;
570
571   isin TEXT[];
572   isin_tokens INT[];
573
574   location_rank_search INTEGER;
575   location_distance FLOAT;
576   location_parent GEOMETRY;
577   location_isaddress BOOLEAN;
578   location_keywords INTEGER[];
579
580   default_language TEXT;
581   name_vector INTEGER[];
582   nameaddress_vector INTEGER[];
583
584   linked_node_id BIGINT;
585   linked_importance FLOAT;
586   linked_wikipedia TEXT;
587
588   result BOOLEAN;
589 BEGIN
590   -- deferred delete
591   IF OLD.indexed_status = 100 THEN
592     --DEBUG: RAISE WARNING 'placex_update delete % %',NEW.osm_type,NEW.osm_id;
593     delete from placex where place_id = OLD.place_id;
594     RETURN NULL;
595   END IF;
596
597   IF NEW.indexed_status != 0 OR OLD.indexed_status = 0 THEN
598     RETURN NEW;
599   END IF;
600
601   --DEBUG: RAISE WARNING 'placex_update % % (%)',NEW.osm_type,NEW.osm_id,NEW.place_id;
602
603   NEW.indexed_date = now();
604
605   IF NOT %REVERSE-ONLY% THEN
606     DELETE from search_name WHERE place_id = NEW.place_id;
607   END IF;
608   result := deleteSearchName(NEW.partition, NEW.place_id);
609   DELETE FROM place_addressline WHERE place_id = NEW.place_id;
610   result := deleteRoad(NEW.partition, NEW.place_id);
611   result := deleteLocationArea(NEW.partition, NEW.place_id, NEW.rank_search);
612   UPDATE placex set linked_place_id = null, indexed_status = 2
613          where linked_place_id = NEW.place_id;
614   -- update not necessary for osmline, cause linked_place_id does not exist
615
616   IF NEW.linked_place_id is not null THEN
617     --DEBUG: RAISE WARNING 'place already linked to %', NEW.linked_place_id;
618     RETURN NEW;
619   END IF;
620
621   --DEBUG: RAISE WARNING 'Copy over address tags';
622   -- housenumber is a computed field, so start with an empty value
623   NEW.housenumber := NULL;
624   IF NEW.address is not NULL THEN
625       IF NEW.address ? 'conscriptionnumber' THEN
626         i := getorcreate_housenumber_id(make_standard_name(NEW.address->'conscriptionnumber'));
627         IF NEW.address ? 'streetnumber' THEN
628             i := getorcreate_housenumber_id(make_standard_name(NEW.address->'streetnumber'));
629             NEW.housenumber := (NEW.address->'conscriptionnumber') || '/' || (NEW.address->'streetnumber');
630         ELSE
631             NEW.housenumber := NEW.address->'conscriptionnumber';
632         END IF;
633       ELSEIF NEW.address ? 'streetnumber' THEN
634         NEW.housenumber := NEW.address->'streetnumber';
635         i := getorcreate_housenumber_id(make_standard_name(NEW.address->'streetnumber'));
636       ELSEIF NEW.address ? 'housenumber' THEN
637         NEW.housenumber := NEW.address->'housenumber';
638         i := getorcreate_housenumber_id(make_standard_name(NEW.housenumber));
639       END IF;
640
641       addr_street := NEW.address->'street';
642       addr_place := NEW.address->'place';
643
644       IF NEW.address ? 'postcode' and NEW.address->'postcode' not similar to '%(,|;)%' THEN
645         i := getorcreate_postcode_id(NEW.address->'postcode');
646       END IF;
647   END IF;
648
649   -- Speed up searches - just use the centroid of the feature
650   -- cheaper but less acurate
651   place_centroid := ST_PointOnSurface(NEW.geometry);
652   -- For searching near features rather use the centroid
653   near_centroid := ST_Envelope(NEW.geometry);
654   NEW.centroid := null;
655   NEW.postcode := null;
656   --DEBUG: RAISE WARNING 'Computing preliminary centroid at %',ST_AsText(place_centroid);
657
658   -- recalculate country and partition
659   IF NEW.rank_search = 4 AND NEW.address is not NULL AND NEW.address ? 'country' THEN
660     -- for countries, believe the mapped country code,
661     -- so that we remain in the right partition if the boundaries
662     -- suddenly expand.
663     NEW.country_code := lower(NEW.address->'country');
664     NEW.partition := get_partition(lower(NEW.country_code));
665     IF NEW.partition = 0 THEN
666       NEW.country_code := lower(get_country_code(place_centroid));
667       NEW.partition := get_partition(NEW.country_code);
668     END IF;
669   ELSE
670     IF NEW.rank_search >= 4 THEN
671       NEW.country_code := lower(get_country_code(place_centroid));
672     ELSE
673       NEW.country_code := NULL;
674     END IF;
675     NEW.partition := get_partition(NEW.country_code);
676   END IF;
677   --DEBUG: RAISE WARNING 'Country updated: "%"', NEW.country_code;
678
679   -- waterway ways are linked when they are part of a relation and have the same class/type
680   IF NEW.osm_type = 'R' and NEW.class = 'waterway' THEN
681       FOR relation_members IN select members from planet_osm_rels r where r.id = NEW.osm_id and r.parts != array[]::bigint[]
682       LOOP
683           FOR i IN 1..array_upper(relation_members, 1) BY 2 LOOP
684               IF relation_members[i+1] in ('', 'main_stream', 'side_stream') AND substring(relation_members[i],1,1) = 'w' THEN
685                 --DEBUG: RAISE WARNING 'waterway parent %, child %/%', NEW.osm_id, i, relation_members[i];
686                 FOR linked_node_id IN SELECT place_id FROM placex
687                   WHERE osm_type = 'W' and osm_id = substring(relation_members[i],2,200)::bigint
688                   and class = NEW.class and type in ('river', 'stream', 'canal', 'drain', 'ditch')
689                   and ( relation_members[i+1] != 'side_stream' or NEW.name->'name' = name->'name')
690                 LOOP
691                   UPDATE placex SET linked_place_id = NEW.place_id WHERE place_id = linked_node_id;
692                 END LOOP;
693               END IF;
694           END LOOP;
695       END LOOP;
696       --DEBUG: RAISE WARNING 'Waterway processed';
697   END IF;
698
699   -- What level are we searching from
700   search_maxrank := NEW.rank_search;
701
702   -- Thought this wasn't needed but when we add new languages to the country_name table
703   -- we need to update the existing names
704   IF NEW.name is not null AND array_upper(akeys(NEW.name),1) > 1 THEN
705     default_language := get_country_language_code(NEW.country_code);
706     IF default_language IS NOT NULL THEN
707       IF NEW.name ? 'name' AND NOT NEW.name ? ('name:'||default_language) THEN
708         NEW.name := NEW.name || hstore(('name:'||default_language), (NEW.name -> 'name'));
709       ELSEIF NEW.name ? ('name:'||default_language) AND NOT NEW.name ? 'name' THEN
710         NEW.name := NEW.name || hstore('name', (NEW.name -> ('name:'||default_language)));
711       END IF;
712     END IF;
713   END IF;
714   --DEBUG: RAISE WARNING 'Local names updated';
715
716   -- Initialise the name vector using our name
717   name_vector := make_keywords(NEW.name);
718   nameaddress_vector := '{}'::int[];
719
720   FOR i IN 1..28 LOOP
721     address_havelevel[i] := false;
722   END LOOP;
723
724   NEW.importance := null;
725   SELECT wikipedia, importance
726     FROM compute_importance(NEW.extratags, NEW.country_code, NEW.osm_type, NEW.osm_id)
727     INTO NEW.wikipedia,NEW.importance;
728
729 --DEBUG: RAISE WARNING 'Importance computed from wikipedia: %', NEW.importance;
730
731   -- ---------------------------------------------------------------------------
732   -- For low level elements we inherit from our parent road
733   IF (NEW.rank_search > 27 OR (NEW.type = 'postcode' AND NEW.rank_search = 25)) THEN
734
735     --DEBUG: RAISE WARNING 'finding street for % %', NEW.osm_type, NEW.osm_id;
736
737     -- We won't get a better centroid, besides these places are too small to care
738     NEW.centroid := place_centroid;
739
740     NEW.parent_place_id := null;
741
742     -- if we have a POI and there is no address information,
743     -- see if we can get it from a surrounding building
744     IF NEW.osm_type = 'N' AND addr_street IS NULL AND addr_place IS NULL
745        AND NEW.housenumber IS NULL THEN
746       FOR location IN select address from placex where ST_Covers(geometry, place_centroid)
747             and address is not null
748             and (address ? 'housenumber' or address ? 'street' or address ? 'place')
749             and rank_search > 28 AND ST_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon')
750             limit 1
751       LOOP
752         NEW.housenumber := location.address->'housenumber';
753         addr_street := location.address->'street';
754         addr_place := location.address->'place';
755         --DEBUG: RAISE WARNING 'Found surrounding building % %', location.osm_type, location.osm_id;
756       END LOOP;
757     END IF;
758
759     -- We have to find our parent road.
760     -- Copy data from linked items (points on ways, addr:street links, relations)
761
762     -- Is this object part of a relation?
763     FOR relation IN select * from planet_osm_rels where parts @> ARRAY[NEW.osm_id] and members @> ARRAY[lower(NEW.osm_type)||NEW.osm_id]
764     LOOP
765       -- At the moment we only process one type of relation - associatedStreet
766       IF relation.tags @> ARRAY['associatedStreet'] THEN
767         FOR i IN 1..array_upper(relation.members, 1) BY 2 LOOP
768           IF NEW.parent_place_id IS NULL AND relation.members[i+1] = 'street' THEN
769 --RAISE WARNING 'node in relation %',relation;
770             SELECT place_id from placex where osm_type = 'W'
771               and osm_id = substring(relation.members[i],2,200)::bigint
772               and rank_search = 26 and name is not null INTO NEW.parent_place_id;
773           END IF;
774         END LOOP;
775       END IF;
776     END LOOP;
777     --DEBUG: RAISE WARNING 'Checked for street relation (%)', NEW.parent_place_id;
778
779     -- Note that addr:street links can only be indexed once the street itself is indexed
780     IF NEW.parent_place_id IS NULL AND addr_street IS NOT NULL THEN
781       address_street_word_ids := get_name_ids(make_standard_name(addr_street));
782       IF address_street_word_ids IS NOT NULL THEN
783         SELECT place_id from getNearestNamedRoadFeature(NEW.partition, near_centroid, address_street_word_ids) INTO NEW.parent_place_id;
784       END IF;
785     END IF;
786     --DEBUG: RAISE WARNING 'Checked for addr:street (%)', NEW.parent_place_id;
787
788     IF NEW.parent_place_id IS NULL AND addr_place IS NOT NULL THEN
789       address_street_word_ids := get_name_ids(make_standard_name(addr_place));
790       IF address_street_word_ids IS NOT NULL THEN
791         SELECT place_id from getNearestNamedPlaceFeature(NEW.partition, near_centroid, address_street_word_ids) INTO NEW.parent_place_id;
792       END IF;
793     END IF;
794     --DEBUG: RAISE WARNING 'Checked for addr:place (%)', NEW.parent_place_id;
795
796     -- Is this node part of an interpolation?
797     IF NEW.parent_place_id IS NULL AND NEW.osm_type = 'N' THEN
798       SELECT q.parent_place_id FROM location_property_osmline q, planet_osm_ways x
799         WHERE q.linegeo && NEW.geometry and x.id = q.osm_id and NEW.osm_id = any(x.nodes)
800         LIMIT 1 INTO NEW.parent_place_id;
801     END IF;
802     --DEBUG: RAISE WARNING 'Checked for interpolation (%)', NEW.parent_place_id;
803
804     -- Is this node part of a way?
805     IF NEW.parent_place_id IS NULL AND NEW.osm_type = 'N' THEN
806
807       FOR location IN
808         SELECT p.place_id, p.osm_id, p.rank_search, p.address from placex p, planet_osm_ways w
809          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)
810       LOOP
811         --DEBUG: RAISE WARNING 'Node is part of way % ', location.osm_id;
812
813         -- Way IS a road then we are on it - that must be our road
814         IF location.rank_search < 28 THEN
815 --RAISE WARNING 'node in way that is a street %',location;
816           NEW.parent_place_id := location.place_id;
817           EXIT;
818         END IF;
819         --DEBUG: RAISE WARNING 'Checked if way is street (%)', NEW.parent_place_id;
820
821         -- If the way mentions a street or place address, try that for parenting.
822         IF location.address is not null THEN
823           IF location.address ? 'street' THEN
824             address_street_word_ids := get_name_ids(make_standard_name(location.address->'street'));
825             IF address_street_word_ids IS NOT NULL THEN
826               SELECT place_id from getNearestNamedRoadFeature(NEW.partition, near_centroid, address_street_word_ids) INTO NEW.parent_place_id;
827               EXIT WHEN NEW.parent_place_id is not NULL;
828             END IF;
829           END IF;
830           --DEBUG: RAISE WARNING 'Checked for addr:street in way (%)', NEW.parent_place_id;
831
832           IF location.address ? 'place' THEN
833             address_street_word_ids := get_name_ids(make_standard_name(location.address->'place'));
834             IF address_street_word_ids IS NOT NULL THEN
835               SELECT place_id from getNearestNamedPlaceFeature(NEW.partition, near_centroid, address_street_word_ids) INTO NEW.parent_place_id;
836               EXIT WHEN NEW.parent_place_id is not NULL;
837             END IF;
838           END IF;
839         --DEBUG: RAISE WARNING 'Checked for addr:place in way (%)', NEW.parent_place_id;
840         END IF;
841
842         -- Is the WAY part of a relation
843         FOR relation IN select * from planet_osm_rels where parts @> ARRAY[location.osm_id] and members @> ARRAY['w'||location.osm_id]
844         LOOP
845           -- At the moment we only process one type of relation - associatedStreet
846           IF relation.tags @> ARRAY['associatedStreet'] AND array_upper(relation.members, 1) IS NOT NULL THEN
847             FOR i IN 1..array_upper(relation.members, 1) BY 2 LOOP
848               IF NEW.parent_place_id IS NULL AND relation.members[i+1] = 'street' THEN
849 --RAISE WARNING 'node in way that is in a relation %',relation;
850                 SELECT place_id from placex where osm_type='W' and osm_id = substring(relation.members[i],2,200)::bigint 
851                   and rank_search = 26 and name is not null INTO NEW.parent_place_id;
852               END IF;
853             END LOOP;
854           END IF;
855         END LOOP;
856         EXIT WHEN NEW.parent_place_id is not null;
857         --DEBUG: RAISE WARNING 'Checked for street relation in way (%)', NEW.parent_place_id;
858
859       END LOOP;
860     END IF;
861
862     -- Still nothing, just use the nearest road
863     IF NEW.parent_place_id IS NULL THEN
864       SELECT place_id FROM getNearestRoadFeature(NEW.partition, near_centroid) INTO NEW.parent_place_id;
865     END IF;
866     --DEBUG: RAISE WARNING 'Checked for nearest way (%)', NEW.parent_place_id;
867
868
869     -- If we didn't find any road fallback to standard method
870     IF NEW.parent_place_id IS NOT NULL THEN
871
872       -- Get the details of the parent road
873       SELECT p.country_code, p.postcode FROM placex p
874        WHERE p.place_id = NEW.parent_place_id INTO location;
875
876       NEW.country_code := location.country_code;
877       --DEBUG: RAISE WARNING 'Got parent details from search name';
878
879       -- determine postcode
880       IF NEW.rank_search > 4 THEN
881           IF NEW.address is not null AND NEW.address ? 'postcode' THEN
882               NEW.postcode = upper(trim(NEW.address->'postcode'));
883           ELSE
884              NEW.postcode := location.postcode;
885           END IF;
886           IF NEW.postcode is null THEN
887             NEW.postcode := get_nearest_postcode(NEW.country_code, NEW.geometry);
888           END IF;
889       END IF;
890
891       -- If there is no name it isn't searchable, don't bother to create a search record
892       IF NEW.name is NULL THEN
893         --DEBUG: RAISE WARNING 'Not a searchable place % %', NEW.osm_type, NEW.osm_id;
894         return NEW;
895       END IF;
896
897       -- Performance, it would be more acurate to do all the rest of the import
898       -- process but it takes too long
899       -- Just be happy with inheriting from parent road only
900       IF NEW.rank_search <= 25 and NEW.rank_address > 0 THEN
901         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);
902         --DEBUG: RAISE WARNING 'Place added to location table';
903       END IF;
904
905       result := insertSearchName(NEW.partition, NEW.place_id, name_vector,
906                                  NEW.rank_search, NEW.rank_address, NEW.geometry);
907
908       IF NOT %REVERSE-ONLY% THEN
909           -- Merge address from parent
910           SELECT s.name_vector, s.nameaddress_vector FROM search_name s
911            WHERE s.place_id = NEW.parent_place_id INTO location;
912
913           nameaddress_vector := array_merge(nameaddress_vector,
914                                             location.nameaddress_vector);
915           nameaddress_vector := array_merge(nameaddress_vector, location.name_vector);
916
917           INSERT INTO search_name (place_id, search_rank, address_rank,
918                                    importance, country_code, name_vector,
919                                    nameaddress_vector, centroid)
920                  VALUES (NEW.place_id, NEW.rank_search, NEW.rank_address,
921                          NEW.importance, NEW.country_code, name_vector,
922                          nameaddress_vector, place_centroid);
923           --DEBUG: RAISE WARNING 'Place added to search table';
924         END IF;
925
926       return NEW;
927     END IF;
928
929   END IF;
930
931   -- ---------------------------------------------------------------------------
932   -- Full indexing
933   --DEBUG: RAISE WARNING 'Using full index mode for % %', NEW.osm_type, NEW.osm_id;
934
935   IF NEW.osm_type = 'R' AND NEW.rank_search < 26 THEN
936
937     -- see if we have any special relation members
938     select members from planet_osm_rels where id = NEW.osm_id INTO relation_members;
939     --DEBUG: RAISE WARNING 'Got relation members';
940
941     IF relation_members IS NOT NULL THEN
942       FOR relMember IN select get_osm_rel_members(relation_members,ARRAY['label']) as member LOOP
943         --DEBUG: RAISE WARNING 'Found label member %', relMember.member;
944
945         FOR linkedPlacex IN select * from placex where osm_type = upper(substring(relMember.member,1,1))::char(1) 
946           and osm_id = substring(relMember.member,2,10000)::bigint
947           and class = 'place' order by rank_search desc limit 1 LOOP
948
949           -- If we don't already have one use this as the centre point of the geometry
950           IF NEW.centroid IS NULL THEN
951             NEW.centroid := coalesce(linkedPlacex.centroid,st_centroid(linkedPlacex.geometry));
952           END IF;
953
954           -- merge in the label name, re-init word vector
955           IF NOT linkedPlacex.name IS NULL THEN
956             NEW.name := linkedPlacex.name || NEW.name;
957             name_vector := array_merge(name_vector, make_keywords(linkedPlacex.name));
958           END IF;
959
960           -- merge in extra tags
961           NEW.extratags := hstore(linkedPlacex.class, linkedPlacex.type) || coalesce(linkedPlacex.extratags, ''::hstore) || coalesce(NEW.extratags, ''::hstore);
962
963           -- mark the linked place (excludes from search results)
964           UPDATE placex set linked_place_id = NEW.place_id where place_id = linkedPlacex.place_id;
965
966           select wikipedia, importance
967             FROM compute_importance(linkedPlacex.extratags, NEW.country_code,
968                                     'N', linkedPlacex.osm_id)
969             INTO linked_wikipedia,linked_importance;
970           --DEBUG: RAISE WARNING 'Linked label member';
971         END LOOP;
972
973       END LOOP;
974
975       IF NEW.centroid IS NULL THEN
976
977         FOR relMember IN select get_osm_rel_members(relation_members,ARRAY['admin_center','admin_centre']) as member LOOP
978           --DEBUG: RAISE WARNING 'Found admin_center member %', relMember.member;
979
980           FOR linkedPlacex IN select * from placex where osm_type = upper(substring(relMember.member,1,1))::char(1) 
981             and osm_id = substring(relMember.member,2,10000)::bigint
982             and class = 'place' order by rank_search desc limit 1 LOOP
983
984             -- For an admin centre we also want a name match - still not perfect, for example 'new york, new york'
985             -- But that can be fixed by explicitly setting the label in the data
986             IF make_standard_name(NEW.name->'name') = make_standard_name(linkedPlacex.name->'name') 
987               AND NEW.rank_address = linkedPlacex.rank_address THEN
988
989               -- If we don't already have one use this as the centre point of the geometry
990               IF NEW.centroid IS NULL THEN
991                 NEW.centroid := coalesce(linkedPlacex.centroid,st_centroid(linkedPlacex.geometry));
992               END IF;
993
994               -- merge in the name, re-init word vector
995               IF NOT linkedPlacex.name IS NULL THEN
996                 NEW.name := linkedPlacex.name || NEW.name;
997                 name_vector := make_keywords(NEW.name);
998               END IF;
999
1000               -- merge in extra tags
1001               NEW.extratags := hstore(linkedPlacex.class, linkedPlacex.type) || coalesce(linkedPlacex.extratags, ''::hstore) || coalesce(NEW.extratags, ''::hstore);
1002
1003               -- mark the linked place (excludes from search results)
1004               UPDATE placex set linked_place_id = NEW.place_id where place_id = linkedPlacex.place_id;
1005
1006               select wikipedia, importance
1007                 FROM compute_importance(linkedPlacex.extratags, NEW.country_code,
1008                                         'N', linkedPlacex.osm_id)
1009                 INTO linked_wikipedia,linked_importance;
1010               --DEBUG: RAISE WARNING 'Linked admin_center';
1011             END IF;
1012
1013           END LOOP;
1014
1015         END LOOP;
1016
1017       END IF;
1018     END IF;
1019
1020   END IF;
1021
1022   -- Name searches can be done for ways as well as relations
1023   IF NEW.osm_type in ('W','R') AND NEW.rank_search < 26 AND NEW.rank_address > 0 THEN
1024
1025     -- not found one yet? how about doing a name search
1026     IF NEW.centroid IS NULL AND (NEW.name->'name') is not null and make_standard_name(NEW.name->'name') != '' THEN
1027
1028       --DEBUG: RAISE WARNING 'Looking for nodes with matching names';
1029       FOR linkedPlacex IN select placex.* from placex WHERE
1030         make_standard_name(name->'name') = make_standard_name(NEW.name->'name')
1031         AND placex.rank_address = NEW.rank_address
1032         AND placex.place_id != NEW.place_id
1033         AND placex.osm_type = 'N'::char(1) AND placex.rank_search < 26
1034         AND st_covers(NEW.geometry, placex.geometry)
1035       LOOP
1036         --DEBUG: RAISE WARNING 'Found matching place node %', linkedPlacex.osm_id;
1037         -- If we don't already have one use this as the centre point of the geometry
1038         IF NEW.centroid IS NULL THEN
1039           NEW.centroid := coalesce(linkedPlacex.centroid,st_centroid(linkedPlacex.geometry));
1040         END IF;
1041
1042         -- merge in the name, re-init word vector
1043         NEW.name := linkedPlacex.name || NEW.name;
1044         name_vector := make_keywords(NEW.name);
1045
1046         -- merge in extra tags
1047         NEW.extratags := hstore(linkedPlacex.class, linkedPlacex.type) || coalesce(linkedPlacex.extratags, ''::hstore) || coalesce(NEW.extratags, ''::hstore);
1048
1049         -- mark the linked place (excludes from search results)
1050         UPDATE placex set linked_place_id = NEW.place_id where place_id = linkedPlacex.place_id;
1051
1052         select wikipedia, importance
1053           FROM compute_importance(linkedPlacex.extratags, NEW.country_code,
1054                                   'N', linkedPlacex.osm_id)
1055           INTO linked_wikipedia,linked_importance;
1056         --DEBUG: RAISE WARNING 'Linked named place';
1057       END LOOP;
1058     END IF;
1059
1060     IF NEW.centroid IS NOT NULL THEN
1061       place_centroid := NEW.centroid;
1062       -- Place might have had only a name tag before but has now received translations
1063       -- from the linked place. Make sure a name tag for the default language exists in
1064       -- this case. 
1065       IF NEW.name is not null AND array_upper(akeys(NEW.name),1) > 1 THEN
1066         default_language := get_country_language_code(NEW.country_code);
1067         IF default_language IS NOT NULL THEN
1068           IF NEW.name ? 'name' AND NOT NEW.name ? ('name:'||default_language) THEN
1069             NEW.name := NEW.name || hstore(('name:'||default_language), (NEW.name -> 'name'));
1070           ELSEIF NEW.name ? ('name:'||default_language) AND NOT NEW.name ? 'name' THEN
1071             NEW.name := NEW.name || hstore('name', (NEW.name -> ('name:'||default_language)));
1072           END IF;
1073         END IF;
1074       END IF;
1075       --DEBUG: RAISE WARNING 'Names updated from linked places';
1076     END IF;
1077
1078     -- Use the maximum importance if a one could be computed from the linked object.
1079     IF linked_importance is not null AND
1080         (NEW.importance is null or NEW.importance < linked_importance) THEN
1081         NEW.importance = linked_importance;
1082     END IF;
1083   END IF;
1084
1085   -- make sure all names are in the word table
1086   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
1087     perform create_country(NEW.name, lower(NEW.country_code));
1088     --DEBUG: RAISE WARNING 'Country names updated';
1089   END IF;
1090
1091   NEW.parent_place_id = 0;
1092   parent_place_id_rank = 0;
1093
1094
1095   -- convert address store to array of tokenids
1096   --DEBUG: RAISE WARNING 'Starting address search';
1097   isin_tokens := '{}'::int[];
1098   IF NEW.address IS NOT NULL THEN
1099     FOR addr_item IN SELECT * FROM each(NEW.address)
1100     LOOP
1101       IF addr_item.key IN ('city', 'tiger:county', 'state', 'suburb', 'province', 'district', 'region', 'county', 'municipality', 'hamlet', 'village', 'subdistrict', 'town', 'neighbourhood', 'quarter', 'parish') THEN
1102         address_street_word_id := get_name_id(make_standard_name(addr_item.value));
1103         IF address_street_word_id IS NOT NULL AND NOT(ARRAY[address_street_word_id] <@ isin_tokens) THEN
1104           isin_tokens := isin_tokens || address_street_word_id;
1105         END IF;
1106         IF NOT %REVERSE-ONLY% THEN
1107           address_street_word_id := get_word_id(make_standard_name(addr_item.value));
1108           IF address_street_word_id IS NOT NULL THEN
1109             nameaddress_vector := array_merge(nameaddress_vector, ARRAY[address_street_word_id]);
1110           END IF;
1111         END IF;
1112       END IF;
1113       IF addr_item.key = 'is_in' THEN
1114         -- is_in items need splitting
1115         isin := regexp_split_to_array(addr_item.value, E'[;,]');
1116         IF array_upper(isin, 1) IS NOT NULL THEN
1117           FOR i IN 1..array_upper(isin, 1) LOOP
1118             address_street_word_id := get_name_id(make_standard_name(isin[i]));
1119             IF address_street_word_id IS NOT NULL AND NOT(ARRAY[address_street_word_id] <@ isin_tokens) THEN
1120               isin_tokens := isin_tokens || address_street_word_id;
1121             END IF;
1122
1123             -- merge word into address vector
1124             IF NOT %REVERSE-ONLY% THEN
1125               address_street_word_id := get_word_id(make_standard_name(isin[i]));
1126               IF address_street_word_id IS NOT NULL THEN
1127                 nameaddress_vector := array_merge(nameaddress_vector, ARRAY[address_street_word_id]);
1128               END IF;
1129             END IF;
1130           END LOOP;
1131         END IF;
1132       END IF;
1133     END LOOP;
1134   END IF;
1135   IF NOT %REVERSE-ONLY% THEN
1136     nameaddress_vector := array_merge(nameaddress_vector, isin_tokens);
1137   END IF;
1138
1139 -- RAISE WARNING 'ISIN: %', isin_tokens;
1140
1141   -- Process area matches
1142   location_rank_search := 0;
1143   location_distance := 0;
1144   location_parent := NULL;
1145   -- added ourself as address already
1146   address_havelevel[NEW.rank_address] := true;
1147   --DEBUG: RAISE WARNING '  getNearFeatures(%,''%'',%,''%'')',NEW.partition, place_centroid, search_maxrank, isin_tokens;
1148   FOR location IN
1149     SELECT * from getNearFeatures(NEW.partition,
1150                                   CASE WHEN NEW.rank_search >= 26
1151                                              AND NEW.rank_search < 30
1152                                        THEN NEW.geometry
1153                                        ELSE place_centroid END,
1154                                   search_maxrank, isin_tokens)
1155   LOOP
1156     IF location.rank_address != location_rank_search THEN
1157       location_rank_search := location.rank_address;
1158       IF location.isguess THEN
1159         location_distance := location.distance * 1.5;
1160       ELSE
1161         IF location.rank_address <= 12 THEN
1162           -- for county and above, if we have an area consider that exact
1163           -- (It would be nice to relax the constraint for places close to
1164           --  the boundary but we'd need the exact geometry for that. Too
1165           --  expensive.)
1166           location_distance = 0;
1167         ELSE
1168           -- Below county level remain slightly fuzzy.
1169           location_distance := location.distance * 0.5;
1170         END IF;
1171       END IF;
1172     ELSE
1173       CONTINUE WHEN location.keywords <@ location_keywords;
1174     END IF;
1175
1176     IF location.distance < location_distance OR NOT location.isguess THEN
1177       location_keywords := location.keywords;
1178
1179       location_isaddress := NOT address_havelevel[location.rank_address];
1180       IF location_isaddress AND location.isguess AND location_parent IS NOT NULL THEN
1181           location_isaddress := ST_Contains(location_parent,location.centroid);
1182       END IF;
1183
1184       -- RAISE WARNING '% isaddress: %', location.place_id, location_isaddress;
1185       -- Add it to the list of search terms
1186       IF NOT %REVERSE-ONLY% THEN
1187           nameaddress_vector := array_merge(nameaddress_vector, location.keywords::integer[]);
1188       END IF;
1189       INSERT INTO place_addressline (place_id, address_place_id, fromarea, isaddress, distance, cached_rank_address)
1190         VALUES (NEW.place_id, location.place_id, true, location_isaddress, location.distance, location.rank_address);
1191
1192       IF location_isaddress THEN
1193         -- add postcode if we have one
1194         -- (If multiple postcodes are available, we end up with the highest ranking one.)
1195         IF location.postcode is not null THEN
1196             NEW.postcode = location.postcode;
1197         END IF;
1198
1199         address_havelevel[location.rank_address] := true;
1200         IF NOT location.isguess THEN
1201           SELECT geometry FROM placex WHERE place_id = location.place_id INTO location_parent;
1202         END IF;
1203
1204         IF location.rank_address > parent_place_id_rank THEN
1205           NEW.parent_place_id = location.place_id;
1206           parent_place_id_rank = location.rank_address;
1207         END IF;
1208
1209       END IF;
1210
1211     --DEBUG: RAISE WARNING '  Terms: (%) %',location, nameaddress_vector;
1212
1213     END IF;
1214
1215   END LOOP;
1216   --DEBUG: RAISE WARNING 'address computed';
1217
1218   IF NEW.address is not null AND NEW.address ? 'postcode' 
1219      AND NEW.address->'postcode' not similar to '%(,|;)%' THEN
1220     NEW.postcode := upper(trim(NEW.address->'postcode'));
1221   END IF;
1222
1223   IF NEW.postcode is null AND NEW.rank_search > 8 THEN
1224     NEW.postcode := get_nearest_postcode(NEW.country_code, NEW.geometry);
1225   END IF;
1226
1227   -- if we have a name add this to the name search table
1228   IF NEW.name IS NOT NULL THEN
1229
1230     IF NEW.rank_search <= 25 and NEW.rank_address > 0 THEN
1231       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);
1232       --DEBUG: RAISE WARNING 'added to location (full)';
1233     END IF;
1234
1235     IF NEW.rank_search between 26 and 27 and NEW.class = 'highway' THEN
1236       result := insertLocationRoad(NEW.partition, NEW.place_id, NEW.country_code, NEW.geometry);
1237       --DEBUG: RAISE WARNING 'insert into road location table (full)';
1238     END IF;
1239
1240     result := insertSearchName(NEW.partition, NEW.place_id, name_vector,
1241                                NEW.rank_search, NEW.rank_address, NEW.geometry);
1242     --DEBUG: RAISE WARNING 'added to search name (full)';
1243
1244     IF NOT %REVERSE-ONLY% THEN
1245         INSERT INTO search_name (place_id, search_rank, address_rank,
1246                                  importance, country_code, name_vector,
1247                                  nameaddress_vector, centroid)
1248                VALUES (NEW.place_id, NEW.rank_search, NEW.rank_address,
1249                        NEW.importance, NEW.country_code, name_vector,
1250                        nameaddress_vector, place_centroid);
1251     END IF;
1252
1253   END IF;
1254
1255   -- If we've not managed to pick up a better one - default centroid
1256   IF NEW.centroid IS NULL THEN
1257     NEW.centroid := place_centroid;
1258   END IF;
1259
1260   --DEBUG: RAISE WARNING 'place update % % finsihed.', NEW.osm_type, NEW.osm_id;
1261
1262   RETURN NEW;
1263 END;
1264 $$
1265 LANGUAGE plpgsql;
1266
1267 CREATE OR REPLACE FUNCTION placex_delete() RETURNS TRIGGER
1268   AS $$
1269 DECLARE
1270   b BOOLEAN;
1271   classtable TEXT;
1272 BEGIN
1273   -- RAISE WARNING 'placex_delete % %',OLD.osm_type,OLD.osm_id;
1274
1275   update placex set linked_place_id = null, indexed_status = 2 where linked_place_id = OLD.place_id and indexed_status = 0;
1276   --DEBUG: RAISE WARNING 'placex_delete:01 % %',OLD.osm_type,OLD.osm_id;
1277   update placex set linked_place_id = null where linked_place_id = OLD.place_id;
1278   --DEBUG: RAISE WARNING 'placex_delete:02 % %',OLD.osm_type,OLD.osm_id;
1279
1280   IF OLD.rank_address < 30 THEN
1281
1282     -- mark everything linked to this place for re-indexing
1283     --DEBUG: RAISE WARNING 'placex_delete:03 % %',OLD.osm_type,OLD.osm_id;
1284     UPDATE placex set indexed_status = 2 from place_addressline where address_place_id = OLD.place_id 
1285       and placex.place_id = place_addressline.place_id and indexed_status = 0 and place_addressline.isaddress;
1286
1287     --DEBUG: RAISE WARNING 'placex_delete:04 % %',OLD.osm_type,OLD.osm_id;
1288     DELETE FROM place_addressline where address_place_id = OLD.place_id;
1289
1290     --DEBUG: RAISE WARNING 'placex_delete:05 % %',OLD.osm_type,OLD.osm_id;
1291     b := deleteRoad(OLD.partition, OLD.place_id);
1292
1293     --DEBUG: RAISE WARNING 'placex_delete:06 % %',OLD.osm_type,OLD.osm_id;
1294     update placex set indexed_status = 2 where parent_place_id = OLD.place_id and indexed_status = 0;
1295     --DEBUG: RAISE WARNING 'placex_delete:07 % %',OLD.osm_type,OLD.osm_id;
1296     -- reparenting also for OSM Interpolation Lines (and for Tiger?)
1297     update location_property_osmline set indexed_status = 2 where indexed_status = 0 and parent_place_id = OLD.place_id;
1298
1299   END IF;
1300
1301   --DEBUG: RAISE WARNING 'placex_delete:08 % %',OLD.osm_type,OLD.osm_id;
1302
1303   IF OLD.rank_address < 26 THEN
1304     b := deleteLocationArea(OLD.partition, OLD.place_id, OLD.rank_search);
1305   END IF;
1306
1307   --DEBUG: RAISE WARNING 'placex_delete:09 % %',OLD.osm_type,OLD.osm_id;
1308
1309   IF OLD.name is not null THEN
1310     IF NOT %REVERSE-ONLY% THEN
1311       DELETE from search_name WHERE place_id = OLD.place_id;
1312     END IF;
1313     b := deleteSearchName(OLD.partition, OLD.place_id);
1314   END IF;
1315
1316   --DEBUG: RAISE WARNING 'placex_delete:10 % %',OLD.osm_type,OLD.osm_id;
1317
1318   DELETE FROM place_addressline where place_id = OLD.place_id;
1319
1320   --DEBUG: RAISE WARNING 'placex_delete:11 % %',OLD.osm_type,OLD.osm_id;
1321
1322   -- remove from tables for special search
1323   classtable := 'place_classtype_' || OLD.class || '_' || OLD.type;
1324   SELECT count(*)>0 FROM pg_tables WHERE tablename = classtable and schemaname = current_schema() INTO b;
1325   IF b THEN
1326     EXECUTE 'DELETE FROM ' || classtable::regclass || ' WHERE place_id = $1' USING OLD.place_id;
1327   END IF;
1328
1329   --DEBUG: RAISE WARNING 'placex_delete:12 % %',OLD.osm_type,OLD.osm_id;
1330
1331   RETURN OLD;
1332
1333 END;
1334 $$
1335 LANGUAGE plpgsql;
1336
1337 CREATE OR REPLACE FUNCTION get_osm_rel_members(members TEXT[], member TEXT) RETURNS TEXT[]
1338   AS $$
1339 DECLARE
1340   result TEXT[];
1341   i INTEGER;
1342 BEGIN
1343
1344   FOR i IN 1..ARRAY_UPPER(members,1) BY 2 LOOP
1345     IF members[i+1] = member THEN
1346       result := result || members[i];
1347     END IF;
1348   END LOOP;
1349
1350   return result;
1351 END;
1352 $$
1353 LANGUAGE plpgsql;
1354
1355 CREATE OR REPLACE FUNCTION get_osm_rel_members(members TEXT[], memberLabels TEXT[]) RETURNS SETOF TEXT
1356   AS $$
1357 DECLARE
1358   i INTEGER;
1359 BEGIN
1360
1361   FOR i IN 1..ARRAY_UPPER(members,1) BY 2 LOOP
1362     IF members[i+1] = ANY(memberLabels) THEN
1363       RETURN NEXT members[i];
1364     END IF;
1365   END LOOP;
1366
1367   RETURN;
1368 END;
1369 $$
1370 LANGUAGE plpgsql;
1371
1372 CREATE OR REPLACE FUNCTION quad_split_geometry(geometry GEOMETRY, maxarea FLOAT, maxdepth INTEGER) 
1373   RETURNS SETOF GEOMETRY
1374   AS $$
1375 DECLARE
1376   xmin FLOAT;
1377   ymin FLOAT;
1378   xmax FLOAT;
1379   ymax FLOAT;
1380   xmid FLOAT;
1381   ymid FLOAT;
1382   secgeo GEOMETRY;
1383   secbox GEOMETRY;
1384   seg INTEGER;
1385   geo RECORD;
1386   area FLOAT;
1387   remainingdepth INTEGER;
1388   added INTEGER;
1389   
1390 BEGIN
1391
1392 --  RAISE WARNING 'quad_split_geometry: maxarea=%, depth=%',maxarea,maxdepth;
1393
1394   IF (ST_GeometryType(geometry) not in ('ST_Polygon','ST_MultiPolygon') OR NOT ST_IsValid(geometry)) THEN
1395     RETURN NEXT geometry;
1396     RETURN;
1397   END IF;
1398
1399   remainingdepth := maxdepth - 1;
1400   area := ST_AREA(geometry);
1401   IF remainingdepth < 1 OR area < maxarea THEN
1402     RETURN NEXT geometry;
1403     RETURN;
1404   END IF;
1405
1406   xmin := st_xmin(geometry);
1407   xmax := st_xmax(geometry);
1408   ymin := st_ymin(geometry);
1409   ymax := st_ymax(geometry);
1410   secbox := ST_SetSRID(ST_MakeBox2D(ST_Point(ymin,xmin),ST_Point(ymax,xmax)),4326);
1411
1412   -- if the geometry completely covers the box don't bother to slice any more
1413   IF ST_AREA(secbox) = area THEN
1414     RETURN NEXT geometry;
1415     RETURN;
1416   END IF;
1417
1418   xmid := (xmin+xmax)/2;
1419   ymid := (ymin+ymax)/2;
1420
1421   added := 0;
1422   FOR seg IN 1..4 LOOP
1423
1424     IF seg = 1 THEN
1425       secbox := ST_SetSRID(ST_MakeBox2D(ST_Point(xmin,ymin),ST_Point(xmid,ymid)),4326);
1426     END IF;
1427     IF seg = 2 THEN
1428       secbox := ST_SetSRID(ST_MakeBox2D(ST_Point(xmin,ymid),ST_Point(xmid,ymax)),4326);
1429     END IF;
1430     IF seg = 3 THEN
1431       secbox := ST_SetSRID(ST_MakeBox2D(ST_Point(xmid,ymin),ST_Point(xmax,ymid)),4326);
1432     END IF;
1433     IF seg = 4 THEN
1434       secbox := ST_SetSRID(ST_MakeBox2D(ST_Point(xmid,ymid),ST_Point(xmax,ymax)),4326);
1435     END IF;
1436
1437     IF st_intersects(geometry, secbox) THEN
1438       secgeo := st_intersection(geometry, secbox);
1439       IF NOT ST_IsEmpty(secgeo) AND ST_GeometryType(secgeo) in ('ST_Polygon','ST_MultiPolygon') THEN
1440         FOR geo IN select quad_split_geometry(secgeo, maxarea, remainingdepth) as geom LOOP
1441           IF NOT ST_IsEmpty(geo.geom) AND ST_GeometryType(geo.geom) in ('ST_Polygon','ST_MultiPolygon') THEN
1442             added := added + 1;
1443             RETURN NEXT geo.geom;
1444           END IF;
1445         END LOOP;
1446       END IF;
1447     END IF;
1448   END LOOP;
1449
1450   RETURN;
1451 END;
1452 $$
1453 LANGUAGE plpgsql;
1454
1455 CREATE OR REPLACE FUNCTION split_geometry(geometry GEOMETRY) 
1456   RETURNS SETOF GEOMETRY
1457   AS $$
1458 DECLARE
1459   geo RECORD;
1460 BEGIN
1461   -- 10000000000 is ~~ 1x1 degree
1462   FOR geo IN select quad_split_geometry(geometry, 0.25, 20) as geom LOOP
1463     RETURN NEXT geo.geom;
1464   END LOOP;
1465   RETURN;
1466 END;
1467 $$
1468 LANGUAGE plpgsql;
1469
1470
1471 CREATE OR REPLACE FUNCTION place_force_delete(placeid BIGINT) RETURNS BOOLEAN
1472   AS $$
1473 DECLARE
1474     osmid BIGINT;
1475     osmtype character(1);
1476     pclass text;
1477     ptype text;
1478 BEGIN
1479   SELECT osm_type, osm_id, class, type FROM placex WHERE place_id = placeid INTO osmtype, osmid, pclass, ptype;
1480   DELETE FROM import_polygon_delete where osm_type = osmtype and osm_id = osmid and class = pclass and type = ptype;
1481   DELETE FROM import_polygon_error where osm_type = osmtype and osm_id = osmid and class = pclass and type = ptype;
1482   -- force delete from place/placex by making it a very small geometry
1483   UPDATE place set geometry = ST_SetSRID(ST_Point(0,0), 4326) where osm_type = osmtype and osm_id = osmid and class = pclass and type = ptype;
1484   DELETE FROM place where osm_type = osmtype and osm_id = osmid and class = pclass and type = ptype;
1485
1486   RETURN TRUE;
1487 END;
1488 $$
1489 LANGUAGE plpgsql;
1490
1491 CREATE OR REPLACE FUNCTION place_force_update(placeid BIGINT) RETURNS BOOLEAN
1492   AS $$
1493 DECLARE
1494   placegeom GEOMETRY;
1495   geom GEOMETRY;
1496   diameter FLOAT;
1497   rank INTEGER;
1498 BEGIN
1499   UPDATE placex SET indexed_status = 2 WHERE place_id = placeid;
1500   SELECT geometry, rank_search FROM placex WHERE place_id = placeid INTO placegeom, rank;
1501   IF placegeom IS NOT NULL AND ST_IsValid(placegeom) THEN
1502     IF ST_GeometryType(placegeom) in ('ST_Polygon','ST_MultiPolygon') THEN
1503       FOR geom IN select split_geometry(placegeom) FROM placex WHERE place_id = placeid LOOP
1504         update placex set indexed_status = 2 where (st_covers(geom, placex.geometry) OR ST_Intersects(geom, placex.geometry)) 
1505         AND rank_search > rank and indexed_status = 0 and ST_geometrytype(placex.geometry) = 'ST_Point' and (rank_search < 28 or name is not null or (rank >= 16 and address ? 'place'));
1506         update placex set indexed_status = 2 where (st_covers(geom, placex.geometry) OR ST_Intersects(geom, placex.geometry)) 
1507         AND rank_search > rank and indexed_status = 0 and ST_geometrytype(placex.geometry) != 'ST_Point' and (rank_search < 28 or name is not null or (rank >= 16 and address ? 'place'));
1508       END LOOP;
1509     ELSE
1510         diameter := 0;
1511         IF rank = 11 THEN
1512           diameter := 0.05;
1513         ELSEIF rank < 18 THEN
1514           diameter := 0.1;
1515         ELSEIF rank < 20 THEN
1516           diameter := 0.05;
1517         ELSEIF rank = 21 THEN
1518           diameter := 0.001;
1519         ELSEIF rank < 24 THEN
1520           diameter := 0.02;
1521         ELSEIF rank < 26 THEN
1522           diameter := 0.002; -- 100 to 200 meters
1523         ELSEIF rank < 28 THEN
1524           diameter := 0.001; -- 50 to 100 meters
1525         END IF;
1526         IF diameter > 0 THEN
1527           IF rank >= 26 THEN
1528             -- roads may cause reparenting for >27 rank places
1529             update placex set indexed_status = 2 where indexed_status = 0 and rank_search > rank and ST_DWithin(placex.geometry, placegeom, diameter);
1530           ELSEIF rank >= 16 THEN
1531             -- up to rank 16, street-less addresses may need reparenting
1532             update placex set indexed_status = 2 where indexed_status = 0 and rank_search > rank and ST_DWithin(placex.geometry, placegeom, diameter) and (rank_search < 28 or name is not null or address ? 'place');
1533           ELSE
1534             -- for all other places the search terms may change as well
1535             update placex set indexed_status = 2 where indexed_status = 0 and rank_search > rank and ST_DWithin(placex.geometry, placegeom, diameter) and (rank_search < 28 or name is not null);
1536           END IF;
1537         END IF;
1538     END IF;
1539     RETURN TRUE;
1540   END IF;
1541
1542   RETURN FALSE;
1543 END;
1544 $$
1545 LANGUAGE plpgsql;