X-Git-Url: https://git.openstreetmap.org./nominatim.git/blobdiff_plain/ab6a99677293c9ddb96a717f03b8a009e98ab955..a974d2509257cb2c4967631cea30eb483c37c837:/sql/functions.sql diff --git a/sql/functions.sql b/sql/functions.sql index 75ab4797..6d2ae5f8 100644 --- a/sql/functions.sql +++ b/sql/functions.sql @@ -5,7 +5,6 @@ -- score integer --); - CREATE OR REPLACE FUNCTION getclasstypekey(c text, t text) RETURNS TEXT AS $$ DECLARE @@ -46,71 +45,30 @@ END; $$ LANGUAGE plpgsql IMMUTABLE; -CREATE OR REPLACE FUNCTION geometry_sector(place geometry) RETURNS INTEGER +CREATE OR REPLACE FUNCTION geometry_sector(partition INTEGER, place geometry) RETURNS INTEGER AS $$ DECLARE NEWgeometry geometry; BEGIN -- RAISE WARNING '%',place; - NEWgeometry := place; - IF ST_IsEmpty(NEWgeometry) OR NOT ST_IsValid(NEWgeometry) OR ST_X(ST_Centroid(NEWgeometry))::text in ('NaN','Infinity','-Infinity') OR ST_Y(ST_Centroid(NEWgeometry))::text in ('NaN','Infinity','-Infinity') THEN - NEWgeometry := ST_buffer(NEWgeometry,0); - IF ST_IsEmpty(NEWgeometry) OR NOT ST_IsValid(NEWgeometry) OR ST_X(ST_Centroid(NEWgeometry))::text in ('NaN','Infinity','-Infinity') OR ST_Y(ST_Centroid(NEWgeometry))::text in ('NaN','Infinity','-Infinity') THEN - RETURN 0; - END IF; - END IF; - RETURN (500-ST_X(ST_Centroid(NEWgeometry))::integer)*1000 + (500-ST_Y(ST_Centroid(NEWgeometry))::integer); -END; -$$ -LANGUAGE plpgsql IMMUTABLE; - -CREATE OR REPLACE FUNCTION debug_geometry_sector(osmid integer, place geometry) RETURNS INTEGER - AS $$ -DECLARE - NEWgeometry geometry; -BEGIN --- RAISE WARNING '%',osmid; - IF osmid = 61315 THEN - return null; - END IF; - NEWgeometry := place; - IF ST_IsEmpty(NEWgeometry) OR NOT ST_IsValid(NEWgeometry) OR ST_X(ST_Centroid(NEWgeometry))::text in ('NaN','Infinity','-Infinity') OR ST_Y(ST_Centroid(NEWgeometry))::text in ('NaN','Infinity','-Infinity') THEN - NEWgeometry := ST_buffer(NEWgeometry,0); - IF ST_IsEmpty(NEWgeometry) OR NOT ST_IsValid(NEWgeometry) OR ST_X(ST_Centroid(NEWgeometry))::text in ('NaN','Infinity','-Infinity') OR ST_Y(ST_Centroid(NEWgeometry))::text in ('NaN','Infinity','-Infinity') THEN - RETURN NULL; - END IF; - END IF; - RETURN (500-ST_X(ST_Centroid(NEWgeometry))::integer)*1000 + (500-ST_Y(ST_Centroid(NEWgeometry))::integer); -END; -$$ -LANGUAGE plpgsql IMMUTABLE; - -CREATE OR REPLACE FUNCTION geometry_index(place geometry,indexed BOOLEAN,name keyvalue[]) RETURNS INTEGER - AS $$ -BEGIN -IF indexed THEN RETURN NULL; END IF; -IF name is null THEN RETURN NULL; END IF; -RETURN geometry_sector(place); -END; -$$ -LANGUAGE plpgsql IMMUTABLE; - -CREATE OR REPLACE FUNCTION geometry_index(sector integer, indexed BOOLEAN, name keyvalue[]) RETURNS INTEGER - AS $$ -BEGIN -IF indexed THEN RETURN NULL; END IF; -IF name is null THEN RETURN NULL; END IF; -RETURN sector; + NEWgeometry := ST_PointOnSurface(place); +-- IF ST_IsEmpty(NEWgeometry) OR NOT ST_IsValid(NEWgeometry) OR ST_X(ST_Centroid(NEWgeometry))::text in ('NaN','Infinity','-Infinity') OR ST_Y(ST_Centroid(NEWgeometry))::text in ('NaN','Infinity','-Infinity') THEN +-- NEWgeometry := ST_buffer(NEWgeometry,0); +-- IF ST_IsEmpty(NEWgeometry) OR NOT ST_IsValid(NEWgeometry) OR ST_X(ST_Centroid(NEWgeometry))::text in ('NaN','Infinity','-Infinity') OR ST_Y(ST_Centroid(NEWgeometry))::text in ('NaN','Infinity','-Infinity') THEN +-- RETURN 0; +-- END IF; +-- END IF; + RETURN (partition*1000000) + (500-ST_X(NEWgeometry)::integer)*1000 + (500-ST_Y(NEWgeometry)::integer); END; $$ LANGUAGE plpgsql IMMUTABLE; CREATE OR REPLACE FUNCTION transliteration(text) RETURNS text - AS '/home/brian/nominatim/live/osm2pgsql/gazetteer/gazetteer.so', 'transliteration' + AS '{modulepath}/nominatim.so', 'transliteration' LANGUAGE c IMMUTABLE STRICT; CREATE OR REPLACE FUNCTION gettokenstring(text) RETURNS text - AS '/home/brian/nominatim/live/osm2pgsql/gazetteer/gazetteer.so', 'gettokenstring' + AS '{modulepath}/nominatim.so', 'gettokenstring' LANGUAGE c IMMUTABLE STRICT; CREATE OR REPLACE FUNCTION make_standard_name(name TEXT) RETURNS TEXT @@ -124,18 +82,24 @@ END; $$ LANGUAGE 'plpgsql' IMMUTABLE; +-- returns NULL if the word is too common CREATE OR REPLACE FUNCTION getorcreate_word_id(lookup_word TEXT) RETURNS INTEGER AS $$ DECLARE lookup_token TEXT; return_word_id INTEGER; + count INTEGER; BEGIN lookup_token := trim(lookup_word); - SELECT min(word_id) FROM word WHERE word_token = lookup_token and class is null and type is null into return_word_id; + SELECT min(word_id), max(search_name_count) FROM word WHERE word_token = lookup_token and class is null and type is null into return_word_id, count; IF return_word_id IS NULL THEN return_word_id := nextval('seq_word'); - INSERT INTO word VALUES (return_word_id, lookup_token, regexp_replace(lookup_token,E'([^0-9])\\1+',E'\\1','g'), null, null, null, null, 0, null); + INSERT INTO word VALUES (return_word_id, lookup_token, null, null, null, null, 0); + ELSE + IF count > get_maxwordfreq() THEN + return_word_id := NULL; + END IF; END IF; RETURN return_word_id; END; @@ -153,7 +117,7 @@ BEGIN SELECT min(word_id) FROM word WHERE word_token = lookup_token and class='place' and type='house' into return_word_id; IF return_word_id IS NULL THEN return_word_id := nextval('seq_word'); - INSERT INTO word VALUES (return_word_id, lookup_token, null, null, 'place', 'house', null, 0, null); + INSERT INTO word VALUES (return_word_id, lookup_token, null, 'place', 'house', null, 0); END IF; RETURN return_word_id; END; @@ -171,7 +135,7 @@ BEGIN SELECT min(word_id) FROM word WHERE word_token = lookup_token and country_code=lookup_country_code into return_word_id; IF return_word_id IS NULL THEN return_word_id := nextval('seq_word'); - INSERT INTO word VALUES (return_word_id, lookup_token, null, null, null, null, lookup_country_code, 0, null); + INSERT INTO word VALUES (return_word_id, lookup_token, null, null, null, lookup_country_code, 0); END IF; RETURN return_word_id; END; @@ -189,7 +153,7 @@ BEGIN SELECT min(word_id) FROM word WHERE word_token = lookup_token and class=lookup_class and type = lookup_type into return_word_id; IF return_word_id IS NULL THEN return_word_id := nextval('seq_word'); - INSERT INTO word VALUES (return_word_id, lookup_token, null, null, lookup_class, lookup_type, null, 0, null); + INSERT INTO word VALUES (return_word_id, lookup_token, null, lookup_class, lookup_type, null, 0); END IF; RETURN return_word_id; END; @@ -207,7 +171,7 @@ BEGIN SELECT min(word_id) FROM word WHERE word_token = lookup_token into return_word_id; IF return_word_id IS NULL THEN return_word_id := nextval('seq_word'); - INSERT INTO word VALUES (return_word_id, lookup_token, null, null, null, null, null, 0, null); + INSERT INTO word VALUES (return_word_id, lookup_token, null, null, null, null, 0); END IF; RETURN return_word_id; END; @@ -239,7 +203,7 @@ BEGIN SELECT min(word_id) FROM word WHERE word_token = lookup_token and class=lookup_class and type = lookup_type and operator = op into return_word_id; IF return_word_id IS NULL THEN return_word_id := nextval('seq_word'); - INSERT INTO word VALUES (return_word_id, lookup_token, null, null, lookup_class, lookup_type, null, 0, null, op); + INSERT INTO word VALUES (return_word_id, lookup_token, null, lookup_class, lookup_type, null, 0, op); END IF; RETURN return_word_id; END; @@ -258,7 +222,7 @@ BEGIN SELECT min(word_id) FROM word WHERE word_token = lookup_token and class is null and type is null into return_word_id; IF return_word_id IS NULL THEN return_word_id := nextval('seq_word'); - INSERT INTO word VALUES (return_word_id, lookup_token, regexp_replace(lookup_token,E'([^0-9])\\1+',E'\\1','g'), src_word, null, null, null, 0, null); + INSERT INTO word VALUES (return_word_id, lookup_token, src_word, null, null, null, 0); -- nospace_lookup_token := replace(replace(lookup_token, '-',''), ' ',''); -- IF ' '||nospace_lookup_token != lookup_token THEN -- INSERT INTO word VALUES (return_word_id, '-'||nospace_lookup_token, null, src_word, null, null, null, 0, null); @@ -322,7 +286,7 @@ BEGIN END IF; r := a; FOR i IN 1..array_upper(b, 1) LOOP - IF NOT (ARRAY[b[i]] && r) THEN + IF NOT (ARRAY[b[i]] <@ r) THEN r := r || b[i]; END IF; END LOOP; @@ -331,36 +295,33 @@ END; $$ LANGUAGE plpgsql IMMUTABLE; -CREATE OR REPLACE FUNCTION add_keywords(a keyvalue[], b keyvalue[]) RETURNS keyvalue[] +CREATE OR REPLACE FUNCTION create_country(src HSTORE, lookup_country_code varchar(2)) RETURNS VOID AS $$ DECLARE - i INTEGER; + s TEXT; + w INTEGER; + words TEXT[]; + item RECORD; j INTEGER; - f BOOLEAN; - r keyvalue[]; BEGIN - IF array_upper(a, 1) IS NULL THEN - RETURN b; - END IF; - IF array_upper(b, 1) IS NULL THEN - RETURN a; - END IF; - r := a; - FOR i IN 1..array_upper(b, 1) LOOP - f := false; - FOR j IN 1..array_upper(a, 1) LOOP - IF (a[j].key = b[i].key) THEN - f := true; - END IF; - END LOOP; - IF NOT f THEN - r := r || b[i]; + FOR item IN SELECT (each(src)).* LOOP + + s := make_standard_name(item.value); + w := getorcreate_country(s, lookup_country_code); + + words := regexp_split_to_array(item.value, E'[,;()]'); + IF array_upper(words, 1) != 1 THEN + FOR j IN 1..array_upper(words, 1) LOOP + s := make_standard_name(words[j]); + IF s != '' THEN + w := getorcreate_country(s, lookup_country_code); + END IF; + END LOOP; END IF; END LOOP; - RETURN r; END; $$ -LANGUAGE plpgsql IMMUTABLE; +LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION make_keywords(src HSTORE) RETURNS INTEGER[] AS $$ @@ -379,15 +340,24 @@ BEGIN s := make_standard_name(item.value); w := getorcreate_name_id(s, item.value); - result := result | w; + + IF not(ARRAY[w] <@ result) THEN + result := result || w; + END IF; + + w := getorcreate_word_id(s); + + IF NOT (ARRAY[w] <@ result) THEN + result := result || w; + END IF; words := string_to_array(s, ' '); IF array_upper(words, 1) IS NOT NULL THEN FOR j IN 1..array_upper(words, 1) LOOP IF (words[j] != '') THEN w = getorcreate_word_id(words[j]); - IF NOT (ARRAY[w] && result) THEN - result := result | w; + IF w IS NOT NULL AND NOT (ARRAY[w] <@ result) THEN + result := result || w; END IF; END IF; END LOOP; @@ -399,8 +369,8 @@ BEGIN s := make_standard_name(words[j]); IF s != '' THEN w := getorcreate_word_id(s); - IF NOT (ARRAY[w] && result) THEN - result := result | w; + IF w IS NOT NULL AND NOT (ARRAY[w] <@ result) THEN + result := result || w; END IF; END IF; END LOOP; @@ -411,8 +381,8 @@ BEGIN s := make_standard_name(s); IF s != '' THEN w := getorcreate_name_id(s, item.value); - IF NOT (ARRAY[w] && result) THEN - result := result | w; + IF NOT (ARRAY[w] <@ result) THEN + result := result || w; END IF; END IF; END IF; @@ -437,9 +407,15 @@ BEGIN result := '{}'::INTEGER[]; s := make_standard_name(src); - w := getorcreate_name_id(s); + w := getorcreate_name_id(s, src); + + IF NOT (ARRAY[w] <@ result) THEN + result := result || w; + END IF; - IF NOT (ARRAY[w] && result) THEN + w := getorcreate_word_id(s); + + IF NOT (ARRAY[w] <@ result) THEN result := result || w; END IF; @@ -448,13 +424,37 @@ BEGIN FOR j IN 1..array_upper(words, 1) LOOP IF (words[j] != '') THEN w = getorcreate_word_id(words[j]); - IF NOT (ARRAY[w] && result) THEN + IF w IS NOT NULL AND NOT (ARRAY[w] <@ result) THEN + result := result || w; + END IF; + END IF; + END LOOP; + END IF; + + words := regexp_split_to_array(src, E'[,;()]'); + IF array_upper(words, 1) != 1 THEN + FOR j IN 1..array_upper(words, 1) LOOP + s := make_standard_name(words[j]); + IF s != '' THEN + w := getorcreate_word_id(s); + IF w IS NOT NULL AND NOT (ARRAY[w] <@ result) THEN result := result || w; END IF; END IF; END LOOP; END IF; + s := regexp_replace(src, '市$', ''); + IF s != src THEN + s := make_standard_name(s); + IF s != '' THEN + w := getorcreate_name_id(s, src); + IF NOT (ARRAY[w] <@ result) THEN + result := result || w; + END IF; + END IF; + END IF; + RETURN result; END; $$ @@ -491,50 +491,89 @@ DECLARE place_centre GEOMETRY; nearcountry RECORD; BEGIN - place_centre := ST_Centroid(place); + place_centre := ST_PointOnSurface(place); + +--DEBUG: RAISE WARNING 'get_country_code, start: %', ST_AsText(place_centre); - -- Try for a OSM polygon first - FOR nearcountry IN select country_code from location_area where st_contains(area, place_centre) limit 1 + -- Try for a OSM polygon + FOR nearcountry IN select country_code from location_area_country where country_code is not null and not isguess and st_covers(geometry, place_centre) limit 1 LOOP RETURN nearcountry.country_code; END LOOP; - -- Try for an OSM polygon first, grid is faster - FOR nearcountry IN select country_code from country_osm_grid where st_contains(geometry, place_centre) limit 1 +--DEBUG: RAISE WARNING 'osm fallback: %', ST_AsText(place_centre); + + -- Try for OSM fallback data + -- The order is to deal with places like HongKong that are 'states' within another polygon + FOR nearcountry IN select country_code from country_osm_grid where st_covers(geometry, place_centre) order by area asc limit 1 LOOP RETURN nearcountry.country_code; END LOOP; - -- Natural earth data (first fallback) --- FOR nearcountry IN select country_code from country_naturalearthdata where st_contains(geometry, place_centre) limit 1 --- LOOP --- RETURN nearcountry.country_code; --- END LOOP; +--DEBUG: RAISE WARNING 'natural earth: %', ST_AsText(place_centre); - -- WorldBoundaries data (second fallback - think there might be something broken in this data) - FOR nearcountry IN select country_code from country where st_contains(geometry, place_centre) limit 1 + -- Natural earth data + FOR nearcountry IN select country_code from country_naturalearthdata where st_covers(geometry, place_centre) limit 1 LOOP RETURN nearcountry.country_code; END LOOP; - -- Still not in a country - try nearest within ~12 miles of a country - FOR nearcountry IN select country_code from country where st_distance(geometry, place_centre) < 0.5 - order by st_distance(geometry, place) limit 1 +--DEBUG: RAISE WARNING 'near osm fallback: %', ST_AsText(place_centre); + + -- + 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 + LOOP + RETURN nearcountry.country_code; + END LOOP; + +--DEBUG: RAISE WARNING 'near natural earth: %', ST_AsText(place_centre); + + -- Natural earth data + FOR nearcountry IN select country_code from country_naturalearthdata where st_dwithin(geometry, place_centre, 0.5) limit 1 LOOP RETURN nearcountry.country_code; END LOOP; + -- WorldBoundaries data (second fallback - think there might be something broken in this data) +-- FOR nearcountry IN select country_code from country where st_covers(geometry, place_centre) limit 1 +-- LOOP +-- RETURN nearcountry.country_code; +-- END LOOP; + +--RAISE WARNING 'near country: %', ST_AsText(place_centre); + + -- Still not in a country - try nearest within ~12 miles of a country +-- FOR nearcountry IN select country_code from country where st_distance(geometry, place_centre) < 0.5 +-- order by st_distance(geometry, place) limit 1 +-- LOOP +-- RETURN nearcountry.country_code; +-- END LOOP; + RETURN NULL; END; $$ LANGUAGE plpgsql IMMUTABLE; +CREATE OR REPLACE FUNCTION get_country_code(place geometry, in_country_code VARCHAR(2)) RETURNS TEXT + AS $$ +DECLARE + nearcountry RECORD; +BEGIN + FOR nearcountry IN select country_code from country_name where country_code = lower(in_country_code) + LOOP + RETURN nearcountry.country_code; + END LOOP; + RETURN get_country_code(place); +END; +$$ +LANGUAGE plpgsql IMMUTABLE; + CREATE OR REPLACE FUNCTION get_country_language_code(search_country_code VARCHAR(2)) RETURNS TEXT AS $$ DECLARE nearcountry RECORD; BEGIN - FOR nearcountry IN select distinct country_default_language_code from country where country_code = search_country_code limit 1 + FOR nearcountry IN select distinct country_default_language_code from country_name where country_code = search_country_code limit 1 LOOP RETURN lower(nearcountry.country_default_language_code); END LOOP; @@ -543,6 +582,35 @@ END; $$ LANGUAGE plpgsql IMMUTABLE; +CREATE OR REPLACE FUNCTION get_country_language_codes(search_country_code VARCHAR(2)) RETURNS TEXT[] + AS $$ +DECLARE + nearcountry RECORD; +BEGIN + FOR nearcountry IN select country_default_language_codes from country_name where country_code = search_country_code limit 1 + LOOP + RETURN lower(nearcountry.country_default_language_codes); + END LOOP; + RETURN NULL; +END; +$$ +LANGUAGE plpgsql IMMUTABLE; + +CREATE OR REPLACE FUNCTION get_partition(place geometry, in_country_code VARCHAR(10)) RETURNS INTEGER + AS $$ +DECLARE + place_centre GEOMETRY; + nearcountry RECORD; +BEGIN + FOR nearcountry IN select partition from country_name where country_code = in_country_code + LOOP + RETURN nearcountry.partition; + END LOOP; + RETURN 0; +END; +$$ +LANGUAGE plpgsql IMMUTABLE; + CREATE OR REPLACE FUNCTION delete_location(OLD_place_id BIGINT) RETURNS BOOLEAN AS $$ DECLARE @@ -556,8 +624,9 @@ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION add_location( place_id BIGINT, - place_country_code varchar(2), - name keyvalue[], + country_code varchar(2), + partition INTEGER, + keywords INTEGER[], rank_search INTEGER, rank_address INTEGER, geometry GEOMETRY @@ -565,152 +634,79 @@ CREATE OR REPLACE FUNCTION add_location( RETURNS BOOLEAN AS $$ DECLARE - keywords INTEGER[]; - country_code VARCHAR(2); locationid INTEGER; isarea BOOLEAN; - xmin INTEGER; - ymin INTEGER; - xmax INTEGER; - ymax INTEGER; - lon INTEGER; - lat INTEGER; - secgeo GEOMETRY; + centroid GEOMETRY; diameter FLOAT; + x BOOLEAN; + splitGeom RECORD; + secgeo GEOMETRY; BEGIN - -- Allocate all tokens ids - prevents multi-processor race condition later on at cost of slowing down import - keywords := make_keywords(name); + IF rank_search > 25 THEN + RAISE EXCEPTION 'Adding location with rank > 25 (% rank %)', place_id, rank_search; + END IF; - -- 26 = street/highway - IF rank_search <= 26 THEN - IF place_country_code IS NULL THEN - country_code := get_country_code(geometry); - END IF; - country_code := lower(place_country_code); +-- RAISE WARNING 'Adding location with rank > 25 (% rank %)', place_id, rank_search; - isarea := false; - IF (ST_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon') AND ST_IsValid(geometry)) THEN + x := deleteLocationArea(partition, place_id, rank_search); - isArea := true; + isarea := false; + IF (ST_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon') AND ST_IsValid(geometry)) THEN - xmin := floor(st_xmin(geometry)); - xmax := ceil(st_xmax(geometry)); - ymin := floor(st_ymin(geometry)); - ymax := ceil(st_ymax(geometry)); + isArea := true; + centroid := ST_Centroid(geometry); - IF xmin = xmax OR ymin = ymax OR (xmax-xmin < 2 AND ymax-ymin < 2) THEN - INSERT INTO location_area values (place_id, country_code, name, keywords, - rank_search, rank_address, false, ST_Centroid(geometry), geometry); - ELSE - FOR lon IN xmin..(xmax-1) LOOP - FOR lat IN ymin..(ymax-1) LOOP - secgeo := st_intersection(geometry, ST_SetSRID(ST_MakeBox2D(ST_Point(lon,lat),ST_Point(lon+1,lat+1)),4326)); - IF NOT ST_IsEmpty(secgeo) AND ST_GeometryType(secgeo) in ('ST_Polygon','ST_MultiPolygon') THEN - INSERT INTO location_area values (place_id, country_code, name, keywords, - rank_search, rank_address, false, ST_Centroid(geometry), - st_intersection(geometry, ST_SetSRID(ST_MakeBox2D(ST_Point(lon,lat),ST_Point(lon+1,lat+1)),4326)) - ); - END IF; - END LOOP; - END LOOP; - END IF; + FOR secgeo IN select split_geometry(geometry) AS geom LOOP + x := insertLocationAreaLarge(partition, place_id, country_code, keywords, rank_search, rank_address, false, centroid, secgeo); + END LOOP; - ELSE + ELSEIF rank_search < 26 THEN + diameter := 0.02; + IF rank_address = 0 THEN diameter := 0.02; - IF rank_search = 14 THEN - diameter := 1; - ELSEIF rank_search = 15 THEN - diameter := 0.5; - ELSEIF rank_search = 16 THEN - diameter := 0.15; - ELSEIF rank_search = 17 THEN - diameter := 0.05; - ELSEIF rank_search = 25 THEN - diameter := 0.005; - ELSEIF rank_search = 26 THEN - diameter := 0.001; - END IF; + ELSEIF rank_search <= 14 THEN + diameter := 1.2; + ELSEIF rank_search <= 15 THEN + diameter := 1; + ELSEIF rank_search <= 16 THEN + diameter := 0.5; + ELSEIF rank_search <= 17 THEN + diameter := 0.2; + ELSEIF rank_search <= 21 THEN + diameter := 0.05; + ELSEIF rank_search = 25 THEN + diameter := 0.005; + END IF; - secgeo := ST_Buffer(geometry, diameter); - INSERT INTO location_area values (place_id, country_code, name, keywords, - rank_search, rank_address, true, ST_Centroid(geometry), secgeo); +-- RAISE WARNING 'adding % diameter %', place_id, diameter; - END IF; + secgeo := ST_Buffer(geometry, diameter); + x := insertLocationAreaLarge(partition, place_id, country_code, keywords, rank_search, rank_address, true, ST_Centroid(geometry), secgeo); + + ELSE - INSERT INTO location_point values (place_id,country_code,name,keywords,rank_search,rank_address,isarea,ST_Centroid(geometry)); + -- ~ 20meters + secgeo := ST_Buffer(geometry, 0.0002); + x := insertLocationAreaRoadNear(partition, place_id, country_code, keywords, rank_search, rank_address, true, ST_Centroid(geometry), secgeo); - RETURN true; + -- ~ 100meters + secgeo := ST_Buffer(geometry, 0.001); + x := insertLocationAreaRoadFar(partition, place_id, country_code, keywords, rank_search, rank_address, true, ST_Centroid(geometry), secgeo); - IF not isarea THEN - IF rank_search < 26 THEN - INSERT INTO location_point_26 values (place_id,country_code,name,keywords,rank_search,rank_address,isarea,ST_Centroid(geometry)); - IF rank_search < 25 THEN - INSERT INTO location_point_25 values (place_id,country_code,name,keywords,rank_search,rank_address,isarea,ST_Centroid(geometry)); - IF rank_search < 24 THEN - INSERT INTO location_point_24 values (place_id,country_code,name,keywords,rank_search,rank_address,isarea,ST_Centroid(geometry)); - IF rank_search < 23 THEN - INSERT INTO location_point_23 values (place_id,country_code,name,keywords,rank_search,rank_address,isarea,ST_Centroid(geometry)); - IF rank_search < 22 THEN - INSERT INTO location_point_22 values (place_id,country_code,name,keywords,rank_search,rank_address,isarea,ST_Centroid(geometry)); - IF rank_search < 21 THEN - INSERT INTO location_point_21 values (place_id,country_code,name,keywords,rank_search,rank_address,isarea,ST_Centroid(geometry)); - IF rank_search < 20 THEN - INSERT INTO location_point_20 values (place_id,country_code,name,keywords,rank_search,rank_address,isarea,ST_Centroid(geometry)); - IF rank_search < 19 THEN - INSERT INTO location_point_19 values (place_id,country_code,name,keywords,rank_search,rank_address,isarea,ST_Centroid(geometry)); - IF rank_search < 18 THEN - INSERT INTO location_point_18 values (place_id,country_code,name,keywords,rank_search,rank_address,isarea,ST_Centroid(geometry)); - IF rank_search < 17 THEN - INSERT INTO location_point_17 values (place_id,country_code,name,keywords,rank_search,rank_address,isarea,ST_Centroid(geometry)); - IF rank_search < 16 THEN - INSERT INTO location_point_16 values (place_id,country_code,name,keywords,rank_search,rank_address,isarea,ST_Centroid(geometry)); - IF rank_search < 15 THEN - INSERT INTO location_point_15 values (place_id,country_code,name,keywords,rank_search,rank_address,isarea,ST_Centroid(geometry)); - IF rank_search < 14 THEN - INSERT INTO location_point_14 values (place_id,country_code,name,keywords,rank_search,rank_address,isarea,ST_Centroid(geometry)); - IF rank_search < 13 THEN - INSERT INTO location_point_13 values (place_id,country_code,name,keywords,rank_search,rank_address,isarea,ST_Centroid(geometry)); - IF rank_search < 12 THEN - INSERT INTO location_point_12 values (place_id,country_code,name,keywords,rank_search,rank_address,isarea,ST_Centroid(geometry)); - IF rank_search < 11 THEN - INSERT INTO location_point_11 values (place_id,country_code,name,keywords,rank_search,rank_address,isarea,ST_Centroid(geometry)); - IF rank_search < 10 THEN - INSERT INTO location_point_10 values (place_id,country_code,name,keywords,rank_search,rank_address,isarea,ST_Centroid(geometry)); - IF rank_search < 9 THEN - INSERT INTO location_point_9 values (place_id,country_code,name,keywords,rank_search,rank_address,isarea,ST_Centroid(geometry)); - IF rank_search < 8 THEN - INSERT INTO location_point_8 values (place_id,country_code,name,keywords,rank_search,rank_address,isarea,ST_Centroid(geometry)); - IF rank_search < 7 THEN - INSERT INTO location_point_7 values (place_id,country_code,name,keywords,rank_search,rank_address,isarea,ST_Centroid(geometry)); - IF rank_search < 6 THEN - INSERT INTO location_point_6 values (place_id,country_code,name,keywords,rank_search,rank_address,isarea,ST_Centroid(geometry)); - IF rank_search < 5 THEN - INSERT INTO location_point_5 values (place_id,country_code,name,keywords,rank_search,rank_address,isarea,ST_Centroid(geometry)); - IF rank_search < 4 THEN - INSERT INTO location_point_4 values (place_id,country_code,name,keywords,rank_search,rank_address,isarea,ST_Centroid(geometry)); - IF rank_search < 3 THEN - INSERT INTO location_point_3 values (place_id,country_code,name,keywords,rank_search,rank_address,isarea,ST_Centroid(geometry)); - IF rank_search < 2 THEN - INSERT INTO location_point_2 values (place_id,country_code,name,keywords,rank_search,rank_address,isarea,ST_Centroid(geometry)); - IF rank_search < 1 THEN - INSERT INTO location_point_1 values (place_id,country_code,name,keywords,rank_search,rank_address,isarea,ST_Centroid(geometry)); - END IF;END IF;END IF;END IF;END IF;END IF;END IF;END IF;END IF;END IF; - END IF;END IF;END IF;END IF;END IF;END IF;END IF;END IF;END IF;END IF; - END IF;END IF;END IF;END IF;END IF;END IF;END IF; - RETURN true; END IF; - RETURN false; + RETURN true; END; $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION update_location( + partition INTEGER, place_id BIGINT, place_country_code varchar(2), - name keyvalue[], + name hstore, rank_search INTEGER, rank_address INTEGER, geometry GEOMETRY @@ -720,7 +716,8 @@ CREATE OR REPLACE FUNCTION update_location( DECLARE b BOOLEAN; BEGIN - b := delete_location(place_id); + b := deleteLocationArea(partition, place_id, rank_search); +-- result := add_location(NEW.place_id, NEW.country_code, NEW.partition, name_vector, NEW.rank_search, NEW.rank_address, NEW.geometry); RETURN add_location(place_id, place_country_code, name, rank_search, rank_address, geometry); END; $$ @@ -743,9 +740,11 @@ BEGIN and search_name.place_id = place_addressline.place_id LOOP delete from search_name where place_id = childplace.place_id; - childplace.nameaddress_vector := uniq(sort_asc(childplace.nameaddress_vector + to_add)); - IF childplace.place_id = parent_place_id THEN - childplace.name_vector := uniq(sort_asc(childplace.name_vector + to_add)); + IF not (ARRAY[to_add] <@ childplace.nameaddress_vector) THEN + childplace.nameaddress_vector := childplace.nameaddress_vector || to_add; + END IF; + IF childplace.place_id = parent_place_id and not (ARRAY[to_add] <@ childplace.name_vector) THEN + childplace.name_vector := childplace.name_vector || to_add; END IF; insert into search_name (place_id, search_rank, address_rank, country_code, name_vector, nameaddress_vector, centroid) values (childplace.place_id, childplace.search_rank, childplace.address_rank, childplace.country_code, @@ -757,7 +756,7 @@ END; $$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION update_location_nameonly(OLD_place_id BIGINT, name keyvalue[]) RETURNS BOOLEAN +CREATE OR REPLACE FUNCTION update_location_nameonly(partition INTEGER, OLD_place_id BIGINT, name hstore) RETURNS BOOLEAN AS $$ DECLARE newkeywords INTEGER[]; @@ -784,35 +783,6 @@ BEGIN END IF; UPDATE location_area set keywords = newkeywords where place_id = OLD_place_id; - UPDATE location_point set keywords = newkeywords where place_id = OLD_place_id; - UPDATE location_point_0 set keywords = newkeywords where place_id = OLD_place_id; - UPDATE location_point_1 set keywords = newkeywords where place_id = OLD_place_id; - UPDATE location_point_2 set keywords = newkeywords where place_id = OLD_place_id; - UPDATE location_point_3 set keywords = newkeywords where place_id = OLD_place_id; - UPDATE location_point_4 set keywords = newkeywords where place_id = OLD_place_id; - UPDATE location_point_5 set keywords = newkeywords where place_id = OLD_place_id; - UPDATE location_point_6 set keywords = newkeywords where place_id = OLD_place_id; - UPDATE location_point_7 set keywords = newkeywords where place_id = OLD_place_id; - UPDATE location_point_8 set keywords = newkeywords where place_id = OLD_place_id; - UPDATE location_point_9 set keywords = newkeywords where place_id = OLD_place_id; - UPDATE location_point_10 set keywords = newkeywords where place_id = OLD_place_id; - UPDATE location_point_11 set keywords = newkeywords where place_id = OLD_place_id; - UPDATE location_point_12 set keywords = newkeywords where place_id = OLD_place_id; - UPDATE location_point_13 set keywords = newkeywords where place_id = OLD_place_id; - UPDATE location_point_14 set keywords = newkeywords where place_id = OLD_place_id; - UPDATE location_point_15 set keywords = newkeywords where place_id = OLD_place_id; - UPDATE location_point_16 set keywords = newkeywords where place_id = OLD_place_id; - UPDATE location_point_17 set keywords = newkeywords where place_id = OLD_place_id; - UPDATE location_point_18 set keywords = newkeywords where place_id = OLD_place_id; - UPDATE location_point_19 set keywords = newkeywords where place_id = OLD_place_id; - UPDATE location_point_20 set keywords = newkeywords where place_id = OLD_place_id; - UPDATE location_point_21 set keywords = newkeywords where place_id = OLD_place_id; - UPDATE location_point_22 set keywords = newkeywords where place_id = OLD_place_id; - UPDATE location_point_23 set keywords = newkeywords where place_id = OLD_place_id; - UPDATE location_point_24 set keywords = newkeywords where place_id = OLD_place_id; - UPDATE location_point_25 set keywords = newkeywords where place_id = OLD_place_id; - UPDATE location_point_26 set keywords = newkeywords where place_id = OLD_place_id; - RETURN search_name_add_words(OLD_place_id, addedkeywords); END; $$ @@ -824,8 +794,8 @@ CREATE OR REPLACE FUNCTION create_interpolation(wayid BIGINT, interpolationtype DECLARE newpoints INTEGER; - waynodes integer[]; - nodeid INTEGER; + waynodes BIGINT[]; + nodeid BIGINT; prevnode RECORD; nextnode RECORD; startnumber INTEGER; @@ -835,7 +805,8 @@ DECLARE originalnumberrange INTEGER; housenum INTEGER; linegeo GEOMETRY; - search_place_id INTEGER; + search_place_id BIGINT; + defpostalcode TEXT; havefirstpoint BOOLEAN; linestr TEXT; @@ -843,6 +814,7 @@ BEGIN newpoints := 0; IF interpolationtype = 'odd' OR interpolationtype = 'even' OR interpolationtype = 'all' THEN + select postcode from placex where osm_type = 'W' and osm_id = wayid INTO defpostalcode; select nodes from planet_osm_ways where id = wayid INTO waynodes; --RAISE WARNING 'interpolation % % %',wayid,interpolationtype,waynodes; IF array_upper(waynodes, 1) IS NOT NULL THEN @@ -851,11 +823,22 @@ BEGIN FOR nodeidpos in 1..array_upper(waynodes, 1) LOOP - select min(place_id) from placex where osm_type = 'N' and osm_id = waynodes[nodeidpos]::bigint and type = 'house' INTO search_place_id; + -- If there is a place of a type other than place/house, use that because + -- it is guaranteed to be the original node. For place/house types use the + -- one with the smallest id because the original node was created first. + -- Ignore all nodes marked for deletion. (Might happen when the type changes.) + select place_id from placex where osm_type = 'N' and osm_id = waynodes[nodeidpos]::BIGINT and indexed_status < 100 order by (type = 'house'),place_id limit 1 INTO search_place_id; IF search_place_id IS NULL THEN - -- null record of right type - select * from placex where osm_type = 'N' and osm_id = waynodes[nodeidpos]::bigint and type = 'house' limit 1 INTO nextnode; + -- if no such node exists, create a record of the right type + select * from placex where osm_type = 'N' and osm_id = waynodes[nodeidpos]::BIGINT and type = 'house' limit 1 INTO nextnode; select ST_SetSRID(ST_Point(lon::float/10000000,lat::float/10000000),4326) from planet_osm_nodes where id = waynodes[nodeidpos] INTO nextnode.geometry; + IF nextnode.geometry IS NULL THEN + -- we don't have any information about this point, most likely + -- because an excerpt was updated and the node never imported + -- because the interpolation is outside the region of the excerpt. + -- Give up. + RETURN newpoints; + END IF; ELSE select * from placex where place_id = search_place_id INTO nextnode; END IF; @@ -868,7 +851,7 @@ BEGIN linestr := linestr||','||ST_X(nextnode.geometry)||' '||ST_Y(nextnode.geometry); endnumber := ('0'||substring(nextnode.housenumber,'[0-9]+'))::integer; - IF startnumber IS NOT NULL and startnumber > 0 AND endnumber IS NOT NULL and endnumber > 0 THEN + IF startnumber IS NOT NULL and startnumber > 0 AND endnumber IS NOT NULL and endnumber > 0 AND @(startnumber - endnumber) < 1000 THEN --RAISE WARNING 'interpolation end % % ',nextnode.place_id,endnumber; @@ -909,7 +892,10 @@ BEGIN FOR housenum IN startnumber..endnumber BY stepsize LOOP -- this should really copy postcodes but it puts a huge burdon on the system for no big benefit -- ideally postcodes should move up to the way - insert into placex values (null,'N',prevnode.osm_id,prevnode.class,prevnode.type,NULL,prevnode.admin_level,housenum,prevnode.street,prevnode.isin,null,prevnode.country_code,prevnode.street_place_id,prevnode.rank_address,prevnode.rank_search,false,ST_Line_Interpolate_Point(linegeo, (housenum::float-orginalstartnumber::float)/originalnumberrange::float)); + insert into placex (osm_type, osm_id, class, type, admin_level, housenumber, street, addr_place, isin, postcode, + country_code, parent_place_id, rank_address, rank_search, indexed_status, geometry) + values ('N',prevnode.osm_id, 'place', 'house', prevnode.admin_level, housenum, prevnode.street, prevnode.addr_place, prevnode.isin, coalesce(prevnode.postcode, defpostalcode), + prevnode.country_code, prevnode.parent_place_id, prevnode.rank_address, prevnode.rank_search, 1, ST_Line_Interpolate_Point(linegeo, (housenum::float-orginalstartnumber::float)/originalnumberrange::float)); newpoints := newpoints + 1; --RAISE WARNING 'interpolation number % % ',prevnode.place_id,housenum; END LOOP; @@ -945,22 +931,25 @@ DECLARE postcode TEXT; result BOOLEAN; country_code VARCHAR(2); + default_language VARCHAR(10); diameter FLOAT; + classtable TEXT; BEGIN --- RAISE WARNING '%',NEW.osm_id; --- RAISE WARNING '%',NEW.osm_id; + --DEBUG: RAISE WARNING '% %',NEW.osm_type,NEW.osm_id; -- just block these IF NEW.class = 'highway' and NEW.type in ('turning_circle','traffic_signals','mini_roundabout','noexit','crossing') THEN +-- RAISE WARNING 'bad highway %',NEW.osm_id; RETURN null; END IF; IF NEW.class in ('landuse','natural') and NEW.name is null THEN +-- RAISE WARNING 'empty landuse %',NEW.osm_id; RETURN null; END IF; --- RAISE WARNING '%',NEW.osm_id; 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 -- block all invalid geometary - just not worth the risk. seg faults are causing serious problems. + RAISE WARNING 'invalid geometry %',NEW.osm_id; RETURN NULL; -- Dead code @@ -975,10 +964,27 @@ BEGIN END IF; END IF; + --DEBUG: RAISE WARNING '% % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type; + NEW.place_id := nextval('seq_place'); - NEW.indexed := false; - NEW.country_code := lower(NEW.country_code); - NEW.geometry_sector := geometry_sector(NEW.geometry); + NEW.indexed_status := 1; --STATUS_NEW + + NEW.calculated_country_code := lower(get_country_code(NEW.geometry, NEW.country_code)); + + NEW.partition := get_partition(NEW.geometry, NEW.calculated_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.calculated_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.admin_level > 15 THEN NEW.admin_level := 15; @@ -995,13 +1001,63 @@ BEGIN NEW.rank_address := NEW.rank_search; -- By doing in postgres we have the country available to us - currently only used for postcode - IF NEW.class = 'place' THEN + IF NEW.class in ('place','boundary') AND NEW.type in ('postcode','postal_code') THEN + + IF NEW.postcode IS NULL THEN + -- most likely just a part of a multipolygon postcode boundary, throw it away + RETURN NULL; + END IF; + + NEW.name := hstore('ref', NEW.postcode); + + IF NEW.calculated_country_code = 'gb' THEN + + IF NEW.postcode ~ '^([A-Z][A-Z]?[0-9][0-9A-Z]? [0-9][A-Z][A-Z])$' THEN + NEW.rank_search := 25; + NEW.rank_address := 5; + ELSEIF NEW.postcode ~ '^([A-Z][A-Z]?[0-9][0-9A-Z]? [0-9])$' THEN + NEW.rank_search := 23; + NEW.rank_address := 5; + ELSEIF NEW.postcode ~ '^([A-Z][A-Z]?[0-9][0-9A-Z])$' THEN + NEW.rank_search := 21; + NEW.rank_address := 5; + END IF; + + ELSEIF NEW.calculated_country_code = 'de' THEN + + IF NEW.postcode ~ '^([0-9]{5})$' THEN + NEW.rank_search := 21; + NEW.rank_address := 11; + END IF; + + ELSE + -- Guess at the postcode format and coverage (!) + IF upper(NEW.postcode) ~ '^[A-Z0-9]{1,5}$' THEN -- Probably too short to be very local + NEW.rank_search := 21; + NEW.rank_address := 11; + ELSE + -- Does it look splitable into and area and local code? + postcode := substring(upper(NEW.postcode) from '^([- :A-Z0-9]+)([- :][A-Z0-9]+)$'); + + IF postcode IS NOT NULL THEN + NEW.rank_search := 25; + NEW.rank_address := 11; + ELSEIF NEW.postcode ~ '^[- :A-Z0-9]{6,}$' THEN + NEW.rank_search := 21; + NEW.rank_address := 11; + END IF; + END IF; + END IF; + + ELSEIF NEW.class = 'place' THEN IF NEW.type in ('continent') THEN NEW.rank_search := 2; NEW.rank_address := NEW.rank_search; + NEW.calculated_country_code := NULL; ELSEIF NEW.type in ('sea') THEN NEW.rank_search := 2; NEW.rank_address := 0; + NEW.calculated_country_code := NULL; ELSEIF NEW.type in ('country') THEN NEW.rank_search := 4; NEW.rank_address := NEW.rank_search; @@ -1009,8 +1065,8 @@ BEGIN NEW.rank_search := 8; NEW.rank_address := NEW.rank_search; ELSEIF NEW.type in ('region') THEN - NEW.rank_search := 10; - NEW.rank_address := NEW.rank_search; + NEW.rank_search := 18; -- dropped from previous value of 10 + NEW.rank_address := 0; -- So badly miss-used that better to just drop it! ELSEIF NEW.type in ('county') THEN NEW.rank_search := 12; NEW.rank_address := NEW.rank_search; @@ -1021,11 +1077,11 @@ BEGIN NEW.rank_search := 17; NEW.rank_address := 0; ELSEIF NEW.type in ('town') THEN - NEW.rank_search := 17; - NEW.rank_address := NEW.rank_search; - ELSEIF NEW.type in ('village','hamlet','municipality','district','unincorporated_area','borough') THEN NEW.rank_search := 18; - NEW.rank_address := 17; + NEW.rank_address := 16; + ELSEIF NEW.type in ('village','hamlet','municipality','district','unincorporated_area','borough') THEN + NEW.rank_search := 19; + NEW.rank_address := 16; ELSEIF NEW.type in ('airport') AND ST_GeometryType(NEW.geometry) in ('ST_Polygon','ST_MultiPolygon') THEN NEW.rank_search := 18; NEW.rank_address := 17; @@ -1038,101 +1094,53 @@ BEGIN ELSEIF NEW.type in ('national_park') THEN NEW.rank_search := 18; NEW.rank_address := 18; - ELSEIF NEW.type in ('suburb','croft','subdivision') THEN + ELSEIF NEW.type in ('suburb','croft','subdivision','isolated_dwelling') THEN NEW.rank_search := 20; NEW.rank_address := NEW.rank_search; - ELSEIF NEW.type in ('farm','locality','islet') THEN + ELSEIF NEW.type in ('farm','locality','islet','mountain_pass') THEN NEW.rank_search := 20; NEW.rank_address := 0; + -- Irish townlands, tagged as place=locality and locality=townland + IF (NEW.extratags -> 'locality') = 'townland' THEN + NEW.rank_address := 20; + END IF; ELSEIF NEW.type in ('hall_of_residence','neighbourhood','housing_estate','nature_reserve') THEN NEW.rank_search := 22; NEW.rank_address := 22; - ELSEIF NEW.type in ('postcode') THEN - - -- Postcode processing is very country dependant - IF NEW.country_code IS NULL THEN - NEW.country_code := get_country_code(NEW.geometry); - END IF; - - NEW.name := ARRAY[ROW('ref',NEW.postcode)::keyvalue]; - - IF NEW.country_code = 'gb' THEN - - IF NEW.postcode ~ '^([A-Z][A-Z]?[0-9][0-9A-Z]? [0-9][A-Z][A-Z])$' THEN - NEW.rank_search := 25; - NEW.rank_address := 5; - NEW.name := ARRAY[ROW('ref',NEW.postcode)::keyvalue]; - ELSEIF NEW.postcode ~ '^([A-Z][A-Z]?[0-9][0-9A-Z]? [0-9])$' THEN - NEW.rank_search := 23; - NEW.rank_address := 5; - NEW.name := ARRAY[ROW('ref',NEW.postcode)::keyvalue]; - ELSEIF NEW.postcode ~ '^([A-Z][A-Z]?[0-9][0-9A-Z])$' THEN - NEW.rank_search := 21; - NEW.rank_address := 5; - NEW.name := ARRAY[ROW('ref',NEW.postcode)::keyvalue]; - END IF; - - ELSEIF NEW.country_code = 'de' THEN - - IF NEW.postcode ~ '^([0-9]{5})$' THEN - NEW.name := ARRAY[ROW('ref',NEW.postcode)::keyvalue]; - NEW.rank_search := 21; - NEW.rank_address := 11; - END IF; - - ELSE - -- Guess at the postcode format and coverage (!) - IF upper(NEW.postcode) ~ '^[A-Z0-9]{1,5}$' THEN -- Probably too short to be very local - NEW.name := ARRAY[ROW('ref',NEW.postcode)::keyvalue]; - NEW.rank_search := 21; - NEW.rank_address := 11; - ELSE - -- Does it look splitable into and area and local code? - postcode := substring(upper(NEW.postcode) from '^([- :A-Z0-9]+)([- :][A-Z0-9]+)$'); - - IF postcode IS NOT NULL THEN - - -- TODO: insert new line into location instead - --result := add_location(NEW.place_id,NEW.country_code,ARRAY[ROW('ref',postcode)::keyvalue],21,11,NEW.geometry); - - NEW.name := ARRAY[ROW('ref',NEW.postcode)::keyvalue]; - NEW.rank_search := 25; - NEW.rank_address := 11; - ELSEIF NEW.postcode ~ '^[- :A-Z0-9]{6,}$' THEN - NEW.name := ARRAY[ROW('ref',NEW.postcode)::keyvalue]; - NEW.rank_search := 21; - NEW.rank_address := 11; - END IF; - END IF; - END IF; - - ELSEIF NEW.type in ('airport','street') THEN - NEW.rank_search := 26; - NEW.rank_address := NEW.rank_search; - ELSEIF NEW.type in ('house','building') THEN - NEW.rank_search := 28; - NEW.rank_address := NEW.rank_search; - ELSEIF NEW.type in ('houses') THEN - -- can't guarantee all required nodes loaded yet due to caching in osm2pgsql - -- insert new point into place for each derived building - --i := create_interpolation(NEW.osm_id, NEW.housenumber); - END IF; + ELSEIF NEW.type in ('airport','street') THEN + NEW.rank_search := 26; + NEW.rank_address := NEW.rank_search; + ELSEIF NEW.type in ('house','building') THEN + NEW.rank_search := 30; + NEW.rank_address := NEW.rank_search; + ELSEIF NEW.type in ('houses') THEN + -- can't guarantee all required nodes loaded yet due to caching in osm2pgsql + -- insert new point into place for each derived building + --i := create_interpolation(NEW.osm_id, NEW.housenumber); + NEW.rank_search := 28; + NEW.rank_address := 0; + END IF; ELSEIF NEW.class = 'boundary' THEN - IF NEW.country_code is null THEN - NEW.country_code := get_country_code(NEW.geometry); + IF ST_GeometryType(NEW.geometry) NOT IN ('ST_Polygon','ST_MultiPolygon') THEN +-- RAISE WARNING 'invalid boundary %',NEW.osm_id; + return NULL; END IF; NEW.rank_search := NEW.admin_level * 2; NEW.rank_address := NEW.rank_search; ELSEIF NEW.class = 'landuse' AND ST_GeometryType(NEW.geometry) in ('ST_Polygon','ST_MultiPolygon') THEN NEW.rank_search := 22; NEW.rank_address := NEW.rank_search; + ELSEIF NEW.class = 'natural' and NEW.type in ('peak','volcano','mountain_range') THEN + NEW.rank_search := 18; + NEW.rank_address := 0; -- any feature more than 5 square miles is probably worth indexing ELSEIF ST_GeometryType(NEW.geometry) in ('ST_Polygon','ST_MultiPolygon') AND ST_Area(NEW.geometry) > 0.1 THEN NEW.rank_search := 22; NEW.rank_address := NEW.rank_search; ELSEIF NEW.class = 'highway' AND NEW.name is NULL AND NEW.type in ('service','cycleway','path','footway','steps','bridleway','track','byway','motorway_link','primary_link','trunk_link','secondary_link','tertiary_link') THEN +-- RAISE WARNING 'unnamed minor feature %',NEW.osm_id; RETURN NULL; ELSEIF NEW.class = 'railway' AND NEW.type in ('rail') THEN RETURN NULL; @@ -1151,9 +1159,9 @@ BEGIN NEW.rank_address := NEW.rank_search; ELSEIF NEW.class = 'natural' and NEW.type in ('coastline') THEN RETURN NULL; - ELSEIF NEW.class = 'natural' and NEW.type in ('peak','volcano') THEN - NEW.rank_search := 18; - NEW.rank_address := 0; + ELSEIF NEW.class = 'mountain_pass' THEN + NEW.rank_search := 20; + NEW.rank_address := 0; END IF; END IF; @@ -1166,35 +1174,42 @@ BEGIN NEW.rank_address := 30; END IF; + IF (NEW.extratags -> 'capital') = 'yes' THEN + NEW.rank_search := NEW.rank_search - 1; + END IF; + + -- a country code make no sense below rank 4 (country) + IF NEW.rank_address < 4 THEN + NEW.calculated_country_code := NULL; + END IF; + -- Block import below rank 22 -- IF NEW.rank_search > 22 THEN -- RETURN NULL; -- END IF; - IF array_upper(NEW.name, 1) is not null THEN - result := add_location(NEW.place_id,NEW.country_code,NEW.name,NEW.rank_search,NEW.rank_address,NEW.geometry); - END IF; + --DEBUG: RAISE WARNING 'placex_insert:END: % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type; - --RETURN NEW; - -- The following is not needed until doing diff updates, and slows the main index process down + RETURN NEW; -- @DIFFUPDATES@ The following is not needed until doing diff updates, and slows the main index process down IF (ST_GeometryType(NEW.geometry) in ('ST_Polygon','ST_MultiPolygon') AND ST_IsValid(NEW.geometry)) THEN -- Performance: We just can't handle re-indexing for country level changes IF st_area(NEW.geometry) < 1 THEN -- mark items within the geometry for re-indexing -- RAISE WARNING 'placex poly insert: % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type; --- work around bug in postgis - update placex set indexed = false where indexed and (ST_Contains(NEW.geometry, placex.geometry) OR ST_Intersects(NEW.geometry, placex.geometry)) - AND rank_search > NEW.rank_search and ST_geometrytype(placex.geometry) = 'ST_Point'; - update placex set indexed = false where indexed and (ST_Contains(NEW.geometry, placex.geometry) OR ST_Intersects(NEW.geometry, placex.geometry)) - AND rank_search > NEW.rank_search and ST_geometrytype(placex.geometry) != 'ST_Point'; + + -- work around bug in postgis, this may have been fixed in 2.0.0 (see http://trac.osgeo.org/postgis/ticket/547) + update placex set indexed_status = 2 where (st_covers(NEW.geometry, placex.geometry) OR ST_Intersects(NEW.geometry, placex.geometry)) + 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 addr_place is not null)); + update placex set indexed_status = 2 where (st_covers(NEW.geometry, placex.geometry) OR ST_Intersects(NEW.geometry, placex.geometry)) + 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 addr_place is not null)); 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.001; + diameter := 0.05; ELSEIF NEW.rank_search < 16 THEN diameter := 0; ELSEIF NEW.rank_search < 18 THEN @@ -1212,11 +1227,31 @@ BEGIN END IF; IF diameter > 0 THEN -- RAISE WARNING 'placex point insert: % % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type,diameter; - update placex set indexed = false where indexed and rank_search > NEW.rank_search and ST_DWithin(placex.geometry, NEW.geometry, diameter); + IF NEW.rank_search >= 26 THEN + -- roads may cause reparenting for >27 rank places + update placex set indexed_status = 2 where indexed_status = 0 and rank_search > NEW.rank_search and ST_DWithin(placex.geometry, NEW.geometry, diameter); + ELSEIF NEW.rank_search >= 16 THEN + -- up to rank 16, street-less addresses may need reparenting + 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 addr_place is not null); + ELSE + -- for all other places the search terms may change as well + 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); + END IF; END IF; END IF; + -- add to tables for special search + -- Note: won't work on initial import because the classtype tables + -- do not yet exist. It won't hurt either. + classtable := 'place_classtype_' || NEW.class || '_' || NEW.type; + SELECT count(*)>0 FROM pg_tables WHERE tablename = classtable INTO result; + IF result THEN + EXECUTE 'INSERT INTO ' || classtable::regclass || ' (place_id, centroid) VALUES ($1,$2)' + USING NEW.place_id, ST_Centroid(NEW.geometry); + END IF; + + -- IF NEW.rank_search < 26 THEN -- RAISE WARNING 'placex insert: % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type; -- END IF; @@ -1233,38 +1268,61 @@ TRIGGER DECLARE place_centroid GEOMETRY; - place_geometry_text TEXT; search_maxdistance FLOAT[]; search_mindistance FLOAT[]; address_havelevel BOOLEAN[]; -- search_scores wordscore[]; -- search_scores_pos INTEGER; - search_country_code_conflict BOOLEAN; i INTEGER; iMax FLOAT; location RECORD; + way RECORD; relation RECORD; + relation_members TEXT[]; + relMember RECORD; + linkedplacex RECORD; search_diameter FLOAT; search_prevdiameter FLOAT; search_maxrank INTEGER; address_maxrank INTEGER; address_street_word_id INTEGER; - street_place_id_count INTEGER; + parent_place_id_rank BIGINT; + isin TEXT[]; - tagpairid INTEGER; + isin_tokens INT[]; - bPointCountryCode BOOLEAN; + location_rank_search INTEGER; + location_distance FLOAT; + location_parent GEOMETRY; + location_isaddress BOOLEAN; + tagpairid INTEGER; + + default_language TEXT; name_vector INTEGER[]; nameaddress_vector INTEGER[]; - result BOOLEAN; + linked_node_id BIGINT; + result BOOLEAN; BEGIN --- RAISE WARNING '%',NEW.place_id; + -- deferred delete + IF OLD.indexed_status = 100 THEN + --DEBUG: RAISE WARNING 'placex_update_delete % %',NEW.osm_type,NEW.osm_id; + delete from placex where place_id = OLD.place_id; + RETURN NULL; + END IF; + + IF NEW.indexed_status != 0 OR OLD.indexed_status = 0 THEN + RETURN NEW; + END IF; + + --DEBUG: RAISE WARNING 'placex_update % %',NEW.osm_type,NEW.osm_id; + +--RAISE WARNING '%',NEW.place_id; --RAISE WARNING '%', NEW; IF NEW.class = 'place' AND NEW.type = 'postcodearea' THEN @@ -1272,91 +1330,64 @@ BEGIN RETURN NEW; END IF; - NEW.country_code := lower(NEW.country_code); + -- TODO: this test is now redundant? + IF OLD.indexed_status != 0 THEN + + NEW.indexed_date = now(); + + result := deleteSearchName(NEW.partition, NEW.place_id); + DELETE FROM place_addressline WHERE place_id = NEW.place_id; + DELETE FROM place_boundingbox where place_id = NEW.place_id; + result := deleteRoad(NEW.partition, NEW.place_id); + result := deleteLocationArea(NEW.partition, NEW.place_id, NEW.rank_search); + UPDATE placex set linked_place_id = null where linked_place_id = NEW.place_id; - IF NEW.indexed and NOT OLD.indexed THEN + IF NEW.linked_place_id is not null THEN + RETURN NEW; + END IF; IF NEW.class = 'place' AND NEW.type = 'houses' THEN i := create_interpolation(NEW.osm_id, NEW.housenumber); RETURN NEW; END IF; ---RAISE WARNING 'PROCESSING: % %', NEW.place_id, NEW.name; - - search_country_code_conflict := false; + -- Speed up searches - just use the centroid of the feature + -- cheaper but less acurate + place_centroid := ST_PointOnSurface(NEW.geometry); + NEW.centroid := null; - DELETE FROM search_name WHERE place_id = NEW.place_id; ---RAISE WARNING 'x1'; - DELETE FROM place_addressline WHERE place_id = NEW.place_id; ---RAISE WARNING 'x2'; - DELETE FROM place_boundingbox where place_id = NEW.place_id; + -- reclaculate country and partition + IF NEW.rank_search >= 4 THEN + --NEW.calculated_country_code := lower(get_country_code(NEW.geometry, NEW.country_code)); + NEW.calculated_country_code := lower(get_country_code(place_centroid)); + ELSE + NEW.calculated_country_code := NULL; + END IF; + NEW.partition := get_partition(place_centroid, NEW.calculated_country_code); + NEW.geometry_sector := geometry_sector(NEW.partition, place_centroid); -- Adding ourselves to the list simplifies address calculations later INSERT INTO place_addressline VALUES (NEW.place_id, NEW.place_id, true, true, 0, NEW.rank_address); ---RAISE WARNING 'x3'; -- What level are we searching from search_maxrank := NEW.rank_search; - -- Default max/min distances to look for a location - FOR i IN 1..28 LOOP - search_maxdistance[i] := 1; - search_mindistance[i] := 0.0; - address_havelevel[i] := false; - END LOOP; - -- Minimum size to search, can be larger but don't let it shink below this - search_mindistance[14] := 0.2; - search_mindistance[15] := 0.1; - search_mindistance[16] := 0.05; - search_mindistance[17] := 0.03; - search_mindistance[18] := 0.015; - search_mindistance[19] := 0.008; - search_mindistance[20] := 0.006; - search_mindistance[21] := 0.004; - search_mindistance[22] := 0.003; - search_mindistance[23] := 0.002; - search_mindistance[24] := 0.002; - search_mindistance[25] := 0.001; - search_mindistance[26] := 0.001; - - search_maxdistance[14] := 1; - search_maxdistance[15] := 0.5; - search_maxdistance[16] := 0.15; - search_maxdistance[17] := 0.05; - search_maxdistance[18] := 0.02; - search_maxdistance[19] := 0.02; - search_maxdistance[20] := 0.02; - search_maxdistance[21] := 0.02; - search_maxdistance[22] := 0.02; - search_maxdistance[23] := 0.02; - search_maxdistance[24] := 0.02; - search_maxdistance[25] := 0.02; - search_maxdistance[26] := 0.02; - - -- Speed up searches - just use the centroid of the feature - -- cheaper but less acurate - place_centroid := ST_Centroid(NEW.geometry); - place_geometry_text := 'ST_GeomFromText('''||ST_AsText(NEW.geometry)||''','||ST_SRID(NEW.geometry)||')'; - - -- copy the building number to the name - -- done here rather than on insert to avoid initial indexing - -- TODO: This might be a silly thing to do - --IF (NEW.name IS NULL OR array_upper(NEW.name,1) IS NULL) AND NEW.housenumber IS NOT NULL THEN - -- NEW.name := ARRAY[ROW('ref',NEW.housenumber)::keyvalue]; - --END IF; - - --Temp hack to prevent need to re-index - IF NEW.name::text = '{"(ref,'||NEW.housenumber||')"}' THEN - NEW.name := NULL; + -- 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.calculated_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.name IS NULL OR array_upper(NEW.name,1) IS NULL) AND NEW.type IS NOT NULL THEN - -- NEW.name := ARRAY[ROW('type',NEW.type)::keyvalue]; - --END IF; - - -- Initialise the name and address vectors using our name + -- Initialise the name vector using our name name_vector := make_keywords(NEW.name); - nameaddress_vector := name_vector; + nameaddress_vector := '{}'::int[]; -- some tag combinations add a special id for search tagpairid := get_tagpair(NEW.class,NEW.type); @@ -1364,30 +1395,43 @@ BEGIN name_vector := name_vector + tagpairid; END IF; ---RAISE WARNING '% %', NEW.place_id, NEW.rank_search; + FOR i IN 1..28 LOOP + address_havelevel[i] := false; + END LOOP; + + NEW.importance := null; + select language||':'||title,importance from get_wikipedia_match(NEW.extratags, NEW.calculated_country_code) INTO NEW.wikipedia,NEW.importance; + IF NEW.importance IS NULL THEN + select language||':'||title,importance from wikipedia_article where osm_type = NEW.osm_type and osm_id = NEW.osm_id order by importance desc limit 1 INTO NEW.wikipedia,NEW.importance; + END IF; + +--RAISE WARNING 'before low level% %', NEW.place_id, NEW.rank_search; -- For low level elements we inherit from our parent road IF (NEW.rank_search > 27 OR (NEW.type = 'postcode' AND NEW.rank_search = 25)) THEN --RAISE WARNING 'finding street for %', NEW; - NEW.street_place_id := null; + -- We won't get a better centroid, besides these places are too small to care + NEW.centroid := place_centroid; + + NEW.parent_place_id := null; -- to do that we have to find our parent road -- Copy data from linked items (points on ways, addr:street links, relations) -- Note that addr:street links can only be indexed once the street itself is indexed - IF NEW.street_place_id IS NULL AND NEW.osm_type = 'N' THEN + IF NEW.parent_place_id IS NULL AND NEW.osm_type = 'N' THEN -- Is this node part of a relation? - FOR relation IN select * from planet_osm_rels where parts @> ARRAY[NEW.osm_id::integer] and members @> ARRAY['n'||NEW.osm_id] + FOR relation IN select * from planet_osm_rels where parts @> ARRAY[NEW.osm_id] and members @> ARRAY['n'||NEW.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.street_place_id IS NULL AND relation.members[i+1] = 'street' THEN + 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)::integer - and rank_search = 26 INTO NEW.street_place_id; + SELECT place_id from placex where osm_type='W' and osm_id = substring(relation.members[i],2,200)::bigint + and rank_search = 26 INTO NEW.parent_place_id; END IF; END LOOP; END IF; @@ -1395,141 +1439,130 @@ BEGIN --RAISE WARNING 'x1'; -- Is this node part of a way? - FOR location IN select * from placex where osm_type = 'W' - and osm_id in (select id from planet_osm_ways where nodes && ARRAY[NEW.osm_id::integer]) + FOR way IN select id from planet_osm_ways where nodes @> ARRAY[NEW.osm_id] LOOP +--RAISE WARNING '%', way; + FOR location IN select * from placex where osm_type = 'W' and osm_id = way.id LOOP --RAISE WARNING '%', location; -- Way IS a road then we are on it - that must be our road - IF location.rank_search = 26 AND NEW.street_place_id IS NULL THEN + IF location.rank_search = 26 AND NEW.parent_place_id IS NULL THEN --RAISE WARNING 'node in way that is a street %',location; - NEW.street_place_id := location.place_id; + NEW.parent_place_id := location.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::integer] 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.street_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)::integer - and rank_search = 26 INTO NEW.street_place_id; + IF NEW.parent_place_id IS NULL THEN + 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 INTO NEW.parent_place_id; + END IF; + END LOOP; END IF; END LOOP; - END IF; - END LOOP; + END IF; -- If the way contains an explicit name of a street copy it - IF NEW.street IS NULL AND location.street IS NOT NULL THEN + IF NEW.street IS NULL AND NEW.addr_place IS NULL AND location.street IS NOT NULL THEN --RAISE WARNING 'node in way that has a streetname %',location; NEW.street := location.street; END IF; + -- IF the way contains an explicit name of a place copy it + IF NEW.addr_place IS NULL AND NEW.street IS NULL AND location.addr_place IS NOT NULL THEN + NEW.addr_place := location.addr_place; + END IF; + -- If this way is a street interpolation line then it is probably as good as we are going to get - IF NEW.street_place_id IS NULL AND NEW.street IS NULL AND location.class = 'place' and location.type='houses' THEN + IF NEW.parent_place_id IS NULL AND NEW.street IS NULL AND NEW.addr_place IS NULL AND location.class = 'place' and location.type='houses' THEN -- Try and find a way that is close roughly parellel to this line FOR relation IN SELECT place_id FROM placex WHERE ST_DWithin(location.geometry, placex.geometry, 0.001) and placex.rank_search = 26 + and st_geometrytype(location.geometry) in ('ST_LineString') ORDER BY (ST_distance(placex.geometry, ST_Line_Interpolate_Point(location.geometry,0))+ ST_distance(placex.geometry, ST_Line_Interpolate_Point(location.geometry,0.5))+ ST_distance(placex.geometry, ST_Line_Interpolate_Point(location.geometry,1))) ASC limit 1 LOOP --RAISE WARNING 'using nearest street to address interpolation line,0.001 %',relation; - NEW.street_place_id := relation.place_id; + NEW.parent_place_id := relation.place_id; END LOOP; END IF; END LOOP; + END LOOP; END IF; --RAISE WARNING 'x2'; - IF NEW.street_place_id IS NULL AND NEW.osm_type = 'W' THEN + IF NEW.parent_place_id IS NULL AND NEW.osm_type = 'W' THEN -- Is this way part of a relation? - FOR relation IN select * from planet_osm_rels where parts @> ARRAY[NEW.osm_id::integer] and members @> ARRAY['w'||NEW.osm_id] + FOR relation IN select * from planet_osm_rels where parts @> ARRAY[NEW.osm_id] and members @> ARRAY['w'||NEW.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.street_place_id IS NULL AND relation.members[i+1] = 'street' THEN + IF NEW.parent_place_id IS NULL AND relation.members[i+1] = 'street' THEN --RAISE WARNING '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)::integer - and rank_search = 26 INTO NEW.street_place_id; + SELECT place_id from placex where osm_type='W' and osm_id = substring(relation.members[i],2,200)::bigint + and rank_search = 26 INTO NEW.parent_place_id; END IF; END LOOP; END IF; END LOOP; END IF; ---RAISE WARNING 'x3'; +--RAISE WARNING 'x3 %',NEW.parent_place_id; - IF NEW.street_place_id IS NULL AND NEW.street IS NOT NULL THEN - address_street_word_id := get_name_id(make_standard_name(NEW.street)); ---RAISE WARNING 'street: % %', NEW.street, address_street_word_id; + IF NEW.parent_place_id IS NULL AND NEW.street IS NOT NULL THEN + address_street_word_id := get_name_id(make_standard_name(NEW.street)); IF address_street_word_id IS NOT NULL THEN - FOR location IN SELECT place_id,ST_distance(NEW.geometry, search_name.centroid) as distance - FROM search_name WHERE search_name.name_vector @> ARRAY[address_street_word_id] - AND ST_DWithin(NEW.geometry, search_name.centroid, 0.01) and search_rank between 22 and 27 - ORDER BY ST_distance(NEW.geometry, search_name.centroid) ASC limit 1 - LOOP ---RAISE WARNING 'streetname found nearby %',location; - NEW.street_place_id := location.place_id; + FOR location IN SELECT * from getNearestNamedRoadFeature(NEW.partition, place_centroid, address_street_word_id) LOOP + NEW.parent_place_id := location.place_id; END LOOP; END IF; - -- Failed, fall back to nearest - don't just stop - IF NEW.street_place_id IS NULL THEN ---RAISE WARNING 'unable to find streetname nearby % %',NEW.street,address_street_word_id; --- RETURN null; + END IF; + + IF NEW.parent_place_id IS NULL AND NEW.addr_place IS NOT NULL THEN + address_street_word_id := get_name_id(make_standard_name(NEW.addr_place)); + IF address_street_word_id IS NOT NULL THEN + FOR location IN SELECT * from getNearestNamedPlaceFeature(NEW.partition, place_centroid, address_street_word_id) LOOP + NEW.parent_place_id := location.place_id; + END LOOP; END IF; END IF; ---RAISE WARNING 'x4'; - -<<<<<<< .mine - IF NEW.street_place_id IS NULL THEN - FOR location IN SELECT place_id - FROM location_area - WHERE ST_Contains(area, place_centroid) and location_area.rank_search = 26 - ORDER BY ST_Distance(place_centroid, centroid) ASC limit 1 -======= - search_diameter := 0.00005; - WHILE NEW.street_place_id IS NULL AND search_diameter < 0.1 LOOP ---RAISE WARNING '% %', search_diameter,ST_AsText(ST_Centroid(NEW.geometry)); - FOR location IN SELECT place_id FROM placex - WHERE ST_DWithin(place_centroid, placex.geometry, search_diameter) and rank_search between 22 and 27 - ORDER BY ST_distance(NEW.geometry, placex.geometry) ASC limit 1 ->>>>>>> .r23726 - LOOP ---RAISE WARNING 'using nearest street,% % %',search_diameter,NEW.street,location; - NEW.street_place_id := location.place_id; +--RAISE WARNING 'x4 %',NEW.parent_place_id; + -- Still nothing, just use the nearest road + IF NEW.parent_place_id IS NULL THEN + FOR location IN SELECT place_id FROM getNearestRoadFeature(NEW.partition, place_centroid) LOOP + NEW.parent_place_id := location.place_id; END LOOP; END IF; ---RAISE WARNING 'x6 %',NEW.street_place_id; +--return NEW; +--RAISE WARNING 'x6 %',NEW.parent_place_id; -- If we didn't find any road fallback to standard method - IF NEW.street_place_id IS NOT NULL THEN - - -- Some unnamed roads won't have been indexed, index now if needed - select count(*) from place_addressline where place_id = NEW.street_place_id INTO street_place_id_count; - IF street_place_id_count = 0 THEN - UPDATE placex set indexed = true where indexed = false and place_id = NEW.street_place_id; - END IF; + IF NEW.parent_place_id IS NOT NULL THEN -- Add the street to the address as zero distance to force to front of list - INSERT INTO place_addressline VALUES (NEW.place_id, NEW.street_place_id, true, true, 0, 26); +-- INSERT INTO place_addressline VALUES (NEW.place_id, NEW.parent_place_id, true, true, 0, 26); address_havelevel[26] := true; -- Import address details from parent, reclculating distance in process - INSERT INTO place_addressline select NEW.place_id, x.address_place_id, x.fromarea, x.isaddress, ST_distance(NEW.geometry, placex.geometry), placex.rank_address - from place_addressline as x join placex on (address_place_id = placex.place_id) - where x.place_id = NEW.street_place_id and x.address_place_id != NEW.street_place_id; +-- INSERT INTO place_addressline select NEW.place_id, x.address_place_id, x.fromarea, x.isaddress, ST_distance(NEW.geometry, placex.geometry), placex.rank_address +-- from place_addressline as x join placex on (address_place_id = placex.place_id) +-- where x.place_id = NEW.parent_place_id and x.address_place_id != NEW.parent_place_id; -- Get the details of the parent road - select * from search_name where place_id = NEW.street_place_id INTO location; - NEW.country_code := location.country_code; + select * from search_name where place_id = NEW.parent_place_id INTO location; + NEW.calculated_country_code := location.country_code; --RAISE WARNING '%', NEW.name; -- If there is no name it isn't searchable, don't bother to create a search record @@ -1539,191 +1572,365 @@ BEGIN -- Merge address from parent nameaddress_vector := array_merge(nameaddress_vector, location.nameaddress_vector); - + nameaddress_vector := array_merge(nameaddress_vector, location.name_vector); +--return NEW; -- 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 - INSERT INTO search_name values (NEW.place_id, NEW.rank_search, NEW.rank_address, NEW.country_code, - name_vector, nameaddress_vector, place_centroid); + + IF NEW.rank_search <= 25 THEN + result := add_location(NEW.place_id, NEW.calculated_country_code, NEW.partition, name_vector, NEW.rank_search, NEW.rank_address, NEW.geometry); + END IF; + + result := insertSearchName(NEW.partition, NEW.place_id, NEW.calculated_country_code, name_vector, nameaddress_vector, NEW.rank_search, NEW.rank_address, NEW.importance, place_centroid, NEW.geometry); return NEW; END IF; END IF; ---RAISE WARNING ' INDEXING: %',NEW; +-- RAISE WARNING ' INDEXING Started:'; +-- RAISE WARNING ' INDEXING: %',NEW; - -- Process area matches (tend to be better quality) - FOR location IN SELECT - place_id, - name, - keywords, - country_code, - rank_address, - rank_search, - ST_Distance(place_centroid, centroid) as distance - FROM location_area - WHERE ST_Contains(area, place_centroid) and location_area.rank_search < search_maxrank - ORDER BY ST_Distance(place_centroid, centroid) ASC - LOOP + IF NEW.osm_type = 'R' AND NEW.rank_search < 26 THEN ---RAISE WARNING ' AREA: % % %',location.keywords,NEW.country_code,location.country_code; + -- see if we have any special relation members + select members from planet_osm_rels where id = NEW.osm_id INTO relation_members; - IF NEW.country_code IS NULL THEN - NEW.country_code := location.country_code; - ELSEIF NEW.country_code != location.country_code and location.rank_search > 3 THEN - search_country_code_conflict := true; - END IF; +-- RAISE WARNING 'get_osm_rel_members, label'; + IF relation_members IS NOT NULL THEN + FOR relMember IN select get_osm_rel_members(relation_members,ARRAY['label']) as member LOOP - -- Add it to the list of search terms - nameaddress_vector := array_merge(nameaddress_vector, location.keywords::integer[]); - INSERT INTO place_addressline VALUES (NEW.place_id, location.place_id, true, NOT address_havelevel[location.rank_address], location.distance, location.rank_address); - address_havelevel[location.rank_address] := true; + FOR linkedPlacex IN select * from placex where osm_type = upper(substring(relMember.member,1,1))::char(1) + and osm_id = substring(relMember.member,2,10000)::bigint order by rank_search desc limit 1 LOOP - END LOOP; + -- If we don't already have one use this as the centre point of the geometry + IF NEW.centroid IS NULL THEN + NEW.centroid := coalesce(linkedPlacex.centroid,st_centroid(linkedPlacex.geometry)); + END IF; - -- try using the isin value to find parent places - address_maxrank := search_maxrank; - IF NEW.isin IS NOT NULL THEN - -- Doing isin without a country code seems to be dangerous - IF NEW.country_code is null THEN - NEW.country_code := get_country_code(place_centroid); - END IF; - isin := regexp_split_to_array(NEW.isin, E'[;,]'); - 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 THEN ---RAISE WARNING ' search: %',address_street_word_id; - FOR location IN SELECT place_id,keywords,rank_search,location_point.country_code,rank_address, - ST_Distance(place_centroid, search_name.centroid) as distance - FROM search_name join location_point using (place_id) - WHERE search_name.name_vector @> ARRAY[address_street_word_id] - AND rank_search < NEW.rank_search - AND (NEW.country_code IS NULL OR search_name.country_code = NEW.country_code OR search_name.address_rank < 4) - ORDER BY ST_distance(NEW.geometry, search_name.centroid) ASC limit 1 - LOOP - - IF NEW.country_code IS NULL THEN - NEW.country_code := location.country_code; - ELSEIF NEW.country_code != location.country_code and location.rank_search > 3 THEN - search_country_code_conflict := true; + -- 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)); END IF; ---RAISE WARNING ' found: %',location.place_id; - nameaddress_vector := array_merge(nameaddress_vector, location.keywords::integer[]); - INSERT INTO place_addressline VALUES (NEW.place_id, location.place_id, false, NOT address_havelevel[location.rank_address], location.distance, location.rank_address); + -- merge in extra tags + IF NOT linkedPlacex.extratags IS NULL THEN + NEW.extratags := linkedPlacex.extratags || NEW.extratags; + END IF; - IF address_maxrank > location.rank_address THEN - address_maxrank := location.rank_address; + IF NOT NEW.extratags ? linkedPlacex.class THEN + NEW.extratags := NEW.extratags || hstore(linkedPlacex.class, linkedPlacex.type); END IF; + + -- mark the linked place (excludes from search results) + UPDATE placex set linked_place_id = NEW.place_id where place_id = linkedPlacex.place_id; + END LOOP; + + END LOOP; + + IF NEW.centroid IS NULL THEN + + FOR relMember IN select get_osm_rel_members(relation_members,ARRAY['admin_center','admin_centre']) as member LOOP + + FOR linkedPlacex IN select * from placex where osm_type = upper(substring(relMember.member,1,1))::char(1) + and osm_id = substring(relMember.member,2,10000)::bigint order by rank_search desc limit 1 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 make_standard_name(NEW.name->'name') = make_standard_name(linkedPlacex.name->'name') + AND NEW.rank_address = linkedPlacex.rank_address THEN + + -- If we don't already have one use this as the centre point of the geometry + IF NEW.centroid IS NULL THEN + NEW.centroid := coalesce(linkedPlacex.centroid,st_centroid(linkedPlacex.geometry)); + END IF; + + -- merge in the name, re-init word vector + IF NOT linkedPlacex.name IS NULL THEN + NEW.name := linkedPlacex.name || NEW.name; + name_vector := make_keywords(NEW.name); + END IF; + + -- merge in extra tags + IF NOT linkedPlacex.extratags IS NULL THEN + NEW.extratags := linkedPlacex.extratags || NEW.extratags; + END IF; + + IF NOT NEW.extratags ? linkedPlacex.class THEN + NEW.extratags := NEW.extratags || hstore(linkedPlacex.class, linkedPlacex.type); + END IF; + + -- mark the linked place (excludes from search results) + UPDATE placex set linked_place_id = NEW.place_id where place_id = linkedPlacex.place_id; + + -- keep a note of the node id in case we need it for wikipedia in a bit + linked_node_id := linkedPlacex.osm_id; + END IF; + + END LOOP; + + END LOOP; + END IF; - END LOOP; - FOR i IN address_maxrank..28 LOOP - address_havelevel[i] := true; - END LOOP; + END IF; + END IF; - -- If we have got a consistent country code from the areas and/or isin then we don't care about points (too inacurate) - bPointCountryCode := NEW.country_code IS NULL; + -- Name searches can be done for ways as well as relations + IF NEW.osm_type in ('W','R') AND NEW.rank_search < 26 THEN - IF true THEN - -- full search using absolute position + -- not found one yet? how about doing a name search + IF NEW.centroid IS NULL AND (NEW.name->'name') is not null and make_standard_name(NEW.name->'name') != '' THEN - search_diameter := 0; - -- 16 = city, anything larger tends to be an area so don't continue - WHILE FALSE AND search_diameter < 1 AND search_maxrank > 16 LOOP + FOR linkedPlacex IN select placex.* from placex WHERE + make_standard_name(name->'name') = make_standard_name(NEW.name->'name') + AND placex.rank_address = NEW.rank_address + AND placex.place_id != NEW.place_id + AND placex.osm_type = 'N'::char(1) AND placex.rank_search < 26 + AND st_covers(NEW.geometry, placex.geometry) + LOOP --- RAISE WARNING 'Nearest: % %', search_diameter, search_maxrank; + -- If we don't already have one use this as the centre point of the geometry + IF NEW.centroid IS NULL THEN + NEW.centroid := coalesce(linkedPlacex.centroid,st_centroid(linkedPlacex.geometry)); + END IF; - search_prevdiameter := search_diameter; - IF search_diameter = 0 THEN - search_diameter := 0.001; - ELSE - search_diameter := search_diameter * 2; + -- merge in the name, re-init word vector + NEW.name := linkedPlacex.name || NEW.name; + name_vector := make_keywords(NEW.name); + + -- merge in extra tags + NEW.extratags := linkedPlacex.extratags || NEW.extratags; + + IF NOT NEW.extratags ? linkedPlacex.class THEN + NEW.extratags := NEW.extratags || hstore(linkedPlacex.class, linkedPlacex.type); + END IF; + + -- mark the linked place (excludes from search results) + UPDATE placex set linked_place_id = NEW.place_id where place_id = linkedPlacex.place_id; + + -- keep a note of the node id in case we need it for wikipedia in a bit + linked_node_id := linkedPlacex.osm_id; + END LOOP; END IF; ---RAISE WARNING '%', 'SELECT place_id, name, keywords, country_code, rank_address, rank_search,'|| --- 'ST_Distance('||place_geometry_text||', centroid) as distance,'|| --- 'ST_Distance('||place_geometry_text||', centroid) as maxdistance'|| -- this version of postgis doesnt have maxdistance ! --- ' FROM location_point_'||(case when search_maxrank > 26 THEN 26 ELSE search_maxrank end)|| --- ' WHERE ST_DWithin('||place_geometry_text||', centroid, '||search_diameter||') '|| --- ' AND ST_Distance('||place_geometry_text||', centroid) > '||search_prevdiameter|| --- ' ORDER BY ST_Distance('||place_geometry_text||', centroid) ASC'; - - -- Try nearest - FOR location IN EXECUTE 'SELECT place_id, name, keywords, country_code, rank_address, rank_search,'|| - 'ST_Distance('||place_geometry_text||', centroid) as distance,'|| - 'ST_Distance('||place_geometry_text||', centroid) as maxdistance'|| -- this version of postgis doesnt have maxdistance ! - ' FROM location_point_'||(case when search_maxrank > 26 THEN 26 ELSE search_maxrank end)|| - ' WHERE ST_DWithin('||place_geometry_text||', centroid, '||search_diameter||') '|| - ' AND ST_Distance('||place_geometry_text||', centroid) >= '||search_prevdiameter|| - ' ORDER BY ST_Distance('||place_geometry_text||', centroid) ASC' - LOOP - - IF bPointCountryCode THEN - IF NEW.country_code IS NULL THEN - NEW.country_code := location.country_code; - ELSEIF NEW.country_code != location.country_code THEN - search_country_code_conflict := true; + IF NEW.centroid IS NOT NULL THEN + place_centroid := NEW.centroid; + -- 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 NEW.name is not null AND array_upper(akeys(NEW.name),1) > 1 THEN + default_language := get_country_language_code(NEW.calculated_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; - - -- Find search words ---RAISE WARNING 'IF % % % %', location.name, location.distance, location.rank_search, search_maxdistance; ---RAISE WARNING ' POINT: % % % % %', location.name, location.rank_search, location.place_id, location.distance, search_maxdistance[location.rank_search]; - IF (location.distance < search_maxdistance[location.rank_search]) THEN ---RAISE WARNING ' adding'; - -- Add it to the list of search terms, de-duplicate - nameaddress_vector := array_merge(nameaddress_vector, location.keywords::integer[]); - - iMax := (location.maxdistance*1.5)::float; - FOR i IN location.rank_search..28 LOOP - IF iMax < search_maxdistance[i] THEN ---RAISE WARNING ' setting % to %',i,iMax; - IF iMax > search_mindistance[i] THEN - search_maxdistance[i] := iMax; - ELSE - search_maxdistance[i] := search_mindistance[i]; - END IF; - END IF; - END LOOP; + END IF; + + -- Did we gain a wikipedia tag in the process? then we need to recalculate our importance + IF NEW.importance is null THEN + select language||':'||title,importance from get_wikipedia_match(NEW.extratags, NEW.calculated_country_code) INTO NEW.wikipedia,NEW.importance; + END IF; + -- Still null? how about looking it up by the node id + IF NEW.importance IS NULL THEN + select language||':'||title,importance from wikipedia_article where osm_type = 'N'::char(1) and osm_id = linked_node_id order by importance desc limit 1 INTO NEW.wikipedia,NEW.importance; + END IF; + + END IF; + + -- 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 THEN + perform create_country(NEW.name, lower(NEW.country_code)); + END IF; + + NEW.parent_place_id = 0; + parent_place_id_rank = 0; + + -- convert isin to array of tokenids + isin_tokens := '{}'::int[]; + IF NEW.isin IS NOT NULL THEN + isin := regexp_split_to_array(NEW.isin, 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 + nameaddress_vector := array_merge(nameaddress_vector, ARRAY[address_street_word_id]); + isin_tokens := isin_tokens || address_street_word_id; + END IF; + + -- merge word into address vector + 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 LOOP; + END IF; + END IF; + IF NEW.postcode IS NOT NULL THEN + isin := regexp_split_to_array(NEW.postcode, 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 + nameaddress_vector := array_merge(nameaddress_vector, ARRAY[address_street_word_id]); + isin_tokens := isin_tokens || address_street_word_id; + END IF; + + -- merge into address vector + 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 LOOP; + END IF; + END IF; + + -- for the USA we have an additional address table. Merge in zip codes from there too + IF NEW.rank_search = 26 AND NEW.calculated_country_code = 'us' THEN + FOR location IN SELECT distinct postcode from location_property_tiger where parent_place_id = NEW.place_id LOOP + address_street_word_id := get_name_id(make_standard_name(location.postcode)); + nameaddress_vector := array_merge(nameaddress_vector, ARRAY[address_street_word_id]); + isin_tokens := isin_tokens || address_street_word_id; + + -- also merge in the single word version + address_street_word_id := get_word_id(make_standard_name(location.postcode)); + nameaddress_vector := array_merge(nameaddress_vector, ARRAY[address_street_word_id]); + END LOOP; + 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; + -- RAISE WARNING ' getNearFeatures(%,''%'',%,''%'')',NEW.partition, place_centroid, search_maxrank, isin_tokens; + FOR location IN SELECT * from getNearFeatures(NEW.partition, place_centroid, search_maxrank, isin_tokens) LOOP + +--RAISE WARNING ' AREA: %',location; + + IF location.rank_address != location_rank_search THEN + location_rank_search := location.rank_address; + location_distance := location.distance * 1.5; + END IF; + + IF location.distance < location_distance OR NOT location.isguess THEN + + 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 location.rank_search > 4 THEN + nameaddress_vector := array_merge(nameaddress_vector, location.keywords::integer[]); + END IF; + INSERT INTO place_addressline VALUES (NEW.place_id, location.place_id, true, location_isaddress, location.distance, location.rank_address); + + IF location_isaddress THEN - INSERT INTO place_addressline VALUES (NEW.place_id, location.place_id, false, NOT address_havelevel[location.rank_address], location.distance, location.rank_address); address_havelevel[location.rank_address] := true; - - ELSE ---RAISE WARNING ' Stopped: % % % %', location.rank_search, location.distance, search_maxdistance[location.rank_search], location.name; - IF search_maxrank > location.rank_search THEN - search_maxrank := location.rank_search; + 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; - - END LOOP; - ---RAISE WARNING ' POINT LOCATIONS, % %', search_maxrank, search_diameter; - - END LOOP; --WHILE - ELSE - -- Cascading search using nearest parent +--RAISE WARNING ' Terms: (%) %',location, nameaddress_vector; + + END IF; + + END LOOP; + + -- try using the isin value to find parent places + IF array_upper(isin_tokens, 1) IS NOT NULL THEN + FOR i IN 1..array_upper(isin_tokens, 1) LOOP +--RAISE WARNING ' getNearestNamedFeature: % % % %',NEW.partition, place_centroid, search_maxrank, isin_tokens[i]; + IF NOT ARRAY[isin_tokens[i]] <@ nameaddress_vector THEN + + FOR location IN SELECT * from getNearestNamedFeature(NEW.partition, place_centroid, search_maxrank, isin_tokens[i]) LOOP + + --RAISE WARNING ' ISIN: %',location; + + IF location.rank_search > 4 THEN + nameaddress_vector := array_merge(nameaddress_vector, location.keywords::integer[]); + INSERT INTO place_addressline VALUES (NEW.place_id, location.place_id, false, NOT address_havelevel[location.rank_address], location.distance, location.rank_address); + address_havelevel[location.rank_address] := true; + + 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; + END LOOP; + + END IF; + + END LOOP; END IF; - IF search_country_code_conflict OR NEW.country_code IS NULL THEN - NEW.country_code := get_country_code(place_centroid); + -- for long ways we should add search terms for the entire length + IF st_length(NEW.geometry) > 0.05 THEN + + location_rank_search := 0; + location_distance := 0; + + FOR location IN SELECT * from getNearFeatures(NEW.partition, NEW.geometry, search_maxrank, isin_tokens) LOOP + + IF location.rank_address != location_rank_search THEN + location_rank_search := location.rank_address; + location_distance := location.distance * 1.5; + END IF; + + IF location.rank_search > 4 AND location.distance < location_distance THEN + + -- Add it to the list of search terms + nameaddress_vector := array_merge(nameaddress_vector, location.keywords::integer[]); + INSERT INTO place_addressline VALUES (NEW.place_id, location.place_id, true, false, location.distance, location.rank_address); + + END IF; + + END LOOP; + END IF; - INSERT INTO search_name values (NEW.place_id, NEW.rank_search, NEW.rank_search, NEW.country_code, - name_vector, nameaddress_vector, place_centroid); + -- if we have a name add this to the name search table + IF NEW.name IS NOT NULL THEN + + IF NEW.rank_search <= 25 THEN + result := add_location(NEW.place_id, NEW.calculated_country_code, NEW.partition, name_vector, NEW.rank_search, NEW.rank_address, NEW.geometry); + END IF; + + IF NEW.rank_search between 26 and 27 and NEW.class = 'highway' THEN + result := insertLocationRoad(NEW.partition, NEW.place_id, NEW.calculated_country_code, NEW.geometry); + END IF; + + result := insertSearchName(NEW.partition, NEW.place_id, NEW.calculated_country_code, name_vector, nameaddress_vector, NEW.rank_search, NEW.rank_address, NEW.importance, place_centroid, NEW.geometry); + + END IF; - IF NEW.country_code IS NOT NULL THEN - DELETE FROM place_addressline WHERE place_id = NEW.place_id and address_place_id in ( - select address_place_id from place_addressline join placex on (address_place_id = placex.place_id) - where place_addressline.place_id = NEW.place_id and placex.country_code != NEW.country_code and cached_rank_address >= 4); + -- 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; END IF; - return NEW; + RETURN NEW; END; $$ LANGUAGE plpgsql; @@ -1731,48 +1938,61 @@ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION placex_delete() RETURNS TRIGGER AS $$ DECLARE + b BOOLEAN; + classtable TEXT; 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.rank_address < 30 THEN + + -- mark everything linked to this place for re-indexing + --DEBUG: RAISE WARNING 'placex_delete:03 % %',OLD.osm_type,OLD.osm_id; + UPDATE placex set indexed_status = 2 from place_addressline where address_place_id = OLD.place_id + and placex.place_id = place_addressline.place_id and indexed_status = 0; + + --DEBUG: RAISE WARNING 'placex_delete:04 % %',OLD.osm_type,OLD.osm_id; + DELETE FROM place_addressline where address_place_id = OLD.place_id; + + --DEBUG: RAISE WARNING 'placex_delete:05 % %',OLD.osm_type,OLD.osm_id; + b := deleteRoad(OLD.partition, OLD.place_id); + + --DEBUG: RAISE WARNING 'placex_delete:06 % %',OLD.osm_type,OLD.osm_id; + update placex set indexed_status = 2 where parent_place_id = OLD.place_id and indexed_status = 0; + --DEBUG: RAISE WARNING 'placex_delete:07 % %',OLD.osm_type,OLD.osm_id; + + END IF; + + --DEBUG: RAISE WARNING 'placex_delete:08 % %',OLD.osm_type,OLD.osm_id; + + IF OLD.rank_address < 26 THEN + b := deleteLocationArea(OLD.partition, OLD.place_id, OLD.rank_search); + END IF; + + --DEBUG: RAISE WARNING 'placex_delete:09 % %',OLD.osm_type,OLD.osm_id; + + IF OLD.name is not null THEN + b := deleteSearchName(OLD.partition, OLD.place_id); + END IF; + + --DEBUG: RAISE WARNING 'placex_delete:10 % %',OLD.osm_type,OLD.osm_id; ---IF OLD.rank_search < 26 THEN ---RAISE WARNING 'delete % % % % %',OLD.place_id,OLD.osm_type,OLD.osm_id,OLD.class,OLD.type; ---END IF; - - -- mark everything linked to this place for re-indexing - UPDATE placex set indexed = false from place_addressline where address_place_id = OLD.place_id and placex.place_id = place_addressline.place_id and indexed; - - -- do the actual delete - DELETE FROM location_area where place_id = OLD.place_id; - DELETE FROM location_point where place_id = OLD.place_id; - DELETE FROM location_point_0 where place_id = OLD.place_id; - DELETE FROM location_point_1 where place_id = OLD.place_id; - DELETE FROM location_point_2 where place_id = OLD.place_id; - DELETE FROM location_point_3 where place_id = OLD.place_id; - DELETE FROM location_point_4 where place_id = OLD.place_id; - DELETE FROM location_point_5 where place_id = OLD.place_id; - DELETE FROM location_point_6 where place_id = OLD.place_id; - DELETE FROM location_point_7 where place_id = OLD.place_id; - DELETE FROM location_point_8 where place_id = OLD.place_id; - DELETE FROM location_point_9 where place_id = OLD.place_id; - DELETE FROM location_point_10 where place_id = OLD.place_id; - DELETE FROM location_point_11 where place_id = OLD.place_id; - DELETE FROM location_point_12 where place_id = OLD.place_id; - DELETE FROM location_point_13 where place_id = OLD.place_id; - DELETE FROM location_point_14 where place_id = OLD.place_id; - DELETE FROM location_point_15 where place_id = OLD.place_id; - DELETE FROM location_point_16 where place_id = OLD.place_id; - DELETE FROM location_point_17 where place_id = OLD.place_id; - DELETE FROM location_point_18 where place_id = OLD.place_id; - DELETE FROM location_point_19 where place_id = OLD.place_id; - DELETE FROM location_point_20 where place_id = OLD.place_id; - DELETE FROM location_point_21 where place_id = OLD.place_id; - DELETE FROM location_point_22 where place_id = OLD.place_id; - DELETE FROM location_point_23 where place_id = OLD.place_id; - DELETE FROM location_point_24 where place_id = OLD.place_id; - DELETE FROM location_point_25 where place_id = OLD.place_id; - DELETE FROM location_point_26 where place_id = OLD.place_id; - DELETE FROM search_name where place_id = OLD.place_id; DELETE FROM place_addressline where place_id = OLD.place_id; - DELETE FROM place_addressline where address_place_id = OLD.place_id; + + --DEBUG: RAISE WARNING 'placex_delete:11 % %',OLD.osm_type,OLD.osm_id; + + -- remove from tables for special search + classtable := 'place_classtype_' || OLD.class || '_' || OLD.type; + SELECT count(*)>0 FROM pg_tables WHERE tablename = classtable INTO b; + IF b THEN + EXECUTE 'DELETE FROM ' || classtable::regclass || ' WHERE place_id = $1' USING OLD.place_id; + END IF; + + --DEBUG: RAISE WARNING 'placex_delete:12 % %',OLD.osm_type,OLD.osm_id; RETURN OLD; @@ -1783,11 +2003,20 @@ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION place_delete() RETURNS TRIGGER AS $$ DECLARE - placeid INTEGER; + placeid BIGINT; BEGIN --- RAISE WARNING 'delete: % % % %',OLD.osm_type,OLD.osm_id,OLD.class,OLD.type; - delete from placex where osm_type = OLD.osm_type and osm_id = OLD.osm_id and class = OLD.class and type = OLD.type; + --DEBUG: RAISE WARNING 'delete: % % % %',OLD.osm_type,OLD.osm_id,OLD.class,OLD.type; + + -- deleting large polygons can have a massive effect on the system - require manual intervention to let them through + IF st_area(OLD.geometry) > 2 and st_isvalid(OLD.geometry) THEN + insert into import_polygon_delete values (OLD.osm_type,OLD.osm_id,OLD.class,OLD.type); + RETURN NULL; + END IF; + + -- mark for delete + 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; + RETURN OLD; END; @@ -1801,15 +2030,17 @@ DECLARE existing RECORD; existingplacex RECORD; existinggeometry GEOMETRY; - existingplace_id bigint; + existingplace_id BIGINT; result BOOLEAN; + partition INTEGER; BEGIN - IF FALSE AND NEW.osm_type = 'R' THEN - RAISE WARNING '-----------------------------------------------------------------------------------'; - RAISE WARNING 'place_insert: % % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type,st_area(NEW.geometry); + --DEBUG: RAISE WARNING '-----------------------------------------------------------------------------------'; + --DEBUG: RAISE WARNING 'place_insert: % % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type,st_area(NEW.geometry); + + IF FALSE and NEW.osm_type = 'R' THEN 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; - RAISE WARNING '%', existingplacex; + --DEBUG: RAISE WARNING '%', existingplacex; END IF; -- Just block these - lots and pointless @@ -1821,14 +2052,15 @@ BEGIN END IF; 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 + INSERT INTO import_polygon_error values (NEW.osm_type, NEW.osm_id, NEW.class, NEW.type, NEW.name, NEW.country_code, + now(), ST_IsValidReason(NEW.geometry), null, NEW.geometry); -- RAISE WARNING 'Invalid Geometry: % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type; RETURN null; END IF; -- Patch in additional country names - -- adminitrative (with typo) is unfortunately hard codes - this probably won't get fixed until v2 - IF NEW.admin_level = 2 AND NEW.type = 'adminitrative' AND NEW.country_code is not null THEN - select add_keywords(NEW.name, country_name.name) from country_name where country_name.country_code = lower(NEW.country_code) INTO NEW.name; + IF NEW.admin_level = 2 AND NEW.type = 'administrative' AND NEW.country_code is not null THEN + select coalesce(country_name.name || NEW.name,NEW.name) from country_name where country_name.country_code = lower(NEW.country_code) INTO NEW.name; END IF; -- Have we already done this place? @@ -1838,46 +2070,40 @@ BEGIN 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; -- Handle a place changing type by removing the old data - -- My generated 'place' types are causing havok because they overlap with real tags - -- TODO: move them to their own special purpose tag to avoid collisions + -- My generated 'place' types are causing havok because they overlap with real keys + -- TODO: move them to their own special purpose key/class to avoid collisions IF existing.osm_type IS NULL AND (NEW.type not in ('postcode','house','houses')) THEN DELETE FROM place where osm_type = NEW.osm_type and osm_id = NEW.osm_id and class = NEW.class and type not in ('postcode','house','houses'); END IF; --- RAISE WARNING 'Existing: %',existing.place_id; - - -- To paraphrase, if there isn't an existing item, OR if the admin level has changed, OR if it is a major change in geometry - IF existing.osm_type IS NULL - OR existingplacex.osm_type IS NULL - OR coalesce(existing.admin_level, 100) != coalesce(NEW.admin_level, 100) --- OR coalesce(existing.country_code, '') != coalesce(NEW.country_code, '') - OR (existing.geometry != NEW.geometry AND ST_Distance(ST_Centroid(existing.geometry),ST_Centroid(NEW.geometry)) > 0.01 AND NOT - (ST_GeometryType(existing.geometry) in ('ST_Polygon','ST_MultiPolygon') AND ST_GeometryType(NEW.geometry) in ('ST_Polygon','ST_MultiPolygon'))) - THEN - --- IF existing.osm_type IS NULL THEN --- RAISE WARNING 'no existing place'; --- END IF; --- IF existingplacex.osm_type IS NULL THEN --- RAISE WARNING 'no existing placex %', existingplacex; --- END IF; + --DEBUG: RAISE WARNING 'Existing: %',existing.osm_id; + --DEBUG: RAISE WARNING 'Existing PlaceX: %',existingplacex.place_id; + + -- Log and discard + IF existing.geometry is not null AND st_isvalid(existing.geometry) + AND st_area(existing.geometry) > 0.02 + AND ST_GeometryType(NEW.geometry) in ('ST_Polygon','ST_MultiPolygon') + AND st_area(NEW.geometry) < st_area(existing.geometry)*0.5 + THEN + INSERT INTO import_polygon_error values (NEW.osm_type, NEW.osm_id, NEW.class, NEW.type, NEW.name, NEW.country_code, now(), + 'Area reduced from '||st_area(existing.geometry)||' to '||st_area(NEW.geometry), existing.geometry, NEW.geometry); + RETURN null; + END IF; + DELETE from import_polygon_error where osm_type = NEW.osm_type and osm_id = NEW.osm_id; + DELETE from import_polygon_delete where osm_type = NEW.osm_type and osm_id = NEW.osm_id; --- RAISE WARNING 'delete and replace'; + -- To paraphrase, if there isn't an existing item, OR if the admin level has changed, OR if it is a major change in geometry + IF existingplacex.osm_type IS NULL THEN IF existing.osm_type IS NOT NULL THEN --- RAISE WARNING 'insert delete % % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type,ST_Distance(ST_Centroid(existing.geometry),ST_Centroid(NEW.geometry)),existing; - IF existing.rank_search < 26 THEN --- RAISE WARNING 'replace placex % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type; - END IF; - DELETE FROM place where osm_type = NEW.osm_type and osm_id = NEW.osm_id and class = NEW.class and type = NEW.type; - END IF; - --- RAISE WARNING 'delete and replace2'; + DELETE from place where osm_type = NEW.osm_type and osm_id = NEW.osm_id and class = NEW.class and type = NEW.type; + END IF; -- No - process it as a new insertion (hopefully of low rank or it will be slow) - insert into placex values (NEW.place_id - ,NEW.osm_type + insert into placex (osm_type, osm_id, class, type, name, admin_level, housenumber, + street, addr_place, isin, postcode, country_code, extratags, geometry) + values (NEW.osm_type ,NEW.osm_id ,NEW.class ,NEW.type @@ -1885,17 +2111,15 @@ BEGIN ,NEW.admin_level ,NEW.housenumber ,NEW.street + ,NEW.addr_place ,NEW.isin ,NEW.postcode ,NEW.country_code - ,NEW.street_place_id - ,NEW.rank_address - ,NEW.rank_search - ,NEW.indexed + ,NEW.extratags ,NEW.geometry ); --- RAISE WARNING 'insert done % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type; + --DEBUG: RAISE WARNING 'insert done % % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type,NEW.name; RETURN NEW; END IF; @@ -1903,7 +2127,7 @@ BEGIN -- Various ways to do the update -- Debug, what's changed? - IF FALSE AND existing.rank_search < 26 THEN + IF FALSE THEN IF coalesce(existing.name::text, '') != coalesce(NEW.name::text, '') THEN RAISE WARNING 'update details, name: % % % %',NEW.osm_type,NEW.osm_id,existing.name::text,NEW.name::text; END IF; @@ -1913,6 +2137,9 @@ BEGIN IF coalesce(existing.street, '') != coalesce(NEW.street, '') THEN RAISE WARNING 'update details, street: % % % %',NEW.osm_type,NEW.osm_id,existing.street,NEW.street; END IF; + IF coalesce(existing.addr_place, '') != coalesce(NEW.addr_place, '') THEN + RAISE WARNING 'update details, street: % % % %',NEW.osm_type,NEW.osm_id,existing.addr_place,NEW.addr_place; + END IF; IF coalesce(existing.isin, '') != coalesce(NEW.isin, '') THEN RAISE WARNING 'update details, isin: % % % %',NEW.osm_type,NEW.osm_id,existing.isin,NEW.isin; END IF; @@ -1925,55 +2152,49 @@ BEGIN END IF; -- Special case for polygon shape changes because they tend to be large and we can be a bit clever about how we handle them - IF existing.geometry != NEW.geometry + IF existing.geometry::text != NEW.geometry::text AND ST_GeometryType(existing.geometry) in ('ST_Polygon','ST_MultiPolygon') AND ST_GeometryType(NEW.geometry) in ('ST_Polygon','ST_MultiPolygon') THEN --- IF existing.rank_search < 26 THEN --- RAISE WARNING 'existing polygon change % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type; --- END IF; - -- Get the version of the geometry actually used (in placex table) 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; -- Performance limit - IF st_area(NEW.geometry) < 1 AND st_area(existinggeometry) < 1 THEN + IF st_area(NEW.geometry) < 0.000000001 AND st_area(existinggeometry) < 1 THEN -- 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 - update placex set indexed = false where indexed and - (ST_Contains(NEW.geometry, placex.geometry) OR ST_Intersects(NEW.geometry, placex.geometry)) - AND NOT (ST_Contains(existinggeometry, placex.geometry) OR ST_Intersects(existinggeometry, placex.geometry)) - AND rank_search > NEW.rank_search; + update placex set indexed_status = 2 where indexed_status = 0 and + (st_covers(NEW.geometry, placex.geometry) OR ST_Intersects(NEW.geometry, placex.geometry)) + AND NOT (st_covers(existinggeometry, placex.geometry) OR ST_Intersects(existinggeometry, placex.geometry)) + AND rank_search > existingplacex.rank_search AND (rank_search < 28 or name is not null); - update placex set indexed = false where indexed and - (ST_Contains(existinggeometry, placex.geometry) OR ST_Intersects(existinggeometry, placex.geometry)) - AND NOT (ST_Contains(NEW.geometry, placex.geometry) OR ST_Intersects(NEW.geometry, placex.geometry)) - AND rank_search > NEW.rank_search; + update placex set indexed_status = 2 where indexed_status = 0 and + (st_covers(existinggeometry, placex.geometry) OR ST_Intersects(existinggeometry, placex.geometry)) + AND NOT (st_covers(NEW.geometry, placex.geometry) OR ST_Intersects(NEW.geometry, placex.geometry)) + AND rank_search > existingplacex.rank_search AND (rank_search < 28 or name is not null); END IF; END IF; -- Special case - if we are just adding extra words we hack them into the search_name table rather than reindexing - IF existingplacex.rank_search < 26 + IF FALSE AND existingplacex.rank_search < 26 AND coalesce(existing.housenumber, '') = coalesce(NEW.housenumber, '') AND coalesce(existing.street, '') = coalesce(NEW.street, '') + AND coalesce(existing.addr_place, '') = coalesce(NEW.addr_place, '') AND coalesce(existing.isin, '') = coalesce(NEW.isin, '') AND coalesce(existing.postcode, '') = coalesce(NEW.postcode, '') AND coalesce(existing.country_code, '') = coalesce(NEW.country_code, '') AND coalesce(existing.name::text, '') != coalesce(NEW.name::text, '') THEN --- IF existing.rank_search < 26 THEN --- RAISE WARNING 'name change only % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type; --- END IF; - IF NOT update_location_nameonly(existingplacex.place_id, NEW.name) THEN IF st_area(NEW.geometry) < 0.5 THEN - UPDATE placex set indexed = false from place_addressline where address_place_id = existingplacex.place_id - and placex.place_id = place_addressline.place_id and indexed; + UPDATE placex set indexed_status = 2 from place_addressline where address_place_id = existingplacex.place_id + and placex.place_id = place_addressline.place_id and indexed_status = 0 + and (rank_search < 28 or name is not null); END IF; END IF; @@ -1984,18 +2205,15 @@ BEGIN IF coalesce(existing.name::text, '') != coalesce(NEW.name::text, '') OR coalesce(existing.housenumber, '') != coalesce(NEW.housenumber, '') OR coalesce(existing.street, '') != coalesce(NEW.street, '') + OR coalesce(existing.addr_place, '') != coalesce(NEW.addr_place, '') OR coalesce(existing.isin, '') != coalesce(NEW.isin, '') OR coalesce(existing.postcode, '') != coalesce(NEW.postcode, '') OR coalesce(existing.country_code, '') != coalesce(NEW.country_code, '') THEN --- IF existing.rank_search < 26 THEN --- RAISE WARNING 'other change % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type; --- END IF; - -- performance, can't take the load of re-indexing a whole country / huge area IF st_area(NEW.geometry) < 0.5 THEN - UPDATE placex set indexed = false from place_addressline where address_place_id = existingplacex.place_id - and placex.place_id = place_addressline.place_id and indexed; +-- UPDATE placex set indexed_status = 2 from place_addressline where address_place_id = existingplacex.place_id +-- and placex.place_id = place_addressline.place_id and indexed_status = 0; END IF; END IF; @@ -2003,22 +2221,27 @@ BEGIN END IF; IF coalesce(existing.name::text, '') != coalesce(NEW.name::text, '') + OR coalesce(existing.extratags::text, '') != coalesce(NEW.extratags::text, '') OR coalesce(existing.housenumber, '') != coalesce(NEW.housenumber, '') OR coalesce(existing.street, '') != coalesce(NEW.street, '') + OR coalesce(existing.addr_place, '') != coalesce(NEW.addr_place, '') OR coalesce(existing.isin, '') != coalesce(NEW.isin, '') OR coalesce(existing.postcode, '') != coalesce(NEW.postcode, '') OR coalesce(existing.country_code, '') != coalesce(NEW.country_code, '') - OR existing.geometry != NEW.geometry + OR coalesce(existing.admin_level, 15) != coalesce(NEW.admin_level, 15) + OR existing.geometry::text != NEW.geometry::text THEN update place set name = NEW.name, housenumber = NEW.housenumber, street = NEW.street, + addr_place = NEW.addr_place, isin = NEW.isin, postcode = NEW.postcode, country_code = NEW.country_code, - street_place_id = null, + extratags = NEW.extratags, + admin_level = NEW.admin_level, geometry = NEW.geometry where osm_type = NEW.osm_type and osm_id = NEW.osm_id and class = NEW.class and type = NEW.type; @@ -2026,16 +2249,17 @@ BEGIN name = NEW.name, housenumber = NEW.housenumber, street = NEW.street, + addr_place = NEW.addr_place, isin = NEW.isin, postcode = NEW.postcode, country_code = NEW.country_code, - street_place_id = null, - indexed = false, + parent_place_id = null, + extratags = NEW.extratags, + admin_level = NEW.admin_level, + indexed_status = 2, geometry = NEW.geometry where place_id = existingplacex.place_id; - result := update_location(existingplacex.place_id, existingplacex.country_code, NEW.name, existingplacex.rank_search, existingplacex.rank_address, NEW.geometry); - END IF; -- Abort the add (we modified the existing place instead) @@ -2044,28 +2268,28 @@ BEGIN END; $$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION get_name_by_language(name keyvalue[], languagepref TEXT[]) RETURNS TEXT +CREATE OR REPLACE FUNCTION get_name_by_language(name hstore, languagepref TEXT[]) RETURNS TEXT AS $$ DECLARE search TEXT[]; found BOOLEAN; BEGIN - IF (array_upper(name, 1) is null) THEN - return null; + IF name is null THEN + RETURN null; END IF; search := languagepref; FOR j IN 1..array_upper(search, 1) LOOP - FOR k IN 1..array_upper(name, 1) LOOP - IF (name[k].key = search[j] AND trim(name[k].value) != '') THEN - return trim(name[k].value); - END IF; - END LOOP; + IF name ? search[j] AND trim(name->search[j]) != '' THEN + return trim(name->search[j]); + END IF; END LOOP; - RETURN null; + -- anything will do as a fallback - just take the first name type thing there is + search := avals(name); + RETURN search[1]; END; $$ LANGUAGE plpgsql IMMUTABLE; @@ -2083,7 +2307,9 @@ BEGIN FOR location IN select nodes from planet_osm_ways where id = way_ids[j] LIMIT 1 LOOP - searchnodes := searchnodes | location.nodes; + IF not (ARRAY[location.nodes] <@ searchnodes) THEN + searchnodes := searchnodes || location.nodes; + END IF; END LOOP; END LOOP; @@ -2106,8 +2332,6 @@ BEGIN search := ARRAY['ref']; result := '{}'; - UPDATE placex set indexed = true where indexed = false and place_id = for_place_id; - select postcode from placex where place_id = for_place_id limit 1 into for_postcode; FOR location IN @@ -2119,7 +2343,7 @@ BEGIN IF array_upper(search, 1) IS NOT NULL AND array_upper(location.name, 1) IS NOT NULL THEN FOR j IN 1..array_upper(search, 1) LOOP FOR k IN 1..array_upper(location.name, 1) LOOP - IF (found > location.rank_address AND location.name[k].key = search[j] AND location.name[k].value != '') AND NOT result && ARRAY[trim(location.name[k].value)] AND (for_postcode IS NULL OR location.name[k].value ilike for_postcode||'%') THEN + IF (found > location.rank_address AND location.name[k].key = search[j] AND location.name[k].value != '') AND NOT result @> ARRAY[trim(location.name[k].value)] AND (for_postcode IS NULL OR location.name[k].value ilike for_postcode||'%') THEN result[(100 - location.rank_address)] := trim(location.name[k].value); found := location.rank_address; END IF; @@ -2137,124 +2361,194 @@ CREATE OR REPLACE FUNCTION get_address_by_language(for_place_id BIGINT, language AS $$ DECLARE result TEXT[]; - search TEXT[]; - found INTEGER; + currresult TEXT; + prevresult TEXT; location RECORD; - searchcountrycode varchar(2); - searchhousenumber TEXT; - searchrankaddress INTEGER; BEGIN - found := 1000; - search := languagepref; result := '{}'; + prevresult := ''; --- UPDATE placex set indexed = false where indexed = true and place_id = for_place_id; - UPDATE placex set indexed = true where indexed = false and place_id = for_place_id; - - select country_code,housenumber,rank_address from placex where place_id = for_place_id into searchcountrycode,searchhousenumber,searchrankaddress; - - FOR location IN - select CASE WHEN address_place_id = for_place_id AND rank_address = 0 THEN 100 ELSE rank_address END as rank_address, - CASE WHEN type = 'postcode' THEN ARRAY[ROW('name',postcode)::keyvalue] ELSE name END as name, - distance,length(name::text) as namelength - from place_addressline join placex on (address_place_id = placex.place_id) - where place_addressline.place_id = for_place_id and ((rank_address > 0 AND rank_address < searchrankaddress) OR address_place_id = for_place_id) - and (placex.country_code IS NULL OR searchcountrycode IS NULL OR placex.country_code = searchcountrycode OR rank_address < 4) - order by rank_address desc,fromarea desc,distance asc,rank_search desc,namelength desc - LOOP - IF array_upper(search, 1) IS NOT NULL AND array_upper(location.name, 1) IS NOT NULL THEN - FOR j IN 1..array_upper(search, 1) LOOP - FOR k IN 1..array_upper(location.name, 1) LOOP - IF (found > location.rank_address AND location.name[k].key = search[j] AND location.name[k].value != '') AND NOT result && ARRAY[trim(location.name[k].value)] THEN - result[(100 - location.rank_address)] := trim(location.name[k].value); - found := location.rank_address; - END IF; - END LOOP; - END LOOP; + FOR location IN select * from get_addressdata(for_place_id) where isaddress order by rank_address desc LOOP + currresult := trim(get_name_by_language(location.name, languagepref)); + IF currresult != prevresult AND currresult IS NOT NULL AND result[(100 - location.rank_address)] IS NULL THEN + result[(100 - location.rank_address)] := trim(get_name_by_language(location.name, languagepref)); + prevresult := currresult; END IF; END LOOP; - IF searchhousenumber IS NOT NULL AND COALESCE(result[(100 - 28)],'') != searchhousenumber THEN - IF result[(100 - 28)] IS NOT NULL THEN - result[(100 - 29)] := result[(100 - 28)]; - END IF; - result[(100 - 28)] := searchhousenumber; - END IF; - - -- No country polygon - add it from the country_code - IF found > 4 THEN - select get_name_by_language(country_name.name,languagepref) as name from placex join country_name using (country_code) - where place_id = for_place_id limit 1 INTO location; - IF location IS NOT NULL THEN - result[(100 - 4)] := trim(location.name); - END IF; - END IF; - RETURN array_to_string(result,', '); END; $$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION get_addressdata_by_language(for_place_id BIGINT, languagepref TEXT[]) RETURNS TEXT[] +DROP TYPE IF EXISTS addressline CASCADE; +create type addressline as ( + place_id BIGINT, + osm_type CHAR(1), + osm_id BIGINT, + name HSTORE, + class TEXT, + type TEXT, + admin_level INTEGER, + fromarea BOOLEAN, + isaddress BOOLEAN, + rank_address INTEGER, + distance FLOAT +); + +CREATE OR REPLACE FUNCTION get_addressdata(in_place_id BIGINT) RETURNS setof addressline AS $$ DECLARE + for_place_id BIGINT; result TEXT[]; search TEXT[]; found INTEGER; location RECORD; + countrylocation RECORD; searchcountrycode varchar(2); searchhousenumber TEXT; + searchhousename HSTORE; + searchrankaddress INTEGER; + searchpostcode TEXT; + searchclass TEXT; + searchtype TEXT; + countryname HSTORE; + hadcountry BOOLEAN; BEGIN - found := 1000; - search := languagepref; - result := '{}'; + select parent_place_id,'us', housenumber, 30, postcode, null, 'place', 'house' from location_property_tiger + WHERE place_id = in_place_id + INTO for_place_id,searchcountrycode, searchhousenumber, searchrankaddress, searchpostcode, searchhousename, searchclass, searchtype; + + IF for_place_id IS NULL THEN + select parent_place_id,'us', housenumber, 30, postcode, null, 'place', 'house' from location_property_aux + WHERE place_id = in_place_id + INTO for_place_id,searchcountrycode, searchhousenumber, searchrankaddress, searchpostcode, searchhousename, searchclass, searchtype; + END IF; --- UPDATE placex set indexed = false where indexed = true and place_id = for_place_id; - UPDATE placex set indexed = true where indexed = false and place_id = for_place_id; + IF for_place_id IS NULL THEN + select parent_place_id, calculated_country_code, housenumber, rank_search, postcode, name, class, type from placex + WHERE place_id = in_place_id and rank_address = 30 + INTO for_place_id, searchcountrycode, searchhousenumber, searchrankaddress, searchpostcode, searchhousename, searchclass, searchtype; + END IF; + + IF for_place_id IS NULL THEN + for_place_id := in_place_id; + select calculated_country_code, housenumber, rank_search, postcode, null from placex where place_id = for_place_id + INTO searchcountrycode, searchhousenumber, searchrankaddress, searchpostcode, searchhousename; + END IF; - select country_code,housenumber from placex where place_id = for_place_id into searchcountrycode,searchhousenumber; +--RAISE WARNING '% % % %',searchcountrycode, searchhousenumber, searchrankaddress, searchpostcode; + found := 1000; + hadcountry := false; FOR location IN - select CASE WHEN address_place_id = for_place_id AND rank_address = 0 THEN 100 ELSE rank_address END as rank_address, - name,distance,length(name::text) as namelength - from place_addressline join placex on (address_place_id = placex.place_id) - where place_addressline.place_id = for_place_id and (rank_address > 0 OR address_place_id = for_place_id) - and (placex.country_code IS NULL OR searchcountrycode IS NULL OR placex.country_code = searchcountrycode OR rank_address < 4) - order by rank_address desc,fromarea desc,distance asc,rank_search desc,namelength desc + select placex.place_id, osm_type, osm_id, + CASE WHEN class = 'place' and type = 'postcode' THEN hstore('name', postcode) ELSE name END as name, + class, type, admin_level, true as fromarea, true as isaddress, + CASE WHEN rank_address = 0 THEN 100 WHEN rank_address = 11 THEN 5 ELSE rank_address END as rank_address, + 0 as distance, calculated_country_code + from placex + where place_id = for_place_id LOOP - IF array_upper(search, 1) IS NOT NULL AND array_upper(location.name, 1) IS NOT NULL THEN - FOR j IN 1..array_upper(search, 1) LOOP - FOR k IN 1..array_upper(location.name, 1) LOOP - IF (found > location.rank_address AND location.name[k].key = search[j] AND location.name[k].value != '') AND NOT result && ARRAY[trim(location.name[k].value)] THEN - result[(100 - location.rank_address)] := trim(location.name[k].value); - found := location.rank_address; - END IF; - END LOOP; - END LOOP; +--RAISE WARNING '%',location; + IF searchcountrycode IS NULL AND location.calculated_country_code IS NOT NULL THEN + searchcountrycode := location.calculated_country_code; + END IF; + IF searchpostcode IS NOT NULL and location.type = 'postcode' THEN + location.isaddress := FALSE; + END IF; + IF location.rank_address = 4 AND location.isaddress THEN + hadcountry := true; + END IF; + IF location.rank_address < 4 AND NOT hadcountry THEN + select name from country_name where country_code = searchcountrycode limit 1 INTO countryname; + IF countryname IS NOT NULL THEN + countrylocation := ROW(null, null, null, countryname, 'place', 'country', null, true, true, 4, 0)::addressline; + RETURN NEXT countrylocation; + END IF; END IF; + countrylocation := ROW(location.place_id, location.osm_type, location.osm_id, location.name, location.class, + location.type, location.admin_level, location.fromarea, location.isaddress, location.rank_address, + location.distance)::addressline; + RETURN NEXT countrylocation; + found := location.rank_address; END LOOP; - IF searchhousenumber IS NOT NULL AND result[(100 - 28)] IS NULL THEN - result[(100 - 28)] := searchhousenumber; - END IF; + FOR location IN + select placex.place_id, osm_type, osm_id, + CASE WHEN class = 'place' and type = 'postcode' THEN hstore('name', postcode) ELSE name END as name, + class, type, admin_level, fromarea, isaddress, + CASE WHEN address_place_id = for_place_id AND rank_address = 0 THEN 100 WHEN rank_address = 11 THEN 5 ELSE rank_address END as rank_address, + distance,calculated_country_code + from place_addressline join placex on (address_place_id = placex.place_id) + where place_addressline.place_id = for_place_id + and (cached_rank_address > 0 AND cached_rank_address < searchrankaddress) + and address_place_id != for_place_id + and (placex.calculated_country_code IS NULL OR searchcountrycode IS NULL OR placex.calculated_country_code = searchcountrycode) + order by rank_address desc,isaddress desc,fromarea desc,distance asc,rank_search desc + LOOP +--RAISE WARNING '%',location; + IF searchcountrycode IS NULL AND location.calculated_country_code IS NOT NULL THEN + searchcountrycode := location.calculated_country_code; + END IF; + IF searchpostcode IS NOT NULL and location.type = 'postcode' THEN + location.isaddress := FALSE; + END IF; + IF location.rank_address = 4 AND location.isaddress THEN + hadcountry := true; + END IF; + IF location.rank_address < 4 AND NOT hadcountry THEN + select name from country_name where country_code = searchcountrycode limit 1 INTO countryname; + IF countryname IS NOT NULL THEN + countrylocation := ROW(null, null, null, countryname, 'place', 'country', null, true, true, 4, 0)::addressline; + RETURN NEXT countrylocation; + END IF; + END IF; + countrylocation := ROW(location.place_id, location.osm_type, location.osm_id, location.name, location.class, + location.type, location.admin_level, location.fromarea, location.isaddress, location.rank_address, + location.distance)::addressline; + RETURN NEXT countrylocation; + found := location.rank_address; + END LOOP; - -- No country polygon - add it from the country_code IF found > 4 THEN - select get_name_by_language(country_name.name,languagepref) as name from placex join country_name using (country_code) - where place_id = for_place_id limit 1 INTO location; - IF location IS NOT NULL THEN - result[(100 - 4)] := trim(location.name); + select name from country_name where country_code = searchcountrycode limit 1 INTO countryname; +--RAISE WARNING '% % %',found,searchcountrycode,countryname; + IF countryname IS NOT NULL THEN + location := ROW(null, null, null, countryname, 'place', 'country', null, true, true, 4, 0)::addressline; + RETURN NEXT location; END IF; END IF; - RETURN result; + IF searchcountrycode IS NOT NULL THEN + location := ROW(null, null, null, hstore('ref', searchcountrycode), 'place', 'country_code', null, true, false, 4, 0)::addressline; + RETURN NEXT location; + END IF; + + IF searchhousename IS NOT NULL THEN + location := ROW(in_place_id, null, null, searchhousename, searchclass, searchtype, null, true, true, 29, 0)::addressline; +-- location := ROW(in_place_id, null, null, searchhousename, 'place', 'house_name', null, true, true, 29, 0)::addressline; + RETURN NEXT location; + END IF; + + IF searchhousenumber IS NOT NULL THEN + location := ROW(in_place_id, null, null, hstore('ref', searchhousenumber), 'place', 'house_number', null, true, true, 28, 0)::addressline; + RETURN NEXT location; + END IF; + + IF searchpostcode IS NOT NULL THEN + location := ROW(null, null, null, hstore('ref', searchpostcode), 'place', 'postcode', null, true, true, 5, 0)::addressline; + RETURN NEXT location; + END IF; + + RETURN; END; $$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION get_place_boundingbox(search_place_id INTEGER) RETURNS place_boundingbox +CREATE OR REPLACE FUNCTION get_place_boundingbox(search_place_id BIGINT) RETURNS place_boundingbox AS $$ DECLARE result place_boundingbox; @@ -2265,10 +2559,10 @@ BEGIN -- remove isaddress = true because if there is a matching polygon it always wins select count(*) from place_addressline where address_place_id = search_place_id into numfeatures; insert into place_boundingbox select place_id, - ST_Y(ST_PointN(ExteriorRing(ST_Box2D(area)),4)),ST_Y(ST_PointN(ExteriorRing(ST_Box2D(area)),2)), - ST_X(ST_PointN(ExteriorRing(ST_Box2D(area)),1)),ST_X(ST_PointN(ExteriorRing(ST_Box2D(area)),3)), - numfeatures, ST_Area(area), - area from location_area where place_id = search_place_id; + ST_Y(ST_PointN(ExteriorRing(ST_Box2D(geometry)),4)),ST_Y(ST_PointN(ExteriorRing(ST_Box2D(geometry)),2)), + ST_X(ST_PointN(ExteriorRing(ST_Box2D(geometry)),1)),ST_X(ST_PointN(ExteriorRing(ST_Box2D(geometry)),3)), + numfeatures, ST_Area(geometry), + geometry as area from location_area where place_id = search_place_id; select * from place_boundingbox into result where place_id = search_place_id; END IF; IF result.place_id IS NULL THEN @@ -2291,7 +2585,7 @@ $$ LANGUAGE plpgsql; -- don't do the operation if it would be slow -CREATE OR REPLACE FUNCTION get_place_boundingbox_quick(search_place_id INTEGER) RETURNS place_boundingbox +CREATE OR REPLACE FUNCTION get_place_boundingbox_quick(search_place_id BIGINT) RETURNS place_boundingbox AS $$ DECLARE result place_boundingbox; @@ -2302,10 +2596,10 @@ BEGIN IF result IS NULL AND rank > 14 THEN select count(*) from place_addressline where address_place_id = search_place_id and isaddress = true into numfeatures; insert into place_boundingbox select place_id, - ST_Y(ST_PointN(ExteriorRing(ST_Box2D(area)),4)),ST_Y(ST_PointN(ExteriorRing(ST_Box2D(area)),2)), - ST_X(ST_PointN(ExteriorRing(ST_Box2D(area)),1)),ST_X(ST_PointN(ExteriorRing(ST_Box2D(area)),3)), - numfeatures, ST_Area(area), - area from location_area where place_id = search_place_id; + ST_Y(ST_PointN(ExteriorRing(ST_Box2D(geometry)),4)),ST_Y(ST_PointN(ExteriorRing(ST_Box2D(geometry)),2)), + ST_X(ST_PointN(ExteriorRing(ST_Box2D(geometry)),1)),ST_X(ST_PointN(ExteriorRing(ST_Box2D(geometry)),3)), + numfeatures, ST_Area(geometry), + geometry as area from location_area where place_id = search_place_id; select * from place_boundingbox into result where place_id = search_place_id; END IF; IF result IS NULL THEN @@ -2330,32 +2624,7 @@ END; $$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION update_place(search_place_id INTEGER) RETURNS BOOLEAN - AS $$ -DECLARE - result place_boundingbox; - numfeatures integer; -BEGIN - update placex set - name = place.name, - housenumber = place.housenumber, - street = place.street, - isin = place.isin, - postcode = place.postcode, - country_code = place.country_code, - street_place_id = null, - indexed = false - from place - where placex.place_id = search_place_id - and place.osm_type = placex.osm_type and place.osm_id = placex.osm_id - and place.class = placex.class and place.type = placex.type; - update placex set indexed = true where place_id = search_place_id and indexed = false; - return true; -END; -$$ -LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION update_place(search_place_id INTEGER) RETURNS BOOLEAN +CREATE OR REPLACE FUNCTION update_place(search_place_id BIGINT) RETURNS BOOLEAN AS $$ DECLARE result place_boundingbox; @@ -2365,16 +2634,17 @@ BEGIN name = place.name, housenumber = place.housenumber, street = place.street, + addr_place = place.addr_place, isin = place.isin, postcode = place.postcode, country_code = place.country_code, - street_place_id = null, - indexed = false + parent_place_id = null from place where placex.place_id = search_place_id and place.osm_type = placex.osm_type and place.osm_id = placex.osm_id and place.class = placex.class and place.type = placex.type; - update placex set indexed = true where place_id = search_place_id and indexed = false; + update placex set indexed_status = 2 where place_id = search_place_id; + update placex set indexed_status = 0 where place_id = search_place_id; return true; END; $$ @@ -2506,11 +2776,339 @@ END; $$ LANGUAGE plpgsql; -CREATE AGGREGATE array_agg(INT[]) -( - sfunc = array_cat, - stype = INT[], - initcond = '{}' +CREATE OR REPLACE FUNCTION aux_create_property(pointgeo GEOMETRY, in_housenumber TEXT, + in_street TEXT, in_isin TEXT, in_postcode TEXT, in_countrycode char(2)) RETURNS INTEGER + AS $$ +DECLARE + + newpoints INTEGER; + place_centroid GEOMETRY; + out_partition INTEGER; + out_parent_place_id BIGINT; + location RECORD; + address_street_word_id INTEGER; + out_postcode TEXT; + +BEGIN + + place_centroid := ST_Centroid(pointgeo); + out_partition := get_partition(place_centroid, in_countrycode); + out_parent_place_id := null; + + address_street_word_id := get_name_id(make_standard_name(in_street)); + IF address_street_word_id IS NOT NULL THEN + FOR location IN SELECT * from getNearestNamedRoadFeature(out_partition, place_centroid, address_street_word_id) LOOP + out_parent_place_id := location.place_id; + END LOOP; + END IF; + + IF out_parent_place_id IS NULL THEN + FOR location IN SELECT place_id FROM getNearestRoadFeature(out_partition, place_centroid) LOOP + out_parent_place_id := location.place_id; + END LOOP; + END IF; + + out_postcode := in_postcode; + IF out_postcode IS NULL THEN + SELECT postcode from placex where place_id = out_parent_place_id INTO out_postcode; + END IF; + IF out_postcode IS NULL THEN + out_postcode := getNearestPostcode(out_partition, place_centroid); + END IF; + + newpoints := 0; + insert into location_property_aux (place_id, partition, parent_place_id, housenumber, postcode, centroid) + values (nextval('seq_place'), out_partition, out_parent_place_id, in_housenumber, out_postcode, place_centroid); + newpoints := newpoints + 1; + + RETURN newpoints; +END; +$$ +LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION get_osm_rel_members(members TEXT[], member TEXT) RETURNS TEXT[] + AS $$ +DECLARE + result TEXT[]; + i INTEGER; +BEGIN + + FOR i IN 1..ARRAY_UPPER(members,1) BY 2 LOOP + IF members[i+1] = member THEN + result := result || members[i]; + END IF; + END LOOP; + + return result; +END; +$$ +LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION get_osm_rel_members(members TEXT[], memberLabels TEXT[]) RETURNS SETOF TEXT + AS $$ +DECLARE + i INTEGER; +BEGIN + + FOR i IN 1..ARRAY_UPPER(members,1) BY 2 LOOP + IF members[i+1] = ANY(memberLabels) THEN + RETURN NEXT members[i]; + END IF; + END LOOP; + + RETURN; +END; +$$ +LANGUAGE plpgsql; + +-- See: http://stackoverflow.com/questions/6410088/how-can-i-mimic-the-php-urldecode-function-in-postgresql +CREATE OR REPLACE FUNCTION decode_url_part(p varchar) RETURNS varchar + AS $$ +SELECT convert_from(CAST(E'\\x' || array_to_string(ARRAY( + SELECT CASE WHEN length(r.m[1]) = 1 THEN encode(convert_to(r.m[1], 'SQL_ASCII'), 'hex') ELSE substring(r.m[1] from 2 for 2) END + FROM regexp_matches($1, '%[0-9a-f][0-9a-f]|.', 'gi') AS r(m) +), '') AS bytea), 'UTF8'); +$$ +LANGUAGE SQL IMMUTABLE STRICT; + +CREATE OR REPLACE FUNCTION catch_decode_url_part(p varchar) RETURNS varchar + AS $$ +DECLARE +BEGIN + RETURN decode_url_part(p); +EXCEPTION + WHEN others THEN return null; +END; +$$ +LANGUAGE plpgsql IMMUTABLE; + +DROP TYPE wikipedia_article_match CASCADE; +create type wikipedia_article_match as ( + language TEXT, + title TEXT, + importance FLOAT ); +CREATE OR REPLACE FUNCTION get_wikipedia_match(extratags HSTORE, country_code varchar(2)) RETURNS wikipedia_article_match + AS $$ +DECLARE + langs TEXT[]; + i INT; + wiki_article TEXT; + wiki_article_title TEXT; + wiki_article_language TEXT; + result wikipedia_article_match; +BEGIN + langs := ARRAY['english','country','ar','bg','ca','cs','da','de','en','es','eo','eu','fa','fr','ko','hi','hr','id','it','he','lt','hu','ms','nl','ja','no','pl','pt','kk','ro','ru','sk','sl','sr','fi','sv','tr','uk','vi','vo','war','zh']; + i := 1; + WHILE langs[i] IS NOT NULL LOOP + wiki_article := extratags->(case when langs[i] in ('english','country') THEN 'wikipedia' ELSE 'wikipedia:'||langs[i] END); + IF wiki_article is not null THEN + wiki_article := regexp_replace(wiki_article,E'^(.*?)([a-z]{2,3}).wikipedia.org/wiki/',E'\\2:'); + wiki_article := regexp_replace(wiki_article,E'^(.*?)([a-z]{2,3}).wikipedia.org/w/index.php\\?title=',E'\\2:'); + wiki_article := regexp_replace(wiki_article,E'^(.*?)/([a-z]{2,3})/wiki/',E'\\2:'); + wiki_article := regexp_replace(wiki_article,E'^(.*?)([a-z]{2,3})[=:]',E'\\2:'); + wiki_article := replace(wiki_article,' ','_'); + wiki_article_title := trim(split_part(wiki_article, ':', 2)); + IF wiki_article_title IS NULL OR wiki_article_title = '' THEN + wiki_article_title := trim(wiki_article); + wiki_article_language := CASE WHEN langs[i] = 'english' THEN 'en' WHEN langs[i] = 'country' THEN get_country_language_code(country_code) ELSE langs[i] END; + ELSE + wiki_article_language := lower(trim(split_part(wiki_article, ':', 1))); + END IF; + + select wikipedia_article.language,wikipedia_article.title,wikipedia_article.importance + from wikipedia_article + where language = wiki_article_language and + (title = wiki_article_title OR title = catch_decode_url_part(wiki_article_title) OR title = replace(catch_decode_url_part(wiki_article_title),E'\\','')) + UNION ALL + select wikipedia_article.language,wikipedia_article.title,wikipedia_article.importance + from wikipedia_redirect join wikipedia_article on (wikipedia_redirect.language = wikipedia_article.language and wikipedia_redirect.to_title = wikipedia_article.title) + where wikipedia_redirect.language = wiki_article_language and + (from_title = wiki_article_title OR from_title = catch_decode_url_part(wiki_article_title) OR from_title = replace(catch_decode_url_part(wiki_article_title),E'\\','')) + order by importance desc limit 1 INTO result; + + IF result.language is not null THEN + return result; + END IF; + END IF; + i := i + 1; + END LOOP; + RETURN NULL; +END; +$$ +LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION quad_split_geometry(geometry GEOMETRY, maxarea FLOAT, maxdepth INTEGER) + RETURNS SETOF GEOMETRY + AS $$ +DECLARE + xmin FLOAT; + ymin FLOAT; + xmax FLOAT; + ymax FLOAT; + xmid FLOAT; + ymid FLOAT; + secgeo GEOMETRY; + secbox GEOMETRY; + seg INTEGER; + geo RECORD; + area FLOAT; + remainingdepth INTEGER; + added INTEGER; + +BEGIN + +-- RAISE WARNING 'quad_split_geometry: maxarea=%, depth=%',maxarea,maxdepth; + + IF (ST_GeometryType(geometry) not in ('ST_Polygon','ST_MultiPolygon') OR NOT ST_IsValid(geometry)) THEN + RETURN NEXT geometry; + RETURN; + END IF; + + remainingdepth := maxdepth - 1; + area := ST_AREA(geometry); + IF remainingdepth < 1 OR area < maxarea THEN + RETURN NEXT geometry; + RETURN; + END IF; + + xmin := st_xmin(geometry); + xmax := st_xmax(geometry); + ymin := st_ymin(geometry); + ymax := st_ymax(geometry); + secbox := ST_SetSRID(ST_MakeBox2D(ST_Point(ymin,xmin),ST_Point(ymax,xmax)),4326); + + -- if the geometry completely covers the box don't bother to slice any more + IF ST_AREA(secbox) = area THEN + RETURN NEXT geometry; + RETURN; + END IF; + + xmid := (xmin+xmax)/2; + ymid := (ymin+ymax)/2; + + added := 0; + FOR seg IN 1..4 LOOP + + IF seg = 1 THEN + secbox := ST_SetSRID(ST_MakeBox2D(ST_Point(xmin,ymin),ST_Point(xmid,ymid)),4326); + END IF; + IF seg = 2 THEN + secbox := ST_SetSRID(ST_MakeBox2D(ST_Point(xmin,ymid),ST_Point(xmid,ymax)),4326); + END IF; + IF seg = 3 THEN + secbox := ST_SetSRID(ST_MakeBox2D(ST_Point(xmid,ymin),ST_Point(xmax,ymid)),4326); + END IF; + IF seg = 4 THEN + secbox := ST_SetSRID(ST_MakeBox2D(ST_Point(xmid,ymid),ST_Point(xmax,ymax)),4326); + END IF; + + IF st_intersects(geometry, secbox) THEN + secgeo := st_intersection(geometry, secbox); + IF NOT ST_IsEmpty(secgeo) AND ST_GeometryType(secgeo) in ('ST_Polygon','ST_MultiPolygon') THEN + FOR geo IN select quad_split_geometry(secgeo, maxarea, remainingdepth) as geom LOOP + IF NOT ST_IsEmpty(geo.geom) AND ST_GeometryType(geo.geom) in ('ST_Polygon','ST_MultiPolygon') THEN + added := added + 1; + RETURN NEXT geo.geom; + END IF; + END LOOP; + END IF; + END IF; + END LOOP; + + RETURN; +END; +$$ +LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION split_geometry(geometry GEOMETRY) + RETURNS SETOF GEOMETRY + AS $$ +DECLARE + geo RECORD; +BEGIN + -- 10000000000 is ~~ 1x1 degree + FOR geo IN select quad_split_geometry(geometry, 0.25, 20) as geom LOOP + RETURN NEXT geo.geom; + END LOOP; + RETURN; +END; +$$ +LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION place_force_delete(placeid BIGINT) RETURNS BOOLEAN + AS $$ +DECLARE + osmid BIGINT; + osmtype character(1); + pclass text; + ptype text; +BEGIN + SELECT osm_type, osm_id, class, type FROM placex WHERE place_id = placeid INTO osmtype, osmid, pclass, ptype; + DELETE FROM import_polygon_delete where osm_type = osmtype and osm_id = osmid and class = pclass and type = ptype; + DELETE FROM import_polygon_error where osm_type = osmtype and osm_id = osmid and class = pclass and type = ptype; + -- force delete from place/placex by making it a very small geometry + 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; + DELETE FROM place where osm_type = osmtype and osm_id = osmid and class = pclass and type = ptype; + + RETURN TRUE; +END; +$$ +LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION place_force_update(placeid BIGINT) RETURNS BOOLEAN + AS $$ +DECLARE + placegeom GEOMETRY; + geom GEOMETRY; + diameter FLOAT; + rank INTEGER; +BEGIN + SELECT geometry, rank_search FROM placex WHERE place_id = placeid INTO placegeom, rank; + IF placegeom IS NOT NULL AND ST_IsValid(placegeom) THEN + IF ST_GeometryType(placegeom) in ('ST_Polygon','ST_MultiPolygon') THEN + FOR geom IN select split_geometry(placegeom) FROM placex WHERE place_id = placeid LOOP + update placex set indexed_status = 2 where (st_covers(geom, placex.geometry) OR ST_Intersects(geom, placex.geometry)) + 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 addr_place is not null)); + update placex set indexed_status = 2 where (st_covers(geom, placex.geometry) OR ST_Intersects(geom, placex.geometry)) + 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 addr_place is not null)); + 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; + IF diameter > 0 THEN + IF rank >= 26 THEN + -- roads may cause reparenting for >27 rank places + update placex set indexed_status = 2 where indexed_status = 0 and rank_search > rank and ST_DWithin(placex.geometry, placegeom, diameter); + ELSEIF rank >= 16 THEN + -- up to rank 16, street-less addresses may need reparenting + 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 addr_place is not null); + ELSE + -- for all other places the search terms may change as well + 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); + END IF; + END IF; + END IF; + RETURN TRUE; + END IF; + RETURN FALSE; +END; +$$ +LANGUAGE plpgsql;