]> git.openstreetmap.org Git - nominatim.git/blobdiff - lib-sql/functions/utils.sql
split up MultiPolygons before adding them to large_areas table
[nominatim.git] / lib-sql / functions / utils.sql
index f8b365c582cce8cae569564bc9655335b8b69e1d..534beb58af6583183fa03a233256af70bc3f5181 100644 (file)
@@ -7,20 +7,37 @@
 
 -- Assorted helper functions for the triggers.
 
-CREATE OR REPLACE FUNCTION geometry_sector(partition INTEGER, place geometry)
-  RETURNS INTEGER
+CREATE OR REPLACE FUNCTION get_center_point(place GEOMETRY)
+  RETURNS GEOMETRY
   AS $$
 DECLARE
-  NEWgeometry geometry;
+  geom_type TEXT;
+BEGIN
+  geom_type := ST_GeometryType(place);
+  IF geom_type = ' ST_Point' THEN
+    RETURN place;
+  END IF;
+  IF geom_type = 'ST_LineString' THEN
+    RETURN ST_LineInterpolatePoint(place, 0.5);
+  END IF;
+
+  RETURN ST_PointOnSurface(place);
+END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+
+
+CREATE OR REPLACE FUNCTION geometry_sector(partition INTEGER, place GEOMETRY)
+  RETURNS INTEGER
+  AS $$
 BEGIN
---  RAISE WARNING '%',place;
-  NEWgeometry := ST_PointOnSurface(place);
-  RETURN (partition*1000000) + (500-ST_X(NEWgeometry)::integer)*1000 + (500-ST_Y(NEWgeometry)::integer);
+  RETURN (partition*1000000) + (500-ST_X(place)::INTEGER)*1000 + (500-ST_Y(place)::INTEGER);
 END;
 $$
 LANGUAGE plpgsql IMMUTABLE;
 
 
+
 CREATE OR REPLACE FUNCTION array_merge(a INTEGER[], b INTEGER[])
   RETURNS INTEGER[]
   AS $$
@@ -162,33 +179,56 @@ CREATE OR REPLACE FUNCTION get_country_code(place geometry)
   RETURNS TEXT
   AS $$
 DECLARE
-  place_centre GEOMETRY;
   nearcountry RECORD;
+  countries TEXT[];
 BEGIN
-  place_centre := ST_PointOnSurface(place);
-
--- RAISE WARNING 'get_country_code, start: %', ST_AsText(place_centre);
+-- RAISE WARNING 'get_country_code, start: %', ST_AsText(place);
 
   -- Try for a OSM polygon
-  FOR nearcountry IN
-    SELECT country_code from location_area_country
-    WHERE country_code is not null and st_covers(geometry, place_centre) limit 1
-  LOOP
-    RETURN nearcountry.country_code;
-  END LOOP;
+  SELECT array_agg(country_code) FROM location_area_country
+    WHERE country_code is not null and st_covers(geometry, place)
+    INTO countries;
+
+  IF array_length(countries, 1) = 1 THEN
+    RETURN countries[1];
+  END IF;
+
+  IF array_length(countries, 1) > 1 THEN
+    -- more than one country found, confirm against the fallback data what to choose
+    FOR nearcountry IN
+        SELECT country_code FROM country_osm_grid
+          WHERE ST_Covers(geometry, place) AND country_code = ANY(countries)
+          ORDER BY area ASC
+    LOOP
+        RETURN nearcountry.country_code;
+    END LOOP;
+    -- Still nothing? Choose the country code with the smallest partition number.
+    -- And failing that, just go by the alphabet.
+    FOR nearcountry IN
+        SELECT cc,
+               (SELECT partition FROM country_name WHERE country_code = cc) as partition
+        FROM unnest(countries) cc
+        ORDER BY partition, cc
+    LOOP
+        RETURN nearcountry.cc;
+    END LOOP;
+
+    -- Should never be reached.
+    RETURN countries[1];
+  END IF;
 
--- RAISE WARNING 'osm fallback: %', ST_AsText(place_centre);
+-- RAISE WARNING 'osm fallback: %', ST_AsText(place);
 
   -- 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
+    WHERE st_covers(geometry, place) order by area asc limit 1
   LOOP
     RETURN nearcountry.country_code;
   END LOOP;
 
--- RAISE WARNING 'near osm fallback: %', ST_AsText(place_centre);
+-- RAISE WARNING 'near osm fallback: %', ST_AsText(place);
 
   RETURN NULL;
 END;
@@ -308,8 +348,6 @@ CREATE OR REPLACE FUNCTION add_location(place_id BIGINT, country_code varchar(2)
   RETURNS BOOLEAN
   AS $$
 DECLARE
-  locationid INTEGER;
-  secgeo GEOMETRY;
   postcode TEXT;
 BEGIN
   PERFORM deleteLocationArea(partition, place_id, rank_search);
@@ -320,18 +358,19 @@ BEGIN
       postcode := upper(trim (in_postcode));
   END IF;
 
-  IF ST_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon') THEN
-    FOR secgeo IN select split_geometry(geometry) AS geom LOOP
-      PERFORM insertLocationAreaLarge(partition, place_id, country_code, keywords, rank_search, rank_address, false, postcode, centroid, secgeo);
-    END LOOP;
-
-  ELSEIF ST_GeometryType(geometry) = 'ST_Point' THEN
-    secgeo := place_node_fuzzy_area(geometry, rank_search);
-    PERFORM insertLocationAreaLarge(partition, place_id, country_code, keywords, rank_search, rank_address, true, postcode, centroid, secgeo);
+  IF ST_Dimension(geometry) = 2 THEN
+    RETURN insertLocationAreaLarge(partition, place_id, country_code, keywords,
+                                   rank_search, rank_address, false, postcode,
+                                   centroid, geometry);
+  END IF;
 
+  IF ST_Dimension(geometry) = 0 THEN
+    RETURN insertLocationAreaLarge(partition, place_id, country_code, keywords,
+                                   rank_search, rank_address, true, postcode,
+                                   centroid, place_node_fuzzy_area(geometry, rank_search));
   END IF;
 
-  RETURN true;
+  RETURN false;
 END;
 $$
 LANGUAGE plpgsql;
@@ -354,19 +393,21 @@ DECLARE
   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
+  IF not ST_IsValid(geometry) THEN
+    RETURN;
+  END IF;
+
+  IF ST_Dimension(geometry) != 2 OR maxdepth <= 1 THEN
     RETURN NEXT geometry;
     RETURN;
   END IF;
 
   remainingdepth := maxdepth - 1;
   area := ST_AREA(geometry);
-  IF remainingdepth < 1 OR area < maxarea THEN
+  IF area < maxarea THEN
     RETURN NEXT geometry;
     RETURN;
   END IF;
@@ -386,7 +427,6 @@ BEGIN
   xmid := (xmin+xmax)/2;
   ymid := (ymin+ymax)/2;
 
-  added := 0;
   FOR seg IN 1..4 LOOP
 
     IF seg = 1 THEN
@@ -402,16 +442,13 @@ BEGIN
       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;
+    secgeo := st_intersection(geometry, secbox);
+    IF NOT ST_IsEmpty(secgeo) AND ST_Dimension(secgeo) = 2 THEN
+      FOR geo IN SELECT quad_split_geometry(secgeo, maxarea, remainingdepth) as geom LOOP
+        IF NOT ST_IsEmpty(geo.geom) AND ST_Dimension(geo.geom) = 2 THEN
+          RETURN NEXT geo.geom;
+        END IF;
+      END LOOP;
     END IF;
   END LOOP;
 
@@ -427,10 +464,22 @@ CREATE OR REPLACE FUNCTION split_geometry(geometry GEOMETRY)
 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;
+  IF ST_GeometryType(geometry) = 'ST_MultiPolygon'
+     and ST_Area(geometry) * 10 > ST_Area(Box2D(geometry))
+  THEN
+    FOR geo IN
+        SELECT quad_split_geometry(g, 0.25, 20) as geom
+        FROM (SELECT (ST_Dump(geometry)).geom::geometry(Polygon, 4326) AS g) xx
+    LOOP
+      RETURN NEXT geo.geom;
+    END LOOP;
+  ELSE
+    FOR geo IN
+        SELECT quad_split_geometry(geometry, 0.25, 20) as geom
+    LOOP
+      RETURN NEXT geo.geom;
+    END LOOP;
+  END IF;
   RETURN;
 END;
 $$