From 4a30ec28b9f5be3851289d1eb0bf1fe5228e5b6b Mon Sep 17 00:00:00 2001 From: Sarah Hoffmann Date: Fri, 10 Apr 2020 11:05:11 +0200 Subject: [PATCH 1/2] move helper functions from placex_triggers into utils Also adds documentation for these functions. --- sql/functions/placex_triggers.sql | 40 ---------------------- sql/functions/utils.sql | 57 +++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 40 deletions(-) diff --git a/sql/functions/placex_triggers.sql b/sql/functions/placex_triggers.sql index c04aef28..19367db4 100644 --- a/sql/functions/placex_triggers.sql +++ b/sql/functions/placex_triggers.sql @@ -1,45 +1,5 @@ -- Trigger functions for the placex table. -CREATE OR REPLACE FUNCTION get_rel_node_members(members TEXT[], memberLabels TEXT[]) - RETURNS SETOF BIGINT - AS $$ -DECLARE - i INTEGER; -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; - END IF; - END LOOP; - - RETURN; -END; -$$ -LANGUAGE plpgsql IMMUTABLE; - --- copy 'name' to or from the default language (if there is a default language) -CREATE OR REPLACE FUNCTION add_default_place_name(country_code VARCHAR(2), - INOUT name HSTORE) - AS $$ -DECLARE - default_language VARCHAR(10); -BEGIN - IF name is not null AND array_upper(akeys(name),1) > 1 THEN - default_language := get_country_language_code(country_code); - IF default_language IS NOT NULL THEN - IF name ? 'name' AND NOT name ? ('name:'||default_language) THEN - name := name || hstore(('name:'||default_language), (name -> 'name')); - ELSEIF name ? ('name:'||default_language) AND NOT name ? 'name' THEN - name := name || hstore('name', (name -> ('name:'||default_language))); - END IF; - END IF; - END IF; -END; -$$ -LANGUAGE plpgsql IMMUTABLE; - -- Find the parent road of a POI. -- -- \returns Place ID of parent object or NULL if none diff --git a/sql/functions/utils.sql b/sql/functions/utils.sql index 6371e9ad..ccb6a203 100644 --- a/sql/functions/utils.sql +++ b/sql/functions/utils.sql @@ -38,6 +38,63 @@ END; $$ LANGUAGE plpgsql IMMUTABLE; +-- Return the node members with a given label from a relation member list +-- as a set. +-- +-- \param members Member list in osm2pgsql middle format. +-- \param memberLabels Array of labels to accept. +-- +-- \returns Set of OSM ids of nodes that are found. +-- +CREATE OR REPLACE FUNCTION get_rel_node_members(members TEXT[], + memberLabels TEXT[]) + RETURNS SETOF BIGINT + AS $$ +DECLARE + i INTEGER; +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; + END IF; + END LOOP; + + RETURN; +END; +$$ +LANGUAGE plpgsql IMMUTABLE; + +-- Copy 'name' to or from the default language. +-- +-- \param country_code Country code of the object being named. +-- \param[inout] name List of names of the object. +-- +-- If the country named by country_code has a single default language, +-- then a `name` tag is copied to `name:` if this tag does +-- not yet exist and vice versa. +CREATE OR REPLACE FUNCTION add_default_place_name(country_code VARCHAR(2), + INOUT name HSTORE) + AS $$ +DECLARE + default_language VARCHAR(10); +BEGIN + IF name is not null AND array_upper(akeys(name),1) > 1 THEN + default_language := get_country_language_code(country_code); + IF default_language IS NOT NULL THEN + IF name ? 'name' AND NOT name ? ('name:'||default_language) THEN + name := name || hstore(('name:'||default_language), (name -> 'name')); + ELSEIF name ? ('name:'||default_language) AND NOT name ? 'name' THEN + name := name || hstore('name', (name -> ('name:'||default_language))); + END IF; + END IF; + END IF; +END; +$$ +LANGUAGE plpgsql IMMUTABLE; + + CREATE OR REPLACE FUNCTION reverse_place_diameter(rank_search SMALLINT) RETURNS FLOAT From f5f0c197be28f8eb80e47eaf00dea12d25aa917e Mon Sep 17 00:00:00 2001 From: Sarah Hoffmann Date: Fri, 10 Apr 2020 11:34:14 +0200 Subject: [PATCH 2/2] move ranks-related functions in separate sql file Also adds a common function for computing the update radius around place nodes. --- lib/setup/SetupClass.php | 1 + sql/functions/placex_triggers.sql | 20 +----- sql/functions/ranking.sql | 116 ++++++++++++++++++++++++++++++ sql/functions/utils.sql | 101 +------------------------- 4 files changed, 119 insertions(+), 119 deletions(-) create mode 100644 sql/functions/ranking.sql diff --git a/lib/setup/SetupClass.php b/lib/setup/SetupClass.php index aa1b291d..ac0f8f02 100755 --- a/lib/setup/SetupClass.php +++ b/lib/setup/SetupClass.php @@ -695,6 +695,7 @@ class SetupFunctions $sBasePath = CONST_BasePath.'/sql/functions/'; $sTemplate = file_get_contents($sBasePath.'utils.sql'); $sTemplate .= file_get_contents($sBasePath.'normalization.sql'); + $sTemplate .= file_get_contents($sBasePath.'ranking.sql'); $sTemplate .= file_get_contents($sBasePath.'importance.sql'); $sTemplate .= file_get_contents($sBasePath.'address_lookup.sql'); $sTemplate .= file_get_contents($sBasePath.'interpolation.sql'); diff --git a/sql/functions/placex_triggers.sql b/sql/functions/placex_triggers.sql index 19367db4..0151162e 100644 --- a/sql/functions/placex_triggers.sql +++ b/sql/functions/placex_triggers.sql @@ -498,25 +498,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 diff --git a/sql/functions/ranking.sql b/sql/functions/ranking.sql new file mode 100644 index 00000000..ecd31e90 --- /dev/null +++ b/sql/functions/ranking.sql @@ -0,0 +1,116 @@ +-- Functions related to search and address ranks + +-- Return an approximate search radius according to the search rank. +CREATE OR REPLACE FUNCTION reverse_place_diameter(rank_search SMALLINT) + RETURNS FLOAT + AS $$ +BEGIN + IF rank_search <= 4 THEN + RETURN 5.0; + ELSIF rank_search <= 8 THEN + RETURN 1.8; + ELSIF rank_search <= 12 THEN + RETURN 0.6; + ELSIF rank_search <= 17 THEN + RETURN 0.16; + ELSIF rank_search <= 18 THEN + RETURN 0.08; + ELSIF rank_search <= 19 THEN + RETURN 0.04; + END IF; + + RETURN 0.02; +END; +$$ +LANGUAGE plpgsql IMMUTABLE; + + +-- Return an approximate update radius according to the search rank. +CREATE OR REPLACE FUNCTION update_place_diameter(rank_search SMALLINT) + RETURNS FLOAT + AS $$ +BEGIN + -- postcodes + IF rank_search = 11 or rank_search = 5 THEN + RETURN 0.05; + -- anything higher than city is effectively ignored (polygon required) + ELSIF rank_search < 16 THEN + RETURN 0; + ELSIF rank_search < 18 THEN + RETURN 0.1; + ELSIF rank_search < 20 THEN + RETURN 0.05; + ELSIF rank_search = 21 THEN + RETURN 0.001; + ELSIF rank_search < 24 THEN + RETURN 0.02; + ELSIF rank_search < 26 THEN + RETURN 0.002; + ELSIF rank_search < 28 THEN + RETURN 0.001; + END IF; + + RETURN 0; +END; +$$ +LANGUAGE plpgsql IMMUTABLE; + + +-- Guess a ranking for postcodes from country and postcode format. +CREATE OR REPLACE FUNCTION get_postcode_rank(country_code VARCHAR(2), postcode TEXT, + OUT rank_search SMALLINT, + OUT rank_address SMALLINT) +AS $$ +DECLARE + part TEXT; +BEGIN + rank_search := 30; + rank_address := 30; + postcode := upper(postcode); + + IF country_code = 'gb' THEN + IF postcode ~ '^([A-Z][A-Z]?[0-9][0-9A-Z]? [0-9][A-Z][A-Z])$' THEN + rank_search := 25; + rank_address := 5; + ELSEIF postcode ~ '^([A-Z][A-Z]?[0-9][0-9A-Z]? [0-9])$' THEN + rank_search := 23; + rank_address := 5; + ELSEIF postcode ~ '^([A-Z][A-Z]?[0-9][0-9A-Z])$' THEN + rank_search := 21; + rank_address := 5; + END IF; + + ELSEIF country_code = 'sg' THEN + IF postcode ~ '^([0-9]{6})$' THEN + rank_search := 25; + rank_address := 11; + END IF; + + ELSEIF country_code = 'de' THEN + IF postcode ~ '^([0-9]{5})$' THEN + rank_search := 21; + rank_address := 11; + END IF; + + ELSE + -- Guess at the postcode format and coverage (!) + IF postcode ~ '^[A-Z0-9]{1,5}$' THEN -- Probably too short to be very local + rank_search := 21; + rank_address := 11; + ELSE + -- Does it look splitable into and area and local code? + part := substring(postcode from '^([- :A-Z0-9]+)([- :][A-Z0-9]+)$'); + + IF part IS NOT NULL THEN + rank_search := 25; + rank_address := 11; + ELSEIF postcode ~ '^[- :A-Z0-9]{6,}$' THEN + rank_search := 21; + rank_address := 11; + END IF; + END IF; + END IF; + +END; +$$ +LANGUAGE plpgsql IMMUTABLE; diff --git a/sql/functions/utils.sql b/sql/functions/utils.sql index ccb6a203..61033fb4 100644 --- a/sql/functions/utils.sql +++ b/sql/functions/utils.sql @@ -95,90 +95,6 @@ $$ LANGUAGE plpgsql IMMUTABLE; - -CREATE OR REPLACE FUNCTION reverse_place_diameter(rank_search SMALLINT) - RETURNS FLOAT - AS $$ -BEGIN - IF rank_search <= 4 THEN - RETURN 5.0; - ELSIF rank_search <= 8 THEN - RETURN 1.8; - ELSIF rank_search <= 12 THEN - RETURN 0.6; - ELSIF rank_search <= 17 THEN - RETURN 0.16; - ELSIF rank_search <= 18 THEN - RETURN 0.08; - ELSIF rank_search <= 19 THEN - RETURN 0.04; - END IF; - - RETURN 0.02; -END; -$$ -LANGUAGE plpgsql IMMUTABLE; - - -CREATE OR REPLACE FUNCTION get_postcode_rank(country_code VARCHAR(2), postcode TEXT, - OUT rank_search SMALLINT, - OUT rank_address SMALLINT) -AS $$ -DECLARE - part TEXT; -BEGIN - rank_search := 30; - rank_address := 30; - postcode := upper(postcode); - - IF country_code = 'gb' THEN - IF postcode ~ '^([A-Z][A-Z]?[0-9][0-9A-Z]? [0-9][A-Z][A-Z])$' THEN - rank_search := 25; - rank_address := 5; - ELSEIF postcode ~ '^([A-Z][A-Z]?[0-9][0-9A-Z]? [0-9])$' THEN - rank_search := 23; - rank_address := 5; - ELSEIF postcode ~ '^([A-Z][A-Z]?[0-9][0-9A-Z])$' THEN - rank_search := 21; - rank_address := 5; - END IF; - - ELSEIF country_code = 'sg' THEN - IF postcode ~ '^([0-9]{6})$' THEN - rank_search := 25; - rank_address := 11; - END IF; - - ELSEIF country_code = 'de' THEN - IF postcode ~ '^([0-9]{5})$' THEN - rank_search := 21; - rank_address := 11; - END IF; - - ELSE - -- Guess at the postcode format and coverage (!) - IF postcode ~ '^[A-Z0-9]{1,5}$' THEN -- Probably too short to be very local - rank_search := 21; - rank_address := 11; - ELSE - -- Does it look splitable into and area and local code? - part := substring(postcode from '^([- :A-Z0-9]+)([- :][A-Z0-9]+)$'); - - IF part IS NOT NULL THEN - rank_search := 25; - rank_address := 11; - ELSEIF postcode ~ '^[- :A-Z0-9]{6,}$' THEN - rank_search := 21; - rank_address := 11; - END IF; - END IF; - END IF; - -END; -$$ -LANGUAGE plpgsql IMMUTABLE; - - -- Find the nearest artificial postcode for the given geometry. -- TODO For areas there should not be more than two inside the geometry. CREATE OR REPLACE FUNCTION get_nearest_postcode(country VARCHAR(2), geom GEOMETRY) @@ -566,22 +482,7 @@ BEGIN 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')); END LOOP; ELSE - diameter := 0; - IF rank = 11 THEN - diameter := 0.05; - ELSEIF rank < 18 THEN - diameter := 0.1; - ELSEIF rank < 20 THEN - diameter := 0.05; - ELSEIF rank = 21 THEN - diameter := 0.001; - ELSEIF rank < 24 THEN - diameter := 0.02; - ELSEIF rank < 26 THEN - diameter := 0.002; -- 100 to 200 meters - ELSEIF rank < 28 THEN - diameter := 0.001; -- 50 to 100 meters - END IF; + diameter := update_place_diameter(rank); IF diameter > 0 THEN IF rank >= 26 THEN -- roads may cause reparenting for >27 rank places