]> git.openstreetmap.org Git - nominatim.git/blob - lib-sql/functions/placex_triggers.sql
Merge pull request #2685 from lonvia/show-inherited-housenumber
[nominatim.git] / lib-sql / functions / placex_triggers.sql
1 -- SPDX-License-Identifier: GPL-2.0-only
2 --
3 -- This file is part of Nominatim. (https://nominatim.org)
4 --
5 -- Copyright (C) 2022 by the Nominatim developer community.
6 -- For a full list of authors see the git log.
7
8 -- Trigger functions for the placex table.
9
10 -- Information returned by update preparation.
11 DROP TYPE IF EXISTS prepare_update_info CASCADE;
12 CREATE TYPE prepare_update_info AS (
13   name HSTORE,
14   address HSTORE,
15   rank_address SMALLINT,
16   country_code TEXT,
17   class TEXT,
18   type TEXT,
19   linked_place_id BIGINT
20 );
21
22 -- Retrieve the data needed by the indexer for updating the place.
23 CREATE OR REPLACE FUNCTION placex_indexing_prepare(p placex)
24   RETURNS prepare_update_info
25   AS $$
26 DECLARE
27   location RECORD;
28   result prepare_update_info;
29   extra_names HSTORE;
30 BEGIN
31   IF not p.address ? '_inherited' THEN
32     result.address := p.address;
33   END IF;
34
35   -- For POI nodes, check if the address should be derived from a surrounding
36   -- building.
37   IF p.rank_search = 30 AND p.osm_type = 'N' THEN
38     IF p.address is null THEN
39         -- The additional && condition works around the misguided query
40         -- planner of postgis 3.0.
41         SELECT placex.address || hstore('_inherited', '') INTO result.address
42           FROM placex
43          WHERE ST_Covers(geometry, p.centroid)
44                and geometry && p.centroid
45                and placex.address is not null
46                and (placex.address ? 'housenumber' or placex.address ? 'street' or placex.address ? 'place')
47                and rank_search = 30 AND ST_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon')
48          LIMIT 1;
49     ELSE
50       -- See if we can inherit addtional address tags from an interpolation.
51       -- These will become permanent.
52       FOR location IN
53         SELECT (address - 'interpolation'::text - 'housenumber'::text) as address
54           FROM place, planet_osm_ways w
55           WHERE place.osm_type = 'W' and place.address ? 'interpolation'
56                 and place.geometry && p.geometry
57                 and place.osm_id = w.id
58                 and p.osm_id = any(w.nodes)
59       LOOP
60         result.address := location.address || result.address;
61       END LOOP;
62     END IF;
63   END IF;
64
65   -- remove internal and derived names
66   result.address := result.address - '_unlisted_place'::TEXT;
67   SELECT hstore(array_agg(key), array_agg(value)) INTO result.name
68     FROM each(p.name) WHERE key not like '\_%';
69
70   result.class := p.class;
71   result.type := p.type;
72   result.country_code := p.country_code;
73   result.rank_address := p.rank_address;
74
75   -- Names of linked places need to be merged in, so search for a linkable
76   -- place already here.
77   SELECT * INTO location FROM find_linked_place(p);
78
79   IF location.place_id is not NULL THEN
80     result.linked_place_id := location.place_id;
81
82     IF location.name is not NULL THEN
83       {% if debug %}RAISE WARNING 'Names original: %, location: %', result.name, location.name;{% endif %}
84       -- Add all names from the place nodes that deviate from the name
85       -- in the relation with the prefix '_place_'. Deviation means that
86       -- either the value is different or a given key is missing completely
87       SELECT hstore(array_agg('_place_' || key), array_agg(value)) INTO extra_names
88         FROM each(location.name - result.name);
89       {% if debug %}RAISE WARNING 'Extra names: %', extra_names;{% endif %}
90
91       IF extra_names is not null THEN
92           result.name := result.name || extra_names;
93       END IF;
94
95       {% if debug %}RAISE WARNING 'Final names: %', result.name;{% endif %}
96     END IF;
97   END IF;
98
99   RETURN result;
100 END;
101 $$
102 LANGUAGE plpgsql STABLE;
103
104
105 CREATE OR REPLACE FUNCTION find_associated_street(poi_osm_type CHAR(1),
106                                                   poi_osm_id BIGINT)
107   RETURNS BIGINT
108   AS $$
109 DECLARE
110   location RECORD;
111   parent RECORD;
112 BEGIN
113   FOR location IN
114     SELECT members FROM planet_osm_rels
115     WHERE parts @> ARRAY[poi_osm_id]
116           and members @> ARRAY[lower(poi_osm_type) || poi_osm_id]
117           and tags @> ARRAY['associatedStreet']
118   LOOP
119     FOR i IN 1..array_upper(location.members, 1) BY 2 LOOP
120       IF location.members[i+1] = 'street' THEN
121         FOR parent IN
122           SELECT place_id from placex
123            WHERE osm_type = 'W' and osm_id = substring(location.members[i],2)::bigint
124                  and name is not null
125                  and rank_search between 26 and 27
126         LOOP
127           RETURN parent.place_id;
128         END LOOP;
129       END IF;
130     END LOOP;
131   END LOOP;
132
133   RETURN NULL;
134 END;
135 $$
136 LANGUAGE plpgsql STABLE;
137
138
139 -- Find the parent road of a POI.
140 --
141 -- \returns Place ID of parent object or NULL if none
142 --
143 -- Copy data from linked items (POIs on ways, addr:street links, relations).
144 --
145 CREATE OR REPLACE FUNCTION find_parent_for_poi(poi_osm_type CHAR(1),
146                                                poi_osm_id BIGINT,
147                                                poi_partition SMALLINT,
148                                                bbox GEOMETRY,
149                                                token_info JSONB,
150                                                is_place_addr BOOLEAN)
151   RETURNS BIGINT
152   AS $$
153 DECLARE
154   parent_place_id BIGINT DEFAULT NULL;
155   location RECORD;
156 BEGIN
157   {% if debug %}RAISE WARNING 'finding street for % %', poi_osm_type, poi_osm_id;{% endif %}
158
159   -- Is this object part of an associatedStreet relation?
160   parent_place_id := find_associated_street(poi_osm_type, poi_osm_id);
161
162   IF parent_place_id is null THEN
163     parent_place_id := find_parent_for_address(token_info, poi_partition, bbox);
164   END IF;
165
166   IF parent_place_id is null and poi_osm_type = 'N' THEN
167     FOR location IN
168       SELECT p.place_id, p.osm_id, p.rank_search, p.address,
169              coalesce(p.centroid, ST_Centroid(p.geometry)) as centroid
170         FROM placex p, planet_osm_ways w
171        WHERE p.osm_type = 'W' and p.rank_search >= 26
172              and p.geometry && bbox
173              and w.id = p.osm_id and poi_osm_id = any(w.nodes)
174     LOOP
175       {% if debug %}RAISE WARNING 'Node is part of way % ', location.osm_id;{% endif %}
176
177       -- Way IS a road then we are on it - that must be our road
178       IF location.rank_search < 28 THEN
179         {% if debug %}RAISE WARNING 'node in way that is a street %',location;{% endif %}
180         RETURN location.place_id;
181       END IF;
182
183       parent_place_id := find_associated_street('W', location.osm_id);
184     END LOOP;
185   END IF;
186
187   IF parent_place_id is NULL THEN
188     IF is_place_addr THEN
189       -- The address is attached to a place we don't know.
190       -- Instead simply use the containing area with the largest rank.
191       FOR location IN
192         SELECT place_id FROM placex
193          WHERE bbox && geometry AND _ST_Covers(geometry, ST_Centroid(bbox))
194                AND rank_address between 5 and 25
195          ORDER BY rank_address desc
196       LOOP
197         RETURN location.place_id;
198       END LOOP;
199     ELSEIF ST_Area(bbox) < 0.005 THEN
200       -- for smaller features get the nearest road
201       SELECT getNearestRoadPlaceId(poi_partition, bbox) INTO parent_place_id;
202       {% if debug %}RAISE WARNING 'Checked for nearest way (%)', parent_place_id;{% endif %}
203     ELSE
204       -- for larger features simply find the area with the largest rank that
205       -- contains the bbox, only use addressable features
206       FOR location IN
207         SELECT place_id FROM placex
208          WHERE bbox && geometry AND _ST_Covers(geometry, ST_Centroid(bbox))
209                AND rank_address between 5 and 25
210         ORDER BY rank_address desc
211       LOOP
212         RETURN location.place_id;
213       END LOOP;
214     END IF;
215   END IF;
216
217   RETURN parent_place_id;
218 END;
219 $$
220 LANGUAGE plpgsql STABLE;
221
222 -- Try to find a linked place for the given object.
223 CREATE OR REPLACE FUNCTION find_linked_place(bnd placex)
224   RETURNS placex
225   AS $$
226 DECLARE
227   relation_members TEXT[];
228   rel_member RECORD;
229   linked_placex placex%ROWTYPE;
230   bnd_name TEXT;
231 BEGIN
232   IF bnd.rank_search >= 26 or bnd.rank_address = 0
233      or ST_GeometryType(bnd.geometry) NOT IN ('ST_Polygon','ST_MultiPolygon')
234      or bnd.type IN ('postcode', 'postal_code')
235   THEN
236     RETURN NULL;
237   END IF;
238
239   IF bnd.osm_type = 'R' THEN
240     -- see if we have any special relation members
241     SELECT members FROM planet_osm_rels WHERE id = bnd.osm_id INTO relation_members;
242     {% if debug %}RAISE WARNING 'Got relation members';{% endif %}
243
244     -- Search for relation members with role 'lable'.
245     IF relation_members IS NOT NULL THEN
246       FOR rel_member IN
247         SELECT get_rel_node_members(relation_members, ARRAY['label']) as member
248       LOOP
249         {% if debug %}RAISE WARNING 'Found label member %', rel_member.member;{% endif %}
250
251         FOR linked_placex IN
252           SELECT * from placex
253           WHERE osm_type = 'N' and osm_id = rel_member.member
254             and class = 'place'
255         LOOP
256           {% if debug %}RAISE WARNING 'Linked label member';{% endif %}
257           RETURN linked_placex;
258         END LOOP;
259
260       END LOOP;
261     END IF;
262   END IF;
263
264   IF bnd.name ? 'name' THEN
265     bnd_name := lower(bnd.name->'name');
266     IF bnd_name = '' THEN
267       bnd_name := NULL;
268     END IF;
269   END IF;
270
271   -- If extratags has a place tag, look for linked nodes by their place type.
272   -- Area and node still have to have the same name.
273   IF bnd.extratags ? 'place' and bnd_name is not null THEN
274     FOR linked_placex IN
275       SELECT * FROM placex
276       WHERE (position(lower(name->'name') in bnd_name) > 0
277              OR position(bnd_name in lower(name->'name')) > 0)
278         AND placex.class = 'place' AND placex.type = bnd.extratags->'place'
279         AND placex.osm_type = 'N'
280         AND placex.linked_place_id is null
281         AND placex.rank_search < 26 -- needed to select the right index
282         AND placex.type != 'postcode'
283         AND ST_Covers(bnd.geometry, placex.geometry)
284     LOOP
285       {% if debug %}RAISE WARNING 'Found type-matching place node %', linked_placex.osm_id;{% endif %}
286       RETURN linked_placex;
287     END LOOP;
288   END IF;
289
290   IF bnd.extratags ? 'wikidata' THEN
291     FOR linked_placex IN
292       SELECT * FROM placex
293       WHERE placex.class = 'place' AND placex.osm_type = 'N'
294         AND placex.extratags ? 'wikidata' -- needed to select right index
295         AND placex.extratags->'wikidata' = bnd.extratags->'wikidata'
296         AND placex.linked_place_id is null
297         AND placex.rank_search < 26
298         AND _st_covers(bnd.geometry, placex.geometry)
299       ORDER BY lower(name->'name') = bnd_name desc
300     LOOP
301       {% if debug %}RAISE WARNING 'Found wikidata-matching place node %', linked_placex.osm_id;{% endif %}
302       RETURN linked_placex;
303     END LOOP;
304   END IF;
305
306   -- Name searches can be done for ways as well as relations
307   IF bnd_name is not null THEN
308     {% if debug %}RAISE WARNING 'Looking for nodes with matching names';{% endif %}
309     FOR linked_placex IN
310       SELECT placex.* from placex
311       WHERE lower(name->'name') = bnd_name
312         AND ((bnd.rank_address > 0
313               and bnd.rank_address = (compute_place_rank(placex.country_code,
314                                                          'N', placex.class,
315                                                          placex.type, 15::SMALLINT,
316                                                          false, placex.postcode)).address_rank)
317              OR (bnd.rank_address = 0 and placex.rank_search = bnd.rank_search))
318         AND placex.osm_type = 'N'
319         AND placex.class = 'place'
320         AND placex.linked_place_id is null
321         AND placex.rank_search < 26 -- needed to select the right index
322         AND placex.type != 'postcode'
323         AND ST_Covers(bnd.geometry, placex.geometry)
324     LOOP
325       {% if debug %}RAISE WARNING 'Found matching place node %', linked_placex.osm_id;{% endif %}
326       RETURN linked_placex;
327     END LOOP;
328   END IF;
329
330   RETURN NULL;
331 END;
332 $$
333 LANGUAGE plpgsql STABLE;
334
335
336 CREATE OR REPLACE FUNCTION create_poi_search_terms(obj_place_id BIGINT,
337                                                    in_partition SMALLINT,
338                                                    parent_place_id BIGINT,
339                                                    is_place_addr BOOLEAN,
340                                                    country TEXT,
341                                                    token_info JSONB,
342                                                    geometry GEOMETRY,
343                                                    OUT name_vector INTEGER[],
344                                                    OUT nameaddress_vector INTEGER[])
345   AS $$
346 DECLARE
347   parent_name_vector INTEGER[];
348   parent_address_vector INTEGER[];
349   addr_place_ids INTEGER[];
350   hnr_vector INTEGER[];
351
352   addr_item RECORD;
353   addr_place RECORD;
354   parent_address_place_ids BIGINT[];
355 BEGIN
356   nameaddress_vector := '{}'::INTEGER[];
357
358   SELECT s.name_vector, s.nameaddress_vector
359     INTO parent_name_vector, parent_address_vector
360     FROM search_name s
361     WHERE s.place_id = parent_place_id;
362
363   FOR addr_item IN
364     SELECT ranks.*, key,
365            token_get_address_search_tokens(token_info, key) as search_tokens
366       FROM token_get_address_keys(token_info) as key,
367            LATERAL get_addr_tag_rank(key, country) as ranks
368       WHERE not token_get_address_search_tokens(token_info, key) <@ parent_address_vector
369   LOOP
370     addr_place := get_address_place(in_partition, geometry,
371                                     addr_item.from_rank, addr_item.to_rank,
372                                     addr_item.extent, token_info, addr_item.key);
373
374     IF addr_place is null THEN
375       -- No place found in OSM that matches. Make it at least searchable.
376       nameaddress_vector := array_merge(nameaddress_vector, addr_item.search_tokens);
377     ELSE
378       IF parent_address_place_ids is null THEN
379         SELECT array_agg(parent_place_id) INTO parent_address_place_ids
380           FROM place_addressline
381           WHERE place_id = parent_place_id;
382       END IF;
383
384       -- If the parent already lists the place in place_address line, then we
385       -- are done. Otherwise, add its own place_address line.
386       IF not parent_address_place_ids @> ARRAY[addr_place.place_id] THEN
387         nameaddress_vector := array_merge(nameaddress_vector, addr_place.keywords);
388
389         INSERT INTO place_addressline (place_id, address_place_id, fromarea,
390                                        isaddress, distance, cached_rank_address)
391           VALUES (obj_place_id, addr_place.place_id, not addr_place.isguess,
392                     true, addr_place.distance, addr_place.rank_address);
393       END IF;
394     END IF;
395   END LOOP;
396
397   name_vector := token_get_name_search_tokens(token_info);
398
399   -- Check if the parent covers all address terms.
400   -- If not, create a search name entry with the house number as the name.
401   -- This is unusual for the search_name table but prevents that the place
402   -- is returned when we only search for the street/place.
403
404   hnr_vector := token_get_housenumber_search_tokens(token_info);
405
406   IF hnr_vector is not null and not nameaddress_vector <@ parent_address_vector THEN
407     name_vector := array_merge(name_vector, hnr_vector);
408   END IF;
409
410   IF is_place_addr THEN
411     addr_place_ids := token_addr_place_search_tokens(token_info);
412     IF not addr_place_ids <@ parent_name_vector THEN
413       -- make sure addr:place terms are always searchable
414       nameaddress_vector := array_merge(nameaddress_vector, addr_place_ids);
415       -- If there is a housenumber, also add the place name as a name,
416       -- so we can search it by the usual housenumber+place algorithms.
417       IF hnr_vector is not null THEN
418         name_vector := array_merge(name_vector, addr_place_ids);
419       END IF;
420     END IF;
421   END IF;
422
423   -- Cheating here by not recomputing all terms but simply using the ones
424   -- from the parent object.
425   nameaddress_vector := array_merge(nameaddress_vector, parent_name_vector);
426   nameaddress_vector := array_merge(nameaddress_vector, parent_address_vector);
427
428 END;
429 $$
430 LANGUAGE plpgsql;
431
432
433 -- Insert address of a place into the place_addressline table.
434 --
435 -- \param obj_place_id  Place_id of the place to compute the address for.
436 -- \param partition     Partition number where the place is in.
437 -- \param maxrank       Rank of the place. All address features must have
438 --                      a search rank lower than the given rank.
439 -- \param address       Address terms for the place.
440 -- \param geometry      Geometry to which the address objects should be close.
441 --
442 -- \retval parent_place_id  Place_id of the address object that is the direct
443 --                          ancestor.
444 -- \retval postcode         Postcode computed from the address. This is the
445 --                          addr:postcode of one of the address objects. If
446 --                          more than one of has a postcode, the highest ranking
447 --                          one is used. May be NULL.
448 -- \retval nameaddress_vector  Search terms for the address. This is the sum
449 --                             of name terms of all address objects.
450 CREATE OR REPLACE FUNCTION insert_addresslines(obj_place_id BIGINT,
451                                                partition SMALLINT,
452                                                maxrank SMALLINT,
453                                                token_info JSONB,
454                                                geometry GEOMETRY,
455                                                centroid GEOMETRY,
456                                                country TEXT,
457                                                OUT parent_place_id BIGINT,
458                                                OUT postcode TEXT,
459                                                OUT nameaddress_vector INT[])
460   AS $$
461 DECLARE
462   address_havelevel BOOLEAN[];
463
464   location_isaddress BOOLEAN;
465   current_boundary GEOMETRY := NULL;
466   current_node_area GEOMETRY := NULL;
467
468   parent_place_rank INT := 0;
469   addr_place_ids BIGINT[] := '{}'::int[];
470   new_address_vector INT[];
471
472   location RECORD;
473 BEGIN
474   parent_place_id := 0;
475   nameaddress_vector := '{}'::int[];
476
477   address_havelevel := array_fill(false, ARRAY[maxrank]);
478
479   FOR location IN
480     SELECT apl.*, key
481       FROM (SELECT extra.*, key
482               FROM token_get_address_keys(token_info) as key,
483                    LATERAL get_addr_tag_rank(key, country) as extra) x,
484            LATERAL get_address_place(partition, geometry, from_rank, to_rank,
485                               extent, token_info, key) as apl
486       ORDER BY rank_address, distance, isguess desc
487   LOOP
488     IF location.place_id is null THEN
489       {% if not db.reverse_only %}
490       nameaddress_vector := array_merge(nameaddress_vector,
491                                         token_get_address_search_tokens(token_info,
492                                                                         location.key));
493       {% endif %}
494     ELSE
495       {% if not db.reverse_only %}
496       nameaddress_vector := array_merge(nameaddress_vector, location.keywords::INTEGER[]);
497       {% endif %}
498
499       location_isaddress := not address_havelevel[location.rank_address];
500       IF not address_havelevel[location.rank_address] THEN
501         address_havelevel[location.rank_address] := true;
502         IF parent_place_rank < location.rank_address THEN
503           parent_place_id := location.place_id;
504           parent_place_rank := location.rank_address;
505         END IF;
506       END IF;
507
508       INSERT INTO place_addressline (place_id, address_place_id, fromarea,
509                                      isaddress, distance, cached_rank_address)
510         VALUES (obj_place_id, location.place_id, not location.isguess,
511                 true, location.distance, location.rank_address);
512
513       addr_place_ids := addr_place_ids || location.place_id;
514     END IF;
515   END LOOP;
516
517   FOR location IN
518     SELECT * FROM getNearFeatures(partition, geometry, centroid, maxrank)
519     WHERE not addr_place_ids @> ARRAY[place_id]
520     ORDER BY rank_address, isguess asc,
521              distance *
522                CASE WHEN rank_address = 16 AND rank_search = 15 THEN 0.2
523                     WHEN rank_address = 16 AND rank_search = 16 THEN 0.25
524                     WHEN rank_address = 16 AND rank_search = 18 THEN 0.5
525                     ELSE 1 END ASC
526   LOOP
527     -- Ignore all place nodes that do not fit in a lower level boundary.
528     CONTINUE WHEN location.isguess
529                   and current_boundary is not NULL
530                   and not ST_Contains(current_boundary, location.centroid);
531
532     -- If this is the first item in the rank, then assume it is the address.
533     location_isaddress := not address_havelevel[location.rank_address];
534
535     -- Further sanity checks to ensure that the address forms a sane hierarchy.
536     IF location_isaddress THEN
537       IF location.isguess and current_node_area is not NULL THEN
538         location_isaddress := ST_Contains(current_node_area, location.centroid);
539       END IF;
540       IF not location.isguess and current_boundary is not NULL
541          and location.rank_address != 11 AND location.rank_address != 5 THEN
542         location_isaddress := ST_Contains(current_boundary, location.centroid);
543       END IF;
544     END IF;
545
546     IF location_isaddress THEN
547       address_havelevel[location.rank_address] := true;
548       parent_place_id := location.place_id;
549
550       -- Set postcode if we have one.
551       -- (Returned will be the highest ranking one.)
552       IF location.postcode is not NULL THEN
553         postcode = location.postcode;
554       END IF;
555
556       -- Recompute the areas we need for hierarchy sanity checks.
557       IF location.rank_address != 11 AND location.rank_address != 5 THEN
558         IF location.isguess THEN
559           current_node_area := place_node_fuzzy_area(location.centroid,
560                                                      location.rank_search);
561         ELSE
562           current_node_area := NULL;
563           SELECT p.geometry FROM placex p
564               WHERE p.place_id = location.place_id INTO current_boundary;
565         END IF;
566       END IF;
567     END IF;
568
569     -- Add it to the list of search terms
570     {% if not db.reverse_only %}
571       nameaddress_vector := array_merge(nameaddress_vector,
572                                         location.keywords::integer[]);
573     {% endif %}
574
575     INSERT INTO place_addressline (place_id, address_place_id, fromarea,
576                                      isaddress, distance, cached_rank_address)
577         VALUES (obj_place_id, location.place_id, not location.isguess,
578                 location_isaddress, location.distance, location.rank_address);
579   END LOOP;
580 END;
581 $$
582 LANGUAGE plpgsql;
583
584
585 CREATE OR REPLACE FUNCTION placex_insert()
586   RETURNS TRIGGER
587   AS $$
588 DECLARE
589   postcode TEXT;
590   result BOOLEAN;
591   is_area BOOLEAN;
592   country_code VARCHAR(2);
593   diameter FLOAT;
594   classtable TEXT;
595 BEGIN
596   {% if debug %}RAISE WARNING '% % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type;{% endif %}
597
598   NEW.place_id := nextval('seq_place');
599   NEW.indexed_status := 1; --STATUS_NEW
600
601   NEW.centroid := ST_PointOnSurface(NEW.geometry);
602   NEW.country_code := lower(get_country_code(NEW.centroid));
603
604   NEW.partition := get_partition(NEW.country_code);
605   NEW.geometry_sector := geometry_sector(NEW.partition, NEW.centroid);
606
607   IF NEW.osm_type = 'X' THEN
608     -- E'X'ternal records should already be in the right format so do nothing
609   ELSE
610     is_area := ST_GeometryType(NEW.geometry) IN ('ST_Polygon','ST_MultiPolygon');
611
612     IF NEW.class in ('place','boundary')
613        AND NEW.type in ('postcode','postal_code')
614     THEN
615       IF NEW.address IS NULL OR NOT NEW.address ? 'postcode' THEN
616           -- most likely just a part of a multipolygon postcode boundary, throw it away
617           RETURN NULL;
618       END IF;
619
620       NEW.name := hstore('ref', NEW.address->'postcode');
621
622     ELSEIF NEW.class = 'highway' AND is_area AND NEW.name is null
623            AND NEW.extratags ? 'area' AND NEW.extratags->'area' = 'yes'
624     THEN
625         RETURN NULL;
626     ELSEIF NEW.class = 'boundary' AND NOT is_area
627     THEN
628         RETURN NULL;
629     ELSEIF NEW.class = 'boundary' AND NEW.type = 'administrative'
630            AND NEW.admin_level <= 4 AND NEW.osm_type = 'W'
631     THEN
632         RETURN NULL;
633     END IF;
634
635     SELECT * INTO NEW.rank_search, NEW.rank_address
636       FROM compute_place_rank(NEW.country_code,
637                               CASE WHEN is_area THEN 'A' ELSE NEW.osm_type END,
638                               NEW.class, NEW.type, NEW.admin_level,
639                               (NEW.extratags->'capital') = 'yes',
640                               NEW.address->'postcode');
641
642     -- a country code make no sense below rank 4 (country)
643     IF NEW.rank_search < 4 THEN
644       NEW.country_code := NULL;
645     END IF;
646
647   END IF;
648
649   {% if debug %}RAISE WARNING 'placex_insert:END: % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type;{% endif %}
650
651 {% if not disable_diff_updates %}
652   -- The following is not needed until doing diff updates, and slows the main index process down
653
654   IF NEW.rank_address > 0 THEN
655     IF (ST_GeometryType(NEW.geometry) in ('ST_Polygon','ST_MultiPolygon') AND ST_IsValid(NEW.geometry)) THEN
656       -- Performance: We just can't handle re-indexing for country level changes
657       IF st_area(NEW.geometry) < 1 THEN
658         -- mark items within the geometry for re-indexing
659   --    RAISE WARNING 'placex poly insert: % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type;
660
661         UPDATE placex SET indexed_status = 2
662          WHERE ST_Intersects(NEW.geometry, placex.geometry)
663                and indexed_status = 0
664                and ((rank_address = 0 and rank_search > NEW.rank_address)
665                     or rank_address > NEW.rank_address
666                     or (class = 'place' and osm_type = 'N')
667                    )
668                and (rank_search < 28
669                     or name is not null
670                     or (NEW.rank_address >= 16 and address ? 'place'));
671       END IF;
672     ELSE
673       -- mark nearby items for re-indexing, where 'nearby' depends on the features rank_search and is a complete guess :(
674       diameter := update_place_diameter(NEW.rank_search);
675       IF diameter > 0 THEN
676   --      RAISE WARNING 'placex point insert: % % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type,diameter;
677         IF NEW.rank_search >= 26 THEN
678           -- roads may cause reparenting for >27 rank places
679           update placex set indexed_status = 2 where indexed_status = 0 and rank_search > NEW.rank_search and ST_DWithin(placex.geometry, NEW.geometry, diameter);
680           -- reparenting also for OSM Interpolation Lines (and for Tiger?)
681           update location_property_osmline set indexed_status = 2 where indexed_status = 0 and startnumber is not null and ST_DWithin(location_property_osmline.linegeo, NEW.geometry, diameter);
682         ELSEIF NEW.rank_search >= 16 THEN
683           -- up to rank 16, street-less addresses may need reparenting
684           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');
685         ELSE
686           -- for all other places the search terms may change as well
687           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);
688         END IF;
689       END IF;
690     END IF;
691   END IF;
692
693
694    -- add to tables for special search
695    -- Note: won't work on initial import because the classtype tables
696    -- do not yet exist. It won't hurt either.
697   classtable := 'place_classtype_' || NEW.class || '_' || NEW.type;
698   SELECT count(*)>0 FROM pg_tables WHERE tablename = classtable and schemaname = current_schema() INTO result;
699   IF result THEN
700     EXECUTE 'INSERT INTO ' || classtable::regclass || ' (place_id, centroid) VALUES ($1,$2)' 
701     USING NEW.place_id, ST_Centroid(NEW.geometry);
702   END IF;
703
704 {% endif %} -- not disable_diff_updates
705
706   RETURN NEW;
707
708 END;
709 $$
710 LANGUAGE plpgsql;
711
712 CREATE OR REPLACE FUNCTION placex_update()
713   RETURNS TRIGGER
714   AS $$
715 DECLARE
716   i INTEGER;
717   location RECORD;
718   relation_members TEXT[];
719
720   geom GEOMETRY;
721   parent_address_level SMALLINT;
722   place_address_level SMALLINT;
723
724   max_rank SMALLINT;
725
726   name_vector INTEGER[];
727   nameaddress_vector INTEGER[];
728   addr_nameaddress_vector INTEGER[];
729
730   linked_place BIGINT;
731
732   linked_node_id BIGINT;
733   linked_importance FLOAT;
734   linked_wikipedia TEXT;
735
736   is_place_address BOOLEAN;
737   result BOOLEAN;
738 BEGIN
739   -- deferred delete
740   IF OLD.indexed_status = 100 THEN
741     {% if debug %}RAISE WARNING 'placex_update delete % %',NEW.osm_type,NEW.osm_id;{% endif %}
742     delete from placex where place_id = OLD.place_id;
743     RETURN NULL;
744   END IF;
745
746   IF NEW.indexed_status != 0 OR OLD.indexed_status = 0 THEN
747     RETURN NEW;
748   END IF;
749
750   {% if debug %}RAISE WARNING 'placex_update % % (%)',NEW.osm_type,NEW.osm_id,NEW.place_id;{% endif %}
751
752   NEW.indexed_date = now();
753
754   {% if 'search_name' in db.tables %}
755     DELETE from search_name WHERE place_id = NEW.place_id;
756   {% endif %}
757   result := deleteSearchName(NEW.partition, NEW.place_id);
758   DELETE FROM place_addressline WHERE place_id = NEW.place_id;
759   result := deleteRoad(NEW.partition, NEW.place_id);
760   result := deleteLocationArea(NEW.partition, NEW.place_id, NEW.rank_search);
761   UPDATE placex set linked_place_id = null, indexed_status = 2
762          where linked_place_id = NEW.place_id;
763   -- update not necessary for osmline, cause linked_place_id does not exist
764
765   NEW.extratags := NEW.extratags - 'linked_place'::TEXT;
766
767   -- NEW.linked_place_id contains the precomputed linkee. Save this and restore
768   -- the previous link status.
769   linked_place := NEW.linked_place_id;
770   NEW.linked_place_id := OLD.linked_place_id;
771
772   IF NEW.linked_place_id is not null THEN
773     NEW.token_info := null;
774     {% if debug %}RAISE WARNING 'place already linked to %', OLD.linked_place_id;{% endif %}
775     RETURN NEW;
776   END IF;
777
778   -- Postcodes are just here to compute the centroids. They are not searchable
779   -- unless they are a boundary=postal_code.
780   -- There was an error in the style so that boundary=postal_code used to be
781   -- imported as place=postcode. That's why relations are allowed to pass here.
782   -- This can go away in a couple of versions.
783   IF NEW.class = 'place'  and NEW.type = 'postcode' and NEW.osm_type != 'R' THEN
784     NEW.token_info := null;
785     RETURN NEW;
786   END IF;
787
788   -- Compute a preliminary centroid.
789   NEW.centroid := ST_PointOnSurface(NEW.geometry);
790
791     -- recalculate country and partition
792   IF NEW.rank_search = 4 AND NEW.address is not NULL AND NEW.address ? 'country' THEN
793     -- for countries, believe the mapped country code,
794     -- so that we remain in the right partition if the boundaries
795     -- suddenly expand.
796     NEW.country_code := lower(NEW.address->'country');
797     NEW.partition := get_partition(lower(NEW.country_code));
798     IF NEW.partition = 0 THEN
799       NEW.country_code := lower(get_country_code(NEW.centroid));
800       NEW.partition := get_partition(NEW.country_code);
801     END IF;
802   ELSE
803     IF NEW.rank_search >= 4 THEN
804       NEW.country_code := lower(get_country_code(NEW.centroid));
805     ELSE
806       NEW.country_code := NULL;
807     END IF;
808     NEW.partition := get_partition(NEW.country_code);
809   END IF;
810   {% if debug %}RAISE WARNING 'Country updated: "%"', NEW.country_code;{% endif %}
811
812
813   -- recompute the ranks, they might change when linking changes
814   SELECT * INTO NEW.rank_search, NEW.rank_address
815     FROM compute_place_rank(NEW.country_code,
816                             CASE WHEN ST_GeometryType(NEW.geometry)
817                                         IN ('ST_Polygon','ST_MultiPolygon')
818                             THEN 'A' ELSE NEW.osm_type END,
819                             NEW.class, NEW.type, NEW.admin_level,
820                             (NEW.extratags->'capital') = 'yes',
821                             NEW.address->'postcode');
822   -- We must always increase the address level relative to the admin boundary.
823   IF NEW.class = 'boundary' and NEW.type = 'administrative'
824      and NEW.osm_type = 'R' and NEW.rank_address > 0
825   THEN
826     -- First, check that admin boundaries do not overtake each other rank-wise.
827     parent_address_level := 3;
828     FOR location IN
829       SELECT rank_address,
830              (CASE WHEN extratags ? 'wikidata' and NEW.extratags ? 'wikidata'
831                         and extratags->'wikidata' = NEW.extratags->'wikidata'
832                    THEN ST_Equals(geometry, NEW.geometry)
833                    ELSE false END) as is_same
834       FROM placex
835       WHERE osm_type = 'R' and class = 'boundary' and type = 'administrative'
836             and admin_level < NEW.admin_level and admin_level > 3
837             and rank_address > 0
838             and geometry && NEW.centroid and _ST_Covers(geometry, NEW.centroid)
839       ORDER BY admin_level desc LIMIT 1
840     LOOP
841       IF location.is_same THEN
842         -- Looks like the same boundary is replicated on multiple admin_levels.
843         -- Usual tagging in Poland. Remove our boundary from addresses.
844         NEW.rank_address := 0;
845       ELSE
846         parent_address_level := location.rank_address;
847         IF location.rank_address >= NEW.rank_address THEN
848           IF location.rank_address >= 24 THEN
849             NEW.rank_address := 25;
850           ELSE
851             NEW.rank_address := location.rank_address + 2;
852           END IF;
853         END IF;
854       END IF;
855     END LOOP;
856
857     IF NEW.rank_address > 9 THEN
858         -- Second check that the boundary is not completely contained in a
859         -- place area with a higher address rank
860         FOR location IN
861           SELECT rank_address FROM placex
862           WHERE class = 'place' and rank_address < 24
863                 and rank_address > NEW.rank_address
864                 and geometry && NEW.geometry
865                 and geometry ~ NEW.geometry -- needed because ST_Relate does not do bbox cover test
866                 and ST_Relate(geometry, NEW.geometry, 'T*T***FF*') -- contains but not equal
867           ORDER BY rank_address desc LIMIT 1
868         LOOP
869           NEW.rank_address := location.rank_address + 2;
870         END LOOP;
871     END IF;
872   ELSEIF NEW.class = 'place' and NEW.osm_type = 'N'
873      and NEW.rank_address between 16 and 23
874   THEN
875     -- If a place node is contained in a admin boundary with the same address level
876     -- and has not been linked, then make the node a subpart by increasing the
877     -- address rank (city level and above).
878     FOR location IN
879         SELECT rank_address FROM placex
880         WHERE osm_type = 'R' and class = 'boundary' and type = 'administrative'
881               and rank_address = NEW.rank_address
882               and geometry && NEW.centroid and _ST_Covers(geometry, NEW.centroid)
883         LIMIT 1
884     LOOP
885       NEW.rank_address = NEW.rank_address + 2;
886     END LOOP;
887   ELSE
888     parent_address_level := 3;
889   END IF;
890
891   NEW.housenumber := token_normalized_housenumber(NEW.token_info);
892
893   NEW.postcode := null;
894
895   -- waterway ways are linked when they are part of a relation and have the same class/type
896   IF NEW.osm_type = 'R' and NEW.class = 'waterway' THEN
897       FOR relation_members IN select members from planet_osm_rels r where r.id = NEW.osm_id and r.parts != array[]::bigint[]
898       LOOP
899           FOR i IN 1..array_upper(relation_members, 1) BY 2 LOOP
900               IF relation_members[i+1] in ('', 'main_stream', 'side_stream') AND substring(relation_members[i],1,1) = 'w' THEN
901                 {% if debug %}RAISE WARNING 'waterway parent %, child %/%', NEW.osm_id, i, relation_members[i];{% endif %}
902                 FOR linked_node_id IN SELECT place_id FROM placex
903                   WHERE osm_type = 'W' and osm_id = substring(relation_members[i],2,200)::bigint
904                   and class = NEW.class and type in ('river', 'stream', 'canal', 'drain', 'ditch')
905                   and ( relation_members[i+1] != 'side_stream' or NEW.name->'name' = name->'name')
906                 LOOP
907                   UPDATE placex SET linked_place_id = NEW.place_id WHERE place_id = linked_node_id;
908                   {% if 'search_name' in db.tables %}
909                     DELETE FROM search_name WHERE place_id = linked_node_id;
910                   {% endif %}
911                 END LOOP;
912               END IF;
913           END LOOP;
914       END LOOP;
915       {% if debug %}RAISE WARNING 'Waterway processed';{% endif %}
916   END IF;
917
918   NEW.importance := null;
919   SELECT wikipedia, importance
920     FROM compute_importance(NEW.extratags, NEW.country_code, NEW.osm_type, NEW.osm_id)
921     INTO NEW.wikipedia,NEW.importance;
922
923 {% if debug %}RAISE WARNING 'Importance computed from wikipedia: %', NEW.importance;{% endif %}
924
925   -- ---------------------------------------------------------------------------
926   -- For low level elements we inherit from our parent road
927   IF NEW.rank_search > 27 THEN
928
929     {% if debug %}RAISE WARNING 'finding street for % %', NEW.osm_type, NEW.osm_id;{% endif %}
930     NEW.parent_place_id := null;
931     is_place_address := coalesce(not NEW.address ? 'street' and NEW.address ? 'place', FALSE);
932
933     -- We have to find our parent road.
934     NEW.parent_place_id := find_parent_for_poi(NEW.osm_type, NEW.osm_id,
935                                                NEW.partition,
936                                                ST_Envelope(NEW.geometry),
937                                                NEW.token_info,
938                                                is_place_address);
939
940     -- If we found the road take a shortcut here.
941     -- Otherwise fall back to the full address getting method below.
942     IF NEW.parent_place_id is not null THEN
943
944       -- Get the details of the parent road
945       SELECT p.country_code, p.postcode, p.name FROM placex p
946        WHERE p.place_id = NEW.parent_place_id INTO location;
947
948       IF is_place_address THEN
949         -- Check if the addr:place tag is part of the parent name
950         SELECT count(*) INTO i
951           FROM svals(location.name) AS pname WHERE pname = NEW.address->'place';
952         IF i = 0 THEN
953           NEW.address = NEW.address || hstore('_unlisted_place', NEW.address->'place');
954         END IF;
955       END IF;
956
957       NEW.country_code := location.country_code;
958       {% if debug %}RAISE WARNING 'Got parent details from search name';{% endif %}
959
960       -- determine postcode
961       NEW.postcode := coalesce(token_normalized_postcode(NEW.address->'postcode'),
962                                location.postcode,
963                                get_nearest_postcode(NEW.country_code, NEW.centroid));
964
965       IF NEW.name is not NULL THEN
966           NEW.name := add_default_place_name(NEW.country_code, NEW.name);
967       END IF;
968
969       {% if not db.reverse_only %}
970       IF NEW.name is not NULL OR NEW.address is not NULL THEN
971         SELECT * INTO name_vector, nameaddress_vector
972           FROM create_poi_search_terms(NEW.place_id,
973                                        NEW.partition, NEW.parent_place_id,
974                                        is_place_address, NEW.country_code,
975                                        NEW.token_info, NEW.centroid);
976
977         IF array_length(name_vector, 1) is not NULL THEN
978           INSERT INTO search_name (place_id, search_rank, address_rank,
979                                    importance, country_code, name_vector,
980                                    nameaddress_vector, centroid)
981                  VALUES (NEW.place_id, NEW.rank_search, NEW.rank_address,
982                          NEW.importance, NEW.country_code, name_vector,
983                          nameaddress_vector, NEW.centroid);
984           {% if debug %}RAISE WARNING 'Place added to search table';{% endif %}
985         END IF;
986       END IF;
987       {% endif %}
988
989       NEW.token_info := token_strip_info(NEW.token_info);
990
991       RETURN NEW;
992     END IF;
993
994   END IF;
995
996   -- ---------------------------------------------------------------------------
997   -- Full indexing
998   {% if debug %}RAISE WARNING 'Using full index mode for % %', NEW.osm_type, NEW.osm_id;{% endif %}
999   IF linked_place is not null THEN
1000     SELECT * INTO location FROM placex WHERE place_id = linked_place;
1001
1002     {% if debug %}RAISE WARNING 'Linked %', location;{% endif %}
1003
1004     -- Use the linked point as the centre point of the geometry,
1005     -- but only if it is within the area of the boundary.
1006     geom := coalesce(location.centroid, ST_Centroid(location.geometry));
1007     IF geom is not NULL AND ST_Within(geom, NEW.geometry) THEN
1008         NEW.centroid := geom;
1009     END IF;
1010
1011     {% if debug %}RAISE WARNING 'parent address: % rank address: %', parent_address_level, location.rank_address;{% endif %}
1012     IF location.rank_address > parent_address_level
1013        and location.rank_address < 26
1014     THEN
1015       NEW.rank_address := location.rank_address;
1016     END IF;
1017
1018     -- merge in extra tags
1019     NEW.extratags := hstore('linked_' || location.class, location.type)
1020                      || coalesce(location.extratags, ''::hstore)
1021                      || coalesce(NEW.extratags, ''::hstore);
1022
1023     -- mark the linked place (excludes from search results)
1024     UPDATE placex set linked_place_id = NEW.place_id
1025       WHERE place_id = location.place_id;
1026     -- ensure that those places are not found anymore
1027     {% if 'search_name' in db.tables %}
1028       DELETE FROM search_name WHERE place_id = location.place_id;
1029     {% endif %}
1030     PERFORM deleteLocationArea(NEW.partition, location.place_id, NEW.rank_search);
1031
1032     SELECT wikipedia, importance
1033       FROM compute_importance(location.extratags, NEW.country_code,
1034                               'N', location.osm_id)
1035       INTO linked_wikipedia,linked_importance;
1036
1037     -- Use the maximum importance if one could be computed from the linked object.
1038     IF linked_importance is not null AND
1039        (NEW.importance is null or NEW.importance < linked_importance)
1040     THEN
1041       NEW.importance = linked_importance;
1042     END IF;
1043   ELSE
1044     -- No linked place? As a last resort check if the boundary is tagged with
1045     -- a place type and adapt the rank address.
1046     IF NEW.rank_address > 0 and NEW.extratags ? 'place' THEN
1047       SELECT address_rank INTO place_address_level
1048         FROM compute_place_rank(NEW.country_code, 'A', 'place',
1049                                 NEW.extratags->'place', 0::SMALLINT, False, null);
1050       IF place_address_level > parent_address_level and
1051          place_address_level < 26 THEN
1052         NEW.rank_address := place_address_level;
1053       END IF;
1054     END IF;
1055   END IF;
1056
1057   IF NEW.admin_level = 2
1058      AND NEW.class = 'boundary' AND NEW.type = 'administrative'
1059      AND NEW.country_code IS NOT NULL AND NEW.osm_type = 'R'
1060   THEN
1061     -- Update the list of country names.
1062     -- Only take the name from the largest area for the given country code
1063     -- in the hope that this is the authoritive one.
1064     -- Also replace any old names so that all mapping mistakes can
1065     -- be fixed through regular OSM updates.
1066     FOR location IN
1067       SELECT osm_id FROM placex
1068        WHERE rank_search = 4 and osm_type = 'R'
1069              and country_code = NEW.country_code
1070        ORDER BY ST_Area(geometry) desc
1071        LIMIT 1
1072     LOOP
1073       IF location.osm_id = NEW.osm_id THEN
1074         {% if debug %}RAISE WARNING 'Updating names for country '%' with: %', NEW.country_code, NEW.name;{% endif %}
1075         UPDATE country_name SET derived_name = NEW.name WHERE country_code = NEW.country_code;
1076       END IF;
1077     END LOOP;
1078   END IF;
1079
1080   -- For linear features we need the full geometry for determining the address
1081   -- because they may go through several administrative entities. Otherwise use
1082   -- the centroid for performance reasons.
1083   IF ST_GeometryType(NEW.geometry) in ('ST_LineString', 'ST_MultiLineString') THEN
1084     geom := NEW.geometry;
1085   ELSE
1086     geom := NEW.centroid;
1087   END IF;
1088
1089   IF NEW.rank_address = 0 THEN
1090     max_rank := geometry_to_rank(NEW.rank_search, NEW.geometry, NEW.country_code);
1091     -- Rank 0 features may also span multiple administrative areas (e.g. lakes)
1092     -- so use the geometry here too. Just make sure the areas don't become too
1093     -- large.
1094     IF NEW.class = 'natural' or max_rank > 10 THEN
1095       geom := NEW.geometry;
1096     END IF;
1097   ELSEIF NEW.rank_address > 25 THEN
1098     max_rank := 25;
1099   ELSE
1100     max_rank := NEW.rank_address;
1101   END IF;
1102
1103   SELECT * FROM insert_addresslines(NEW.place_id, NEW.partition, max_rank,
1104                                     NEW.token_info, geom, NEW.centroid,
1105                                     NEW.country_code)
1106     INTO NEW.parent_place_id, NEW.postcode, nameaddress_vector;
1107
1108   {% if debug %}RAISE WARNING 'RETURN insert_addresslines: %, %, %', NEW.parent_place_id, NEW.postcode, nameaddress_vector;{% endif %}
1109
1110   NEW.postcode := coalesce(token_normalized_postcode(NEW.address->'postcode'),
1111                            NEW.postcode);
1112
1113   -- if we have a name add this to the name search table
1114   IF NEW.name IS NOT NULL THEN
1115     -- Initialise the name vector using our name
1116     NEW.name := add_default_place_name(NEW.country_code, NEW.name);
1117     name_vector := token_get_name_search_tokens(NEW.token_info);
1118
1119     IF NEW.rank_search <= 25 and NEW.rank_address > 0 THEN
1120       result := add_location(NEW.place_id, NEW.country_code, NEW.partition,
1121                              name_vector, NEW.rank_search, NEW.rank_address,
1122                              NEW.postcode, NEW.geometry, NEW.centroid);
1123       {% if debug %}RAISE WARNING 'added to location (full)';{% endif %}
1124     END IF;
1125
1126     IF NEW.rank_search between 26 and 27 and NEW.class = 'highway' THEN
1127       result := insertLocationRoad(NEW.partition, NEW.place_id, NEW.country_code, NEW.geometry);
1128       {% if debug %}RAISE WARNING 'insert into road location table (full)';{% endif %}
1129     END IF;
1130
1131     IF NEW.rank_address between 16 and 27 THEN
1132       result := insertSearchName(NEW.partition, NEW.place_id,
1133                                  token_get_name_match_tokens(NEW.token_info),
1134                                  NEW.rank_search, NEW.rank_address, NEW.geometry);
1135     END IF;
1136     {% if debug %}RAISE WARNING 'added to search name (full)';{% endif %}
1137
1138     {% if not db.reverse_only %}
1139         INSERT INTO search_name (place_id, search_rank, address_rank,
1140                                  importance, country_code, name_vector,
1141                                  nameaddress_vector, centroid)
1142                VALUES (NEW.place_id, NEW.rank_search, NEW.rank_address,
1143                        NEW.importance, NEW.country_code, name_vector,
1144                        nameaddress_vector, NEW.centroid);
1145     {% endif %}
1146   END IF;
1147
1148   IF NEW.postcode is null AND NEW.rank_search > 8 THEN
1149     NEW.postcode := get_nearest_postcode(NEW.country_code, NEW.geometry);
1150   END IF;
1151
1152   {% if debug %}RAISE WARNING 'place update % % finsihed.', NEW.osm_type, NEW.osm_id;{% endif %}
1153
1154   NEW.token_info := token_strip_info(NEW.token_info);
1155   RETURN NEW;
1156 END;
1157 $$
1158 LANGUAGE plpgsql;
1159
1160
1161 CREATE OR REPLACE FUNCTION placex_delete()
1162   RETURNS TRIGGER
1163   AS $$
1164 DECLARE
1165   b BOOLEAN;
1166   classtable TEXT;
1167 BEGIN
1168   -- RAISE WARNING 'placex_delete % %',OLD.osm_type,OLD.osm_id;
1169
1170   IF OLD.linked_place_id is null THEN
1171     update placex set linked_place_id = null, indexed_status = 2 where linked_place_id = OLD.place_id and indexed_status = 0;
1172     {% if debug %}RAISE WARNING 'placex_delete:01 % %',OLD.osm_type,OLD.osm_id;{% endif %}
1173     update placex set linked_place_id = null where linked_place_id = OLD.place_id;
1174     {% if debug %}RAISE WARNING 'placex_delete:02 % %',OLD.osm_type,OLD.osm_id;{% endif %}
1175   ELSE
1176     update placex set indexed_status = 2 where place_id = OLD.linked_place_id and indexed_status = 0;
1177   END IF;
1178
1179   IF OLD.rank_address < 30 THEN
1180
1181     -- mark everything linked to this place for re-indexing
1182     {% if debug %}RAISE WARNING 'placex_delete:03 % %',OLD.osm_type,OLD.osm_id;{% endif %}
1183     UPDATE placex set indexed_status = 2 from place_addressline where address_place_id = OLD.place_id 
1184       and placex.place_id = place_addressline.place_id and indexed_status = 0 and place_addressline.isaddress;
1185
1186     {% if debug %}RAISE WARNING 'placex_delete:04 % %',OLD.osm_type,OLD.osm_id;{% endif %}
1187     DELETE FROM place_addressline where address_place_id = OLD.place_id;
1188
1189     {% if debug %}RAISE WARNING 'placex_delete:05 % %',OLD.osm_type,OLD.osm_id;{% endif %}
1190     b := deleteRoad(OLD.partition, OLD.place_id);
1191
1192     {% if debug %}RAISE WARNING 'placex_delete:06 % %',OLD.osm_type,OLD.osm_id;{% endif %}
1193     update placex set indexed_status = 2 where parent_place_id = OLD.place_id and indexed_status = 0;
1194     {% if debug %}RAISE WARNING 'placex_delete:07 % %',OLD.osm_type,OLD.osm_id;{% endif %}
1195     -- reparenting also for OSM Interpolation Lines (and for Tiger?)
1196     update location_property_osmline set indexed_status = 2 where indexed_status = 0 and parent_place_id = OLD.place_id;
1197
1198   END IF;
1199
1200   {% if debug %}RAISE WARNING 'placex_delete:08 % %',OLD.osm_type,OLD.osm_id;{% endif %}
1201
1202   IF OLD.rank_address < 26 THEN
1203     b := deleteLocationArea(OLD.partition, OLD.place_id, OLD.rank_search);
1204   END IF;
1205
1206   {% if debug %}RAISE WARNING 'placex_delete:09 % %',OLD.osm_type,OLD.osm_id;{% endif %}
1207
1208   IF OLD.name is not null THEN
1209     {% if 'search_name' in db.tables %}
1210       DELETE from search_name WHERE place_id = OLD.place_id;
1211     {% endif %}
1212     b := deleteSearchName(OLD.partition, OLD.place_id);
1213   END IF;
1214
1215   {% if debug %}RAISE WARNING 'placex_delete:10 % %',OLD.osm_type,OLD.osm_id;{% endif %}
1216
1217   DELETE FROM place_addressline where place_id = OLD.place_id;
1218
1219   {% if debug %}RAISE WARNING 'placex_delete:11 % %',OLD.osm_type,OLD.osm_id;{% endif %}
1220
1221   -- remove from tables for special search
1222   classtable := 'place_classtype_' || OLD.class || '_' || OLD.type;
1223   SELECT count(*)>0 FROM pg_tables WHERE tablename = classtable and schemaname = current_schema() INTO b;
1224   IF b THEN
1225     EXECUTE 'DELETE FROM ' || classtable::regclass || ' WHERE place_id = $1' USING OLD.place_id;
1226   END IF;
1227
1228   {% if debug %}RAISE WARNING 'placex_delete:12 % %',OLD.osm_type,OLD.osm_id;{% endif %}
1229
1230   RETURN OLD;
1231
1232 END;
1233 $$
1234 LANGUAGE plpgsql;