[prev in list] [next in list] [prev in thread] [next in thread] 

List:       postgis-users
Subject:    Re: [postgis-users] Cutting a Polygon into Pieces with maximum
From:       Markus Schaber <schabi () logix-tt ! com>
Date:       2008-06-24 9:18:45
Message-ID: 20080624111845.4fe221f5 () kingfisher ! sec ! intern ! logix-tt ! com
[Download RAW message or body]

Hi, all,

I now have an basic implementation of this functionality in plpgsql,
which I post here for all those googlers who might have the same
problem.

I'm not shure whether it is in shape for committing, yet.


Regards,
Markus

-- 
Markus Schaber | Logical Tracking&Tracing International AG
Dipl. Inf.     | Software Development GIS

Fight against software patents in Europe! www.ffii.org
www.nosoftwarepatents.org

["prep_2_addPolySplitter.sql" (text/x-sql)]

--
-- Function and helpers to split a Polygon into multiple smaller polygons (fragments).
-- © 2008 Markus Schaber <schabi@logix-tt.com>
-- Licensed under GPL V2 or later.

-- Helper function to get a point from a linestring with wraparound at the end.
CREATE OR REPLACE FUNCTION pointNWrap(in_line geometry, -- LINESTRING 
					index int4) -- Index based on 1
RETURNS geometry -- POINT
LANGUAGE SQL
IMMUTABLE
AS $$
	SELECT PointN($1, ($2-1)%nPoints($1) + 1);
$$;

-- Helper function to get substring from a linestring with wraparound at the end.
-- This could be speed up with a native implementation like the pgis_GeometryRangeN()
-- discussed on the PostGIS list.
CREATE OR REPLACE FUNCTION splitLineStringWrap(in_line geometry, -- LINESTRING
						start int4, -- Indices based on 1 
						len int4) -- Number of points, will be wrapped / expanded.
RETURNS geometry -- LINESTRING
LANGUAGE SQL
IMMUTABLE
AS $$
SELECT makeLine(point) FROM (
	SELECT pointNWrap($1, generate_series($2, $2+$3-1)) AS point) 
AS foo;
$$;

-- This function splits a polygon into partial polygons which have at
-- maximum maxPoints points in their outer ring.
-- This algorithm does not guarantee the optimal result, it may deliver
-- more (and smaller) fragments than necessary.
-- Currently, all inner Rings are ignored (dropped), this could clearly
-- be enhanced in a future version. It is even possible to make an
-- implementation that guarantees to eliminate all inner Rings by
-- cutting appropriately.
-- The current implementation also contains some debug output which
-- was commented out for speed reasons.
CREATE OR REPLACE FUNCTION _splitPolyMaxVertex(in_poly geometry, maxPoints int4)
RETURNS SETOF geometry
LANGUAGE plpgsql
IMMUTABLE
AS $$
DECLARE
	currGap int4;
	currIndex int4;
	closedRing geometry;
	openRing geometry;
	restPoly geometry;
	
	cutStart geometry;
	cutEnd geometry;
	cutLine geometry;
	
	splitPart geometry;
	splitPoly geometry;
BEGIN
--	RAISE NOTICE ' +++ next polygon +++ ';
	IF geometryType(in_poly) != 'POLYGON' THEN
		RAISE WARNING 'Dropping bad geometry type %', geometryType(in_poly);
		return;
	ELSIF not isvalid(in_poly) THEN
		RAISE WARNING 'Dropping invalid geometry';
		return;
	ELSIF maxPoints <3 THEN
		RAISE EXCEPTION 'maxPoints=%, but must be 4 or greater!', maxPoints;
	ELSIF nPoints(exteriorRing(in_poly))<=maxPoints THEN
		-- polygon is small enough
		return next in_poly;
		return;
	ELSIF NumInteriorRings(in_poly)>0 THEN
		RAISE NOTICE 'Ignoring % inner rings', NumInteriorRings(in_poly);
	END IF;
	
	closedRing := exteriorring(in_poly);
	restPoly := makePolygon(closedRing);
	openRing := removePoint(closedRing, nPoints(closedRing)-1);

	currIndex := 1;
	currGap := maxPoints-1;
	
--	RAISE DEBUG 'Exterior ring before outer loop: % %', nPoints(closedRing), asText(closedRing);
	WHILE currGap > 0 LOOP
--		RAISE NOTICE ' --- next try --- ';
		cutStart := pointN(openRing, currIndex);
		cutEnd := pointNWrap(openRing, currIndex + currGap-1);		
		cutLine := makeLine(cutStart, cutEnd);
		
		IF contains(restPoly, cutLine) THEN
--			RAISE NOTICE ' --- next cut --- ';
			splitPart := splitLineStringWrap(openRing, currIndex, currGap);
			
--			IF startPoint(splitPart)!=cutStart THEN
--				RAISE WARNING 'cut start point broken. % %', asText(cutLine), asText(splitPart);
--			END IF;

--			IF endPoint(splitPart) != cutend THEN
--				RAISE WARNING 'cut end point broken. % %', asText(cutLine), asText(splitPart);
--			END IF;
			
			splitPoly := makePolygon(addPoint(splitPart, startPoint(splitPart)));
			
--			IF not isvalid(splitPoly) THEN
--				RAISE WARNING 'splitPoly invalid.';
--			END IF;
			return next splitPoly;
			
			openRing := splitLineStringWrap(openRing, currIndex+currGap-1, nPoints(openRing)-currGap+2);

--			IF startPoint(openRing)!=cutEnd THEN
--				RAISE WARNING 'rest start point broken. % %', asText(cutLine), asText(openRing);
--			END IF;
			
--			IF endPoint(openRing) != cutStart THEN
--				RAISE WARNING 'rest end point broken. % %', asText(cutLine), asText(openRing);
--			END IF;
			
			closedRing := addPoint(openRing, startPoint(openRing));
			restPoly := makePolygon(closedRing);
			
--			IF not isvalid(restpoly) THEN
--				RAISE WARNING 'restPoly invalid.';
--			END IF;

			IF nPoints(closedRing) <= maxPoints THEN
				return next restPoly;
				return;
			END IF;

			currIndex := 1;
			currGap := maxPoints -1; -- little slowdown, but potentially delivers better results.
		ELSIF currIndex = nPoints(openRing) THEN
			currIndex := 1;
			currGap := currGap-1;
		ELSE
			currIndex := currIndex + 1;					 
		END IF;
	END LOOP;
	RAISE EXCEPTION 'ASSERTION failed - could not cut polygon into triangles!';	      
END;
$$;

-- SQL language wrapper for _splitPolyMaxVertex.
-- Due to some historical artifacts in the plpgsql implementation, there are some
-- limitations in what contexts set returning functions can be called. Those do not
-- apply to SQL functions, so creating an SQL wrapper around the plpgsql function
-- solves the problem.
CREATE OR REPLACE FUNCTION splitPolyMaxVertex(in_poly geometry, maxPoints int4)
RETURNS SETOF geometry
LANGUAGE sql 
IMMUTABLE
AS $$
	SELECT * FROM _splitPolyMaxVertex($1, $2);
$$;


_______________________________________________
postgis-users mailing list
postgis-users@postgis.refractions.net
http://postgis.refractions.net/mailman/listinfo/postgis-users


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic