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