[postgis-tickets] r15336 - #3589, Orientation checking and forcing fuctions

Daniel Baston dbaston at gmail.com
Thu Mar 16 17:28:07 PDT 2017


Author: dbaston
Date: 2017-03-16 17:28:07 -0700 (Thu, 16 Mar 2017)
New Revision: 15336

Added:
   trunk/regress/orientation.sql
   trunk/regress/orientation_expected
Modified:
   trunk/NEWS
   trunk/doc/reference_accessor.xml
   trunk/doc/reference_editor.xml
   trunk/liblwgeom/cunit/cu_libgeom.c
   trunk/liblwgeom/liblwgeom.h.in
   trunk/liblwgeom/lwgeom.c
   trunk/liblwgeom/lwpoly.c
   trunk/liblwgeom/lwtriangle.c
   trunk/postgis/lwgeom_functions_analytic.c
   trunk/postgis/postgis.sql.in
   trunk/regress/Makefile.in
Log:
#3589, Orientation checking and forcing fuctions

Modified: trunk/NEWS
===================================================================
--- trunk/NEWS	2017-03-16 13:08:38 UTC (rev 15335)
+++ trunk/NEWS	2017-03-17 00:28:07 UTC (rev 15336)
@@ -5,6 +5,7 @@
 
   - #3599, Geobuf output support via ST_AsGeobuf (Björn Harrtell)
   - #3661, Mapbox vector tile output support via ST_AsMVT (Björn Harrtell / CartoDB)
+  - #3689, Add orientation checking and forcing functions (Dan Baston)
 
 PostGIS 2.3.0
 2016/09/26

Modified: trunk/doc/reference_accessor.xml
===================================================================
--- trunk/doc/reference_accessor.xml	2017-03-16 13:08:38 UTC (rev 15335)
+++ trunk/doc/reference_accessor.xml	2017-03-17 00:28:07 UTC (rev 15336)
@@ -872,6 +872,129 @@
 	  </refsection>
 	</refentry>
 
+	<refentry id="ST_IsPolygonCCW">
+		<refnamediv>
+			<refname>
+				ST_IsPolygonCCW
+			</refname>
+			<refpurpose>
+				Returns true if all exterior rings are oriented counter-clockwise and all interior rings are oriented clockwise.
+			</refpurpose>
+		</refnamediv>
+
+		<refsynopsisdiv>
+			<funcsynopsis>
+				<funcprototype>
+					<funcdef>
+						boolean
+						<function>ST_IsPolygonCCW</function>
+					</funcdef>
+					<paramdef>
+						<type>geometry</type>
+						<parameter>geom</parameter>
+					</paramdef>
+				</funcprototype>
+			</funcsynopsis>
+		</refsynopsisdiv>
+
+		<refsection>
+			<title>Description</title>
+
+			<para>
+				Returns true for (Multi)Polygons that use a counter-clockwise 
+				orientation for their exterior ring, and a clockwise direction 
+				for all interior rings.
+			</para>
+
+			<para>
+				Returns true for all non-polygonal geometries.
+			</para>
+
+			<note>
+				<para>
+					If a polygonal geometry does not use reversed orientation
+					for interior rings (i.e., if one or more interior rings
+					are oriented in the same direction as an exterior ring)
+					then both ST_IsPolygonCW and ST_IsPolygonCCW will return false.
+				</para>
+			</note>
+
+			<para>&Z_support;</para>
+			<para>&M_support;</para>
+
+		</refsection>
+
+		<refsection>
+			<title>See Also</title>
+			<para>
+				<xref linkend="ST_ForcePolygonCW" />,
+				<xref linkend="ST_ForcePolygonCCW" />,
+				<xref linkend="ST_IsPolygonCW" />
+			</para>
+		</refsection>
+	</refentry>
+
+	<refentry id="ST_IsPolygonCW">
+		<refnamediv>
+			<refname>
+				ST_IsPolygonCW
+			</refname>
+			<refpurpose>
+				Returns true if all exterior rings are oriented clockwise and all interior rings are oriented counter-clockwise.
+			</refpurpose>
+		</refnamediv>
+
+		<refsynopsisdiv>
+			<funcsynopsis>
+				<funcprototype>
+					<funcdef>
+						boolean
+						<function>ST_IsPolygonCW</function>
+					</funcdef>
+					<paramdef>
+						<type>geometry</type>
+						<parameter>geom</parameter>
+					</paramdef>
+				</funcprototype>
+			</funcsynopsis>
+		</refsynopsisdiv>
+
+		<refsection>
+			<title>Description</title>
+
+			<para>
+				Returns true for (Multi)Polygons that use a clockwise 
+				orientation for their exterior ring, and a counter-clockwise direction 
+				for all interior rings.
+			</para>
+
+			<para>
+				Returns true for all non-polygonal geometries.
+			</para>
+
+			<note>
+				<para>
+					If a polygonal geometry does not use reversed orientation
+					for interior rings (i.e., if one or more interior rings
+					are oriented in the same direction as an exterior ring)
+					then both ST_IsPolygonCW and ST_IsPolygonCCW will return false.
+				</para>
+			</note>
+
+			<para>&Z_support;</para>
+			<para>&M_support;</para>
+		</refsection>
+
+		<refsection>
+			<title>See Also</title>
+			<para>
+				<xref linkend="ST_ForcePolygonCW" />,
+				<xref linkend="ST_ForcePolygonCCW" />,
+				<xref linkend="ST_IsPolygonCW" />
+			</para>
+		</refsection>
+	</refentry>
+
 	<refentry id="ST_IsClosed">
 	  <refnamediv>
 		<refname>ST_IsClosed</refname>

Modified: trunk/doc/reference_editor.xml
===================================================================
--- trunk/doc/reference_editor.xml	2017-03-16 13:08:38 UTC (rev 15335)
+++ trunk/doc/reference_editor.xml	2017-03-17 00:28:07 UTC (rev 15336)
@@ -449,6 +449,54 @@
 	  </refsection>
 	</refentry>
 
+	<refentry id="ST_ForcePolygonCCW">
+		<refnamediv>
+			<refname>
+				ST_ForcePolygonCCW
+			</refname>
+			<refpurpose>
+				Orients all exterior rings counter-clockwise and all interior rings clockwise.
+			</refpurpose>
+		</refnamediv>
+
+		<refsynopsisdiv>
+			<funcsynopsis>
+				<funcprototype>
+					<funcdef>
+						geometry
+						<function>ST_ForcePolygonCCW</function>
+					</funcdef>
+					<paramdef>
+						<type>geometry</type>
+						<parameter>geom</parameter>
+					</paramdef>
+				</funcprototype>
+			</funcsynopsis>
+		</refsynopsisdiv>
+
+		<refsection>
+			<title>Description</title>
+
+			<para>
+				Forces (Multi)Polygons to use a counter-clockwise orientation for
+				their exterior ring, and a clockwise orientation for their interior
+				rings.  Non-polygonal geometries are returned unchanged.
+			</para>
+
+			<para>&Z_support;</para>
+			<para>&M_support;</para>
+		</refsection>
+
+		<refsection>
+			<title>See Also</title>
+			<para>
+				<xref linkend="ST_ForcePolygonCW" />,
+				<xref linkend="ST_IsPolygonCCW" />,
+				<xref linkend="ST_IsPolygonCW" />
+			</para>
+		</refsection>
+	</refentry>
+
 	<refentry id="ST_Force_Collection">
 	  <refnamediv>
 		<refname>ST_ForceCollection</refname>
@@ -531,7 +579,54 @@
 	  </refsection>
 	</refentry>
 
+	<refentry id="ST_ForcePolygonCW">
+		<refnamediv>
+			<refname>
+				ST_ForcePolygonCW
+			</refname>
+			<refpurpose>
+				Orients all exterior rings clockwise and all interior rings counter-clockwise.
+			</refpurpose>
+		</refnamediv>
 
+		<refsynopsisdiv>
+			<funcsynopsis>
+				<funcprototype>
+					<funcdef>
+						geometry
+						<function>ST_ForcePolygonCW</function>
+					</funcdef>
+					<paramdef>
+						<type>geometry</type>
+						<parameter>geom</parameter>
+					</paramdef>
+				</funcprototype>
+			</funcsynopsis>
+		</refsynopsisdiv>
+
+		<refsection>
+			<title>Description</title>
+
+			<para>
+				Forces (Multi)Polygons to use a clockwise orientation for
+				their exterior ring, and a counter-clockwise orientation for their interior
+				rings.  Non-polygonal geometries are returned unchanged.
+			</para>
+
+			<para>&Z_support;</para>
+			<para>&M_support;</para>
+		</refsection>
+
+		<refsection>
+			<title>See Also</title>
+			<para>
+				<xref linkend="ST_ForcePolygonCCW" />,
+				<xref linkend="ST_IsPolygonCCW" />,
+				<xref linkend="ST_IsPolygonCW" />
+			</para>
+		</refsection>
+	</refentry>
+
 	<refentry id="ST_ForceSFS">
 	  <refnamediv>
 		<refname>ST_ForceSFS</refname>
@@ -585,12 +680,18 @@
 		<refsection>
 			<title>Description</title>
 
-			<para>Forces the orientation of the vertices in a polygon to follow the
-				Right-Hand-Rule. In GIS terminology, this means that the area that is bounded by the
+			<para>Forces the orientation of the vertices in a polygon to follow a
+				Right-Hand-Rule, in which the area that is bounded by the
 				polygon is to the right of the boundary. In particular, the exterior ring is
 				orientated in a clockwise direction and the interior rings in a counter-clockwise
-				direction.</para>
+				direction.  This function is a synonym for <xref linkend="ST_ForcePolygonCW" /></para>
 
+			<note>
+				<para>
+					The above definition of the Right-Hand-Rule conflicts with definitions used in other contexts.  To avoid confusion, it is recommended to use ST_ForcePolygonCW.
+				</para>
+			</note>
+
 			<para>Enhanced: 2.0.0 support for Polyhedral surfaces was introduced.</para>
 			<para>&Z_support;</para>
 			<para>&P_support;</para>
@@ -613,7 +714,12 @@
 		<refsection>
 			<title>See Also</title>
 
-			<para><xref linkend="ST_BuildArea"/>,
+			<para>
+				<xref linkend="ST_ForcePolygonCCW"/>,
+				<xref linkend="ST_ForcePolygonCW"/>,
+				<xref linkend="ST_IsPolygonCCW"/>,
+				<xref linkend="ST_IsPolygonCW"/>,
+				<xref linkend="ST_BuildArea"/>,
 				<xref linkend="ST_Polygonize"/>,
 				<xref linkend="ST_Reverse"/></para>
 		</refsection>

Modified: trunk/liblwgeom/cunit/cu_libgeom.c
===================================================================
--- trunk/liblwgeom/cunit/cu_libgeom.c	2017-03-16 13:08:38 UTC (rev 15335)
+++ trunk/liblwgeom/cunit/cu_libgeom.c	2017-03-17 00:28:07 UTC (rev 15336)
@@ -741,6 +741,7 @@
 
 	/* counterclockwise, must be reversed */
 	geom = lwgeom_from_wkt("POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))", LW_PARSER_CHECK_NONE);
+	CU_ASSERT_FALSE(lwgeom_is_clockwise(geom));
 	lwgeom_force_clockwise(geom);
 	in_ewkt = "POLYGON((0 0,0 10,10 10,10 0,0 0))";
 	out_ewkt = lwgeom_to_ewkt(geom);
@@ -752,6 +753,7 @@
 
 	/* clockwise, fine as is */
 	geom = lwgeom_from_wkt("POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))", LW_PARSER_CHECK_NONE);
+	CU_ASSERT_TRUE(lwgeom_is_clockwise(geom));
 	lwgeom_force_clockwise(geom);
 	in_ewkt = "POLYGON((0 0,0 10,10 10,10 0,0 0))";
 	out_ewkt = lwgeom_to_ewkt(geom);
@@ -763,6 +765,7 @@
 
 	/* counterclockwise shell (must be reversed), mixed-wise holes */
 	geom = lwgeom_from_wkt("POLYGON((0 0,10 0,10 10,0 10,0 0),(2 2,2 4,4 2,2 2),(6 2,8 2,8 4,6 2))", LW_PARSER_CHECK_NONE);
+	CU_ASSERT_FALSE(lwgeom_is_clockwise(geom));
 	lwgeom_force_clockwise(geom);
 	in_ewkt = "POLYGON((0 0,0 10,10 10,10 0,0 0),(2 2,4 2,2 4,2 2),(6 2,8 2,8 4,6 2))";
 	out_ewkt = lwgeom_to_ewkt(geom);
@@ -774,6 +777,7 @@
 
 	/* clockwise shell (fine), mixed-wise holes */
 	geom = lwgeom_from_wkt("POLYGON((0 0,0 10,10 10,10 0,0 0),(2 2,4 2,2 4,2 2),(6 2,8 4,8 2,6 2))", LW_PARSER_CHECK_NONE);
+	CU_ASSERT_FALSE(lwgeom_is_clockwise(geom));
 	lwgeom_force_clockwise(geom);
 	in_ewkt = "POLYGON((0 0,0 10,10 10,10 0,0 0),(2 2,4 2,2 4,2 2),(6 2,8 2,8 4,6 2))";
 	out_ewkt = lwgeom_to_ewkt(geom);

Modified: trunk/liblwgeom/liblwgeom.h.in
===================================================================
--- trunk/liblwgeom/liblwgeom.h.in	2017-03-16 13:08:38 UTC (rev 15335)
+++ trunk/liblwgeom/liblwgeom.h.in	2017-03-17 00:28:07 UTC (rev 15336)
@@ -1218,6 +1218,9 @@
 extern void lwpoly_force_clockwise(LWPOLY *poly);
 extern void lwtriangle_force_clockwise(LWTRIANGLE *triangle);
 
+extern int lwgeom_is_clockwise(LWGEOM *lwgeom);
+extern int lwpoly_is_clockwise(LWPOLY *poly);
+extern int lwtriangle_is_clockwise(LWTRIANGLE *triangle);
 
 extern void interpolate_point4d(POINT4D *A, POINT4D *B, POINT4D *I, double F);
 

Modified: trunk/liblwgeom/lwgeom.c
===================================================================
--- trunk/liblwgeom/lwgeom.c	2017-03-16 13:08:38 UTC (rev 15335)
+++ trunk/liblwgeom/lwgeom.c	2017-03-17 00:28:07 UTC (rev 15336)
@@ -59,6 +59,35 @@
 	}
 }
 
+/** Check clockwise orientation on LWGEOM polygons **/
+int
+lwgeom_is_clockwise(LWGEOM *lwgeom)
+{
+	switch (lwgeom->type)
+	{
+		case POLYGONTYPE:
+			return lwpoly_is_clockwise((LWPOLY *)lwgeom);
+
+		case TRIANGLETYPE:
+			return lwtriangle_is_clockwise((LWTRIANGLE *)lwgeom);
+
+		case MULTIPOLYGONTYPE:
+		case COLLECTIONTYPE:
+		{
+			int i;
+			LWCOLLECTION* coll = (LWCOLLECTION *)lwgeom;
+
+			for (i=0; i < coll->ngeoms; i++)
+				if (!lwgeom_is_clockwise(coll->geoms[i]))
+					return LW_FALSE;
+			return LW_TRUE;
+		}
+		default:
+			return LW_TRUE;
+		return LW_FALSE;
+	}
+}
+
 /** Reverse vertex order of LWGEOM **/
 void
 lwgeom_reverse(LWGEOM *lwgeom)

Modified: trunk/liblwgeom/lwpoly.c
===================================================================
--- trunk/liblwgeom/lwpoly.c	2017-03-16 13:08:38 UTC (rev 15335)
+++ trunk/liblwgeom/lwpoly.c	2017-03-17 00:28:07 UTC (rev 15336)
@@ -284,6 +284,24 @@
 
 }
 
+int
+lwpoly_is_clockwise(LWPOLY *poly)
+{
+	int i;
+
+	if ( lwpoly_is_empty(poly) )
+		return LW_TRUE;
+
+	if ( ptarray_isccw(poly->rings[0]) )
+		return LW_FALSE;
+
+	for ( i = 1; i < poly->nrings; i++)
+		if ( !ptarray_isccw(poly->rings[i]) )
+			return LW_FALSE;
+
+	return LW_TRUE;
+}
+
 void
 lwpoly_release(LWPOLY *lwpoly)
 {

Modified: trunk/liblwgeom/lwtriangle.c
===================================================================
--- trunk/liblwgeom/lwtriangle.c	2017-03-16 13:08:38 UTC (rev 15335)
+++ trunk/liblwgeom/lwtriangle.c	2017-03-17 00:28:07 UTC (rev 15336)
@@ -109,6 +109,12 @@
 		ptarray_reverse(triangle->points);
 }
 
+int
+lwtriangle_is_clockwise(LWTRIANGLE *triangle)
+{
+	return !ptarray_isccw(triangle->points);
+}
+
 void
 lwtriangle_reverse(LWTRIANGLE *triangle)
 {

Modified: trunk/postgis/lwgeom_functions_analytic.c
===================================================================
--- trunk/postgis/lwgeom_functions_analytic.c	2017-03-16 13:08:38 UTC (rev 15335)
+++ trunk/postgis/lwgeom_functions_analytic.c	2017-03-17 00:28:07 UTC (rev 15336)
@@ -55,6 +55,8 @@
 Datum ST_MinimumBoundingRadius(PG_FUNCTION_ARGS);
 Datum ST_MinimumBoundingCircle(PG_FUNCTION_ARGS);
 Datum ST_GeometricMedian(PG_FUNCTION_ARGS);
+Datum ST_IsPolygonCCW(PG_FUNCTION_ARGS);
+Datum ST_IsPolygonCW(PG_FUNCTION_ARGS);
 
 
 static double determineSide(const POINT2D *seg1, const POINT2D *seg2, const POINT2D *point);
@@ -1282,3 +1284,57 @@
 	PG_RETURN_POINTER(result);
 }
 
+/**********************************************************************
+ *
+ * ST_IsPolygonCW
+ *
+ **********************************************************************/
+
+PG_FUNCTION_INFO_V1(ST_IsPolygonCW);
+Datum ST_IsPolygonCW(PG_FUNCTION_ARGS)
+{
+	GSERIALIZED* geom;
+	LWGEOM* input;
+	bool is_clockwise;
+
+	if (PG_ARGISNULL(0))
+		PG_RETURN_NULL();
+
+	geom = PG_GETARG_GSERIALIZED_P(0);
+	input = lwgeom_from_gserialized(geom);
+
+	is_clockwise = lwgeom_is_clockwise(input);
+
+	lwgeom_free(input);
+	PG_FREE_IF_COPY(geom, 0);
+
+	PG_RETURN_BOOL(is_clockwise);
+}
+
+/**********************************************************************
+ *
+ * ST_IsPolygonCCW
+ *
+ **********************************************************************/
+
+PG_FUNCTION_INFO_V1(ST_IsPolygonCCW);
+Datum ST_IsPolygonCCW(PG_FUNCTION_ARGS)
+{
+	GSERIALIZED* geom;
+	LWGEOM* input;
+	bool is_ccw;
+
+	if (PG_ARGISNULL(0))
+		PG_RETURN_NULL();
+
+	geom = PG_GETARG_GSERIALIZED_P_COPY(0);
+	input = lwgeom_from_gserialized(geom);
+
+    lwgeom_reverse(input);
+	is_ccw = lwgeom_is_clockwise(input);
+
+	lwgeom_free(input);
+	PG_FREE_IF_COPY(geom, 0);
+
+	PG_RETURN_BOOL(is_ccw);
+}

Modified: trunk/postgis/postgis.sql.in
===================================================================
--- trunk/postgis/postgis.sql.in	2017-03-16 13:08:38 UTC (rev 15335)
+++ trunk/postgis/postgis.sql.in	2017-03-17 00:28:07 UTC (rev 15336)
@@ -1235,6 +1235,20 @@
 	LANGUAGE 'c' IMMUTABLE STRICT _PARALLEL
 	COST 10;
 
+-- Availability: 2.4.0
+CREATE OR REPLACE FUNCTION ST_IsPolygonCW(geometry)
+	RETURNS boolean
+	AS 'MODULE_PATHNAME','ST_IsPolygonCW'
+	LANGUAGE 'c' IMMUTABLE STRICT
+	COST 10;
+
+-- Availability: 2.4.0
+CREATE OR REPLACE FUNCTION ST_IsPolygonCCW(geometry)
+	RETURNS boolean
+	AS 'MODULE_PATHNAME','ST_IsPolygonCCW'
+	LANGUAGE 'c' IMMUTABLE STRICT
+	COST 10;
+
 -- Availability: 2.0.0
 CREATE OR REPLACE FUNCTION ST_DistanceSpheroid(geom1 geometry, geom2 geometry,spheroid)
 	RETURNS FLOAT8
@@ -1467,6 +1481,20 @@
 	LANGUAGE 'c' IMMUTABLE STRICT _PARALLEL
 	COST 1; -- reset cost, see #3675
 
+-- Availability: 2.4.0
+CREATE OR REPLACE FUNCTION ST_ForcePolygonCW(geometry)
+	RETURNS geometry
+	AS 'MODULE_PATHNAME', 'LWGEOM_force_clockwise_poly'
+	LANGUAGE 'c' IMMUTABLE STRICT _PARALLEL
+	COST 15;
+
+-- Availability: 2.4.0
+CREATE OR REPLACE FUNCTION ST_ForcePolygonCCW(geometry)
+	RETURNS geometry
+	AS $$ SELECT @extschema at .ST_Reverse(@extschema at .ST_ForcePolygonCW($1)) $$
+	LANGUAGE SQL IMMUTABLE STRICT _PARALLEL
+	COST 15;
+
 -- Availability: 1.2.2
 CREATE OR REPLACE FUNCTION ST_ForceRHR(geometry)
 	RETURNS geometry

Modified: trunk/regress/Makefile.in
===================================================================
--- trunk/regress/Makefile.in	2017-03-16 13:08:38 UTC (rev 15335)
+++ trunk/regress/Makefile.in	2017-03-17 00:28:07 UTC (rev 15336)
@@ -102,6 +102,7 @@
 	minimum_bounding_circle \
 	normalize \
 	operators \
+	orientation \
 	out_geometry \
 	out_geography \
 	polygonize \

Added: trunk/regress/orientation.sql
===================================================================
--- trunk/regress/orientation.sql	                        (rev 0)
+++ trunk/regress/orientation.sql	2017-03-17 00:28:07 UTC (rev 15336)
@@ -0,0 +1,65 @@
+-- pg24
+
+-- Non-applicable types
+SELECT '1', ST_IsPolygonCCW('POINT (0 0)');
+SELECT '2', ST_IsPolygonCCW('MULTIPOINT ((0 0), (1 1))');
+SELECT '3', ST_IsPolygonCCW('LINESTRING (1 1, 2 2)');
+SELECT '4', ST_IsPolygonCCW('MULTILINESTRING ((1 1, 2 2), (3 3, 0 0))');
+SELECT '5', ST_IsPolygonCW('POINT (0 0)');
+SELECT '6', ST_IsPolygonCW('MULTIPOINT ((0 0), (1 1))');
+SELECT '7', ST_IsPolygonCW('LINESTRING (1 1, 2 2)');
+SELECT '8', ST_IsPolygonCW('MULTILINESTRING ((1 1, 2 2), (3 3, 0 0))');
+
+-- NULL handling
+SELECT '101', ST_IsPolygonCW(NULL::geometry);
+SELECT '102', ST_IsPolygonCCW(NULL::geometry);
+SELECT '103', ST_ForcePolygonCW(NULL::geometry);
+SELECT '104', ST_ForcePolygonCCW(NULL::geometry);
+
+-- EMPTY handling
+SELECT '201', ST_AsText(ST_ForcePolygonCW('POLYGON EMPTY'::geometry));
+SELECT '202', ST_AsText(ST_ForcePolygonCCW('POLYGON EMPTY'::geometry));
+SELECT '203', ST_IsPolygonCW('POLYGON EMPTY'::geometry);
+SELECT '204', ST_IsPolygonCCW('POLYGON EMPTY'::geometry);
+
+-- Preserves SRID
+SELECT '301', ST_SRID(ST_ForcePolygonCW('SRID=4269;POLYGON ((0 0, 1 1, 1 0, 0 0))'));
+SELECT '302', ST_SRID(ST_ForcePolygonCCW('SRID=4269;POLYGON ((0 0, 1 0, 1 1, 0 0))'));
+
+-- Single polygon, ccw exterior ring only
+SELECT '401', ST_IsPolygonCW('POLYGON ((0 0, 1 0, 1 1, 0 0))');
+SELECT '402', ST_IsPolygonCCW('POLYGON ((0 0, 1 0, 1 1, 0 0))');
+
+-- Single polygon, cw exterior ring only
+SELECT '403', ST_IsPolygonCW('POLYGON ((0 0, 1 1, 1 0, 0 0))');
+SELECT '404', ST_IsPolygonCCW('POLYGON ((0 0, 1 1, 1 0, 0 0))');
+
+-- Single polygon, ccw exterior ring, cw interior rings
+SELECT '405', ST_IsPolygonCW('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 1 1), (5 5, 5 7, 7 7, 5 5))');
+SELECT '406', ST_IsPolygonCCW('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 1 1), (5 5, 5 7, 7 7, 5 5))');
+
+-- Single polygon, cw exterior ring, ccw interior rings
+SELECT '407', ST_IsPolygonCW( 'POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (1 1, 2 2, 1 2, 1 1), (5 5, 7 7, 5 7, 5 5))');
+SELECT '408', ST_IsPolygonCCW('POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (1 1, 2 2, 1 2, 1 1), (5 5, 7 7, 5 7, 5 5))');
+
+-- Single polygon, ccw exerior ring, mixed interior rings
+SELECT '409', ST_IsPolygonCW( 'POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 1 1), (5 5, 7 7, 5 7, 5 5))');
+SELECT '410', ST_IsPolygonCCW('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 1 1), (5 5, 7 7, 5 7, 5 5))');
+
+-- Single polygon, cw exterior ring, mixed interior rings
+SELECT '411', ST_IsPolygonCW( 'POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (1 1, 2 2, 1 2, 1 1), (5 5, 5 7, 7 7, 5 5))');
+SELECT '412', ST_IsPolygonCCW('POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (1 1, 2 2, 1 2, 1 1), (5 5, 5 7, 7 7, 5 5))');
+
+-- MultiPolygon, ccw exterior rings only
+SELECT '413', ST_IsPolygonCW( 'MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((100 0, 101 0, 101 1, 100 0)))');
+SELECT '414', ST_IsPolygonCCW('MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((100 0, 101 0, 101 1, 100 0)))');
+
+-- MultiPolygon, cw exterior rings only
+SELECT '415', ST_IsPolygonCW( 'MULTIPOLYGON (((0 0, 1 1, 1 0, 0 0)), ((100 0, 101 1, 101 0, 100 0)))');
+SELECT '416', ST_IsPolygonCCW('MULTIPOLYGON (((0 0, 1 1, 1 0, 0 0)), ((100 0, 101 1, 101 0, 100 0)))');
+
+-- MultiPolygon, mixed exterior rings
+SELECT '417', ST_IsPolygonCW( 'MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((100 0, 101 1, 101 0, 100 0)))');
+SELECT '418', ST_IsPolygonCCW('MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((100 0, 101 1, 101 0, 100 0)))');
+
+

Added: trunk/regress/orientation_expected
===================================================================
--- trunk/regress/orientation_expected	                        (rev 0)
+++ trunk/regress/orientation_expected	2017-03-17 00:28:07 UTC (rev 15336)
@@ -0,0 +1,36 @@
+1|t
+2|t
+3|t
+4|t
+5|t
+6|t
+7|t
+8|t
+101|
+102|
+103|
+104|
+201|POLYGON EMPTY
+202|POLYGON EMPTY
+203|t
+204|t
+301|4269
+302|4269
+401|f
+402|t
+403|t
+404|f
+405|f
+406|t
+407|t
+408|f
+409|f
+410|f
+411|f
+412|f
+413|f
+414|t
+415|t
+416|f
+417|f
+418|f



More information about the postgis-tickets mailing list