[postgis-users] Splitting large polygons into smaller polygons
    Josh Preston 
    Josh.Preston at rpdata.com
       
    Thu May 27 18:42:33 PDT 2010
    
    
  
Hi All,
 
I have a requirement to split large polygons into smaller pieces which
can't have any interior rings.
 
I started writing a plpgsql function for my solution with limited
spatial experience and less postgis experience and want some tips on how
it can be improved.
 
I pass the function two parameters, the geometry and the size I want the
smaller polygons to be. It then returns a setof geometries with the
results.
 
If the split of polygon has an rings in it, the function recursively
calls itself with pdp_size / 2 until all objects have no interior
rings..
 
The function works well with larger values in pdp_size (ie 1.0) but is
quite slow when using smaller values (ie 0.002).
 
To give you an idea of the data I am working with, it is Oceans around
the Australian mainland and Lakes/Rivers/Catchments in Australia.
Ocean's I split up using a size of 1.0 and Lakes/Rives/Catchments using
a size of 0.002.
 
My postgres/Postgis versions are
SELECT version(), postgis_full_version();
PostgreSQL 8.3.6 on x86_64-redhat-linux-gnu, compiled by GCC gcc (GCC)
4.1.2 20071124 (Red Hat 4.1.2-42),POSTGIS="1.3.5"
GEOS="2.2.3-CAPI-1.1.1" PROJ="Rel. 4.6.0, 21 Dec 2007" USE_STATS
 
Regards,
Josh
 
 
-- Function: principal_gis.denonut_primitive(geometry, double precision)
 
-- DROP FUNCTION principal_gis.denonut_primitive(geometry, double
precision);
 
CREATE OR REPLACE FUNCTION principal_gis.denonut_primitive(pgeom_obj
geometry, pdp_size double precision)
  RETURNS SETOF geometry AS
$BODY$
DECLARE
 
  vrec_ring record;
 
  -- Bounding box extents
  vdp_xmin double precision;
  vdp_xmax double precision;
  vdp_ymin double precision;
  vdp_ymax double precision;
 
  -- Cursors
  vdp_xpos double precision;
  vdp_ypos double precision;
 
  -- Stepping
  vdp_xstep double precision;
  vdp_ystep double precision;
 
  -- Variables to hold different versions of the geometry
  vgeom_orig geometry;
  vgeom_test geometry;
  vgeom_cut  geometry;
  vgeom_coll geometry;
  vgeom_dump geometry;
  
  -- Variable to hold SRID
  vi_srid integer;
 
BEGIN
 
  /******************************************************
    Do split
  ******************************************************/
  -- Get Bounding box extents
  vdp_xmin := ST_XMin(ST_Envelope(pgeom_obj));
  vdp_xmax := ST_XMax(ST_Envelope(pgeom_obj));
  vdp_ymin := ST_YMin(ST_Envelope(pgeom_obj));
  vdp_ymax := ST_YMax(ST_Envelope(pgeom_obj));
 
  vi_srid := ST_SRID(pgeom_obj);
 
  -- Setup xmin, xmax as xstep so they go east to west.
  IF vdp_xmax < 0 THEN
    vdp_xpos := vdp_xmax;
    vdp_xmax := vdp_xmin;
    vdp_xmin := vdp_xpos;
    vdp_xstep := 0 - pdp_size;
  ELSE
--    vdp_xpos := vdp_xmin;
    vdp_xstep := pdp_size;
  END IF;
  
  -- Setup ymin, ymax as ystep so they go north to south.
  IF vdp_ymax < 0 THEN
    vdp_ypos := vdp_ymax;
    vdp_ymax := vdp_ymin;
    vdp_ymin := vdp_ypos;
    vdp_ystep := 0 - pdp_size;
  ELSE 
--    vdp_ypos := vdp_ymin;
    vdp_ystep := pdp_size;
  END IF;
 
  -- Make Test Polgon for Intersection
  vgeom_orig := ST_GeomFromText('POLYGON((' || vdp_xmin            || '
' || vdp_ymin || ',' 
                                           || vdp_xmin + vdp_xstep || '
' || vdp_ymin || ',' 
                                           || vdp_xmin + vdp_xstep || '
' || vdp_ymin + vdp_ystep || ','
                                           || vdp_xmin             || '
' || vdp_ymin + vdp_ystep || ','
                                           || vdp_xmin             || '
' || vdp_ymin || '))', vi_srid);
 
  -- Check geometry 
  IF ST_Area(pgeom_obj) < (ST_Area(vgeom_orig) / 2) AND
     ST_Area(ST_Envelope(pgeom_obj)) < (ST_Area(ST_Envelope(vgeom_orig))
* 2) AND
     NumInteriorRings(pgeom_obj) = 0
   THEN
     -- Regardless of the shape, the object is small enough to pass.
     --   That is, the actual area of the geometry is less than half the
split area AND
     --   the bounding box of the geometry is not double the split area
AND
     --   the geometry has no inner rings
     RETURN NEXT pgeom_obj;
  ELSE
 
    -- While we have not gone past the original polygon (To the east)
    WHILE NOT pgeom_obj << vgeom_orig LOOP
 
      -- Initalise Test Polygon (Y axis)
      vgeom_test := vgeom_orig;
 
      --While we have not gone below the original polygon (To the south)
      WHILE NOT pgeom_obj |>> vgeom_test LOOP
 
        -- Get the intersection
        vgeom_cut := SetSRID(ST_Intersection(AsText(pgeom_obj),
SetSRID(vgeom_test,-1)), vi_srid);
/*
EXPLANATION OF ABOVE QUERY
 -- In our version of postgis (or more precisely GEOS), there is a bug
with precision of geometeries.
 -- The AsText function has some precision reduction which makes the
error go way. This bug has been
 -- fixed in later versions.
 
 -- Info at
 --
http://geos.refractions.net/pipermail/geos-devel/2005-May/001441.html
*/
 
        -- Did anything intersect?
        IF ST_IsEmpty(vgeom_cut) THEN
          -- No, do nothing
          NULL;
        ELSE
          -- Yes, check what turned out in the results
          IF GeometryType(vgeom_cut) = 'POLYGON' THEN
            -- Was there rings?
            IF NumInteriorRings(vgeom_cut) > 0 THEN
              -- Yes Rings, Dump each polygon through this function
again
              RETURN QUERY SELECT denonut_primitive FROM
principal_gis.denonut_primitive(vgeom_cut, pdp_size / 2);
            ELSE
              RETURN NEXT vgeom_cut;
            END IF;
          ELSE
            -- Not a polygon (linestring, point, multi*something* or
geometrycollection)
            FOR vgeom_dump IN SELECT geom FROM ST_Dump(vgeom_cut) LOOP
              IF ST_IsValid(vgeom_dump) THEN
                vgeom_coll := ST_BuildArea(vgeom_dump);
 
                IF NumInteriorRings(vgeom_coll) > 0 THEN
                  RETURN QUERY SELECT denonut_primitive FROM
principal_gis.denonut_primitive(vgeom_coll, pdp_size / 2);
                ELSE
                  RETURN NEXT vgeom_coll;
                END IF;
              ELSE
                -- Yes, skip it
                CONTINUE;
              END IF;
            END LOOP;
          END IF;
        END IF; /* ST_IsEmpty */
 
        -- Move the Test Polygon (Y Axis)
        vgeom_test := ST_Translate(vgeom_test, 0, vdp_ystep);
 
      END LOOP;
 
      -- Move the Test Polygon (X Axis)
      vgeom_orig := ST_Translate(vgeom_orig, vdp_xstep, 0);
 
    END LOOP;
  END IF;
 
  -- We are finished, return the result set now
  RETURN;
 
END;
$BODY$
  LANGUAGE 'plpgsql' STABLE STRICT
  COST 100
  ROWS 1000;
ALTER FUNCTION principal_gis.denonut_primitive(geometry, double
precision) OWNER TO postgres;
 
 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osgeo.org/pipermail/postgis-users/attachments/20100528/e0ae1f2b/attachment.html>
    
    
More information about the postgis-users
mailing list