[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