1 -- Splits the line at the given point and returns the two parts
2 -- in a multilinestring.
3 CREATE OR REPLACE FUNCTION split_line_on_node(line GEOMETRY, point GEOMETRY)
7 RETURN ST_Split(ST_Snap(line, point, 0.0005), point);
13 CREATE OR REPLACE FUNCTION geometry_sector(partition INTEGER, place geometry) RETURNS INTEGER
18 -- RAISE WARNING '%',place;
19 NEWgeometry := ST_PointOnSurface(place);
20 RETURN (partition*1000000) + (500-ST_X(NEWgeometry)::integer)*1000 + (500-ST_Y(NEWgeometry)::integer);
23 LANGUAGE plpgsql IMMUTABLE;
26 CREATE OR REPLACE FUNCTION array_merge(a INTEGER[], b INTEGER[])
33 IF array_upper(a, 1) IS NULL THEN
36 IF array_upper(b, 1) IS NULL THEN
40 FOR i IN 1..array_upper(b, 1) LOOP
41 IF NOT (ARRAY[b[i]] <@ r) THEN
48 LANGUAGE plpgsql IMMUTABLE;
50 CREATE OR REPLACE FUNCTION reverse_place_diameter(rank_search SMALLINT)
54 IF rank_search <= 4 THEN
56 ELSIF rank_search <= 8 THEN
58 ELSIF rank_search <= 12 THEN
60 ELSIF rank_search <= 17 THEN
62 ELSIF rank_search <= 18 THEN
64 ELSIF rank_search <= 19 THEN
71 LANGUAGE plpgsql IMMUTABLE;
73 CREATE OR REPLACE FUNCTION get_postcode_rank(country_code VARCHAR(2), postcode TEXT,
74 OUT rank_search SMALLINT, OUT rank_address SMALLINT)
81 postcode := upper(postcode);
83 IF country_code = 'gb' THEN
84 IF postcode ~ '^([A-Z][A-Z]?[0-9][0-9A-Z]? [0-9][A-Z][A-Z])$' THEN
87 ELSEIF postcode ~ '^([A-Z][A-Z]?[0-9][0-9A-Z]? [0-9])$' THEN
90 ELSEIF postcode ~ '^([A-Z][A-Z]?[0-9][0-9A-Z])$' THEN
95 ELSEIF country_code = 'sg' THEN
96 IF postcode ~ '^([0-9]{6})$' THEN
101 ELSEIF country_code = 'de' THEN
102 IF postcode ~ '^([0-9]{5})$' THEN
108 -- Guess at the postcode format and coverage (!)
109 IF postcode ~ '^[A-Z0-9]{1,5}$' THEN -- Probably too short to be very local
113 -- Does it look splitable into and area and local code?
114 part := substring(postcode from '^([- :A-Z0-9]+)([- :][A-Z0-9]+)$');
116 IF part IS NOT NULL THEN
119 ELSEIF postcode ~ '^[- :A-Z0-9]{6,}$' THEN
128 LANGUAGE plpgsql IMMUTABLE;
130 -- Find the nearest artificial postcode for the given geometry.
131 -- TODO For areas there should not be more than two inside the geometry.
132 CREATE OR REPLACE FUNCTION get_nearest_postcode(country VARCHAR(2), geom GEOMETRY) RETURNS TEXT
138 -- If the geometry is an area then only one postcode must be within
139 -- that area, otherwise consider the area as not having a postcode.
140 IF ST_GeometryType(geom) in ('ST_Polygon','ST_MultiPolygon') THEN
141 SELECT min(postcode), count(*) FROM
142 (SELECT postcode FROM location_postcode
143 WHERE ST_Contains(geom, location_postcode.geometry) LIMIT 2) sub
153 SELECT postcode FROM location_postcode
154 WHERE ST_DWithin(geom, location_postcode.geometry, 0.05)
155 AND location_postcode.country_code = country
156 ORDER BY ST_Distance(geom, location_postcode.geometry) LIMIT 1
165 CREATE OR REPLACE FUNCTION get_country_code(place geometry) RETURNS TEXT
168 place_centre GEOMETRY;
171 place_centre := ST_PointOnSurface(place);
173 -- RAISE WARNING 'get_country_code, start: %', ST_AsText(place_centre);
175 -- Try for a OSM polygon
176 FOR nearcountry IN select country_code from location_area_country where country_code is not null and st_covers(geometry, place_centre) limit 1
178 RETURN nearcountry.country_code;
181 -- RAISE WARNING 'osm fallback: %', ST_AsText(place_centre);
183 -- Try for OSM fallback data
184 -- The order is to deal with places like HongKong that are 'states' within another polygon
185 FOR nearcountry IN select country_code from country_osm_grid where st_covers(geometry, place_centre) order by area asc limit 1
187 RETURN nearcountry.country_code;
190 -- RAISE WARNING 'near osm fallback: %', ST_AsText(place_centre);
193 FOR nearcountry IN select country_code from country_osm_grid where st_dwithin(geometry, place_centre, 0.5) order by st_distance(geometry, place_centre) asc, area asc limit 1
195 RETURN nearcountry.country_code;
201 LANGUAGE plpgsql IMMUTABLE;
203 CREATE OR REPLACE FUNCTION get_country_language_code(search_country_code VARCHAR(2)) RETURNS TEXT
208 FOR nearcountry IN select distinct country_default_language_code from country_name where country_code = search_country_code limit 1
210 RETURN lower(nearcountry.country_default_language_code);
215 LANGUAGE plpgsql IMMUTABLE;
217 CREATE OR REPLACE FUNCTION get_country_language_codes(search_country_code VARCHAR(2)) RETURNS TEXT[]
222 FOR nearcountry IN select country_default_language_codes from country_name where country_code = search_country_code limit 1
224 RETURN lower(nearcountry.country_default_language_codes);
229 LANGUAGE plpgsql IMMUTABLE;
231 CREATE OR REPLACE FUNCTION get_partition(in_country_code VARCHAR(10)) RETURNS INTEGER
236 FOR nearcountry IN select partition from country_name where country_code = in_country_code
238 RETURN nearcountry.partition;
243 LANGUAGE plpgsql IMMUTABLE;
245 CREATE OR REPLACE FUNCTION delete_location(OLD_place_id BIGINT) RETURNS BOOLEAN
249 DELETE FROM location_area where place_id = OLD_place_id;
250 -- TODO:location_area
256 CREATE OR REPLACE FUNCTION add_location(
258 country_code varchar(2),
262 rank_address INTEGER,
278 IF rank_search > 25 THEN
279 RAISE EXCEPTION 'Adding location with rank > 25 (% rank %)', place_id, rank_search;
282 x := deleteLocationArea(partition, place_id, rank_search);
284 -- add postcode only if it contains a single entry, i.e. ignore postcode lists
286 IF in_postcode is not null AND in_postcode not similar to '%(,|;)%' THEN
287 postcode := upper(trim (in_postcode));
290 IF ST_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon') THEN
291 centroid := ST_Centroid(geometry);
293 FOR secgeo IN select split_geometry(geometry) AS geom LOOP
294 x := insertLocationAreaLarge(partition, place_id, country_code, keywords, rank_search, rank_address, false, postcode, centroid, secgeo);
300 IF rank_address = 0 THEN
302 ELSEIF rank_search <= 14 THEN
304 ELSEIF rank_search <= 15 THEN
306 ELSEIF rank_search <= 16 THEN
308 ELSEIF rank_search <= 17 THEN
310 ELSEIF rank_search <= 21 THEN
312 ELSEIF rank_search = 25 THEN
316 -- RAISE WARNING 'adding % diameter %', place_id, diameter;
318 secgeo := ST_Buffer(geometry, diameter);
319 x := insertLocationAreaLarge(partition, place_id, country_code, keywords, rank_search, rank_address, true, postcode, ST_Centroid(geometry), secgeo);
329 -- find the parent road of the cut road parts
330 CREATE OR REPLACE FUNCTION get_interpolation_parent(wayid BIGINT, street TEXT, place TEXT,
331 partition INTEGER, centroid GEOMETRY, geom GEOMETRY)
336 parent_place_id BIGINT;
337 address_street_word_ids INTEGER[];
343 addr_street = street;
346 IF addr_street is null and addr_place is null THEN
347 select nodes from planet_osm_ways where id = wayid INTO waynodes;
348 FOR location IN SELECT placex.address from placex
349 where osm_type = 'N' and osm_id = ANY(waynodes)
350 and placex.address is not null
351 and (placex.address ? 'street' or placex.address ? 'place')
352 and indexed_status < 100
354 addr_street = location.address->'street';
355 addr_place = location.address->'place';
359 IF addr_street IS NOT NULL THEN
360 address_street_word_ids := get_name_ids(make_standard_name(addr_street));
361 IF address_street_word_ids IS NOT NULL THEN
362 FOR location IN SELECT place_id from getNearestNamedRoadFeature(partition, centroid, address_street_word_ids) LOOP
363 parent_place_id := location.place_id;
368 IF parent_place_id IS NULL AND addr_place IS NOT NULL THEN
369 address_street_word_ids := get_name_ids(make_standard_name(addr_place));
370 IF address_street_word_ids IS NOT NULL THEN
371 FOR location IN SELECT place_id from getNearestNamedPlaceFeature(partition, centroid, address_street_word_ids) LOOP
372 parent_place_id := location.place_id;
377 IF parent_place_id is null THEN
378 FOR location IN SELECT place_id FROM placex
379 WHERE ST_DWithin(geom, placex.geometry, 0.001) and placex.rank_search = 26
380 ORDER BY (ST_distance(placex.geometry, ST_LineInterpolatePoint(geom,0))+
381 ST_distance(placex.geometry, ST_LineInterpolatePoint(geom,0.5))+
382 ST_distance(placex.geometry, ST_LineInterpolatePoint(geom,1))) ASC limit 1
384 parent_place_id := location.place_id;
388 IF parent_place_id is null THEN
392 RETURN parent_place_id;
397 CREATE OR REPLACE FUNCTION osmline_reinsert(node_id BIGINT, geom GEOMETRY)
403 SELECT w.id FROM planet_osm_ways w, location_property_osmline p
404 WHERE p.linegeo && geom and p.osm_id = w.id and p.indexed_status = 0
405 and node_id = any(w.nodes) INTO existingline;
407 IF existingline.id is not NULL THEN
408 DELETE FROM location_property_osmline WHERE osm_id = existingline.id;
409 INSERT INTO location_property_osmline (osm_id, address, linegeo)
410 SELECT osm_id, address, geometry FROM place
411 WHERE osm_type = 'W' and osm_id = existingline.id;
420 CREATE OR REPLACE FUNCTION osmline_insert() RETURNS TRIGGER
423 NEW.place_id := nextval('seq_place');
424 NEW.indexed_date := now();
426 IF NEW.indexed_status IS NULL THEN
427 IF NEW.address is NULL OR NOT NEW.address ? 'interpolation'
428 OR NEW.address->'interpolation' NOT IN ('odd', 'even', 'all') THEN
429 -- other interpolation types than odd/even/all (e.g. numeric ones) are not supported
433 NEW.indexed_status := 1; --STATUS_NEW
434 NEW.country_code := lower(get_country_code(NEW.linegeo));
436 NEW.partition := get_partition(NEW.country_code);
437 NEW.geometry_sector := geometry_sector(NEW.partition, NEW.linegeo);
446 CREATE OR REPLACE FUNCTION placex_insert() RETURNS TRIGGER
453 country_code VARCHAR(2);
454 default_language VARCHAR(10);
459 --DEBUG: RAISE WARNING '% % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type;
461 NEW.place_id := nextval('seq_place');
462 NEW.indexed_status := 1; --STATUS_NEW
464 NEW.country_code := lower(get_country_code(NEW.geometry));
466 NEW.partition := get_partition(NEW.country_code);
467 NEW.geometry_sector := geometry_sector(NEW.partition, NEW.geometry);
469 -- copy 'name' to or from the default language (if there is a default language)
470 IF NEW.name is not null AND array_upper(akeys(NEW.name),1) > 1 THEN
471 default_language := get_country_language_code(NEW.country_code);
472 IF default_language IS NOT NULL THEN
473 IF NEW.name ? 'name' AND NOT NEW.name ? ('name:'||default_language) THEN
474 NEW.name := NEW.name || hstore(('name:'||default_language), (NEW.name -> 'name'));
475 ELSEIF NEW.name ? ('name:'||default_language) AND NOT NEW.name ? 'name' THEN
476 NEW.name := NEW.name || hstore('name', (NEW.name -> ('name:'||default_language)));
481 IF NEW.osm_type = 'X' THEN
482 -- E'X'ternal records should already be in the right format so do nothing
484 is_area := ST_GeometryType(NEW.geometry) IN ('ST_Polygon','ST_MultiPolygon');
486 IF NEW.class in ('place','boundary')
487 AND NEW.type in ('postcode','postal_code') THEN
489 IF NEW.address IS NULL OR NOT NEW.address ? 'postcode' THEN
490 -- most likely just a part of a multipolygon postcode boundary, throw it away
494 NEW.name := hstore('ref', NEW.address->'postcode');
496 SELECT * FROM get_postcode_rank(NEW.country_code, NEW.address->'postcode')
497 INTO NEW.rank_search, NEW.rank_address;
500 NEW.rank_address := 0;
502 ELSEIF NEW.class = 'boundary' AND NOT is_area THEN
504 ELSEIF NEW.class = 'boundary' AND NEW.type = 'administrative'
505 AND NEW.admin_level <= 4 AND NEW.osm_type = 'W' THEN
507 ELSEIF NEW.class = 'railway' AND NEW.type in ('rail') THEN
509 ELSEIF NEW.osm_type = 'N' AND NEW.class = 'highway' THEN
510 NEW.rank_search = 30;
511 NEW.rank_address = 0;
512 ELSEIF NEW.class = 'landuse' AND NOT is_area THEN
513 NEW.rank_search = 30;
514 NEW.rank_address = 0;
516 -- do table lookup stuff
517 IF NEW.class = 'boundary' and NEW.type = 'administrative' THEN
518 classtype = NEW.type || NEW.admin_level::TEXT;
520 classtype = NEW.type;
522 SELECT l.rank_search, l.rank_address FROM address_levels l
523 WHERE (l.country_code = NEW.country_code or l.country_code is NULL)
524 AND l.class = NEW.class AND (l.type = classtype or l.type is NULL)
525 ORDER BY l.country_code, l.class, l.type LIMIT 1
526 INTO NEW.rank_search, NEW.rank_address;
528 IF NEW.rank_search is NULL THEN
529 NEW.rank_search := 30;
532 IF NEW.rank_address is NULL THEN
533 NEW.rank_address := 30;
537 -- some postcorrections
538 IF NEW.class = 'waterway' AND NEW.osm_type = 'R' THEN
539 -- Slightly promote waterway relations so that they are processed
540 -- before their members.
541 NEW.rank_search := NEW.rank_search - 1;
544 IF (NEW.extratags -> 'capital') = 'yes' THEN
545 NEW.rank_search := NEW.rank_search - 1;
550 -- a country code make no sense below rank 4 (country)
551 IF NEW.rank_search < 4 THEN
552 NEW.country_code := NULL;
555 --DEBUG: RAISE WARNING 'placex_insert:END: % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type;
557 RETURN NEW; -- %DIFFUPDATES% The following is not needed until doing diff updates, and slows the main index process down
559 IF NEW.osm_type = 'N' and NEW.rank_search > 28 THEN
560 -- might be part of an interpolation
561 result := osmline_reinsert(NEW.osm_id, NEW.geometry);
562 ELSEIF NEW.rank_address > 0 THEN
563 IF (ST_GeometryType(NEW.geometry) in ('ST_Polygon','ST_MultiPolygon') AND ST_IsValid(NEW.geometry)) THEN
564 -- Performance: We just can't handle re-indexing for country level changes
565 IF st_area(NEW.geometry) < 1 THEN
566 -- mark items within the geometry for re-indexing
567 -- RAISE WARNING 'placex poly insert: % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type;
569 -- work around bug in postgis, this may have been fixed in 2.0.0 (see http://trac.osgeo.org/postgis/ticket/547)
570 update placex set indexed_status = 2 where (st_covers(NEW.geometry, placex.geometry) OR ST_Intersects(NEW.geometry, placex.geometry))
571 AND rank_search > NEW.rank_search and indexed_status = 0 and ST_geometrytype(placex.geometry) = 'ST_Point' and (rank_search < 28 or name is not null or (NEW.rank_search >= 16 and address ? 'place'));
572 update placex set indexed_status = 2 where (st_covers(NEW.geometry, placex.geometry) OR ST_Intersects(NEW.geometry, placex.geometry))
573 AND rank_search > NEW.rank_search and indexed_status = 0 and ST_geometrytype(placex.geometry) != 'ST_Point' and (rank_search < 28 or name is not null or (NEW.rank_search >= 16 and address ? 'place'));
576 -- mark nearby items for re-indexing, where 'nearby' depends on the features rank_search and is a complete guess :(
578 -- 16 = city, anything higher than city is effectively ignored (polygon required!)
579 IF NEW.type='postcode' THEN
581 ELSEIF NEW.rank_search < 16 THEN
583 ELSEIF NEW.rank_search < 18 THEN
585 ELSEIF NEW.rank_search < 20 THEN
587 ELSEIF NEW.rank_search = 21 THEN
589 ELSEIF NEW.rank_search < 24 THEN
591 ELSEIF NEW.rank_search < 26 THEN
592 diameter := 0.002; -- 100 to 200 meters
593 ELSEIF NEW.rank_search < 28 THEN
594 diameter := 0.001; -- 50 to 100 meters
597 -- RAISE WARNING 'placex point insert: % % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type,diameter;
598 IF NEW.rank_search >= 26 THEN
599 -- roads may cause reparenting for >27 rank places
600 update placex set indexed_status = 2 where indexed_status = 0 and rank_search > NEW.rank_search and ST_DWithin(placex.geometry, NEW.geometry, diameter);
601 -- reparenting also for OSM Interpolation Lines (and for Tiger?)
602 update location_property_osmline set indexed_status = 2 where indexed_status = 0 and ST_DWithin(location_property_osmline.linegeo, NEW.geometry, diameter);
603 ELSEIF NEW.rank_search >= 16 THEN
604 -- up to rank 16, street-less addresses may need reparenting
605 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');
607 -- for all other places the search terms may change as well
608 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);
615 -- add to tables for special search
616 -- Note: won't work on initial import because the classtype tables
617 -- do not yet exist. It won't hurt either.
618 classtable := 'place_classtype_' || NEW.class || '_' || NEW.type;
619 SELECT count(*)>0 FROM pg_tables WHERE tablename = classtable and schemaname = current_schema() INTO result;
621 EXECUTE 'INSERT INTO ' || classtable::regclass || ' (place_id, centroid) VALUES ($1,$2)'
622 USING NEW.place_id, ST_Centroid(NEW.geometry);
631 CREATE OR REPLACE FUNCTION osmline_update() RETURNS
635 place_centroid GEOMETRY;
645 interpol_postcode TEXT;
649 IF OLD.indexed_status = 100 THEN
650 delete from location_property_osmline where place_id = OLD.place_id;
654 IF NEW.indexed_status != 0 OR OLD.indexed_status = 0 THEN
658 NEW.interpolationtype = NEW.address->'interpolation';
660 place_centroid := ST_PointOnSurface(NEW.linegeo);
661 NEW.parent_place_id = get_interpolation_parent(NEW.osm_id, NEW.address->'street',
662 NEW.address->'place',
663 NEW.partition, place_centroid, NEW.linegeo);
665 IF NEW.address is not NULL AND NEW.address ? 'postcode' AND NEW.address->'postcode' not similar to '%(,|;)%' THEN
666 interpol_postcode := NEW.address->'postcode';
667 housenum := getorcreate_postcode_id(NEW.address->'postcode');
669 interpol_postcode := NULL;
672 -- if the line was newly inserted, split the line as necessary
673 IF OLD.indexed_status = 1 THEN
674 select nodes from planet_osm_ways where id = NEW.osm_id INTO waynodes;
676 IF array_upper(waynodes, 1) IS NULL THEN
680 linegeo := NEW.linegeo;
683 FOR nodeidpos in 1..array_upper(waynodes, 1) LOOP
685 select osm_id, address, geometry
686 from place where osm_type = 'N' and osm_id = waynodes[nodeidpos]::BIGINT
687 and address is not NULL and address ? 'housenumber' limit 1 INTO nextnode;
688 --RAISE NOTICE 'Nextnode.place_id: %s', nextnode.place_id;
689 IF nextnode.osm_id IS NOT NULL THEN
690 --RAISE NOTICE 'place_id is not null';
691 IF nodeidpos > 1 and nodeidpos < array_upper(waynodes, 1) THEN
692 -- Make sure that the point is actually on the line. That might
693 -- be a bit paranoid but ensures that the algorithm still works
694 -- should osm2pgsql attempt to repair geometries.
695 splitline := split_line_on_node(linegeo, nextnode.geometry);
696 sectiongeo := ST_GeometryN(splitline, 1);
697 linegeo := ST_GeometryN(splitline, 2);
699 sectiongeo = linegeo;
701 endnumber := substring(nextnode.address->'housenumber','[0-9]+')::integer;
703 IF startnumber IS NOT NULL AND endnumber IS NOT NULL
704 AND startnumber != endnumber
705 AND ST_GeometryType(sectiongeo) = 'ST_LineString' THEN
707 IF (startnumber > endnumber) THEN
708 housenum := endnumber;
709 endnumber := startnumber;
710 startnumber := housenum;
711 sectiongeo := ST_Reverse(sectiongeo);
714 -- determine postcode
715 postcode := coalesce(interpol_postcode,
716 prevnode.address->'postcode',
717 nextnode.address->'postcode',
720 IF postcode is NULL THEN
721 SELECT placex.postcode FROM placex WHERE place_id = NEW.parent_place_id INTO postcode;
723 IF postcode is NULL THEN
724 postcode := get_nearest_postcode(NEW.country_code, nextnode.geometry);
727 IF NEW.startnumber IS NULL THEN
728 NEW.startnumber := startnumber;
729 NEW.endnumber := endnumber;
730 NEW.linegeo := sectiongeo;
731 NEW.postcode := upper(trim(postcode));
733 insert into location_property_osmline
734 (linegeo, partition, osm_id, parent_place_id,
735 startnumber, endnumber, interpolationtype,
736 address, postcode, country_code,
737 geometry_sector, indexed_status)
738 values (sectiongeo, NEW.partition, NEW.osm_id, NEW.parent_place_id,
739 startnumber, endnumber, NEW.interpolationtype,
740 NEW.address, postcode,
741 NEW.country_code, NEW.geometry_sector, 0);
745 -- early break if we are out of line string,
746 -- might happen when a line string loops back on itself
747 IF ST_GeometryType(linegeo) != 'ST_LineString' THEN
751 startnumber := substring(nextnode.address->'housenumber','[0-9]+')::integer;
752 prevnode := nextnode;
757 -- marking descendants for reparenting is not needed, because there are
758 -- actually no descendants for interpolation lines
764 -- Trigger for updates of location_postcode
766 -- Computes the parent object the postcode most likely refers to.
767 -- This will be the place that determines the address displayed when
768 -- searching for this postcode.
769 CREATE OR REPLACE FUNCTION postcode_update() RETURNS
776 IF NEW.indexed_status != 0 OR OLD.indexed_status = 0 THEN
780 NEW.indexed_date = now();
782 partition := get_partition(NEW.country_code);
784 SELECT * FROM get_postcode_rank(NEW.country_code, NEW.postcode)
785 INTO NEW.rank_search, NEW.rank_address;
787 NEW.parent_place_id = 0;
790 FROM getNearFeatures(partition, NEW.geometry, NEW.rank_search, '{}'::int[])
791 WHERE NOT isguess ORDER BY rank_address DESC LIMIT 1
793 NEW.parent_place_id = location.place_id;
801 CREATE OR REPLACE FUNCTION placex_update() RETURNS
806 place_centroid GEOMETRY;
807 near_centroid GEOMETRY;
809 search_maxdistance FLOAT[];
810 search_mindistance FLOAT[];
811 address_havelevel BOOLEAN[];
818 relation_members TEXT[];
822 search_diameter FLOAT;
823 search_prevdiameter FLOAT;
824 search_maxrank INTEGER;
825 address_maxrank INTEGER;
826 address_street_word_id INTEGER;
827 address_street_word_ids INTEGER[];
828 parent_place_id_rank BIGINT;
836 location_rank_search INTEGER;
837 location_distance FLOAT;
838 location_parent GEOMETRY;
839 location_isaddress BOOLEAN;
840 location_keywords INTEGER[];
842 default_language TEXT;
843 name_vector INTEGER[];
844 nameaddress_vector INTEGER[];
846 linked_node_id BIGINT;
847 linked_importance FLOAT;
848 linked_wikipedia TEXT;
853 IF OLD.indexed_status = 100 THEN
854 --DEBUG: RAISE WARNING 'placex_update delete % %',NEW.osm_type,NEW.osm_id;
855 delete from placex where place_id = OLD.place_id;
859 IF NEW.indexed_status != 0 OR OLD.indexed_status = 0 THEN
863 --DEBUG: RAISE WARNING 'placex_update % % (%)',NEW.osm_type,NEW.osm_id,NEW.place_id;
865 NEW.indexed_date = now();
867 IF NOT %REVERSE-ONLY% THEN
868 DELETE from search_name WHERE place_id = NEW.place_id;
870 result := deleteSearchName(NEW.partition, NEW.place_id);
871 DELETE FROM place_addressline WHERE place_id = NEW.place_id;
872 result := deleteRoad(NEW.partition, NEW.place_id);
873 result := deleteLocationArea(NEW.partition, NEW.place_id, NEW.rank_search);
874 UPDATE placex set linked_place_id = null, indexed_status = 2
875 where linked_place_id = NEW.place_id;
876 -- update not necessary for osmline, cause linked_place_id does not exist
878 IF NEW.linked_place_id is not null THEN
879 --DEBUG: RAISE WARNING 'place already linked to %', NEW.linked_place_id;
883 --DEBUG: RAISE WARNING 'Copy over address tags';
884 -- housenumber is a computed field, so start with an empty value
885 NEW.housenumber := NULL;
886 IF NEW.address is not NULL THEN
887 IF NEW.address ? 'conscriptionnumber' THEN
888 i := getorcreate_housenumber_id(make_standard_name(NEW.address->'conscriptionnumber'));
889 IF NEW.address ? 'streetnumber' THEN
890 i := getorcreate_housenumber_id(make_standard_name(NEW.address->'streetnumber'));
891 NEW.housenumber := (NEW.address->'conscriptionnumber') || '/' || (NEW.address->'streetnumber');
893 NEW.housenumber := NEW.address->'conscriptionnumber';
895 ELSEIF NEW.address ? 'streetnumber' THEN
896 NEW.housenumber := NEW.address->'streetnumber';
897 i := getorcreate_housenumber_id(make_standard_name(NEW.address->'streetnumber'));
898 ELSEIF NEW.address ? 'housenumber' THEN
899 NEW.housenumber := NEW.address->'housenumber';
900 i := getorcreate_housenumber_id(make_standard_name(NEW.housenumber));
903 addr_street := NEW.address->'street';
904 addr_place := NEW.address->'place';
906 IF NEW.address ? 'postcode' and NEW.address->'postcode' not similar to '%(,|;)%' THEN
907 i := getorcreate_postcode_id(NEW.address->'postcode');
911 -- Speed up searches - just use the centroid of the feature
912 -- cheaper but less acurate
913 place_centroid := ST_PointOnSurface(NEW.geometry);
914 -- For searching near features rather use the centroid
915 near_centroid := ST_Envelope(NEW.geometry);
916 NEW.centroid := null;
917 NEW.postcode := null;
918 --DEBUG: RAISE WARNING 'Computing preliminary centroid at %',ST_AsText(place_centroid);
920 -- recalculate country and partition
921 IF NEW.rank_search = 4 AND NEW.address is not NULL AND NEW.address ? 'country' THEN
922 -- for countries, believe the mapped country code,
923 -- so that we remain in the right partition if the boundaries
925 NEW.country_code := lower(NEW.address->'country');
926 NEW.partition := get_partition(lower(NEW.country_code));
927 IF NEW.partition = 0 THEN
928 NEW.country_code := lower(get_country_code(place_centroid));
929 NEW.partition := get_partition(NEW.country_code);
932 IF NEW.rank_search >= 4 THEN
933 NEW.country_code := lower(get_country_code(place_centroid));
935 NEW.country_code := NULL;
937 NEW.partition := get_partition(NEW.country_code);
939 --DEBUG: RAISE WARNING 'Country updated: "%"', NEW.country_code;
941 -- waterway ways are linked when they are part of a relation and have the same class/type
942 IF NEW.osm_type = 'R' and NEW.class = 'waterway' THEN
943 FOR relation_members IN select members from planet_osm_rels r where r.id = NEW.osm_id and r.parts != array[]::bigint[]
945 FOR i IN 1..array_upper(relation_members, 1) BY 2 LOOP
946 IF relation_members[i+1] in ('', 'main_stream', 'side_stream') AND substring(relation_members[i],1,1) = 'w' THEN
947 --DEBUG: RAISE WARNING 'waterway parent %, child %/%', NEW.osm_id, i, relation_members[i];
948 FOR linked_node_id IN SELECT place_id FROM placex
949 WHERE osm_type = 'W' and osm_id = substring(relation_members[i],2,200)::bigint
950 and class = NEW.class and type in ('river', 'stream', 'canal', 'drain', 'ditch')
951 and ( relation_members[i+1] != 'side_stream' or NEW.name->'name' = name->'name')
953 UPDATE placex SET linked_place_id = NEW.place_id WHERE place_id = linked_node_id;
958 --DEBUG: RAISE WARNING 'Waterway processed';
961 -- What level are we searching from
962 search_maxrank := NEW.rank_search;
964 -- Thought this wasn't needed but when we add new languages to the country_name table
965 -- we need to update the existing names
966 IF NEW.name is not null AND array_upper(akeys(NEW.name),1) > 1 THEN
967 default_language := get_country_language_code(NEW.country_code);
968 IF default_language IS NOT NULL THEN
969 IF NEW.name ? 'name' AND NOT NEW.name ? ('name:'||default_language) THEN
970 NEW.name := NEW.name || hstore(('name:'||default_language), (NEW.name -> 'name'));
971 ELSEIF NEW.name ? ('name:'||default_language) AND NOT NEW.name ? 'name' THEN
972 NEW.name := NEW.name || hstore('name', (NEW.name -> ('name:'||default_language)));
976 --DEBUG: RAISE WARNING 'Local names updated';
978 -- Initialise the name vector using our name
979 name_vector := make_keywords(NEW.name);
980 nameaddress_vector := '{}'::int[];
983 address_havelevel[i] := false;
986 NEW.importance := null;
987 SELECT wikipedia, importance
988 FROM compute_importance(NEW.extratags, NEW.country_code, NEW.osm_type, NEW.osm_id)
989 INTO NEW.wikipedia,NEW.importance;
991 --DEBUG: RAISE WARNING 'Importance computed from wikipedia: %', NEW.importance;
993 -- ---------------------------------------------------------------------------
994 -- For low level elements we inherit from our parent road
995 IF (NEW.rank_search > 27 OR (NEW.type = 'postcode' AND NEW.rank_search = 25)) THEN
997 --DEBUG: RAISE WARNING 'finding street for % %', NEW.osm_type, NEW.osm_id;
999 -- We won't get a better centroid, besides these places are too small to care
1000 NEW.centroid := place_centroid;
1002 NEW.parent_place_id := null;
1004 -- if we have a POI and there is no address information,
1005 -- see if we can get it from a surrounding building
1006 IF NEW.osm_type = 'N' AND addr_street IS NULL AND addr_place IS NULL
1007 AND NEW.housenumber IS NULL THEN
1008 FOR location IN select address from placex where ST_Covers(geometry, place_centroid)
1009 and address is not null
1010 and (address ? 'housenumber' or address ? 'street' or address ? 'place')
1011 and rank_search > 28 AND ST_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon')
1014 NEW.housenumber := location.address->'housenumber';
1015 addr_street := location.address->'street';
1016 addr_place := location.address->'place';
1017 --DEBUG: RAISE WARNING 'Found surrounding building % %', location.osm_type, location.osm_id;
1021 -- We have to find our parent road.
1022 -- Copy data from linked items (points on ways, addr:street links, relations)
1024 -- Is this object part of a relation?
1025 FOR relation IN select * from planet_osm_rels where parts @> ARRAY[NEW.osm_id] and members @> ARRAY[lower(NEW.osm_type)||NEW.osm_id]
1027 -- At the moment we only process one type of relation - associatedStreet
1028 IF relation.tags @> ARRAY['associatedStreet'] THEN
1029 FOR i IN 1..array_upper(relation.members, 1) BY 2 LOOP
1030 IF NEW.parent_place_id IS NULL AND relation.members[i+1] = 'street' THEN
1031 --RAISE WARNING 'node in relation %',relation;
1032 SELECT place_id from placex where osm_type = 'W'
1033 and osm_id = substring(relation.members[i],2,200)::bigint
1034 and rank_search = 26 and name is not null INTO NEW.parent_place_id;
1039 --DEBUG: RAISE WARNING 'Checked for street relation (%)', NEW.parent_place_id;
1041 -- Note that addr:street links can only be indexed once the street itself is indexed
1042 IF NEW.parent_place_id IS NULL AND addr_street IS NOT NULL THEN
1043 address_street_word_ids := get_name_ids(make_standard_name(addr_street));
1044 IF address_street_word_ids IS NOT NULL THEN
1045 SELECT place_id from getNearestNamedRoadFeature(NEW.partition, near_centroid, address_street_word_ids) INTO NEW.parent_place_id;
1048 --DEBUG: RAISE WARNING 'Checked for addr:street (%)', NEW.parent_place_id;
1050 IF NEW.parent_place_id IS NULL AND addr_place IS NOT NULL THEN
1051 address_street_word_ids := get_name_ids(make_standard_name(addr_place));
1052 IF address_street_word_ids IS NOT NULL THEN
1053 SELECT place_id from getNearestNamedPlaceFeature(NEW.partition, near_centroid, address_street_word_ids) INTO NEW.parent_place_id;
1056 --DEBUG: RAISE WARNING 'Checked for addr:place (%)', NEW.parent_place_id;
1058 -- Is this node part of an interpolation?
1059 IF NEW.parent_place_id IS NULL AND NEW.osm_type = 'N' THEN
1060 SELECT q.parent_place_id FROM location_property_osmline q, planet_osm_ways x
1061 WHERE q.linegeo && NEW.geometry and x.id = q.osm_id and NEW.osm_id = any(x.nodes)
1062 LIMIT 1 INTO NEW.parent_place_id;
1064 --DEBUG: RAISE WARNING 'Checked for interpolation (%)', NEW.parent_place_id;
1066 -- Is this node part of a way?
1067 IF NEW.parent_place_id IS NULL AND NEW.osm_type = 'N' THEN
1070 SELECT p.place_id, p.osm_id, p.rank_search, p.address from placex p, planet_osm_ways w
1071 WHERE p.osm_type = 'W' and p.rank_search >= 26 and p.geometry && NEW.geometry and w.id = p.osm_id and NEW.osm_id = any(w.nodes)
1073 --DEBUG: RAISE WARNING 'Node is part of way % ', location.osm_id;
1075 -- Way IS a road then we are on it - that must be our road
1076 IF location.rank_search < 28 THEN
1077 --RAISE WARNING 'node in way that is a street %',location;
1078 NEW.parent_place_id := location.place_id;
1081 --DEBUG: RAISE WARNING 'Checked if way is street (%)', NEW.parent_place_id;
1083 -- If the way mentions a street or place address, try that for parenting.
1084 IF location.address is not null THEN
1085 IF location.address ? 'street' THEN
1086 address_street_word_ids := get_name_ids(make_standard_name(location.address->'street'));
1087 IF address_street_word_ids IS NOT NULL THEN
1088 SELECT place_id from getNearestNamedRoadFeature(NEW.partition, near_centroid, address_street_word_ids) INTO NEW.parent_place_id;
1089 EXIT WHEN NEW.parent_place_id is not NULL;
1092 --DEBUG: RAISE WARNING 'Checked for addr:street in way (%)', NEW.parent_place_id;
1094 IF location.address ? 'place' THEN
1095 address_street_word_ids := get_name_ids(make_standard_name(location.address->'place'));
1096 IF address_street_word_ids IS NOT NULL THEN
1097 SELECT place_id from getNearestNamedPlaceFeature(NEW.partition, near_centroid, address_street_word_ids) INTO NEW.parent_place_id;
1098 EXIT WHEN NEW.parent_place_id is not NULL;
1101 --DEBUG: RAISE WARNING 'Checked for addr:place in way (%)', NEW.parent_place_id;
1104 -- Is the WAY part of a relation
1105 FOR relation IN select * from planet_osm_rels where parts @> ARRAY[location.osm_id] and members @> ARRAY['w'||location.osm_id]
1107 -- At the moment we only process one type of relation - associatedStreet
1108 IF relation.tags @> ARRAY['associatedStreet'] AND array_upper(relation.members, 1) IS NOT NULL THEN
1109 FOR i IN 1..array_upper(relation.members, 1) BY 2 LOOP
1110 IF NEW.parent_place_id IS NULL AND relation.members[i+1] = 'street' THEN
1111 --RAISE WARNING 'node in way that is in a relation %',relation;
1112 SELECT place_id from placex where osm_type='W' and osm_id = substring(relation.members[i],2,200)::bigint
1113 and rank_search = 26 and name is not null INTO NEW.parent_place_id;
1118 EXIT WHEN NEW.parent_place_id is not null;
1119 --DEBUG: RAISE WARNING 'Checked for street relation in way (%)', NEW.parent_place_id;
1124 -- Still nothing, just use the nearest road
1125 IF NEW.parent_place_id IS NULL THEN
1126 SELECT place_id FROM getNearestRoadFeature(NEW.partition, near_centroid) INTO NEW.parent_place_id;
1128 --DEBUG: RAISE WARNING 'Checked for nearest way (%)', NEW.parent_place_id;
1131 -- If we didn't find any road fallback to standard method
1132 IF NEW.parent_place_id IS NOT NULL THEN
1134 -- Get the details of the parent road
1135 SELECT p.country_code, p.postcode FROM placex p
1136 WHERE p.place_id = NEW.parent_place_id INTO location;
1138 NEW.country_code := location.country_code;
1139 --DEBUG: RAISE WARNING 'Got parent details from search name';
1141 -- determine postcode
1142 IF NEW.rank_search > 4 THEN
1143 IF NEW.address is not null AND NEW.address ? 'postcode' THEN
1144 NEW.postcode = upper(trim(NEW.address->'postcode'));
1146 NEW.postcode := location.postcode;
1148 IF NEW.postcode is null THEN
1149 NEW.postcode := get_nearest_postcode(NEW.country_code, NEW.geometry);
1153 -- If there is no name it isn't searchable, don't bother to create a search record
1154 IF NEW.name is NULL THEN
1155 --DEBUG: RAISE WARNING 'Not a searchable place % %', NEW.osm_type, NEW.osm_id;
1159 -- Performance, it would be more acurate to do all the rest of the import
1160 -- process but it takes too long
1161 -- Just be happy with inheriting from parent road only
1162 IF NEW.rank_search <= 25 and NEW.rank_address > 0 THEN
1163 result := add_location(NEW.place_id, NEW.country_code, NEW.partition, name_vector, NEW.rank_search, NEW.rank_address, upper(trim(NEW.address->'postcode')), NEW.geometry);
1164 --DEBUG: RAISE WARNING 'Place added to location table';
1167 result := insertSearchName(NEW.partition, NEW.place_id, name_vector,
1168 NEW.rank_search, NEW.rank_address, NEW.geometry);
1170 IF NOT %REVERSE-ONLY% THEN
1171 -- Merge address from parent
1172 SELECT s.name_vector, s.nameaddress_vector FROM search_name s
1173 WHERE s.place_id = NEW.parent_place_id INTO location;
1175 nameaddress_vector := array_merge(nameaddress_vector,
1176 location.nameaddress_vector);
1177 nameaddress_vector := array_merge(nameaddress_vector, location.name_vector);
1179 INSERT INTO search_name (place_id, search_rank, address_rank,
1180 importance, country_code, name_vector,
1181 nameaddress_vector, centroid)
1182 VALUES (NEW.place_id, NEW.rank_search, NEW.rank_address,
1183 NEW.importance, NEW.country_code, name_vector,
1184 nameaddress_vector, place_centroid);
1185 --DEBUG: RAISE WARNING 'Place added to search table';
1193 -- ---------------------------------------------------------------------------
1195 --DEBUG: RAISE WARNING 'Using full index mode for % %', NEW.osm_type, NEW.osm_id;
1197 IF NEW.osm_type = 'R' AND NEW.rank_search < 26 THEN
1199 -- see if we have any special relation members
1200 select members from planet_osm_rels where id = NEW.osm_id INTO relation_members;
1201 --DEBUG: RAISE WARNING 'Got relation members';
1203 IF relation_members IS NOT NULL THEN
1204 FOR relMember IN select get_osm_rel_members(relation_members,ARRAY['label']) as member LOOP
1205 --DEBUG: RAISE WARNING 'Found label member %', relMember.member;
1207 FOR linkedPlacex IN select * from placex where osm_type = upper(substring(relMember.member,1,1))::char(1)
1208 and osm_id = substring(relMember.member,2,10000)::bigint
1209 and class = 'place' order by rank_search desc limit 1 LOOP
1211 -- If we don't already have one use this as the centre point of the geometry
1212 IF NEW.centroid IS NULL THEN
1213 NEW.centroid := coalesce(linkedPlacex.centroid,st_centroid(linkedPlacex.geometry));
1216 -- merge in the label name, re-init word vector
1217 IF NOT linkedPlacex.name IS NULL THEN
1218 NEW.name := linkedPlacex.name || NEW.name;
1219 name_vector := array_merge(name_vector, make_keywords(linkedPlacex.name));
1222 -- merge in extra tags
1223 NEW.extratags := hstore(linkedPlacex.class, linkedPlacex.type) || coalesce(linkedPlacex.extratags, ''::hstore) || coalesce(NEW.extratags, ''::hstore);
1225 -- mark the linked place (excludes from search results)
1226 UPDATE placex set linked_place_id = NEW.place_id where place_id = linkedPlacex.place_id;
1228 select wikipedia, importance
1229 FROM compute_importance(linkedPlacex.extratags, NEW.country_code,
1230 'N', linkedPlacex.osm_id)
1231 INTO linked_wikipedia,linked_importance;
1232 --DEBUG: RAISE WARNING 'Linked label member';
1237 IF NEW.centroid IS NULL THEN
1239 FOR relMember IN select get_osm_rel_members(relation_members,ARRAY['admin_center','admin_centre']) as member LOOP
1240 --DEBUG: RAISE WARNING 'Found admin_center member %', relMember.member;
1242 FOR linkedPlacex IN select * from placex where osm_type = upper(substring(relMember.member,1,1))::char(1)
1243 and osm_id = substring(relMember.member,2,10000)::bigint
1244 and class = 'place' order by rank_search desc limit 1 LOOP
1246 -- For an admin centre we also want a name match - still not perfect, for example 'new york, new york'
1247 -- But that can be fixed by explicitly setting the label in the data
1248 IF make_standard_name(NEW.name->'name') = make_standard_name(linkedPlacex.name->'name')
1249 AND NEW.rank_address = linkedPlacex.rank_address THEN
1251 -- If we don't already have one use this as the centre point of the geometry
1252 IF NEW.centroid IS NULL THEN
1253 NEW.centroid := coalesce(linkedPlacex.centroid,st_centroid(linkedPlacex.geometry));
1256 -- merge in the name, re-init word vector
1257 IF NOT linkedPlacex.name IS NULL THEN
1258 NEW.name := linkedPlacex.name || NEW.name;
1259 name_vector := make_keywords(NEW.name);
1262 -- merge in extra tags
1263 NEW.extratags := hstore(linkedPlacex.class, linkedPlacex.type) || coalesce(linkedPlacex.extratags, ''::hstore) || coalesce(NEW.extratags, ''::hstore);
1265 -- mark the linked place (excludes from search results)
1266 UPDATE placex set linked_place_id = NEW.place_id where place_id = linkedPlacex.place_id;
1268 select wikipedia, importance
1269 FROM compute_importance(linkedPlacex.extratags, NEW.country_code,
1270 'N', linkedPlacex.osm_id)
1271 INTO linked_wikipedia,linked_importance;
1272 --DEBUG: RAISE WARNING 'Linked admin_center';
1284 -- Name searches can be done for ways as well as relations
1285 IF NEW.osm_type in ('W','R') AND NEW.rank_search < 26 AND NEW.rank_address > 0 THEN
1287 -- not found one yet? how about doing a name search
1288 IF NEW.centroid IS NULL AND (NEW.name->'name') is not null and make_standard_name(NEW.name->'name') != '' THEN
1290 --DEBUG: RAISE WARNING 'Looking for nodes with matching names';
1291 FOR linkedPlacex IN select placex.* from placex WHERE
1292 make_standard_name(name->'name') = make_standard_name(NEW.name->'name')
1293 AND placex.rank_address = NEW.rank_address
1294 AND placex.place_id != NEW.place_id
1295 AND placex.osm_type = 'N'::char(1) AND placex.rank_search < 26
1296 AND st_covers(NEW.geometry, placex.geometry)
1298 --DEBUG: RAISE WARNING 'Found matching place node %', linkedPlacex.osm_id;
1299 -- If we don't already have one use this as the centre point of the geometry
1300 IF NEW.centroid IS NULL THEN
1301 NEW.centroid := coalesce(linkedPlacex.centroid,st_centroid(linkedPlacex.geometry));
1304 -- merge in the name, re-init word vector
1305 NEW.name := linkedPlacex.name || NEW.name;
1306 name_vector := make_keywords(NEW.name);
1308 -- merge in extra tags
1309 NEW.extratags := hstore(linkedPlacex.class, linkedPlacex.type) || coalesce(linkedPlacex.extratags, ''::hstore) || coalesce(NEW.extratags, ''::hstore);
1311 -- mark the linked place (excludes from search results)
1312 UPDATE placex set linked_place_id = NEW.place_id where place_id = linkedPlacex.place_id;
1314 select wikipedia, importance
1315 FROM compute_importance(linkedPlacex.extratags, NEW.country_code,
1316 'N', linkedPlacex.osm_id)
1317 INTO linked_wikipedia,linked_importance;
1318 --DEBUG: RAISE WARNING 'Linked named place';
1322 IF NEW.centroid IS NOT NULL THEN
1323 place_centroid := NEW.centroid;
1324 -- Place might have had only a name tag before but has now received translations
1325 -- from the linked place. Make sure a name tag for the default language exists in
1327 IF NEW.name is not null AND array_upper(akeys(NEW.name),1) > 1 THEN
1328 default_language := get_country_language_code(NEW.country_code);
1329 IF default_language IS NOT NULL THEN
1330 IF NEW.name ? 'name' AND NOT NEW.name ? ('name:'||default_language) THEN
1331 NEW.name := NEW.name || hstore(('name:'||default_language), (NEW.name -> 'name'));
1332 ELSEIF NEW.name ? ('name:'||default_language) AND NOT NEW.name ? 'name' THEN
1333 NEW.name := NEW.name || hstore('name', (NEW.name -> ('name:'||default_language)));
1337 --DEBUG: RAISE WARNING 'Names updated from linked places';
1340 -- Use the maximum importance if a one could be computed from the linked object.
1341 IF linked_importance is not null AND
1342 (NEW.importance is null or NEW.importance < linked_importance) THEN
1343 NEW.importance = linked_importance;
1347 -- make sure all names are in the word table
1348 IF NEW.admin_level = 2 AND NEW.class = 'boundary' AND NEW.type = 'administrative' AND NEW.country_code IS NOT NULL AND NEW.osm_type = 'R' THEN
1349 perform create_country(NEW.name, lower(NEW.country_code));
1350 --DEBUG: RAISE WARNING 'Country names updated';
1353 NEW.parent_place_id = 0;
1354 parent_place_id_rank = 0;
1357 -- convert address store to array of tokenids
1358 --DEBUG: RAISE WARNING 'Starting address search';
1359 isin_tokens := '{}'::int[];
1360 IF NEW.address IS NOT NULL THEN
1361 FOR addr_item IN SELECT * FROM each(NEW.address)
1363 IF addr_item.key IN ('city', 'tiger:county', 'state', 'suburb', 'province', 'district', 'region', 'county', 'municipality', 'hamlet', 'village', 'subdistrict', 'town', 'neighbourhood', 'quarter', 'parish') THEN
1364 address_street_word_id := get_name_id(make_standard_name(addr_item.value));
1365 IF address_street_word_id IS NOT NULL AND NOT(ARRAY[address_street_word_id] <@ isin_tokens) THEN
1366 isin_tokens := isin_tokens || address_street_word_id;
1368 IF NOT %REVERSE-ONLY% THEN
1369 address_street_word_id := get_word_id(make_standard_name(addr_item.value));
1370 IF address_street_word_id IS NOT NULL THEN
1371 nameaddress_vector := array_merge(nameaddress_vector, ARRAY[address_street_word_id]);
1375 IF addr_item.key = 'is_in' THEN
1376 -- is_in items need splitting
1377 isin := regexp_split_to_array(addr_item.value, E'[;,]');
1378 IF array_upper(isin, 1) IS NOT NULL THEN
1379 FOR i IN 1..array_upper(isin, 1) LOOP
1380 address_street_word_id := get_name_id(make_standard_name(isin[i]));
1381 IF address_street_word_id IS NOT NULL AND NOT(ARRAY[address_street_word_id] <@ isin_tokens) THEN
1382 isin_tokens := isin_tokens || address_street_word_id;
1385 -- merge word into address vector
1386 IF NOT %REVERSE-ONLY% THEN
1387 address_street_word_id := get_word_id(make_standard_name(isin[i]));
1388 IF address_street_word_id IS NOT NULL THEN
1389 nameaddress_vector := array_merge(nameaddress_vector, ARRAY[address_street_word_id]);
1397 IF NOT %REVERSE-ONLY% THEN
1398 nameaddress_vector := array_merge(nameaddress_vector, isin_tokens);
1401 -- RAISE WARNING 'ISIN: %', isin_tokens;
1403 -- Process area matches
1404 location_rank_search := 0;
1405 location_distance := 0;
1406 location_parent := NULL;
1407 -- added ourself as address already
1408 address_havelevel[NEW.rank_address] := true;
1409 --DEBUG: RAISE WARNING ' getNearFeatures(%,''%'',%,''%'')',NEW.partition, place_centroid, search_maxrank, isin_tokens;
1411 SELECT * from getNearFeatures(NEW.partition,
1412 CASE WHEN NEW.rank_search >= 26
1413 AND NEW.rank_search < 30
1415 ELSE place_centroid END,
1416 search_maxrank, isin_tokens)
1418 IF location.rank_address != location_rank_search THEN
1419 location_rank_search := location.rank_address;
1420 IF location.isguess THEN
1421 location_distance := location.distance * 1.5;
1423 IF location.rank_address <= 12 THEN
1424 -- for county and above, if we have an area consider that exact
1425 -- (It would be nice to relax the constraint for places close to
1426 -- the boundary but we'd need the exact geometry for that. Too
1428 location_distance = 0;
1430 -- Below county level remain slightly fuzzy.
1431 location_distance := location.distance * 0.5;
1435 CONTINUE WHEN location.keywords <@ location_keywords;
1438 IF location.distance < location_distance OR NOT location.isguess THEN
1439 location_keywords := location.keywords;
1441 location_isaddress := NOT address_havelevel[location.rank_address];
1442 IF location_isaddress AND location.isguess AND location_parent IS NOT NULL THEN
1443 location_isaddress := ST_Contains(location_parent,location.centroid);
1446 -- RAISE WARNING '% isaddress: %', location.place_id, location_isaddress;
1447 -- Add it to the list of search terms
1448 IF NOT %REVERSE-ONLY% THEN
1449 nameaddress_vector := array_merge(nameaddress_vector, location.keywords::integer[]);
1451 INSERT INTO place_addressline (place_id, address_place_id, fromarea, isaddress, distance, cached_rank_address)
1452 VALUES (NEW.place_id, location.place_id, true, location_isaddress, location.distance, location.rank_address);
1454 IF location_isaddress THEN
1455 -- add postcode if we have one
1456 -- (If multiple postcodes are available, we end up with the highest ranking one.)
1457 IF location.postcode is not null THEN
1458 NEW.postcode = location.postcode;
1461 address_havelevel[location.rank_address] := true;
1462 IF NOT location.isguess THEN
1463 SELECT geometry FROM placex WHERE place_id = location.place_id INTO location_parent;
1466 IF location.rank_address > parent_place_id_rank THEN
1467 NEW.parent_place_id = location.place_id;
1468 parent_place_id_rank = location.rank_address;
1473 --DEBUG: RAISE WARNING ' Terms: (%) %',location, nameaddress_vector;
1478 --DEBUG: RAISE WARNING 'address computed';
1480 IF NEW.address is not null AND NEW.address ? 'postcode'
1481 AND NEW.address->'postcode' not similar to '%(,|;)%' THEN
1482 NEW.postcode := upper(trim(NEW.address->'postcode'));
1485 IF NEW.postcode is null AND NEW.rank_search > 8 THEN
1486 NEW.postcode := get_nearest_postcode(NEW.country_code, NEW.geometry);
1489 -- if we have a name add this to the name search table
1490 IF NEW.name IS NOT NULL THEN
1492 IF NEW.rank_search <= 25 and NEW.rank_address > 0 THEN
1493 result := add_location(NEW.place_id, NEW.country_code, NEW.partition, name_vector, NEW.rank_search, NEW.rank_address, upper(trim(NEW.address->'postcode')), NEW.geometry);
1494 --DEBUG: RAISE WARNING 'added to location (full)';
1497 IF NEW.rank_search between 26 and 27 and NEW.class = 'highway' THEN
1498 result := insertLocationRoad(NEW.partition, NEW.place_id, NEW.country_code, NEW.geometry);
1499 --DEBUG: RAISE WARNING 'insert into road location table (full)';
1502 result := insertSearchName(NEW.partition, NEW.place_id, name_vector,
1503 NEW.rank_search, NEW.rank_address, NEW.geometry);
1504 --DEBUG: RAISE WARNING 'added to search name (full)';
1506 IF NOT %REVERSE-ONLY% THEN
1507 INSERT INTO search_name (place_id, search_rank, address_rank,
1508 importance, country_code, name_vector,
1509 nameaddress_vector, centroid)
1510 VALUES (NEW.place_id, NEW.rank_search, NEW.rank_address,
1511 NEW.importance, NEW.country_code, name_vector,
1512 nameaddress_vector, place_centroid);
1517 -- If we've not managed to pick up a better one - default centroid
1518 IF NEW.centroid IS NULL THEN
1519 NEW.centroid := place_centroid;
1522 --DEBUG: RAISE WARNING 'place update % % finsihed.', NEW.osm_type, NEW.osm_id;
1529 CREATE OR REPLACE FUNCTION placex_delete() RETURNS TRIGGER
1535 -- RAISE WARNING 'placex_delete % %',OLD.osm_type,OLD.osm_id;
1537 update placex set linked_place_id = null, indexed_status = 2 where linked_place_id = OLD.place_id and indexed_status = 0;
1538 --DEBUG: RAISE WARNING 'placex_delete:01 % %',OLD.osm_type,OLD.osm_id;
1539 update placex set linked_place_id = null where linked_place_id = OLD.place_id;
1540 --DEBUG: RAISE WARNING 'placex_delete:02 % %',OLD.osm_type,OLD.osm_id;
1542 IF OLD.rank_address < 30 THEN
1544 -- mark everything linked to this place for re-indexing
1545 --DEBUG: RAISE WARNING 'placex_delete:03 % %',OLD.osm_type,OLD.osm_id;
1546 UPDATE placex set indexed_status = 2 from place_addressline where address_place_id = OLD.place_id
1547 and placex.place_id = place_addressline.place_id and indexed_status = 0 and place_addressline.isaddress;
1549 --DEBUG: RAISE WARNING 'placex_delete:04 % %',OLD.osm_type,OLD.osm_id;
1550 DELETE FROM place_addressline where address_place_id = OLD.place_id;
1552 --DEBUG: RAISE WARNING 'placex_delete:05 % %',OLD.osm_type,OLD.osm_id;
1553 b := deleteRoad(OLD.partition, OLD.place_id);
1555 --DEBUG: RAISE WARNING 'placex_delete:06 % %',OLD.osm_type,OLD.osm_id;
1556 update placex set indexed_status = 2 where parent_place_id = OLD.place_id and indexed_status = 0;
1557 --DEBUG: RAISE WARNING 'placex_delete:07 % %',OLD.osm_type,OLD.osm_id;
1558 -- reparenting also for OSM Interpolation Lines (and for Tiger?)
1559 update location_property_osmline set indexed_status = 2 where indexed_status = 0 and parent_place_id = OLD.place_id;
1563 --DEBUG: RAISE WARNING 'placex_delete:08 % %',OLD.osm_type,OLD.osm_id;
1565 IF OLD.rank_address < 26 THEN
1566 b := deleteLocationArea(OLD.partition, OLD.place_id, OLD.rank_search);
1569 --DEBUG: RAISE WARNING 'placex_delete:09 % %',OLD.osm_type,OLD.osm_id;
1571 IF OLD.name is not null THEN
1572 IF NOT %REVERSE-ONLY% THEN
1573 DELETE from search_name WHERE place_id = OLD.place_id;
1575 b := deleteSearchName(OLD.partition, OLD.place_id);
1578 --DEBUG: RAISE WARNING 'placex_delete:10 % %',OLD.osm_type,OLD.osm_id;
1580 DELETE FROM place_addressline where place_id = OLD.place_id;
1582 --DEBUG: RAISE WARNING 'placex_delete:11 % %',OLD.osm_type,OLD.osm_id;
1584 -- remove from tables for special search
1585 classtable := 'place_classtype_' || OLD.class || '_' || OLD.type;
1586 SELECT count(*)>0 FROM pg_tables WHERE tablename = classtable and schemaname = current_schema() INTO b;
1588 EXECUTE 'DELETE FROM ' || classtable::regclass || ' WHERE place_id = $1' USING OLD.place_id;
1591 --DEBUG: RAISE WARNING 'placex_delete:12 % %',OLD.osm_type,OLD.osm_id;
1599 CREATE OR REPLACE FUNCTION place_delete() RETURNS TRIGGER
1605 --DEBUG: RAISE WARNING 'delete: % % % %',OLD.osm_type,OLD.osm_id,OLD.class,OLD.type;
1607 -- deleting large polygons can have a massive effect on the system - require manual intervention to let them through
1608 IF st_area(OLD.geometry) > 2 and st_isvalid(OLD.geometry) THEN
1609 SELECT bool_or(not (rank_address = 0 or rank_address > 26)) as ranked FROM placex WHERE osm_type = OLD.osm_type and osm_id = OLD.osm_id and class = OLD.class and type = OLD.type INTO has_rank;
1611 insert into import_polygon_delete (osm_type, osm_id, class, type) values (OLD.osm_type,OLD.osm_id,OLD.class,OLD.type);
1617 UPDATE placex set indexed_status = 100 where osm_type = OLD.osm_type and osm_id = OLD.osm_id and class = OLD.class and type = OLD.type;
1619 -- interpolations are special
1620 IF OLD.osm_type='W' and OLD.class = 'place' and OLD.type = 'houses' THEN
1621 UPDATE location_property_osmline set indexed_status = 100 where osm_id = OLD.osm_id; -- osm_id = wayid (=old.osm_id)
1630 CREATE OR REPLACE FUNCTION place_insert() RETURNS TRIGGER
1635 existingplacex RECORD;
1636 existingline RECORD;
1637 existinggeometry GEOMETRY;
1638 existingplace_id BIGINT;
1643 --DEBUG: RAISE WARNING '-----------------------------------------------------------------------------------';
1644 --DEBUG: RAISE WARNING 'place_insert: % % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type,st_area(NEW.geometry);
1645 -- filter wrong tupels
1646 IF ST_IsEmpty(NEW.geometry) OR NOT ST_IsValid(NEW.geometry) OR ST_X(ST_Centroid(NEW.geometry))::text in ('NaN','Infinity','-Infinity') OR ST_Y(ST_Centroid(NEW.geometry))::text in ('NaN','Infinity','-Infinity') THEN
1647 INSERT INTO import_polygon_error (osm_type, osm_id, class, type, name, country_code, updated, errormessage, prevgeometry, newgeometry)
1648 VALUES (NEW.osm_type, NEW.osm_id, NEW.class, NEW.type, NEW.name, NEW.address->'country', now(), ST_IsValidReason(NEW.geometry), null, NEW.geometry);
1649 -- RAISE WARNING 'Invalid Geometry: % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type;
1653 -- decide, whether it is an osm interpolation line => insert intoosmline, or else just placex
1654 IF NEW.class='place' and NEW.type='houses' and NEW.osm_type='W' and ST_GeometryType(NEW.geometry) = 'ST_LineString' THEN
1655 -- Have we already done this place?
1656 select * from place where osm_type = NEW.osm_type and osm_id = NEW.osm_id and class = NEW.class and type = NEW.type INTO existing;
1658 -- Get the existing place_id
1659 select * from location_property_osmline where osm_id = NEW.osm_id INTO existingline;
1661 -- Handle a place changing type by removing the old data (this trigger is executed BEFORE INSERT of the NEW tupel)
1662 IF existing.osm_type IS NULL THEN
1663 DELETE FROM place where osm_type = NEW.osm_type and osm_id = NEW.osm_id and class = NEW.class;
1666 DELETE from import_polygon_error where osm_type = NEW.osm_type and osm_id = NEW.osm_id;
1667 DELETE from import_polygon_delete where osm_type = NEW.osm_type and osm_id = NEW.osm_id;
1669 -- update method for interpolation lines: delete all old interpolation lines with same osm_id (update on place) and insert the new one(s) (they can be split up, if they have > 2 nodes)
1670 IF existingline.osm_id IS NOT NULL THEN
1671 delete from location_property_osmline where osm_id = NEW.osm_id;
1674 -- for interpolations invalidate all nodes on the line
1675 update placex p set indexed_status = 2
1676 from planet_osm_ways w
1677 where w.id = NEW.osm_id and p.osm_type = 'N' and p.osm_id = any(w.nodes);
1680 INSERT INTO location_property_osmline (osm_id, address, linegeo)
1681 VALUES (NEW.osm_id, NEW.address, NEW.geometry);
1684 IF existing.osm_type IS NULL THEN
1688 IF coalesce(existing.address, ''::hstore) != coalesce(NEW.address, ''::hstore)
1689 OR (coalesce(existing.extratags, ''::hstore) != coalesce(NEW.extratags, ''::hstore))
1690 OR existing.geometry::text != NEW.geometry::text
1695 address = NEW.address,
1696 extratags = NEW.extratags,
1697 admin_level = NEW.admin_level,
1698 geometry = NEW.geometry
1699 where osm_type = NEW.osm_type and osm_id = NEW.osm_id and class = NEW.class and type = NEW.type;
1704 ELSE -- insert to placex
1706 -- Patch in additional country names
1707 IF NEW.admin_level = 2 AND NEW.type = 'administrative'
1708 AND NEW.address is not NULL AND NEW.address ? 'country' THEN
1709 SELECT name FROM country_name WHERE country_code = lower(NEW.address->'country') INTO existing;
1710 IF existing.name IS NOT NULL THEN
1711 NEW.name = existing.name || NEW.name;
1715 -- Have we already done this place?
1716 select * from place where osm_type = NEW.osm_type and osm_id = NEW.osm_id and class = NEW.class and type = NEW.type INTO existing;
1718 -- Get the existing place_id
1719 select * from placex where osm_type = NEW.osm_type and osm_id = NEW.osm_id and class = NEW.class and type = NEW.type INTO existingplacex;
1721 -- Handle a place changing type by removing the old data
1722 -- My generated 'place' types are causing havok because they overlap with real keys
1723 -- TODO: move them to their own special purpose key/class to avoid collisions
1724 IF existing.osm_type IS NULL THEN
1725 DELETE FROM place where osm_type = NEW.osm_type and osm_id = NEW.osm_id and class = NEW.class;
1728 --DEBUG: RAISE WARNING 'Existing: %',existing.osm_id;
1729 --DEBUG: RAISE WARNING 'Existing PlaceX: %',existingplacex.place_id;
1732 IF existing.geometry is not null AND st_isvalid(existing.geometry)
1733 AND st_area(existing.geometry) > 0.02
1734 AND ST_GeometryType(NEW.geometry) in ('ST_Polygon','ST_MultiPolygon')
1735 AND st_area(NEW.geometry) < st_area(existing.geometry)*0.5
1737 INSERT INTO import_polygon_error (osm_type, osm_id, class, type, name, country_code, updated, errormessage, prevgeometry, newgeometry)
1738 VALUES (NEW.osm_type, NEW.osm_id, NEW.class, NEW.type, NEW.name, NEW.address->'country', now(),
1739 'Area reduced from '||st_area(existing.geometry)||' to '||st_area(NEW.geometry), existing.geometry, NEW.geometry);
1743 DELETE from import_polygon_error where osm_type = NEW.osm_type and osm_id = NEW.osm_id;
1744 DELETE from import_polygon_delete where osm_type = NEW.osm_type and osm_id = NEW.osm_id;
1746 -- To paraphrase, if there isn't an existing item, OR if the admin level has changed
1747 IF existingplacex.osm_type IS NULL OR
1748 (existingplacex.class = 'boundary' AND
1749 ((coalesce(existingplacex.admin_level, 15) != coalesce(NEW.admin_level, 15) AND existingplacex.type = 'administrative') OR
1750 (existingplacex.type != NEW.type)))
1753 IF existingplacex.osm_type IS NOT NULL THEN
1754 -- sanity check: ignore admin_level changes on places with too many active children
1755 -- or we end up reindexing entire countries because somebody accidentally deleted admin_level
1756 --LIMIT INDEXING: SELECT count(*) FROM (SELECT 'a' FROM placex , place_addressline where address_place_id = existingplacex.place_id and placex.place_id = place_addressline.place_id and indexed_status = 0 and place_addressline.isaddress LIMIT 100001) sub INTO i;
1757 --LIMIT INDEXING: IF i > 100000 THEN
1758 --LIMIT INDEXING: RETURN null;
1759 --LIMIT INDEXING: END IF;
1762 IF existing.osm_type IS NOT NULL THEN
1763 -- pathological case caused by the triggerless copy into place during initial import
1764 -- force delete even for large areas, it will be reinserted later
1765 UPDATE place set geometry = ST_SetSRID(ST_Point(0,0), 4326) where osm_type = NEW.osm_type and osm_id = NEW.osm_id and class = NEW.class and type = NEW.type;
1766 DELETE from place where osm_type = NEW.osm_type and osm_id = NEW.osm_id and class = NEW.class and type = NEW.type;
1769 -- No - process it as a new insertion (hopefully of low rank or it will be slow)
1770 insert into placex (osm_type, osm_id, class, type, name,
1771 admin_level, address, extratags, geometry)
1772 values (NEW.osm_type, NEW.osm_id, NEW.class, NEW.type, NEW.name,
1773 NEW.admin_level, NEW.address, NEW.extratags, NEW.geometry);
1775 --DEBUG: RAISE WARNING 'insert done % % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type,NEW.name;
1780 -- Special case for polygon shape changes because they tend to be large and we can be a bit clever about how we handle them
1781 IF existing.geometry::text != NEW.geometry::text
1782 AND ST_GeometryType(existing.geometry) in ('ST_Polygon','ST_MultiPolygon')
1783 AND ST_GeometryType(NEW.geometry) in ('ST_Polygon','ST_MultiPolygon')
1786 -- Get the version of the geometry actually used (in placex table)
1787 select geometry from placex where osm_type = NEW.osm_type and osm_id = NEW.osm_id and class = NEW.class and type = NEW.type into existinggeometry;
1789 -- Performance limit
1790 IF st_area(NEW.geometry) < 0.000000001 AND st_area(existinggeometry) < 1 THEN
1792 -- re-index points that have moved in / out of the polygon, could be done as a single query but postgres gets the index usage wrong
1793 update placex set indexed_status = 2 where indexed_status = 0 and
1794 (st_covers(NEW.geometry, placex.geometry) OR ST_Intersects(NEW.geometry, placex.geometry))
1795 AND NOT (st_covers(existinggeometry, placex.geometry) OR ST_Intersects(existinggeometry, placex.geometry))
1796 AND rank_search > existingplacex.rank_search AND (rank_search < 28 or name is not null);
1798 update placex set indexed_status = 2 where indexed_status = 0 and
1799 (st_covers(existinggeometry, placex.geometry) OR ST_Intersects(existinggeometry, placex.geometry))
1800 AND NOT (st_covers(NEW.geometry, placex.geometry) OR ST_Intersects(NEW.geometry, placex.geometry))
1801 AND rank_search > existingplacex.rank_search AND (rank_search < 28 or name is not null);
1808 IF coalesce(existing.name::text, '') != coalesce(NEW.name::text, '')
1809 OR coalesce(existing.extratags::text, '') != coalesce(NEW.extratags::text, '')
1810 OR coalesce(existing.address, ''::hstore) != coalesce(NEW.address, ''::hstore)
1811 OR coalesce(existing.admin_level, 15) != coalesce(NEW.admin_level, 15)
1812 OR existing.geometry::text != NEW.geometry::text
1817 address = NEW.address,
1818 extratags = NEW.extratags,
1819 admin_level = NEW.admin_level,
1820 geometry = NEW.geometry
1821 where osm_type = NEW.osm_type and osm_id = NEW.osm_id and class = NEW.class and type = NEW.type;
1824 IF NEW.class in ('place','boundary') AND NEW.type in ('postcode','postal_code') THEN
1825 IF NEW.address is NULL OR NOT NEW.address ? 'postcode' THEN
1826 -- postcode was deleted, no longer retain in placex
1827 DELETE FROM placex where place_id = existingplacex.place_id;
1831 NEW.name := hstore('ref', NEW.address->'postcode');
1834 IF NEW.class in ('boundary')
1835 AND ST_GeometryType(NEW.geometry) not in ('ST_Polygon','ST_MultiPolygon') THEN
1836 DELETE FROM placex where place_id = existingplacex.place_id;
1842 address = NEW.address,
1843 parent_place_id = null,
1844 extratags = NEW.extratags,
1845 admin_level = NEW.admin_level,
1847 geometry = NEW.geometry
1848 where place_id = existingplacex.place_id;
1849 -- if a node(=>house), which is part of a interpolation line, changes (e.g. the street attribute) => mark this line for reparenting
1850 -- (already here, because interpolation lines are reindexed before nodes, so in the second call it would be too late)
1852 and (coalesce(existing.address, ''::hstore) != coalesce(NEW.address, ''::hstore)
1853 or existing.geometry::text != NEW.geometry::text)
1855 result:= osmline_reinsert(NEW.osm_id, NEW.geometry);
1858 -- linked places should get potential new naming and addresses
1859 IF existingplacex.linked_place_id is not NULL THEN
1862 extratags = p.extratags,
1865 where x.place_id = existingplacex.linked_place_id
1866 and x.indexed_status = 0
1867 and x.osm_type = p.osm_type
1868 and x.osm_id = p.osm_id
1869 and x.class = p.class;
1874 -- Abort the add (we modified the existing place instead)
1879 $$ LANGUAGE plpgsql;
1882 CREATE OR REPLACE FUNCTION get_name_by_language(name hstore, languagepref TEXT[]) RETURNS TEXT
1887 IF name is null THEN
1891 FOR j IN 1..array_upper(languagepref,1) LOOP
1892 IF name ? languagepref[j] THEN
1893 result := trim(name->languagepref[j]);
1894 IF result != '' THEN
1900 -- anything will do as a fallback - just take the first name type thing there is
1901 RETURN trim((avals(name))[1]);
1904 LANGUAGE plpgsql IMMUTABLE;
1906 --housenumber only needed for tiger data
1907 CREATE OR REPLACE FUNCTION get_address_by_language(for_place_id BIGINT, housenumber INTEGER, languagepref TEXT[]) RETURNS TEXT
1919 FOR location IN select * from get_addressdata(for_place_id, housenumber) where isaddress order by rank_address desc LOOP
1920 currresult := trim(get_name_by_language(location.name, languagepref));
1921 IF currresult != prevresult AND currresult IS NOT NULL AND result[(100 - location.rank_address)] IS NULL THEN
1922 result[(100 - location.rank_address)] := trim(get_name_by_language(location.name, languagepref));
1923 prevresult := currresult;
1927 RETURN array_to_string(result,', ');
1932 DROP TYPE IF EXISTS addressline CASCADE;
1933 create type addressline as (
1940 admin_level INTEGER,
1943 rank_address INTEGER,
1947 -- Compute the list of address parts for the given place.
1949 -- If in_housenumber is greator or equal 0, look for an interpolation.
1950 CREATE OR REPLACE FUNCTION get_addressdata(in_place_id BIGINT, in_housenumber INTEGER) RETURNS setof addressline
1953 for_place_id BIGINT;
1958 countrylocation RECORD;
1959 searchcountrycode varchar(2);
1960 searchhousenumber TEXT;
1961 searchhousename HSTORE;
1962 searchrankaddress INTEGER;
1963 searchpostcode TEXT;
1964 postcode_isaddress BOOL;
1969 -- The place ein question might not have a direct entry in place_addressline.
1970 -- Look for the parent of such places then and save if in for_place_id.
1972 postcode_isaddress := true;
1974 -- first query osmline (interpolation lines)
1975 IF in_housenumber >= 0 THEN
1976 SELECT parent_place_id, country_code, in_housenumber::text, 30, postcode,
1977 null, 'place', 'house'
1978 FROM location_property_osmline
1979 WHERE place_id = in_place_id AND in_housenumber>=startnumber
1980 AND in_housenumber <= endnumber
1981 INTO for_place_id, searchcountrycode, searchhousenumber, searchrankaddress,
1982 searchpostcode, searchhousename, searchclass, searchtype;
1985 --then query tiger data
1986 -- %NOTIGERDATA% IF 0 THEN
1987 IF for_place_id IS NULL AND in_housenumber >= 0 THEN
1988 SELECT parent_place_id, 'us', in_housenumber::text, 30, postcode, null,
1990 FROM location_property_tiger
1991 WHERE place_id = in_place_id AND in_housenumber >= startnumber
1992 AND in_housenumber <= endnumber
1993 INTO for_place_id, searchcountrycode, searchhousenumber, searchrankaddress,
1994 searchpostcode, searchhousename, searchclass, searchtype;
1996 -- %NOTIGERDATA% END IF;
1998 -- %NOAUXDATA% IF 0 THEN
1999 IF for_place_id IS NULL THEN
2000 SELECT parent_place_id, 'us', housenumber, 30, postcode, null, 'place', 'house'
2001 FROM location_property_aux
2002 WHERE place_id = in_place_id
2003 INTO for_place_id,searchcountrycode, searchhousenumber, searchrankaddress,
2004 searchpostcode, searchhousename, searchclass, searchtype;
2006 -- %NOAUXDATA% END IF;
2009 IF for_place_id IS NULL THEN
2010 SELECT parent_place_id, country_code, rank_search, postcode, 'place', 'postcode'
2011 FROM location_postcode
2012 WHERE place_id = in_place_id
2013 INTO for_place_id, searchcountrycode, searchrankaddress, searchpostcode,
2014 searchclass, searchtype;
2017 -- POI objects in the placex table
2018 IF for_place_id IS NULL THEN
2019 SELECT parent_place_id, country_code, housenumber, rank_search, postcode,
2022 WHERE place_id = in_place_id and rank_search > 27
2023 INTO for_place_id, searchcountrycode, searchhousenumber, searchrankaddress,
2024 searchpostcode, searchhousename, searchclass, searchtype;
2027 -- If for_place_id is still NULL at this point then the object has its own
2028 -- entry in place_address line. However, still check if there is not linked
2029 -- place we should be using instead.
2030 IF for_place_id IS NULL THEN
2031 select coalesce(linked_place_id, place_id), country_code,
2032 housenumber, rank_search, postcode, null
2033 from placex where place_id = in_place_id
2034 INTO for_place_id, searchcountrycode, searchhousenumber, searchrankaddress, searchpostcode, searchhousename;
2037 --RAISE WARNING '% % % %',searchcountrycode, searchhousenumber, searchrankaddress, searchpostcode;
2039 found := 1000; -- the lowest rank_address included
2041 -- Return the record for the base entry.
2043 SELECT placex.place_id, osm_type, osm_id, name,
2044 class, type, admin_level,
2045 type not in ('postcode', 'postal_code') as isaddress,
2046 CASE WHEN rank_address = 0 THEN 100
2047 WHEN rank_address = 11 THEN 5
2048 ELSE rank_address END as rank_address,
2049 0 as distance, country_code, postcode
2051 WHERE place_id = for_place_id
2053 --RAISE WARNING '%',location;
2054 IF searchcountrycode IS NULL AND location.country_code IS NOT NULL THEN
2055 searchcountrycode := location.country_code;
2057 IF location.rank_address < 4 THEN
2058 -- no country locations for ranks higher than country
2059 searchcountrycode := NULL;
2061 countrylocation := ROW(location.place_id, location.osm_type, location.osm_id,
2062 location.name, location.class, location.type,
2063 location.admin_level, true, location.isaddress,
2064 location.rank_address, location.distance)::addressline;
2065 RETURN NEXT countrylocation;
2066 found := location.rank_address;
2070 SELECT placex.place_id, osm_type, osm_id, name,
2071 CASE WHEN extratags ? 'place' THEN 'place' ELSE class END as class,
2072 CASE WHEN extratags ? 'place' THEN extratags->'place' ELSE type END as type,
2073 admin_level, fromarea, isaddress,
2074 CASE WHEN rank_address = 11 THEN 5 ELSE rank_address END as rank_address,
2075 distance, country_code, postcode
2076 FROM place_addressline join placex on (address_place_id = placex.place_id)
2077 WHERE place_addressline.place_id = for_place_id
2078 AND (cached_rank_address >= 4 AND cached_rank_address < searchrankaddress)
2079 AND linked_place_id is null
2080 AND (placex.country_code IS NULL OR searchcountrycode IS NULL
2081 OR placex.country_code = searchcountrycode)
2082 ORDER BY rank_address desc, isaddress desc, fromarea desc,
2083 distance asc, rank_search desc
2085 --RAISE WARNING '%',location;
2086 IF searchcountrycode IS NULL AND location.country_code IS NOT NULL THEN
2087 searchcountrycode := location.country_code;
2089 IF location.type in ('postcode', 'postal_code') THEN
2090 postcode_isaddress := false;
2091 IF location.osm_type != 'R' THEN
2092 location.isaddress := FALSE;
2095 countrylocation := ROW(location.place_id, location.osm_type, location.osm_id,
2096 location.name, location.class, location.type,
2097 location.admin_level, location.fromarea,
2098 location.isaddress, location.rank_address,
2099 location.distance)::addressline;
2100 RETURN NEXT countrylocation;
2101 found := location.rank_address;
2104 -- If no country was included yet, add the name information from country_name.
2106 SELECT name FROM country_name
2107 WHERE country_code = searchcountrycode LIMIT 1 INTO countryname;
2108 --RAISE WARNING '% % %',found,searchcountrycode,countryname;
2109 IF countryname IS NOT NULL THEN
2110 location := ROW(null, null, null, countryname, 'place', 'country',
2111 null, true, true, 4, 0)::addressline;
2112 RETURN NEXT location;
2116 -- Finally add some artificial rows.
2117 IF searchcountrycode IS NOT NULL THEN
2118 location := ROW(null, null, null, hstore('ref', searchcountrycode),
2119 'place', 'country_code', null, true, false, 4, 0)::addressline;
2120 RETURN NEXT location;
2123 IF searchhousename IS NOT NULL THEN
2124 location := ROW(in_place_id, null, null, searchhousename, searchclass,
2125 searchtype, null, true, true, 29, 0)::addressline;
2126 RETURN NEXT location;
2129 IF searchhousenumber IS NOT NULL THEN
2130 location := ROW(in_place_id, null, null, hstore('ref', searchhousenumber),
2131 'place', 'house_number', null, true, true, 28, 0)::addressline;
2132 RETURN NEXT location;
2135 IF searchpostcode IS NOT NULL THEN
2136 location := ROW(null, null, null, hstore('ref', searchpostcode), 'place',
2137 'postcode', null, false, postcode_isaddress, 5, 0)::addressline;
2138 RETURN NEXT location;
2147 CREATE OR REPLACE FUNCTION get_osm_rel_members(members TEXT[], member TEXT) RETURNS TEXT[]
2154 FOR i IN 1..ARRAY_UPPER(members,1) BY 2 LOOP
2155 IF members[i+1] = member THEN
2156 result := result || members[i];
2165 CREATE OR REPLACE FUNCTION get_osm_rel_members(members TEXT[], memberLabels TEXT[]) RETURNS SETOF TEXT
2171 FOR i IN 1..ARRAY_UPPER(members,1) BY 2 LOOP
2172 IF members[i+1] = ANY(memberLabels) THEN
2173 RETURN NEXT members[i];
2182 CREATE OR REPLACE FUNCTION quad_split_geometry(geometry GEOMETRY, maxarea FLOAT, maxdepth INTEGER)
2183 RETURNS SETOF GEOMETRY
2197 remainingdepth INTEGER;
2202 -- RAISE WARNING 'quad_split_geometry: maxarea=%, depth=%',maxarea,maxdepth;
2204 IF (ST_GeometryType(geometry) not in ('ST_Polygon','ST_MultiPolygon') OR NOT ST_IsValid(geometry)) THEN
2205 RETURN NEXT geometry;
2209 remainingdepth := maxdepth - 1;
2210 area := ST_AREA(geometry);
2211 IF remainingdepth < 1 OR area < maxarea THEN
2212 RETURN NEXT geometry;
2216 xmin := st_xmin(geometry);
2217 xmax := st_xmax(geometry);
2218 ymin := st_ymin(geometry);
2219 ymax := st_ymax(geometry);
2220 secbox := ST_SetSRID(ST_MakeBox2D(ST_Point(ymin,xmin),ST_Point(ymax,xmax)),4326);
2222 -- if the geometry completely covers the box don't bother to slice any more
2223 IF ST_AREA(secbox) = area THEN
2224 RETURN NEXT geometry;
2228 xmid := (xmin+xmax)/2;
2229 ymid := (ymin+ymax)/2;
2232 FOR seg IN 1..4 LOOP
2235 secbox := ST_SetSRID(ST_MakeBox2D(ST_Point(xmin,ymin),ST_Point(xmid,ymid)),4326);
2238 secbox := ST_SetSRID(ST_MakeBox2D(ST_Point(xmin,ymid),ST_Point(xmid,ymax)),4326);
2241 secbox := ST_SetSRID(ST_MakeBox2D(ST_Point(xmid,ymin),ST_Point(xmax,ymid)),4326);
2244 secbox := ST_SetSRID(ST_MakeBox2D(ST_Point(xmid,ymid),ST_Point(xmax,ymax)),4326);
2247 IF st_intersects(geometry, secbox) THEN
2248 secgeo := st_intersection(geometry, secbox);
2249 IF NOT ST_IsEmpty(secgeo) AND ST_GeometryType(secgeo) in ('ST_Polygon','ST_MultiPolygon') THEN
2250 FOR geo IN select quad_split_geometry(secgeo, maxarea, remainingdepth) as geom LOOP
2251 IF NOT ST_IsEmpty(geo.geom) AND ST_GeometryType(geo.geom) in ('ST_Polygon','ST_MultiPolygon') THEN
2253 RETURN NEXT geo.geom;
2265 CREATE OR REPLACE FUNCTION split_geometry(geometry GEOMETRY)
2266 RETURNS SETOF GEOMETRY
2271 -- 10000000000 is ~~ 1x1 degree
2272 FOR geo IN select quad_split_geometry(geometry, 0.25, 20) as geom LOOP
2273 RETURN NEXT geo.geom;
2281 CREATE OR REPLACE FUNCTION place_force_delete(placeid BIGINT) RETURNS BOOLEAN
2285 osmtype character(1);
2289 SELECT osm_type, osm_id, class, type FROM placex WHERE place_id = placeid INTO osmtype, osmid, pclass, ptype;
2290 DELETE FROM import_polygon_delete where osm_type = osmtype and osm_id = osmid and class = pclass and type = ptype;
2291 DELETE FROM import_polygon_error where osm_type = osmtype and osm_id = osmid and class = pclass and type = ptype;
2292 -- force delete from place/placex by making it a very small geometry
2293 UPDATE place set geometry = ST_SetSRID(ST_Point(0,0), 4326) where osm_type = osmtype and osm_id = osmid and class = pclass and type = ptype;
2294 DELETE FROM place where osm_type = osmtype and osm_id = osmid and class = pclass and type = ptype;
2301 CREATE OR REPLACE FUNCTION place_force_update(placeid BIGINT) RETURNS BOOLEAN
2309 UPDATE placex SET indexed_status = 2 WHERE place_id = placeid;
2310 SELECT geometry, rank_search FROM placex WHERE place_id = placeid INTO placegeom, rank;
2311 IF placegeom IS NOT NULL AND ST_IsValid(placegeom) THEN
2312 IF ST_GeometryType(placegeom) in ('ST_Polygon','ST_MultiPolygon') THEN
2313 FOR geom IN select split_geometry(placegeom) FROM placex WHERE place_id = placeid LOOP
2314 update placex set indexed_status = 2 where (st_covers(geom, placex.geometry) OR ST_Intersects(geom, placex.geometry))
2315 AND rank_search > rank and indexed_status = 0 and ST_geometrytype(placex.geometry) = 'ST_Point' and (rank_search < 28 or name is not null or (rank >= 16 and address ? 'place'));
2316 update placex set indexed_status = 2 where (st_covers(geom, placex.geometry) OR ST_Intersects(geom, placex.geometry))
2317 AND rank_search > rank and indexed_status = 0 and ST_geometrytype(placex.geometry) != 'ST_Point' and (rank_search < 28 or name is not null or (rank >= 16 and address ? 'place'));
2323 ELSEIF rank < 18 THEN
2325 ELSEIF rank < 20 THEN
2327 ELSEIF rank = 21 THEN
2329 ELSEIF rank < 24 THEN
2331 ELSEIF rank < 26 THEN
2332 diameter := 0.002; -- 100 to 200 meters
2333 ELSEIF rank < 28 THEN
2334 diameter := 0.001; -- 50 to 100 meters
2336 IF diameter > 0 THEN
2338 -- roads may cause reparenting for >27 rank places
2339 update placex set indexed_status = 2 where indexed_status = 0 and rank_search > rank and ST_DWithin(placex.geometry, placegeom, diameter);
2340 ELSEIF rank >= 16 THEN
2341 -- up to rank 16, street-less addresses may need reparenting
2342 update placex set indexed_status = 2 where indexed_status = 0 and rank_search > rank and ST_DWithin(placex.geometry, placegeom, diameter) and (rank_search < 28 or name is not null or address ? 'place');
2344 -- for all other places the search terms may change as well
2345 update placex set indexed_status = 2 where indexed_status = 0 and rank_search > rank and ST_DWithin(placex.geometry, placegeom, diameter) and (rank_search < 28 or name is not null);