X-Git-Url: https://git.openstreetmap.org./nominatim.git/blobdiff_plain/c3dc66ce9c4f5fccaec405c01e7d71130dc19d2b..1529666232c5d72813280c081a4dc086cd0f9910:/sql/functions/placex_triggers.sql diff --git a/sql/functions/placex_triggers.sql b/sql/functions/placex_triggers.sql index 70e32e83..38b3421f 100644 --- a/sql/functions/placex_triggers.sql +++ b/sql/functions/placex_triggers.sql @@ -1,24 +1,119 @@ -- Trigger functions for the placex table. -CREATE OR REPLACE FUNCTION get_rel_node_members(members TEXT[], memberLabels TEXT[]) - RETURNS SETOF BIGINT +-- Find the parent road of a POI. +-- +-- \returns Place ID of parent object or NULL if none +-- +-- Copy data from linked items (POIs on ways, addr:street links, relations). +-- +CREATE OR REPLACE FUNCTION find_parent_for_poi(poi_osm_type CHAR(1), + poi_osm_id BIGINT, + poi_partition SMALLINT, + bbox GEOMETRY, + addr_street TEXT, + addr_place TEXT, + fallback BOOL = true) + RETURNS BIGINT AS $$ DECLARE - i INTEGER; + parent_place_id BIGINT DEFAULT NULL; + location RECORD; + parent RECORD; BEGIN - FOR i IN 1..ARRAY_UPPER(members,1) BY 2 LOOP - IF members[i+1] = ANY(memberLabels) - AND upper(substring(members[i], 1, 1))::char(1) = 'N' - THEN - RETURN NEXT substring(members[i], 2)::bigint; + --DEBUG: RAISE WARNING 'finding street for % %', poi_osm_type, poi_osm_id; + + -- Is this object part of an associatedStreet relation? + FOR location IN + SELECT members FROM planet_osm_rels + WHERE parts @> ARRAY[poi_osm_id] + and members @> ARRAY[lower(poi_osm_type) || poi_osm_id] + and tags @> ARRAY['associatedStreet'] + LOOP + FOR i IN 1..array_upper(location.members, 1) BY 2 LOOP + IF location.members[i+1] = 'street' THEN + FOR parent IN + SELECT place_id from placex + WHERE osm_type = 'W' and osm_id = substring(location.members[i],2)::bigint + and name is not null + and rank_search between 26 and 27 + LOOP + RETURN parent.place_id; + END LOOP; + END IF; + END LOOP; + END LOOP; + + parent_place_id := find_parent_for_address(addr_street, addr_place, + poi_partition, bbox); + IF parent_place_id is not null THEN + RETURN parent_place_id; END IF; - END LOOP; - RETURN; + IF poi_osm_type = 'N' THEN + -- Is this node part of an interpolation? + FOR parent IN + SELECT q.parent_place_id + FROM location_property_osmline q, planet_osm_ways x + WHERE q.linegeo && bbox and x.id = q.osm_id + and poi_osm_id = any(x.nodes) + LIMIT 1 + LOOP + --DEBUG: RAISE WARNING 'Get parent from interpolation: %', parent.parent_place_id; + RETURN parent.parent_place_id; + END LOOP; + + -- Is this node part of any other way? + FOR location IN + SELECT p.place_id, p.osm_id, p.rank_search, p.address, + coalesce(p.centroid, ST_Centroid(p.geometry)) as centroid + FROM placex p, planet_osm_ways w + WHERE p.osm_type = 'W' and p.rank_search >= 26 + and p.geometry && bbox + and w.id = p.osm_id and poi_osm_id = any(w.nodes) + LOOP + --DEBUG: RAISE WARNING 'Node is part of way % ', location.osm_id; + + -- Way IS a road then we are on it - that must be our road + IF location.rank_search < 28 THEN + --DEBUG: RAISE WARNING 'node in way that is a street %',location; + return location.place_id; + END IF; + + SELECT find_parent_for_poi('W', location.osm_id, poi_partition, + location.centroid, + location.address->'street', + location.address->'place', + false) + INTO parent_place_id; + IF parent_place_id is not null THEN + RETURN parent_place_id; + END IF; + END LOOP; + END IF; + + IF fallback THEN + IF ST_Area(bbox) < 0.01 THEN + -- for smaller features get the nearest road + SELECT getNearestRoadPlaceId(poi_partition, bbox) INTO parent_place_id; + --DEBUG: RAISE WARNING 'Checked for nearest way (%)', parent_place_id; + ELSE + -- for larger features simply find the area with the largest rank that + -- contains the bbox, only use addressable features + FOR location IN + SELECT place_id FROM placex + WHERE bbox @ geometry AND _ST_Covers(geometry, ST_Centroid(bbox)) + AND rank_address between 5 and 25 + ORDER BY rank_address desc + LOOP + RETURN location.place_id; + END LOOP; + END IF; + END IF; + + RETURN parent_place_id; END; $$ -LANGUAGE plpgsql IMMUTABLE; - +LANGUAGE plpgsql STABLE; -- Try to find a linked place for the given object. CREATE OR REPLACE FUNCTION find_linked_place(bnd placex) @@ -68,43 +163,50 @@ BEGIN END IF; END IF; - -- Search for relation members with role admin_center. - IF bnd.osm_type = 'R' and bnd_name is not null - and relation_members is not null THEN - FOR rel_member IN - SELECT get_rel_node_members(relation_members, - ARRAY['admin_center','admin_centre']) as member + -- If extratags has a place tag, look for linked nodes by their place type. + -- Area and node still have to have the same name. + IF bnd.extratags ? 'place' and bnd_name is not null THEN + FOR linked_placex IN + SELECT * FROM placex + WHERE make_standard_name(name->'name') = bnd_name + AND placex.class = 'place' AND placex.type = bnd.extratags->'place' + AND placex.osm_type = 'N' + AND placex.rank_search < 26 -- needed to select the right index + AND _st_covers(bnd.geometry, placex.geometry) LOOP - --DEBUG: RAISE WARNING 'Found admin_center member %', rel_member.member; - FOR linked_placex IN - SELECT * from placex - WHERE osm_type = 'N' and osm_id = rel_member.member - and class = 'place' - LOOP - -- For an admin centre we also want a name match - still not perfect, - -- for example 'new york, new york' - -- But that can be fixed by explicitly setting the label in the data - IF bnd_name = make_standard_name(linked_placex.name->'name') - AND bnd.rank_address = linked_placex.rank_address - THEN - RETURN linked_placex; - END IF; - --DEBUG: RAISE WARNING 'Linked admin_center'; - END LOOP; + --DEBUG: RAISE WARNING 'Found type-matching place node %', linked_placex.osm_id; + RETURN linked_placex; + END LOOP; + END IF; + + IF bnd.extratags ? 'wikidata' THEN + FOR linked_placex IN + SELECT * FROM placex + WHERE placex.class = 'place' AND placex.osm_type = 'N' + AND placex.extratags ? 'wikidata' -- needed to select right index + AND placex.extratags->'wikidata' = bnd.extratags->'wikidata' + AND placex.rank_search < 26 + AND _st_covers(bnd.geometry, placex.geometry) + ORDER BY make_standard_name(name->'name') = bnd_name desc + LOOP + --DEBUG: RAISE WARNING 'Found wikidata-matching place node %', linked_placex.osm_id; + RETURN linked_placex; END LOOP; END IF; -- Name searches can be done for ways as well as relations - IF bnd.osm_type in ('W','R') and bnd_name is not null THEN + IF bnd_name is not null THEN --DEBUG: RAISE WARNING 'Looking for nodes with matching names'; FOR linked_placex IN SELECT placex.* from placex WHERE make_standard_name(name->'name') = bnd_name - AND placex.rank_address = bnd.rank_address + AND ((bnd.rank_address > 0 and placex.rank_address = bnd.rank_address) + OR (bnd.rank_address = 0 and placex.rank_search = bnd.rank_search)) AND placex.osm_type = 'N' - AND st_covers(geometry, placex.geometry) + AND placex.rank_search < 26 -- needed to select the right index + AND _st_covers(bnd.geometry, placex.geometry) LOOP - --DEBUG: RAISE WARNING 'Found matching place node %', linkedPlacex.osm_id; + --DEBUG: RAISE WARNING 'Found matching place node %', linked_placex.osm_id; RETURN linked_placex; END LOOP; END IF; @@ -112,21 +214,183 @@ BEGIN RETURN NULL; END; $$ +LANGUAGE plpgsql STABLE; + + +-- Insert address of a place into the place_addressline table. +-- +-- \param obj_place_id Place_id of the place to compute the address for. +-- \param partition Partition number where the place is in. +-- \param maxrank Rank of the place. All address features must have +-- a search rank lower than the given rank. +-- \param address Address terms for the place. +-- \param geoemtry Geometry to which the address objects should be close. +-- +-- \retval parent_place_id Place_id of the address object that is the direct +-- ancestor. +-- \retval postcode Postcode computed from the address. This is the +-- addr:postcode of one of the address objects. If +-- more than one of has a postcode, the highest ranking +-- one is used. May be NULL. +-- \retval nameaddress_vector Search terms for the address. This is the sum +-- of name terms of all address objects. +CREATE OR REPLACE FUNCTION insert_addresslines(obj_place_id BIGINT, + partition SMALLINT, + maxrank SMALLINT, + address HSTORE, + geometry GEOMETRY, + OUT parent_place_id BIGINT, + OUT postcode TEXT, + OUT nameaddress_vector INT[]) + AS $$ +DECLARE + current_rank_address INTEGER := 0; + location_distance FLOAT := 0; + location_parent GEOMETRY := NULL; + parent_place_id_rank SMALLINT := 0; + + location_isaddress BOOLEAN; + + address_havelevel BOOLEAN[]; + location_keywords INT[]; + + location RECORD; + addr_item RECORD; + + isin_tokens INT[]; + isin TEXT[]; +BEGIN + parent_place_id := 0; + nameaddress_vector := '{}'::int[]; + isin_tokens := '{}'::int[]; + + ---- convert address store to array of tokenids + IF address IS NOT NULL THEN + FOR addr_item IN SELECT * FROM each(address) + LOOP + IF addr_item.key IN ('city', 'tiger:county', 'state', 'suburb', 'province', + 'district', 'region', 'county', 'municipality', + 'hamlet', 'village', 'subdistrict', 'town', + 'neighbourhood', 'quarter', 'parish') + THEN + isin_tokens := array_merge(isin_tokens, + word_ids_from_name(addr_item.value)); + IF NOT %REVERSE-ONLY% THEN + nameaddress_vector := array_merge(nameaddress_vector, + addr_ids_from_name(addr_item.value)); + END IF; + END IF; + END LOOP; + + IF address ? 'is_in' THEN + -- is_in items need splitting + isin := regexp_split_to_array(address->'is_in', E'[;,]'); + IF array_upper(isin, 1) IS NOT NULL THEN + FOR i IN 1..array_upper(isin, 1) LOOP + isin_tokens := array_merge(isin_tokens, + word_ids_from_name(isin[i])); + + -- merge word into address vector + IF NOT %REVERSE-ONLY% THEN + nameaddress_vector := array_merge(nameaddress_vector, + addr_ids_from_name(isin[i])); + END IF; + END LOOP; + END IF; + END IF; + END IF; + IF NOT %REVERSE-ONLY% THEN + nameaddress_vector := array_merge(nameaddress_vector, isin_tokens); + END IF; + + ---- now compute the address terms + FOR i IN 1..28 LOOP + address_havelevel[i] := false; + END LOOP; + + FOR location IN + SELECT * FROM getNearFeatures(partition, geometry, maxrank, isin_tokens) + LOOP + IF location.rank_address != current_rank_address THEN + current_rank_address := location.rank_address; + IF location.isguess THEN + location_distance := location.distance * 1.5; + ELSE + IF location.rank_address <= 12 THEN + -- for county and above, if we have an area consider that exact + -- (It would be nice to relax the constraint for places close to + -- the boundary but we'd need the exact geometry for that. Too + -- expensive.) + location_distance = 0; + ELSE + -- Below county level remain slightly fuzzy. + location_distance := location.distance * 0.5; + END IF; + END IF; + ELSE + CONTINUE WHEN location.keywords <@ location_keywords; + END IF; + + IF location.distance < location_distance OR NOT location.isguess THEN + location_keywords := location.keywords; + + location_isaddress := NOT address_havelevel[location.rank_address]; + --DEBUG: RAISE WARNING 'should be address: %, is guess: %, rank: %', location_isaddress, location.isguess, location.rank_address; + IF location_isaddress AND location.isguess AND location_parent IS NOT NULL THEN + location_isaddress := ST_Contains(location_parent, location.centroid); + END IF; + + --DEBUG: RAISE WARNING '% isaddress: %', location.place_id, location_isaddress; + -- Add it to the list of search terms + IF NOT %REVERSE-ONLY% THEN + nameaddress_vector := array_merge(nameaddress_vector, + location.keywords::integer[]); + END IF; + + INSERT INTO place_addressline (place_id, address_place_id, fromarea, + isaddress, distance, cached_rank_address) + VALUES (obj_place_id, location.place_id, true, + location_isaddress, location.distance, location.rank_address); + + IF location_isaddress THEN + -- add postcode if we have one + -- (If multiple postcodes are available, we end up with the highest ranking one.) + IF location.postcode is not null THEN + postcode = location.postcode; + END IF; + + address_havelevel[location.rank_address] := true; + -- add a hack against postcode ranks + IF NOT location.isguess + AND location.rank_address != 11 AND location.rank_address != 5 + THEN + SELECT p.geometry FROM placex p + WHERE p.place_id = location.place_id INTO location_parent; + END IF; + + IF location.rank_address > parent_place_id_rank THEN + parent_place_id = location.place_id; + parent_place_id_rank = location.rank_address; + END IF; + END IF; + END IF; + + END LOOP; +END; +$$ LANGUAGE plpgsql; + CREATE OR REPLACE FUNCTION placex_insert() RETURNS TRIGGER AS $$ DECLARE - i INTEGER; postcode TEXT; result BOOLEAN; is_area BOOLEAN; country_code VARCHAR(2); - default_language VARCHAR(10); diameter FLOAT; classtable TEXT; - classtype TEXT; BEGIN --DEBUG: RAISE WARNING '% % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type; @@ -138,26 +402,14 @@ BEGIN NEW.partition := get_partition(NEW.country_code); NEW.geometry_sector := geometry_sector(NEW.partition, NEW.geometry); - -- copy 'name' to or from the default language (if there is a default language) - IF NEW.name is not null AND array_upper(akeys(NEW.name),1) > 1 THEN - default_language := get_country_language_code(NEW.country_code); - IF default_language IS NOT NULL THEN - IF NEW.name ? 'name' AND NOT NEW.name ? ('name:'||default_language) THEN - NEW.name := NEW.name || hstore(('name:'||default_language), (NEW.name -> 'name')); - ELSEIF NEW.name ? ('name:'||default_language) AND NOT NEW.name ? 'name' THEN - NEW.name := NEW.name || hstore('name', (NEW.name -> ('name:'||default_language))); - END IF; - END IF; - END IF; - IF NEW.osm_type = 'X' THEN -- E'X'ternal records should already be in the right format so do nothing ELSE is_area := ST_GeometryType(NEW.geometry) IN ('ST_Polygon','ST_MultiPolygon'); IF NEW.class in ('place','boundary') - AND NEW.type in ('postcode','postal_code') THEN - + AND NEW.type in ('postcode','postal_code') + THEN IF NEW.address IS NULL OR NOT NEW.address ? 'postcode' THEN -- most likely just a part of a multipolygon postcode boundary, throw it away RETURN NULL; @@ -165,63 +417,28 @@ BEGIN NEW.name := hstore('ref', NEW.address->'postcode'); - SELECT * FROM get_postcode_rank(NEW.country_code, NEW.address->'postcode') - INTO NEW.rank_search, NEW.rank_address; - - IF NOT is_area THEN - NEW.rank_address := 0; - END IF; ELSEIF NEW.class = 'boundary' AND NOT is_area THEN - return NULL; + RETURN NULL; ELSEIF NEW.class = 'boundary' AND NEW.type = 'administrative' - AND NEW.admin_level <= 4 AND NEW.osm_type = 'W' THEN - return NULL; - ELSEIF NEW.osm_type = 'N' AND NEW.class = 'highway' THEN - NEW.rank_search = 30; - NEW.rank_address = 0; - ELSEIF NEW.class = 'landuse' AND NOT is_area THEN - NEW.rank_search = 30; - NEW.rank_address = 0; - ELSE - -- do table lookup stuff - IF NEW.class = 'boundary' and NEW.type = 'administrative' THEN - classtype = NEW.type || NEW.admin_level::TEXT; - ELSE - classtype = NEW.type; - END IF; - SELECT l.rank_search, l.rank_address FROM address_levels l - WHERE (l.country_code = NEW.country_code or l.country_code is NULL) - AND l.class = NEW.class AND (l.type = classtype or l.type is NULL) - ORDER BY l.country_code, l.class, l.type LIMIT 1 - INTO NEW.rank_search, NEW.rank_address; - - IF NEW.rank_search is NULL THEN - NEW.rank_search := 30; - END IF; - - IF NEW.rank_address is NULL THEN - NEW.rank_address := 30; - END IF; + AND NEW.admin_level <= 4 AND NEW.osm_type = 'W' + THEN + RETURN NULL; END IF; - -- some postcorrections - IF NEW.class = 'waterway' AND NEW.osm_type = 'R' THEN - -- Slightly promote waterway relations so that they are processed - -- before their members. - NEW.rank_search := NEW.rank_search - 1; - END IF; + SELECT * INTO NEW.rank_search, NEW.rank_address + FROM compute_place_rank(NEW.country_code, + CASE WHEN is_area THEN 'A' ELSE NEW.osm_type END, + NEW.class, NEW.type, NEW.admin_level, + (NEW.extratags->'capital') = 'yes', + NEW.address->'postcode'); - IF (NEW.extratags -> 'capital') = 'yes' THEN - NEW.rank_search := NEW.rank_search - 1; + -- a country code make no sense below rank 4 (country) + IF NEW.rank_search < 4 THEN + NEW.country_code := NULL; END IF; END IF; - -- a country code make no sense below rank 4 (country) - IF NEW.rank_search < 4 THEN - NEW.country_code := NULL; - END IF; - --DEBUG: RAISE WARNING 'placex_insert:END: % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type; RETURN NEW; -- %DIFFUPDATES% The following is not needed until doing diff updates, and slows the main index process down @@ -244,25 +461,7 @@ BEGIN END IF; ELSE -- mark nearby items for re-indexing, where 'nearby' depends on the features rank_search and is a complete guess :( - diameter := 0; - -- 16 = city, anything higher than city is effectively ignored (polygon required!) - IF NEW.type='postcode' THEN - diameter := 0.05; - ELSEIF NEW.rank_search < 16 THEN - diameter := 0; - ELSEIF NEW.rank_search < 18 THEN - diameter := 0.1; - ELSEIF NEW.rank_search < 20 THEN - diameter := 0.05; - ELSEIF NEW.rank_search = 21 THEN - diameter := 0.001; - ELSEIF NEW.rank_search < 24 THEN - diameter := 0.02; - ELSEIF NEW.rank_search < 26 THEN - diameter := 0.002; -- 100 to 200 meters - ELSEIF NEW.rank_search < 28 THEN - diameter := 0.001; -- 50 to 100 meters - END IF; + diameter := update_place_diameter(NEW.rank_search); IF diameter > 0 THEN -- RAISE WARNING 'placex point insert: % % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type,diameter; IF NEW.rank_search >= 26 THEN @@ -298,48 +497,48 @@ END; $$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION placex_update() - RETURNS TRIGGER +CREATE OR REPLACE FUNCTION get_parent_address_level(geom GEOMETRY, in_level SMALLINT) + RETURNS SMALLINT AS $$ DECLARE + address_rank SMALLINT; +BEGIN + IF in_level <= 3 or in_level > 15 THEN + address_rank := 3; + ELSE + SELECT rank_address INTO address_rank + FROM placex + WHERE osm_type = 'R' and class = 'boundary' and type = 'administrative' + and admin_level < in_level + and geometry && geom and ST_Covers(geometry, geom) + ORDER BY admin_level desc LIMIT 1; + END IF; - place_centroid GEOMETRY; - near_centroid GEOMETRY; + IF address_rank is NULL or address_rank <= 3 THEN + RETURN 3; + END IF; - search_maxdistance FLOAT[]; - search_mindistance FLOAT[]; - address_havelevel BOOLEAN[]; + RETURN address_rank; +END; +$$ +LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION placex_update() + RETURNS TRIGGER + AS $$ +DECLARE i INTEGER; - iMax FLOAT; location RECORD; - way RECORD; - relation RECORD; relation_members TEXT[]; - linkedplacex RECORD; - addr_item RECORD; - search_diameter FLOAT; - search_prevdiameter FLOAT; - search_maxrank INTEGER; - address_maxrank INTEGER; - address_street_word_id INTEGER; - address_street_word_ids INTEGER[]; - parent_place_id_rank BIGINT; + + centroid GEOMETRY; + parent_address_level SMALLINT; + place_address_level SMALLINT; addr_street TEXT; addr_place TEXT; - isin TEXT[]; - isin_tokens INT[]; - - location_rank_search INTEGER; - location_distance FLOAT; - location_parent GEOMETRY; - location_isaddress BOOLEAN; - location_keywords INTEGER[]; - - default_language TEXT; name_vector INTEGER[]; nameaddress_vector INTEGER[]; @@ -375,11 +574,41 @@ BEGIN where linked_place_id = NEW.place_id; -- update not necessary for osmline, cause linked_place_id does not exist + NEW.extratags := NEW.extratags - 'linked_place'::TEXT; + IF NEW.linked_place_id is not null THEN --DEBUG: RAISE WARNING 'place already linked to %', NEW.linked_place_id; RETURN NEW; END IF; + -- Speed up searches - just use the centroid of the feature + -- cheaper but less acurate + NEW.centroid := ST_PointOnSurface(NEW.geometry); + --DEBUG: RAISE WARNING 'Computing preliminary centroid at %',ST_AsText(NEW.centroid); + + -- recompute the ranks, they might change when linking changes + SELECT * INTO NEW.rank_search, NEW.rank_address + FROM compute_place_rank(NEW.country_code, + CASE WHEN ST_GeometryType(NEW.geometry) + IN ('ST_Polygon','ST_MultiPolygon') + THEN 'A' ELSE NEW.osm_type END, + NEW.class, NEW.type, NEW.admin_level, + (NEW.extratags->'capital') = 'yes', + NEW.address->'postcode'); + -- We must always increase the address level relative to the admin boundary. + IF NEW.class = 'boundary' and NEW.type = 'administrative' and NEW.osm_type = 'R' THEN + parent_address_level := get_parent_address_level(NEW.centroid, NEW.admin_level); + IF parent_address_level >= NEW.rank_address THEN + IF parent_address_level >= 24 THEN + NEW.rank_address := 25; + ELSE + NEW.rank_address := parent_address_level + 2; + END IF; + END IF; + ELSE + parent_address_level := 3; + END IF; + --DEBUG: RAISE WARNING 'Copy over address tags'; -- housenumber is a computed field, so start with an empty value NEW.housenumber := NULL; @@ -408,14 +637,7 @@ BEGIN END IF; END IF; - -- Speed up searches - just use the centroid of the feature - -- cheaper but less acurate - place_centroid := ST_PointOnSurface(NEW.geometry); - -- For searching near features rather use the centroid - near_centroid := ST_Envelope(NEW.geometry); - NEW.centroid := null; NEW.postcode := null; - --DEBUG: RAISE WARNING 'Computing preliminary centroid at %',ST_AsText(place_centroid); -- recalculate country and partition IF NEW.rank_search = 4 AND NEW.address is not NULL AND NEW.address ? 'country' THEN @@ -425,12 +647,12 @@ BEGIN NEW.country_code := lower(NEW.address->'country'); NEW.partition := get_partition(lower(NEW.country_code)); IF NEW.partition = 0 THEN - NEW.country_code := lower(get_country_code(place_centroid)); + NEW.country_code := lower(get_country_code(NEW.centroid)); NEW.partition := get_partition(NEW.country_code); END IF; ELSE IF NEW.rank_search >= 4 THEN - NEW.country_code := lower(get_country_code(place_centroid)); + NEW.country_code := lower(get_country_code(NEW.centroid)); ELSE NEW.country_code := NULL; END IF; @@ -451,6 +673,9 @@ BEGIN and ( relation_members[i+1] != 'side_stream' or NEW.name->'name' = name->'name') LOOP UPDATE placex SET linked_place_id = NEW.place_id WHERE place_id = linked_node_id; + IF NOT %REVERSE-ONLY% THEN + DELETE FROM search_name WHERE place_id = linked_node_id; + END IF; END LOOP; END IF; END LOOP; @@ -458,31 +683,6 @@ BEGIN --DEBUG: RAISE WARNING 'Waterway processed'; END IF; - -- What level are we searching from - search_maxrank := NEW.rank_search; - - -- Thought this wasn't needed but when we add new languages to the country_name table - -- we need to update the existing names - IF NEW.name is not null AND array_upper(akeys(NEW.name),1) > 1 THEN - default_language := get_country_language_code(NEW.country_code); - IF default_language IS NOT NULL THEN - IF NEW.name ? 'name' AND NOT NEW.name ? ('name:'||default_language) THEN - NEW.name := NEW.name || hstore(('name:'||default_language), (NEW.name -> 'name')); - ELSEIF NEW.name ? ('name:'||default_language) AND NOT NEW.name ? 'name' THEN - NEW.name := NEW.name || hstore('name', (NEW.name -> ('name:'||default_language))); - END IF; - END IF; - END IF; - --DEBUG: RAISE WARNING 'Local names updated'; - - -- Initialise the name vector using our name - name_vector := make_keywords(NEW.name); - nameaddress_vector := '{}'::int[]; - - FOR i IN 1..28 LOOP - address_havelevel[i] := false; - END LOOP; - NEW.importance := null; SELECT wikipedia, importance FROM compute_importance(NEW.extratags, NEW.country_code, NEW.osm_type, NEW.osm_id) @@ -495,18 +695,17 @@ BEGIN IF (NEW.rank_search > 27 OR (NEW.type = 'postcode' AND NEW.rank_search = 25)) THEN --DEBUG: RAISE WARNING 'finding street for % %', NEW.osm_type, NEW.osm_id; - - -- We won't get a better centroid, besides these places are too small to care - NEW.centroid := place_centroid; - NEW.parent_place_id := null; -- if we have a POI and there is no address information, -- see if we can get it from a surrounding building IF NEW.osm_type = 'N' AND addr_street IS NULL AND addr_place IS NULL AND NEW.housenumber IS NULL THEN - FOR location IN select address from placex where ST_Covers(geometry, place_centroid) - and address is not null + FOR location IN + -- The additional && condition works around the misguided query + -- planner of postgis 3.0. + SELECT address from placex where ST_Covers(geometry, NEW.centroid) + and geometry && NEW.centroid and (address ? 'housenumber' or address ? 'street' or address ? 'place') and rank_search > 28 AND ST_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon') limit 1 @@ -514,122 +713,18 @@ BEGIN NEW.housenumber := location.address->'housenumber'; addr_street := location.address->'street'; addr_place := location.address->'place'; - --DEBUG: RAISE WARNING 'Found surrounding building % %', location.osm_type, location.osm_id; END LOOP; END IF; -- We have to find our parent road. - -- Copy data from linked items (points on ways, addr:street links, relations) + NEW.parent_place_id := find_parent_for_poi(NEW.osm_type, NEW.osm_id, + NEW.partition, + ST_Envelope(NEW.geometry), + addr_street, addr_place); - -- Is this object part of a relation? - FOR relation IN select * from planet_osm_rels where parts @> ARRAY[NEW.osm_id] and members @> ARRAY[lower(NEW.osm_type)||NEW.osm_id] - LOOP - -- At the moment we only process one type of relation - associatedStreet - IF relation.tags @> ARRAY['associatedStreet'] THEN - FOR i IN 1..array_upper(relation.members, 1) BY 2 LOOP - IF NEW.parent_place_id IS NULL AND relation.members[i+1] = 'street' THEN ---RAISE WARNING 'node in relation %',relation; - SELECT place_id from placex where osm_type = 'W' - and osm_id = substring(relation.members[i],2,200)::bigint - and rank_search = 26 and name is not null INTO NEW.parent_place_id; - END IF; - END LOOP; - END IF; - END LOOP; - --DEBUG: RAISE WARNING 'Checked for street relation (%)', NEW.parent_place_id; - - -- Note that addr:street links can only be indexed once the street itself is indexed - IF NEW.parent_place_id IS NULL AND addr_street IS NOT NULL THEN - address_street_word_ids := get_name_ids(make_standard_name(addr_street)); - IF address_street_word_ids IS NOT NULL THEN - SELECT place_id from getNearestNamedRoadFeature(NEW.partition, near_centroid, address_street_word_ids) INTO NEW.parent_place_id; - END IF; - END IF; - --DEBUG: RAISE WARNING 'Checked for addr:street (%)', NEW.parent_place_id; - - IF NEW.parent_place_id IS NULL AND addr_place IS NOT NULL THEN - address_street_word_ids := get_name_ids(make_standard_name(addr_place)); - IF address_street_word_ids IS NOT NULL THEN - SELECT place_id from getNearestNamedPlaceFeature(NEW.partition, near_centroid, address_street_word_ids) INTO NEW.parent_place_id; - END IF; - END IF; - --DEBUG: RAISE WARNING 'Checked for addr:place (%)', NEW.parent_place_id; - - -- Is this node part of an interpolation? - IF NEW.parent_place_id IS NULL AND NEW.osm_type = 'N' THEN - SELECT q.parent_place_id FROM location_property_osmline q, planet_osm_ways x - WHERE q.linegeo && NEW.geometry and x.id = q.osm_id and NEW.osm_id = any(x.nodes) - LIMIT 1 INTO NEW.parent_place_id; - END IF; - --DEBUG: RAISE WARNING 'Checked for interpolation (%)', NEW.parent_place_id; - - -- Is this node part of a way? - IF NEW.parent_place_id IS NULL AND NEW.osm_type = 'N' THEN - - FOR location IN - SELECT p.place_id, p.osm_id, p.rank_search, p.address from placex p, planet_osm_ways w - 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) - LOOP - --DEBUG: RAISE WARNING 'Node is part of way % ', location.osm_id; - - -- Way IS a road then we are on it - that must be our road - IF location.rank_search < 28 THEN ---RAISE WARNING 'node in way that is a street %',location; - NEW.parent_place_id := location.place_id; - EXIT; - END IF; - --DEBUG: RAISE WARNING 'Checked if way is street (%)', NEW.parent_place_id; - - -- If the way mentions a street or place address, try that for parenting. - IF location.address is not null THEN - IF location.address ? 'street' THEN - address_street_word_ids := get_name_ids(make_standard_name(location.address->'street')); - IF address_street_word_ids IS NOT NULL THEN - SELECT place_id from getNearestNamedRoadFeature(NEW.partition, near_centroid, address_street_word_ids) INTO NEW.parent_place_id; - EXIT WHEN NEW.parent_place_id is not NULL; - END IF; - END IF; - --DEBUG: RAISE WARNING 'Checked for addr:street in way (%)', NEW.parent_place_id; - - IF location.address ? 'place' THEN - address_street_word_ids := get_name_ids(make_standard_name(location.address->'place')); - IF address_street_word_ids IS NOT NULL THEN - SELECT place_id from getNearestNamedPlaceFeature(NEW.partition, near_centroid, address_street_word_ids) INTO NEW.parent_place_id; - EXIT WHEN NEW.parent_place_id is not NULL; - END IF; - END IF; - --DEBUG: RAISE WARNING 'Checked for addr:place in way (%)', NEW.parent_place_id; - END IF; - - -- Is the WAY part of a relation - FOR relation IN select * from planet_osm_rels where parts @> ARRAY[location.osm_id] and members @> ARRAY['w'||location.osm_id] - LOOP - -- At the moment we only process one type of relation - associatedStreet - IF relation.tags @> ARRAY['associatedStreet'] AND array_upper(relation.members, 1) IS NOT NULL THEN - FOR i IN 1..array_upper(relation.members, 1) BY 2 LOOP - IF NEW.parent_place_id IS NULL AND relation.members[i+1] = 'street' THEN ---RAISE WARNING 'node in way that is in a relation %',relation; - SELECT place_id from placex where osm_type='W' and osm_id = substring(relation.members[i],2,200)::bigint - and rank_search = 26 and name is not null INTO NEW.parent_place_id; - END IF; - END LOOP; - END IF; - END LOOP; - EXIT WHEN NEW.parent_place_id is not null; - --DEBUG: RAISE WARNING 'Checked for street relation in way (%)', NEW.parent_place_id; - - END LOOP; - END IF; - - -- Still nothing, just use the nearest road - IF NEW.parent_place_id IS NULL THEN - SELECT place_id FROM getNearestRoadFeature(NEW.partition, near_centroid) INTO NEW.parent_place_id; - END IF; - --DEBUG: RAISE WARNING 'Checked for nearest way (%)', NEW.parent_place_id; - - - -- If we didn't find any road fallback to standard method - IF NEW.parent_place_id IS NOT NULL THEN + -- If we found the road take a shortcut here. + -- Otherwise fall back to the full address getting method below. + IF NEW.parent_place_id is not null THEN -- Get the details of the parent road SELECT p.country_code, p.postcode FROM placex p @@ -639,15 +734,13 @@ BEGIN --DEBUG: RAISE WARNING 'Got parent details from search name'; -- determine postcode - IF NEW.rank_search > 4 THEN - IF NEW.address is not null AND NEW.address ? 'postcode' THEN - NEW.postcode = upper(trim(NEW.address->'postcode')); - ELSE - NEW.postcode := location.postcode; - END IF; - IF NEW.postcode is null THEN - NEW.postcode := get_nearest_postcode(NEW.country_code, NEW.geometry); - END IF; + IF NEW.address is not null AND NEW.address ? 'postcode' THEN + NEW.postcode = upper(trim(NEW.address->'postcode')); + ELSE + NEW.postcode := location.postcode; + END IF; + IF NEW.postcode is null THEN + NEW.postcode := get_nearest_postcode(NEW.country_code, NEW.geometry); END IF; -- If there is no name it isn't searchable, don't bother to create a search record @@ -656,6 +749,9 @@ BEGIN return NEW; END IF; + NEW.name := add_default_place_name(NEW.country_code, NEW.name); + name_vector := make_keywords(NEW.name); + -- Performance, it would be more acurate to do all the rest of the import -- process but it takes too long -- Just be happy with inheriting from parent road only @@ -669,19 +765,17 @@ BEGIN IF NOT %REVERSE-ONLY% THEN -- Merge address from parent - SELECT s.name_vector, s.nameaddress_vector FROM search_name s - WHERE s.place_id = NEW.parent_place_id INTO location; - - nameaddress_vector := array_merge(nameaddress_vector, - location.nameaddress_vector); - nameaddress_vector := array_merge(nameaddress_vector, location.name_vector); + SELECT array_merge(s.name_vector, s.nameaddress_vector) + INTO nameaddress_vector + FROM search_name s + WHERE s.place_id = NEW.parent_place_id; INSERT INTO search_name (place_id, search_rank, address_rank, importance, country_code, name_vector, nameaddress_vector, centroid) VALUES (NEW.place_id, NEW.rank_search, NEW.rank_address, NEW.importance, NEW.country_code, name_vector, - nameaddress_vector, place_centroid); + nameaddress_vector, NEW.centroid); --DEBUG: RAISE WARNING 'Place added to search table'; END IF; @@ -693,191 +787,91 @@ BEGIN -- --------------------------------------------------------------------------- -- Full indexing --DEBUG: RAISE WARNING 'Using full index mode for % %', NEW.osm_type, NEW.osm_id; + SELECT * INTO location FROM find_linked_place(NEW); + IF location.place_id is not null THEN + --DEBUG: RAISE WARNING 'Linked %', location; + + -- Use the linked point as the centre point of the geometry, + -- but only if it is within the area of the boundary. + centroid := coalesce(location.centroid, ST_Centroid(location.geometry)); + IF centroid is not NULL AND ST_Within(centroid, NEW.geometry) THEN + NEW.centroid := centroid; + END IF; - FOR linkedPlacex IN SELECT * FROM find_linked_place(NEW) LOOP - -- If we don't already have one use this as the centre point of the geometry - IF NEW.centroid IS NULL THEN - --DEBUG: RAISE WARNING 'Linked %', linkedPlacex; - NEW.centroid := coalesce(linkedPlacex.centroid, - ST_Centroid(linkedPlacex.geometry)); + --DEBUG: RAISE WARNING 'parent address: % rank address: %', parent_address_level, location.rank_address; + IF location.rank_address > parent_address_level + and location.rank_address < 26 + THEN + NEW.rank_address := location.rank_address; END IF; - place_centroid := NEW.centroid; - - -- merge in the label name, re-init word vector - IF NOT linkedPlacex.name IS NULL THEN - NEW.name := linkedPlacex.name || NEW.name; - name_vector := array_merge(name_vector, make_keywords(linkedPlacex.name)); - - -- Place might have had only a name tag before but has now received - -- translations from the linked place. Make sure a name tag for the - -- default language exists in this case. - IF array_upper(akeys(NEW.name), 1) > 1 THEN - default_language := get_country_language_code(NEW.country_code); - IF default_language IS NOT NULL THEN - IF NEW.name ? 'name' AND NOT NEW.name ? ('name:'||default_language) THEN - NEW.name := NEW.name || hstore(('name:'||default_language), (NEW.name -> 'name')); - ELSEIF NEW.name ? ('name:'||default_language) AND NOT NEW.name ? 'name' THEN - NEW.name := NEW.name || hstore('name', (NEW.name -> ('name:'||default_language))); - END IF; - END IF; - END IF; + + -- merge in the label name + IF NOT location.name IS NULL THEN + NEW.name := location.name || NEW.name; END IF; -- merge in extra tags - NEW.extratags := hstore(linkedPlacex.class, linkedPlacex.type) - || coalesce(linkedPlacex.extratags, ''::hstore) + NEW.extratags := hstore('linked_' || location.class, location.type) + || coalesce(location.extratags, ''::hstore) || coalesce(NEW.extratags, ''::hstore); -- mark the linked place (excludes from search results) UPDATE placex set linked_place_id = NEW.place_id - WHERE place_id = linkedPlacex.place_id; + WHERE place_id = location.place_id; + -- ensure that those places are not found anymore + IF NOT %REVERSE-ONLY% THEN + DELETE FROM search_name WHERE place_id = location.place_id; + END IF; + PERFORM deleteLocationArea(NEW.partition, location.place_id, NEW.rank_search); SELECT wikipedia, importance - FROM compute_importance(linkedPlacex.extratags, NEW.country_code, - 'N', linkedPlacex.osm_id) + FROM compute_importance(location.extratags, NEW.country_code, + 'N', location.osm_id) INTO linked_wikipedia,linked_importance; - -- Use the maximum importance if a one could be computed from the linked object. + -- Use the maximum importance if one could be computed from the linked object. IF linked_importance is not null AND (NEW.importance is null or NEW.importance < linked_importance) THEN NEW.importance = linked_importance; END IF; + ELSE + -- No linked place? As a last resort check if the boundary is tagged with + -- a place type and adapt the rank address. + IF NEW.rank_address > 0 and NEW.extratags ? 'place' THEN + SELECT address_rank INTO place_address_level + FROM compute_place_rank(NEW.country_code, 'A', 'place', + NEW.extratags->'place', 0::SMALLINT, False, null); + IF place_address_level > parent_address_level and + place_address_level < 26 THEN + NEW.rank_address := place_address_level; + END IF; + END IF; + END IF; - END LOOP; + -- Initialise the name vector using our name + NEW.name := add_default_place_name(NEW.country_code, NEW.name); + name_vector := make_keywords(NEW.name); -- make sure all names are in the word table - 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 - perform create_country(NEW.name, lower(NEW.country_code)); + 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 + PERFORM create_country(NEW.name, lower(NEW.country_code)); --DEBUG: RAISE WARNING 'Country names updated'; END IF; - NEW.parent_place_id = 0; - parent_place_id_rank = 0; - - - -- convert address store to array of tokenids - --DEBUG: RAISE WARNING 'Starting address search'; - isin_tokens := '{}'::int[]; - IF NEW.address IS NOT NULL THEN - FOR addr_item IN SELECT * FROM each(NEW.address) - LOOP - IF addr_item.key IN ('city', 'tiger:county', 'state', 'suburb', 'province', 'district', 'region', 'county', 'municipality', 'hamlet', 'village', 'subdistrict', 'town', 'neighbourhood', 'quarter', 'parish') THEN - address_street_word_id := get_name_id(make_standard_name(addr_item.value)); - IF address_street_word_id IS NOT NULL AND NOT(ARRAY[address_street_word_id] <@ isin_tokens) THEN - isin_tokens := isin_tokens || address_street_word_id; - END IF; - IF NOT %REVERSE-ONLY% THEN - address_street_word_id := get_word_id(make_standard_name(addr_item.value)); - IF address_street_word_id IS NOT NULL THEN - nameaddress_vector := array_merge(nameaddress_vector, ARRAY[address_street_word_id]); - END IF; - END IF; - END IF; - IF addr_item.key = 'is_in' THEN - -- is_in items need splitting - isin := regexp_split_to_array(addr_item.value, E'[;,]'); - IF array_upper(isin, 1) IS NOT NULL THEN - FOR i IN 1..array_upper(isin, 1) LOOP - address_street_word_id := get_name_id(make_standard_name(isin[i])); - IF address_street_word_id IS NOT NULL AND NOT(ARRAY[address_street_word_id] <@ isin_tokens) THEN - isin_tokens := isin_tokens || address_street_word_id; - END IF; - - -- merge word into address vector - IF NOT %REVERSE-ONLY% THEN - address_street_word_id := get_word_id(make_standard_name(isin[i])); - IF address_street_word_id IS NOT NULL THEN - nameaddress_vector := array_merge(nameaddress_vector, ARRAY[address_street_word_id]); - END IF; - END IF; - END LOOP; - END IF; - END IF; - END LOOP; - END IF; - IF NOT %REVERSE-ONLY% THEN - nameaddress_vector := array_merge(nameaddress_vector, isin_tokens); - END IF; - --- RAISE WARNING 'ISIN: %', isin_tokens; - - -- Process area matches - location_rank_search := 0; - location_distance := 0; - location_parent := NULL; - -- added ourself as address already - address_havelevel[NEW.rank_address] := true; - --DEBUG: RAISE WARNING ' getNearFeatures(%,''%'',%,''%'')',NEW.partition, place_centroid, search_maxrank, isin_tokens; - FOR location IN - SELECT * from getNearFeatures(NEW.partition, - CASE WHEN NEW.rank_search >= 26 + SELECT * FROM insert_addresslines(NEW.place_id, NEW.partition, + CASE WHEN NEW.rank_address = 0 + THEN NEW.rank_search ELSE NEW.rank_address END, + NEW.address, + CASE WHEN NEW.rank_search >= 26 AND NEW.rank_search < 30 - THEN NEW.geometry - ELSE place_centroid END, - search_maxrank, isin_tokens) - LOOP - IF location.rank_address != location_rank_search THEN - location_rank_search := location.rank_address; - IF location.isguess THEN - location_distance := location.distance * 1.5; - ELSE - IF location.rank_address <= 12 THEN - -- for county and above, if we have an area consider that exact - -- (It would be nice to relax the constraint for places close to - -- the boundary but we'd need the exact geometry for that. Too - -- expensive.) - location_distance = 0; - ELSE - -- Below county level remain slightly fuzzy. - location_distance := location.distance * 0.5; - END IF; - END IF; - ELSE - CONTINUE WHEN location.keywords <@ location_keywords; - END IF; - - IF location.distance < location_distance OR NOT location.isguess THEN - location_keywords := location.keywords; - - location_isaddress := NOT address_havelevel[location.rank_address]; - IF location_isaddress AND location.isguess AND location_parent IS NOT NULL THEN - location_isaddress := ST_Contains(location_parent,location.centroid); - END IF; - - -- RAISE WARNING '% isaddress: %', location.place_id, location_isaddress; - -- Add it to the list of search terms - IF NOT %REVERSE-ONLY% THEN - nameaddress_vector := array_merge(nameaddress_vector, location.keywords::integer[]); - END IF; - INSERT INTO place_addressline (place_id, address_place_id, fromarea, isaddress, distance, cached_rank_address) - VALUES (NEW.place_id, location.place_id, true, location_isaddress, location.distance, location.rank_address); - - IF location_isaddress THEN - -- add postcode if we have one - -- (If multiple postcodes are available, we end up with the highest ranking one.) - IF location.postcode is not null THEN - NEW.postcode = location.postcode; - END IF; + THEN NEW.geometry ELSE NEW.centroid END) + INTO NEW.parent_place_id, NEW.postcode, nameaddress_vector; - address_havelevel[location.rank_address] := true; - IF NOT location.isguess THEN - SELECT geometry FROM placex WHERE place_id = location.place_id INTO location_parent; - END IF; - - IF location.rank_address > parent_place_id_rank THEN - NEW.parent_place_id = location.place_id; - parent_place_id_rank = location.rank_address; - END IF; - - END IF; - - --DEBUG: RAISE WARNING ' Terms: (%) %',location, nameaddress_vector; - - END IF; - - END LOOP; - --DEBUG: RAISE WARNING 'address computed'; + --DEBUG: RAISE WARNING 'RETURN insert_addresslines: %, %, %', NEW.parent_place_id, NEW.postcode, nameaddress_vector; IF NEW.address is not null AND NEW.address ? 'postcode' AND NEW.address->'postcode' not similar to '%(,|;)%' THEN @@ -911,16 +905,11 @@ BEGIN nameaddress_vector, centroid) VALUES (NEW.place_id, NEW.rank_search, NEW.rank_address, NEW.importance, NEW.country_code, name_vector, - nameaddress_vector, place_centroid); + nameaddress_vector, NEW.centroid); END IF; END IF; - -- If we've not managed to pick up a better one - default centroid - IF NEW.centroid IS NULL THEN - NEW.centroid := place_centroid; - END IF; - --DEBUG: RAISE WARNING 'place update % % finsihed.', NEW.osm_type, NEW.osm_id; RETURN NEW; @@ -938,10 +927,14 @@ DECLARE BEGIN -- RAISE WARNING 'placex_delete % %',OLD.osm_type,OLD.osm_id; - update placex set linked_place_id = null, indexed_status = 2 where linked_place_id = OLD.place_id and indexed_status = 0; - --DEBUG: RAISE WARNING 'placex_delete:01 % %',OLD.osm_type,OLD.osm_id; - update placex set linked_place_id = null where linked_place_id = OLD.place_id; - --DEBUG: RAISE WARNING 'placex_delete:02 % %',OLD.osm_type,OLD.osm_id; + IF OLD.linked_place_id is null THEN + update placex set linked_place_id = null, indexed_status = 2 where linked_place_id = OLD.place_id and indexed_status = 0; + --DEBUG: RAISE WARNING 'placex_delete:01 % %',OLD.osm_type,OLD.osm_id; + update placex set linked_place_id = null where linked_place_id = OLD.place_id; + --DEBUG: RAISE WARNING 'placex_delete:02 % %',OLD.osm_type,OLD.osm_id; + ELSE + update placex set indexed_status = 2 where place_id = OLD.linked_place_id and indexed_status = 0; + END IF; IF OLD.rank_address < 30 THEN