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